|  | /* Copyright (c) 2020 Google Inc | 
|  | * Copyright (c) 2014 The Regents of the University of California | 
|  | * Barret Rhoden <brho@cs.berkeley.edu> | 
|  | * See LICENSE for details. | 
|  | * | 
|  | * HPET nonsense */ | 
|  |  | 
|  | #include <string.h> | 
|  | #include <stdio.h> | 
|  | #include <assert.h> | 
|  | #include <endian.h> | 
|  | #include <pmap.h> | 
|  | #include <acpi.h> | 
|  | #include <kmalloc.h> | 
|  |  | 
|  | #include "hpet.h" | 
|  |  | 
|  | #define HPET_BLOCK_LEN		1024 | 
|  |  | 
|  | #define HPET_CAP_ID		0x00 | 
|  | #define HPET_CONFIG		0x10 | 
|  | #define HPET_IRQ_STS		0x20 | 
|  | #define HPET_MAIN_COUNTER	0xf0 | 
|  |  | 
|  | #define HPET_CONF_LEG_RT_CNF	(1 << 1) | 
|  | #define HPET_CONF_ENABLE_CNF	(1 << 0) | 
|  |  | 
|  | #define HPET_TIMER_CONF		0x00 | 
|  | #define HPET_TIMER_COMP		0x08 | 
|  | #define HPET_TIMER_FSB		0x10 | 
|  |  | 
|  | #define HPET_TN_INT_TYPE_CNF	(1 << 1) | 
|  | #define HPET_TN_INT_ENB_CNF	(1 << 2) | 
|  | #define HPET_TN_TYPE_CNF	(1 << 3) | 
|  | #define HPET_TN_PER_INT_CAP	(1 << 4) | 
|  | #define HPET_TN_SIZE_CAP	(1 << 5) | 
|  | #define HPET_TN_VAL_SET_CNF	(1 << 6) | 
|  | #define HPET_TN_32MODE_CNF	(1 << 8) | 
|  | #define HPET_TN_INT_ROUTE_CNF	(0x1f << 9) | 
|  | #define HPET_TN_FSB_EN_CNF	(1 << 14) | 
|  | #define HPET_TN_FSB_INT_DEL_CAP	(1 << 15) | 
|  | #define HPET_TN_INT_ROUTE_CAP	(0xffffffffULL << 32) | 
|  |  | 
|  | static struct hpet_block *gbl_hpet; | 
|  |  | 
|  | /* The HPET likes 64bit mmreg reads and writes.  If the arch doesn't support | 
|  | * them, then things are a little trickier.  Probably just replace these with | 
|  | * mm64 ops, and quit supporting 32 bit. */ | 
|  | static inline void hpet_w64(uintptr_t reg, uint64_t val) | 
|  | { | 
|  | *((volatile uint64_t*)reg) = val; | 
|  | } | 
|  |  | 
|  | static inline uint64_t hpet_r64(uintptr_t reg) | 
|  | { | 
|  | return *((volatile uint64_t*)reg); | 
|  | } | 
|  |  | 
|  | void hpet_timer_enable(struct hpet_timer *ht) | 
|  | { | 
|  | hpet_w64(ht->base + HPET_TIMER_CONF, ht->enable_cmd); | 
|  | } | 
|  |  | 
|  | void hpet_timer_disable(struct hpet_timer *ht) | 
|  | { | 
|  | hpet_w64(ht->base + HPET_TIMER_CONF, 0); | 
|  | } | 
|  |  | 
|  | /* This only works on 64 bit counters, where we don't have to deal with | 
|  | * wrap-around. */ | 
|  | bool hpet_check_spurious_64(struct hpet_timer *ht) | 
|  | { | 
|  | return hpet_r64(ht->hpb->base + HPET_MAIN_COUNTER) < | 
|  | hpet_r64(ht->base + HPET_TIMER_COMP); | 
|  | } | 
|  |  | 
|  | /* There is no upper addr!  But o/w, this is basically MSI.  To be fair, we zero | 
|  | * out the upper addr in msi.c too. */ | 
|  | static uint64_t fsb_make_addr(uint8_t dest) | 
|  | { | 
|  | return 0xfee00000 | (dest << 12); | 
|  | } | 
|  |  | 
|  | /* dmode: e.g. 000 = fixed, 100 = NMI.  Assuming edge triggered */ | 
|  | static uint64_t fsb_make_data(uint8_t vno, uint8_t dmode) | 
|  | { | 
|  | return (dmode << 8) | vno; | 
|  | } | 
|  |  | 
|  | /* This is a very limited HPET timer, primarily used by the watchdog. | 
|  | * | 
|  | * It's a one-shot (non-periodic), FSB, 64-bit, edge-triggered timer for Core 0 | 
|  | * with vector vno and delivery mode dmode. | 
|  | * | 
|  | * Why so specific?  Okay, the HPET is a piece of shit, at least on my machine. | 
|  | * If you disable the interrupt, and then the time comes where MAIN == COMP, the | 
|  | * IRQ will be suppressed, but when you enable later on, the IRQ will fire.  No | 
|  | * way around it that I can find. | 
|  | * | 
|  | * One trick is to set the COMP to some time that won't fire, i.e.  in the past. | 
|  | * However, the 32 bit counters are only about a 5 minute reach.  So this trick | 
|  | * only works with the 64 bit counter. | 
|  | * | 
|  | * However, even with that, if the IRQ ever legitimately fires, any time you | 
|  | * reenable the timer, it triggers an IRQ. | 
|  | * | 
|  | * This is all with FSB (which has to be edge triggered) interrupt styles.  I | 
|  | * tried various combos of "write to the IRQ_STS register", change certain | 
|  | * fields in timer CONF, disable the global counter while making changes, etc. | 
|  | * All things that aren't in the book, but that might clear whatever internal | 
|  | * bit is set. | 
|  | * | 
|  | * Ultimately, I opted to handle the 'spurious' interrupt in SW, though with a 5 | 
|  | * minute reach, you can't tell between an old value and a new one.  Unless you | 
|  | * use a 64 bit counter - then wraparound isn't a concern.  If we wanted to do | 
|  | * this for a 32 bit counter, we'd need to drastically limit the reach. */ | 
|  | void hpet_magic_timer_setup(struct hpet_timer *ht, uint8_t vno, uint8_t dmode) | 
|  | { | 
|  | /* Unlike the other reserved bits in the hpb's registers, the spec says | 
|  | * that timer (ht) conf reserved bits should be set to 0. | 
|  | * | 
|  | * In lieu of screwing around too much, we just set the entire register | 
|  | * in one shot.  Disabled = 0, enabled = all bits needed.  The | 
|  | * disastrous behavior mentioned above occurs regardless of the | 
|  | * technique used. */ | 
|  | hpet_timer_disable(ht); | 
|  |  | 
|  | /* Core 0, fixed, with vno */ | 
|  | hpet_w64(ht->base + HPET_TIMER_FSB, | 
|  | (fsb_make_addr(0) << 32) | fsb_make_data(vno, dmode)); | 
|  |  | 
|  | ht->enable_cmd = HPET_TN_FSB_EN_CNF | HPET_TN_INT_ENB_CNF; | 
|  | } | 
|  |  | 
|  | /* Sets the time to fire X ns in the future.  No guarantees or sanity checks. | 
|  | * If we get delayed setting this, the main counter may pass by our ticks value | 
|  | * before we write it, and you'll never get it. */ | 
|  | void hpet_timer_increment_comparator(struct hpet_timer *ht, uint64_t nsec) | 
|  | { | 
|  | uint64_t ticks; | 
|  |  | 
|  | ticks = hpet_r64(ht->hpb->base + HPET_MAIN_COUNTER); | 
|  | ticks += nsec / ht->hpb->nsec_per_tick; | 
|  | hpet_w64(ht->base + HPET_TIMER_COMP, ticks); | 
|  | } | 
|  |  | 
|  | /* See above.  Need 64 bit and FSB */ | 
|  | struct hpet_timer *hpet_get_magic_timer(void) | 
|  | { | 
|  | struct hpet_block *hpb = gbl_hpet; | 
|  | struct hpet_timer *ret = NULL; | 
|  |  | 
|  | if (!hpb) | 
|  | return NULL; | 
|  | spin_lock(&hpb->lock); | 
|  | for (int i = 0; i < hpb->nr_timers; i++) { | 
|  | struct hpet_timer *ht = &hpb->timers[i]; | 
|  |  | 
|  | if (ht->in_use) | 
|  | continue; | 
|  | if (!ht->fsb) | 
|  | continue; | 
|  | if (!ht->bit64) | 
|  | continue; | 
|  | ht->in_use = true; | 
|  | ret = ht; | 
|  | break; | 
|  | } | 
|  | spin_unlock(&hpb->lock); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | void hpet_put_timer(struct hpet_timer *ht) | 
|  | { | 
|  | struct hpet_block *hpb = gbl_hpet; | 
|  |  | 
|  | if (!hpb) | 
|  | return; | 
|  | spin_lock(&hpb->lock); | 
|  | ht->in_use = false; | 
|  | spin_unlock(&hpb->lock); | 
|  | } | 
|  |  | 
|  | static void print_hpb_stats(struct hpet_block *hpb) | 
|  | { | 
|  | printk("HPET at %p:\n", hpb->base); | 
|  | printk("\tVendor: 0x%x\n", (hpb->cap_id >> 16) & 0xffff); | 
|  | printk("\tPeriod: 0x%08x\n", hpb->period); | 
|  | printk("\t32 bit reach: %d sec\n", hpb->reach32); | 
|  | printk("\tTimers: %d\n", hpb->nr_timers); | 
|  | printk("\tMain counter size: %d\n", hpb->cap_id & (1 << 13) ? 64 : 32); | 
|  |  | 
|  | printd("\tcap/id %p\n", hpb->cap_id); | 
|  | printd("\tconfig %p\n", hpet_r64(hpb->base + HPET_CONFIG)); | 
|  | printd("\tirqsts %p\n", hpet_r64(hpb->base + HPET_IRQ_STS)); | 
|  |  | 
|  | for (int i = 0; i < hpb->nr_timers; i++) { | 
|  | printk("\t\tTimer %d: conf %p comp %p, %d bit, %sFSB\n", i, | 
|  | hpet_r64(hpb->timers[i].base + HPET_TIMER_CONF), | 
|  | hpet_r64(hpb->timers[i].base + HPET_TIMER_COMP), | 
|  | hpb->timers[i].bit64 ? 64 : 32, | 
|  | hpb->timers[i].fsb ? "" : "no "); | 
|  | } | 
|  | } | 
|  |  | 
|  | struct Atable *parsehpet(struct Atable *parent, | 
|  | char *name, uint8_t *raw, size_t rawsize) | 
|  | { | 
|  | struct hpet_block *hpb; | 
|  | struct Atable *hpet; | 
|  | uint32_t evt_blk_id; | 
|  |  | 
|  | /* Only dealing with one block of these. */ | 
|  | if (gbl_hpet) { | 
|  | printk("Found another HPET, skipping!\n"); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | /* Do we want to keep this table around?  if so, we can use newtable, | 
|  | * which allocs an Atable and puts it on a global stailq.  then we | 
|  | * return that pointer, not as an addr, but as a signal to parse code | 
|  | * about whether or not it is safe to unmap (which we don't do anymore). | 
|  | */ | 
|  | hpet = mkatable(parent, HPET, "HPET", raw, rawsize, 0); | 
|  | assert(hpet); | 
|  | printk("HPET table detected at %p, for %d bytes\n", raw, rawsize); | 
|  |  | 
|  | evt_blk_id = l32get(raw + 36); | 
|  |  | 
|  | hpb = kzmalloc(sizeof(*hpb), MEM_WAIT); | 
|  | spinlock_init(&hpb->lock); | 
|  |  | 
|  | hpb->base = vmap_pmem_nocache(l64get(raw + 44), HPET_BLOCK_LEN); | 
|  | if (!hpb->base) { | 
|  | printk("HPET failed to get an iomapping, aborting\n"); | 
|  | kfree(hpb); | 
|  | kfree(hpet); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | hpb->cap_id = hpet_r64(hpb->base + HPET_CAP_ID); | 
|  | if (evt_blk_id != (hpb->cap_id & 0xffffffff)) { | 
|  | printk("HPET ACPI mismatch: ACPI: %p HPET: %p\n", evt_blk_id, | 
|  | hpb->cap_id); | 
|  | } | 
|  | hpb->nr_timers = ((hpb->cap_id >> 8) & 0xf) + 1; | 
|  |  | 
|  | /* femtoseconds (10E-15) per tick. | 
|  | * e.g. 69 ns per tick. | 
|  | * freq is just 10^15 / period: 14.318 MHz | 
|  | * reach of 32 bit counter: | 
|  | * period fs/tick * 1sec/10^15fs * 2^32tick/wrap ~= 299 seconds */ | 
|  | hpb->period = hpb->cap_id >> 32; | 
|  | hpb->nsec_per_tick = hpb->period / 1000000; | 
|  | hpb->reach32 = hpb->period * (1ULL << 32) / 1000000000000000ULL; | 
|  |  | 
|  | for (int i = 0; i < hpb->nr_timers; i++) { | 
|  | struct hpet_timer *ht = &hpb->timers[i]; | 
|  | uint64_t conf; | 
|  |  | 
|  | ht->base = hpb->base + 0x100 + 0x20 * i; | 
|  | ht->hpb = hpb; | 
|  | conf = hpet_r64(ht->base + HPET_TIMER_CONF); | 
|  | ht->bit64 = conf & HPET_TN_SIZE_CAP; | 
|  | ht->fsb = conf & HPET_TN_FSB_INT_DEL_CAP; | 
|  | hpet_timer_disable(ht); | 
|  | } | 
|  |  | 
|  | /* No interest in legacy mode. */ | 
|  | hpet_w64(hpb->base + HPET_CONFIG, | 
|  | hpet_r64(hpb->base + HPET_CONFIG) & HPET_CONF_LEG_RT_CNF); | 
|  | /* All timers are off currently */ | 
|  | hpet_w64(hpb->base + HPET_CONFIG, | 
|  | hpet_r64(hpb->base + HPET_CONFIG) | HPET_CONF_ENABLE_CNF); | 
|  |  | 
|  | gbl_hpet = hpb; | 
|  |  | 
|  | return finatable_nochildren(hpet); | 
|  | } | 
|  |  | 
|  | void cmos_dumping_ground(void) | 
|  | { | 
|  | uint8_t cmos_b; | 
|  |  | 
|  | /* this stuff tries to turn off various cmos / RTC timer bits.  keeping | 
|  | * around if we need to disable the RTC alarm.  note that the HPET | 
|  | * replaces the RTC periodic function (where available), and in those | 
|  | * cases the RTC alarm function is implemented with SMM. */ | 
|  | outb(0x70, 0xb); | 
|  | cmos_b = inb(0x71); | 
|  | printk("cmos b 0x%02x\n", cmos_b); | 
|  |  | 
|  | cmos_b &= ~((1 << 5) | (1 << 6)); | 
|  | outb(0x70, 0xb); | 
|  | outb(0x71, cmos_b); | 
|  |  | 
|  | outb(0x70, 0xb); | 
|  | cmos_b = inb(0x71); | 
|  | printk("cmos b 0x%02x\n", cmos_b); | 
|  | } |