1// Copyright 2016 The Fuchsia Authors
2//
3// Use of this source code is governed by a MIT-style
4// license that can be found in the LICENSE file or at
5// https://opensource.org/licenses/MIT
6
7#include <platform/pc/hpet.h>
8
9#include <bits.h>
10#include <err.h>
11#include <fbl/algorithm.h>
12#include <kernel/auto_lock.h>
13#include <kernel/spinlock.h>
14#include <lk/init.h>
15#include <vm/vm_aspace.h>
16#include <zircon/types.h>
17
18struct hpet_timer_registers {
19    volatile uint64_t conf_caps;
20    volatile uint64_t comparator_value;
21    volatile uint64_t fsb_int_route;
22    uint8_t _reserved[8];
23} __PACKED;
24
25struct hpet_registers {
26    volatile uint64_t general_caps;
27    uint8_t _reserved0[8];
28    volatile uint64_t general_config;
29    uint8_t _reserved1[8];
30    volatile uint64_t general_int_status;
31    uint8_t _reserved2[0xf0 - 0x28];
32    volatile uint64_t main_counter_value;
33    uint8_t _reserved3[8];
34    struct hpet_timer_registers timers[];
35} __PACKED;
36
37static spin_lock_t lock = SPIN_LOCK_INITIAL_VALUE;
38
39static struct acpi_hpet_descriptor hpet_desc;
40static bool hpet_present = false;
41static struct hpet_registers* hpet_regs;
42uint64_t _hpet_ticks_per_ms;
43static uint64_t tick_period_in_fs;
44static uint8_t num_timers;
45
46/* Minimum number of ticks ahead a oneshot timer needs to be.  Targetted
47 * to be 100ns */
48static uint64_t min_ticks_ahead;
49
50#define MAX_PERIOD_IN_FS 0x05F5E100ULL
51
52/* Bit masks for the general_config register */
53#define GEN_CONF_EN 1
54
55/* Bit masks for the per-time conf_caps register */
56#define TIMER_CONF_LEVEL_TRIGGERED (1ULL << 1)
57#define TIMER_CONF_INT_EN (1ULL << 2)
58#define TIMER_CONF_PERIODIC (1ULL << 3)
59#define TIMER_CAP_PERIODIC(reg) BIT_SET(reg, 4)
60#define TIMER_CAP_64BIT(reg) BIT_SET(reg, 5)
61#define TIMER_CONF_PERIODIC_SET_COUNT (1ULL << 6)
62#define TIMER_CONF_IRQ(n) ((uint64_t)((n) & (0x1f)) << 9)
63#define TIMER_CAP_IRQS(reg) static_cast<uint32_t>(BITS_SHIFT(reg, 63, 32))
64
65static void hpet_init(uint level) {
66    zx_status_t status = platform_find_hpet(&hpet_desc);
67    if (status != ZX_OK) {
68        return;
69    }
70
71    if (hpet_desc.port_io) {
72        return;
73    }
74
75    zx_status_t res = VmAspace::kernel_aspace()->AllocPhysical(
76        "hpet",
77        PAGE_SIZE,                  /* size */
78        (void**)&hpet_regs,         /* returned virtual address */
79        PAGE_SIZE_SHIFT,            /* alignment log2 */
80        (paddr_t)hpet_desc.address, /* physical address */
81        0,                          /* vmm flags */
82        ARCH_MMU_FLAG_UNCACHED_DEVICE | ARCH_MMU_FLAG_PERM_READ |
83            ARCH_MMU_FLAG_PERM_WRITE);
84    if (res != ZX_OK) {
85        return;
86    }
87
88    bool has_64bit_count = BIT_SET(hpet_regs->general_caps, 13);
89    tick_period_in_fs = hpet_regs->general_caps >> 32;
90    if (tick_period_in_fs == 0 || tick_period_in_fs > MAX_PERIOD_IN_FS) {
91        goto fail;
92    }
93
94    /* We only support HPETs that are 64-bit and have at least two timers */
95    num_timers = static_cast<uint8_t>(BITS_SHIFT(hpet_regs->general_caps, 12, 8) + 1);
96    if (!has_64bit_count || num_timers < 2) {
97        goto fail;
98    }
99
100    /* Make sure all timers have interrupts disabled */
101    for (uint8_t i = 0; i < num_timers; ++i) {
102        hpet_regs->timers[i].conf_caps &= ~TIMER_CONF_INT_EN;
103    }
104
105    _hpet_ticks_per_ms = 1000000000000ULL / tick_period_in_fs;
106    min_ticks_ahead = 100000000ULL / tick_period_in_fs;
107    hpet_present = true;
108    return;
109
110fail:
111    VmAspace::kernel_aspace()->FreeRegion(reinterpret_cast<vaddr_t>(hpet_regs));
112    hpet_regs = nullptr;
113    num_timers = 0;
114}
115/* Begin running after ACPI tables are up */
116LK_INIT_HOOK(hpet, hpet_init, LK_INIT_LEVEL_VM + 2);
117
118zx_status_t hpet_timer_disable(uint n) {
119    if (unlikely(n >= num_timers)) {
120        return ZX_ERR_NOT_SUPPORTED;
121    }
122
123    AutoSpinLockNoIrqSave guard(&lock);
124    hpet_regs->timers[n].conf_caps &= ~TIMER_CONF_INT_EN;
125
126    return ZX_OK;
127}
128
129uint64_t hpet_get_value(void) {
130    uint64_t v = hpet_regs->main_counter_value;
131    uint64_t v2 = hpet_regs->main_counter_value;
132    /* Even though the specification says it should not be necessary to read
133     * multiple times, we have observed that QEMU converts the 64-bit
134     * memory access in to two 32-bit accesses, resulting in bad reads. QEMU
135     * reads the low 32-bits first, so the result is a large jump when it
136     * wraps 32 bits.  To work around this, we return the lesser of two reads.
137     */
138    return fbl::min(v, v2);
139}
140
141zx_status_t hpet_set_value(uint64_t v) {
142    AutoSpinLockNoIrqSave guard(&lock);
143
144    if (hpet_regs->general_config & GEN_CONF_EN) {
145        return ZX_ERR_BAD_STATE;
146    }
147
148    hpet_regs->main_counter_value = v;
149    return ZX_OK;
150}
151
152zx_status_t hpet_timer_configure_irq(uint n, uint irq) {
153    if (unlikely(n >= num_timers)) {
154        return ZX_ERR_NOT_SUPPORTED;
155    }
156
157    AutoSpinLockNoIrqSave guard(&lock);
158
159    uint32_t irq_bitmap = TIMER_CAP_IRQS(hpet_regs->timers[n].conf_caps);
160    if (irq >= 32 || !BIT_SET(irq_bitmap, irq)) {
161        return ZX_ERR_NOT_SUPPORTED;
162    }
163
164    uint64_t conf = hpet_regs->timers[n].conf_caps;
165    conf &= ~TIMER_CONF_IRQ(~0ULL);
166    conf |= TIMER_CONF_IRQ(irq);
167    hpet_regs->timers[n].conf_caps = conf;
168
169    return ZX_OK;
170}
171
172zx_status_t hpet_timer_set_oneshot(uint n, uint64_t deadline) {
173    if (unlikely(n >= num_timers)) {
174        return ZX_ERR_NOT_SUPPORTED;
175    }
176
177    AutoSpinLockNoIrqSave guard(&lock);
178
179    uint64_t difference = deadline - hpet_get_value();
180    if (unlikely(difference > (1ULL >> 63))) {
181        /* Either this is a very long timer, or we wrapped around */
182        return ZX_ERR_INVALID_ARGS;
183    }
184    if (unlikely(difference < min_ticks_ahead)) {
185        return ZX_ERR_INVALID_ARGS;
186    }
187
188    hpet_regs->timers[n].conf_caps &= ~(TIMER_CONF_PERIODIC |
189                                        TIMER_CONF_PERIODIC_SET_COUNT);
190    hpet_regs->timers[n].comparator_value = deadline;
191    hpet_regs->timers[n].conf_caps |= TIMER_CONF_INT_EN;
192
193    return ZX_OK;
194}
195
196zx_status_t hpet_timer_set_periodic(uint n, uint64_t period) {
197    if (unlikely(n >= num_timers)) {
198        return ZX_ERR_NOT_SUPPORTED;
199    }
200
201    AutoSpinLockNoIrqSave guard(&lock);
202
203    if (!TIMER_CAP_PERIODIC(hpet_regs->timers[n].conf_caps)) {
204        return ZX_ERR_NOT_SUPPORTED;
205    }
206
207    /* It's unsafe to set a periodic timer while the hpet is running or the
208     * main counter value is not 0. */
209    if ((hpet_regs->general_config & GEN_CONF_EN) ||
210        hpet_regs->main_counter_value != 0ULL) {
211        return ZX_ERR_BAD_STATE;
212    }
213
214    hpet_regs->timers[n].conf_caps |= TIMER_CONF_PERIODIC |
215                                      TIMER_CONF_PERIODIC_SET_COUNT;
216    hpet_regs->timers[n].comparator_value = period;
217    hpet_regs->timers[n].conf_caps |= TIMER_CONF_INT_EN;
218
219    return ZX_OK;
220}
221
222bool hpet_is_present(void) {
223    return hpet_present;
224}
225
226void hpet_enable(void) {
227    DEBUG_ASSERT(hpet_is_present());
228
229    AutoSpinLockNoIrqSave guard(&lock);
230    hpet_regs->general_config |= GEN_CONF_EN;
231}
232
233void hpet_disable(void) {
234    DEBUG_ASSERT(hpet_is_present());
235
236    AutoSpinLockNoIrqSave guard(&lock);
237    hpet_regs->general_config &= ~GEN_CONF_EN;
238}
239
240/* Blocks for the requested number of milliseconds.
241 * For use in calibration */
242void hpet_wait_ms(uint16_t ms) {
243    uint64_t init_timer_value = hpet_regs->main_counter_value;
244    uint64_t target = (uint64_t)ms * _hpet_ticks_per_ms;
245    while (hpet_regs->main_counter_value - init_timer_value <= target)
246        ;
247}
248