106 lines
3.8 KiB
Python
106 lines
3.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Build the minimal FAT16 SD card used by the Renode V5 model.
|
|
|
|
The firmware's stdlib loader reaches the SDHCI block-device path, while the
|
|
mainloop looks for assets.bin on the same removable filesystem. Keep both
|
|
vendor inputs in an ordinary FAT root directory so the guest owns all file
|
|
lookup and reads.
|
|
"""
|
|
|
|
import argparse
|
|
import math
|
|
import os
|
|
import struct
|
|
import sys
|
|
|
|
SECTOR = 512
|
|
SECTORS = 65536 # 32 MiB
|
|
SECTORS_PER_CLUSTER = 8 # 4 KiB clusters
|
|
RESERVED = 1
|
|
FATS = 2
|
|
ROOT_ENTRIES = 512
|
|
FAT_SECTORS = 32
|
|
ROOT_SECTORS = (ROOT_ENTRIES * 32 + SECTOR - 1) // SECTOR
|
|
DATA_START = RESERVED + FATS * FAT_SECTORS + ROOT_SECTORS
|
|
CLUSTERS = (SECTORS - DATA_START) // SECTORS_PER_CLUSTER
|
|
|
|
|
|
def fat_name(name):
|
|
stem, ext = name.upper().rsplit('.', 1)
|
|
return stem.encode('ascii').ljust(8) + ext.encode('ascii').ljust(3)
|
|
|
|
|
|
def root_entry(name, first_cluster, size):
|
|
entry = bytearray(32)
|
|
entry[0:11] = fat_name(name)
|
|
entry[11] = 0x20
|
|
struct.pack_into('<H', entry, 26, first_cluster)
|
|
struct.pack_into('<I', entry, 28, size)
|
|
return entry
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument('--out', default='.build/emu/sdcard_mbr.img')
|
|
ap.add_argument('--vexos-dir', default='.build/vexos-0/VEXOS_V5_1_1_5_0')
|
|
ap.add_argument('--stdlib', default='.build/emu/assets/fs/stdlib_0.bin')
|
|
args = ap.parse_args()
|
|
|
|
assets = os.path.join(args.vexos_dir, 'assets.bin')
|
|
inputs = [('ASSETS.BIN', assets), ('STDLIB_0.BIN', args.stdlib)]
|
|
for _, path in inputs:
|
|
if not os.path.isfile(path):
|
|
print('missing %s (run: make assets)' % path, file=sys.stderr)
|
|
return 2
|
|
|
|
files = [(name, open(path, 'rb').read()) for name, path in inputs]
|
|
needed = sum((len(data) + SECTOR * SECTORS_PER_CLUSTER - 1) //
|
|
(SECTOR * SECTORS_PER_CLUSTER) for _, data in files)
|
|
if needed > CLUSTERS:
|
|
raise ValueError('SD image too small for %d clusters' % needed)
|
|
|
|
image = bytearray(SECTORS * SECTOR)
|
|
boot = memoryview(image)[:SECTOR]
|
|
boot[0:3] = b'\xeb\x3c\x90'
|
|
boot[3:11] = b'ROSESD '
|
|
struct.pack_into('<H', boot, 11, SECTOR)
|
|
boot[13] = SECTORS_PER_CLUSTER
|
|
struct.pack_into('<H', boot, 14, RESERVED)
|
|
boot[16] = FATS
|
|
struct.pack_into('<H', boot, 17, ROOT_ENTRIES)
|
|
# TotalSectors16 uses zero when the volume needs the 32-bit field.
|
|
struct.pack_into('<H', boot, 19, 0)
|
|
boot[21] = 0xF8
|
|
struct.pack_into('<H', boot, 22, FAT_SECTORS)
|
|
struct.pack_into('<H', boot, 24, 32)
|
|
struct.pack_into('<H', boot, 26, 64)
|
|
struct.pack_into('<I', boot, 32, SECTORS)
|
|
boot[510:512] = b'\x55\xaa'
|
|
|
|
fat = bytearray(FAT_SECTORS * SECTOR)
|
|
struct.pack_into('<H', fat, 0, 0xFFF8)
|
|
struct.pack_into('<H', fat, 2, 0xFFFF)
|
|
root = memoryview(image)[(RESERVED + FATS * FAT_SECTORS) * SECTOR:
|
|
DATA_START * SECTOR]
|
|
cluster = 2
|
|
for index, (name, data) in enumerate(files):
|
|
count = max(1, math.ceil(len(data) / (SECTOR * SECTORS_PER_CLUSTER)))
|
|
for n in range(count):
|
|
value = 0xFFFF if n + 1 == count else cluster + n + 1
|
|
struct.pack_into('<H', fat, 2 * (cluster + n), value)
|
|
root[index * 32:(index + 1) * 32] = root_entry(name, cluster, len(data))
|
|
start = (DATA_START + (cluster - 2) * SECTORS_PER_CLUSTER) * SECTOR
|
|
image[start:start + len(data)] = data
|
|
cluster += count
|
|
|
|
for fat_index in range(FATS):
|
|
start = (RESERVED + fat_index * FAT_SECTORS) * SECTOR
|
|
image[start:start + len(fat)] = fat
|
|
os.makedirs(os.path.dirname(os.path.abspath(args.out)), exist_ok=True)
|
|
open(args.out, 'wb').write(image)
|
|
print('wrote %s: FAT16, %d MiB, %s' %
|
|
(args.out, len(image) // (1024 * 1024), ', '.join(name for name, _ in files)))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|