I have disabled the legacy routing in the GCR so it should not be going through IRQ 2. And even if it was, does it make sense that all 3 timers need to route through the same IRQ channel (2)? Does that even make sense? Is there a bug with QEMU and HPET, or do I likely have a bug in my code? Also, when I run hpet_init and then hpet_start_oneshot, I get a Segment Not Present exception with error code 0xc22 which represents idt index 388 which does not even exist.
For reference, my HPET code is below:
Code: Select all
#define _hpet_MAX_TIMERS 32
#define _hpet_REGISTER_SIZE 0x8
#define _hpet_GCIDR 0
#define _hpet_GCR 0x2
#define _hpet_MCR 0x1e
#define _hpet_CCR 0x20
#define _hpet_CVR 0x21
#define _hpet_get_register(reg) \
(hpet_info.base_addr + _hpet_REGISTER_SIZE * (reg))
#define _hpet_nth_cvr(n) (_hpet_CVR + 4 * (n))
#define _hpet_nth_ccr(n) (_hpet_CCR + 4 * (n))
typedef struct {
uint8_t rev_id : 8; // 0-7
uint8_t num_timers : 5; // 8 - 12
// 1 for 64 bit, 0 for 32 bit
uint8_t main_counter_width : 1; // 13
uint8_t reserved : 1; // 14
uint8_t legacy_capable : 1; // 15
uint16_t vendor_id : 16; // 16 - 31
uint32_t counter_clock_period : 32; // 32 - 63
}__attribute__((packed)) gcidr_t;
typedef struct {
uint8_t reserved1 : 1; // 0
// edge triggered (0) or level triggered (1)
uint8_t interrupt_type : 1; // 1
uint8_t interrupt_enable : 1; // 2
uint8_t set_periodic : 1; // 3
// Read only 1 if periodic supported
uint8_t supports_periodic : 1; // 4
// 1 = 64 bits, 0 = 32 bits
uint8_t enable_64_bit : 1; // 5
// If periodic, can set to 1 to write to accumulator
uint8_t set_accumulator : 1; // 6
uint8_t reserved2 : 1; // 7
uint8_t force_32_bit : 1; // 8
uint8_t irq_idx : 5; // 9 - 13
uint8_t enable_fsb : 1; // 14
uint8_t supports_fsb : 1; // 15
uint16_t reserved3 : 16; // 16 - 31
uint32_t irq_routing_capability : 32; // 32 - 63
} __attribute__((packed)) ccr_t;
typedef struct {
uint8_t enable_main_counter : 1; // 0
uint8_t enable_legacy_routing : 1; // 1
uint64_t reserved : 62; // 2 - 63
} __attribute__((packed)) gcr_t;
// My own type for storing timer information
typedef struct {
uint8_t present : 1;
uint8_t periodic : 1;
uint8_t reserved : 6;
} __attribute__((packed)) ntimers_t;
static struct {
ntimers_t timers[32];
void *base_addr;
uint32_t clock_period;
uint16_t min_tick;
} hpet_info;
#define _hpet_frequency (femtosecond / hpet_info.clock_period)
static ALWAYS_INLINE void _hpet_init_gcr() {
volatile gcr_t *gcr = _hpet_get_register(_hpet_GCR);
gcr->enable_legacy_routing = 0;
}
static ALWAYS_INLINE void _hpet_init_timers(const gcidr_t gcidr) {
int idt_idx = k_HPET_INTERRUPT_IDX_BASE;
for (int i = 0; i <= gcidr.num_timers; ++i) {
ntimers_t timer;
int ccr_reg_id = _hpet_nth_ccr(i);
volatile ccr_t *ccr = _hpet_get_register(ccr_reg_id);
ccr->interrupt_type = 0;
ccr->enable_64_bit = 1;
ccr->set_accumulator = 0;
ccr->reserved3 = 0;
ccr->force_32_bit = 0;
timer.present = 1;
timer.periodic = ccr->supports_periodic;
hpet_info.timers[i] = timer;
int irq_idx = ctz(ccr->irq_routing_capability);
ccr->irq_idx = irq_idx;
ioapic_set_irq(irq_idx, apic_get_id(), idt_idx++);
}
}
void hpet_start_one_shot(uint64_t time_ns) {
// TODO choose a timer in a better way
int timer_id = 0;
uint64_t time_fs = max(hpet_info.clock_period, ns_to_fs(time_ns));
ccr_t *ccr = _hpet_get_register(_hpet_nth_ccr(timer_id));
ccr->set_periodic = 0;
uint64_t mcr = *(uint64_t *)_hpet_get_register(_hpet_MCR);
uint64_t *cvr = _hpet_get_register(_hpet_nth_cvr(timer_id));
*cvr = mcr + time_fs;
ccr->interrupt_enable = 1;
}
int hpet_init(const hpet_infov1_t *hpet) {
hpet_info.base_addr = (void *)hpet->address.address;
hpet_info.min_tick = hpet->minimum_tick;
memset(&hpet_info.timers, 0, sizeof(hpet_info.timers));
frame_t frame = {.addr = (size_t)hpet_info.base_addr};
if (paging_identity_map(frame, paging_ENTRY_SET_WRITABLE(0)) == 1) {
return 1;
}
_hpet_init_gcr();
const gcidr_t gcidr = *(volatile gcidr_t *)_hpet_get_register(_hpet_GCIDR);
_hpet_init_timers(gcidr);
return 0;
}