PatchworkOS  c9fea19
A non-POSIX operating system.
Loading...
Searching...
No Matches
hpet.c
Go to the documentation of this file.
1#include <kernel/cpu/cpu.h>
2#include <kernel/log/log.h>
3#include <kernel/log/panic.h>
4#include <kernel/mem/vmm.h>
9#include <kernel/sched/wait.h>
10#include <kernel/sync/seqlock.h>
11#include <kernel/utils/utils.h>
12#include <modules/acpi/tables.h>
13
14/**
15 * @brief High Precision Event Timer
16 * @defgroup modules_drivers_hpet HPET
17 * @ingroup modules_drivers
18 *
19 * @note Since the HPET might be 32bit it could overflow rather quickly, so we implement a system for checking roughly
20 * when it will overflow and accumulate the counter into a 64 bit nanosecond counter.
21 *
22 * @see [OSDev HPET](https://wiki.osdev.org/HPET)
23 *
24 * @{
25 */
26
27/**
28 * @brief HPET register offsets
29 * @enum hpet_register_t
30 */
40
41/**
42 * @brief The bit offset of the clock period in the capabilities register
43 */
44#define HPET_CAP_COUNTER_CLK_PERIOD_SHIFT 32
45
46/**
47 * @brief The bit to set to enable the HPET in the configuration register
48 */
49#define HPET_CONF_ENABLE_CNF_BIT (1 << 0)
50
51/**
52 * @brief The bit to set to enable legacy replacement mode in the configuration register
53 */
54#define HPET_CONF_LEG_RT_CNF_BIT (1 << 1)
55
56/**
57 * @brief If `hpet_t::addressSpaceId` is equal to this, the address is in system memory space.
58 */
59#define HPET_ADDRESS_SPACE_MEMORY 0
60
61/**
62 * @brief If `hpet_t::addressSpaceId` is equal to this, the address is in system I/O space.
63 */
64#define HPET_ADDRESS_SPACE_IO 1
65
66/**
67 * @brief The number of femtoseconds in one second
68 */
69#define HPET_FEMTOSECONDS_PER_SECOND 1000000000000000ULL
70
71/**
72 * @brief High Precision Event Timer structure
73 * @struct hpet_t
74 */
93
94static hpet_t* hpet; ///< Pointer to the HPET ACPI table.
95static uintptr_t address; ///< Mapped virtual address of the HPET registers.
96static uint64_t period; ///< Main counter tick period in femtoseconds (10^-15 s).
97
98static atomic_uint64_t counter = ATOMIC_VAR_INIT(0); ///< Accumulated nanosecond counter, used to avoid overflows.
99static seqlock_t counterLock = SEQLOCK_CREATE(); ///< Seqlock for the accumulated counter.
100
101static tid_t overflowThreadTid = 0; ///< Thread ID of the overflow thread.
102static wait_queue_t overflowQueue = WAIT_QUEUE_CREATE(overflowQueue); ///< Wait queue for the overflow thread.
103static atomic_bool overflowShouldStop = ATOMIC_VAR_INIT(false); ///< Flag to signal the overflow thread to stop.
104
105/**
106 * @brief Write to an HPET register.
107 *
108 * @param reg The register to write to.
109 * @param value The value to write.
110 */
111static inline void hpet_write(hpet_register_t reg, uint64_t value)
112{
113 WRITE_64(address + reg, value);
114}
115
116/**
117 * @brief Read from an HPET register.
118 *
119 * @param reg The register to read from.
120 * @return The value read from the register.
121 */
123{
124 return READ_64(address + reg);
125}
126
127/**
128 * @brief Get the HPET clock period in nanoseconds.
129 *
130 * @return The HPET clock period in nanoseconds.
131 */
132static inline clock_t hpet_ns_per_tick(void)
133{
135}
136
137/**
138 * @brief Safely read the HPET counter value in nanoseconds.
139 *
140 * @return The current value of the HPET counter in nanoseconds.
141 */
143{
145 uint64_t seq;
146 do
147 {
150 } while (seqlock_read_retry(&counterLock, seq));
151 return time;
152}
153
154/**
155 * @brief Reset the HPET main counter to zero and enable the HPET.
156 */
163
164/**
165 * @brief Thread function that periodically accumulates the HPET counter to prevent overflow.
166 *
167 * @param arg Unused.
168 */
169static void hpet_overflow_thread(void* arg)
170{
171 (void)arg;
172
173 // Assume the worst case where the HPET is 32bit, since `clock_t` isent large enough to hold the time otherwise and
174 // i feel paranoid.
175 clock_t sleepInterval = (UINT32_MAX * hpet_ns_per_tick()) / 2;
176 LOG_INFO("HPET overflow thread started, sleep interval %lluns\n", sleepInterval);
177
179 {
180 WAIT_BLOCK_TIMEOUT(&overflowQueue, false, sleepInterval);
181
186 }
187}
188
189/**
190 * @brief Structure to describe the HPET to the sys time subsystem.
191 */
193 .name = "HPET",
194 .precision = 0, // Filled in during init
195 .read_ns = hpet_read_ns_counter,
196 .read_epoch = NULL,
197};
198
199/**
200 * @brief Initialize the HPET.
201 *
202 * @return On success, `0`. On failure, `ERR`.
203 */
204static uint64_t hpet_init(void)
205{
206 hpet = (hpet_t*)acpi_tables_lookup("HPET", sizeof(hpet_t), 0);
207 if (hpet == NULL)
208 {
209 LOG_ERR("failed to locate HPET table\n");
210 return ERR;
211 }
212
214 {
215 LOG_ERR("HPET address space is not memory (id=%u) which is not supported\n", hpet->addressSpaceId);
216 return ERR;
217 }
218
221 NULL) == NULL)
222 {
223 LOG_ERR("failed to map HPET memory at 0x%016lx\n", hpet->address);
224 return ERR;
225 }
226
229
230 if (period == 0 || period >= 0x05F5E100)
231 {
232 LOG_ERR("HPET reported an invalid counter period %llu fs\n", period);
233 return ERR;
234 }
235
236 LOG_INFO("started HPET timer phys=0x%016lx virt=0x%016lx period=%lluns timers=%u %s-bit\n", hpet->address, address,
238 hpet->counterIs64Bit ? "64" : "32");
239
241
244 {
245 LOG_ERR("failed to register HPET as system time source\n");
246 return ERR;
247 }
248
250 if (overflowThreadTid == ERR)
251 {
252 LOG_ERR("failed to create HPET overflow thread\n");
254 return ERR;
255 }
256
257 return 0;
258}
259
260/**
261 * @brief Deinitialize the HPET.
262 */
276
277/** @} */
278
280{
281 switch (event->type)
282 {
284 if (hpet_init() == ERR)
285 {
286 panic(NULL, "Failed to initialize HPET module");
287 }
288 break;
290 hpet_deinit();
291 break;
292 default:
293 break;
294 }
295
296 return 0;
297}
298
299MODULE_INFO("HPET Driver", "Kai Norberg", "A High Precision Event Timer driver", OS_VERSION, "MIT", "PNP0103");
#define CLOCKS_PER_SEC
Definition clock_t.h:15
#define PACKED
GCC packed attribute.
Definition defs.h:32
NORETURN void panic(const interrupt_frame_t *frame, const char *format,...)
Panic the kernel, printing a message and halting.
Definition panic.c:266
#define LOG_ERR(format,...)
Definition log.h:108
#define LOG_INFO(format,...)
Definition log.h:106
#define PML_LOWER_TO_HIGHER(addr)
Converts an address from the lower half to the higher half.
@ PML_PRESENT
@ PML_WRITE
@ PML_GLOBAL
void * vmm_map(space_t *space, void *virtAddr, void *physAddr, uint64_t length, pml_flags_t flags, space_callback_func_t func, void *private)
Maps physical memory to virtual memory in a given address space.
Definition vmm.c:226
#define MODULE_INFO(_name, _author, _description, _version, _licence, _deviceTypes)
Macro to define module information.
Definition module.h:200
@ MODULE_EVENT_DEVICE_ATTACH
Definition module.h:251
@ MODULE_EVENT_DEVICE_DETACH
Definition module.h:257
process_t * process_get_kernel(void)
Gets the kernel process.
Definition process.c:1031
bool process_has_thread(process_t *process, tid_t tid)
Checks if a process has a thread with the specified thread ID.
Definition process.c:1015
uint64_t clock_source_register(const clock_source_t *source)
Register a system timer source.
Definition clock.c:47
void clock_source_unregister(const clock_source_t *source)
Unregister a system timer source.
Definition clock.c:72
tid_t thread_kernel_create(thread_kernel_entry_t entry, void *arg)
Creates a new thread that runs in kernel mode and submits it to the scheduler.
Definition thread.c:94
uint64_t wait_unblock(wait_queue_t *queue, uint64_t amount, errno_t err)
Unblock threads waiting on a wait queue.
Definition wait.c:296
#define WAIT_ALL
Used to indicate that the wait should unblock all waiting threads.
Definition wait.h:40
#define WAIT_BLOCK_TIMEOUT(queue, condition, timeout)
Blocks until the condition is true, condition will be tested on every wakeup. Reaching the timeout wi...
Definition wait.h:72
#define WAIT_QUEUE_CREATE(name)
Create a wait queue initializer.
Definition wait.h:219
void sched_yield(void)
Yield the current thread's time slice to allow other threads to run.
Definition sched.c:643
static uint64_t seqlock_read_begin(seqlock_t *seqlock)
Begins a read operation on a sequence lock.
Definition seqlock.h:94
static void seqlock_write_release(seqlock_t *seqlock)
Releases the write lock of a sequence lock.
Definition seqlock.h:71
static bool seqlock_read_retry(seqlock_t *seqlock, uint64_t seq)
Checks if a read operation on a sequence lock needs to be retried.
Definition seqlock.h:106
#define SEQLOCK_CREATE()
Create a sequence lock initializer.
Definition seqlock.h:40
static void seqlock_write_acquire(seqlock_t *seqlock)
Acquires the write lock of a sequence lock.
Definition seqlock.h:60
#define EOK
No error.
Definition errno.h:32
#define PAGE_SIZE
The size of a memory page in bytes.
Definition proc.h:106
#define NULL
Pointer error value.
Definition NULL.h:23
#define ERR
Integer error value.
Definition ERR.h:17
__UINT64_TYPE__ tid_t
Thread Identifier.
Definition tid_t.h:12
__UINT64_TYPE__ clock_t
A nanosecond time.
Definition clock_t.h:13
sdt_header_t * acpi_tables_lookup(const char *signature, uint64_t minSize, uint64_t n)
Lookup the n'th table matching the signature.
Definition tables.c:260
static uint64_t hpet_init(void)
Initialize the HPET.
Definition hpet.c:204
static clock_t hpet_read_ns_counter(void)
Safely read the HPET counter value in nanoseconds.
Definition hpet.c:142
#define HPET_CONF_ENABLE_CNF_BIT
The bit to set to enable the HPET in the configuration register.
Definition hpet.c:49
static atomic_uint64_t counter
Accumulated nanosecond counter, used to avoid overflows.
Definition hpet.c:98
static uint64_t period
Main counter tick period in femtoseconds (10^-15 s).
Definition hpet.c:96
static void hpet_write(hpet_register_t reg, uint64_t value)
Write to an HPET register.
Definition hpet.c:111
hpet_register_t
HPET register offsets.
Definition hpet.c:32
static hpet_t * hpet
Pointer to the HPET ACPI table.
Definition hpet.c:94
static wait_queue_t overflowQueue
Wait queue for the overflow thread.
Definition hpet.c:102
#define HPET_FEMTOSECONDS_PER_SECOND
The number of femtoseconds in one second.
Definition hpet.c:69
#define HPET_CAP_COUNTER_CLK_PERIOD_SHIFT
The bit offset of the clock period in the capabilities register.
Definition hpet.c:44
static atomic_bool overflowShouldStop
Flag to signal the overflow thread to stop.
Definition hpet.c:103
static clock_t hpet_ns_per_tick(void)
Get the HPET clock period in nanoseconds.
Definition hpet.c:132
static void hpet_overflow_thread(void *arg)
Thread function that periodically accumulates the HPET counter to prevent overflow.
Definition hpet.c:169
static void hpet_reset_counter(void)
Reset the HPET main counter to zero and enable the HPET.
Definition hpet.c:157
static clock_source_t source
Structure to describe the HPET to the sys time subsystem.
Definition hpet.c:192
static void hpet_deinit(void)
Deinitialize the HPET.
Definition hpet.c:263
static tid_t overflowThreadTid
Thread ID of the overflow thread.
Definition hpet.c:101
static uint64_t hpet_read(hpet_register_t reg)
Read from an HPET register.
Definition hpet.c:122
static seqlock_t counterLock
Seqlock for the accumulated counter.
Definition hpet.c:99
static uintptr_t address
Mapped virtual address of the HPET registers.
Definition hpet.c:95
#define HPET_ADDRESS_SPACE_MEMORY
If hpet_t::addressSpaceId is equal to this, the address is in system memory space.
Definition hpet.c:59
@ HPET_REG_GENERAL_CONFIG
Definition hpet.c:34
@ HPET_REG_GENERAL_CAPABILITIES_ID
Definition hpet.c:33
@ HPET_REG_MAIN_COUNTER_VALUE
Definition hpet.c:36
@ HPET_REG_GENERAL_INTERRUPT
Definition hpet.c:35
@ HPET_REG_TIMER0_COMPARATOR
Definition hpet.c:38
@ HPET_REG_TIMER0_CONFIG_CAP
Definition hpet.c:37
uint64_t _module_procedure(const module_event_t *event)
Definition hpet.c:279
#define atomic_store(object, desired)
Definition stdatomic.h:289
#define atomic_load(object)
Definition stdatomic.h:288
#define ATOMIC_VAR_INIT(value)
Definition stdatomic.h:74
#define atomic_fetch_add(object, operand)
Definition stdatomic.h:283
__UINT64_TYPE__ uint64_t
Definition stdint.h:17
__UINT8_TYPE__ uint8_t
Definition stdint.h:11
__UINTPTR_TYPE__ uintptr_t
Definition stdint.h:43
__UINT16_TYPE__ uint16_t
Definition stdint.h:13
#define UINT32_MAX
Definition stdint.h:70
Clock source structure.
Definition clock.h:33
const char * name
Definition clock.h:34
clock_t precision
Definition clock.h:35
High Precision Event Timer structure.
Definition hpet.c:76
uint8_t hardwareRevId
Definition hpet.c:78
uint8_t legacyReplacementCapable
Definition hpet.c:82
uint8_t reserved1
Definition hpet.c:81
uint16_t pciVendorId
Definition hpet.c:83
uint8_t registerBitWidth
Definition hpet.c:85
uint64_t address
Definition hpet.c:88
uint8_t hpetNumber
Definition hpet.c:89
uint8_t addressSpaceId
Definition hpet.c:84
sdt_header_t header
Definition hpet.c:77
uint8_t counterIs64Bit
Definition hpet.c:80
uint8_t pageProtection
Definition hpet.c:91
uint16_t minimumTick
Definition hpet.c:90
uint8_t registerBitOffset
Definition hpet.c:86
uint8_t comparatorCount
Definition hpet.c:79
uint8_t reserved2
Definition hpet.c:87
module_event_type_t type
Definition module.h:268
System Description Table Header.
Definition acpi.h:90
Sequence lock structure.
Definition seqlock.h:30
The primitive that threads block on.
Definition wait.h:182
_PUBLIC time_t time(time_t *timer)
Definition time.c:5
#define WRITE_64(address, value)
Definition utils.h:19
#define READ_64(address)
Definition utils.h:18