Kernel: Add basic HPET support to replace PIT if exists
This works same way as the PIT implementation; calls Scheduler every milli second.
This commit is contained in:
		
							parent
							
								
									d4adcff958
								
							
						
					
					
						commit
						fdae253695
					
				|  | @ -56,6 +56,7 @@ set(KERNEL_SOURCES | |||
| 	kernel/Terminal/TTY.cpp | ||||
| 	kernel/Terminal/VesaTerminalDriver.cpp | ||||
| 	kernel/Thread.cpp | ||||
| 	kernel/Timer/HPET.cpp | ||||
| 	kernel/Timer/PIT.cpp | ||||
| 	kernel/Timer/RTC.cpp | ||||
| 	kernel/Timer/Timer.cpp | ||||
|  |  | |||
|  | @ -0,0 +1,27 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <kernel/Timer/Timer.h> | ||||
| 
 | ||||
| namespace Kernel | ||||
| { | ||||
| 
 | ||||
| 	class HPET final : public Timer | ||||
| 	{ | ||||
| 	public: | ||||
| 		static BAN::ErrorOr<BAN::UniqPtr<HPET>> create(); | ||||
| 
 | ||||
| 		virtual uint64_t ms_since_boot() const override; | ||||
| 
 | ||||
| 	private: | ||||
| 		HPET() = default; | ||||
| 		BAN::ErrorOr<void> initialize(); | ||||
| 
 | ||||
| 		void write_register(ptrdiff_t reg, uint64_t value) const; | ||||
| 		uint64_t read_register(ptrdiff_t reg) const; | ||||
| 
 | ||||
| 	private: | ||||
| 		uint64_t m_counter_tick_period_fs { 0 }; | ||||
| 		vaddr_t m_mmio_base { 0 }; | ||||
| 	}; | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,113 @@ | |||
| #include <BAN/ScopeGuard.h> | ||||
| #include <kernel/ACPI.h> | ||||
| #include <kernel/IDT.h> | ||||
| #include <kernel/InterruptController.h> | ||||
| #include <kernel/Memory/PageTable.h> | ||||
| #include <kernel/MMIO.h> | ||||
| #include <kernel/Scheduler.h> | ||||
| #include <kernel/Timer/HPET.h> | ||||
| 
 | ||||
| #define HPET_REG_CAPABILIES	0x00 | ||||
| #define HPET_REG_CONFIG		0x10 | ||||
| #define HPET_REG_COUNTER	0xF0 | ||||
| 
 | ||||
| #define HPET_CONFIG_ENABLE	0x01 | ||||
| #define HPET_CONFIG_LEG_RT	0x02 | ||||
| 
 | ||||
| #define HPET_REG_TIMER_CONFIG(N)		(0x100 + 0x20 * N) | ||||
| #define HPET_REG_TIMER_COMPARATOR(N)	(0x108 + 0x20 * N) | ||||
| 
 | ||||
| #define HPET_Tn_INT_ENB_CNF	(1 << 2) | ||||
| #define HPET_Tn_TYPE_CNF	(1 << 3) | ||||
| #define HPET_Tn_PER_INT_CAP	(1 << 4) | ||||
| #define HPET_Tn_VAL_SET_CNF	(1 << 6) | ||||
| 
 | ||||
| #define FS_PER_MS 1'000'000'000'000 | ||||
| 
 | ||||
| namespace Kernel | ||||
| { | ||||
| 
 | ||||
| 	static uint64_t s_system_time = 0; | ||||
| 
 | ||||
| 	BAN::ErrorOr<BAN::UniqPtr<HPET>> HPET::create() | ||||
| 	{ | ||||
| 		HPET* hpet = new HPET(); | ||||
| 		if (hpet == nullptr) | ||||
| 			return BAN::Error::from_errno(ENOMEM); | ||||
| 		if (auto ret = hpet->initialize(); ret.is_error()) | ||||
| 		{ | ||||
| 			delete hpet; | ||||
| 			return ret.release_error(); | ||||
| 		} | ||||
| 		return BAN::UniqPtr<HPET>::adopt(hpet); | ||||
| 	} | ||||
| 
 | ||||
| 	BAN::ErrorOr<void> HPET::initialize() | ||||
| 	{ | ||||
| 		auto* header = (ACPI::HPET*)ACPI::get().get_header("HPET"); | ||||
| 		if (header == nullptr) | ||||
| 			return BAN::Error::from_errno(ENODEV); | ||||
| 
 | ||||
| 		if (header->hardware_rev_id == 0) | ||||
| 			return BAN::Error::from_errno(EINVAL); | ||||
| 
 | ||||
| 		if (!header->legacy_replacement_irq_routing_cable) | ||||
| 		{ | ||||
| 			dwarnln("HPET doesn't support legacy mapping"); | ||||
| 			return BAN::Error::from_errno(ENOTSUP); | ||||
| 		} | ||||
| 
 | ||||
| 		m_mmio_base = PageTable::kernel().reserve_free_page(KERNEL_OFFSET); | ||||
| 		ASSERT(m_mmio_base); | ||||
| 
 | ||||
| 		PageTable::kernel().map_page_at(header->base_address.address, m_mmio_base, PageTable::Flags::ReadWrite | PageTable::Flags::Present); | ||||
| 		BAN::ScopeGuard unmapper([this] { PageTable::kernel().unmap_page(m_mmio_base); }); | ||||
| 
 | ||||
| 		m_counter_tick_period_fs = read_register(HPET_REG_CAPABILIES) >> 32; | ||||
| 
 | ||||
| 		uint64_t ticks_per_ms = FS_PER_MS / m_counter_tick_period_fs; | ||||
| 
 | ||||
| 		uint64_t timer0_config = read_register(HPET_REG_TIMER_CONFIG(0)); | ||||
| 		if (!(timer0_config & HPET_Tn_PER_INT_CAP)) | ||||
| 		{ | ||||
| 			dwarnln("timer 0 doesn't support periodic"); | ||||
| 			return BAN::Error::from_errno(ENOTSUP); | ||||
| 		} | ||||
| 
 | ||||
| 		unmapper.disable(); | ||||
| 
 | ||||
| 		// Enable main counter
 | ||||
| 		write_register(HPET_REG_CONFIG, read_register(HPET_REG_CONFIG) | HPET_CONFIG_LEG_RT | HPET_CONFIG_ENABLE); | ||||
| 
 | ||||
| 		// Enable timer 0 as 1 ms periodic
 | ||||
| 		write_register(HPET_REG_TIMER_CONFIG(0), HPET_Tn_INT_ENB_CNF | HPET_Tn_TYPE_CNF | HPET_Tn_VAL_SET_CNF); | ||||
| 		write_register(HPET_REG_TIMER_COMPARATOR(0), read_register(HPET_REG_COUNTER) + ticks_per_ms); | ||||
| 		write_register(HPET_REG_TIMER_COMPARATOR(0), ticks_per_ms); | ||||
| 
 | ||||
| 		// Disable timers 1->
 | ||||
| 		for (int i = 1; i <= header->comparator_count; i++) | ||||
| 			write_register(HPET_REG_TIMER_CONFIG(i), 0); | ||||
| 
 | ||||
| 		IDT::register_irq_handler(0, [] { s_system_time++; Scheduler::get().timer_reschedule(); }); | ||||
| 		InterruptController::get().enable_irq(0); | ||||
| 
 | ||||
| 		return {}; | ||||
| 	} | ||||
| 
 | ||||
| 	uint64_t HPET::ms_since_boot() const | ||||
| 	{ | ||||
| 		// FIXME: 32 bit CPUs should use 32 bit counter with 32 bit reads
 | ||||
| 		return read_register(HPET_REG_COUNTER) * m_counter_tick_period_fs / FS_PER_MS; | ||||
| 	} | ||||
| 
 | ||||
| 	void HPET::write_register(ptrdiff_t reg, uint64_t value) const | ||||
| 	{ | ||||
| 		MMIO::write64(m_mmio_base + reg, value); | ||||
| 	} | ||||
| 
 | ||||
| 	uint64_t HPET::read_register(ptrdiff_t reg) const | ||||
| 	{ | ||||
| 		return MMIO::read64(m_mmio_base + reg); | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  | @ -1,4 +1,5 @@ | |||
| #include <kernel/Scheduler.h> | ||||
| #include <kernel/Timer/HPET.h> | ||||
| #include <kernel/Timer/PIT.h> | ||||
| #include <kernel/Timer/Timer.h> | ||||
| 
 | ||||
|  | @ -32,6 +33,15 @@ namespace Kernel | |||
| 		m_rtc = MUST(BAN::UniqPtr<RTC>::create()); | ||||
| 		m_boot_time = BAN::to_unix_time(m_rtc->get_current_time()); | ||||
| 
 | ||||
| 		if (auto res = HPET::create(); res.is_error()) | ||||
| 			dwarnln("HPET: {}", res.error()); | ||||
| 		else | ||||
| 		{ | ||||
| 			m_timer = res.release_value(); | ||||
| 			dprintln("HPET initialized"); | ||||
| 			return; | ||||
| 		} | ||||
| 
 | ||||
| 		if (auto res = PIT::create(); res.is_error()) | ||||
| 			dwarnln("PIT: {}", res.error()); | ||||
| 		else | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue