hpet: add basic support for using timers

The HPET is a disaster.  This commit adds the basical support I needed
for a watchdog timer.  YMMV.

Signed-off-by: Barret Rhoden <brho@cs.berkeley.edu>
diff --git a/kern/drivers/timers/hpet.c b/kern/drivers/timers/hpet.c
index 0dd88e2..aeb4592 100644
--- a/kern/drivers/timers/hpet.c
+++ b/kern/drivers/timers/hpet.c
@@ -1,12 +1,48 @@
+/* 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. */
@@ -20,45 +56,229 @@
 	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).
 	 */
-	struct Atable *hpet = mkatable(parent, HPET, "HPET", raw, rawsize, 0);
-	unsigned long hp_addr;
-	uint32_t evt_blk_id;
-	int nr_timers;
-
+	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);
-	printd("EV BID 0x%08x\n", evt_blk_id);
 
-	hp_addr = (unsigned long)KADDR_NOCHECK(l64get(raw + 44));
+	hpb = kzmalloc(sizeof(*hpb), MEM_WAIT);
+	spinlock_init(&hpb->lock);
 
-	printd("cap/ip %p\n", hpet_r64(hp_addr + 0x00));
-	printd("config %p\n", hpet_r64(hp_addr + 0x10));
-	printd("irqsts %p\n", hpet_r64(hp_addr + 0x20));
+	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;
+	}
 
-	nr_timers = ((hpet_r64(hp_addr) >> 8) & 0xf) + 1;
-	for (int i = 0; i < nr_timers; i++)
-		printd("Timer %d, config reg %p\n", i,
-		       hpet_r64(hp_addr + 0x100 + 0x20 * i));
-	/* 0x10, general config register.  bottom two bits are legacy mode and
-	 * global enable.  turning them both off.  need to do read-modify-writes
-	 * to HPET registers with reserved fields.*/
-	hpet_w64(hp_addr + 0x10, hpet_r64(hp_addr + 0x10) & ~0x3);
-	printk("Disabled the HPET timer\n");
+	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 cmos_dumping_ground(void)
 {
 	uint8_t cmos_b;
 
diff --git a/kern/drivers/timers/hpet.h b/kern/drivers/timers/hpet.h
index 70dd7f5..ddde2cf 100644
--- a/kern/drivers/timers/hpet.h
+++ b/kern/drivers/timers/hpet.h
@@ -1,6 +1,42 @@
+/* Copyright (c) 2020 Google Inc
+ * Barret Rhoden <brho@cs.berkeley.edu>
+ * See LICENSE for details.
+ *
+ * HPET nonsense */
+
 #pragma once
 
 #include <acpi.h>
+#include <atomic.h>
+
+struct hpet_timer {
+	uintptr_t base;
+	uint64_t enable_cmd;
+	bool bit64;
+	bool fsb;
+	bool in_use;
+	struct hpet_block *hpb;
+};
+
+struct hpet_block {
+	spinlock_t lock;
+	uintptr_t base;
+	uint64_t cap_id;
+	uint32_t period;
+	uint32_t nsec_per_tick;
+	uint32_t reach32;
+	unsigned int nr_timers;
+	struct hpet_timer timers[32];
+};
+
+struct hpet_timer *hpet_get_magic_timer(void);
+void hpet_put_timer(struct hpet_timer *ht);
+
+void hpet_timer_enable(struct hpet_timer *ht);
+void hpet_timer_disable(struct hpet_timer *ht);
+bool hpet_check_spurious_64(struct hpet_timer *ht);
+void hpet_magic_timer_setup(struct hpet_timer *ht, uint8_t vno, uint8_t dmode);
+void hpet_timer_increment_comparator(struct hpet_timer *ht, uint64_t nsec);
 
 struct Atable *parsehpet(struct Atable *parent,
                          char *name, uint8_t *p, size_t rawsize);