vmspawn: Add --console-transport= option to select serial vs virtio-serial

Add a --console-transport= option that selects between virtio-serial
(the default, appearing as /dev/hvc0) and a regular serial port
(appearing as /dev/ttyS0 or /dev/ttyAMA0 depending on architecture).

This is primarily useful for testing purposes, for example to test
sd-stub's automatic console= kernel command line parameter handling. It
allows verifying that the guest OS correctly handles serial console
configurations without virtio.

When serial transport is selected, -serial chardev:console is used on
the QEMU command line to connect the chardev to the platform's default
serial device. This cannot be done via the QEMU config file as on some
platforms (e.g. ARM) the serial device is a sysbus device that can only
be connected via serial_hd() which is populated by -serial.
This commit is contained in:
Daan De Meyer
2026-04-02 20:43:42 +02:00
committed by Daan De Meyer
parent eb584f164f
commit a586648800
5 changed files with 73 additions and 12 deletions
+14
View File
@@ -712,6 +712,20 @@
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--console-transport=<replaceable>TRANSPORT</replaceable></option></term>
<listitem><para>Configures the transport to use for the VM console. Takes one of
<literal>virtio</literal> or <literal>serial</literal>. Defaults to <literal>virtio</literal>.
<literal>virtio</literal> uses a virtio-serial device, which appears as
<filename>/dev/hvc0</filename> in the VM. <literal>serial</literal> uses a regular serial port,
which appears as <filename>/dev/ttyS0</filename> (or <filename>/dev/ttyAMA0</filename> on ARM) in the VM. This option only has an effect in
<option>--console=interactive</option>, <option>--console=read-only</option>, and
<option>--console=native</option> modes.</para>
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--background=<replaceable>COLOR</replaceable></option></term>
+7
View File
@@ -37,3 +37,10 @@ static const char *const console_mode_table[_CONSOLE_MODE_MAX] = {
};
DEFINE_STRING_TABLE_LOOKUP(console_mode, ConsoleMode);
static const char *const console_transport_table[_CONSOLE_TRANSPORT_MAX] = {
[CONSOLE_TRANSPORT_VIRTIO] = "virtio",
[CONSOLE_TRANSPORT_SERIAL] = "serial",
};
DEFINE_STRING_TABLE_LOOKUP(console_transport, ConsoleTransport);
+8
View File
@@ -42,6 +42,13 @@ typedef enum ConsoleMode {
_CONSOLE_MODE_INVALID = -EINVAL,
} ConsoleMode;
typedef enum ConsoleTransport {
CONSOLE_TRANSPORT_VIRTIO, /* virtio-serial (hvc0) */
CONSOLE_TRANSPORT_SERIAL, /* regular serial port (ttyS0/ttyAMA0) */
_CONSOLE_TRANSPORT_MAX,
_CONSOLE_TRANSPORT_INVALID = -EINVAL,
} ConsoleTransport;
typedef enum SettingsMask {
SETTING_START_MODE = UINT64_C(1) << 0,
SETTING_MACHINE_ID = UINT64_C(1) << 6,
@@ -53,5 +60,6 @@ typedef enum SettingsMask {
} SettingsMask;
DECLARE_STRING_TABLE_LOOKUP(console_mode, ConsoleMode);
DECLARE_STRING_TABLE_LOOKUP(console_transport, ConsoleTransport);
DECLARE_STRING_TABLE_LOOKUP(disk_type, DiskType);
DECLARE_STRING_TABLE_LOOKUP(image_format, ImageFormat);
+6
View File
@@ -49,6 +49,12 @@
# define QEMU_MACHINE_TYPE "none"
#endif
#if defined(__arm__) || defined(__aarch64__)
# define QEMU_SERIAL_CONSOLE_NAME "ttyAMA0"
#else
# define QEMU_SERIAL_CONSOLE_NAME "ttyS0"
#endif
typedef struct OvmfConfig {
char *path;
char *format;
+38 -12
View File
@@ -137,6 +137,7 @@ static int arg_tpm = -1;
static char *arg_linux = NULL;
static char **arg_initrds = NULL;
static ConsoleMode arg_console_mode = CONSOLE_INTERACTIVE;
static ConsoleTransport arg_console_transport = CONSOLE_TRANSPORT_VIRTIO;
static NetworkStack arg_network_stack = NETWORK_STACK_NONE;
static MachineCredentialContext arg_credentials = {};
static uid_t arg_uid_shift = UID_INVALID, arg_uid_range = 0x10000U;
@@ -291,6 +292,8 @@ static int help(void) {
"\n%3$sInput/Output:%4$s\n"
" --console=MODE Console mode (interactive, native, gui, read-only\n"
" or headless)\n"
" --console-transport=TRANSPORT\n"
" Console transport (virtio or serial)\n"
" --background=COLOR Set ANSI color for background\n"
"\n%3$sCredentials:%4$s\n"
" --set-credential=ID:VALUE\n"
@@ -375,6 +378,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_USER,
ARG_IMAGE_FORMAT,
ARG_IMAGE_DISK_TYPE,
ARG_CONSOLE_TRANSPORT,
};
static const struct option options[] = {
@@ -402,6 +406,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "linux", required_argument, NULL, ARG_LINUX },
{ "initrd", required_argument, NULL, ARG_INITRD },
{ "console", required_argument, NULL, ARG_CONSOLE },
{ "console-transport", required_argument, NULL, ARG_CONSOLE_TRANSPORT },
{ "qemu-gui", no_argument, NULL, ARG_QEMU_GUI }, /* compat option */
{ "network-tap", no_argument, NULL, 'n' },
{ "network-user-mode", no_argument, NULL, ARG_NETWORK_USER_MODE },
@@ -578,6 +583,13 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ARG_CONSOLE_TRANSPORT:
arg_console_transport = console_transport_from_string(optarg);
if (arg_console_transport < 0)
return log_error_errno(arg_console_transport, "Failed to parse specified console transport: %s", optarg);
break;
case ARG_QEMU_GUI:
arg_console_mode = CONSOLE_GUI;
break;
@@ -2588,11 +2600,6 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
if (r < 0)
return log_oom();
r = qemu_config_section(config_file, "device", "vmspawn-virtio-serial-pci",
"driver", "virtio-serial-pci");
if (r < 0)
return r;
/* Enable mux for native console so the QEMU monitor is accessible via Ctrl-a c */
r = qemu_config_section(config_file, "chardev", "console",
"backend", "serial",
@@ -2601,12 +2608,6 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
if (r < 0)
return r;
r = qemu_config_section(config_file, "device", "virtconsole0",
"driver", "virtconsole",
"chardev", "console");
if (r < 0)
return r;
if (arg_console_mode == CONSOLE_NATIVE) {
r = qemu_config_section(config_file, "mon", "mon0",
"chardev", "console");
@@ -2655,6 +2656,29 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
assert_not_reached();
}
if (!IN_SET(arg_console_mode, CONSOLE_GUI, CONSOLE_HEADLESS)) {
if (arg_console_transport == CONSOLE_TRANSPORT_SERIAL) {
/* Use -serial to connect the chardev to the platform's default serial
* device (e.g. isa-serial on x86, PL011 on ARM). On some platforms the
* serial device is a sysbus device that can only be connected via
* serial_hd() which is populated by -serial, not via the config file. */
r = strv_extend_many(&cmdline, "-serial", "chardev:console");
if (r < 0)
return log_oom();
} else {
r = qemu_config_section(config_file, "device", "vmspawn-virtio-serial-pci",
"driver", "virtio-serial-pci");
if (r < 0)
return r;
r = qemu_config_section(config_file, "device", "virtconsole0",
"driver", "virtconsole",
"chardev", "console");
if (r < 0)
return r;
}
}
r = qemu_config_section(config_file, "drive", "ovmf-code",
"if", "pflash",
"format", ovmf_config_format(ovmf_config),
@@ -3028,7 +3052,9 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
}
if (!IN_SET(arg_console_mode, CONSOLE_GUI, CONSOLE_HEADLESS)) {
r = strv_prepend(&arg_kernel_cmdline_extra, "console=hvc0");
r = strv_prepend(&arg_kernel_cmdline_extra,
arg_console_transport == CONSOLE_TRANSPORT_SERIAL ?
"console=" QEMU_SERIAL_CONSOLE_NAME : "console=hvc0");
if (r < 0)
return log_oom();