TESTED: This works in qemu correctly TESTED: This works on an AMD board with ACPI v2.0 correctly
--- i386/i386/apic.c | 89 +++++++++++++++++++++++++++++++++++ i386/i386/apic.h | 5 ++ i386/i386at/acpi_parse_apic.c | 35 ++++++++++---- i386/i386at/acpi_parse_apic.h | 23 +++++++++ i386/i386at/model_dep.c | 5 ++ 5 files changed, 149 insertions(+), 8 deletions(-) diff --git a/i386/i386/apic.c b/i386/i386/apic.c index feb49c85..0b5be50f 100644 --- a/i386/i386/apic.c +++ b/i386/i386/apic.c @@ -26,6 +26,10 @@ #include <kern/printf.h> #include <kern/kalloc.h> +/* + * Period of HPET timer in nanoseconds + */ +uint32_t hpet_period_nsec; /* * This dummy structure is needed so that CPU_NUMBER can be called @@ -362,3 +366,88 @@ lapic_eoi(void) { lapic->eoi.r = 0; } + +#define HPET32(x) *((volatile uint32_t *)((uint8_t *)hpet_addr + x)) +#define HPET_CAP_PERIOD 0x04 +#define HPET_CFG 0x10 +# define HPET_CFG_ENABLE (1 << 0) +# define HPET_LEGACY_ROUTE (1 << 1) +#define HPET_COUNTER 0xf0 +#define HPET_T0_CFG 0x100 +# define HPET_T0_32BIT_MODE (1 << 8) +# define HPET_T0_VAL_SET (1 << 6) +# define HPET_T0_TYPE_PERIODIC (1 << 3) +# define HPET_T0_INT_ENABLE (1 << 2) +#define HPET_T0_COMPARATOR 0x108 + +#define FSEC_PER_NSEC 1000000 +#define NSEC_PER_USEC 1000 + +/* This function sets up the HPET timer to be in + * 32 bit periodic mode and not generating any interrupts. + * The timer counts upwards and when it reaches 0xffffffff it + * wraps to zero. The timer ticks at a constant rate in nanoseconds which + * is stored in hpet_period_nsec variable. + */ +void +hpet_init(void) +{ + uint32_t period; + uint32_t val; + + assert(hpet_addr != 0); + + /* Find out how often the HPET ticks in nanoseconds */ + period = HPET32(HPET_CAP_PERIOD); + hpet_period_nsec = period / FSEC_PER_NSEC; + printf("HPET ticks every %d nanoseconds\n", hpet_period_nsec); + + /* Disable HPET and legacy interrupt routing mode */ + val = HPET32(HPET_CFG); + val = val & ~(HPET_LEGACY_ROUTE | HPET_CFG_ENABLE); + HPET32(HPET_CFG) = val; + + /* Clear the counter */ + HPET32(HPET_COUNTER) = 0; + + /* Set up 32 bit periodic timer with no interrupts */ + val = HPET32(HPET_T0_CFG); + val = (val & ~HPET_T0_INT_ENABLE) | HPET_T0_32BIT_MODE | HPET_T0_TYPE_PERIODIC | HPET_T0_VAL_SET; + HPET32(HPET_T0_CFG) = val; + + /* Set comparator to max */ + HPET32(HPET_T0_COMPARATOR) = 0xffffffff; + + /* Enable the HPET */ + HPET32(HPET_CFG) |= HPET_CFG_ENABLE; + + printf("HPET enabled\n"); +} + +void +hpet_udelay(uint32_t us) +{ + uint32_t start, now; + uint32_t max_delay_us = 0xffffffff / NSEC_PER_USEC; + + if (us > max_delay_us) { + printf("HPET ERROR: Delay too long, %d usec, truncating to %d usec\n", + us, max_delay_us); + us = max_delay_us; + } + + /* Convert us to HPET ticks */ + us = (us * NSEC_PER_USEC) / hpet_period_nsec; + + start = HPET32(HPET_COUNTER); + do { + now = HPET32(HPET_COUNTER); + } while (now - start < us); +} + +void +hpet_mdelay(uint32_t ms) +{ + hpet_udelay(ms * 1000); +} + diff --git a/i386/i386/apic.h b/i386/i386/apic.h index 29387d9d..9eef0d8b 100644 --- a/i386/i386/apic.h +++ b/i386/i386/apic.h @@ -252,10 +252,15 @@ void calibrate_lapic_timer(void); void ioapic_toggle(int pin, int mask); void ioapic_configure(void); +void hpet_init(void); +void hpet_udelay(uint32_t us); +void hpet_mdelay(uint32_t ms); + extern int timer_pin; extern void intnull(int unit); extern volatile ApicLocalUnit* lapic; extern int cpu_id_lut[]; +extern uint32_t *hpet_addr; #endif diff --git a/i386/i386at/acpi_parse_apic.c b/i386/i386at/acpi_parse_apic.c index dcd5da89..1cfc1791 100644 --- a/i386/i386at/acpi_parse_apic.c +++ b/i386/i386at/acpi_parse_apic.c @@ -34,6 +34,7 @@ static struct acpi_apic *apic_madt = NULL; unsigned lapic_addr; +uint32_t *hpet_addr; /* * acpi_print_info: shows by screen the ACPI's rsdp and rsdt virtual address @@ -292,28 +293,37 @@ acpi_get_xsdt(phys_addr_t rsdp_phys, int* acpi_xsdt_n) * and the number of entries of RSDT table. * * Returns a reference to APIC/MADT table if success, NULL if failure. + * Also sets hpet_addr to base address of HPET. */ static struct acpi_apic* acpi_get_apic(struct acpi_rsdt *rsdt, int acpi_rsdt_n) { struct acpi_dhdr *descr_header; + struct acpi_apic *madt = NULL; int check_signature; + uint64_t map_addr; /* Search APIC entries in rsdt table. */ for (int i = 0; i < acpi_rsdt_n; i++) { descr_header = (struct acpi_dhdr*) kmem_map_aligned_table(rsdt->entry[i], sizeof(struct acpi_dhdr), VM_PROT_READ); - /* Check if the entry contains an APIC. */ + /* Check if the entry is a MADT */ check_signature = acpi_check_signature(descr_header->signature, ACPI_APIC_SIG, 4*sizeof(uint8_t)); + if (check_signature == ACPI_SUCCESS) + madt = (struct acpi_apic*) descr_header; + /* Check if the entry is a HPET */ + check_signature = acpi_check_signature(descr_header->signature, ACPI_HPET_SIG, 4*sizeof(uint8_t)); if (check_signature == ACPI_SUCCESS) { - /* If yes, return the APIC. */ - return (struct acpi_apic*) descr_header; + map_addr = ((struct acpi_hpet *)descr_header)->address.addr64; + assert (map_addr != 0); + hpet_addr = (uint32_t *)kmem_map_aligned_table(map_addr, 1024, VM_PROT_READ | VM_PROT_WRITE); + printf("HPET at physical address 0x%llx\n", map_addr); } } - return NULL; + return madt; } /* @@ -323,28 +333,37 @@ acpi_get_apic(struct acpi_rsdt *rsdt, int acpi_rsdt_n) * and the number of entries of XSDT table. * * Returns a reference to APIC/MADT table if success, NULL if failure. + * Also sets hpet_addr to base address of HPET. */ static struct acpi_apic* acpi_get_apic2(struct acpi_xsdt *xsdt, int acpi_xsdt_n) { struct acpi_dhdr *descr_header; + struct acpi_apic *madt = NULL; int check_signature; + uint64_t map_addr; /* Search APIC entries in rsdt table. */ for (int i = 0; i < acpi_xsdt_n; i++) { descr_header = (struct acpi_dhdr*) kmem_map_aligned_table(xsdt->entry[i], sizeof(struct acpi_dhdr), VM_PROT_READ); - /* Check if the entry contains an APIC. */ + /* Check if the entry is an APIC. */ check_signature = acpi_check_signature(descr_header->signature, ACPI_APIC_SIG, 4*sizeof(uint8_t)); + if (check_signature == ACPI_SUCCESS) + madt = (struct acpi_apic *)descr_header; + /* Check if the entry is a HPET. */ + check_signature = acpi_check_signature(descr_header->signature, ACPI_HPET_SIG, 4*sizeof(uint8_t)); if (check_signature == ACPI_SUCCESS) { - /* If yes, return the APIC. */ - return (struct acpi_apic*) descr_header; + map_addr = ((struct acpi_hpet *)descr_header)->address.addr64; + assert (map_addr != 0); + hpet_addr = (uint32_t *)kmem_map_aligned_table(map_addr, 1024, VM_PROT_READ | VM_PROT_WRITE); + printf("HPET at physical address 0x%llx\n", map_addr); } } - return NULL; + return madt; } /* diff --git a/i386/i386at/acpi_parse_apic.h b/i386/i386at/acpi_parse_apic.h index df36bb31..85e01170 100644 --- a/i386/i386at/acpi_parse_apic.h +++ b/i386/i386at/acpi_parse_apic.h @@ -91,6 +91,14 @@ struct acpi_xsdt { uint64_t entry[0]; } __attribute__((__packed__)); +struct acpi_address { + uint8_t is_io; + uint8_t reg_width; + uint8_t reg_offset; + uint8_t reserved; + uint64_t addr64; +} __attribute__((__packed__)); + /* APIC table signature. */ #define ACPI_APIC_SIG "APIC" @@ -170,6 +178,21 @@ struct acpi_apic_irq_override { uint16_t flags; } __attribute__((__packed__)); + +#define ACPI_HPET_SIG "HPET" + +/* + * HPET High Precision Event Timer structure + */ +struct acpi_hpet { + struct acpi_dhdr header; + uint32_t id; + struct acpi_address address; + uint8_t sequence; + uint16_t minimum_tick; + uint8_t flags; +} __attribute__((__packed__)); + int acpi_apic_init(void); void acpi_print_info(phys_addr_t rsdp, void *rsdt, int acpi_rsdt_n); diff --git a/i386/i386at/model_dep.c b/i386/i386at/model_dep.c index 173b99f5..b5f56c7d 100644 --- a/i386/i386at/model_dep.c +++ b/i386/i386at/model_dep.c @@ -229,6 +229,11 @@ void machine_init(void) */ gdt_descr_tmp.linear_base += apboot_addr; apboot_jmp_offset += apboot_addr; + + /* + * Initialize the HPET + */ + hpet_init(); #endif } -- 2.43.0