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/* Implementation of a logical timer for pc99 platforms
13 *
14 * We try to use the HPET, but if that doesn't work we use the PIT.
15 */
16#include <platsupport/plat/timer.h>
17#include <platsupport/arch/tsc.h>
18#include <platsupport/pmem.h>
19#include <utils/util.h>
20#include <platsupport/plat/acpi/acpi.h>
21#include <platsupport/plat/hpet.h>
22
23#include "../../ltimer.h"
24
25/* This is duplicated from constants.h in libsel4 for the moment. Interrupt allocation
26   shouldn't be happening here in this driver, until that is fixed this hack is needed */
27#define IRQ_OFFSET (0x20 + 16)
28
29typedef enum {
30    HPET,
31    PIT
32} pc99_timer_t;
33
34typedef struct {
35    pc99_timer_t type;
36    /* we are either using the HPET or the PIT */
37    union {
38        struct {
39             hpet_t device;
40             pmem_region_t region;
41             uint64_t period;
42             hpet_config_t config;
43        } hpet;
44        struct {
45            pit_t device;
46            uint32_t freq;
47            /* the PIT can only set short timeouts - if we have
48             * set intermediate irqs we track when the actual timeout is due here */
49            uint64_t abs_time;
50        } pit;
51    };
52    ps_irq_t irq;
53    ps_io_ops_t ops;
54    irq_id_t irq_id;
55    ltimer_callback_fn_t user_callback;
56    void *user_callback_token;
57} pc99_ltimer_t;
58
59static size_t get_num_irqs(void *data)
60{
61    assert(data != NULL);
62
63    /* both PIT and HPET only have one irq */
64    return 1;
65}
66
67static int get_nth_irq(void *data, size_t n, ps_irq_t *irq)
68{
69
70    assert(data != NULL);
71    assert(irq != NULL);
72    assert(n == 0);
73
74    pc99_ltimer_t *pc99_ltimer = data;
75    *irq = pc99_ltimer->irq;
76    return 0;
77}
78
79static size_t get_num_pmems(void *data)
80{
81    assert(data != NULL);
82    pc99_ltimer_t *pc99_ltimer = data;
83
84    return pc99_ltimer->type == HPET ? 1 : 0;
85}
86
87static int hpet_ltimer_get_nth_pmem(void *data, size_t n, pmem_region_t *pmem)
88{
89    assert(data != NULL);
90    assert(pmem != NULL);
91    assert(n == 0);
92
93    pc99_ltimer_t *pc99_ltimer = data;
94    *pmem = pc99_ltimer->hpet.region;
95    return 0;
96}
97
98static int pit_ltimer_handle_irq(pc99_ltimer_t *pc99_ltimer)
99{
100    if (!pc99_ltimer->pit.abs_time) {
101        /* nothing to do */
102        return 0;
103    }
104
105    uint64_t time = tsc_get_time(pc99_ltimer->pit.freq);
106    if (time > pc99_ltimer->pit.abs_time) {
107        /* we're done here */
108        pc99_ltimer->pit.abs_time = 0;
109        return 0;
110    }
111
112    /* otherwise need to set another irq */
113    uint64_t ns = MIN(pc99_ltimer->pit.abs_time - time, PIT_MAX_NS);
114    if (ns < PIT_MIN_NS) {
115        return 0;
116    }
117    return pit_set_timeout(&pc99_ltimer->pit.device, ns, false);
118}
119
120static int hpet_ltimer_handle_irq(pc99_ltimer_t *pc99_ltimer)
121{
122    /* our hpet driver doesn't do periodic timeouts, so emulate them here */
123    if (pc99_ltimer->hpet.period > 0) {
124        // try a few times to set a timeout. If we continuously get ETIME then we have
125        // no choice but to panic as there is no meaningful error we can return here
126        // that will allow the user to work out what happened and recover
127        // The whole time we are doing this we are of course losing time as we have to keep on
128        // retrying the timeout with a new notion of the current time, but there is nothing
129        // better we can do with this interface
130        int retries = 10;
131        int error;
132        do {
133            error = hpet_set_timeout(&pc99_ltimer->hpet.device,
134                hpet_get_time(&pc99_ltimer->hpet.device) + pc99_ltimer->hpet.period);
135            retries--;
136        } while (error == ETIME && retries > 0);
137        if (error == ETIME) {
138            ZF_LOGF("Failed to reprogram periodic timeout. Unable to continue");
139        }
140        if (error != 0) {
141            ZF_LOGF("Unexpected error when reprogramming periodic timeout. Unable to continue.");
142        }
143    }
144    return 0;
145}
146
147static void handle_irq(void *data, ps_irq_acknowledge_fn_t acknowledge_fn, void *ack_data)
148{
149    assert(data != NULL);
150    pc99_ltimer_t *pc99_ltimer = data;
151
152    /* pc99 timer interrupts are edge triggered so acknowledge now */
153    int UNUSED error = acknowledge_fn(ack_data);
154    assert(!error);
155
156    error = pc99_ltimer->type == PIT ? pit_ltimer_handle_irq(pc99_ltimer)
157                                     : hpet_ltimer_handle_irq(pc99_ltimer);
158    assert(!error);
159
160    /* the only interrupts we get are from timeout interrupts */
161    if (pc99_ltimer->user_callback) {
162        pc99_ltimer->user_callback(pc99_ltimer->user_callback_token, LTIMER_TIMEOUT_EVENT);
163    }
164}
165
166static int hpet_ltimer_get_time(void *data, uint64_t *time)
167{
168    assert(data != NULL);
169    assert(time != NULL);
170
171    pc99_ltimer_t *pc99_ltimer = data;
172    *time = hpet_get_time(&pc99_ltimer->hpet.device);
173    return 0;
174}
175
176static int pit_ltimer_get_time(void *data, uint64_t *time)
177{
178    pc99_ltimer_t *pc99_ltimer = data;
179    *time = tsc_get_time(pc99_ltimer->pit.freq);
180    return 0;
181}
182
183static int get_resolution(void *data, uint64_t *resolution)
184{
185    return ENOSYS;
186}
187
188static int hpet_ltimer_set_timeout(void *data, uint64_t ns, timeout_type_t type)
189{
190    assert(data != NULL);
191    pc99_ltimer_t *pc99_ltimer = data;
192
193    if (type == TIMEOUT_PERIODIC) {
194        pc99_ltimer->hpet.period = ns;
195    } else {
196        pc99_ltimer->hpet.period = 0;
197    }
198
199    if (type != TIMEOUT_ABSOLUTE) {
200        ns += hpet_get_time(&pc99_ltimer->hpet.device);
201    }
202
203    return hpet_set_timeout(&pc99_ltimer->hpet.device, ns);
204}
205
206static int pit_ltimer_set_timeout(void *data, uint64_t ns, timeout_type_t type)
207{
208    assert(data != NULL);
209    pc99_ltimer_t *pc99_ltimer = data;
210
211    /* we are overriding any existing timeouts */
212    pc99_ltimer->pit.abs_time = 0;
213
214    uint64_t time = tsc_get_time(pc99_ltimer->pit.freq);
215    switch (type) {
216    case TIMEOUT_RELATIVE:
217        if (ns > PIT_MAX_NS) {
218            pc99_ltimer->pit.abs_time = ns + time;
219            ns = PIT_MAX_NS;
220        }
221        break;
222    case TIMEOUT_ABSOLUTE:
223        if (ns <= time) {
224            return ETIME;
225        }
226        pc99_ltimer->pit.abs_time = ns;
227        ns = MIN(PIT_MAX_NS, ns - time);
228        break;
229    case TIMEOUT_PERIODIC:
230        if (ns > PIT_MAX_NS) {
231            ZF_LOGE("Periodic timeouts %u not implemented for PIT ltimer", (uint32_t) PIT_MAX_NS);
232            return ENOSYS;
233        }
234        break;
235    }
236
237    int error = pit_set_timeout(&pc99_ltimer->pit.device, ns, type == TIMEOUT_PERIODIC);
238    if (error == EINVAL && type == TIMEOUT_ABSOLUTE ) {
239        /* we capped the value we set at the highest value for the PIT, however this
240         * could still have been too small - in this case the absolute timeout has
241         * already passed */
242        return ETIME;
243    }
244
245    return error;
246}
247
248static int pit_ltimer_reset(void *data)
249{
250    assert(data != NULL);
251    pc99_ltimer_t *pc99_ltimer = data;
252    pit_cancel_timeout(&pc99_ltimer->pit.device);
253    return 0;
254}
255
256static int hpet_ltimer_reset(void *data)
257{
258    assert(data != NULL);
259    pc99_ltimer_t *pc99_ltimer = data;
260
261    hpet_stop(&pc99_ltimer->hpet.device);
262    hpet_start(&pc99_ltimer->hpet.device);
263    pc99_ltimer->hpet.period = 0;
264    return 0;
265}
266
267static void destroy(void *data)
268{
269    assert(data);
270
271    pc99_ltimer_t *pc99_ltimer = data;
272
273    if (pc99_ltimer->type == HPET && pc99_ltimer->hpet.config.vaddr) {
274        hpet_stop(&pc99_ltimer->hpet.device);
275        ps_pmem_unmap(&pc99_ltimer->ops, pc99_ltimer->hpet.region, pc99_ltimer->hpet.config.vaddr);
276    } else {
277        assert(pc99_ltimer->type == PIT);
278        pit_cancel_timeout(&pc99_ltimer->pit.device);
279    }
280
281    if (pc99_ltimer->irq_id > PS_INVALID_IRQ_ID) {
282        ZF_LOGF_IF(ps_irq_unregister(&pc99_ltimer->ops.irq_ops, pc99_ltimer->irq_id),
283                   "Failed to clean-up the IRQ ID!");
284    }
285
286    ps_free(&pc99_ltimer->ops.malloc_ops, sizeof(pc99_ltimer), pc99_ltimer);
287}
288
289static inline int
290ltimer_init_common(ltimer_t *ltimer, ps_io_ops_t ops, ltimer_callback_fn_t callback, void *callback_token)
291{
292    pc99_ltimer_t *pc99_ltimer = ltimer->data;
293    pc99_ltimer->ops = ops;
294    pc99_ltimer->user_callback = callback;
295    pc99_ltimer->user_callback_token = callback_token;
296    ltimer->destroy = destroy;
297
298    /* setup the interrupts */
299    pc99_ltimer->irq_id = ps_irq_register(&ops.irq_ops, pc99_ltimer->irq, handle_irq,
300                                          pc99_ltimer);
301    if (pc99_ltimer->irq_id < 0) {
302        return EIO;
303    }
304
305    return 0;
306}
307
308static int ltimer_hpet_init_internal(ltimer_t *ltimer, ps_io_ops_t ops, ltimer_callback_fn_t callback,
309                                     void *callback_token)
310{
311    pc99_ltimer_t *pc99_ltimer = ltimer->data;
312
313    int error = ltimer_init_common(ltimer, ops, callback, callback_token);
314    if (error) {
315        destroy(pc99_ltimer);
316        return -1;
317    }
318
319    /* map in the paddr */
320    pc99_ltimer->hpet.config.vaddr = ps_pmem_map(&ops, pc99_ltimer->hpet.region, false, PS_MEM_NORMAL);
321    if (pc99_ltimer->hpet.config.vaddr == NULL) {
322        destroy(pc99_ltimer);
323        return -1;
324    }
325
326    ltimer->get_time = hpet_ltimer_get_time;
327    ltimer->get_resolution = get_resolution;
328    ltimer->set_timeout = hpet_ltimer_set_timeout;
329    ltimer->reset = hpet_ltimer_reset;
330
331    /* check if we have requested the IRQ that collides with the PIT. This check is not
332     * particularly robust, as the legacy PIT route does not *have* to live on pin 2
333     * and to be more accurate we should check the ACPI tables instead, but that is
334     * difficult to do here and we shall ignore as an unlikely case */
335    if (pc99_ltimer->hpet.config.ioapic_delivery && pc99_ltimer->hpet.config.irq == 2) {
336        /* put the PIT into a known state to disable it from counting and genering interrupts */
337        pit_t temp_pit;
338        /* the pit_init function declares that it may only be called once, we can only hope that
339         * it hasn't been called before and carry on */
340        error = pit_init(&temp_pit, ops.io_port_ops);
341        if (!error) {
342            error = pit_cancel_timeout(&temp_pit);
343            if (error) {
344                /* if we fail to operate on an initialized pit then assume nothing is sane and abort */
345                ZF_LOGE("PIT command failed!");
346                return error;
347            } else {
348                ZF_LOGI("Disabled PIT under belief it was using same interrupt as HPET, and "
349                        "this driver does not support interrupt sharing.");
350            }
351        } else {
352            ZF_LOGW("Could not ensure PIT was not counting on pin 2, you may get spurious interrupts");
353        }
354    }
355
356    error = hpet_init(&pc99_ltimer->hpet.device, pc99_ltimer->hpet.config);
357    if (!error) {
358        error = hpet_start(&pc99_ltimer->hpet.device);
359    }
360
361    return error;
362}
363
364int ltimer_default_init(ltimer_t *ltimer, ps_io_ops_t ops, ltimer_callback_fn_t callback, void *callback_token)
365{
366    int error = ltimer_default_describe(ltimer, ops);
367    if (error) {
368        return error;
369    }
370
371    pc99_ltimer_t *pc99_ltimer = ltimer->data;
372    if (pc99_ltimer->type == PIT) {
373        return ltimer_pit_init(ltimer, ops, callback, callback_token);
374    } else {
375        assert(pc99_ltimer->type == HPET);
376        return ltimer_hpet_init_internal(ltimer, ops, callback, callback_token);
377    }
378}
379
380int ltimer_hpet_init(ltimer_t *ltimer, ps_io_ops_t ops, ps_irq_t irq, pmem_region_t region,
381                     ltimer_callback_fn_t callback, void *callback_token)
382{
383    int error = ltimer_hpet_describe(ltimer, ops, irq, region);
384    if (error) {
385        return error;
386    }
387
388    return ltimer_hpet_init_internal(ltimer, ops, callback, callback_token);
389}
390
391int ltimer_pit_init_freq(ltimer_t *ltimer, ps_io_ops_t ops, uint64_t freq, ltimer_callback_fn_t callback,
392                         void *callback_token)
393{
394    int error = ltimer_pit_describe(ltimer, ops);
395    if (error) {
396        return error;
397    }
398
399    pc99_ltimer_t *pc99_ltimer = ltimer->data;
400
401    error = ltimer_init_common(ltimer, ops, callback, callback_token);
402    if (error) {
403        destroy(pc99_ltimer);
404        return error;
405    }
406
407    ltimer->get_time = pit_ltimer_get_time;
408    ltimer->get_resolution = get_resolution;
409    ltimer->set_timeout = pit_ltimer_set_timeout;
410    ltimer->reset = pit_ltimer_reset;
411    pc99_ltimer->pit.freq = freq;
412    return pit_init(&pc99_ltimer->pit.device, ops.io_port_ops);
413}
414
415int ltimer_pit_init(ltimer_t *ltimer, ps_io_ops_t ops, ltimer_callback_fn_t callback, void *callback_token)
416{
417    int error = ltimer_pit_init_freq(ltimer, ops, 0, callback, callback_token);
418    if (error) {
419        return error;
420    }
421
422    /* now calculate the tsc freq */
423    pc99_ltimer_t *pc99_ltimer = ltimer->data;
424    pc99_ltimer->pit.freq = tsc_calculate_frequency_pit(&pc99_ltimer->pit.device);
425    if (pc99_ltimer->pit.freq == 0) {
426        ltimer_destroy(ltimer);
427        return ENOSYS;
428    }
429    return 0;
430}
431
432uint32_t ltimer_pit_get_tsc_freq(ltimer_t *ltimer)
433{
434    pc99_ltimer_t *pc99_ltimer = ltimer->data;
435    return pc99_ltimer->pit.freq;
436}
437
438int _ltimer_default_describe(ltimer_t *ltimer, ps_io_ops_t ops, acpi_t *acpi)
439{
440    pmem_region_t hpet_region;
441
442    int error = (acpi != NULL) ? hpet_parse_acpi(acpi, &hpet_region): 1;
443
444    if (!error) {
445        ps_irq_t irq;
446        error = ltimer_hpet_describe_with_region(ltimer, ops, hpet_region, &irq);
447    }
448
449    if (error) {
450        /* HPET failed - use the pit */
451        error = ltimer_pit_describe(ltimer, ops);
452    }
453
454    return error;
455}
456
457int ltimer_default_describe(ltimer_t *ltimer, ps_io_ops_t ops)
458{
459    acpi_t *acpi = acpi_init(ops.io_mapper);
460    return _ltimer_default_describe(ltimer, ops, acpi);
461}
462
463int ltimer_default_describe_with_rsdp(ltimer_t *ltimer, ps_io_ops_t ops, acpi_rsdp_t rsdp)
464{
465    acpi_t *acpi = acpi_init_with_rsdp(ops.io_mapper, rsdp);
466    return _ltimer_default_describe(ltimer, ops, acpi);
467}
468
469int ltimer_hpet_describe_with_region(ltimer_t *ltimer, ps_io_ops_t ops, pmem_region_t region, ps_irq_t *irq)
470{
471    /* try to map the HPET to query its properties */
472    void *vaddr = ps_pmem_map(&ops, region, false, PS_MEM_NORMAL);
473    if (vaddr == NULL) {
474        return ENOSYS;
475    }
476
477    /* first try to use MSIs */
478    if (hpet_supports_fsb_delivery(vaddr)) {
479        irq->type = PS_MSI;
480        irq->msi.pci_bus = 0;
481        irq->msi.pci_dev = 0;
482        irq->msi.pci_func = 0;
483        irq->msi.handle = 0;
484        irq->msi.vector = DEFAULT_HPET_MSI_VECTOR;
485    } else {
486     /* try a IOAPIC */
487        irq->type = PS_IOAPIC;
488        irq->ioapic.pin = FFS(hpet_ioapic_irq_delivery_mask(vaddr)) - 1;
489        irq->ioapic.level = hpet_level(vaddr);
490        /* HPET is always active high polarity */
491        irq->ioapic.polarity = 1;
492        /* HPET always delivers to the first I/O APIC */
493        irq->ioapic.ioapic = 0;
494        irq->ioapic.vector = 0; /* TODO how to work this out properly */
495    }
496
497    ps_pmem_unmap(&ops, region, vaddr);
498    return ltimer_hpet_describe(ltimer, ops, *irq, region);
499}
500
501int ltimer_pit_describe(ltimer_t *ltimer, ps_io_ops_t ops)
502{
503    int error = ps_calloc(&ops.malloc_ops, 1, sizeof(pc99_ltimer_t), &ltimer->data);
504    if (error) {
505        return error;
506    }
507
508    pc99_ltimer_t *pc99_ltimer = ltimer->data;
509    pc99_ltimer->type = PIT;
510    if (config_set(CONFIG_IRQ_IOAPIC)) {
511        /* Use the IOAPIC if we can */
512        pc99_ltimer->irq = (ps_irq_t) { .type = PS_IOAPIC, .ioapic = { .ioapic = 0, .pin = PIT_INTERRUPT,
513                                                                       .level = 0, .polarity = 0,
514                                                                       .vector = PIT_INTERRUPT }};
515    } else {
516        /* Default to the PIC */
517        pc99_ltimer->irq = (ps_irq_t) { .type = PS_INTERRUPT, .irq = { .number = PIT_INTERRUPT }};
518    }
519    pc99_ltimer->irq_id = PS_INVALID_IRQ_ID;
520    ltimer->get_num_irqs = get_num_irqs;
521    ltimer->get_num_pmems = get_num_pmems;
522    ltimer->get_nth_irq = get_nth_irq;
523    return 0;
524}
525
526int ltimer_hpet_describe(ltimer_t *ltimer, ps_io_ops_t ops, ps_irq_t irq, pmem_region_t region)
527{
528    int error = ps_calloc(&ops.malloc_ops, 1, sizeof(pc99_ltimer_t), &ltimer->data);
529    if (error) {
530        return error;
531    }
532
533    pc99_ltimer_t *pc99_ltimer = ltimer->data;
534    pc99_ltimer->type = HPET;
535    pc99_ltimer->irq_id = PS_INVALID_IRQ_ID;
536    ltimer->get_num_irqs = get_num_irqs;
537    ltimer->get_nth_irq = get_nth_irq;
538    ltimer->get_num_pmems = get_num_pmems;
539    ltimer->get_nth_pmem = hpet_ltimer_get_nth_pmem;
540
541    pc99_ltimer->hpet.region = region;
542    pc99_ltimer->hpet.config.irq = irq.type == PS_MSI ? irq.msi.vector + IRQ_OFFSET : irq.ioapic.pin;
543    pc99_ltimer->hpet.config.ioapic_delivery = (irq.type == PS_IOAPIC);
544    pc99_ltimer->irq = irq;
545
546    return 0;
547}
548