Storage internals¶
Reference data for the Zune HD storage stack: the module surface, the CE storage-manager API,
the on-disk structures, the driver IOCTL codes, and the SoC register layout. This is the
companion to Storage overview, which explains how the pieces fit together. Everything here
is for Pavo firmware 4.5. Addresses and codes are firmware-specific and do not transfer to
other versions.
Provenance: an unmarked value is device-verified on a live v4.5 unit, or recovered from the v4.5
firmware disassembly and then confirmed against the device. [static] marks a value taken from
disassembly and not exercised on hardware. [unverified] marks an open item. Kernel-side load
and driver-state addresses that are reallocated on each boot are noted as per-boot.
Module surface¶
| Layer | Module | Role |
|---|---|---|
| Block driver | libnvemmc.dll |
DSK*: stream driver over the NVIDIA SDMMC DDK. |
| Partition | zpartstream.dll |
Zune partition-stream layer, owns the live partitioning. |
| Partition (MBR) | mspart.dll |
MS-partition driver, loaded and fronts DSK1:. |
| Filesystem | zbinfs.dll |
Read-only Zune binary filesystem (the OS image partition). |
| Filesystem | zexfat.dll |
Read-write Zune exFAT (the data partitions). |
| Dispatch | fsdmgr.dll, filesys.dll |
CE filesystem-driver manager and kernel filesys. |
| Application API | coredll.dll |
Storage-manager and file exports. |
The NK-side load bases float per boot for the demand-loaded filesystem drivers. The user-mode
coredll.dll is fixed at 0x40320000. sdmemory.dll and sdbus.dll are not loaded, matching
the absence of a removable-card path. In libnvemmc, DSK_Read is a stub that returns 0. All
input and output, sector reads included, goes through DSK_IOControl.
CE storage-manager API¶
The topology-walk and file APIs live in coredll.dll (base 0x40320000).
| Export | RVA | Live VA |
|---|---|---|
OpenStore |
0x02a880 |
0x4034a880 |
FindFirstStore |
0x02a980 |
0x4034a980 |
FindNextStore |
0x02a9ac |
0x4034a9ac |
FindCloseStore |
0x02a9cc |
0x4034a9cc |
GetStoreInfo |
0x02a9dc |
0x4034a9dc |
OpenPartition |
0x02aa8c |
0x4034aa8c |
GetPartitionInfo |
0x02ab18 |
0x4034ab18 |
FindFirstPartition |
0x02ab74 |
0x4034ab74 |
FindNextPartition |
0x02ab90 |
0x4034ab90 |
CreateFileW |
0x016b64 |
0x40336b64 |
ReadFile |
0x016ba0 |
0x40336ba0 |
WriteFile |
0x016bcc |
0x40336bcc |
DeviceIoControl |
0x016aa0 |
0x40336aa0 |
CloseHandle |
0x01226c |
0x4033226c |
Call notes:
OpenStoreis single-argument on this build:HANDLE OpenStore(LPCWSTR pszDeviceName), returning the handle directly. The two-argumentOpenStore(LPCWSTR, LPHANDLE)form some CE references show is rejected.FindFirstStoreandFindNextStoretake aSTOREINFO*withdwSizepreset to0xF0. They return a search handle, not a store handle. Enumerate a store's partitions byOpenStorefollowed byFindFirstPartition.FindFirstPartitiontakes the handle fromOpenStore, withPARTINFO.dwSizepreset to0x128.- These calls require a normal user-mode thread context. Driving them from outside the shell is covered in the Lyra runtime reference.
STOREINFO¶
240 bytes, as populated by filesys.dll on this firmware. Verified by dumping the buffer after
a successful FindFirstStore.
| Offset | Field | Type | Notes |
|---|---|---|---|
+0x000 |
dwSize |
DWORD | Always 0xF0, set by the caller. |
+0x004 |
szDeviceName |
WCHAR[16] | "DSK1:", "RAMDisk". |
+0x054 |
dwDeviceClass |
DWORD | |
+0x058 |
dwDeviceType |
DWORD | |
+0x05C |
dwDeviceFlags |
DWORD | |
+0x060 |
szStoreName |
WCHAR[32] | "eMMCFlashDisk". |
+0x0B0 |
snNumSectors |
u64 | Store size in sectors. |
+0x0B8 |
dwBytesPerSector |
DWORD | 0x800 (2048). |
+0x0E4 |
dwPartitionCount |
DWORD | |
+0x0E8 |
dwMountCount |
DWORD |
Observed (class, type, flags): DSK1: = (1, 2, 0x50); RAMDisk = (1, 0x20000000, 0).
PARTINFO¶
296 bytes. Verified by buffer dump on all three partitions.
| Offset | Field | Type | Notes |
|---|---|---|---|
+0x000 |
dwSize |
DWORD | Always 0x128. |
+0x004 |
szPartitionName |
WCHAR[32] | "Part00". |
+0x044 |
szFileSys |
WCHAR[32] | Names the FSD DLL: "zbinfs.dll", "zexfat.dll". |
+0x084 |
szVolumeName |
WCHAR[] | "ZBINFS", "Flash", "Flash2". |
+0x108 |
snNumSectors |
u64 | |
+0x124 |
bPartType |
BYTE | MBR type byte: 0x21 zbinfs OS, 0x07 zexfat data. |
szFileSys is the diagnostic field. It names the filesystem driver the partition is mounted
with directly, independent of the MBR type byte.
Live topology¶
Read live from the storage manager.
Store 'DSK1:' ('eMMCFlashDisk') class=1 type=2 flags=0x50
15,716,608 sectors x 2048 B = 29.977 GiB, 3 partitions
Part00 zbinfs.dll 'ZBINFS' 65,544 sectors = 128.0 MiB type=0x21
Part01 zexfat.dll 'Flash' 65,544 sectors = 128.0 MiB type=0x07
Part02 zexfat.dll 'Flash2' 15,585,480 sectors = 30,440.4 MiB type=0x07
Store 'RAMDisk' ('RAMDisk') class=1 type=0x20000000 flags=0
2,048 sectors x 512 B = 1.0 MiB, 1 partition (volatile scratch, not eMMC)
The three DSK1: partitions sum to the store total within about 80 KB.
On-disk layout (MBR)¶
Sector 0 of DSK1: is a DOS master boot record.
| Field | Value |
|---|---|
| Boot code (first bytes) | e9 fd ff 00 ... |
| Partition [0] status | 0x00 |
| Partition [0] type | 0x05 (extended) |
| Partition [0] LBA start | 16 |
| Partition [0] LBA count | 15,716,592 (2048-byte sectors) |
| Partitions [1..3] | empty |
Signature @0x1FE |
55 AA |
The three storage-manager partitions are logical partitions chained as extended boot records inside this one extended partition. The eMMC uses 512-byte native blocks (the standard eMMC block size). The block driver folds four of them into each 2048-byte logical sector.
Disk IOCTL codes¶
The codes this firmware actually dispatches, recovered from fsdmgr's disk-read helper and
confirmed by a working sector-0 read.
| Name | Code | CTL_CODE |
|---|---|---|
IOCTL_DISK_READ |
0x75C08 |
CTL_CODE(7, 0x702, BUFFERED, FILE_READ_ACCESS) |
IOCTL_DISK_WRITE |
0x79C0C |
CTL_CODE(7, 0x703, BUFFERED, FILE_READ_ACCESS) |
IOCTL_DISK_GETINFO |
0x71C00 |
CTL_CODE(7, 0x700, BUFFERED, ANY_ACCESS) |
The historical IOCTL_DISK_READ value 0x75400 from CE's public headers is not in the dispatch
table on this firmware.
SG_REQ (CE 6 R3), the read/write request structure: a 20-byte header followed by one 8-byte
SG_BUF per buffer.
| Offset | Field | Notes |
|---|---|---|
+0x00 |
sr_start |
Start sector. |
+0x04 |
sr_num_sec |
Sector count. |
+0x08 |
sr_num_sg |
Scatter-gather count. |
+0x0C |
sr_status |
Written by the driver. |
+0x10 |
reserved | Zero. |
+0x14 |
sr_sglist[0].sb_buf |
Buffer pointer. |
+0x18 |
sr_sglist[0].sb_len |
Buffer length. |
mspart.dll's PD_DeviceIoControl accepts only these codes and fails others with
ERROR_GEN_FAILURE: 1, 2, 3 (legacy), 0x71C00, 0x71C4C, 0x71C50, 0x71C58,
0x71C64, 0x71C80, 0x75C08, 0x79C0C. The legacy single-integer read and write codes 4
and 5 are accepted only by libnvemmc's DSK_IOControl below mspart, not by mspart
itself.
libnvemmc.dll DSK_* export RVAs (live at the per-boot libnvemmc base):
| Export | RVA | Export | RVA | |
|---|---|---|---|---|
DSK_Init |
0x001f70 |
DSK_Seek |
0x0014a0 |
|
DSK_Open |
0x001478 |
DSK_IOControl |
0x0021f8 |
|
DSK_Close |
0x0013f4 |
DSK_PowerUp |
0x0014ac |
|
DSK_Read |
0x001490 (stub) |
DSK_PowerDown |
0x0014a8 |
|
DSK_Write |
0x001498 |
DSK_Deinit |
0x001418 |
DSK_IOControl also exposes eMMC partition-mode IOCTLs 0x321004 (GET_PARTITION_MODE) and
0x321008 (SET_PARTITION_MODE). On this device they are effectively inert: there are no boot,
RPMB, or GPP partitions to switch to (see below).
Boot, RPMB, and GP partitions¶
The eMMC has no boot, RPMB, or general-purpose partitions. Read directly with CMD8 SEND_EXT_CSD,
its provisioning multipliers are all zero, and a zero multiplier means the region is unprovisioned.
EXT_CSD_REV reads 2, which the JEDEC table maps to revision 1.2 (eMMC 4.2), a revision predating
the boot-partition feature (4.3) and the RPMB and GP-partition features (4.4), consistent with the
zero multipliers.
| EXT_CSD field | Byte | Value |
|---|---|---|
SEC_COUNT |
212 | 0x03C60000 (63,307,776, the full user area) |
EXT_CSD_REV |
192 | 2 (revision 1.2, eMMC 4.2) |
BOOT_SIZE_MULT |
226 | 0 (no boot partitions) |
RPMB_SIZE_MULT |
168 | 0 (no RPMB) |
GP_SIZE_MULT (GPP1..4) |
143..154 | 0 (no GP partitions) |
PARTITIONING_SUPPORT |
160 | 0 |
Reading EXT_CSD¶
CMD8 SEND_EXT_CSD cannot be programmed into the controller directly. The register layout is
non-standard (below), and the SD clock is gated at idle: NvDdkHsmmcSendCommand does no clock
control, and the clock is brought up by the read path through NvRm. The read drives the driver's
own machinery instead:
- Ungate the SDMMC module clock.
NvDdkHsmmcResumedoes this asNvRmPowerModuleClockControl(hRm, (instance << 16) | 0x11, clientId, enable), withhRm,instance, andclientIdtaken from the Hsmmc handle. The handle is atgeom + 0x58off the geometry struct; the arguments arehandle[0],handle[+4], andhandle[+0x30]. - Call
NvDdkHsmmcRead(handle, 512, buffer, pCmd)with a hand-builtpCmdof{index = 8, arg = 0, class = 1 (read), 0, 0, 0, 0, 0}. The driver issuesCMD8and DMAs the 512-byteEXT_CSDinto the buffer. The call then returnsNvError_NotImplementedfrom its block-read wrapper (CMD8is not a block command), but the data is captured before that; aSEC_COUNT-matches-the-known-capacity check confirms the read.
Host tool: read-ext-csd.py.
SDMMC controller register map¶
NvDdkHsmmcSendCommand and NvDdkHsmmcGetCommandResponse (in libnvddk_misc.dll, module base
0xc08e0000) drive the controller at register base handle[+0x14]. The layout is not standard
SDHCI:
| Field | Offset from base |
|---|---|
| Block size / count | +0x04 |
| Command / control word (writing this issues the command) | +0x10 |
| Command index (6-bit) | +0x28 |
| Argument | +0x2c |
| Response | +0x34 |
Writing the argument to +0x08 and the command to +0x0c (the standard SDHCI positions) hits
registers that do not issue a command.
SoC storage registers¶
The controller MMIO base is handle[+0x14] (0xd2e60500 on the sampled boot). Its command-issue
register layout is the non-standard one in the register map above, not standard SDHCI. The handle
also records the APB DMA channel-4 physical base (0x70008500) at +0x10 and the power state at
+0x68; the handle's own base address is reallocated per boot.
The SD clock is gated at idle. NvDdkHsmmcSendCommand does no clock control and makes no calls;
the clock is ungated by the read path through NvRm (see the EXT_CSD read above). A command injected
at the SendCommand entry hook finds the clock off, so it never latches.
The block driver caches the card geometry in its NvDdk device state. It parses the eMMC's
EXT_CSD SEC_COUNT field (byte 212, a JEDEC value in 512-byte units) to 0x03C60000,
63,307,776 native sectors, a raw user-area capacity of 30.19 GiB. CE exposes 15,716,608 sectors
of 2048 bytes (62,866,432 native, 29.977 GiB), and the 512-byte native block folds four-to-one
into each logical sector. The struct base is reallocated per boot, so the field is found by its
signature (SEC_COUNT flanked by 0 and 0x400), not a fixed address.
The reserved front region¶
CE maps DSK1: onto the top of the card. A DSK1: read of logical sector 0 reaches the eMMC as
CMD18 READ_MULTIPLE_BLOCK for native block 441,344, so the block driver adds a fixed
441,344-block base to every logical read. That base plus the store size equals the full user area:
441,344 + 62,866,432 = 63,307,776 = SEC_COUNT. Native blocks 0 through 441,343 (215.5 MiB) sit in
front of the store and hold the boot and recovery firmware.
DSK1: cannot address them: the store IOCTL bounds-checks against the store size, and no
CreateFile path reaches libnvemmc below mspart. They are read by hooking
NvDdkHsmmcSendCommand and, while armed, subtracting a delta from a read command's native block
address, so a normal DSK1: read is remapped into the front region and served by the driver's own
DMA path. The read technique is in the
Lyra runtime reference.
NvDdkHsmmcSendCommand(hHsmmc, pCmd) takes a 32-byte command structure:
| Offset | Field | Notes |
|---|---|---|
+0x00 |
command index | 0x12 = CMD18 READ_MULTIPLE_BLOCK for reads |
+0x04 |
block address | native 512-byte block; a DSK1: read of logical sector N arrives as 441344 + 4*N |
+0x10 |
logical sector | the requested LBA |
Layout, read from a full dump of the region. Offsets are into the front region; about 55 MiB is
content and the rest is erased (0xFF) or zero.
| Offset | Size | Contents |
|---|---|---|
0 |
2 KiB, 24 copies every 128 KiB | signed header: a signature prefix, an a1a1ddba marker at +0x5EC, then a PKCS#7 signedData catalog (OID 1.2.840.113549.1.7.2) naming device model Pavo, signed 2011-04-18 (UTCTime 110418181445Z). All 24 copies byte-identical |
| 3.0 MiB | 128 KiB | Tegra boot configuration table, tagged BCT |
| 3.1 MiB, 6.1 MiB | 651.5 KiB each | encrypted bootloader, two byte-identical copies |
| 9.1 MiB, 76.8 MiB | ~12 MiB each | Windows CE boot image, two byte-identical copies: Tegra APX 2600, WOOKIE board support, NVIDIA bootloader (NvBl), CE kernel (kernel.dll, kitl.dll, coredll.dll) |
| 144.4 MiB | ~30 MiB | Windows CE image with networking: RNDIS, RAS, libnvodm_kernel_query.dll |
Boot-chain component identities¶
The bootloader, BCT, and CE images were identified against the extracted v4.5 firmware package
(ZBoot.bin, NK.bin, Recovery.bin).
- BCT. The 3.0 MiB block is a Tegra boot configuration table stored as a redundant 2 KiB record
replicated 64 times across the 128 KiB slot. Each record carries a mirrored internal header
(
8f9e8d8b ffffffff 00000100 00000380at both+0x00and+0x28) withBCTtags at+0x44and+0x54. - Bootloader. The copies at 3.1 and 6.1 MiB match the
ZBootstage. The firmware'sZBoot.binunpacks to 667,360 bytes; the on-flash copy is that length padded to a 4 KiB boundary (667,648 bytes), encrypted (entropy 8.0 against 6.1 for the plaintext). A zero-key AES pass yields noise, so a real key is in use (see below); AES is the platform's secure-boot cipher. - CE images. The 12 MiB copies at 9.1 and 76.8 MiB are a v4.5 boot and recovery image, not
byte-identical to either
NK.binorRecovery.bin. Unpacked and walked module by module (146 modules, 29 data files), they match those two except for the pointer relocation a different ROM layout requires (137 modules differ by under 1 percent, 32 are identical) and six modules that genuinely differ:nk.exe,zbinfs.dll,zbinfs_NK.hash,ZAccessoryDriver.dll,ZAcsState.dll,default.fdf.
Secure boot¶
The bootloader is encrypted, and a zero-key decrypt yields noise, so a real key is in use. The key
is a secure-boot key in the AP16 fuse block at 0x7000F800. The fuse rows the bootloader references
for its key material (0x7000F9A4..0x7000F9B4), read back from the running OS through the
runtime's register-mapping primitive,
return 0xFFFFFFFF, the all-ones of read-locked secure fuses. The key is burned but not
software-readable, so the bootloader cannot be decrypted on the device. That those specific rows are
the secure-boot and device keys is inferred from the bootloader's fuse references, not confirmed
against a fuse map [unverified]. The signed header is a Microsoft signedData catalog; its
coverage of the boot chain is not confirmed, since the on-flash copy is truncated to 2 KiB. The BCT
and both CE images are stored unencrypted; the bootloader is the only encrypted payload.