blob: aeb459295f551d03483a7008d33a3e690cdd3075 [file] [log] [blame]
/* 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);
}