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 
18 struct 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 
25 struct 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 
37 static spin_lock_t lock = SPIN_LOCK_INITIAL_VALUE;
38 
39 static struct acpi_hpet_descriptor hpet_desc;
40 static bool hpet_present = false;
41 static struct hpet_registers* hpet_regs;
42 uint64_t _hpet_ticks_per_ms;
43 static uint64_t tick_period_in_fs;
44 static uint8_t num_timers;
45 
46 /* Minimum number of ticks ahead a oneshot timer needs to be.  Targetted
47  * to be 100ns */
48 static 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 
hpet_init(uint level)65 static 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 
110 fail:
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 */
116 LK_INIT_HOOK(hpet, hpet_init, LK_INIT_LEVEL_VM + 2);
117 
hpet_timer_disable(uint n)118 zx_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 
hpet_get_value(void)129 uint64_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 
hpet_set_value(uint64_t v)141 zx_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 
hpet_timer_configure_irq(uint n,uint irq)152 zx_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 
hpet_timer_set_oneshot(uint n,uint64_t deadline)172 zx_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 
hpet_timer_set_periodic(uint n,uint64_t period)196 zx_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 
hpet_is_present(void)222 bool hpet_is_present(void) {
223     return hpet_present;
224 }
225 
hpet_enable(void)226 void hpet_enable(void) {
227     DEBUG_ASSERT(hpet_is_present());
228 
229     AutoSpinLockNoIrqSave guard(&lock);
230     hpet_regs->general_config |= GEN_CONF_EN;
231 }
232 
hpet_disable(void)233 void 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 */
hpet_wait_ms(uint16_t ms)242 void 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