1/*
2 * Copyright 2017, Data61
3 * Commonwealth Scientific and Industrial Research Organisation (CSIRO)
4 * ABN 41 687 119 230.
5 *
6 * This software may be distributed and modified according to the terms of
7 * the BSD 2-Clause license. Note that NO WARRANTY is provided.
8 * See "LICENSE_BSD2.txt" for details.
9 *
10 * @TAG(DATA61_BSD)
11 */
12
13#include <autoconf.h>
14#include <platsupport/gen_config.h>
15#include <errno.h>
16
17#include <platsupport/timer.h>
18#include <platsupport/plat/hpet.h>
19#include <platsupport/plat/acpi/acpi.h>
20
21#include <stdbool.h>
22#include <stdio.h>
23#include <stdlib.h>
24#include <inttypes.h>
25
26#include <utils/attribute.h>
27#include <utils/util.h>
28#include <utils/fence.h>
29
30/* HPET timer config bits - these can't be changed, but allow us to
31 * find out details of the timer */
32
33enum {
34    /* 0 is reserved */
35    /* 0 if edge triggered, 1 if level triggered. */
36    TN_INT_TYPE_CNF = 1,
37    /* Set to 1 to cause an interrupt when main timer hits comparator for this timer */
38    TN_INT_ENB_CNF = 2,
39    /* If this bit is 1 you can write a 1 to it for periodic interrupts,
40     * or a 0 for non-periodic interrupts */
41    TN_TYPE_CNF = 3,
42    /* If this bit is 1, hardware supports periodic mode for this timer */
43    TN_PER_INT_CAP = 4,
44    /* 1 = timer is 64 bit, 0 = timer is 32 bit */
45    TN_SIZE_CAP = 5,
46    /* Writing 1 to this bit allows software to directly set a periodic timers accumulator */
47    TN_VAL_SET_CNF = 6,
48    /* 7 is reserved */
49    /* Set this bit to force the timer to be a 32-bit timer (only works on a 64-bit timer) */
50    TN_32MODE_CNF = 8,
51    /* 5 bit wide field (9:13). Specifies routing for IO APIC if using */
52    TN_INT_ROUTE_CNF = 9,
53    /* Set this bit to force interrupt delivery to the front side bus, don't use the IO APIC */
54    TN_FSB_EN_CNF = 14,
55    /* If this bit is one, bit TN_FSB_EN_CNF can be set */
56    TN_FSB_INT_DEL_CAP = 15,
57    /* Bits 16:31 are reserved */
58    /* Read-only 32-bit field that specifies which routes in the IO APIC this timer can be configured
59       to take */
60    TN_INT_ROUTE_CAP = 32
61};
62
63/* General HPET config bits */
64enum {
65    /* 1 if main counter is running and interrupts are enabled */
66    ENABLE_CNF = 0,
67    /* 1 if LegacyReplacementRoute is being used */
68    LEG_RT_CNF = 1
69};
70
71/* MSI registers - used to configure front side bus delivery of the
72 * HPET interrupt. This allows us to avoid writing an I/O APIC driver.
73 *
74 * For details see section 10.10 "APIC message passing mechanism
75 * and protocol (P6 family,pentium processors)" in "Intel 64 and IA-32
76 * Architectures Software Developers Manual, Volume 3 (3A, 3B & 3C),
77 * System Programming Guide" */
78/* Message value register layout */
79enum {
80    /* 0:7 irq_vector */
81    IRQ_VECTOR = 0,
82    /* 8:10 */
83    DELIVERY_MODE = 8,
84    /* 11:13 reserved */
85    LEVEL_TRIGGER = 14,
86    TRIGGER_MODE = 15,
87    /* 16:32 reserved */
88};
89
90/* Message address register layout */
91enum {
92    /* 0:1 reserved */
93    DESTINATION_MODE = 2,
94    REDIRECTION_HINT = 3,
95    /* 4:11 reserved */
96    /* 12:19 Destination ID */
97    DESTINATION_ID = 12,
98    /* 20:31 Fixed value 0x0FEE */
99    FIXED = 20
100};
101
102#define CAP_ID_REG 0x0
103#define GENERAL_CONFIG_REG 0x10
104#define MAIN_COUNTER_REG 0xF0
105#define TIMERS_OFFSET 0x100
106
107static inline uint64_t *hpet_get_general_config(void *vaddr)
108{
109    return (uint64_t*)((uintptr_t)vaddr + GENERAL_CONFIG_REG);
110}
111
112static inline uint64_t *hpet_get_main_counter(void *vaddr)
113{
114    return (uint64_t*)((uintptr_t)vaddr + MAIN_COUNTER_REG);
115}
116
117static inline uint64_t *hpet_get_cap_id(void *vaddr)
118{
119    return (uint64_t*)((uintptr_t)vaddr + CAP_ID_REG);
120}
121
122static inline hpet_timer_t *hpet_get_hpet_timer(void *vaddr, unsigned int timer)
123{
124    return ((hpet_timer_t*)((uintptr_t)vaddr + TIMERS_OFFSET)) + timer;
125}
126
127int hpet_start(const hpet_t *hpet)
128{
129
130    hpet_timer_t *timer = hpet_get_hpet_timer(hpet->base_addr, 0);
131    /* enable the global timer */
132    *hpet_get_general_config(hpet->base_addr) |= BIT(ENABLE_CNF);
133
134    /* make sure the comparator is 0 before we turn time0 on*/
135    timer->comparator = 0llu;
136    COMPILER_MEMORY_RELEASE();
137
138    /* turn timer0 on */
139    timer->config |= BIT(TN_INT_ENB_CNF);
140
141    /* ensure the compiler sends the writes to the hardware */
142    COMPILER_MEMORY_RELEASE();
143    return 0;
144}
145
146int hpet_stop(const hpet_t *hpet)
147{
148    hpet_timer_t *timer = hpet_get_hpet_timer(hpet->base_addr, 0);
149
150    /* turn off timer0 */
151    timer->config &= ~(BIT(TN_INT_ENB_CNF));
152
153    /* turn the global timer off */
154    *hpet_get_general_config(hpet->base_addr) &= ~BIT(ENABLE_CNF);
155
156    /* ensure the compiler sends the writes to the hardware */
157    COMPILER_MEMORY_RELEASE();
158    return 0;
159}
160
161uint64_t hpet_get_time(const hpet_t *hpet)
162{
163    uint64_t time;
164
165    do {
166        time = *hpet_get_main_counter(hpet->base_addr);
167        COMPILER_MEMORY_ACQUIRE();
168        /* race condition on 32-bit systems: check the bottom 32 bits didn't overflow */
169    } while (CONFIG_WORD_SIZE == 32 && ((uint32_t) (time >> 32llu)) != ((uint32_t *)hpet_get_main_counter(hpet->base_addr))[1]);
170
171    return time * hpet->period_ns;
172}
173
174int hpet_set_timeout(const hpet_t *hpet, uint64_t absolute_ns)
175{
176    hpet_timer_t *timer = hpet_get_hpet_timer(hpet->base_addr, 0);
177    uint64_t absolute_fs = absolute_ns / hpet->period_ns;
178
179    timer->comparator = absolute_fs;
180    COMPILER_MEMORY_RELEASE();
181
182    if (hpet_get_time(hpet) > absolute_ns) {
183        return ETIME;
184    }
185
186    return 0;
187}
188
189bool hpet_supports_fsb_delivery(void *vaddr)
190{
191    hpet_timer_t *timer0 = hpet_get_hpet_timer(vaddr, 0);
192    uint32_t timer0_config_low = timer0->config;
193    return !!(timer0_config_low & BIT(TN_FSB_INT_DEL_CAP));
194}
195
196uint32_t hpet_ioapic_irq_delivery_mask(void *vaddr)
197{
198    hpet_timer_t *timer0 = hpet_get_hpet_timer(vaddr, 0);
199    uint32_t irq_mask = timer0->config >> TN_INT_ROUTE_CAP;
200    return irq_mask;
201}
202
203uint32_t hpet_level(void *vaddr)
204{
205    hpet_timer_t *timer0 = hpet_get_hpet_timer(vaddr, 0);
206    return timer0->config & BIT(TN_INT_TYPE_CNF);
207}
208
209int hpet_init(hpet_t *hpet, hpet_config_t config)
210{
211    hpet->base_addr = config.vaddr;
212    hpet_timer_t *hpet_timer = hpet_get_hpet_timer(hpet->base_addr, 0);
213
214    uint32_t timer0_config_low = (uint32_t) hpet_timer->config;
215
216    /* check that this timer is edge triggered */
217    if (timer0_config_low & BIT(TN_INT_TYPE_CNF)) {
218        ZF_LOGE("This driver expects the timer to be edge triggered");
219        return -1;
220    }
221
222    /* check that this timer is 64 bit */
223    if (!(timer0_config_low & BIT(TN_SIZE_CAP))) {
224        ZF_LOGE("This driver expects hpet timer0 to be 64bit");
225        return -1;
226    }
227
228    if (config.ioapic_delivery) {
229        /* Check if this IO/APIC offset is valid */
230        uint32_t irq_mask = hpet_timer->config >> TN_INT_ROUTE_CAP;
231        if (!(BIT(config.irq) & irq_mask)) {
232            ZF_LOGE("IRQ %d not in the support mask 0x%x", config.irq, irq_mask);
233            return -1;
234        }
235        /* Remove any legacy replacement route so our interrupts go where we want them
236         * NOTE: PIT will cease to function from here on */
237        *hpet_get_general_config(hpet->base_addr) &= ~BIT(LEG_RT_CNF);
238        /* Make sure we're not deliverying by MSI */
239        hpet_timer->config &= ~BIT(TN_FSB_EN_CNF);
240        /* Put the IO/APIC offset in (this is called an irq, but in reality it is
241         * an index into whichever IO/APIC the HPET delivers to */
242        hpet_timer->config &= ~(MASK(5) << TN_INT_ROUTE_CNF);
243        hpet_timer->config |= config.irq << TN_INT_ROUTE_CNF;
244    } else {
245        /* check that this timer supports front size bus delivery */
246        if (!(timer0_config_low & BIT(TN_FSB_INT_DEL_CAP))) {
247            ZF_LOGE("Requested fsb delivery, but timer0 does not support");
248            return ENOSYS;
249        }
250
251        /* set timer 0 to delivery interrupts via the front side bus (using MSIs) */
252        hpet_timer->config |= BIT(TN_FSB_EN_CNF);
253
254        /* set up the message address register and message value register so we receive
255         * MSIs for timer 0*/
256        hpet_timer->fsb_irr =
257            /* top 32 bits is the message address register */
258            ((0x0FEEllu << FIXED) << 32llu)
259            /* bottom 32 bits is the message value register */
260            | config.irq;
261    }
262    COMPILER_MEMORY_RELEASE();
263
264    /* read the period of the timer (its in femptoseconds) and calculate no of ticks per ns */
265    uint32_t tick_period_fs = (uint32_t) (*hpet_get_cap_id(hpet->base_addr) >> 32llu);
266    hpet->period_ns = tick_period_fs / 1000000;
267
268    return 0;
269}
270
271int hpet_parse_acpi(acpi_t *acpi, pmem_region_t *region)
272{
273    if (!acpi || !region) {
274        ZF_LOGE("arguments cannot be NULL");
275        return EINVAL;
276    }
277
278    acpi_hpet_t *header = (acpi_hpet_t *) acpi_find_region(acpi, ACPI_HPET);
279    if (header == NULL) {
280        ZF_LOGE("Could not find HPET ACPI header");
281        return ENOSYS;
282    }
283
284    region->base_addr = header->base_address.address;
285    region->length = header->header.length;
286    return 0;
287}
288