summaryrefslogtreecommitdiff
path: root/arch/x86/machine/lapic.c
blob: 7e8299e3995ab383539830034d8cf35bb25653f0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
/*
 * Copyright (c) 2011-2017 Richard Braun.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

#include <kern/clock.h>
#include <kern/init.h>
#include <kern/log.h>
#include <kern/macros.h>
#include <kern/panic.h>
#include <kern/perfmon.h>
#include <machine/cpu.h>
#include <machine/lapic.h>
#include <machine/pmap.h>
#include <machine/trap.h>
#include <vm/vm_kmem.h>

/*
 * Mask used to check that local APICS are internal.
 */
#define LAPIC_VERSION_MASK 0x10

/*
 * Common bits for registers in the local vector table.
 */
#define LAPIC_LVT_DELIVERY_FIXED    0x00000000
#define LAPIC_LVT_DELIVERY_NMI      0x00000400
#define LAPIC_LVT_DELIVERY_EXTINT   0x00000700
#define LAPIC_LVT_MASK_INTR         0x00010000

/*
 * LVT timer register bits.
 */
#define LAPIC_LVT_TIMER_PERIODIC    0x00020000

/*
 * Various values related to the local APIC timer.
 */
#define LAPIC_TIMER_DCR_DIV1    0x0000000b
#define LAPIC_TIMER_COUNT_MAX   0xffffffff

/*
 * Delay used to calibrate the local APIC timer, in microseconds.
 */
#define LAPIC_TIMER_CAL_DELAY   100000

/*
 * Spurious-interrupt vector register bits.
 */
#define LAPIC_SVR_SOFT_EN 0x00000100

/*
 * Interrupt command register (lower word) bits.
 */
#define LAPIC_ICR_VECTOR_MASK           0x000000ff
#define LAPIC_ICR_DELIVERY_INIT         0x00000500
#define LAPIC_ICR_DELIVERY_STARTUP      0x00000600
#define LAPIC_ICR_STATUS_PENDING        0x00001000
#define LAPIC_ICR_LEVEL_ASSERT          0x00004000
#define LAPIC_ICR_TRIGGER_LEVEL         0x00008000
#define LAPIC_ICR_DEST_SELF             0x00040000
#define LAPIC_ICR_DEST_ALL_WITH_SELF    0x00080000
#define LAPIC_ICR_DEST_ALL_EXCEPT_SELF  0x000c0000
#define LAPIC_ICR_DEST_MASK             0x000c0000
#define LAPIC_ICR_RESERVED              0xfff32000

/*
 * ICR destination shift and mask.
 */
#define LAPIC_DEST_SHIFT    24
#define LAPIC_DEST_MASK     0xff000000

/*
 * Local APIC registers are accessed with 32-bits loads/stores aligned on
 * 128 bits.
 */
struct lapic_register {
    uint32_t reg;
    uint32_t reserved0;
    uint32_t reserved1;
    uint32_t reserved2;
};

/*
 * Local APIC register map.
 */
struct lapic_map {
    const struct lapic_register reserved0;
    const struct lapic_register reserved1;

    /*
     * Some processors don't allow writing to this register, and the
     * specification explicitely discourages modifications. Consider it
     * read only.
     */
    const struct lapic_register id;
    const struct lapic_register version;
    const struct lapic_register reserved2;
    const struct lapic_register reserved3;
    const struct lapic_register reserved4;
    const struct lapic_register reserved5;
    struct lapic_register tpr;
    const struct lapic_register reserved6; /* APR */
    const struct lapic_register ppr;
    struct lapic_register eoi;
    const struct lapic_register reserved7; /* RRD */
    struct lapic_register ldr;
    struct lapic_register dfr;
    struct lapic_register svr;
    const struct lapic_register isr0;
    const struct lapic_register isr1;
    const struct lapic_register isr2;
    const struct lapic_register isr3;
    const struct lapic_register isr4;
    const struct lapic_register isr5;
    const struct lapic_register isr6;
    const struct lapic_register isr7;
    const struct lapic_register tmr0;
    const struct lapic_register tmr1;
    const struct lapic_register tmr2;
    const struct lapic_register tmr3;
    const struct lapic_register tmr4;
    const struct lapic_register tmr5;
    const struct lapic_register tmr6;
    const struct lapic_register tmr7;
    const struct lapic_register irr0;
    const struct lapic_register irr1;
    const struct lapic_register irr2;
    const struct lapic_register irr3;
    const struct lapic_register irr4;
    const struct lapic_register irr5;
    const struct lapic_register irr6;
    const struct lapic_register irr7;
    struct lapic_register esr;
    const struct lapic_register reserved8;
    const struct lapic_register reserved9;
    const struct lapic_register reserved10;
    const struct lapic_register reserved11;
    const struct lapic_register reserved12;
    const struct lapic_register reserved13;
    struct lapic_register lvt_cmci;
    struct lapic_register icr_low;
    struct lapic_register icr_high;
    struct lapic_register lvt_timer;
    const struct lapic_register reserved14; /* Thermal sensor register */
    struct lapic_register lvt_pmc; /* Performance counters register */
    struct lapic_register lvt_lint0;
    struct lapic_register lvt_lint1;
    struct lapic_register lvt_error;
    struct lapic_register timer_icr;
    const struct lapic_register timer_ccr;
    const struct lapic_register reserved16;
    const struct lapic_register reserved17;
    const struct lapic_register reserved18;
    const struct lapic_register reserved19;
    struct lapic_register timer_dcr;
    const struct lapic_register reserved20;
};

/*
 * Address where local APIC registers are mapped.
 */
static volatile struct lapic_map *lapic_map __read_mostly;

/*
 * Base frequency of the local APIC timer.
 */
static uint32_t lapic_bus_freq __read_mostly;

static uint32_t
lapic_read(const volatile struct lapic_register *r)
{
    return r->reg;
}

static void
lapic_write(volatile struct lapic_register *r, uint32_t value)
{
    r->reg = value;
}

static void __init
lapic_compute_freq(void)
{
    uint32_t c1, c2;

    lapic_write(&lapic_map->svr, LAPIC_SVR_SOFT_EN | TRAP_LAPIC_SPURIOUS);
    lapic_write(&lapic_map->timer_dcr, LAPIC_TIMER_DCR_DIV1);

    /* The APIC timer counter should never wrap around here */
    lapic_write(&lapic_map->timer_icr, LAPIC_TIMER_COUNT_MAX);
    c1 = lapic_read(&lapic_map->timer_ccr);
    cpu_delay(LAPIC_TIMER_CAL_DELAY);
    c2 = lapic_read(&lapic_map->timer_ccr);
    lapic_bus_freq = (c1 - c2) * (1000000 / LAPIC_TIMER_CAL_DELAY);
    log_info("lapic: bus frequency: %u.%02u MHz", lapic_bus_freq / 1000000,
             lapic_bus_freq % 1000000);
    lapic_write(&lapic_map->timer_icr, lapic_bus_freq / CLOCK_FREQ);
    lapic_write(&lapic_map->svr, 0);
}

void
lapic_eoi(void)
{
    lapic_write(&lapic_map->eoi, 0);
}

static void __init
lapic_setup_registers(void)
{
    /*
     * LVT mask bits can only be cleared when the local APIC is enabled.
     * They are kept disabled while the local APIC is disabled.
     */
    lapic_write(&lapic_map->svr, LAPIC_SVR_SOFT_EN | TRAP_LAPIC_SPURIOUS);
    lapic_write(&lapic_map->tpr, 0);
    lapic_write(&lapic_map->eoi, 0);
    lapic_write(&lapic_map->esr, 0);
    lapic_write(&lapic_map->lvt_timer, LAPIC_LVT_TIMER_PERIODIC
                                       | TRAP_LAPIC_TIMER);
    lapic_write(&lapic_map->lvt_lint0, LAPIC_LVT_MASK_INTR);
    lapic_write(&lapic_map->lvt_lint1, LAPIC_LVT_MASK_INTR);
    lapic_write(&lapic_map->lvt_error, TRAP_LAPIC_ERROR);
    lapic_write(&lapic_map->timer_dcr, LAPIC_TIMER_DCR_DIV1);
    lapic_write(&lapic_map->timer_icr, lapic_bus_freq / CLOCK_FREQ);
    lapic_write(&lapic_map->lvt_pmc, TRAP_LAPIC_PMC_OF);
}

void __init
lapic_setup(uint32_t map_addr)
{
    uint32_t value;

    lapic_map = vm_kmem_map_pa(map_addr, sizeof(*lapic_map), NULL, NULL);

    if (lapic_map == NULL) {
        panic("lapic: unable to map registers in kernel map");
    }

    value = lapic_read(&lapic_map->version);

    if ((value & LAPIC_VERSION_MASK) != LAPIC_VERSION_MASK) {
        panic("lapic: external local APIC not supported");
    }

    lapic_compute_freq();
    lapic_setup_registers();
}

void __init
lapic_ap_setup(void)
{
    lapic_setup_registers();
}

static void
lapic_ipi(uint32_t apic_id, uint32_t icr)
{
    unsigned long flags;

    cpu_intr_save(&flags);

    if ((icr & LAPIC_ICR_DEST_MASK) == 0) {
        lapic_write(&lapic_map->icr_high, apic_id << LAPIC_DEST_SHIFT);
    }

    lapic_write(&lapic_map->icr_low, icr & ~LAPIC_ICR_RESERVED);

    cpu_intr_restore(flags);
}

static void
lapic_ipi_wait(void)
{
    uint32_t value;

    do {
        value = lapic_read(&lapic_map->icr_low);
        cpu_pause();
    } while (value & LAPIC_ICR_STATUS_PENDING);
}

void
lapic_ipi_init_assert(uint32_t apic_id)
{
    lapic_ipi(apic_id, LAPIC_ICR_TRIGGER_LEVEL | LAPIC_ICR_LEVEL_ASSERT
                       | LAPIC_ICR_DELIVERY_INIT);
    lapic_ipi_wait();
}

void
lapic_ipi_init_deassert(uint32_t apic_id)
{
    lapic_ipi(apic_id, LAPIC_ICR_TRIGGER_LEVEL | LAPIC_ICR_DELIVERY_INIT);
    lapic_ipi_wait();
}

void
lapic_ipi_startup(uint32_t apic_id, uint32_t vector)
{
    lapic_ipi(apic_id, LAPIC_ICR_DELIVERY_STARTUP
                       | (vector & LAPIC_ICR_VECTOR_MASK));
    lapic_ipi_wait();
}

void
lapic_ipi_send(uint32_t apic_id, uint32_t vector)
{
    lapic_ipi_wait();
    lapic_ipi(apic_id, vector & LAPIC_ICR_VECTOR_MASK);
}

void
lapic_ipi_broadcast(uint32_t vector)
{
    lapic_ipi_wait();
    lapic_ipi(0, LAPIC_ICR_DEST_ALL_EXCEPT_SELF
                 | (vector & LAPIC_ICR_VECTOR_MASK));
}

void
lapic_pmc_of_intr(struct trap_frame *frame)
{
    (void)frame;
#ifdef CONFIG_PERFMON
    perfmon_handle_of_intr(frame);
#endif
    lapic_eoi();
}

void
lapic_timer_intr(struct trap_frame *frame)
{
    (void)frame;

    lapic_eoi();
    clock_tick_intr();
}

void
lapic_error_intr(struct trap_frame *frame)
{
    uint32_t esr;

    (void)frame;
    esr = lapic_read(&lapic_map->esr);
    log_err("lapic: error on cpu%u: esr:%08x", cpu_id(), esr);
    lapic_write(&lapic_map->esr, 0);
    lapic_eoi();
}

void
lapic_spurious_intr(struct trap_frame *frame)
{
    (void)frame;
    log_warning("lapic: spurious interrupt");

    /* No EOI for this interrupt */
}