Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,27 @@ ifeq ($(call has, VIRTIOGPU), 1)
OBJS_EXTRA += vgpu-display.o
endif

# Guest console: UART-8250 or virtio-console
SEMU_CONSOLE ?= virtio

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The SEMU_CONSOLE ?= virtio default flips the build from the previous primary 8250 UART console to virtio-console, but the selection is mutually exclusive: when SEMU_CONSOLE is left at virtio the else branch is taken and uart.o is not compiled at all. This contradicts the PR description, which states the existing 8250 UART "remains the primary console and early boot path" and that virtio-console is a secondary console. With the default, the 8250 console (and its early-boot UART path) is silently removed from the default build, and the backend cannot be a "secondary" console since only one console object is built and main.c dispatches via #if SEMU_HAS(UART8250) ... #else virtio_console ... #endif. Keep uart8250 as the default (or build both backends so virtio can genuinely be secondary) unless removing the 8250 primary console is the intended behavior.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Makefile, line 219:

<comment>The `SEMU_CONSOLE ?= virtio` default flips the build from the previous primary 8250 UART console to virtio-console, but the selection is mutually exclusive: when `SEMU_CONSOLE` is left at `virtio` the `else` branch is taken and `uart.o` is not compiled at all. This contradicts the PR description, which states the existing 8250 UART "remains the primary console and early boot path" and that virtio-console is a secondary console. With the default, the 8250 console (and its early-boot UART path) is silently removed from the default build, and the backend cannot be a "secondary" console since only one console object is built and main.c dispatches via `#if SEMU_HAS(UART8250) ... #else virtio_console ... #endif`. Keep `uart8250` as the default (or build both backends so virtio can genuinely be secondary) unless removing the 8250 primary console is the intended behavior.</comment>

<file context>
@@ -215,6 +215,27 @@ ifeq ($(call has, VIRTIOGPU), 1)
 endif
 
+# Guest console: UART-8250 or virtio-console
+SEMU_CONSOLE ?= virtio
+SEMU_CONSOLE := $(strip $(SEMU_CONSOLE))
+override ENABLE_UART8250 := 0
</file context>

SEMU_CONSOLE := $(strip $(SEMU_CONSOLE))
override ENABLE_UART8250 := 0
override ENABLE_VIRTIOCONSOLE := 0
ifeq ($(SEMU_CONSOLE),uart8250)
override ENABLE_UART8250 := 1
else ifeq ($(SEMU_CONSOLE),virtio)
override ENABLE_VIRTIOCONSOLE := 1
else
$(error SEMU_CONSOLE must be either uart8250 or virtio)
endif
$(call set-feature, UART8250)
$(call set-feature, VIRTIOCONSOLE)

ifeq ($(call has, UART8250), 1)
OBJS_EXTRA += uart.o
else
OBJS_EXTRA += virtio-console.o
endif

ifneq ($(filter 1,$(call has, VIRTIOGPU) $(call has, VIRTIOINPUT)),)
OBJS_EXTRA += window-sw.o
endif
Expand All @@ -227,7 +248,7 @@ OBJS := \
ram.o \
utils.o \
plic.o \
uart.o \
console.o \
main.o \
aclint.o \
coro.o \
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ A minimalist RISC-V system emulator capable of running Linux the kernel and corr
- Standard SBI, with the timer extension
- I/O support using VirtIO standard:
- virtio-blk acquires disk image from the host.
- virtio-console provides a paravirtualized console.
- virtio-rng provides entropy to the guest.
- virtio-net is mapped as TAP interface.
- virtio-snd uses [PortAudio](https://github.com/PortAudio/portaudio) for sound playback on the host with one limitations:
- As some unknown issues in guest Linux OS (confirmed in v6.7 and v6.12), you need
Expand Down
2 changes: 1 addition & 1 deletion configs/linux.config
Original file line number Diff line number Diff line change
Expand Up @@ -834,7 +834,7 @@ CONFIG_SERIAL_CORE_CONSOLE=y
# CONFIG_NULL_TTY is not set
# CONFIG_SERIAL_DEV_BUS is not set
# CONFIG_TTY_PRINTK is not set
# CONFIG_VIRTIO_CONSOLE is not set
CONFIG_VIRTIO_CONSOLE=y
# CONFIG_IPMI_HANDLER is not set
CONFIG_HW_RANDOM=y
CONFIG_HW_RANDOM_VIRTIO=y
Expand Down
59 changes: 59 additions & 0 deletions console.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <termios.h>
#include <unistd.h>

#include "console.h"

#define CONSOLE_ESCAPE 1 /* Ctrl-a */

static struct termios saved_termios;
static int terminal_fd = -1;
static int output_fd = -1;
static bool escape_pending;

static void host_console_restore(void)
{
if (terminal_fd >= 0)
tcsetattr(terminal_fd, TCSANOW, &saved_termios);
}

void host_console_setup(int in_fd, int out_fd)
{
struct termios termios;

output_fd = out_fd;
if (!isatty(in_fd) || tcgetattr(in_fd, &saved_termios) < 0)
return;

termios = saved_termios;
termios.c_lflag &= ~(ICANON | ECHO | ISIG);
if (tcsetattr(in_fd, TCSANOW, &termios) < 0)
return;

terminal_fd = in_fd;
atexit(host_console_restore);
}

ssize_t host_console_read(int fd, void *buf, size_t len)
{
ssize_t nread = read(fd, buf, len);
uint8_t *bytes = buf;

if (nread <= 0)
return nread;

for (ssize_t i = 0; i < nread; i++) {
if (escape_pending && bytes[i] == 'x') {
if (output_fd >= 0) {
ssize_t written = write(output_fd, "\n", 1);
(void) written;
}
exit(0);
}
escape_pending = bytes[i] == CONSOLE_ESCAPE;
}

return nread;
}
7 changes: 7 additions & 0 deletions console.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#pragma once

#include <stddef.h>
#include <sys/types.h>

void host_console_setup(int in_fd, int out_fd);
ssize_t host_console_read(int fd, void *buf, size_t len);
65 changes: 61 additions & 4 deletions device.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ void plic_write(hart_t *core,
uint32_t value);
/* UART */

#if SEMU_HAS(UART8250)
#define IRQ_UART 1
#define IRQ_UART_BIT (1 << IRQ_UART)

Expand Down Expand Up @@ -84,7 +85,7 @@ void u8250_write(hart_t *core,
uint32_t value);
void u8250_check_ready(u8250_state_t *uart);
void u8250_flush_out(u8250_state_t *uart);
void capture_keyboard_input();
#endif /* SEMU_HAS(UART8250) */

/* virtio-net */

Expand Down Expand Up @@ -347,6 +348,57 @@ uint32_t virtio_gpu_register_scanout(virtio_gpu_state_t *vgpu,
uint32_t height);
#endif /* SEMU_HAS(VIRTIOGPU) */

/* VirtIO-Console */

#if SEMU_HAS(VIRTIOCONSOLE)

#define IRQ_VCONSOLE 10
#define IRQ_VCONSOLE_BIT (1 << IRQ_VCONSOLE)

typedef struct {
uint32_t QueueNum;
uint32_t QueueDesc;
uint32_t QueueAvail;
uint32_t QueueUsed;
uint16_t last_avail;
bool ready;
} virtio_console_queue_t;

typedef struct {
/* feature negotiation */
uint32_t DeviceFeaturesSel;
uint32_t DriverFeatures;
uint32_t DriverFeaturesSel;
/* queue config */
uint32_t QueueSel;
virtio_console_queue_t queues[2];
/* status */
uint32_t Status;
uint32_t InterruptStatus;
/* supplied by environment */
uint32_t *ram;
int in_fd;
int out_fd;
} virtio_console_state_t;

void virtio_console_read(hart_t *core,
virtio_console_state_t *vcon,
uint32_t addr,
uint8_t width,
uint32_t *value);
void virtio_console_write(hart_t *core,
virtio_console_state_t *vcon,
uint32_t addr,
uint8_t width,
uint32_t value);
void virtio_console_refresh(virtio_console_state_t *vcon);
void virtio_console_init(virtio_console_state_t *vcon,
uint32_t *ram,
int in_fd,
int out_fd);

#endif /* SEMU_HAS(VIRTIOCONSOLE) */

/* ACLINT MTIMER */
typedef struct {
/* A MTIMER device has two separate base addresses: one for the MTIME
Expand Down Expand Up @@ -558,7 +610,9 @@ typedef struct {
uint32_t *disk;
vm_t vm;
plic_state_t plic;
#if SEMU_HAS(UART8250)
u8250_state_t uart;
#endif
#if SEMU_HAS(VIRTIONET)
virtio_net_state_t vnet;
#endif
Expand All @@ -581,13 +635,16 @@ typedef struct {
#if SEMU_HAS(VIRTIOGPU)
virtio_gpu_state_t vgpu;
#endif
#if SEMU_HAS(VIRTIOCONSOLE)
virtio_console_state_t vconsole;
#endif
#if SEMU_HAS(VIRTIOINPUT) || SEMU_HAS(VIRTIOGPU)
/* Use self-pipe trick to unblock the emulator loop when the window backend
* has queued work, such as input events or window shutdown. When all harts
* are idle, 'semu_run()' can call 'poll(-1)' and block indefinitely
* waiting for timer or UART events. The window-event thread has no way to
* wake that blocked 'poll()' other than writing to a file descriptor it is
* watching.
* waiting for timer or console events. The window-event thread has no way
* to wake that blocked 'poll()' other than writing to a file descriptor it
* is watching.
*
* 'wake_fd[0]' (read end) is added to 'pfds[]' so 'poll()' monitors it.
* 'wake_fd[1]' (write end) is handed to the window backend, which
Expand Down
8 changes: 8 additions & 0 deletions feature.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

/* enable/disable (compile time) features in this header */

#ifndef SEMU_FEATURE_VIRTIOCONSOLE
#define SEMU_FEATURE_VIRTIOCONSOLE 1
#endif

#ifndef SEMU_FEATURE_UART8250
#define SEMU_FEATURE_UART8250 0
#endif

/* virtio-blk */
#ifndef SEMU_FEATURE_VIRTIOBLK
#define SEMU_FEATURE_VIRTIOBLK 1
Expand Down
Loading