diff --git a/build/meta-linux/recipes-linux/soo/files/soo-generic/0020-vdevback.c.patch b/build/meta-linux/recipes-linux/soo/files/soo-generic/0020-vdevback.c.patch index 69e4e37d3c..4495aaff0e 100644 --- a/build/meta-linux/recipes-linux/soo/files/soo-generic/0020-vdevback.c.patch +++ b/build/meta-linux/recipes-linux/soo/files/soo-generic/0020-vdevback.c.patch @@ -1,6 +1,6 @@ --- /home/rossierd/soo/git/micofe/build/tmp/work/linux-6.12-r0/linux-6.12/soo/drivers/vdevback.c 1970-01-01 01:00:00.000000000 +0100 +++ ./soo/drivers/vdevback.c 2025-08-29 08:46:41.395627342 +0200 -@@ -0,0 +1,309 @@ +@@ -0,0 +1,350 @@ + +/* + * Copyright (C) 2020 Daniel Rossier @@ -81,6 +81,46 @@ +} + +/* ++ * Non-blocking counterpart of vdevback_processing_begin(): it never waits for ++ * the frontend to (re)connect and reports false instead. ++ * ++ * Any caller running in a context which must not sleep -- the console input ++ * path, driven by the serial interrupt -- has to use this one. A capsule being ++ * suspended, snapshotted or shut down leaves its frontend out of the connected ++ * state, and waiting for it there would hang the interrupt. ++ */ ++bool vdevback_processing_try_begin(struct vbus_device *vdev) ++{ ++ void *priv = dev_get_drvdata(&vdev->dev); ++ vdevback_t *vdevback = (vdevback_t *) priv; ++ ++ if (vdev->fe_state != VbusStateConnected) ++ return false; ++ ++ if (atomic_read(&vdevback->processing_count) == 0) { ++ atomic_inc(&vdevback->processing_count); ++ ++ if (!mutex_trylock(&vdevback->processing_lock)) { ++ atomic_dec(&vdevback->processing_count); ++ ++ return false; ++ } ++ ++ /* Make sure we are still connected after taking the lock. */ ++ ++ if (vdev->fe_state != VbusStateConnected) { ++ mutex_unlock(&vdevback->processing_lock); ++ atomic_dec(&vdevback->processing_count); ++ ++ return false; ++ } ++ } else ++ atomic_inc(&vdevback->processing_count); ++ ++ return true; ++} ++ ++/* + * Finish a processing section against suspend/close prevention + */ +void vdevback_processing_end(struct vbus_device *vdev) { @@ -98,12 +138,13 @@ + * Check if the frontend state and tell whether it is connected. + */ +bool vdevfront_is_connected(struct vbus_device *vdev) { -+ void *priv = dev_get_drvdata(&vdev->dev); -+ vdevback_t *vdevback = (vdevback_t *) priv; ++ vdevback_t *vdevback; + + if (!vdev) + return false; + ++ vdevback = (vdevback_t *) dev_get_drvdata(&vdev->dev); ++ + return vdevback->vdevfront_connected; +} + diff --git a/build/meta-linux/recipes-linux/soo/files/soo-generic/0032-vuart.c.patch b/build/meta-linux/recipes-linux/soo/files/soo-generic/0032-vuart.c.patch index ad90931477..f1270f5594 100644 --- a/build/meta-linux/recipes-linux/soo/files/soo-generic/0032-vuart.c.patch +++ b/build/meta-linux/recipes-linux/soo/files/soo-generic/0032-vuart.c.patch @@ -1,6 +1,6 @@ --- /home/rossierd/soo/git/micofe/build/tmp/work/linux-6.12-r0/linux-6.12/soo/drivers/vuartback/vuart.c 1970-01-01 01:00:00.000000000 +0100 +++ ./soo/drivers/vuartback/vuart.c 2025-08-29 08:46:41.395804736 +0200 -@@ -0,0 +1,327 @@ +@@ -0,0 +1,336 @@ +/* + * Copyright (C) 2016-2018 Daniel Rossier + * Copyright (C) 2016 Baptiste Delporte @@ -191,10 +191,19 @@ + + console = get_console(domid); + -+ if (!vdevfront_is_connected(console)) ++ /* The capsule may have gone while the console focus was still pointing at ++ * its slot -- a paused capsule is suspended, then shut down. ++ */ ++ ++ if (!console || !vdevfront_is_connected(console)) + return; + -+ vdevback_processing_begin(console); ++ /* This runs from the serial interrupt: the blocking flavour would wait for ++ * a frontend which is suspended or already gone, and hang the console. ++ */ ++ ++ if (!vdevback_processing_try_begin(console)) ++ return; + + vuart_priv = dev_get_drvdata(&console->dev); + diff --git a/build/meta-linux/recipes-linux/soo/files/soo-generic/0036-capsule.h.patch b/build/meta-linux/recipes-linux/soo/files/soo-generic/0036-capsule.h.patch index 864d8cd682..3fd830f43c 100644 --- a/build/meta-linux/recipes-linux/soo/files/soo-generic/0036-capsule.h.patch +++ b/build/meta-linux/recipes-linux/soo/files/soo-generic/0036-capsule.h.patch @@ -25,7 +25,7 @@ +int inject_capsule(void *buffer, size_t size, int slotID, unsigned capsuleId); +void start_capsule(int slotID); + -+void read_snapshot(uint32_t slotID, void *buffer, uint32_t *size); ++void read_snapshot(uint32_t slotID, void *buffer, uint32_t *size, bool resume); +int write_snapshot(void *buffer); + +int get_S3C_state(unsigned int S3C_slotID); @@ -40,5 +40,4 @@ +void capsule_init(void); +void capsule_exit(void); + -+#endif /* CAPSULE_H */ -\ No newline at end of file ++#endif /* CAPSULE_H */ \ No newline at end of file diff --git a/build/meta-linux/recipes-linux/soo/files/soo-generic/0062-soo.h.patch b/build/meta-linux/recipes-linux/soo/files/soo-generic/0062-soo.h.patch index 448e20a866..33ecf062ff 100644 --- a/build/meta-linux/recipes-linux/soo/files/soo-generic/0062-soo.h.patch +++ b/build/meta-linux/recipes-linux/soo/files/soo-generic/0062-soo.h.patch @@ -1,6 +1,6 @@ --- /home/rossierd/soo/git/micofe/build/tmp/work/linux-6.12-r0/linux-6.12/soo/include/soo/uapi/soo.h 1970-01-01 01:00:00.000000000 +0100 +++ ./soo/include/soo/uapi/soo.h 2025-08-29 08:46:41.395924678 +0200 -@@ -0,0 +1,482 @@ +@@ -0,0 +1,538 @@ +/* + * Copyright (C) 2014-2025 Daniel Rossier + * @@ -74,6 +74,13 @@ + +void do_gnttab(gnttab_op_t *args); + ++#define MAX_FBDEV_PFN 8 ++typedef struct fbdev_pfns { ++ size_t pfn_count; ++ addr_t pfn[MAX_FBDEV_PFN]; ++ size_t page_count[MAX_FBDEV_PFN]; ++} fbdev_pfns_t; ++ +#define AVZ_SCHEDULER_FLIP 0 + +/* @@ -167,6 +174,11 @@ +#define AGENCY_IOCTL_GET_S3C_ID _IOWR('S', 6, agency_ioctl_args_t) +#define AGENCY_IOCTL_GET_S3C_ID_ARRAY _IOR('S', 7, agency_ioctl_args_t) + ++/* Same as AGENCY_IOCTL_READ_SNAPSHOT, but the capsule is left suspended instead ++ * of being resumed: for a caller which shuts it down right after. ++ */ ++#define AGENCY_IOCTL_READ_SNAPSHOT_HOLD _IOWR('S', 8, agency_ioctl_args_t) ++ +#define SOO_NAME_SIZE 16 + +/* @@ -302,6 +314,9 @@ +#define AVZ_SET_S3C_STATE 11 +#define AVZ_GET_DOM_DESC 12 +#define AVZ_GRANT_TABLE_OP 13 ++#define AVZ_FBDEV_SET_PFNS 14 ++#define AVZ_FBDEV_CHANGE_FOCUS 15 ++#define AVZ_FBDEV_GET_S3C_ADDR 16 + +/* Staged hypercalls. + * @@ -320,6 +335,22 @@ +#define AVZ_STAGE_CHUNK 1 +#define AVZ_STAGE_FINALIZE 2 + ++/* Snapshot only: complete without resuming the capsule, which is left ++ * suspended. Taking a snapshot normally leaves the capsule living -- that is ++ * the point of snapshotting a running capsule -- but a caller which is about to ++ * shut it down would otherwise let it run, and diverge from the snapshot it has ++ * just taken, for nothing. ++ */ ++#define AVZ_STAGE_FINALIZE_HOLD 3 ++ ++/* Maximum amount of capsule memory moved in a single AVZ_STAGE_CHUNK call. ++ * ++ * A snapshot is streamed through a bounce buffer of that size instead of being ++ * mapped as a whole: the agency has no reason to find several hundreds of ++ * contiguous MB in its CMA zone just to save or restore a capsule. ++ */ ++#define AVZ_STAGE_CHUNK_SIZE (4 * 1024 * 1024) ++ +/* AVZ_INJECT_CAPSULE */ +typedef struct { + void *itb_paddr; @@ -367,10 +398,17 @@ + +/* AVZ_READ_SNAPSHOT */ +/* AVZ_WRITE_SNAPSHOT */ ++/* ++ * `snapshot_paddr` points to the agency bounce buffer and is read again at each ++ * stage. At the INIT and FINALIZE stages the buffer holds the snapshot header ++ * (payload size + domain context), which AVZ reads to restore the capsule; at ++ * the CHUNK stage it holds one chunk of capsule memory, at its very beginning, ++ * and `size` tells AVZ how much of it may be used. ++ */ +typedef struct { + void *snapshot_paddr; + int32_t slotID; -+ int size; ++ int size; /* INIT: IN/OUT snapshot size / CHUNK: IN bounce buffer size */ + uint32_t stage; /* IN: AVZ_STAGE_* */ + uint32_t offset; /* CHUNK: IN/OUT byte cursor / INIT: OUT bytes to copy */ +} avz_snapshot_t; @@ -385,6 +423,21 @@ + gnttab_op_t gnttab_op; +} avz_gnttab_t; + ++/* AVZ_FBDEV_SET_PFNS */ ++typedef struct { ++ fbdev_pfns_t fbdev; ++} avz_fbdev_pfns_t; ++ ++/* AVZ_FBDEV_CHANGE_FOCUS */ ++typedef struct { ++ int new_slotID; ++} avz_fbdev_focus_t; ++ ++/* AVZ_FBDEV_GET_S3C_ADDR */ ++typedef struct { ++ addr_t paddr; ++} avz_fbdev_addr_t; ++ +/* + * AVZ hypercall argument + */ @@ -404,6 +457,9 @@ + avz_console_io_t avz_console_io_args; + avz_domctl_t avz_domctl_args; + avz_gnttab_t avz_gnttab_args; ++ avz_fbdev_pfns_t avz_fbdev_pfns_args; ++ avz_fbdev_focus_t avz_fbdev_focus_args; ++ avz_fbdev_addr_t avz_fbdev_addr_args; + } u; +} avz_hyp_t; + diff --git a/build/meta-linux/recipes-linux/soo/files/soo-generic/0064-vbus.h.patch b/build/meta-linux/recipes-linux/soo/files/soo-generic/0064-vbus.h.patch index a618647d40..7b85b32769 100644 --- a/build/meta-linux/recipes-linux/soo/files/soo-generic/0064-vbus.h.patch +++ b/build/meta-linux/recipes-linux/soo/files/soo-generic/0064-vbus.h.patch @@ -1,6 +1,6 @@ --- /home/rossierd/soo/git/micofe/build/tmp/work/linux-6.12-r0/linux-6.12/soo/include/soo/vbus.h 1970-01-01 01:00:00.000000000 +0100 +++ ./soo/include/soo/vbus.h 2025-08-29 08:46:41.396103436 +0200 -@@ -0,0 +1,336 @@ +@@ -0,0 +1,339 @@ +/* + * Copyright (C) 2014-2019 Daniel Rossier + * Copyright (C) 2016, 2018 Baptiste Delporte @@ -265,10 +265,13 @@ + +extern void vbus_read_otherend_details(struct vbus_device *vdev, char *id_node, char *path_node); +extern int vbus_suspend_dev(struct bus_type *bus, unsigned int domID); ++int vbus_remove_dev(struct bus_type *bus, unsigned int domID); +extern int vbus_resume_dev(struct bus_type *bus, unsigned int domID); + +/* Prepare for domain suspend: then resume or cancel the suspend. */ +int vbus_suspend_devices(unsigned int domID); ++int vbus_remove_devices(unsigned int domID); ++void vbstore_capsule_remove(unsigned int domID); +int vbus_resume_devices(unsigned int domID); + +int vdev_probe(char *node); diff --git a/build/meta-linux/recipes-linux/soo/files/soo-generic/0065-vdevback.h.patch b/build/meta-linux/recipes-linux/soo/files/soo-generic/0065-vdevback.h.patch index a257a278dc..d2d1d9d3b0 100644 --- a/build/meta-linux/recipes-linux/soo/files/soo-generic/0065-vdevback.h.patch +++ b/build/meta-linux/recipes-linux/soo/files/soo-generic/0065-vdevback.h.patch @@ -1,6 +1,6 @@ --- /home/rossierd/soo/git/micofe/build/tmp/work/linux-6.12-r0/linux-6.12/soo/include/soo/vdevback.h 1970-01-01 01:00:00.000000000 +0100 +++ ./soo/include/soo/vdevback.h 2025-08-29 08:46:41.396178895 +0200 -@@ -0,0 +1,90 @@ +@@ -0,0 +1,91 @@ +/* + * Copyright (C) 2020 Daniel Rossier + * @@ -72,6 +72,7 @@ + +void vdevback_init(char *name, vdrvback_t *vdrvback); +bool vdevback_processing_begin(struct vbus_device *vdev); ++bool vdevback_processing_try_begin(struct vbus_device *vdev); +void vdevback_processing_end(struct vbus_device *vdev); + +bool vdevfront_is_connected(struct vbus_device *vdev); diff --git a/build/meta-linux/recipes-linux/soo/files/soo-generic/0073-capsule.c.patch b/build/meta-linux/recipes-linux/soo/files/soo-generic/0073-capsule.c.patch index 7c85d02998..6419145de1 100644 --- a/build/meta-linux/recipes-linux/soo/files/soo-generic/0073-capsule.c.patch +++ b/build/meta-linux/recipes-linux/soo/files/soo-generic/0073-capsule.c.patch @@ -1,6 +1,6 @@ --- /home/rossierd/soo/git/micofe/build/tmp/work/linux-6.12-r0/linux-6.12/soo/kernel/capsule.c 1970-01-01 01:00:00.000000000 +0100 +++ ./soo/kernel/capsule.c 2025-08-30 15:37:09.704925348 +0200 -@@ -0,0 +1,627 @@ +@@ -0,0 +1,804 @@ +/* + * Copyright (C) 2017-2024 Daniel Rossier + * @@ -30,6 +30,7 @@ +#include +#include +#include ++#include +#include +#include +#include @@ -59,6 +60,16 @@ + +struct device *capsule_dev = NULL; + ++/* Bounce buffer used to stream capsule snapshots to and from AVZ, one ++ * AVZ_STAGE_CHUNK_SIZE chunk at a time. It is allocated once, at init time, ++ * while the CMA zone is still pristine: a snapshot no longer requires a ++ * contiguous region of its own size, which the agency could not reliably ++ * obtain any more once its memory had been used (capsule slots are hundreds ++ * of MB, and cma_alloc() has to migrate every movable page out of the way). ++ */ ++static void *snapshot_bounce = NULL; ++static dma_addr_t snapshot_bounce_handle; ++ +/* Serialize the staged injection hypercall sequence (issue #287). */ + +static DEFINE_MUTEX(inject_lock); @@ -91,15 +102,38 @@ + void *me = NULL; + dma_addr_t dma_handle; + avz_hyp_t args; -+ uint32_t slot_size; ++ uint32_t slot_size, offset; + -+ DBG("Original contents at address: 0x%08x\n with size %d bytes\n", (unsigned long) buffer, size); ++ DBG("Original contents at address: %p\n with size %zu bytes\n", buffer, size); ++ ++ /* The size comes from user space along with the buffer it describes: ++ * reject anything the agency could not hold anyway, rather than turning ++ * it into an allocation request. ++ */ + -+ /* Allocate a contiguous memory region to host the S3C */ -+ me = dma_alloc_coherent(capsule_dev, size, &dma_handle, GFP_KERNEL); -+ BUG_ON(!me); ++ if ((size == 0) || (size > (totalram_pages() << PAGE_SHIFT))) { ++ printk("%s: Invalid capsule size (%zu bytes)\n", __func__, size); + -+ memcpy(me, buffer, size); ++ return -EINVAL; ++ } ++ ++ /* Allocate a contiguous memory region to host the S3C. AVZ parses the ++ * ITB as a whole, so this one cannot be streamed the way a snapshot is. ++ */ ++ ++ me = dma_alloc_coherent(capsule_dev, size, &dma_handle, GFP_KERNEL | __GFP_NOWARN); ++ if (!me) { ++ printk("%s: Failed to allocate %zu contiguous bytes for the capsule\n", __func__, size); ++ ++ return -ENOMEM; ++ } ++ ++ if (copy_from_user(me, buffer, size)) { ++ printk("%s: Failed to retrieve the capsule from user space\n", __func__); ++ dma_free_coherent(capsule_dev, size, me, dma_handle); ++ ++ return -EFAULT; ++ } + + /* Since the S3C buffer is in the CMA zone and allocated via the + * dma_alloc_cohenrent() function, we cannot use virt_to_phys(). So, we @@ -136,6 +170,8 @@ + args.u.avz_inject_capsule_args.offset = 0; + + while (args.u.avz_inject_capsule_args.offset < slot_size) { ++ offset = args.u.avz_inject_capsule_args.offset; ++ + args.u.avz_inject_capsule_args.stage = AVZ_STAGE_CHUNK; + + avz_hypercall(&args); @@ -143,6 +179,15 @@ + if (args.u.avz_inject_capsule_args.slotID < 0) + goto out; + ++ /* AVZ advances the cursor by what it actually cleared. */ ++ ++ if (args.u.avz_inject_capsule_args.offset <= offset) { ++ printk("%s: no progress at offset %u\n", __func__, offset); ++ args.u.avz_inject_capsule_args.slotID = -1; ++ ++ goto out; ++ } ++ + cond_resched(); + } + @@ -193,12 +238,9 @@ + * @param slotID + * @param buffer pointer to the S3C buffer + */ -+void read_snapshot(uint32_t slotID, void *buffer, uint32_t *size) { ++void read_snapshot(uint32_t slotID, void *buffer, uint32_t *size, bool resume) { + avz_hyp_t args; -+ void *me = NULL; -+ int ret; -+ uint32_t payload_size; -+ dma_addr_t dma_handle; ++ uint32_t payload_size, header_size, offset, chunk_size; + S3C_state_t S3C_state; + + /* No snapshot can be taken from a slot which does not host any capsule. */ @@ -236,19 +278,19 @@ + vbus_suspend_devices(slotID); + + do_sync_dom(slotID, DC_SUSPEND); -+ -+ printk("### state: %d\n", get_S3C_state(slotID)); + } + -+ /* -+ * Prepare a buffer to store the S3C and additional header information like the snapshot structure. -+ */ ++ if (!snapshot_bounce) { ++ printk("%s: no snapshot bounce buffer available\n", __func__); ++ *size = 0; + -+ me = dma_alloc_coherent(capsule_dev, *size, &dma_handle, GFP_KERNEL); -+ BUG_ON(!me); ++ return; ++ } + + /* The snapshot is read in stages (issue #287) so that the calling CPU -+ * gets its interrupts back between two chunks of capsule memory. ++ * gets its interrupts back between two chunks of capsule memory. Each ++ * chunk transits through the bounce buffer and is handed over to user ++ * space right away, so the whole snapshot is never held twice in RAM. + */ + + mutex_lock(&inject_lock); @@ -256,32 +298,78 @@ + args.cmd = AVZ_S3C_READ_SNAPSHOT; + + args.u.avz_snapshot_args.slotID = slotID; -+ args.u.avz_snapshot_args.snapshot_paddr = (void *) dma_handle; ++ args.u.avz_snapshot_args.snapshot_paddr = (void *) snapshot_bounce_handle; + args.u.avz_snapshot_args.size = *size; + args.u.avz_snapshot_args.stage = AVZ_STAGE_INIT; + + avz_hypercall(&args); + + if (args.u.avz_snapshot_args.size == 0) -+ goto out; ++ goto fail; + -+ /* INIT returns the amount of capsule memory to be copied. */ ++ /* INIT returns the amount of capsule memory to be copied in , ++ * the header it just left in the bounce buffer accounting for the rest. ++ */ + + payload_size = args.u.avz_snapshot_args.offset; + ++ if ((payload_size >= args.u.avz_snapshot_args.size) || ++ (args.u.avz_snapshot_args.size - payload_size > AVZ_STAGE_CHUNK_SIZE)) { ++ printk("%s: inconsistent snapshot header (size %d, payload %u)\n", __func__, ++ args.u.avz_snapshot_args.size, payload_size); ++ ++ goto fail; ++ } ++ ++ header_size = args.u.avz_snapshot_args.size - payload_size; ++ ++ if (copy_to_user(buffer, snapshot_bounce, header_size)) ++ goto fail; ++ + args.u.avz_snapshot_args.offset = 0; + + while (args.u.avz_snapshot_args.offset < payload_size) { ++ offset = args.u.avz_snapshot_args.offset; ++ + args.u.avz_snapshot_args.stage = AVZ_STAGE_CHUNK; ++ args.u.avz_snapshot_args.size = AVZ_STAGE_CHUNK_SIZE; + + avz_hypercall(&args); + + if (args.u.avz_snapshot_args.size == 0) -+ goto out; ++ goto fail; ++ ++ /* AVZ advances the cursor by what it actually copied. */ ++ ++ chunk_size = args.u.avz_snapshot_args.offset - offset; ++ ++ if ((chunk_size == 0) || (chunk_size > AVZ_STAGE_CHUNK_SIZE)) { ++ printk("%s: invalid chunk of %u bytes at offset %u\n", __func__, chunk_size, offset); ++ ++ goto fail; ++ } ++ ++ if (copy_to_user(buffer + header_size + offset, snapshot_bounce, chunk_size)) ++ goto fail; + + cond_resched(); + } + ++ /* The capsule is resumed, unless the caller is about to shut it down. */ ++ ++ args.u.avz_snapshot_args.stage = (resume ? AVZ_STAGE_FINALIZE : AVZ_STAGE_FINALIZE_HOLD); ++ ++ avz_hypercall(&args); ++ ++ goto out; ++ ++fail: ++ *size = 0; ++ ++ /* The capsule was suspended before the first stage: bring it back ++ * whatever went wrong, rather than leaving it frozen for good. ++ */ ++ + args.u.avz_snapshot_args.stage = AVZ_STAGE_FINALIZE; + + avz_hypercall(&args); @@ -289,12 +377,6 @@ +out: + mutex_unlock(&inject_lock); + -+ /* Copy the snapshot to the user buffer */ -+ ret = copy_to_user(buffer, me, *size); -+ BUG_ON(ret); -+ -+ dma_free_coherent(capsule_dev, *size, me, dma_handle); -+ + S3C_state = get_S3C_state(slotID); + + if (S3C_state == S3C_state_resuming) { @@ -322,30 +404,60 @@ + */ +int write_snapshot(void *buffer) { + avz_hyp_t args; -+ void *me = NULL; -+ int ret; -+ dma_addr_t dma_handle; -+ uint32_t snapshot_size; -+ uint32_t payload_size; ++ int ret = 0; ++ uint32_t snapshot_size, header_size, head_len; ++ uint32_t payload_size, offset, chunk_size; + uint32_t slotID; + S3C_state_t S3C_state; + -+ snapshot_size = *((uint32_t *) buffer); ++ /* The snapshot begins with its own size. The buffer belongs to user space, ++ * hence the copy_from_user() and the sanity check below: a corrupted or ++ * uninitialized buffer must be rejected here instead of being turned into ++ * an absurd allocation request. ++ */ ++ ++ if (copy_from_user(&snapshot_size, buffer, sizeof(snapshot_size))) ++ return -EFAULT; ++ ++ DBG("Retrieving a size of %u bytes.\n", snapshot_size); + -+ DBG("Retrieving a size of %d bytes.\n", snapshot_size); ++ if ((snapshot_size < sizeof(snapshot_size)) || ++ (snapshot_size > (totalram_pages() << PAGE_SHIFT))) { ++ printk("%s: Invalid snapshot size (%u bytes)\n", __func__, snapshot_size); + -+ /* -+ * Prepare a buffer to store the S3C and additional header information like the snapshot structure. ++ return -EINVAL; ++ } ++ ++ if (!snapshot_bounce) { ++ printk("%s: no snapshot bounce buffer available\n", __func__); ++ ++ return -ENOMEM; ++ } ++ ++ /* AVZ reads the snapshot header (payload size + domain context) at the ++ * INIT stage: hand it over in the bounce buffer, which the CHUNK stages ++ * then reuse for the capsule memory itself. + */ -+ me = dma_alloc_coherent(capsule_dev, snapshot_size, &dma_handle, GFP_KERNEL); -+ BUG_ON(!me); + -+ /* Copy the snapshot to the user buffer */ -+ ret = copy_from_user(me, buffer, snapshot_size); -+ BUG_ON(ret); ++ /* The size stored at the beginning of a snapshot does not count itself, ++ * so the buffer holds sizeof(uint32_t) more bytes than it says. ++ */ ++ ++ head_len = snapshot_size + sizeof(snapshot_size); ++ ++ if (head_len > AVZ_STAGE_CHUNK_SIZE) ++ head_len = AVZ_STAGE_CHUNK_SIZE; ++ ++ if (copy_from_user(snapshot_bounce, buffer, head_len)) { ++ printk("%s: Failed to retrieve the snapshot header from user space\n", __func__); ++ ++ return -EFAULT; ++ } + + /* The snapshot is written in stages (issue #287) so that the calling + * CPU gets its interrupts back between two chunks of capsule memory. ++ * Each chunk transits through the bounce buffer, so the whole snapshot ++ * is never held twice in RAM. + */ + + mutex_lock(&inject_lock); @@ -354,7 +466,7 @@ + + args.u.avz_snapshot_args.size = snapshot_size; + args.u.avz_snapshot_args.slotID = -1; -+ args.u.avz_snapshot_args.snapshot_paddr = (void *) dma_handle; ++ args.u.avz_snapshot_args.snapshot_paddr = (void *) snapshot_bounce_handle; + args.u.avz_snapshot_args.stage = AVZ_STAGE_INIT; + + DBG("%s: Now asking AVZ to re-implement the snapshot of size %d bytes...\n", __func__, snapshot_size); @@ -368,23 +480,73 @@ + if (args.u.avz_snapshot_args.slotID < 2) + goto out; + -+ /* INIT returns the amount of capsule memory to be copied. */ ++ /* INIT returns the amount of capsule memory to be copied in , ++ * the header accounting for the rest of the snapshot. ++ */ + + payload_size = args.u.avz_snapshot_args.offset; + ++ if (payload_size >= snapshot_size) { ++ printk("%s: inconsistent snapshot (size %u, payload %u)\n", __func__, snapshot_size, payload_size); ++ args.u.avz_snapshot_args.slotID = -1; ++ ++ goto out; ++ } ++ ++ /* The header is the size field plus the domain context, the latter being ++ * whatever the snapshot holds beyond the capsule memory itself. ++ */ ++ ++ header_size = sizeof(snapshot_size) + (snapshot_size - payload_size); ++ + args.u.avz_snapshot_args.offset = 0; + + while (args.u.avz_snapshot_args.offset < payload_size) { ++ offset = args.u.avz_snapshot_args.offset; ++ chunk_size = payload_size - offset; ++ ++ if (chunk_size > AVZ_STAGE_CHUNK_SIZE) ++ chunk_size = AVZ_STAGE_CHUNK_SIZE; ++ ++ if (copy_from_user(snapshot_bounce, buffer + header_size + offset, chunk_size)) { ++ printk("%s: Failed to retrieve the snapshot from user space\n", __func__); ++ ret = -EFAULT; ++ ++ goto out; ++ } ++ + args.u.avz_snapshot_args.stage = AVZ_STAGE_CHUNK; ++ args.u.avz_snapshot_args.size = chunk_size; + + avz_hypercall(&args); + + if (args.u.avz_snapshot_args.slotID < 2) + goto out; + ++ /* AVZ advances the cursor by what it actually copied. */ ++ ++ if (args.u.avz_snapshot_args.offset <= offset) { ++ printk("%s: no progress at offset %u\n", __func__, offset); ++ args.u.avz_snapshot_args.slotID = -1; ++ ++ goto out; ++ } ++ + cond_resched(); + } + ++ /* AVZ reads the domain context out of the buffer again at the FINALIZE ++ * stage, to restore the EL2 frame of the capsule: put the header back, ++ * the CHUNK stages having filled the buffer with capsule memory since. ++ */ ++ ++ if (copy_from_user(snapshot_bounce, buffer, head_len)) { ++ printk("%s: Failed to retrieve the snapshot header from user space\n", __func__); ++ ret = -EFAULT; ++ ++ goto out; ++ } ++ + args.u.avz_snapshot_args.stage = AVZ_STAGE_FINALIZE; + + avz_hypercall(&args); @@ -392,16 +554,17 @@ +out: + mutex_unlock(&inject_lock); + ++ if (ret) ++ return ret; ++ + if (args.u.avz_snapshot_args.slotID < 2) { + printk("%s: No free space...\n", __func__); -+ dma_free_coherent(capsule_dev, snapshot_size, me, dma_handle); ++ + return -1; /* No free space */ + } + + slotID = args.u.avz_snapshot_args.slotID; + -+ dma_free_coherent(capsule_dev, snapshot_size, me, dma_handle); -+ + S3C_state = get_S3C_state(slotID); + + if (S3C_state == S3C_state_stopped) { @@ -622,9 +785,23 @@ + capsule_dev = cma_malloc_miscdevice.this_device; + capsule_dev->coherent_dma_mask = DMA_BIT_MASK(64); + capsule_dev->dma_mask = &capsule_dev->coherent_dma_mask; ++ ++ /* Reserve the snapshot bounce buffer now, while the CMA zone is still ++ * free of any movable page which would have to be migrated away. ++ */ ++ ++ snapshot_bounce = dma_alloc_coherent(capsule_dev, AVZ_STAGE_CHUNK_SIZE, &snapshot_bounce_handle, GFP_KERNEL); ++ if (!snapshot_bounce) ++ printk("%s: failed to reserve the %d bytes snapshot bounce buffer\n", __func__, ++ AVZ_STAGE_CHUNK_SIZE); +} + +void capsule_exit(void) { ++ if (snapshot_bounce) { ++ dma_free_coherent(capsule_dev, AVZ_STAGE_CHUNK_SIZE, snapshot_bounce, snapshot_bounce_handle); ++ snapshot_bounce = NULL; ++ } ++ + misc_deregister(&cma_malloc_miscdevice); + capsule_dev = NULL; +} diff --git a/build/meta-linux/recipes-linux/soo/files/soo-generic/0074-console.c.patch b/build/meta-linux/recipes-linux/soo/files/soo-generic/0074-console.c.patch index b4270b4115..0c408acda1 100644 --- a/build/meta-linux/recipes-linux/soo/files/soo-generic/0074-console.c.patch +++ b/build/meta-linux/recipes-linux/soo/files/soo-generic/0074-console.c.patch @@ -1,6 +1,6 @@ --- /home/rossierd/soo/git/micofe/build/tmp/work/linux-6.12-r0/linux-6.12/soo/kernel/console/console.c 1970-01-01 01:00:00.000000000 +0100 +++ ./soo/kernel/console/console.c 2025-08-29 08:46:41.394716106 +0200 -@@ -0,0 +1,152 @@ +@@ -0,0 +1,210 @@ +/* + * Copyright (C) 2016-2019 Daniel Rossier + * @@ -32,6 +32,7 @@ + +#include +#include ++#include +#include + +/* 1 is the code for CTRL-A */ @@ -39,6 +40,14 @@ + +#define N_SWITCH_FOCUS 8 + ++/* Focus targets, in the order of below: the agency, the reserved ++ * slot, the capsule slots, and the hypervisor. ++ */ ++#define FOCUS_AGENCY 0 ++#define FOCUS_RESERVED 1 ++#define FOCUS_S3C_FIRST 2 ++#define FOCUS_S3C_LAST (N_SWITCH_FOCUS - 2) ++ +/* Volatile to ensure the value is read from memory and no optimization occurs */ +static int volatile avzcons_active = 0; +static int volatile vfb_active = 0; @@ -51,6 +60,45 @@ + return avzcons_active; +} + ++/** ++ * Tell whether the console input may be routed to this focus target. ++ * ++ * The agency and the hypervisor always qualify, the reserved slot never does, ++ * and a capsule slot only while its capsule is living. Every other state is a ++ * capsule which is gone or in transit -- being injected, suspended for a ++ * snapshot, resuming, or shut down -- and its vUART frontend is then not ++ * connected, so the keystrokes would have nowhere to go. ++ */ ++static bool console_focus_available(int focus) ++{ ++ if (focus == FOCUS_RESERVED) ++ return false; ++ ++ if ((focus < FOCUS_S3C_FIRST) || (focus > FOCUS_S3C_LAST)) ++ return true; ++ ++ return (get_S3C_state(focus) == S3C_state_living); ++} ++ ++/** ++ * Find the next focus target after @focus, skipping those which cannot take the ++ * input. The agency always qualifies, so the scan always ends somewhere. ++ */ ++static int console_next_focus(int focus, u32 focus_max) ++{ ++ int candidate = focus; ++ int scanned; ++ ++ for (scanned = 0; scanned <= focus_max; scanned++) { ++ candidate = (candidate + 1) % (focus_max + 1); ++ ++ if (console_focus_available(candidate)) ++ return candidate; ++ } ++ ++ return FOCUS_AGENCY; ++} ++ +int avzcons_set_focus(int next_domain) +{ + avzcons_active = next_domain; @@ -104,11 +152,8 @@ + /* We eat CTRL- in groups of 2 to switch console input. */ + if (++switch_code_count == 1) { + -+ active = (avzcons_get_focus() + 1) % (focus_max + 1); -+ active = ((active == 1) ? active+1 : active); -+ -+ next = (active + 1) % (focus_max + 1); -+ next = ((next == 1) ? next+1 : next); ++ active = console_next_focus(avzcons_get_focus(), focus_max); ++ next = console_next_focus(active, focus_max); + + avzcons_set_focus(active); + @@ -122,6 +167,19 @@ + } else { + switch_code_count = 0; + ++ /* The focused capsule may have gone in the meantime -- taking a ++ * snapshot of it shuts it down. Hand the input back to the agency ++ * instead of dropping every keystroke into a slot nobody serves. ++ */ ++ ++ if (!console_focus_available(avzcons_get_focus())) { ++ avzcons_set_focus(FOCUS_AGENCY); ++ ++ lprintk("*** The capsule is gone, serial input -> %s.\n", input_str[FOCUS_AGENCY]); ++ ++ return 0; ++ } ++ + switch (avzcons_get_focus()) { + default: + case 0: /* Input to the agency */ diff --git a/build/meta-linux/recipes-linux/soo/files/soo-generic/0077-core.c.patch b/build/meta-linux/recipes-linux/soo/files/soo-generic/0077-core.c.patch index 30e7ac88a7..f14552c429 100644 --- a/build/meta-linux/recipes-linux/soo/files/soo-generic/0077-core.c.patch +++ b/build/meta-linux/recipes-linux/soo/files/soo-generic/0077-core.c.patch @@ -1,6 +1,6 @@ --- /home/rossierd/soo/git/micofe/build/tmp/work/linux-6.12-r0/linux-6.12/soo/kernel/core.c 1970-01-01 01:00:00.000000000 +0100 +++ ./soo/kernel/core.c 2025-08-30 15:29:26.627707308 +0200 -@@ -0,0 +1,317 @@ +@@ -0,0 +1,368 @@ +/* + * Copyright (C) 2014-2019 Daniel Rossier + * Copyright (C) 2017, 2018 Baptiste Delporte @@ -39,6 +39,7 @@ +#include +#include +#include ++#include + +#define AGENCY_DEV_NAME "soo/core" +#define AGENCY_DEV_MAJOR 126 @@ -59,28 +60,50 @@ +static int shutdown_capsule(unsigned int S3C_slotID) +{ + avz_hyp_t args; ++ S3C_state_t S3C_state; + + /* Nothing to do if the slot does not host any capsule; a DC event + * sent to an empty slot has no possible recipient. + */ + -+ if (get_S3C_state(S3C_slotID) == S3C_state_dead) { ++ S3C_state = get_S3C_state(S3C_slotID); ++ ++ if (S3C_state == S3C_state_dead) { + lprintk("%s: no capsule in slot %d, nothing to shut down.\n", __func__, S3C_slotID); + return -ESRCH; + } + -+ /* Asynchronous termination of the capsule. A crashed capsule (killed -+ * by AVZ after a fault) cannot answer the DC_SHUTDOWN handshake -+ * anymore: it is reaped directly. ++ /* Asynchronous termination of the capsule, except for the states in which ++ * it cannot answer the DC_SHUTDOWN handshake: stopped (injected, never ++ * started), killed (faulted, reaped by AVZ) and suspended (held after a ++ * snapshot, hence not scheduled any more). do_sync_dom() waits for that ++ * answer without any deadline, so those are reaped directly. + */ + -+ if ((get_S3C_state(S3C_slotID) != S3C_state_stopped) && (get_S3C_state(S3C_slotID) != S3C_state_killed)) ++ if ((S3C_state != S3C_state_stopped) && (S3C_state != S3C_state_killed) && ++ (S3C_state != S3C_state_suspended)) { + do_sync_dom(S3C_slotID, DC_SHUTDOWN); + ++ S3C_state = get_S3C_state(S3C_slotID); ++ } else { ++ /* That handshake is also what makes the capsule close its frontends, ++ * and a backend is removed when its frontend does so. Nobody will: ++ * tear them down here, or the next capsule to land in this slot finds ++ * them and vbus_dev_changed() refuses to probe over them. ++ */ ++ ++ vbus_remove_devices(S3C_slotID); ++ ++ /* Same for the vbstore entries the capsule would have removed ++ * itself: the next capsule in this slot must not find them. ++ */ ++ ++ vbstore_capsule_remove(S3C_slotID); ++ } ++ + /* Then, final termination of the residual S3C */ -+ if ((get_S3C_state(S3C_slotID) == S3C_state_terminated) || -+ (get_S3C_state(S3C_slotID) == S3C_state_stopped) || -+ (get_S3C_state(S3C_slotID) == S3C_state_killed)) { ++ if ((S3C_state == S3C_state_terminated) || (S3C_state == S3C_state_stopped) || ++ (S3C_state == S3C_state_killed) || (S3C_state == S3C_state_suspended)) { + args.cmd = AVZ_KILL_S3C; + args.u.avz_kill_me_args.slotID = S3C_slotID; + @@ -117,15 +140,43 @@ + break; + + case AGENCY_IOCTL_READ_SNAPSHOT: -+ read_snapshot(args.slotID, args.buffer, (uint32_t *)&args.value); ++ case AGENCY_IOCTL_READ_SNAPSHOT_HOLD: ++ ++ /* A capsule keeps running once it has been snapshotted, unless the ++ * caller asked to hold it suspended because it shuts it down next. ++ */ ++ ++ read_snapshot(args.slotID, args.buffer, (uint32_t *)&args.value, ++ (cmd == AGENCY_IOCTL_READ_SNAPSHOT)); ++ ++ /* A size of zero means no snapshot could be read: the caller must ++ * not store what it was given. ++ */ ++ ++ if (args.value == 0) ++ ret = -EIO; + break; + + case AGENCY_IOCTL_WRITE_SNAPSHOT: + args.slotID = write_snapshot(args.buffer); ++ ++ /* Let the caller see the failure: the snapshot may be rejected ++ * before any slot gets allocated. ++ */ ++ ++ if (args.slotID < 0) ++ ret = args.slotID; + break; + + case AGENCY_IOCTL_INJECT_CAPSULE: + args.slotID = inject_capsule(args.buffer, args.value, args.slotID, args.capsuleID); ++ ++ /* Let the caller see the failure: no slot could be allocated, or ++ * the capsule image was rejected before that. ++ */ ++ ++ if (args.slotID < 0) ++ ret = args.slotID; + break; + + case AGENCY_IOCTL_START_CAPSULE: diff --git a/build/meta-linux/recipes-linux/soo/files/soo-generic/0093-vbus_backend.c.patch b/build/meta-linux/recipes-linux/soo/files/soo-generic/0093-vbus_backend.c.patch index 06a77a0f23..073acdb352 100644 --- a/build/meta-linux/recipes-linux/soo/files/soo-generic/0093-vbus_backend.c.patch +++ b/build/meta-linux/recipes-linux/soo/files/soo-generic/0093-vbus_backend.c.patch @@ -1,6 +1,6 @@ --- /home/rossierd/soo/git/micofe/build/tmp/work/linux-6.12-r0/linux-6.12/soo/kernel/vbus/vbus_backend.c 1970-01-01 01:00:00.000000000 +0100 +++ ./soo/kernel/vbus/vbus_backend.c 2025-08-29 08:46:41.395033838 +0200 -@@ -0,0 +1,314 @@ +@@ -0,0 +1,372 @@ +/* + * Copyright (C) 2016-2019 Daniel Rossier + * @@ -295,6 +295,64 @@ +} + +/* ++ * vbus_remove_devices ++ * ++ * Remove the backends of a capsule which cannot close them itself. ++ */ ++int vbus_remove_devices(unsigned int domID) ++{ ++ return vbus_remove_dev(&vbus_backend.bus, domID); ++} ++ ++/* ++ * vbstore_capsule_remove ++ * ++ * Remove the vbstore entries a capsule leaves behind. ++ * ++ * A capsule removes its own entries when it terminates, as part of the ++ * DC_SHUTDOWN handshake. One which is suspended, stopped or already dead never ++ * gets there, so the agency does it in its stead: those entries must not ++ * outlive the capsule which owned them, or the next capsule to land in that ++ * slot comes up on top of what the previous one left. ++ * ++ * The layout mirrors what vbus_probe_backend() walks: one directory per device ++ * type under backend/, each holding a directory per domain. ++ */ ++void vbstore_capsule_remove(unsigned int domID) ++{ ++ char noderoot[VBS_KEY_LENGTH]; ++ char node[VBS_KEY_LENGTH]; ++ char **dir; ++ unsigned int i, dir_n = 0; ++ ++ sprintf(node, "%d", domID); ++ ++ DBG("Removing the vbstore entries of domain %d...\n", domID); ++ ++ dir = vbus_directory(VBT_NIL, "backend", "", &dir_n); ++ if (IS_ERR(dir)) ++ return; ++ ++ for (i = 0; i < dir_n; i++) { ++ strcpy(noderoot, "backend/"); ++ strcat(noderoot, dir[i]); ++ ++ if (vbus_directory_exists(VBT_NIL, noderoot, node)) ++ vbus_rm(VBT_NIL, noderoot, node); ++ } ++ ++ kfree(dir); ++ ++ /* The frontend side, and the capsule entry itself. */ ++ ++ if (vbus_directory_exists(VBT_NIL, "device", node)) ++ vbus_rm(VBT_NIL, "device", node); ++ ++ if (vbus_directory_exists(VBT_NIL, "soo/s3c", node)) ++ vbus_rm(VBT_NIL, "soo/s3c", node); ++} ++ ++/* + * vbus_resume_devices + * + * Inform all vbus devices that we are preparing to suspend (or some other scenarios requiring diff --git a/build/meta-linux/recipes-linux/soo/files/soo-generic/0094-vbus.c.patch b/build/meta-linux/recipes-linux/soo/files/soo-generic/0094-vbus.c.patch index c91c6abe1d..979cb62325 100644 --- a/build/meta-linux/recipes-linux/soo/files/soo-generic/0094-vbus.c.patch +++ b/build/meta-linux/recipes-linux/soo/files/soo-generic/0094-vbus.c.patch @@ -1,6 +1,6 @@ --- /home/rossierd/soo/git/micofe/build/tmp/work/linux-6.12-r0/linux-6.12/soo/kernel/vbus/vbus.c 1970-01-01 01:00:00.000000000 +0100 +++ ./soo/kernel/vbus/vbus.c 2025-08-29 08:46:41.395052929 +0200 -@@ -0,0 +1,870 @@ +@@ -0,0 +1,947 @@ + +/* + * Copyright (C) 2014-2019 Daniel Rossier @@ -695,6 +695,83 @@ + return 0; +} + ++/* ++ * Look for a backend still attached to a capsule. The device is reported back ++ * instead of being removed on the spot: device_unregister() must not run from ++ * within the bus walk. ++ */ ++struct vb_domain_info { ++ unsigned int domID; ++ struct vbus_device *vdev; ++}; ++ ++static int find_dev_of_domain(struct device *dev, void *data) ++{ ++ struct vb_domain_info *info = (struct vb_domain_info *) data; ++ struct vbus_device *vdev = (struct vbus_device *) to_vbus_device(dev); ++ unsigned int curDomID; ++ char *ptr_item; ++ char item[80]; ++ ++ /* Node names are shaped as backend///. */ ++ ++ ptr_item = strchr(vdev->nodename, '/'); ++ if (ptr_item == NULL) ++ return 0; ++ ++ ptr_item = strchr(ptr_item + 1, '/'); ++ if (ptr_item == NULL) ++ return 0; ++ ++ if (sscanf(ptr_item + 1, "%d/%s", &curDomID, item) < 1) ++ return 0; ++ ++ if (curDomID != info->domID) ++ return 0; ++ ++ info->vdev = vdev; ++ ++ /* Hold the device until the caller is done removing it, the walk being ++ * over by then. ++ */ ++ ++ get_device(dev); ++ ++ return 1; ++} ++ ++/* ++ * Remove every backend a capsule leaves behind. ++ * ++ * A backend is normally removed when its frontend switches to Closing, which ++ * the DC_SHUTDOWN handshake drives. A capsule which is suspended, stopped or ++ * already dead cannot answer that handshake, so the agency has to clean up on ++ * its own before the slot is handed over: the next capsule landing there would ++ * otherwise find those nodes, and vbus_dev_changed() refuses to probe a device ++ * which already exists. ++ */ ++int vbus_remove_dev(struct bus_type *bus, unsigned int domID) ++{ ++ struct vb_domain_info info; ++ ++ while (true) { ++ info.domID = domID; ++ info.vdev = NULL; ++ ++ bus_for_each_dev(bus, NULL, &info, find_dev_of_domain); ++ ++ if (info.vdev == NULL) ++ break; ++ ++ DBG("Removing the leftover backend %s\n", info.vdev->nodename); ++ ++ device_unregister(&info.vdev->dev); ++ put_device(&info.vdev->dev); ++ } ++ ++ return 0; ++} ++ +/******************/ + +/* diff --git a/build/meta-usr/recipes-usr/soo/files/0001-usr-linux-1.0-r0/0012-soo.h.patch b/build/meta-usr/recipes-usr/soo/files/0001-usr-linux-1.0-r0/0012-soo.h.patch index 5f1b204e16..49ec35038b 100644 --- a/build/meta-usr/recipes-usr/soo/files/0001-usr-linux-1.0-r0/0012-soo.h.patch +++ b/build/meta-usr/recipes-usr/soo/files/0001-usr-linux-1.0-r0/0012-soo.h.patch @@ -1,6 +1,6 @@ --- /home/rossierd/soo/micofe/build/tmp/work/usr-linux-1.0-r0/usr-linux-1.0/include/soo/uapi/soo.h 1970-01-01 01:00:00.000000000 +0100 +++ ./include/soo/uapi/soo.h 2025-10-10 17:28:33.584815499 +0200 -@@ -0,0 +1,98 @@ +@@ -0,0 +1,103 @@ +/* + * Copyright (C) 2014-2025 Daniel Rossier + * @@ -90,6 +90,11 @@ +#define AGENCY_IOCTL_GET_S3C_ID _IOWR('S', 6, agency_ioctl_args_t) +#define AGENCY_IOCTL_GET_S3C_ID_ARRAY _IOR('S', 7, agency_ioctl_args_t) + ++/* Same as AGENCY_IOCTL_READ_SNAPSHOT, but the capsule is left suspended instead ++ * of being resumed: for a caller which shuts it down right after. ++ */ ++#define AGENCY_IOCTL_READ_SNAPSHOT_HOLD _IOWR('S', 8, agency_ioctl_args_t) ++ +/* struct agency_ioctl_args used in IOCTLs */ +typedef struct agency_ioctl_args { + void *buffer; /* IN/OUT */ diff --git a/scripts/common/sudo_session.sh b/scripts/common/sudo_session.sh index 2f5b40416f..11f8ada4d0 100644 --- a/scripts/common/sudo_session.sh +++ b/scripts/common/sudo_session.sh @@ -17,13 +17,21 @@ # # keep-alive is auto-killed on EXIT/INT/TERM via the installed trap sudo_session_start() { - # Validate the sudo timestamp upfront. With a NOPASSWD sudoers - # entry this is a silent no-op; otherwise the user gets a single + # Validate the sudo timestamp upfront, so the user gets a single # password prompt right here, BEFORE bitbake starts. - if ! sudo -v + # + # The probe is a real (harmless) command rather than `sudo -v`: the + # latter validates the user through the 'validate' pseudo-command, + # which a sudoers granting NOPASSWD to commands may still refuse -- + # and it would then demand an authentication that no privileged task + # of the build actually needs. + if ! sudo -n true 2>/dev/null then - printf "Error: failed to acquire sudo credentials\n" >&2 - return 1 + if ! sudo -v + then + printf "Error: failed to acquire sudo credentials\n" >&2 + return 1 + fi fi # The keep-alive below refreshes the timestamp every 60 s so that diff --git a/so3/so3/avz/include/avz/gnttab.h b/so3/so3/avz/include/avz/gnttab.h index dbfa64d49a..b0ccf5e3d9 100644 --- a/so3/so3/avz/include/avz/gnttab.h +++ b/so3/so3/avz/include/avz/gnttab.h @@ -38,6 +38,7 @@ struct gnttab { typedef struct gnttab gnttab_t; void gnttab_init(struct domain *d); +void gnttab_destroy(struct domain *d); void do_gnttab(gnttab_op_t *args); addr_t map_vbstore_pfn(int target_domid, int pfn); diff --git a/so3/so3/avz/kernel/domain.c b/so3/so3/avz/kernel/domain.c index 379a07bef6..37137e9cc8 100644 --- a/so3/so3/avz/kernel/domain.c +++ b/so3/so3/avz/kernel/domain.c @@ -134,6 +134,12 @@ static void complete_domain_destroy(struct domain *d) { sched_destroy_domain(d); +#ifdef CONFIG_SOO + /* The grants this domain still holds die with it. */ + + gnttab_destroy(d); +#endif /* CONFIG_SOO */ + /* Remove the root page table */ reset_root_pgtable((void *) d->pagetable_l0_vaddr, true); diff --git a/so3/so3/avz/kernel/gnttab.c b/so3/so3/avz/kernel/gnttab.c index e2df5b23d7..e9cb2346ed 100644 --- a/so3/so3/avz/kernel/gnttab.c +++ b/so3/so3/avz/kernel/gnttab.c @@ -105,6 +105,25 @@ gnttab_t *new_gnttab_entry(struct domain *d, domid_t target_domid, addr_t pfn) return gnttab; } +/** + * @brief Release the whole grant table of a domain. + * + * A domain revokes its grants one by one, as its frontends close. One which is + * destroyed before it gets there -- suspended for a snapshot, crashed, or + * killed outright -- leaves them all behind, and the entries would then outlive + * the domain they describe: the memory is recycled by the next domain, which + * inherits list links pointing into it. + */ +void gnttab_destroy(struct domain *d) +{ + gnttab_t *cur, *tmp; + + list_for_each_entry_safe(cur, tmp, &d->gnttab, list) { + list_del(&cur->list); + free(cur); + } +} + void revoke_gnttab_entry(struct domain *d, grant_ref_t ref) { gnttab_t *cur; diff --git a/so3/so3/avz/kernel/injector.c b/so3/so3/avz/kernel/injector.c index 4406e2f727..5c3a817c35 100644 --- a/so3/so3/avz/kernel/injector.c +++ b/so3/so3/avz/kernel/injector.c @@ -47,20 +47,27 @@ static struct dom_context domain_context = { 0 }; /* Maximum amount of domain memory moved in one AVZ_STAGE_CHUNK call. * This bounds the time spent at EL2 with IRQs off on the calling CPU - * (issue #287). + * (issue #287). It is shared with the agency, which sizes its snapshot + * bounce buffer accordingly. */ -#define STAGE_CHUNK_SIZE (4 * SZ_1M) +#define STAGE_CHUNK_SIZE AVZ_STAGE_CHUNK_SIZE /** * Compute the size of the next chunk to be moved, and advance the cursor. + * + * @max further bounds the chunk to what the agency buffer can hold; pass 0 when + * no agency buffer is involved (the injection clears the slot in place). */ -static size_t stage_next_chunk(uint32_t *offset, size_t total) +static size_t stage_next_chunk(uint32_t *offset, size_t total, size_t max) { size_t chunk_size = total - *offset; if (chunk_size > STAGE_CHUNK_SIZE) chunk_size = STAGE_CHUNK_SIZE; + if (max && (chunk_size > max)) + chunk_size = max; + *offset += chunk_size; return chunk_size; @@ -159,7 +166,7 @@ void inject_capsule(avz_hyp_t *args) /* Clear the next chunk of the RAM allocated to this capsule */ - chunk_size = stage_next_chunk(&args->u.avz_inject_capsule_args.offset, memslot[slotID].size); + chunk_size = stage_next_chunk(&args->u.avz_inject_capsule_args.offset, memslot[slotID].size, 0); memset((void *) __xva(slotID, memslot[slotID].base_paddr + offset), 0, chunk_size); @@ -392,14 +399,29 @@ void read_S3C_snapshot(avz_hyp_t *args) return; } - chunk_size = stage_next_chunk(&args->u.avz_snapshot_args.offset, memslot[slotID].size); + chunk_size = stage_next_chunk(&args->u.avz_snapshot_args.offset, memslot[slotID].size, + args->u.avz_snapshot_args.size); - memcpy(snapshot_buffer + payload_offset + offset, (void *) __xva(slotID, memslot[slotID].base_paddr + offset), - chunk_size); + /* The chunk is handed over at the beginning of the bounce buffer; + * placing it in the snapshot is up to the agency. + */ + + memcpy(snapshot_buffer, (void *) __xva(slotID, memslot[slotID].base_paddr + offset), chunk_size); break; case AVZ_STAGE_FINALIZE: + case AVZ_STAGE_FINALIZE_HOLD: + + /* A snapshot leaves the capsule living: it is suspended for the time + * the memory is read, then resumed. The HOLD variant skips that, for + * an agency which shuts the capsule down right after -- resuming it + * only to kill it would let it run, and diverge from the snapshot + * just taken, for nothing. + */ + + if (args->u.avz_snapshot_args.stage == AVZ_STAGE_FINALIZE_HOLD) + break; if (dom_S3C->avz_shared->dom_desc.u.S3C.state == S3C_state_suspended) { /* Now, this capsule is suspended and must be resumed by the agency */ @@ -560,10 +582,14 @@ void write_S3C_snapshot(avz_hyp_t *args) /* Copy the next chunk of the capsule content */ - chunk_size = stage_next_chunk(&args->u.avz_snapshot_args.offset, memslot[slotID].size); + chunk_size = stage_next_chunk(&args->u.avz_snapshot_args.offset, memslot[slotID].size, + args->u.avz_snapshot_args.size); + + /* The agency has placed the chunk at the beginning of the bounce + * buffer, whatever its position in the snapshot. + */ - memcpy((void *) __xva(slotID, memslot[slotID].base_paddr + offset), snapshot_buffer + payload_offset + offset, - chunk_size); + memcpy((void *) __xva(slotID, memslot[slotID].base_paddr + offset), snapshot_buffer, chunk_size); return; } diff --git a/so3/so3/soo/include/soo/uapi/soo.h b/so3/so3/soo/include/soo/uapi/soo.h index 008e2beaa6..3e2429a0ff 100644 --- a/so3/so3/soo/include/soo/uapi/soo.h +++ b/so3/so3/soo/include/soo/uapi/soo.h @@ -334,6 +334,22 @@ typedef struct agency_ioctl_args { #define AVZ_STAGE_CHUNK 1 #define AVZ_STAGE_FINALIZE 2 +/* Snapshot only: complete without resuming the capsule, which is left + * suspended. Taking a snapshot normally leaves the capsule living -- that is + * the point of snapshotting a running capsule -- but a caller which is about to + * shut it down would otherwise let it run, and diverge from the snapshot it has + * just taken, for nothing. + */ +#define AVZ_STAGE_FINALIZE_HOLD 3 + +/* Maximum amount of capsule memory moved in a single AVZ_STAGE_CHUNK call. + * + * A snapshot is streamed through a bounce buffer of that size instead of being + * mapped as a whole: the agency has no reason to find several hundreds of + * contiguous MB in its CMA zone just to save or restore a capsule. + */ +#define AVZ_STAGE_CHUNK_SIZE (4 * 1024 * 1024) + /* AVZ_INJECT_CAPSULE */ typedef struct { void *itb_paddr; @@ -381,10 +397,17 @@ typedef struct { /* AVZ_READ_SNAPSHOT */ /* AVZ_WRITE_SNAPSHOT */ +/* + * `snapshot_paddr` points to the agency bounce buffer and is read again at each + * stage. At the INIT and FINALIZE stages the buffer holds the snapshot header + * (payload size + domain context), which AVZ reads to restore the capsule; at + * the CHUNK stage it holds one chunk of capsule memory, at its very beginning, + * and `size` tells AVZ how much of it may be used. + */ typedef struct { void *snapshot_paddr; int32_t slotID; - int size; + int size; /* INIT: IN/OUT snapshot size / CHUNK: IN bounce buffer size */ uint32_t stage; /* IN: AVZ_STAGE_* */ uint32_t offset; /* CHUNK: IN/OUT byte cursor / INIT: OUT bytes to copy */ } avz_snapshot_t;