rp23xx: add an OTP driver on the efuse interface#19532
Conversation
|
Updated the branch: the programming path is now exercised under emulation, and doing so found and fixed a real bug in it. What was wrong. The guard that refuses to re-program an already-programmed row checked only whether the row held data outside the bits the field covers: if ((current & ~touched) != 0) { return -EROFS; }For a field covering the whole row, if ((uint16_t)(current | value) == current) { continue; } /* already programmed */
if (current != 0) { return -EROFS; } /* ECC cannot be recomputed */How it was found. I added a functional RP2350 OTP model to my Renode setup (the 4096 x 24 bit array, the ECC window at 19 checks now pass in emulation, covering: a blank row reads zero; programming a whole row and reading it back; re-writing the same value being a no-op rather than an error; a different value being refused with The read path results in the PR description are unchanged and still come from real hardware — the fix touches only |
24bb669 to
bfcae67
Compare
The rp2350 holds 4096 rows of 24 bit one-time-programmable memory, which the port did not expose at all. This adds a driver on the NuttX efuse interface, registered by the common board bringup as /dev/efuse. The driver uses the ECC interpretation of a row, in which 16 bits carry data and the remaining 8 carry a Hamming code, so the OTP appears as a flat space of 4096 * 16 bits for the efuse field descriptors to index: a descriptor at bit offset N refers to bit N % 16 of row N / 16. That matches the row numbers already listed in hardware/rp23xx_otp_data.h. Reads come from the chip's ECC-translated window and have no side effects. Rows are locked in pages of 64; a page locked against reads would raise a bus fault, so the lock is checked first and reported as an error instead. Programming needs the separate RP23XX_OTP_WRITE option, which defaults to off; without it a write returns EPERM and no programming code is built at all. When enabled, a row is programmed as a whole through the bootrom, since the ECC bits cover the whole row. For the same reason a row that already holds data cannot be modified, and such a write is rejected rather than left to corrupt the row's ECC. Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
Summary
The rp2350 holds 4096 rows of 24 bit one-time-programmable memory, which the
port did not expose at all, although
hardware/rp23xx_otp.handhardware/rp23xx_otp_data.hhave been in tree unused. This adds a driver onthe NuttX efuse interface, registered by the common board bringup as
/dev/efuse.Addressing. The driver uses the ECC interpretation of a row, in which 16
bits carry data and the remaining 8 carry a Hamming code. The OTP therefore
appears as a flat space of 4096 * 16 bits for the efuse field descriptors to
index: a descriptor at bit offset N refers to bit
N % 16of rowN / 16.That lines up with the row numbers already listed in
rp23xx_otp_data.h, so afield can be described directly from the constants there.
Reads come from the chip's ECC-translated window and have no side effects;
single bit errors are corrected in hardware. Rows are locked in pages of 64,
and reading a page that is locked against reads raises a bus fault, so the
driver checks the lock first and reports
EPERMinstead of faulting.Programming requires the separate
RP23XX_OTP_WRITEoption, which defaultsto off; with it off a write returns
EPERMand no programming code is compiledin at all. When enabled, a row is programmed as a whole through the bootrom
OTP_ACCESSentry point, because the ECC bits cover the whole row. For thesame reason a row that already holds data cannot be modified, and the driver
rejects such a write rather than leaving it to corrupt the row's ECC.
Impact
CONFIG_RP23XX_OTP(default n, selectsEFUSE). Nochange to existing behavior when disabled.
arm(rp23xx / RP2350). Boards: rp23xx common bringup registers/dev/efusewhen the option is enabled.symbols and the new device node.
Documentation/platforms/arm/rp23xx/index.rst.Testing
Hardware: RP2350 board (Pimoroni Pico Plus 2),
raspberrypi-pico-2:nshplus
RP23XX_OTP. Programmed over SWD with a Raspberry Pi Debug Probe(probe-rs); console on UART0.
Read path, on hardware
Exercised with a small read-only application that issues
EFUSEIOC_READ_FIELD:/dev/efuseregisters at boot.49a98f93d781c687, which is exactly what the four individual row readscompose to (
0xc687,0xd781,0x8f93,0x49a9). The 64 bits of therandom identifier in rows 4-7 likewise agree with their individual rows.
0x7, the low nibble of the0xc687held in that row, confirming the bit offset and masking.than read.
raw OTP window (
0x40134000, which exposes all 24 bits). All eight matchthe driver's ECC-decoded values, and the upper bytes hold the Hamming codes,
confirming the ECC interpretation is the right one.
Programming path: verified in emulation, never run on silicon
The write path is not run on hardware, because programming is irreversible
and would permanently consume fuses on the test board. It is instead exercised
against a functional RP2350 OTP model in Renode (the array, both read windows,
the SW_LOCK registers and OR-only programming, with the bootrom
otp_access()entry point intercepted). 19 checks pass, covering: programming a blank row and
reading it back, re-writing the same value as a no-op, a different value refused
with
EROFS, sub-row bit placement, a 32-bit field split across two rows,out-of-range rejection, and the factory rows left untouched.
That found a real bug, now fixed: the already-programmed guard tested only for
data outside the field's bits, so a field covering a whole row bypassed it and
the driver would have asked the bootrom to program over a row whose ECC was
already fixed. The guard is now "any non-blank row is off limits".
Emulation validates this driver's logic, not the bootrom's programming sequence
on real silicon. What was also verified:
CONFIG_RP23XX_OTP_WRITE=ylinks cleanly.above) the programming code is genuinely absent, not merely unused: the OTP
object's only external references are
efuse_registerandmemset, thelinked image contains no reference to the bootrom OTP entry point, and
rp23xx_otp_write_fieldcompiles down tomov r0, #-1; bx lr.EFUSEIOC_WRITE_FIELDreturnsEPERMon the test board, and the row thetest aimed at was afterwards confirmed still blank by reading it over SWD.
tools/checkpatch.sh -f(nxstyle) and-g(patch + commit message) pass.