akaros/kern/drivers/timers/hpet.c
<<
>>
Prefs
   1/* Copyright (c) 2020 Google Inc
   2 * Copyright (c) 2014 The Regents of the University of California
   3 * Barret Rhoden <brho@cs.berkeley.edu>
   4 * See LICENSE for details.
   5 *
   6 * HPET nonsense */
   7
   8#include <string.h>
   9#include <stdio.h>
  10#include <assert.h>
  11#include <endian.h>
  12#include <pmap.h>
  13#include <acpi.h>
  14#include <kmalloc.h>
  15
  16#include "hpet.h"
  17
  18#define HPET_BLOCK_LEN          1024
  19
  20#define HPET_CAP_ID             0x00
  21#define HPET_CONFIG             0x10
  22#define HPET_IRQ_STS            0x20
  23#define HPET_MAIN_COUNTER       0xf0
  24
  25#define HPET_CONF_LEG_RT_CNF    (1 << 1)
  26#define HPET_CONF_ENABLE_CNF    (1 << 0)
  27
  28#define HPET_TIMER_CONF         0x00
  29#define HPET_TIMER_COMP         0x08
  30#define HPET_TIMER_FSB          0x10
  31
  32#define HPET_TN_INT_TYPE_CNF    (1 << 1)
  33#define HPET_TN_INT_ENB_CNF     (1 << 2)
  34#define HPET_TN_TYPE_CNF        (1 << 3)
  35#define HPET_TN_PER_INT_CAP     (1 << 4)
  36#define HPET_TN_SIZE_CAP        (1 << 5)
  37#define HPET_TN_VAL_SET_CNF     (1 << 6)
  38#define HPET_TN_32MODE_CNF      (1 << 8)
  39#define HPET_TN_INT_ROUTE_CNF   (0x1f << 9)
  40#define HPET_TN_FSB_EN_CNF      (1 << 14)
  41#define HPET_TN_FSB_INT_DEL_CAP (1 << 15)
  42#define HPET_TN_INT_ROUTE_CAP   (0xffffffffULL << 32)
  43
  44static struct hpet_block *gbl_hpet;
  45
  46/* The HPET likes 64bit mmreg reads and writes.  If the arch doesn't support
  47 * them, then things are a little trickier.  Probably just replace these with
  48 * mm64 ops, and quit supporting 32 bit. */
  49static inline void hpet_w64(uintptr_t reg, uint64_t val)
  50{
  51        *((volatile uint64_t*)reg) = val;
  52}
  53
  54static inline uint64_t hpet_r64(uintptr_t reg)
  55{
  56        return *((volatile uint64_t*)reg);
  57}
  58
  59void hpet_timer_enable(struct hpet_timer *ht)
  60{
  61        hpet_w64(ht->base + HPET_TIMER_CONF, ht->enable_cmd);
  62}
  63
  64void hpet_timer_disable(struct hpet_timer *ht)
  65{
  66        hpet_w64(ht->base + HPET_TIMER_CONF, 0);
  67}
  68
  69/* This only works on 64 bit counters, where we don't have to deal with
  70 * wrap-around. */
  71bool hpet_check_spurious_64(struct hpet_timer *ht)
  72{
  73        return hpet_r64(ht->hpb->base + HPET_MAIN_COUNTER) <
  74                hpet_r64(ht->base + HPET_TIMER_COMP);
  75}
  76
  77/* There is no upper addr!  But o/w, this is basically MSI.  To be fair, we zero
  78 * out the upper addr in msi.c too. */
  79static uint64_t fsb_make_addr(uint8_t dest)
  80{
  81        return 0xfee00000 | (dest << 12);
  82}
  83
  84/* dmode: e.g. 000 = fixed, 100 = NMI.  Assuming edge triggered */
  85static uint64_t fsb_make_data(uint8_t vno, uint8_t dmode)
  86{
  87        return (dmode << 8) | vno;
  88}
  89
  90/* This is a very limited HPET timer, primarily used by the watchdog.
  91 *
  92 * It's a one-shot (non-periodic), FSB, 64-bit, edge-triggered timer for Core 0
  93 * with vector vno and delivery mode dmode.
  94 *
  95 * Why so specific?  Okay, the HPET is a piece of shit, at least on my machine.
  96 * If you disable the interrupt, and then the time comes where MAIN == COMP, the
  97 * IRQ will be suppressed, but when you enable later on, the IRQ will fire.  No
  98 * way around it that I can find.
  99 *
 100 * One trick is to set the COMP to some time that won't fire, i.e.  in the past.
 101 * However, the 32 bit counters are only about a 5 minute reach.  So this trick
 102 * only works with the 64 bit counter.
 103 *
 104 * However, even with that, if the IRQ ever legitimately fires, any time you
 105 * reenable the timer, it triggers an IRQ.
 106 *
 107 * This is all with FSB (which has to be edge triggered) interrupt styles.  I
 108 * tried various combos of "write to the IRQ_STS register", change certain
 109 * fields in timer CONF, disable the global counter while making changes, etc.
 110 * All things that aren't in the book, but that might clear whatever internal
 111 * bit is set.
 112 *
 113 * Ultimately, I opted to handle the 'spurious' interrupt in SW, though with a 5
 114 * minute reach, you can't tell between an old value and a new one.  Unless you
 115 * use a 64 bit counter - then wraparound isn't a concern.  If we wanted to do
 116 * this for a 32 bit counter, we'd need to drastically limit the reach. */
 117void hpet_magic_timer_setup(struct hpet_timer *ht, uint8_t vno, uint8_t dmode)
 118{
 119        /* Unlike the other reserved bits in the hpb's registers, the spec says
 120         * that timer (ht) conf reserved bits should be set to 0.
 121         *
 122         * In lieu of screwing around too much, we just set the entire register
 123         * in one shot.  Disabled = 0, enabled = all bits needed.  The
 124         * disastrous behavior mentioned above occurs regardless of the
 125         * technique used. */
 126        hpet_timer_disable(ht);
 127
 128        /* Core 0, fixed, with vno */
 129        hpet_w64(ht->base + HPET_TIMER_FSB,
 130                 (fsb_make_addr(0) << 32) | fsb_make_data(vno, dmode));
 131
 132        ht->enable_cmd = HPET_TN_FSB_EN_CNF | HPET_TN_INT_ENB_CNF;
 133}
 134
 135/* Sets the time to fire X ns in the future.  No guarantees or sanity checks.
 136 * If we get delayed setting this, the main counter may pass by our ticks value
 137 * before we write it, and you'll never get it. */
 138void hpet_timer_increment_comparator(struct hpet_timer *ht, uint64_t nsec)
 139{
 140        uint64_t ticks;
 141
 142        ticks = hpet_r64(ht->hpb->base + HPET_MAIN_COUNTER);
 143        ticks += nsec / ht->hpb->nsec_per_tick;
 144        hpet_w64(ht->base + HPET_TIMER_COMP, ticks);
 145}
 146
 147/* See above.  Need 64 bit and FSB */
 148struct hpet_timer *hpet_get_magic_timer(void)
 149{
 150        struct hpet_block *hpb = gbl_hpet;
 151        struct hpet_timer *ret = NULL;
 152
 153        if (!hpb)
 154                return NULL;
 155        spin_lock(&hpb->lock);
 156        for (int i = 0; i < hpb->nr_timers; i++) {
 157                struct hpet_timer *ht = &hpb->timers[i];
 158
 159                if (ht->in_use)
 160                        continue;
 161                if (!ht->fsb)
 162                        continue;
 163                if (!ht->bit64)
 164                        continue;
 165                ht->in_use = true;
 166                ret = ht;
 167                break;
 168        }
 169        spin_unlock(&hpb->lock);
 170        return ret;
 171}
 172
 173void hpet_put_timer(struct hpet_timer *ht)
 174{
 175        struct hpet_block *hpb = gbl_hpet;
 176
 177        if (!hpb)
 178                return;
 179        spin_lock(&hpb->lock);
 180        ht->in_use = false;
 181        spin_unlock(&hpb->lock);
 182}
 183
 184static void print_hpb_stats(struct hpet_block *hpb)
 185{
 186        printk("HPET at %p:\n", hpb->base);
 187        printk("\tVendor: 0x%x\n", (hpb->cap_id >> 16) & 0xffff);
 188        printk("\tPeriod: 0x%08x\n", hpb->period);
 189        printk("\t32 bit reach: %d sec\n", hpb->reach32);
 190        printk("\tTimers: %d\n", hpb->nr_timers);
 191        printk("\tMain counter size: %d\n", hpb->cap_id & (1 << 13) ? 64 : 32);
 192
 193        printd("\tcap/id %p\n", hpb->cap_id);
 194        printd("\tconfig %p\n", hpet_r64(hpb->base + HPET_CONFIG));
 195        printd("\tirqsts %p\n", hpet_r64(hpb->base + HPET_IRQ_STS));
 196
 197        for (int i = 0; i < hpb->nr_timers; i++) {
 198                printk("\t\tTimer %d: conf %p comp %p, %d bit, %sFSB\n", i,
 199                       hpet_r64(hpb->timers[i].base + HPET_TIMER_CONF),
 200                       hpet_r64(hpb->timers[i].base + HPET_TIMER_COMP),
 201                       hpb->timers[i].bit64 ? 64 : 32,
 202                       hpb->timers[i].fsb ? "" : "no ");
 203        }
 204}
 205
 206struct Atable *parsehpet(struct Atable *parent,
 207                         char *name, uint8_t *raw, size_t rawsize)
 208{
 209        struct hpet_block *hpb;
 210        struct Atable *hpet;
 211        uint32_t evt_blk_id;
 212
 213        /* Only dealing with one block of these. */
 214        if (gbl_hpet) {
 215                printk("Found another HPET, skipping!\n");
 216                return NULL;
 217        }
 218
 219        /* Do we want to keep this table around?  if so, we can use newtable,
 220         * which allocs an Atable and puts it on a global stailq.  then we
 221         * return that pointer, not as an addr, but as a signal to parse code
 222         * about whether or not it is safe to unmap (which we don't do anymore).
 223         */
 224        hpet = mkatable(parent, HPET, "HPET", raw, rawsize, 0);
 225        assert(hpet);
 226        printk("HPET table detected at %p, for %d bytes\n", raw, rawsize);
 227
 228        evt_blk_id = l32get(raw + 36);
 229
 230        hpb = kzmalloc(sizeof(*hpb), MEM_WAIT);
 231        spinlock_init(&hpb->lock);
 232
 233        hpb->base = vmap_pmem_nocache(l64get(raw + 44), HPET_BLOCK_LEN);
 234        if (!hpb->base) {
 235                printk("HPET failed to get an iomapping, aborting\n");
 236                kfree(hpb);
 237                kfree(hpet);
 238                return NULL;
 239        }
 240
 241        hpb->cap_id = hpet_r64(hpb->base + HPET_CAP_ID);
 242        if (evt_blk_id != (hpb->cap_id & 0xffffffff)) {
 243                printk("HPET ACPI mismatch: ACPI: %p HPET: %p\n", evt_blk_id,
 244                       hpb->cap_id);
 245        }
 246        hpb->nr_timers = ((hpb->cap_id >> 8) & 0xf) + 1;
 247
 248        /* femtoseconds (10E-15) per tick.
 249         * e.g. 69 ns per tick.
 250         * freq is just 10^15 / period: 14.318 MHz
 251         * reach of 32 bit counter:
 252         * period fs/tick * 1sec/10^15fs * 2^32tick/wrap ~= 299 seconds */
 253        hpb->period = hpb->cap_id >> 32;
 254        hpb->nsec_per_tick = hpb->period / 1000000;
 255        hpb->reach32 = hpb->period * (1ULL << 32) / 1000000000000000ULL;
 256
 257        for (int i = 0; i < hpb->nr_timers; i++) {
 258                struct hpet_timer *ht = &hpb->timers[i];
 259                uint64_t conf;
 260
 261                ht->base = hpb->base + 0x100 + 0x20 * i;
 262                ht->hpb = hpb;
 263                conf = hpet_r64(ht->base + HPET_TIMER_CONF);
 264                ht->bit64 = conf & HPET_TN_SIZE_CAP;
 265                ht->fsb = conf & HPET_TN_FSB_INT_DEL_CAP;
 266                hpet_timer_disable(ht);
 267        }
 268
 269        /* No interest in legacy mode. */
 270        hpet_w64(hpb->base + HPET_CONFIG,
 271                 hpet_r64(hpb->base + HPET_CONFIG) & HPET_CONF_LEG_RT_CNF);
 272        /* All timers are off currently */
 273        hpet_w64(hpb->base + HPET_CONFIG,
 274                 hpet_r64(hpb->base + HPET_CONFIG) | HPET_CONF_ENABLE_CNF);
 275
 276        gbl_hpet = hpb;
 277
 278        return finatable_nochildren(hpet);
 279}
 280
 281void cmos_dumping_ground(void)
 282{
 283        uint8_t cmos_b;
 284
 285        /* this stuff tries to turn off various cmos / RTC timer bits.  keeping
 286         * around if we need to disable the RTC alarm.  note that the HPET
 287         * replaces the RTC periodic function (where available), and in those
 288         * cases the RTC alarm function is implemented with SMM. */
 289        outb(0x70, 0xb);
 290        cmos_b = inb(0x71);
 291        printk("cmos b 0x%02x\n", cmos_b);
 292
 293        cmos_b &= ~((1 << 5) | (1 << 6));
 294        outb(0x70, 0xb);
 295        outb(0x71, cmos_b);
 296
 297        outb(0x70, 0xb);
 298        cmos_b = inb(0x71);
 299        printk("cmos b 0x%02x\n", cmos_b);
 300}
 301