Skip to content

Commit 860d661

Browse files
committed
Implement event-driven UART coroutine
This fixes boot failure and CPU spinning issues in SMP mode through hybrid polling strategy that dynamically switches between blocking and non-blocking modes based on hart activity. 1. Event loop optimization - Observe hart states BEFORE resuming them - Set poll_timeout = -1 when all harts idle (event-driven) - Set poll_timeout = 0 when harts have work (polling) - Always resume harts unconditionally (avoid deadlock) 2. UART coroutine support - Hart yields when no stdin data available - Event loop resumes hart when stdin becomes readable - Spurious wakeup handling with state re-check 3. Coroutine state tracking - waiting_hart_id tracks which hart is waiting for UART - has_waiting_hart enables quick idle state check - Sentinel value (UINT32_MAX) for single-core fallback
1 parent 4552c62 commit 860d661

File tree

4 files changed

+72
-12
lines changed

4 files changed

+72
-12
lines changed

coro.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,5 +607,9 @@ bool coro_is_suspended(uint32_t slot_id)
607607

608608
uint32_t coro_current_hart_id(void)
609609
{
610+
/* Return sentinel value if coroutine subsystem not initialized */
611+
if (!coro_state.initialized)
612+
return UINT32_MAX;
613+
610614
return coro_state.current_hart;
611615
}

device.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ typedef struct {
6060
/* I/O handling */
6161
int in_fd, out_fd;
6262
bool in_ready;
63+
/* Coroutine support for input waiting (SMP mode) */
64+
uint32_t waiting_hart_id; /**< Hart ID waiting for input, UINT32_MAX if none */
65+
bool has_waiting_hart; /**< true if a hart is yielding for input */
6366
} u8250_state_t;
6467

6568
void u8250_update_interrupts(u8250_state_t *uart);

main.c

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -768,6 +768,8 @@ static int semu_init(emu_state_t *emu, int argc, char **argv)
768768

769769
/* Set up peripherals */
770770
emu->uart.in_fd = 0, emu->uart.out_fd = 1;
771+
emu->uart.waiting_hart_id = UINT32_MAX;
772+
emu->uart.has_waiting_hart = false;
771773
capture_keyboard_input(); /* set up uart */
772774
#if SEMU_HAS(VIRTIONET)
773775
/* Always set ram pointer, even if netdev is not configured.
@@ -1177,29 +1179,41 @@ static int semu_run(emu_state_t *emu)
11771179
pfd_count++;
11781180
}
11791181

1180-
/* Determine poll timeout based on hart WFI states:
1182+
/* Determine poll timeout based on hart states BEFORE resuming them.
1183+
* This check must happen before coro_resume_hart() modifies flags.
1184+
*
11811185
* - If no harts are STARTED, block indefinitely (wait for IPI)
1182-
* - If all STARTED harts are in WFI, block indefinitely
1186+
* - If all STARTED harts are idle (WFI or UART waiting), block
11831187
* - Otherwise, use non-blocking poll (timeout=0)
11841188
*/
11851189
int poll_timeout = 0;
11861190
uint32_t started_harts = 0;
1187-
uint32_t wfi_harts = 0;
1191+
uint32_t idle_harts = 0;
11881192
for (uint32_t i = 0; i < vm->n_hart; i++) {
11891193
if (vm->hart[i]->hsm_status == SBI_HSM_STATE_STARTED) {
11901194
started_harts++;
1191-
if (vm->hart[i]->in_wfi)
1192-
wfi_harts++;
1195+
/* Count hart as idle if it's in WFI or waiting for UART */
1196+
if (vm->hart[i]->in_wfi ||
1197+
(emu->uart.has_waiting_hart &&
1198+
emu->uart.waiting_hart_id == i)) {
1199+
idle_harts++;
1200+
}
11931201
}
11941202
}
1195-
/* Block if no harts running or all running harts are waiting */
1203+
1204+
/* Set poll timeout based on current idle state.
1205+
* Block if no harts running or all running harts are idle.
1206+
*/
11961207
if (pfd_count > 0 &&
1197-
(started_harts == 0 || wfi_harts == started_harts))
1198-
poll_timeout = -1;
1208+
(started_harts == 0 || idle_harts == started_harts)) {
1209+
poll_timeout = -1; /* Blocking poll - wait for events */
1210+
} else {
1211+
poll_timeout = 0; /* Non-blocking poll - harts have work */
1212+
}
11991213

12001214
/* Execute poll() to wait for I/O events.
1201-
* - timeout=0: non-blocking poll when harts are running
1202-
* - timeout=-1: blocking poll when all harts in WFI (idle state)
1215+
* - timeout=0: non-blocking poll when harts are active
1216+
* - timeout=-1: blocking poll when all harts idle (WFI or UART wait)
12031217
*/
12041218
if (pfd_count > 0) {
12051219
int nevents = poll(pfds, pfd_count, poll_timeout);
@@ -1231,6 +1245,11 @@ static int semu_run(emu_state_t *emu)
12311245
* Each hart executes a batch of instructions, then yields back.
12321246
* Harts in WFI will clear their in_wfi flag when resuming from
12331247
* coro_yield() in wfi_handler().
1248+
*
1249+
* Note: We must always resume harts after poll() returns, even if
1250+
* all harts appear idle. The in_wfi flag is only cleared during
1251+
* resume, so skipping resume would cause a deadlock where harts
1252+
* remain stuck waiting even after events arrive.
12341253
*/
12351254
for (uint32_t i = 0; i < vm->n_hart; i++) {
12361255
coro_resume_hart(i);

uart.c

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include "device.h"
1010
#include "riscv.h"
11+
#include "coro.h"
1112
#include "riscv_private.h"
1213

1314
/*
@@ -86,12 +87,45 @@ static void u8250_handle_out(u8250_state_t *uart, uint8_t value)
8687
fprintf(stderr, "failed to write UART output: %s\n", strerror(errno));
8788
}
8889

90+
/* Wait for UART input using coroutine yield (SMP mode only)
91+
* This function allows a hart to yield when no UART input is available,
92+
* preventing CPU spinning when waiting for stdin. The hart will be resumed
93+
* by the event loop when stdin becomes readable.
94+
*/
95+
static void u8250_wait_for_input(u8250_state_t *uart)
96+
{
97+
/* Only yield in SMP mode - single-core mode doesn't use coroutines */
98+
uint32_t hart_id = coro_current_hart_id();
99+
if (hart_id == UINT32_MAX)
100+
return; /* Not in a coroutine, skip yielding */
101+
102+
/* Mark this hart as waiting for UART input */
103+
uart->waiting_hart_id = hart_id;
104+
uart->has_waiting_hart = true;
105+
106+
/* Yield until stdin has data available. The event loop will resume this
107+
* hart when poll() detects POLLIN on stdin fd.
108+
*/
109+
coro_yield();
110+
111+
/* Resumed - clear waiting state */
112+
uart->has_waiting_hart = false;
113+
uart->waiting_hart_id = UINT32_MAX;
114+
}
115+
89116
static uint8_t u8250_handle_in(u8250_state_t *uart)
90117
{
91118
uint8_t value = 0;
92119
u8250_check_ready(uart);
93-
if (!uart->in_ready)
94-
return value;
120+
121+
/* If no data available, yield and wait for stdin to become readable */
122+
if (!uart->in_ready) {
123+
u8250_wait_for_input(uart);
124+
/* After resume, re-check if data is now available */
125+
u8250_check_ready(uart);
126+
if (!uart->in_ready)
127+
return value; /* Spurious wakeup - still no data */
128+
}
95129

96130
if (read(uart->in_fd, &value, 1) < 0)
97131
fprintf(stderr, "failed to read UART input: %s\n", strerror(errno));

0 commit comments

Comments
 (0)