Skip to content

Commit 6626519

Browse files
track down and fix spinlocks being reentered within an interrupt causing an infinite spin wait
1 parent 167d31e commit 6626519

File tree

13 files changed

+181
-58
lines changed

13 files changed

+181
-58
lines changed

include/apic.h

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,4 +249,35 @@ void wake_cpu(uint8_t logical_cpu_id);
249249
* @see APIC_SVR
250250
* @see APIC_TPR
251251
*/
252-
void apic_setup_ap();
252+
void apic_setup_ap();
253+
254+
/**
255+
* @brief Serialises all pending CPU operations.
256+
*
257+
* This function enforces a strict ordering of all memory, I/O, and MSR
258+
* operations issued by the CPU prior to the call. It ensures that all writes
259+
* and reads complete and are globally visible before any instructions issued
260+
* after the call are allowed to execute.
261+
*
262+
* Implementation details:
263+
* - `mfence` is used to flush and order all pending memory operations,
264+
* including MMIO writes to devices such as the LAPIC or IOAPIC.
265+
* - `cpuid` is a serialising instruction which drains the CPU pipeline,
266+
* guaranteeing that no later instructions are executed until all earlier
267+
* ones have fully completed. It also serves as a convenient barrier in
268+
* emulators such as QEMU, which may otherwise defer committing MMIO state.
269+
*
270+
* This is particularly important when programming interrupt controllers
271+
* (APIC, IOAPIC, PIC), as their state machines may lose or misroute
272+
* interrupts if configuration writes have not yet taken effect before
273+
* enabling interrupts with `sti`.
274+
*
275+
* @note This function clobbers the general-purpose registers EAX, EBX, ECX,
276+
* and EDX as required by the `cpuid` instruction. It does not return
277+
* any values.
278+
*/
279+
static inline void cpu_serialise(void)
280+
{
281+
__asm__ volatile("mfence; cpuid"
282+
::: "eax", "ebx", "ecx", "edx");
283+
}

include/input.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* @file kinput.h
2+
* @file input.h
33
* @author Craig Edwards ([email protected])
44
* @brief Console line input API for Retro Rocket.
55
*

include/spinlock.h

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616

1717
#pragma once
1818

19+
#include <io.h>
1920
#include <stdint.h>
2021
#include <stdbool.h>
2122

23+
2224
/**
2325
* @typedef spinlock_t
2426
* @brief Opaque 32-bit integer used as a spinlock state.
@@ -76,3 +78,96 @@ static inline void unlock_spinlock(spinlock_t* lock) {
7678
static inline bool try_lock_spinlock(spinlock_t* lock) {
7779
return !__atomic_test_and_set(lock, __ATOMIC_ACQUIRE);
7880
}
81+
82+
/**
83+
* @brief Read the current RFLAGS register.
84+
*
85+
* Captures the full 64-bit RFLAGS value from the CPU.
86+
*
87+
* @return The current value of the RFLAGS register.
88+
*/
89+
static inline uint64_t read_rflags(void) {
90+
uint64_t flags;
91+
__asm__ volatile("pushfq; pop %0" : "=r"(flags));
92+
return flags;
93+
}
94+
95+
/**
96+
* @brief Write a value into the RFLAGS register.
97+
*
98+
* Restores the CPU's RFLAGS register to the specified value.
99+
* Typically used to restore saved interrupt flag state.
100+
*
101+
* @param flags The 64-bit value to load into RFLAGS.
102+
*/
103+
static inline void write_rflags(uint64_t flags) {
104+
__asm__ volatile("push %0; popfq" :: "r"(flags) : "memory", "cc");
105+
}
106+
107+
/**
108+
* @brief Query whether interrupts are currently enabled.
109+
*
110+
* Uses the IF (Interrupt Flag) in the RFLAGS register to determine
111+
* if maskable interrupts are permitted.
112+
*
113+
* @return true if interrupts are enabled (IF = 1), false otherwise.
114+
*/
115+
static inline bool are_interrupts_enabled(void) {
116+
return (read_rflags() & (1ULL << 9)) != 0;
117+
}
118+
119+
/**
120+
* @brief Attempt to acquire a spinlock once (non-blocking).
121+
*
122+
* Performs an atomic compare-and-swap on the lock.
123+
*
124+
* @param lock Pointer to the spinlock variable.
125+
* @return true if the lock was acquired, false otherwise.
126+
*/
127+
static inline bool try_lock(spinlock_t *lock) {
128+
uint32_t expected = 0;
129+
return __atomic_compare_exchange_n(lock, &expected, 1, false, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED);
130+
}
131+
132+
/**
133+
* @brief Busy-wait until a spinlock is acquired.
134+
*
135+
* Continuously calls try_lock() until success.
136+
* Uses the `pause` instruction to reduce contention on hyper-threaded CPUs.
137+
*
138+
* @param lock Pointer to the spinlock variable.
139+
*/
140+
static inline void spin_until_locked(spinlock_t *lock) {
141+
while (!try_lock(lock)) {
142+
__asm__ volatile("pause"); // reduce contention
143+
}
144+
}
145+
146+
/**
147+
* @brief Acquire a spinlock while disabling interrupts.
148+
*
149+
* Saves the current interrupt flag (IF) state into @p flags,
150+
* disables interrupts, then spins until the lock is acquired.
151+
*
152+
* @param lock Pointer to the spinlock variable.
153+
* @param flags Pointer to a uint64_t used to store the saved RFLAGS register.
154+
*/
155+
static inline void lock_spinlock_irq(spinlock_t *lock, uint64_t *flags) {
156+
*flags = read_rflags(); // save current IF
157+
interrupts_off(); // mask interrupts
158+
spin_until_locked(lock); // acquire
159+
}
160+
161+
/**
162+
* @brief Release a spinlock and restore interrupts.
163+
*
164+
* Releases the given spinlock and restores the CPU interrupt flag (IF)
165+
* from the previously saved @p flags value.
166+
*
167+
* @param lock Pointer to the spinlock variable.
168+
* @param flags RFLAGS value saved by lock_spinlock_irq().
169+
*/
170+
static inline void unlock_spinlock_irq(spinlock_t *lock, uint64_t flags) {
171+
__atomic_store_n(lock, 0, __ATOMIC_RELEASE); // unlock
172+
write_rflags(flags); // restore IF exactly as before
173+
}

src/basic/console.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,11 +125,12 @@ void print_statement(struct basic_ctx* ctx)
125125
accept_or_return(PRINT, ctx);
126126
const char* out = printable_syntax(ctx);
127127
if (out) {
128-
lock_spinlock(&console_spinlock);
128+
uint64_t flags;
129+
lock_spinlock_irq(&console_spinlock, &flags);
129130
lock_spinlock(&debug_console_spinlock);
130131
putstring((console*)ctx->cons, out);
131-
unlock_spinlock(&console_spinlock);
132132
unlock_spinlock(&debug_console_spinlock);
133+
unlock_spinlock_irq(&console_spinlock, flags);
133134
}
134135
}
135136

src/e1000.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ void e1000_transmit_init() {
176176
void e1000_handle_receive() {
177177

178178
if (!rx_descs[rx_cur]) {
179-
kprintf("No rx_desc[rx_cur]\n");
179+
dprintf("No rx_desc[rx_cur]\n");
180180
return;
181181
}
182182
netdev_t* dev = get_active_network_device();
@@ -188,7 +188,7 @@ void e1000_handle_receive() {
188188
uint8_t *buf = (uint8_t *) rx_descs[rx_cur]->addr;
189189
uint16_t len = rx_descs[rx_cur]->length;
190190
if (!buf) {
191-
kprintf("No buf\n");
191+
dprintf("No buf\n");
192192
return;
193193
}
194194

src/hashmap.c

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ struct hashmap *hashmap_new_with_allocator(
9494
}
9595
// hashmap + spare + edata
9696
size_t size = sizeof(struct hashmap)+bucketsz*2;
97-
struct hashmap *map = _malloc(size);
97+
struct hashmap *map = kmalloc(size);
9898
if (!map) {
9999
return NULL;
100100
}
@@ -120,10 +120,10 @@ struct hashmap *hashmap_new_with_allocator(
120120
memset(map->buckets, 0, map->bucketsz*map->nbuckets);
121121
map->growat = map->nbuckets*0.75;
122122
map->shrinkat = map->nbuckets*0.10;
123-
map->malloc = _malloc;
124-
map->realloc = _realloc;
125-
map->free = _free;
126-
return map;
123+
map->malloc = kmalloc;
124+
map->realloc = krealloc;
125+
map->free = kfree;
126+
return map;
127127
}
128128

129129

@@ -144,14 +144,7 @@ struct hashmap *hashmap_new_with_allocator(
144144
// The hashmap must be freed with hashmap_free().
145145
// Param `elfree` is a function that frees a specific item. This should be NULL
146146
// unless you're storing some kind of reference data in the hash.
147-
struct hashmap *hashmap_new(size_t elsize, size_t cap,
148-
uint64_t seed0, uint64_t seed1,
149-
uint64_t (*hash)(const void *item,
150-
uint64_t seed0, uint64_t seed1),
151-
int (*compare)(const void *a, const void *b,
152-
void *udata),
153-
void (*elfree)(const void *item),
154-
void *udata)
147+
struct hashmap *hashmap_new(size_t elsize, size_t cap, uint64_t seed0, uint64_t seed1, uint64_t (*hash)(const void *item, uint64_t seed0, uint64_t seed1), int (*compare)(const void *a, const void *b, void *udata), void (*elfree)(const void *item), void *udata)
155148
{
156149
return hashmap_new_with_allocator(
157150
(_malloc?_malloc:kmalloc),

src/idt.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,12 @@ void init_idt() {
103103
pic_remap(0x20, 0x28);
104104
#ifdef USE_IOAPIC
105105
pic_disable();
106+
cpu_serialise();
106107
remap_irqs_to_ioapic();
108+
cpu_serialise();
107109
init_lapic_timer(100);
110+
cpu_serialise();
111+
apic_setup_ap();
108112
#else
109113
pic_enable();
110114
#endif

src/input.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ size_t kinput(size_t maxlen, console* cons)
2626
cons->bufcnt = 0;
2727
}
2828

29-
lock_spinlock(&console_spinlock);
29+
uint64_t flags;
30+
lock_spinlock_irq(&console_spinlock, &flags);
3031
lock_spinlock(&debug_console_spinlock);
3132
switch (cons->last) {
3233
case KEY_UP:
@@ -61,8 +62,8 @@ size_t kinput(size_t maxlen, console* cons)
6162
}
6263
break;
6364
}
64-
unlock_spinlock(&console_spinlock);
6565
unlock_spinlock(&debug_console_spinlock);
66+
unlock_spinlock_irq(&console_spinlock, flags);
6667
/* Terminate string */
6768
*cons->buffer = 0;
6869

src/interrupt.c

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -83,36 +83,33 @@ void local_apic_clear_interrupt()
8383

8484
/* Both the Interrupt() and ISR() functions are dispatched from the assembly code trampoline via a pre-set IDT */
8585

86-
void Interrupt(uint64_t isrnumber, uint64_t errorcode)
87-
{
88-
uint8_t fx[512];
86+
void Interrupt(uint64_t isrnumber, uint64_t errorcode) {
87+
__attribute__((aligned(16))) uint8_t fx[512];
8988
__builtin_ia32_fxsave64(&fx);
90-
for (shared_interrupt_t* si = shared_interrupt[isrnumber]; si; si = si->next) {
89+
90+
for (shared_interrupt_t *si = shared_interrupt[isrnumber]; si; si = si->next) {
9191
/* There is no shared interrupt routing on these interrupts,
9292
* they are purely routed to interested handlers
9393
*/
9494
if (si->interrupt_handler) {
95-
si->interrupt_handler((uint8_t)isrnumber, errorcode, 0, si->opaque);
95+
si->interrupt_handler((uint8_t) isrnumber, errorcode, 0, si->opaque);
9696
}
9797
}
9898

9999
if (isrnumber < 32) {
100100
/* This simple error handler is replaced by a more complex debugger once the system is up */
101-
kprintf("CPU %d halted with exception %016lx, error code %016lx: %s.\n", logical_cpu_id(), isrnumber, errorcode, error_table[isrnumber]);
101+
kprintf("CPU %d halted with exception %016lx, error code %016lx: %s.\n", logical_cpu_id(), isrnumber,
102+
errorcode, error_table[isrnumber]);
102103
wait_forever();
103104
}
104105

105-
#ifdef USE_IOAPIC
106106
local_apic_clear_interrupt();
107-
#else
108-
pic_eoi(isrnumber);
109-
#endif
110107
__builtin_ia32_fxrstor64(&fx);
111108
}
112109

113110
void IRQ(uint64_t isrnumber, uint64_t irqnum)
114111
{
115-
uint8_t fx[512];
112+
__attribute__((aligned(16))) uint8_t fx[512];
116113
__builtin_ia32_fxsave64(&fx);
117114
for (shared_interrupt_t* si = shared_interrupt[isrnumber]; si; si = si->next) {
118115
if (si->device.bits == 0 && si->interrupt_handler) {
@@ -127,14 +124,7 @@ void IRQ(uint64_t isrnumber, uint64_t irqnum)
127124
}
128125
}
129126
}
130-
#ifdef USE_IOAPIC
127+
131128
local_apic_clear_interrupt();
132-
#else
133-
/* IRQ7 is the PIC spurious interrupt, we never acknowledge it */
134-
if (irqnum != IRQ7) {
135-
pic_eoi(isrnumber);
136-
}
137-
#endif
138129
__builtin_ia32_fxrstor64(&fx);
139130
}
140-

src/ioapic.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ void ioapic_redir_set(uint32_t irq, uint32_t vector, uint32_t del_mode, uint32_t
8686
return;
8787
}
8888
uint32_t lower = (vector & 0xff) | (del_mode << 8) | (dest_mode << 11) | (intpol << 13) | (trigger_mode << 15) | (mask << 16);
89-
uint32_t upper = (dest_mode << 24);
89+
uint32_t bsp_apic_id = get_lapic_id_from_cpu_id(logical_cpu_id());
90+
uint32_t upper = (bsp_apic_id << 24);
9091
ioapic_write_gsi(ioapic, gsi, lower, upper);
9192
dprintf("Remapping IRQ %d -> GSI %d -> Vector %d (trigger mode: %s, polarity: %s)\n", irq, gsi, vector,
9293
triggering_str(trigger_mode), polarity_str(trigger_mode));

0 commit comments

Comments
 (0)