Skip to content

agent: read-only eMMC support via DesignWare MMC controller (hi3516cv500-family)#105

Merged
widgetii merged 1 commit into
masterfrom
agent-emmc-himci-read-mvp
May 15, 2026
Merged

agent: read-only eMMC support via DesignWare MMC controller (hi3516cv500-family)#105
widgetii merged 1 commit into
masterfrom
agent-emmc-himci-read-mvp

Conversation

@widgetii
Copy link
Copy Markdown
Member

Why

The cv500-family eMMC av300 board (hi3516av300:emmc) gets defib's boot protocol working as of #103, but the agent itself has no idea what eMMC is — flash_init probes SPI NOR via FMC, finds nothing, and falls back to no-flash. CMD_READ against the FLASH_MEM virtual window has nowhere to route.

This PR teaches the agent to drive the Synopsys DesignWare MMC host controller (EMMC_BASE=0x10100000), bring up an eMMC card, and stream blocks back through the existing CMD_READ. End-to-end verified on the real board: bytes returned through defib agent read --chip hi3516av300:emmc -p /dev/ttyUSB2 --address 0x14000000 --size 0x6000 match the shipped hi3516av300-emmc-spl.bin exactly (≈10 KB/s).

What

```
agent/Makefile | 16 +-
agent/emmc_himci.c | 485 +++++++++++++++++++++++++++++++++++++++++++++++++++++
agent/emmc_himci.h | 36 ++++
agent/main.c | 78 ++++++++-
agent/spi_flash.h | 1 +
```

  • `agent/emmc_himci.{c,h}` — minimal DWMMC driver (~470 LOC): pinmux setup, CRG configuration, controller reset, CMD0/1/2/3/9/7 identification, CMD17 single-block read with FIFO drain. The bootrom's PERISTAT-driven init returns -1 when `PERISTAT[9:8]==0` (the state the chip lands in after UART fastboot + truncated vendor SPL), so we bypass that path entirely and hardcode mode-1 pinmux values pulled from the bootrom ROM at offset 0x7d98.

  • `agent/main.c` — eMMC init runs after SPI flash probe fails. On success it populates `flash_info` with `FLASH_TYPE_EMMC=2`, CID-derived jedec_id, capacity, and 512-byte sector size. `handle_read` adds an eMMC branch that loops block-by-block through `emmc_read_block`. The `FLASH_MEM + flash_info.size` arithmetic moved to `uint64_t` — eMMC capacity values near 4 GiB were overflowing `uint32_t` and silently routing reads to the (non-existent) SPI flash window.

  • `addr_readable` — extended whitelist for `EMMC_BASE`, `IO_CTRL0_BASE`, and the bootrom mask ROM (`BOOTROM_BASE..+64 KiB`), each gated by per-SoC Makefile defines. The bootrom-ROM range is the source of the pinmux table; eMMC + IO ranges are needed by the driver.

  • `spi_flash.h` — adds `FLASH_TYPE_EMMC = 2` to the enum.

  • `Makefile` — adds `EMMC_BASE / IO_CTRL0_BASE / SYSCTRL_BASE / BOOTROM_BASE / BOOTROM_SIZE` under the `hi3516cv500` stanza and threads the corresponding `-D` defines into CFLAGS. `emmc_himci.c` joins `SRCS_C` only when `EMMC_BASE` is set, so non-eMMC SoCs are unaffected.

Known limitations (intentional MVP cuts)

  • 1-bit bus mode (skip CMD6 SWITCH). Setting CTYPE=1 without first switching the card to 4-bit wedges the data path silently — a real 4-bit path needs CMD6, follow-up.
  • HC capacity capped at uint32_t max - sector (0xFFFFFE00). True capacity for ≥ 2 GiB eMMC lives in EXT_CSD SEC_COUNT, which needs CMD8 SEND_EXT_CSD (data-bearing command) to retrieve. Until that lands, the host can address the first 4 GiB linearly — fine for partition dumps.
  • CMD17 single-block only (no CMD18 multi-block / no CMD12 STOP).
  • No writes, no erase — that's the whole "read-only MVP" framing.

Test plan

  • Real hardware: agent identifies eMMC (CID MID=0x88, OEM=0x0103 — matches vendor U-Boot's `Manufacturer ID: 0x88, OEM: 103, Name: "D9D16"` boot log), 24 KiB read returns bytes matching the shipped SPL blob exactly.
  • C-side tests (`make -C agent test HOST_CC=gcc`) — 5412 assertions pass.
  • Python suite (`uv run pytest tests/ -x --ignore=tests/fuzz`) — 527 passed, 2 skipped.
  • Lint/mypy clean on touched files.
  • SPI NOR av300 not regressed — the eMMC code is `#ifdef EMMC_BASE` gated and only enters when `flash_init()` failed, so chips with working SPI flash never see the eMMC path.

Gotchas captured in the PR for future debug

  1. CTYPE bus-width must match the card's actual mode (default 1-bit until CMD6).
  2. uint32_t overflow at `FLASH_MEM + flash_info.size` near 4 GiB — needs uint64_t arithmetic.
  3. CMD2/9 responses are R2 long (no CRC); pass `EMMC_FLAG_NO_CRC` to skip controller CRC check.

Followup work captured in kaeru `agent-emmc-himci-read-mvp-2026-05-15`.

🤖 Generated with Claude Code

…500-family)

Wires up just enough of the Synopsys DesignWare MMC host controller at
EMMC_BASE=0x10100000 for the agent to identify an eMMC card and stream
blocks back to the host through CMD_READ — closing the loop on
"defib agent ... :emmc" on the hi3516av300 eMMC board.

Tested on hi3516av300:emmc + D9D16 14.5 GiB: agent reports
CID MID=0x88 / OEM=0x0103 (matches the vendor U-Boot boot log),
FLASH_TYPE_EMMC, and CMD_READ against the FLASH_MEM virtual window
(addr=0x14000000+LBA byte offset) returns real eMMC contents — the
first 24 KiB matches the shipped hi3516av300-emmc-spl.bin blob exactly.
Throughput ~10 KB/s on the 1-bit / slow-divider MVP config, which is
fine for partition dumps.

What's in this PR:

* `agent/emmc_himci.{c,h}` (~470 LOC) — minimal DWMMC driver: pinmux
  setup, CRG configuration, controller reset, CMD0/1/2/3/9/7
  identification, CMD17 single-block read with FIFO drain. The bootrom's
  PERISTAT-driven init returns -1 when PERISTAT[9:8]==0 (where the chip
  lands after UART fastboot + truncated vendor SPL), so we bypass that
  path entirely and just hardcode mode-1 pinmux values pulled from the
  bootrom ROM table at offset 0x7d98.

* `agent/main.c` — eMMC init runs after SPI flash probe fails; on success
  it populates `flash_info` with `FLASH_TYPE_EMMC`, CID-derived
  jedec_id, capacity, and 512-byte sector size. `handle_read` adds an
  eMMC branch that loops block-by-block through `emmc_read_block`. The
  `FLASH_MEM + flash_info.size` arithmetic moved to uint64_t — eMMC
  capacity values near 4 GiB were overflowing uint32_t and silently
  routing reads to the (non-existent) SPI flash window.

* `agent/main.c addr_readable` — extended whitelist for EMMC_BASE,
  IO_CTRL0_BASE, and the bootrom mask ROM (BOOTROM_BASE..+64 KiB),
  each gated by per-SoC Makefile defines. The bootrom-ROM range is the
  source of the pinmux table; eMMC + IO ranges are needed by the driver.

* `agent/spi_flash.h` — adds `FLASH_TYPE_EMMC = 2` to the enum.

* `agent/Makefile` — adds EMMC_BASE / IO_CTRL0_BASE / SYSCTRL_BASE /
  BOOTROM_BASE / BOOTROM_SIZE under the hi3516cv500 stanza and threads
  the corresponding `-D` defines into CFLAGS. `emmc_himci.c` joins
  `SRCS_C` only when EMMC_BASE is set.

Known limitations (clearly intentional MVP cuts):

* 1-bit bus mode (skip CMD6 SWITCH). Setting CTYPE=1 without first
  switching the card to 4-bit wedges the data path silently — a real
  4-bit path needs CMD6.
* HC capacity capped at uint32_t max - sector (0xFFFFFE00). True
  capacity for ≥ 2 GiB eMMC lives in EXT_CSD SEC_COUNT which needs
  CMD8 SEND_EXT_CSD (data-bearing command) to retrieve.
* CMD17 single-block only (no CMD18 multi-block / no CMD12 STOP).
* No CMD16 SET_BLOCKLEN (default 512 is fine for the MVP).
* No writes, no erase.

C-side tests (host build) still pass — `make -C agent test` runs 5412
assertions. The Python suite still passes — 527 / 2 skipped. The Lint
& Type Check job stays green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@widgetii widgetii merged commit 76a6aa1 into master May 15, 2026
13 checks passed
@widgetii widgetii deleted the agent-emmc-himci-read-mvp branch May 15, 2026 15:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant