diff --git a/docs/system/device-emulation.rst b/docs/system/device-emulation.rst index db714ad47a..971325527a 100644 --- a/docs/system/device-emulation.rst +++ b/docs/system/device-emulation.rst @@ -88,6 +88,7 @@ Emulated Devices devices/canokey.rst devices/ccid.rst devices/cxl.rst + devices/emmc.rst devices/igb.rst devices/ivshmem-flat.rst devices/ivshmem.rst diff --git a/docs/system/devices/emmc.rst b/docs/system/devices/emmc.rst new file mode 100644 index 0000000000..e62adfd68e --- /dev/null +++ b/docs/system/devices/emmc.rst @@ -0,0 +1,55 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +============== +eMMC Emulation +============== + +Besides SD card emulation, QEMU also offers an eMMC model as found on many +embedded boards. An eMMC, just like an SD card, is connected to the machine +via an SDHCI controller. + +Create eMMC Images +================== + +A recent eMMC consists of 4 partitions: 2 boot partitions, 1 Replay protected +Memory Block (RPMB), and the user data area. QEMU expects backing images for +the eMMC to contain those partitions concatenated in exactly that order. +However, the boot partitions as well as the RPMB might be absent if their sizes +are configured to zero. + +The eMMC specification defines alignment constraints for the partitions. The +two boot partitions must be of the same size. Furthermore, boot and RPMB +partitions must be multiples of 128 KB with a maximum of 32640 KB for each +boot partition and 16384K for the RPMB partition. + +The alignment constrain of the user data area depends on its size. Up to 2 +GByte, the size must be a power of 2. From 2 GByte onward, the size has to be +multiples of 512 byte. + +QEMU is enforcing those alignment rules before instantiating the device. +Therefore, the provided image has to strictly follow them as well. The helper +script ``scripts/mkemmc.sh`` can be used to create compliant images, with or +without pre-filled partitions. E.g., to create an eMMC image from a firmware +image and an OS image with an empty 2 MByte RPMB, use the following command: + +.. code-block:: console + + scripts/mkemmc.sh -b firmware.img -r /dev/zero:2M os.img emmc.img + +This will take care of rounding up the partition sizes to the next valid value +and will leave the RPMB and the second boot partition empty (zeroed). + +Adding eMMC Devices +=================== + +An eMMC is either automatically created by a machine model (e.g. Aspeed boards) +or can be user-created when using a PCI-attached SDHCI controller. To +instantiate the eMMC image from the example above in a machine without other +SDHCI controllers while assuming that the firmware needs a boot partitions of +1 MB, use the following options: + +.. code-block:: console + + -drive file=emmc.img,if=none,format=raw,id=emmc-img + -device sdhci-pci + -device emmc,drive=emmc-img,boot-partition-size=1048576,rpmb-partition-size=2097152 diff --git a/hw/audio/ac97.c b/hw/audio/ac97.c index be7a39377f..60daa62ea3 100644 --- a/hw/audio/ac97.c +++ b/hw/audio/ac97.c @@ -1279,30 +1279,11 @@ static void ac97_realize(PCIDevice *dev, Error **errp) return; } - /* TODO: no need to override */ - c[PCI_COMMAND] = 0x00; /* pcicmd pci command rw, ro */ - c[PCI_COMMAND + 1] = 0x00; - - /* TODO: */ c[PCI_STATUS] = PCI_STATUS_FAST_BACK; /* pcists pci status rwc, ro */ c[PCI_STATUS + 1] = PCI_STATUS_DEVSEL_MEDIUM >> 8; c[PCI_CLASS_PROG] = 0x00; /* pi programming interface ro */ - /* TODO set when bar is registered. no need to override. */ - /* nabmar native audio mixer base address rw */ - c[PCI_BASE_ADDRESS_0] = PCI_BASE_ADDRESS_SPACE_IO; - c[PCI_BASE_ADDRESS_0 + 1] = 0x00; - c[PCI_BASE_ADDRESS_0 + 2] = 0x00; - c[PCI_BASE_ADDRESS_0 + 3] = 0x00; - - /* TODO set when bar is registered. no need to override. */ - /* nabmbar native audio bus mastering base address rw */ - c[PCI_BASE_ADDRESS_0 + 4] = PCI_BASE_ADDRESS_SPACE_IO; - c[PCI_BASE_ADDRESS_0 + 5] = 0x00; - c[PCI_BASE_ADDRESS_0 + 6] = 0x00; - c[PCI_BASE_ADDRESS_0 + 7] = 0x00; - c[PCI_INTERRUPT_LINE] = 0x00; /* intr_ln interrupt line rw */ c[PCI_INTERRUPT_PIN] = 0x01; /* intr_pn interrupt pin ro */ diff --git a/hw/i386/microvm-dt.c b/hw/i386/microvm-dt.c index cb27dfd732..81eaddafc8 100644 --- a/hw/i386/microvm-dt.c +++ b/hw/i386/microvm-dt.c @@ -48,6 +48,7 @@ static bool debug; static void dt_add_microvm_irq(MicrovmMachineState *mms, const char *nodename, uint32_t irq) { + MachineState *ms = MACHINE(mms); int index = 0; if (irq >= IO_APIC_SECONDARY_IRQBASE) { @@ -55,13 +56,14 @@ static void dt_add_microvm_irq(MicrovmMachineState *mms, index++; } - qemu_fdt_setprop_cell(mms->fdt, nodename, "interrupt-parent", + qemu_fdt_setprop_cell(ms->fdt, nodename, "interrupt-parent", mms->ioapic_phandle[index]); - qemu_fdt_setprop_cells(mms->fdt, nodename, "interrupts", irq, 0); + qemu_fdt_setprop_cells(ms->fdt, nodename, "interrupts", irq, 0); } static void dt_add_virtio(MicrovmMachineState *mms, VirtIOMMIOProxy *mmio) { + MachineState *ms = MACHINE(mms); SysBusDevice *dev = SYS_BUS_DEVICE(mmio); VirtioBusState *mmio_virtio_bus = &mmio->bus; BusState *mmio_bus = &mmio_virtio_bus->parent_obj; @@ -77,10 +79,10 @@ static void dt_add_virtio(MicrovmMachineState *mms, VirtIOMMIOProxy *mmio) uint32_t irq = mms->virtio_irq_base + index; nodename = g_strdup_printf("/virtio_mmio@%" PRIx64, base); - qemu_fdt_add_subnode(mms->fdt, nodename); - qemu_fdt_setprop_string(mms->fdt, nodename, "compatible", "virtio,mmio"); - qemu_fdt_setprop_sized_cells(mms->fdt, nodename, "reg", 2, base, 2, size); - qemu_fdt_setprop(mms->fdt, nodename, "dma-coherent", NULL, 0); + qemu_fdt_add_subnode(ms->fdt, nodename); + qemu_fdt_setprop_string(ms->fdt, nodename, "compatible", "virtio,mmio"); + qemu_fdt_setprop_sized_cells(ms->fdt, nodename, "reg", 2, base, 2, size); + qemu_fdt_setprop(ms->fdt, nodename, "dma-coherent", NULL, 0); dt_add_microvm_irq(mms, nodename, irq); g_free(nodename); } @@ -88,40 +90,42 @@ static void dt_add_virtio(MicrovmMachineState *mms, VirtIOMMIOProxy *mmio) static void dt_add_xhci(MicrovmMachineState *mms) { const char compat[] = "generic-xhci"; + MachineState *ms = MACHINE(mms); uint32_t irq = MICROVM_XHCI_IRQ; hwaddr base = MICROVM_XHCI_BASE; hwaddr size = XHCI_LEN_REGS; char *nodename; nodename = g_strdup_printf("/usb@%" PRIx64, base); - qemu_fdt_add_subnode(mms->fdt, nodename); - qemu_fdt_setprop(mms->fdt, nodename, "compatible", compat, sizeof(compat)); - qemu_fdt_setprop_sized_cells(mms->fdt, nodename, "reg", 2, base, 2, size); - qemu_fdt_setprop(mms->fdt, nodename, "dma-coherent", NULL, 0); + qemu_fdt_add_subnode(ms->fdt, nodename); + qemu_fdt_setprop(ms->fdt, nodename, "compatible", compat, sizeof(compat)); + qemu_fdt_setprop_sized_cells(ms->fdt, nodename, "reg", 2, base, 2, size); + qemu_fdt_setprop(ms->fdt, nodename, "dma-coherent", NULL, 0); dt_add_microvm_irq(mms, nodename, irq); g_free(nodename); } static void dt_add_pcie(MicrovmMachineState *mms) { + MachineState *ms = MACHINE(mms); hwaddr base = PCIE_MMIO_BASE; int nr_pcie_buses; char *nodename; nodename = g_strdup_printf("/pcie@%" PRIx64, base); - qemu_fdt_add_subnode(mms->fdt, nodename); - qemu_fdt_setprop_string(mms->fdt, nodename, + qemu_fdt_add_subnode(ms->fdt, nodename); + qemu_fdt_setprop_string(ms->fdt, nodename, "compatible", "pci-host-ecam-generic"); - qemu_fdt_setprop_string(mms->fdt, nodename, "device_type", "pci"); - qemu_fdt_setprop_cell(mms->fdt, nodename, "#address-cells", 3); - qemu_fdt_setprop_cell(mms->fdt, nodename, "#size-cells", 2); - qemu_fdt_setprop_cell(mms->fdt, nodename, "linux,pci-domain", 0); - qemu_fdt_setprop(mms->fdt, nodename, "dma-coherent", NULL, 0); + qemu_fdt_setprop_string(ms->fdt, nodename, "device_type", "pci"); + qemu_fdt_setprop_cell(ms->fdt, nodename, "#address-cells", 3); + qemu_fdt_setprop_cell(ms->fdt, nodename, "#size-cells", 2); + qemu_fdt_setprop_cell(ms->fdt, nodename, "linux,pci-domain", 0); + qemu_fdt_setprop(ms->fdt, nodename, "dma-coherent", NULL, 0); - qemu_fdt_setprop_sized_cells(mms->fdt, nodename, "reg", + qemu_fdt_setprop_sized_cells(ms->fdt, nodename, "reg", 2, PCIE_ECAM_BASE, 2, PCIE_ECAM_SIZE); if (mms->gpex.mmio64.size) { - qemu_fdt_setprop_sized_cells(mms->fdt, nodename, "ranges", + qemu_fdt_setprop_sized_cells(ms->fdt, nodename, "ranges", 1, FDT_PCI_RANGE_MMIO, 2, mms->gpex.mmio32.base, @@ -133,7 +137,7 @@ static void dt_add_pcie(MicrovmMachineState *mms) 2, mms->gpex.mmio64.base, 2, mms->gpex.mmio64.size); } else { - qemu_fdt_setprop_sized_cells(mms->fdt, nodename, "ranges", + qemu_fdt_setprop_sized_cells(ms->fdt, nodename, "ranges", 1, FDT_PCI_RANGE_MMIO, 2, mms->gpex.mmio32.base, @@ -142,7 +146,7 @@ static void dt_add_pcie(MicrovmMachineState *mms) } nr_pcie_buses = PCIE_ECAM_SIZE / PCIE_MMCFG_SIZE_MIN; - qemu_fdt_setprop_cells(mms->fdt, nodename, "bus-range", 0, + qemu_fdt_setprop_cells(ms->fdt, nodename, "bus-range", 0, nr_pcie_buses - 1); g_free(nodename); @@ -150,6 +154,7 @@ static void dt_add_pcie(MicrovmMachineState *mms) static void dt_add_ioapic(MicrovmMachineState *mms, SysBusDevice *dev) { + MachineState *ms = MACHINE(mms); hwaddr base = dev->mmio[0].addr; char *nodename; uint32_t ph; @@ -168,18 +173,18 @@ static void dt_add_ioapic(MicrovmMachineState *mms, SysBusDevice *dev) } nodename = g_strdup_printf("/ioapic%d@%" PRIx64, index + 1, base); - qemu_fdt_add_subnode(mms->fdt, nodename); - qemu_fdt_setprop_string(mms->fdt, nodename, + qemu_fdt_add_subnode(ms->fdt, nodename); + qemu_fdt_setprop_string(ms->fdt, nodename, "compatible", "intel,ce4100-ioapic"); - qemu_fdt_setprop(mms->fdt, nodename, "interrupt-controller", NULL, 0); - qemu_fdt_setprop_cell(mms->fdt, nodename, "#interrupt-cells", 0x2); - qemu_fdt_setprop_cell(mms->fdt, nodename, "#address-cells", 0x2); - qemu_fdt_setprop_sized_cells(mms->fdt, nodename, "reg", + qemu_fdt_setprop(ms->fdt, nodename, "interrupt-controller", NULL, 0); + qemu_fdt_setprop_cell(ms->fdt, nodename, "#interrupt-cells", 0x2); + qemu_fdt_setprop_cell(ms->fdt, nodename, "#address-cells", 0x2); + qemu_fdt_setprop_sized_cells(ms->fdt, nodename, "reg", 2, base, 2, 0x1000); - ph = qemu_fdt_alloc_phandle(mms->fdt); - qemu_fdt_setprop_cell(mms->fdt, nodename, "phandle", ph); - qemu_fdt_setprop_cell(mms->fdt, nodename, "linux,phandle", ph); + ph = qemu_fdt_alloc_phandle(ms->fdt); + qemu_fdt_setprop_cell(ms->fdt, nodename, "phandle", ph); + qemu_fdt_setprop_cell(ms->fdt, nodename, "linux,phandle", ph); mms->ioapic_phandle[index] = ph; g_free(nodename); @@ -190,17 +195,18 @@ static void dt_add_isa_serial(MicrovmMachineState *mms, ISADevice *dev) const char compat[] = "ns16550"; uint32_t irq = object_property_get_int(OBJECT(dev), "irq", &error_fatal); hwaddr base = object_property_get_int(OBJECT(dev), "iobase", &error_fatal); + MachineState *ms = MACHINE(mms); hwaddr size = 8; char *nodename; nodename = g_strdup_printf("/serial@%" PRIx64, base); - qemu_fdt_add_subnode(mms->fdt, nodename); - qemu_fdt_setprop(mms->fdt, nodename, "compatible", compat, sizeof(compat)); - qemu_fdt_setprop_sized_cells(mms->fdt, nodename, "reg", 2, base, 2, size); + qemu_fdt_add_subnode(ms->fdt, nodename); + qemu_fdt_setprop(ms->fdt, nodename, "compatible", compat, sizeof(compat)); + qemu_fdt_setprop_sized_cells(ms->fdt, nodename, "reg", 2, base, 2, size); dt_add_microvm_irq(mms, nodename, irq); if (base == 0x3f8 /* com1 */) { - qemu_fdt_setprop_string(mms->fdt, "/chosen", "stdout-path", nodename); + qemu_fdt_setprop_string(ms->fdt, "/chosen", "stdout-path", nodename); } g_free(nodename); @@ -211,13 +217,14 @@ static void dt_add_isa_rtc(MicrovmMachineState *mms, ISADevice *dev) const char compat[] = "motorola,mc146818"; uint32_t irq = object_property_get_uint(OBJECT(dev), "irq", &error_fatal); hwaddr base = object_property_get_uint(OBJECT(dev), "iobase", &error_fatal); + MachineState *ms = MACHINE(mms); hwaddr size = 8; char *nodename; nodename = g_strdup_printf("/rtc@%" PRIx64, base); - qemu_fdt_add_subnode(mms->fdt, nodename); - qemu_fdt_setprop(mms->fdt, nodename, "compatible", compat, sizeof(compat)); - qemu_fdt_setprop_sized_cells(mms->fdt, nodename, "reg", 2, base, 2, size); + qemu_fdt_add_subnode(ms->fdt, nodename); + qemu_fdt_setprop(ms->fdt, nodename, "compatible", compat, sizeof(compat)); + qemu_fdt_setprop_sized_cells(ms->fdt, nodename, "reg", 2, base, 2, size); dt_add_microvm_irq(mms, nodename, irq); g_free(nodename); } @@ -317,27 +324,28 @@ static void dt_setup_sys_bus(MicrovmMachineState *mms) void dt_setup_microvm(MicrovmMachineState *mms) { X86MachineState *x86ms = X86_MACHINE(mms); + MachineState *ms = MACHINE(mms); int size = 0; - mms->fdt = create_device_tree(&size); + ms->fdt = create_device_tree(&size); /* root node */ - qemu_fdt_setprop_string(mms->fdt, "/", "compatible", "linux,microvm"); - qemu_fdt_setprop_cell(mms->fdt, "/", "#address-cells", 0x2); - qemu_fdt_setprop_cell(mms->fdt, "/", "#size-cells", 0x2); + qemu_fdt_setprop_string(ms->fdt, "/", "compatible", "linux,microvm"); + qemu_fdt_setprop_cell(ms->fdt, "/", "#address-cells", 0x2); + qemu_fdt_setprop_cell(ms->fdt, "/", "#size-cells", 0x2); - qemu_fdt_add_subnode(mms->fdt, "/chosen"); + qemu_fdt_add_subnode(ms->fdt, "/chosen"); dt_setup_sys_bus(mms); /* add to fw_cfg */ if (debug) { fprintf(stderr, "%s: add etc/fdt to fw_cfg\n", __func__); } - fw_cfg_add_file(x86ms->fw_cfg, "etc/fdt", mms->fdt, size); + fw_cfg_add_file(x86ms->fw_cfg, "etc/fdt", ms->fdt, size); if (debug) { fprintf(stderr, "%s: writing microvm.fdt\n", __func__); - if (!g_file_set_contents("microvm.fdt", mms->fdt, size, NULL)) { + if (!g_file_set_contents("microvm.fdt", ms->fdt, size, NULL)) { fprintf(stderr, "%s: writing microvm.fdt failed\n", __func__); return; } diff --git a/hw/sd/sd.c b/hw/sd/sd.c index d7a496d77c..9c86c016cc 100644 --- a/hw/sd/sd.c +++ b/hw/sd/sd.c @@ -51,6 +51,7 @@ #include "qemu/module.h" #include "sdmmc-internal.h" #include "trace.h" +#include "crypto/hmac.h" //#define DEBUG_SD 1 @@ -117,6 +118,27 @@ typedef struct SDProto { } cmd[SDMMC_CMD_MAX], acmd[SDMMC_CMD_MAX]; } SDProto; +#define RPMB_STUFF_LEN 196 +#define RPMB_KEY_MAC_LEN 32 +#define RPMB_DATA_LEN 256 /* one RPMB block is half a sector */ +#define RPMB_NONCE_LEN 16 +#define RPMB_HASH_LEN 284 + +typedef struct QEMU_PACKED { + uint8_t stuff_bytes[RPMB_STUFF_LEN]; + uint8_t key_mac[RPMB_KEY_MAC_LEN]; + uint8_t data[RPMB_DATA_LEN]; + uint8_t nonce[RPMB_NONCE_LEN]; + uint32_t write_counter; + uint16_t address; + uint16_t block_count; + uint16_t result; + uint16_t req_resp; +} RPMBDataFrame; + +QEMU_BUILD_BUG_MSG(sizeof(RPMBDataFrame) != 512, + "invalid RPMBDataFrame size"); + struct SDState { DeviceState parent_obj; @@ -140,6 +162,7 @@ struct SDState { uint8_t spec_version; uint64_t boot_part_size; + uint64_t rpmb_part_size; BlockBackend *blk; uint8_t boot_config; @@ -172,6 +195,12 @@ struct SDState { uint32_t data_offset; size_t data_size; uint8_t data[512]; + struct { + uint32_t write_counter; + uint8_t key[RPMB_KEY_MAC_LEN]; + uint8_t key_set; + RPMBDataFrame result; + } rpmb; QEMUTimer *ocr_power_timer; uint8_t dat_lines; bool cmd_line; @@ -506,7 +535,9 @@ static void emmc_set_ext_csd(SDState *sd, uint64_t size) sd->ext_csd[205] = 0x46; /* Min read perf for 4bit@26Mhz */ sd->ext_csd[EXT_CSD_CARD_TYPE] = 0b11; sd->ext_csd[EXT_CSD_STRUCTURE] = 2; - sd->ext_csd[EXT_CSD_REV] = 3; + sd->ext_csd[EXT_CSD_REV] = 5; + sd->ext_csd[EXT_CSD_RPMB_MULT] = sd->rpmb_part_size / (128 * KiB); + sd->ext_csd[EXT_CSD_PARTITION_SUPPORT] = 0b111; /* Mode segment (RW) */ sd->ext_csd[EXT_CSD_PART_CONFIG] = sd->boot_config; @@ -834,7 +865,8 @@ static uint32_t sd_blk_len(SDState *sd) /* * This requires a disk image that has two boot partitions inserted at the * beginning of it, followed by an RPMB partition. The size of the boot - * partitions is the "boot-partition-size" property. + * partitions is the "boot-partition-size" property, the one of the RPMB + * partition is 'rpmb-partition-size'. */ static uint32_t sd_part_offset(SDState *sd) { @@ -848,11 +880,13 @@ static uint32_t sd_part_offset(SDState *sd) & EXT_CSD_PART_CONFIG_ACC_MASK; switch (partition_access) { case EXT_CSD_PART_CONFIG_ACC_DEFAULT: - return sd->boot_part_size * 2; + return sd->boot_part_size * 2 + sd->rpmb_part_size; case EXT_CSD_PART_CONFIG_ACC_BOOT1: return 0; case EXT_CSD_PART_CONFIG_ACC_BOOT2: return sd->boot_part_size * 1; + case EXT_CSD_PART_CONFIG_ACC_RPMB: + return sd->boot_part_size * 2; default: g_assert_not_reached(); } @@ -891,7 +925,7 @@ static void sd_reset(DeviceState *dev) } size = sect << HWBLOCK_SHIFT; if (sd_is_emmc(sd)) { - size -= sd->boot_part_size * 2; + size -= sd->boot_part_size * 2 + sd->rpmb_part_size; } sect = sd_addr_to_wpnum(size) + 1; @@ -979,6 +1013,34 @@ static const VMStateDescription sd_ocr_vmstate = { }, }; +static bool vmstate_needed_for_rpmb(void *opaque) +{ + SDState *sd = opaque; + + return sd->rpmb_part_size > 0; +} + +static const VMStateDescription emmc_rpmb_vmstate = { + .name = "sd-card/ext_csd_modes-state", + .version_id = 1, + .minimum_version_id = 1, + .needed = vmstate_needed_for_rpmb, + .fields = (const VMStateField[]) { + VMSTATE_UINT8_ARRAY(rpmb.result.key_mac, SDState, RPMB_KEY_MAC_LEN), + VMSTATE_UINT8_ARRAY(rpmb.result.data, SDState, RPMB_DATA_LEN), + VMSTATE_UINT8_ARRAY(rpmb.result.nonce, SDState, RPMB_NONCE_LEN), + VMSTATE_UINT32(rpmb.result.write_counter, SDState), + VMSTATE_UINT16(rpmb.result.address, SDState), + VMSTATE_UINT16(rpmb.result.block_count, SDState), + VMSTATE_UINT16(rpmb.result.result, SDState), + VMSTATE_UINT16(rpmb.result.req_resp, SDState), + VMSTATE_UINT32(rpmb.write_counter, SDState), + VMSTATE_UINT8_ARRAY(rpmb.key, SDState, 32), + VMSTATE_UINT8(rpmb.key_set, SDState), + VMSTATE_END_OF_LIST() + }, +}; + static bool vmstate_needed_for_emmc(void *opaque) { SDState *sd = opaque; @@ -1045,6 +1107,7 @@ static const VMStateDescription sd_vmstate = { .subsections = (const VMStateDescription * const []) { &sd_ocr_vmstate, &emmc_extcsd_vmstate, + &emmc_rpmb_vmstate, NULL }, }; @@ -1067,6 +1130,186 @@ static void sd_blk_write(SDState *sd, uint64_t addr, uint32_t len) } } +static bool rpmb_calc_hmac(SDState *sd, const RPMBDataFrame *frame, + unsigned int num_blocks, uint8_t *mac) +{ + g_autoptr(QCryptoHmac) hmac = NULL; + size_t mac_len = RPMB_KEY_MAC_LEN; + bool success = true; + Error *err = NULL; + uint64_t offset; + + hmac = qcrypto_hmac_new(QCRYPTO_HASH_ALGO_SHA256, sd->rpmb.key, + RPMB_KEY_MAC_LEN, &err); + if (!hmac) { + error_report_err(err); + return false; + } + + /* + * This implies a read request because we only support single-block write + * requests so far. + */ + if (num_blocks > 1) { + /* + * Unfortunately, the underlying crypto libraries do not allow us to + * migrate an active QCryptoHmac state. Therefore, we have to calculate + * the HMAC in one run. To avoid buffering a complete read sequence in + * SDState, reconstruct all frames except for the last one. + */ + void *buf = sd->data; + + assert(RPMB_HASH_LEN <= sizeof(sd->data)); + + memcpy((uint8_t *)buf + RPMB_DATA_LEN, &frame->data[RPMB_DATA_LEN], + RPMB_HASH_LEN - RPMB_DATA_LEN); + offset = lduw_be_p(&frame->address) * RPMB_DATA_LEN + sd_part_offset(sd); + do { + if (blk_pread(sd->blk, offset, RPMB_DATA_LEN, buf, 0) < 0) { + error_report("sd_blk_read: read error on host side"); + success = false; + break; + } + if (qcrypto_hmac_bytes(hmac, buf, RPMB_HASH_LEN, NULL, NULL, + &err) < 0) { + error_report_err(err); + success = false; + break; + } + offset += RPMB_DATA_LEN; + } while (--num_blocks > 1); + } + + if (success && + qcrypto_hmac_bytes(hmac, frame->data, RPMB_HASH_LEN, &mac, + &mac_len, &err) < 0) { + error_report_err(err); + success = false; + } + assert(!success || mac_len == RPMB_KEY_MAC_LEN); + + return success; +} + +static void emmc_rpmb_blk_read(SDState *sd, uint64_t addr, uint32_t len) +{ + uint16_t resp = lduw_be_p(&sd->rpmb.result.req_resp); + uint16_t result = lduw_be_p(&sd->rpmb.result.result); + unsigned int curr_block = 0; + + if ((result & ~RPMB_RESULT_COUTER_EXPIRED) == RPMB_RESULT_OK && + resp == RPMB_RESP(RPMB_REQ_AUTH_DATA_READ)) { + curr_block = lduw_be_p(&sd->rpmb.result.address); + if (sd->rpmb.result.block_count == 0) { + stw_be_p(&sd->rpmb.result.block_count, sd->multi_blk_cnt); + } else { + curr_block += lduw_be_p(&sd->rpmb.result.block_count); + curr_block -= sd->multi_blk_cnt; + } + addr = curr_block * RPMB_DATA_LEN + sd_part_offset(sd); + if (blk_pread(sd->blk, addr, RPMB_DATA_LEN, + sd->rpmb.result.data, 0) < 0) { + error_report("sd_blk_read: read error on host side"); + memset(sd->rpmb.result.data, 0, sizeof(sd->rpmb.result.data)); + stw_be_p(&sd->rpmb.result.result, + RPMB_RESULT_READ_FAILURE + | (result & RPMB_RESULT_COUTER_EXPIRED)); + } + if (sd->multi_blk_cnt == 1 && + !rpmb_calc_hmac(sd, &sd->rpmb.result, + lduw_be_p(&sd->rpmb.result.block_count), + sd->rpmb.result.key_mac)) { + memset(sd->rpmb.result.data, 0, sizeof(sd->rpmb.result.data)); + stw_be_p(&sd->rpmb.result.result, RPMB_RESULT_AUTH_FAILURE); + } + } else if (!rpmb_calc_hmac(sd, &sd->rpmb.result, 1, + sd->rpmb.result.key_mac)) { + memset(sd->rpmb.result.data, 0, sizeof(sd->rpmb.result.data)); + stw_be_p(&sd->rpmb.result.result, RPMB_RESULT_AUTH_FAILURE); + } + memcpy(sd->data, &sd->rpmb.result, sizeof(sd->rpmb.result)); + + trace_sdcard_rpmb_read_block(resp, curr_block, + lduw_be_p(&sd->rpmb.result.result)); +} + +static void emmc_rpmb_blk_write(SDState *sd, uint64_t addr, uint32_t len) +{ + RPMBDataFrame *frame = (RPMBDataFrame *)sd->data; + uint16_t req = lduw_be_p(&frame->req_resp); + uint8_t mac[RPMB_KEY_MAC_LEN]; + + if (req == RPMB_REQ_READ_RESULT) { + /* just return the current result register */ + goto exit; + } + memset(&sd->rpmb.result, 0, sizeof(sd->rpmb.result)); + memcpy(sd->rpmb.result.nonce, frame->nonce, sizeof(sd->rpmb.result.nonce)); + stw_be_p(&sd->rpmb.result.result, RPMB_RESULT_OK); + stw_be_p(&sd->rpmb.result.req_resp, RPMB_RESP(req)); + + if (!sd->rpmb.key_set && req != RPMB_REQ_PROGRAM_AUTH_KEY) { + stw_be_p(&sd->rpmb.result.result, RPMB_RESULT_NO_AUTH_KEY); + goto exit; + } + + switch (req) { + case RPMB_REQ_PROGRAM_AUTH_KEY: + if (sd->rpmb.key_set) { + stw_be_p(&sd->rpmb.result.result, RPMB_RESULT_WRITE_FAILURE); + break; + } + memcpy(sd->rpmb.key, frame->key_mac, sizeof(sd->rpmb.key)); + sd->rpmb.key_set = 1; + break; + case RPMB_REQ_READ_WRITE_COUNTER: + stl_be_p(&sd->rpmb.result.write_counter, sd->rpmb.write_counter); + break; + case RPMB_REQ_AUTH_DATA_WRITE: + /* We only support single-block writes so far */ + if (sd->multi_blk_cnt != 1) { + stw_be_p(&sd->rpmb.result.result, RPMB_RESULT_GENERAL_FAILURE); + break; + } + if (sd->rpmb.write_counter == 0xffffffff) { + stw_be_p(&sd->rpmb.result.result, RPMB_RESULT_WRITE_FAILURE); + break; + } + if (!rpmb_calc_hmac(sd, frame, 1, mac) || + memcmp(frame->key_mac, mac, RPMB_KEY_MAC_LEN) != 0) { + stw_be_p(&sd->rpmb.result.result, RPMB_RESULT_AUTH_FAILURE); + break; + } + if (ldl_be_p(&frame->write_counter) != sd->rpmb.write_counter) { + stw_be_p(&sd->rpmb.result.result, RPMB_RESULT_COUNTER_FAILURE); + break; + } + sd->rpmb.result.address = frame->address; + addr = lduw_be_p(&frame->address) * RPMB_DATA_LEN + sd_part_offset(sd); + if (blk_pwrite(sd->blk, addr, RPMB_DATA_LEN, frame->data, 0) < 0) { + error_report("sd_blk_write: write error on host side"); + stw_be_p(&sd->rpmb.result.result, RPMB_RESULT_WRITE_FAILURE); + } else { + sd->rpmb.write_counter++; + } + stl_be_p(&sd->rpmb.result.write_counter, sd->rpmb.write_counter); + break; + case RPMB_REQ_AUTH_DATA_READ: + sd->rpmb.result.address = frame->address; + break; + default: + qemu_log_mask(LOG_UNIMP, "RPMB request %d not implemented\n", req); + stw_be_p(&sd->rpmb.result.result, RPMB_RESULT_GENERAL_FAILURE); + break; + } +exit: + if (sd->rpmb.write_counter == 0xffffffff) { + stw_be_p(&sd->rpmb.result.result, + lduw_be_p(&sd->rpmb.result.result) | RPMB_RESULT_COUTER_EXPIRED); + } + trace_sdcard_rpmb_write_block(req, lduw_be_p(&sd->rpmb.result.result)); +} + static void sd_erase(SDState *sd) { uint64_t erase_start = sd->erase_start; @@ -1180,6 +1423,19 @@ static void emmc_function_switch(SDState *sd, uint32_t arg) break; } + if (index == EXT_CSD_PART_CONFIG) { + uint8_t part = b & EXT_CSD_PART_CONFIG_ACC_MASK; + + if (((part == EXT_CSD_PART_CONFIG_ACC_BOOT1 || + part == EXT_CSD_PART_CONFIG_ACC_BOOT2) && !sd->boot_part_size) || + (part == EXT_CSD_PART_CONFIG_ACC_RPMB && !sd->rpmb_part_size)) { + qemu_log_mask(LOG_GUEST_ERROR, + "MMC switching to illegal partition\n"); + sd->card_status |= R_CSR_SWITCH_ERROR_MASK; + return; + } + } + trace_sdcard_ext_csd_update(index, sd->ext_csd[index], b); sd->ext_csd[index] = b; } @@ -2378,6 +2634,7 @@ static bool sd_generic_read_byte(SDState *sd, uint8_t *value) static void sd_write_byte(SDState *sd, uint8_t value) { + unsigned int partition_access; int i; if (!sd->blk || !blk_is_inserted(sd->blk)) { @@ -2427,7 +2684,13 @@ static void sd_write_byte(SDState *sd, uint8_t value) if (sd->data_offset >= sd->blk_len) { /* TODO: Check CRC before committing */ sd->state = sd_programming_state; - sd_blk_write(sd, sd->data_start, sd->data_offset); + partition_access = sd->ext_csd[EXT_CSD_PART_CONFIG] + & EXT_CSD_PART_CONFIG_ACC_MASK; + if (partition_access == EXT_CSD_PART_CONFIG_ACC_RPMB) { + emmc_rpmb_blk_write(sd, sd->data_start, sd->data_offset); + } else { + sd_blk_write(sd, sd->data_start, sd->data_offset); + } sd->blk_written++; sd->data_start += sd->blk_len; sd->data_offset = 0; @@ -2510,6 +2773,7 @@ static uint8_t sd_read_byte(SDState *sd) { /* TODO: Append CRCs */ const uint8_t dummy_byte = 0x00; + unsigned int partition_access; uint8_t ret; uint32_t io_len; @@ -2553,7 +2817,13 @@ static uint8_t sd_read_byte(SDState *sd) sd->data_start, io_len)) { return dummy_byte; } - sd_blk_read(sd, sd->data_start, io_len); + partition_access = sd->ext_csd[EXT_CSD_PART_CONFIG] + & EXT_CSD_PART_CONFIG_ACC_MASK; + if (partition_access == EXT_CSD_PART_CONFIG_ACC_RPMB) { + emmc_rpmb_blk_read(sd, sd->data_start, io_len); + } else { + sd_blk_read(sd, sd->data_start, io_len); + } } ret = sd->data[sd->data_offset ++]; @@ -2759,9 +3029,32 @@ static void sd_instance_finalize(Object *obj) timer_free(sd->ocr_power_timer); } +static void sd_blk_size_error(SDState *sd, int64_t blk_size, + int64_t blk_size_aligned, const char *rule, + Error **errp) +{ + const char *dev_type = sd_is_emmc(sd) ? "eMMC" : "SD card"; + char *blk_size_str; + + blk_size_str = size_to_str(blk_size); + error_setg(errp, "Invalid %s size: %s", dev_type, blk_size_str); + g_free(blk_size_str); + + blk_size_str = size_to_str(blk_size_aligned); + error_append_hint(errp, + "%s size has to be %s, e.g. %s.\n" + "You can resize disk images with" + " 'qemu-img resize '\n" + "(note that this will lose data if you make the" + " image smaller than it currently is).\n", + dev_type, rule, blk_size_str); + g_free(blk_size_str); +} + static void sd_realize(DeviceState *dev, Error **errp) { SDState *sd = SDMMC_COMMON(dev); + int64_t blk_size = -ENOMEDIUM; int ret; switch (sd->spec_version) { @@ -2774,32 +3067,36 @@ static void sd_realize(DeviceState *dev, Error **errp) } if (sd->blk) { - int64_t blk_size; - if (!blk_supports_write_perm(sd->blk)) { error_setg(errp, "Cannot use read-only drive as SD card"); return; } blk_size = blk_getlength(sd->blk); - if (blk_size > 0 && !is_power_of_2(blk_size)) { - int64_t blk_size_aligned = pow2ceil(blk_size); - char *blk_size_str; - - blk_size_str = size_to_str(blk_size); - error_setg(errp, "Invalid SD card size: %s", blk_size_str); - g_free(blk_size_str); - - blk_size_str = size_to_str(blk_size_aligned); - error_append_hint(errp, - "SD card size has to be a power of 2, e.g. %s.\n" - "You can resize disk images with" - " 'qemu-img resize '\n" - "(note that this will lose data if you make the" - " image smaller than it currently is).\n", - blk_size_str); - g_free(blk_size_str); - + } + if (blk_size >= 0) { + blk_size -= sd->boot_part_size * 2 + sd->rpmb_part_size; + if (blk_size > SDSC_MAX_CAPACITY) { + if (sd_is_emmc(sd) && + !QEMU_IS_ALIGNED(blk_size, 1 << HWBLOCK_SHIFT)) { + int64_t blk_size_aligned = + ((blk_size >> HWBLOCK_SHIFT) + 1) << HWBLOCK_SHIFT; + sd_blk_size_error(sd, blk_size, blk_size_aligned, + "multiples of 512", errp); + return; + } else if (!sd_is_emmc(sd) && + !QEMU_IS_ALIGNED(blk_size, 512 * KiB)) { + int64_t blk_size_aligned = ((blk_size >> 19) + 1) << 19; + sd_blk_size_error(sd, blk_size, blk_size_aligned, + "multiples of 512K", errp); + return; + } + } else if (blk_size > 0 && !is_power_of_2(blk_size)) { + sd_blk_size_error(sd, blk_size, pow2ceil(blk_size), "a power of 2", + errp); + return; + } else if (blk_size < 0) { + error_setg(errp, "eMMC image smaller than boot partitions"); return; } @@ -2810,7 +3107,7 @@ static void sd_realize(DeviceState *dev, Error **errp) } blk_set_dev_ops(sd->blk, &sd_block_ops, sd); } - if (sd->boot_part_size % (128 * KiB) || + if (!QEMU_IS_ALIGNED(sd->boot_part_size, 128 * KiB) || sd->boot_part_size > 255 * 128 * KiB) { g_autofree char *size_str = size_to_str(sd->boot_part_size); @@ -2819,13 +3116,23 @@ static void sd_realize(DeviceState *dev, Error **errp) "The boot partition size must be multiples of 128K" "and not larger than 32640K.\n"); } + if (!QEMU_IS_ALIGNED(sd->rpmb_part_size, 128 * KiB) || + sd->rpmb_part_size > 128 * 128 * KiB) { + char *size_str = size_to_str(sd->boot_part_size); + + error_setg(errp, "Invalid RPMB partition size: %s", size_str); + g_free(size_str); + error_append_hint(errp, + "The RPMB partition size must be multiples of 128K" + "and not larger than 16384K.\n"); + } } static void emmc_realize(DeviceState *dev, Error **errp) { SDState *sd = SDMMC_COMMON(dev); - sd->spec_version = SD_PHY_SPECv3_01_VERS; /* Actually v4.3 */ + sd->spec_version = SD_PHY_SPECv3_01_VERS; /* Actually v4.5 */ sd_realize(dev, errp); } @@ -2842,6 +3149,7 @@ static const Property sd_properties[] = { static const Property emmc_properties[] = { DEFINE_PROP_UINT64("boot-partition-size", SDState, boot_part_size, 0), DEFINE_PROP_UINT8("boot-config", SDState, boot_config, 0x0), + DEFINE_PROP_UINT64("rpmb-partition-size", SDState, rpmb_part_size, 0), }; static void sdmmc_common_class_init(ObjectClass *klass, const void *data) @@ -2900,11 +3208,11 @@ static void emmc_class_init(ObjectClass *klass, const void *data) DeviceClass *dc = DEVICE_CLASS(klass); SDCardClass *sc = SDMMC_COMMON_CLASS(klass); + assert(qcrypto_hmac_supports(QCRYPTO_HASH_ALGO_SHA256)); + dc->desc = "eMMC"; dc->realize = emmc_realize; device_class_set_props(dc, emmc_properties); - /* Reason: Soldered on board */ - dc->user_creatable = false; sc->proto = &sd_proto_emmc; diff --git a/hw/sd/sdmmc-internal.h b/hw/sd/sdmmc-internal.h index ce6bc4e6ec..c115f472ef 100644 --- a/hw/sd/sdmmc-internal.h +++ b/hw/sd/sdmmc-internal.h @@ -118,9 +118,31 @@ DECLARE_OBJ_CHECKERS(SDState, SDCardClass, SDMMC_COMMON, TYPE_SDMMC_COMMON) #define EXT_CSD_PART_CONFIG_ACC_DEFAULT (0x0) #define EXT_CSD_PART_CONFIG_ACC_BOOT1 (0x1) #define EXT_CSD_PART_CONFIG_ACC_BOOT2 (0x2) +#define EXT_CSD_PART_CONFIG_ACC_RPMB (0x3) #define EXT_CSD_PART_CONFIG_EN_MASK (0x7 << 3) #define EXT_CSD_PART_CONFIG_EN_BOOT0 (0x1 << 3) #define EXT_CSD_PART_CONFIG_EN_USER (0x7 << 3) +#define RPMB_REQ_PROGRAM_AUTH_KEY (1) +#define RPMB_REQ_READ_WRITE_COUNTER (2) +#define RPMB_REQ_AUTH_DATA_WRITE (3) +#define RPMB_REQ_AUTH_DATA_READ (4) +#define RPMB_REQ_READ_RESULT (5) +#define RPMB_REQ_AUTH_CONFIG_WRITE (6) +#define RPMB_REQ_AUTH_CONFIG_READ (7) + +#define RPMB_RESP(__req__) ((__req__) << 8) + +#define RPMB_RESULT_OK (0) +#define RPMB_RESULT_GENERAL_FAILURE (1) +#define RPMB_RESULT_AUTH_FAILURE (2) +#define RPMB_RESULT_COUNTER_FAILURE (3) +#define RPMB_RESULT_ADDRESS_FAILURE (4) +#define RPMB_RESULT_WRITE_FAILURE (5) +#define RPMB_RESULT_READ_FAILURE (6) +#define RPMB_RESULT_NO_AUTH_KEY (7) + +#define RPMB_RESULT_COUTER_EXPIRED (0x80) + #endif diff --git a/hw/sd/trace-events b/hw/sd/trace-events index 8d49840917..d30daa2143 100644 --- a/hw/sd/trace-events +++ b/hw/sd/trace-events @@ -59,6 +59,8 @@ sdcard_read_data(const char *proto, const char *cmd_desc, uint8_t cmd, uint32_t sdcard_set_voltage(uint16_t millivolts) "%u mV" sdcard_ext_csd_update(unsigned index, uint8_t oval, uint8_t nval) "index %u: 0x%02x -> 0x%02x" sdcard_switch(unsigned access, unsigned index, unsigned value, unsigned set) "SWITCH acc:%u idx:%u val:%u set:%u" +sdcard_rpmb_read_block(uint16_t resp, uint16_t read_addr, uint16_t result) "resp 0x%x read_addr 0x%x result 0x%x" +sdcard_rpmb_write_block(uint16_t req, uint16_t result) "req 0x%x result 0x%x" # pl181.c pl181_command_send(uint8_t cmd, uint32_t arg) "sending CMD%02d arg 0x%08" PRIx32 diff --git a/include/hw/i386/microvm.h b/include/hw/i386/microvm.h index b9ac34a3ef..e73d1e65de 100644 --- a/include/hw/i386/microvm.h +++ b/include/hw/i386/microvm.h @@ -102,8 +102,6 @@ struct MicrovmMachineState { Notifier powerdown_req; struct GPEXConfig gpex; - /* device tree */ - void *fdt; uint32_t ioapic_phandle[2]; }; diff --git a/scripts/mkemmc.sh b/scripts/mkemmc.sh new file mode 100755 index 0000000000..429388213c --- /dev/null +++ b/scripts/mkemmc.sh @@ -0,0 +1,216 @@ +#!/bin/sh -e +# SPDX-License-Identifier: GPL-2.0-only +# +# Create eMMC block device image from boot, RPMB and user data images +# +# Copyright (c) Siemens, 2025 +# +# Authors: +# Jan Kiszka +# + +usage() { + echo "$0 [OPTIONS] USER_IMG[:SIZE] OUTPUT_IMG" + echo "" + echo "SIZE must be a power of 2 up to 2G and multiples of 512 byte from there on." + echo "If no SIZE is specified, the size of USER_ING will be used (rounded up)." + echo "" + echo "Supported options:" + echo " -b BOOT1_IMG[:SIZE] Add boot partitions. SIZE must be multiples of 128K. If" + echo " no SIZE is specified, the size of BOOT1_IMG will be" + echo " used (rounded up). BOOT1_IMG will be stored in boot" + echo " partition 1, and a boot partition 2 of the same size" + echo " will be created as empty (all zeros) unless -B is" + echo " specified as well." + echo " -B BOOT2_IMG Fill boot partition 2 with BOOT2_IMG. Must be combined" + echo " with -b which is also defining the partition size." + echo " -r RPMB_IMG[:SIZE] Add RPMB partition. SIZE must be multiples of 128K. If" + echo " no SIZE is specified, the size of RPMB_IMG will be" + echo " used (rounded up)." + echo " -h, --help This help" + echo "" + echo "All SIZE parameters support the units K, M, G. If SIZE is smaller than the" + echo "associated image, it will be truncated in the output image." + exit "$1" +} + +process_size() { + name=$1 + image_file=$2 + alignment=$3 + image_arg=$4 + if [ "${image_arg#*:}" = "$image_arg" ]; then + if ! size=$(wc -c < "$image_file" 2>/dev/null); then + echo "Missing $name image '$image_file'." >&2 + exit 1 + fi + if [ "$alignment" = 128 ]; then + size=$(( (size + 128 * 1024 - 1) & ~(128 * 1024 - 1) )) + elif [ $size -gt $((2 * 1024 * 1024 * 1024)) ]; then + size=$(( (size + 511) & ~511 )) + elif [ $(( size & (size - 1) )) -gt 0 ]; then + n=0 + while [ "$size" -gt 0 ]; do + size=$((size >> 1)) + n=$((n + 1)) + done + size=$((1 << n)) + fi + else + value="${image_arg#*:}" + if [ "${value%K}" != "$value" ]; then + size=${value%K} + multiplier=1024 + elif [ "${value%M}" != "$value" ]; then + size=${value%M} + multiplier=$((1024 * 1024)) + elif [ "${value%G}" != "$value" ]; then + size=${value%G} + multiplier=$((1024 * 1024 * 1024)) + else + size=$value + multiplier=1 + fi + # check if "$size" is a valid integer by doing a self-comparison + if [ "$size" -eq "$size" ] 2>/dev/null; then + size=$((size * multiplier)) + else + echo "Invalid value '$value' specified for $image_file image size." >&2 + exit 1 + fi + if [ "$alignment" = 128 ]; then + if [ $(( size & (128 * 1024 - 1) )) -ne 0 ]; then + echo "The $name image size must be multiples of 128K." >&2 + exit 1 + fi + elif [ $size -gt $((2 * 1024 * 1024 * 1024)) ]; then + if [ $(( size & 511)) -ne 0 ]; then + echo "The $name image size must be multiples of 512 (if >2G)." >&2 + exit 1 + fi + elif [ $(( size & (size - 1) )) -gt 0 ]; then + echo "The $name image size must be power of 2 (up to 2G)." >&2 + exit 1 + fi + fi + echo $size +} + +check_truncation() { + image_file=$1 + output_size=$2 + if [ "$image_file" = "/dev/zero" ]; then + return + fi + if ! actual_size=$(wc -c < "$image_file" 2>/dev/null); then + echo "Missing image '$image_file'." >&2 + exit 1 + fi + if [ "$actual_size" -gt "$output_size" ]; then + echo "Warning: image '$image_file' will be truncated on output." + fi +} + +userimg= +outimg= +bootimg1= +bootimg2=/dev/zero +bootsz=0 +rpmbimg= +rpmbsz=0 + +while [ $# -gt 0 ]; do + case "$1" in + -b) + shift + [ $# -ge 1 ] || usage 1 + bootimg1=${1%%:*} + bootsz=$(process_size boot "$bootimg1" 128 "$1") + shift + ;; + -B) + shift + [ $# -ge 1 ] || usage 1 + bootimg2=$1 + shift + ;; + -r) + shift + [ $# -ge 1 ] || usage 1 + rpmbimg=${1%%:*} + rpmbsz=$(process_size RPMB "$rpmbimg" 128 "$1") + shift + ;; + -h|--help) + usage 0 + ;; + *) + if [ -z "$userimg" ]; then + userimg=${1%%:*} + usersz=$(process_size user "$userimg" U "$1") + elif [ -z "$outimg" ]; then + outimg=$1 + else + usage 1 + fi + shift + ;; + esac +done + +[ -n "$outimg" ] || usage 1 + +if [ "$bootsz" -gt $((32640 * 1024)) ]; then + echo "Boot image size is larger than 32640K." >&2 + exit 1 +fi +if [ "$rpmbsz" -gt $((16384 * 1024)) ]; then + echo "RPMB image size is larger than 16384K." >&2 + exit 1 +fi + +echo "Creating eMMC image" + +truncate -s 0 "$outimg" +pos=0 + +if [ "$bootsz" -gt 0 ]; then + echo " Boot partition 1 and 2: $((bootsz / 1024))K each" + blocks=$(( bootsz / (128 * 1024) )) + check_truncation "$bootimg1" "$bootsz" + dd if="$bootimg1" of="$outimg" conv=sparse bs=128K count=$blocks \ + status=none + check_truncation "$bootimg2" "$bootsz" + dd if="$bootimg2" of="$outimg" conv=sparse bs=128K count=$blocks \ + seek=$blocks status=none + pos=$((2 * bootsz)) +fi + +if [ "$rpmbsz" -gt 0 ]; then + echo " RPMB partition: $((rpmbsz / 1024))K" + blocks=$(( rpmbsz / (128 * 1024) )) + check_truncation "$rpmbimg" "$rpmbsz" + dd if="$rpmbimg" of="$outimg" conv=sparse bs=128K count=$blocks \ + seek=$(( pos / (128 * 1024) )) status=none + pos=$((pos + rpmbsz)) +fi + +if [ "$usersz" -lt 1024 ]; then + echo " User data: $usersz bytes" +elif [ "$usersz" -lt $((1024 * 1024)) ]; then + echo " User data: $(( (usersz + 1023) / 1024 ))K ($usersz)" +elif [ "$usersz" -lt $((1024 * 1024 * 1024)) ]; then + echo " User data: $(( (usersz + 1048575) / 1048576))M ($usersz)" +else + echo " User data: $(( (usersz + 1073741823) / 1073741824))G ($usersz)" +fi +check_truncation "$userimg" "$usersz" +dd if="$userimg" of="$outimg" conv=sparse bs=128K seek=$(( pos / (128 * 1024) )) \ + count=$(( (usersz + 128 * 1024 - 1) / (128 * 1024) )) status=none +pos=$((pos + usersz)) +truncate -s $pos "$outimg" + +echo "" +echo "Instantiate by appending to the qemu command line:" +echo " -drive file=$outimg,if=none,format=raw,id=emmc-img" +echo " -device emmc,boot-partition-size=$bootsz,rpmb-partition-size=$rpmbsz,drive=emmc-img"