On Mon, Jul 20, 2026 at 5:19 AM Bernhard Beschow <[email protected]> wrote:
>
> In Linux, the ESDHC_MIX_CTRL qirk is guarded by esdhc_is_usdhc() while
> the eSDHC code path uses the standard SDHC interface. Extract the quirk
> into a new `usdhc_write()` function.
>
> Fixes file system corruption on emulated i.MX53 where Linux'
> esdhc_is_usdhc() returns false. The same likely happens on e500 and
> imx25-pdk machines.
>
> Fixes: 75e98bc4f859 ("hw/sd/sdhci: Add TYPE_FSL_ESDHC_BE")
> cc: qemu-stable
> Signed-off-by: Bernhard Beschow <[email protected]>
> ---
> hw/sd/sdhci.c | 73 +++++++++++++++++++++++++++++++--------------------
> 1 file changed, 44 insertions(+), 29 deletions(-)
>
> diff --git a/hw/sd/sdhci.c b/hw/sd/sdhci.c
> index c86dfa281f..e58a610397 100644
> --- a/hw/sd/sdhci.c
> +++ b/hw/sd/sdhci.c
> @@ -1795,34 +1795,6 @@ esdhc_write(void *opaque, hwaddr offset, uint64_t val,
> unsigned size)
> sdhci_write(opaque, offset, value, size);
> break;
>
> - case ESDHC_MIX_CTRL:
> - /*
> - * So, when SD/MMC stack in Linux tries to write to "Transfer
> - * Mode Register", ESDHC i.MX quirk code will translate it
> - * into a write to ESDHC_MIX_CTRL, so we do the opposite in
> - * order to get where we started
> - *
> - * Note that Auto CMD23 Enable bit is located in a wrong place
> - * on i.MX, but since it is not used by QEMU we do not care.
> - *
> - * We don't want to call sdhci_write(.., SDHC_TRNMOD, ...)
> - * here because it will result in a call to
> - * sdhci_send_command(s) which we don't want.
> - *
> - */
> - s->trnmod = value & UINT16_MAX;
> - break;
> - case SDHC_TRNMOD:
Looks like this register is not eSDHC specific? I looked at linux
driver source codes and suspect we may need some more turning here
other than uSDHC only.
> - /*
> - * Similar to above, but this time a write to "Command
> - * Register" will be translated into a 4-byte write to
> - * "Transfer Mode register" where lower 16-bit of value would
> - * be set to zero. So what we do is fill those bits with
> - * cached value from s->trnmod and let the SDHCI
> - * infrastructure handle the rest
> - */
> - sdhci_write(opaque, offset, val | s->trnmod, size);
> - break;
> case SDHC_BLKSIZE:
> /*
> * ESDHCI does not implement "Host SDMA Buffer Boundary", and
> @@ -1891,9 +1863,52 @@ static void fsl_esdhc_le_init(Object *obj)
> qdev_prop_set_uint8(dev, "sd-spec-version", 2);
> }
>
> +static void
> +usdhc_write(void *opaque, hwaddr offset, uint64_t val, unsigned size)
> +{
> + SDHCIState *s = SYSBUS_SDHCI(opaque);
> + uint32_t value = (uint32_t)val;
> +
> + switch (offset) {
> + case ESDHC_MIX_CTRL:
> + /*
> + * So, when SD/MMC stack in Linux tries to write to "Transfer
> + * Mode Register", uSDHC i.MX quirk code will translate it
> + * into a write to ESDHC_MIX_CTRL, so we do the opposite in
> + * order to get where we started.
> + *
> + * Note that Auto CMD23 Enable bit is located in a wrong place
> + * on i.MX, but since it is not used by QEMU we do not care.
> + *
> + * We don't want to call sdhci_write(.., SDHC_TRNMOD, ...)
> + * here because it will result in a call to
> + * sdhci_send_command(s) which we don't want.
> + *
> + */
> + s->trnmod = value & UINT16_MAX;
> + break;
> +
> + case SDHC_TRNMOD:
> + /*
> + * Similar to above, but this time a write to "Command
> + * Register" will be translated into a 4-byte write to
> + * "Transfer Mode register" where lower 16-bit of value would
> + * be set to zero. So what we do is fill those bits with
> + * cached value from s->trnmod and let the SDHCI
> + * infrastructure handle the rest
> + */
> + sdhci_write(opaque, offset, val | s->trnmod, size);
> + break;
> +
> + default:
> + esdhc_write(opaque, offset, val, size);
> + break;
> + }
> +}
> +
> static const MemoryRegionOps usdhc_mmio_ops = {
> .read = esdhc_read,
> - .write = esdhc_write,
> + .write = usdhc_write,
> .valid = {
> .min_access_size = 1,
> .max_access_size = 4,
> --
Regards,
Bin