1#include <acpi/hpet.h>
2#include <acpi/lapic.h>
3#include <global.h>
4#include <irq/irq.h>
5#include <math/align.h>
6#include <smp/core.h>
7#include <time/clock_evdev.h>
8#include <time/lapic_timer.h>
9#include <time/names.h>
10
11#define LAPIC_CALIBRATION_WINDOW_US 2000
12#define LAPIC_CALIBRATION_SAMPLES 5
13
14/* Get the actual elapsed time since vCPU descheduling and whatnot
15 * could alter the amount of time we thought passed */
16static freq_khz_t lapic_timer_calibrate_once(void) {
17 lapic_write(LAPIC_REG_TIMER_INIT, val: 0xFFFFFFFF);
18 uint32_t start_ticks = lapic_read(LAPIC_REG_TIMER_CUR);
19 uint64_t start_us = hpet_timestamp_us();
20
21 while (hpet_timestamp_us() - start_us < LAPIC_CALIBRATION_WINDOW_US)
22 cpu_pause();
23
24 uint32_t end_ticks = lapic_read(LAPIC_REG_TIMER_CUR);
25 uint64_t elapsed_us = hpet_timestamp_us() - start_us;
26
27 return ((uint64_t) (start_ticks - end_ticks) * 1000) / elapsed_us;
28}
29
30static freq_khz_t lapic_timer_calibrate(void) {
31 freq_khz_t samples[LAPIC_CALIBRATION_SAMPLES];
32
33 for (size_t i = 0; i < LAPIC_CALIBRATION_SAMPLES; i++) {
34 freq_khz_t s = lapic_timer_calibrate_once();
35 size_t j = i;
36 for (; j > 0 && samples[j - 1] > s; j--)
37 samples[j] = samples[j - 1];
38
39 samples[j] = s;
40 }
41
42 return samples[LAPIC_CALIBRATION_SAMPLES / 2];
43}
44
45void lapic_timer_init(cpu_id_t core_id) {
46 lapic_write(LAPIC_REG_SVR, LAPIC_ENABLE | 0xFF);
47 lapic_write(LAPIC_REG_TIMER_DIV, val: 0b0011);
48 lapic_write(LAPIC_REG_LVT_TIMER,
49 IRQ_TIMER | LAPIC_LVT_MASK | TIMER_MODE_ONESHOT);
50
51 global.cores[core_id]->lapic_khz = lapic_timer_calibrate();
52 lapic_timer_disable();
53}
54
55void lapic_timer_init_bsp(void) {
56 lapic_timer_init(core_id: 0);
57}
58
59void lapic_timer_disable(void) {
60 uint32_t lvt = lapic_read(LAPIC_REG_LVT_TIMER);
61 lvt |= LAPIC_LVT_MASK;
62 lapic_write(LAPIC_REG_LVT_TIMER, val: lvt);
63}
64
65void lapic_timer_enable(void) {
66 uint32_t lvt = lapic_read(LAPIC_REG_LVT_TIMER);
67 lvt &= ~LAPIC_LVT_MASK;
68 lapic_write(LAPIC_REG_LVT_TIMER, val: lvt);
69}
70
71bool lapic_timer_is_enabled(void) {
72 uint32_t lvt = lapic_read(LAPIC_REG_LVT_TIMER);
73 return !BIT_TEST(lvt, 16);
74}
75
76static enum err lapic_evdev_set_next_event(struct clock_evdev *ced,
77 time_ns_t delta_ns) {
78 cc_ignore(ced);
79
80 /* The timer context guarantees that set_next_event happens under HIGH */
81 freq_khz_t freq_khz = smp_core(cond: TOPC_IRQL)->lapic_khz;
82 uint64_t ticks = (freq_khz * (uint64_t) delta_ns) / 1000000ULL;
83
84 if (ticks == 0)
85 ticks = 1;
86
87 if (ticks > 0xFFFFFFFFULL)
88 ticks = 0xFFFFFFFFULL;
89
90 uint32_t lvt = lapic_read(LAPIC_REG_LVT_TIMER);
91 lvt &= ~(TIMER_MODE_PERIODIC | LAPIC_LVT_MASK);
92 lvt |= (IRQ_TIMER | TIMER_MODE_ONESHOT);
93 lapic_write(LAPIC_REG_LVT_TIMER, val: lvt);
94
95 /* Oneshot countdown */
96 lapic_write(LAPIC_REG_TIMER_INIT, val: (uint32_t) ticks);
97
98 return 0;
99}
100
101static enum err lapic_evdev_change_state(struct clock_evdev *ced,
102 enum clock_evdev_state state) {
103 cc_ignore(ced);
104 uint32_t lvt = lapic_read(LAPIC_REG_LVT_TIMER);
105
106 switch (state) {
107 case CLOCK_EVDEV_STATE_ONESHOT:
108 lvt &= ~(TIMER_MODE_PERIODIC | LAPIC_LVT_MASK);
109 lvt |= (IRQ_TIMER | TIMER_MODE_ONESHOT);
110 lapic_write(LAPIC_REG_LVT_TIMER, val: lvt);
111 break;
112
113 case CLOCK_EVDEV_STATE_PERIODIC:
114 lvt &= ~LAPIC_LVT_MASK;
115 lvt |= (IRQ_TIMER | TIMER_MODE_PERIODIC);
116 lapic_write(LAPIC_REG_LVT_TIMER, val: lvt);
117 break;
118
119 case CLOCK_EVDEV_STATE_OFF:
120 case CLOCK_EVDEV_STATE_ONESHOT_STOPPED:
121 lapic_write(LAPIC_REG_TIMER_INIT, val: 0);
122 lvt |= LAPIC_LVT_MASK;
123 lapic_write(LAPIC_REG_LVT_TIMER, val: lvt);
124 break;
125 }
126
127 ced->state = state;
128 return 0;
129}
130
131static struct clock_evdev *lapic_clock_evdev_create(cpu_id_t core_id) {
132 static const uint64_t nanoseconds_per_khz_tick = 1000000;
133
134 struct clock_evdev *ced = clock_evdev_create(fmt: "lapic_timer_%zu", core_id);
135
136 ced->set_next_event = lapic_evdev_set_next_event;
137 ced->change_state = lapic_evdev_change_state;
138
139 /* bounds based on LAPIC frequency */
140 freq_khz_t freq_khz = global.cores[core_id]->lapic_khz;
141
142 ced->min_delta_ns =
143 (time_ns_t) DIV_ROUND_UP(nanoseconds_per_khz_tick, freq_khz);
144 ced->max_delta_ns = (0xFFFFFFFFULL * 1000000ULL) / freq_khz;
145
146 ced->min_delta_ticks = 1;
147 ced->max_delta_ticks = 0xFFFFFFFF;
148
149 ced->flags =
150 CLOCK_EVDEV_ONESHOT | CLOCK_EVDEV_PERCPU | CLOCK_EVDEV_TICK_SUITABLE;
151 ced->rating = CLOCK_RATING_BEST;
152 ced->bound_to_cpu = core_id;
153 ced->irq = IRQ_TIMER;
154
155 /* ready LAPIC divider and vector */
156 lapic_write(LAPIC_REG_TIMER_DIV, val: 0b0011);
157
158 lapic_evdev_change_state(ced, state: CLOCK_EVDEV_STATE_ONESHOT_STOPPED);
159 return ced;
160}
161
162void lapic_clock_evdev_group_init(void) {
163 struct clock_evdev_group *cedg =
164 nofail(clock_evdev_group_create(CLOCK_NAME_LAPIC));
165
166 /* No need to set evdev_for_cpu if CLOCK_EVDEV_GROUP_PERCPU set */
167 cedg->flags = CLOCK_EVDEV_GROUP_PERCPU;
168
169 size_t i;
170 for_each_cpu_id(i) {
171 clock_evdev_group_add(cedg, ced: lapic_clock_evdev_create(core_id: i));
172 }
173
174 clock_evdev_group_register(cedg);
175}
176