| 1 | #include <acpi/hpet.h> |
| 2 | #include <asm.h> |
| 3 | #include <console/printf.h> |
| 4 | #include <drivers/mmio.h> |
| 5 | #include <log.h> |
| 6 | #include <math/bit.h> |
| 7 | #include <mem/page.h> |
| 8 | #include <mem/vmm.h> |
| 9 | #include <smp/core.h> |
| 10 | #include <uacpi/tables.h> |
| 11 | |
| 12 | #include "uacpi/acpi.h" |
| 13 | #include "uacpi/status.h" |
| 14 | |
| 15 | LOG_HANDLE_DEFINE_PRINT(hpet); |
| 16 | |
| 17 | uint64_t cc_mem_io *hpet_base; |
| 18 | uint64_t hpet_timer_count; |
| 19 | uint64_t hpet_fs_per_tick; |
| 20 | |
| 21 | void hpet_enable(void) { |
| 22 | uint64_t conf = hpet_read64(HPET_GEN_CONF_OFFSET); |
| 23 | conf |= 1; // enable bit |
| 24 | hpet_write64(HPET_GEN_CONF_OFFSET, value: conf); |
| 25 | } |
| 26 | |
| 27 | void hpet_disable(void) { |
| 28 | uint64_t conf = hpet_read64(HPET_GEN_CONF_OFFSET); |
| 29 | conf &= ~1ULL; |
| 30 | hpet_write64(HPET_GEN_CONF_OFFSET, value: conf); |
| 31 | } |
| 32 | |
| 33 | void hpet_clear_interrupt_status(void) { |
| 34 | hpet_write64(HPET_GEN_INT_STAT_OFFSET, value: 1); |
| 35 | } |
| 36 | |
| 37 | static inline uint64_t hpet_us_to_ticks(uint64_t us) { |
| 38 | return (us * 1000000000ULL) / hpet_fs_per_tick; |
| 39 | } |
| 40 | |
| 41 | void hpet_setup_periodic_interrupt_us(uint64_t microseconds_period) { |
| 42 | hpet_disable(); |
| 43 | |
| 44 | uint64_t ticks = hpet_us_to_ticks(us: microseconds_period); |
| 45 | |
| 46 | hpet_write64(HPET_MAIN_COUNTER_OFFSET, HPET_CURRENT); |
| 47 | union hpet_timer_config timer_cfg; |
| 48 | timer_cfg.raw = hpet_read64(HPET_TIMER_CONF_OFFSET(HPET_CURRENT)); |
| 49 | timer_cfg.interrupt_enable = 1; |
| 50 | timer_cfg.interrupt_type = 1; |
| 51 | timer_cfg.type = 1; // periodic |
| 52 | |
| 53 | hpet_write64(HPET_TIMER_CONF_OFFSET(HPET_CURRENT), value: timer_cfg.raw); |
| 54 | hpet_write64(HPET_TIMER_COMPARATOR_OFFSET(HPET_CURRENT), value: ticks); |
| 55 | hpet_enable(); |
| 56 | } |
| 57 | |
| 58 | void hpet_program_oneshot(uint64_t future_ms) { |
| 59 | |
| 60 | union hpet_timer_config conf; |
| 61 | conf.raw = hpet_read64(HPET_TIMER_CONF_OFFSET(HPET_CURRENT)); |
| 62 | conf.type = 0; |
| 63 | conf.interrupt_type = 0; |
| 64 | conf.interrupt_enable = 1; |
| 65 | hpet_disable(); |
| 66 | hpet_write64(HPET_TIMER_CONF_OFFSET(HPET_CURRENT), value: conf.raw); |
| 67 | |
| 68 | uint64_t now = hpet_timestamp_ms(); |
| 69 | uint64_t delta_us = (future_ms > now) ? (future_ms - now) * 1000 : 1000; |
| 70 | uint64_t ticks = hpet_us_to_ticks(us: delta_us); |
| 71 | |
| 72 | uint64_t fire_time = hpet_read64(HPET_MAIN_COUNTER_OFFSET) + ticks; |
| 73 | |
| 74 | hpet_write64(HPET_TIMER_COMPARATOR_OFFSET(HPET_CURRENT), value: fire_time); |
| 75 | hpet_enable(); |
| 76 | } |
| 77 | |
| 78 | static inline void pit_disable(void) { |
| 79 | outb(port: 0x43, value: 0x38); |
| 80 | outb(port: 0x40, value: 0xFF); |
| 81 | outb(port: 0x40, value: 0xFF); |
| 82 | } |
| 83 | |
| 84 | void hpet_setup_timer(uint8_t timer_index, irq_t irq_line, bool periodic, |
| 85 | bool edge_triggered) { |
| 86 | uint64_t cfg_addr = HPET_TIMER_CONF_OFFSET(timer_index); |
| 87 | |
| 88 | union hpet_timer_config cfg; |
| 89 | uint64_t old_config = hpet_read64(offset: cfg_addr); |
| 90 | cfg.raw = hpet_read64(offset: cfg_addr); |
| 91 | |
| 92 | cfg.ioapic_route = 0; |
| 93 | cfg.ioapic_route = irq_line; |
| 94 | |
| 95 | hpet_write64(offset: cfg_addr, value: cfg.raw); |
| 96 | |
| 97 | union hpet_timer_config confirmed_config; |
| 98 | confirmed_config.raw = hpet_read64(offset: cfg_addr); |
| 99 | uint8_t confirmed_irq = confirmed_config.ioapic_route; |
| 100 | |
| 101 | if (confirmed_irq != irq_line) { |
| 102 | log_warn_global(LOG_HANDLE(hpet), |
| 103 | "Timer %u: IRQ %u not accepted (got %u)" , timer_index, |
| 104 | irq_line, confirmed_irq); |
| 105 | hpet_write64(offset: cfg_addr, value: old_config); |
| 106 | return; |
| 107 | } |
| 108 | |
| 109 | cfg.ioapic_route = 0; |
| 110 | cfg.ioapic_route = irq_line; |
| 111 | |
| 112 | cfg.interrupt_enable = true; |
| 113 | |
| 114 | if (periodic) { |
| 115 | cfg.type = 1; |
| 116 | } else { |
| 117 | cfg.type = 0; |
| 118 | } |
| 119 | |
| 120 | if (edge_triggered) { |
| 121 | cfg.interrupt_type = 0; |
| 122 | } else { |
| 123 | cfg.interrupt_type = 1; |
| 124 | } |
| 125 | |
| 126 | hpet_write64(offset: cfg_addr, value: cfg.raw); |
| 127 | } |
| 128 | |
| 129 | void hpet_init(void) { |
| 130 | struct uacpi_table hpet_table; |
| 131 | if (uacpi_table_find_by_signature(signature: "HPET" , out_table: &hpet_table) != UACPI_STATUS_OK) { |
| 132 | log_global(LOG_HANDLE(hpet), LOG_ERROR, "Did not find HPET ACPI entry" ); |
| 133 | } |
| 134 | |
| 135 | struct acpi_hpet *hpet = hpet_table.ptr; |
| 136 | uint64_t hpet_addr = hpet->address.address; |
| 137 | |
| 138 | hpet_base = mmio_map(phys: hpet_addr, PAGE_SIZE); |
| 139 | |
| 140 | hpet_disable(); |
| 141 | hpet_write64(HPET_MAIN_COUNTER_OFFSET, value: 0); |
| 142 | uint64_t config = hpet_read64(HPET_GEN_CONF_OFFSET); |
| 143 | config = BIT_CLEAR(config, 1); // legacy replacement mode off |
| 144 | hpet_write64(HPET_GEN_CONF_OFFSET, value: config); |
| 145 | |
| 146 | union hpet_timer_general_capabilities cap = {0}; |
| 147 | cap.raw = hpet_read64(HPET_GEN_CAP_ID_OFFSET); |
| 148 | hpet_timer_count = cap.num_timers; |
| 149 | hpet_fs_per_tick = hpet_read64(HPET_GEN_CAP_ID_OFFSET) >> 32; |
| 150 | |
| 151 | pit_disable(); |
| 152 | hpet_enable(); |
| 153 | hpet_timestamp_us(); |
| 154 | |
| 155 | log_info_global(LOG_HANDLE(hpet), "HPET initialized - %lu timers" , |
| 156 | hpet_timer_count); |
| 157 | } |
| 158 | |
| 159 | uint64_t hpet_timestamp_ns(void) { |
| 160 | uint64_t ticks = hpet_read64(HPET_MAIN_COUNTER_OFFSET); |
| 161 | |
| 162 | uint64_t fs_total = ticks * hpet_fs_per_tick; |
| 163 | return fs_total / 1000000ULL; |
| 164 | } |
| 165 | |
| 166 | uint64_t hpet_timestamp_us(void) { |
| 167 | uint64_t ticks = hpet_read64(HPET_MAIN_COUNTER_OFFSET); |
| 168 | |
| 169 | uint64_t fs_total = ticks * hpet_fs_per_tick; |
| 170 | return fs_total / 1000000000ULL; |
| 171 | } |
| 172 | |
| 173 | uint64_t hpet_timestamp_ms(void) { |
| 174 | return hpet_timestamp_us() / 1000; |
| 175 | } |
| 176 | |