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