Misc HW patches
- Add RPMB emulation to eMMC model - Use generic MachineState::fdt field in microvm machine - Remove dead code in ac97_realize() -----BEGIN PGP SIGNATURE----- iQIzBAABCAAdFiEE+qvnXhKRciHc/Wuy4+MsLN6twN4FAmkLEUMACgkQ4+MsLN6t wN4yURAAuiQPYC6rcPbjCI1RZ5iPyrajH1iKW6HSV6nMWHap1vjL8hUnrfDu1GRH uCyf8ExMkPWemNJW1WcxMN19Gie/J42PfKv7ggHTVoEQwg70DLmKBUcFBbsPfLy7 7NJ9qNnyZANNgBlvywZRPxs3v+3WEgqa6NEjpWqS5ivIEQjW4bxGa6yJ6LmJq1UY YpdSuK/9tsdPcDnc0b95cEBOZa7y8tjr8gtxCAraPwY+elaM9EYDwB8Mrg84RWiN zeeiCt1PL/Hc9qRiZral2MsWGtfefeOPGCir0jawaYl7UfbLi/0EXvpHJbMTl626 MjilMlUi23aUbn1cuxygA1NV3sy+yRpZtxrpfJTOhoo7WZUBnn0atcH6GKMH2AM0 S/thR6c1ArUck8d8ABUBESskmZpZQFPGXLcW+XCi8SOP/HwmtT/0L+OlexQPLAep nqu/T/yXer2C4sUHB2iwK7DrF7Dl2bzhdRZhyTEtIYuT4dC0FDVv9bwdgna/xWj3 Re0HPT5J9o0tzQ2QaGMwPkjepf+LH1z3ntXhgJstr0D5G2wJ8+g1ZlPFKgrvBsCj C/YWZ3og31THAIb12exxaF4mHUF4fBrerQHg4E93MPhz1403D+sqJDxOUaC/PRJB OWwBCkWsWE8tjLie+1igNWKKB0N4ZTNKTGu0yxXFbcocu9LO6r0= =X6wb -----END PGP SIGNATURE----- Merge tag 'hw-misc-20251104' of https://github.com/philmd/qemu into staging Misc HW patches - Add RPMB emulation to eMMC model - Use generic MachineState::fdt field in microvm machine - Remove dead code in ac97_realize() # -----BEGIN PGP SIGNATURE----- # # iQIzBAABCAAdFiEE+qvnXhKRciHc/Wuy4+MsLN6twN4FAmkLEUMACgkQ4+MsLN6t # wN4yURAAuiQPYC6rcPbjCI1RZ5iPyrajH1iKW6HSV6nMWHap1vjL8hUnrfDu1GRH # uCyf8ExMkPWemNJW1WcxMN19Gie/J42PfKv7ggHTVoEQwg70DLmKBUcFBbsPfLy7 # 7NJ9qNnyZANNgBlvywZRPxs3v+3WEgqa6NEjpWqS5ivIEQjW4bxGa6yJ6LmJq1UY # YpdSuK/9tsdPcDnc0b95cEBOZa7y8tjr8gtxCAraPwY+elaM9EYDwB8Mrg84RWiN # zeeiCt1PL/Hc9qRiZral2MsWGtfefeOPGCir0jawaYl7UfbLi/0EXvpHJbMTl626 # MjilMlUi23aUbn1cuxygA1NV3sy+yRpZtxrpfJTOhoo7WZUBnn0atcH6GKMH2AM0 # S/thR6c1ArUck8d8ABUBESskmZpZQFPGXLcW+XCi8SOP/HwmtT/0L+OlexQPLAep # nqu/T/yXer2C4sUHB2iwK7DrF7Dl2bzhdRZhyTEtIYuT4dC0FDVv9bwdgna/xWj3 # Re0HPT5J9o0tzQ2QaGMwPkjepf+LH1z3ntXhgJstr0D5G2wJ8+g1ZlPFKgrvBsCj # C/YWZ3og31THAIb12exxaF4mHUF4fBrerQHg4E93MPhz1403D+sqJDxOUaC/PRJB # OWwBCkWsWE8tjLie+1igNWKKB0N4ZTNKTGu0yxXFbcocu9LO6r0= # =X6wb # -----END PGP SIGNATURE----- # gpg: Signature made Wed 05 Nov 2025 09:56:35 AM CET # gpg: using RSA key FAABE75E12917221DCFD6BB2E3E32C2CDEADC0DE # gpg: Good signature from "Philippe Mathieu-Daudé (F4BUG) <f4bug@amsat.org>" [unknown] # gpg: WARNING: This key is not certified with a trusted signature! # gpg: There is no indication that the signature belongs to the owner. # Primary key fingerprint: FAAB E75E 1291 7221 DCFD 6BB2 E3E3 2C2C DEAD C0DE * tag 'hw-misc-20251104' of https://github.com/philmd/qemu: hw/audio: Remove dead code from ac97_realize hw/i386/microvm: Use fdt field from MachineState docs: Add eMMC device model description scripts: Add helper script to generate eMMC block device images hw/sd/sdcard: Handle RPMB MAC field hw/sd/sdcard: Add basic support for RPMB partition hw/sd/sdcard: Allow user creation of eMMCs hw/sd/sdcard: Fix size check for backing block image Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
This commit is contained in:
commit
deed5c8e93
9 changed files with 686 additions and 95 deletions
|
|
@ -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
|
||||
|
|
|
|||
55
docs/system/devices/emmc.rst
Normal file
55
docs/system/devices/emmc.rst
Normal file
|
|
@ -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
|
||||
|
|
@ -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 */
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
364
hw/sd/sd.c
364
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;
|
||||
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,8 +2817,14 @@ static uint8_t sd_read_byte(SDState *sd)
|
|||
sd->data_start, io_len)) {
|
||||
return dummy_byte;
|
||||
}
|
||||
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 ++];
|
||||
|
||||
if (sd->data_offset >= io_len) {
|
||||
|
|
@ -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 <imagefile> <new-size>'\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 <imagefile> <new-size>'\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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -102,8 +102,6 @@ struct MicrovmMachineState {
|
|||
Notifier powerdown_req;
|
||||
struct GPEXConfig gpex;
|
||||
|
||||
/* device tree */
|
||||
void *fdt;
|
||||
uint32_t ioapic_phandle[2];
|
||||
};
|
||||
|
||||
|
|
|
|||
216
scripts/mkemmc.sh
Executable file
216
scripts/mkemmc.sh
Executable file
|
|
@ -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 <jan.kiszka@siemens.com>
|
||||
#
|
||||
|
||||
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"
|
||||
Loading…
Add table
Add a link
Reference in a new issue