Emulating MMC/SD card storage as /dev/mmcblk0 in a libvirt guest

It is a bit tricky but not impossible to emulate MMC/SD card storage in a virtual machine controlled by libvirt.

Ok, let’s do the FAQs first.

“Why would anyone emulate a MMC/SD card in a virtual machine?!”
– Of course because some real device has MMC/SD card-kinda storage and you want to test an installation for such a device virtually first.

“But that’s removable storage?”
– No it isn’t necessarily, there is embedded flash, too, think eMMC. Your fancy-pants NAS has its firmware stored on an eMMC but likely transfers it onto a part of the HDD array to avoid wearout.

“But what weird kind of installation targets such embedded flash?”
– I didn’t say the installation is going to happen on the embedded flash, I just said I want to emulate the presence of a MMC/SD card-kinda storage.

“But why does have it to be present if you’re not even going to install on it?”
– Exactly to test that it does not.

Still confused? I want to test the installation of an alternative OS on the HDDs of a NAS without touching the built-in firmware stored on the internal eMMC. Now clear? Good.

So where’s the problem? The first problem is that virt-manager doesn’t allow you to add anything that looks like MMC/SD card storage:

Screenshot of virt-manager's "Add hardware / storage" dialog
Screenshot of virt-manager’s “Add hardware / storage” dialog

The device type “Disk device” isn’t wrong but SD/MMC storage sits on neither SATA, SCSI nor USB buses and there’s also no virtio support for it. With virt-manager we’re not going anywhere. Instead we have to go “under the hood” and virsh edit the libvirt domain’s XML.

This is what a typical disk section looks like:

  <disk type='file' device='disk'>
    <driver name='qemu' type='qcow2'/>
    <source file='/path/to/backing_file.qcow2'/>
    <target dev='vda' bus='virtio'/>
  </disk>

From libvirt.org’s description of the domain’s XML code we learn that the target element would actually support bus='sd'. Then dev='vda' obviously doesn’t make sense. But if you try to change it to dev='mmcblk0' or dev='sd' you will get an XML document failed to validate against schema error. And if you change it to dev='sd0' you’ll get a Unsupported configuration: invalid disk target 'sd0' error. I did not manage to get this working with the normal libvirt configuration elements.

So what do we actually want? We want libvirt to start qemu with additional parameters like this:

  -drive id=mysdcard,if=none,format=qcow2,file=/path/to/backing_file.qcow2 \
  -device sdhci-pci \
  -device sd-card,drive=mysdcard

These parameters first manually define a drive, then add emulation of a SD host controller bridge driver (sdhci-pci, it’s necessary to explicitly define it since this change) and finally add emulation of an sd-card using the manually defined drive.

So how do we get libvirt to generate these parameters? We don’t, as we saw above. Instead, we can tell it to pass these parameters verbatim when constructing its qemu call using command-line passthrough. virsh edit the domain’s XML again and add the namespace declaration at the top to the <domain> element and the other block at the end:

<domain type='kvm' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>
  [...]
  </devices>
  <qemu:commandline>
    <qemu:arg value='-device'/>
    <qemu:arg value='sdhci-pci'/>
    <qemu:arg value='-drive'/>
    <qemu:arg value='file=/path/to/backing_file.qcow2,format=qcow2,id=mysdcard,if=none'/>
    <qemu:arg value='-device'/>
    <qemu:arg value='sd-card,drive=mysdcard'/>
  </qemu:commandline>
</domain>

The file will now appear as /dev/mmcblk0 inside the virtual machine.

Note 1: You might have to change ownership/permissions of the backing file manually. libvirt can’t do that because it doesn’t know anything about it, of course.

Note 2: This will likely not work on RHEL 7 and later virtualization hosts because RedHat disabled SD card support.

Note 3: You can not currently have more than one sd-card, i.e. you can’t have -device sd-card0,drive=mysdcard0 -device sd-card1,drive=mysdcard1 because support for that https://stackoverflow.com/questions/64964659/qemu-how-to-connect-a-sd-card-to-a-specific-controller, at least it did in 2020.