| 1 | /* |
| 2 | * Nitro Enclave Vsock Serial |
| 3 | * |
| 4 | * Copyright © 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved. |
| 5 | * |
| 6 | * Authors: |
| 7 | * Alexander Graf <graf@amazon.com> |
| 8 | * |
| 9 | * With Nitro Enclaves in debug mode, the Nitro Hypervisor provides a vsock |
| 10 | * port that the parent can connect to to receive serial console output of |
| 11 | * the Enclave. This driver implements short-circuit logic to establish the |
| 12 | * vsock connection to that port and feed its data into a chardev, so that |
| 13 | * a machine model can use it as serial device. |
| 14 | * |
| 15 | * SPDX-License-Identifier: GPL-2.0-or-later |
| 16 | */ |
| 17 | |
| 18 | #include "qemu/osdep.h" |
| 19 | #include "qemu/error-report.h" |
| 20 | #include "qapi/error.h" |
| 21 | #include "chardev/char.h" |
| 22 | #include "chardev/char-fe.h" |
| 23 | #include "hw/core/qdev-properties.h" |
| 24 | #include "hw/core/qdev-properties-system.h" |
| 25 | #include "hw/nitro/serial-vsock.h" |
| 26 | #include "trace.h" |
| 27 | |
| 28 | #define CONSOLE_PORT_START 10000 |
| 29 | #define VMADDR_CID_HYPERVISOR_STR "0" |
| 30 | |
| 31 | static int nitro_serial_vsock_can_read(void *opaque) |
| 32 | { |
| 33 | NitroSerialVsockState *s = opaque; |
| 34 | |
| 35 | /* Refuse vsock input until the output backend is ready */ |
| 36 | return qemu_chr_fe_backend_open(&s->output) ? 4096 : 0; |
| 37 | } |
| 38 | |
| 39 | static void nitro_serial_vsock_read(void *opaque, const uint8_t *buf, int size) |
| 40 | { |
| 41 | NitroSerialVsockState *s = opaque; |
| 42 | |
| 43 | /* Forward all vsock data to the output chardev */ |
| 44 | qemu_chr_fe_write_all(&s->output, buf, size); |
| 45 | } |
| 46 | |
| 47 | static void nitro_serial_vsock_event(void *opaque, QEMUChrEvent event) |
| 48 | { |
| 49 | /* No need to action on connect/disconnect events, but trace for debug */ |
| 50 | trace_nitro_serial_vsock_event(event); |
| 51 | } |
| 52 | |
| 53 | static void nitro_serial_vsock_enclave_started(NitroVsockDevice *dev, |
| 54 | uint32_t enclave_cid, |
| 55 | Error **errp) |
| 56 | { |
| 57 | NitroSerialVsockState *s = NITRO_SERIAL_VSOCK(dev); |
| 58 | uint32_t port = enclave_cid + CONSOLE_PORT_START; |
| 59 | g_autofree char *chardev_id = NULL; |
| 60 | Chardev *chr; |
| 61 | ChardevBackend *backend; |
| 62 | ChardevSocket *sock; |
| 63 | |
| 64 | /* |
| 65 | * We know the Enclave CID to connect to now. Create a vsock |
| 66 | * client chardev that connects to the Enclave's console. |
| 67 | */ |
| 68 | chardev_id = g_strdup_printf("nitro-console-%u", enclave_cid); |
| 69 | |
| 70 | backend = g_new0(ChardevBackend, 1); |
| 71 | backend->type = CHARDEV_BACKEND_KIND_SOCKET; |
| 72 | sock = backend->u.socket.data = g_new0(ChardevSocket, 1); |
| 73 | sock->addr = g_new0(SocketAddressLegacy, 1); |
| 74 | sock->addr->type = SOCKET_ADDRESS_TYPE_VSOCK; |
| 75 | sock->addr->u.vsock.data = g_new0(VsockSocketAddress, 1); |
| 76 | sock->addr->u.vsock.data->cid = g_strdup(VMADDR_CID_HYPERVISOR_STR); |
| 77 | sock->addr->u.vsock.data->port = g_strdup_printf("%u", port); |
| 78 | sock->server = false; |
| 79 | sock->has_server = true; |
| 80 | |
| 81 | chr = qemu_chardev_new(chardev_id, TYPE_CHARDEV_SOCKET, |
| 82 | backend, NULL, errp); |
| 83 | if (!chr) { |
| 84 | return; |
| 85 | } |
| 86 | |
| 87 | if (!qemu_chr_fe_init(&s->vsock, chr, errp)) { |
| 88 | return; |
| 89 | } |
| 90 | |
| 91 | qemu_chr_fe_set_handlers(&s->vsock, |
| 92 | nitro_serial_vsock_can_read, |
| 93 | nitro_serial_vsock_read, |
| 94 | nitro_serial_vsock_event, |
| 95 | NULL, s, NULL, true); |
| 96 | } |
| 97 | |
| 98 | static const Property nitro_serial_vsock_props[] = { |
| 99 | DEFINE_PROP_CHR("chardev", NitroSerialVsockState, output), |
| 100 | }; |
| 101 | |
| 102 | static void nitro_serial_vsock_class_init(ObjectClass *oc, const void *data) |
| 103 | { |
| 104 | DeviceClass *dc = DEVICE_CLASS(oc); |
| 105 | NitroVsockDeviceClass *ndc = NITRO_VSOCK_DEVICE_CLASS(oc); |
| 106 | |
| 107 | device_class_set_props(dc, nitro_serial_vsock_props); |
| 108 | ndc->enclave_started = nitro_serial_vsock_enclave_started; |
| 109 | } |
| 110 | |
| 111 | static const TypeInfo nitro_serial_vsock_info = { |
| 112 | .name = TYPE_NITRO_SERIAL_VSOCK, |
| 113 | .parent = TYPE_NITRO_VSOCK_DEVICE, |
| 114 | .instance_size = sizeof(NitroSerialVsockState), |
| 115 | .class_init = nitro_serial_vsock_class_init, |
| 116 | }; |
| 117 | |
| 118 | static void nitro_serial_vsock_register(void) |
| 119 | { |
| 120 | type_register_static(&nitro_serial_vsock_info); |
| 121 | } |
| 122 | |
| 123 | type_init(nitro_serial_vsock_register); |