Nitro Enclaves expect the parent instance to host a vsock heartbeat listener at port 9000. To host a Nitro Enclave with the nitro accel in QEMU, add such a heartbeat listener as device model, so that the machine can easily instantiate it.
Signed-off-by: Alexander Graf <[email protected]> --- hw/nitro/Kconfig | 4 ++ hw/nitro/heartbeat.c | 115 +++++++++++++++++++++++++++++++++++ hw/nitro/meson.build | 1 + hw/nitro/trace-events | 4 ++ include/hw/nitro/heartbeat.h | 25 ++++++++ 5 files changed, 149 insertions(+) create mode 100644 hw/nitro/heartbeat.c create mode 100644 include/hw/nitro/heartbeat.h diff --git a/hw/nitro/Kconfig b/hw/nitro/Kconfig index 86c817c766..6fe050d35d 100644 --- a/hw/nitro/Kconfig +++ b/hw/nitro/Kconfig @@ -1,3 +1,7 @@ config NITRO_SERIAL_VSOCK bool depends on NITRO + +config NITRO_HEARTBEAT + bool + depends on NITRO diff --git a/hw/nitro/heartbeat.c b/hw/nitro/heartbeat.c new file mode 100644 index 0000000000..3d2a371098 --- /dev/null +++ b/hw/nitro/heartbeat.c @@ -0,0 +1,115 @@ +/* + * Nitro Enclave Heartbeat device + * + * Copyright © 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Authors: + * Alexander Graf <[email protected]> + * + * The Nitro Enclave init process sends a heartbeat byte (0xB7) to + * CID 3 (parent) port 9000 on boot to signal it reached initramfs. + * The parent must accept the connection, read the byte, and echo it + * back. If the enclave init cannot reach the listener, it exits. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "chardev/char.h" +#include "chardev/char-fe.h" +#include "hw/nitro/heartbeat.h" +#include "trace.h" + +#define HEARTBEAT_PORT 9000 +#define VMADDR_CID_ANY_STR "4294967295" + +static int nitro_heartbeat_can_read(void *opaque) +{ + NitroHeartbeatState *s = opaque; + + /* One-shot protocol: stop reading after the first heartbeat */ + return s->done ? 0 : 1; +} + +static void nitro_heartbeat_read(void *opaque, const uint8_t *buf, int size) +{ + NitroHeartbeatState *s = opaque; + + if (s->done || size < 1) { + return; + } + + /* Echo the heartbeat byte back and disconnect */ + qemu_chr_fe_write_all(&s->vsock, buf, 1); + s->done = true; + qemu_chr_fe_deinit(&s->vsock, true); + + trace_nitro_heartbeat_done(); +} + +static void nitro_heartbeat_event(void *opaque, QEMUChrEvent event) +{ + trace_nitro_heartbeat_event(event); +} + +static void nitro_heartbeat_realize(DeviceState *dev, Error **errp) +{ + NitroHeartbeatState *s = NITRO_HEARTBEAT(dev); + g_autofree char *chardev_id = NULL; + Chardev *chr; + ChardevBackend *backend; + ChardevSocket *sock; + + chardev_id = g_strdup_printf("nitro-heartbeat"); + + backend = g_new0(ChardevBackend, 1); + backend->type = CHARDEV_BACKEND_KIND_SOCKET; + sock = backend->u.socket.data = g_new0(ChardevSocket, 1); + sock->addr = g_new0(SocketAddressLegacy, 1); + sock->addr->type = SOCKET_ADDRESS_TYPE_VSOCK; + sock->addr->u.vsock.data = g_new0(VsockSocketAddress, 1); + sock->addr->u.vsock.data->cid = g_strdup(VMADDR_CID_ANY_STR); + sock->addr->u.vsock.data->port = g_strdup_printf("%u", HEARTBEAT_PORT); + sock->server = true; + sock->has_server = true; + sock->wait = false; + sock->has_wait = true; + + chr = qemu_chardev_new(chardev_id, TYPE_CHARDEV_SOCKET, + backend, NULL, errp); + if (!chr) { + return; + } + + if (!qemu_chr_fe_init(&s->vsock, chr, errp)) { + return; + } + + qemu_chr_fe_set_handlers(&s->vsock, + nitro_heartbeat_can_read, + nitro_heartbeat_read, + nitro_heartbeat_event, + NULL, s, NULL, true); +} + +static void nitro_heartbeat_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = nitro_heartbeat_realize; +} + +static const TypeInfo nitro_heartbeat_info = { + .name = TYPE_NITRO_HEARTBEAT, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(NitroHeartbeatState), + .class_init = nitro_heartbeat_class_init, +}; + +static void nitro_heartbeat_register(void) +{ + type_register_static(&nitro_heartbeat_info); +} + +type_init(nitro_heartbeat_register); diff --git a/hw/nitro/meson.build b/hw/nitro/meson.build index d95ed8dd79..b921da2b97 100644 --- a/hw/nitro/meson.build +++ b/hw/nitro/meson.build @@ -1 +1,2 @@ system_ss.add(when: 'CONFIG_NITRO_SERIAL_VSOCK', if_true: files('serial-vsock.c')) +system_ss.add(when: 'CONFIG_NITRO_HEARTBEAT', if_true: files('heartbeat.c')) diff --git a/hw/nitro/trace-events b/hw/nitro/trace-events index 20617a024a..311ab78e69 100644 --- a/hw/nitro/trace-events +++ b/hw/nitro/trace-events @@ -2,3 +2,7 @@ # serial-vsock.c nitro_serial_vsock_event(int event) "event %d" + +# heartbeat.c +nitro_heartbeat_event(int event) "event %d" +nitro_heartbeat_done(void) "enclave heartbeat received" diff --git a/include/hw/nitro/heartbeat.h b/include/hw/nitro/heartbeat.h new file mode 100644 index 0000000000..3ed4d40bac --- /dev/null +++ b/include/hw/nitro/heartbeat.h @@ -0,0 +1,25 @@ +/* + * Nitro Heartbeat device + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_NITRO_HEARTBEAT_H +#define HW_MISC_NITRO_HEARTBEAT_H + +#include "hw/core/qdev.h" +#include "hw/core/sysbus.h" +#include "chardev/char-fe.h" +#include "qom/object.h" + +#define TYPE_NITRO_HEARTBEAT "nitro-heartbeat" +OBJECT_DECLARE_SIMPLE_TYPE(NitroHeartbeatState, NITRO_HEARTBEAT) + +struct NitroHeartbeatState { + SysBusDevice parent_obj; + + CharFrontend vsock; /* vsock server chardev for heartbeat */ + bool done; +}; + +#endif /* HW_MISC_NITRO_HEARTBEAT_H */ -- 2.47.1 Amazon Web Services Development Center Germany GmbH Tamara-Danz-Str. 13 10243 Berlin Geschaeftsfuehrung: Christof Hellmis, Andreas Stieger Eingetragen am Amtsgericht Charlottenburg unter HRB 257764 B Sitz: Berlin Ust-ID: DE 365 538 597
