Conversation
joeferner
marked this pull request as ready for review
August 30, 2026 14:25
A board's identity lives in an EEPROM on ID_SD/ID_SC, and every existing way to write one assumes Linux on the target: `eepflash.sh` instantiates a bit-banged i2c-gpio bus and talks to it through /dev/i2c. A board that runs bare metal has none of that, and pulling the part to program it in a socket is not a workflow. The loader is already attached to such a board over serial and already reaches its hardware through rpi-hal, so it is where this belongs. `eeprom-write` takes the `.eep` that `eepmake` produces and programs it; `eeprom-read` copies it back off. Wire commands 9 and 10, both shaped like the `sd-*` ones -- arguments, a status byte, then the existing CRC-checked chunk stream in whichever direction. Both take `--address` (0x50 by default, what the HAT specification assigns) and `--offset`. With no `--length`, `eeprom-read` reads the 12-byte HAT header first and takes the image length out of it, so a dump is the couple of hundred bytes of atoms rather than 32 KiB of mostly 0xff. The device reads every page back after programming it, and this is not belt and braces. A write-protected part -- `WP` tied high, which on a board that puts write protect on a solder jumper is a state you will meet -- acknowledges every byte and stores none. Without the read-back this command would report a clean success and leave the EEPROM exactly as it was. That is error code 8, and it is deliberately distinct from 7 (nothing acknowledged the page write) and 10 (the page was written but the part never answered the read that checks it), because those three send you to different parts of the board. The wait between writing a page and reading it back is a fixed delay, not the acknowledge polling the datasheet also offers. Polling was the first implementation and it failed on hardware in a way worth recording: exactly one 32-byte page landed and the rest of the image stayed erased. The poll is issued tens of microseconds after the page write's STOP, which is the same moment the part begins the cycle being polled about -- so it can answer about a cycle that has not started. Waiting out the family's 5ms tWR, rounded up to 6, has no such race, and the read-back that follows is the real evidence a page took, so the wait only has to be long enough rather than exact. The read is retried a few times for a part slower than its own datasheet. Page size comes from the host because the device cannot know what part is fitted, and getting it wrong is not a clean failure: a page write that runs past the part's page boundary wraps to the start of that page rather than carrying, overwriting data already programmed. The default is 32, the page of a 24C32 -- the smallest part the specification allows -- which divides every larger part's page and is therefore always safe. A 24C256 takes `--page-size 64` and programs in half the time. `send_chunked` gains an ACK-attempt count. Every other command stores a chunk and acknowledges immediately; this one programs it a page at a time, so a 4 KiB chunk takes seconds before its ACK, and one timeout window had the host resending chunks the device was still working through. The per-chunk ACK is flow control here as everywhere else in this protocol, so the fix is to wait longer for it, not to send ahead. Three protocol tests cover the new framing against the fake device: the image transfer, the verify failure naming write protect, and the header-driven read length. The firmware's rpi-hal floor moves to 0.3.0, which is where `I2c::<BSC0>::init_id` -- BSC0 muxed to GPIO0/1 -- first appears. There is no way to reach that routing in an earlier version, so this is a real floor rather than whatever happened to be current. Verified against a Pi 2 carrying a HAT with a CAT24C256 at 0x50: an `eepmake -v1` image written and reported verified, read back byte-for-byte, and round-tripped through `eepdump` with the vendor atom and GPIO map intact.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A board's identity lives in an EEPROM on ID_SD/ID_SC, and every existing way to write one assumes Linux on the target:
eepflash.shinstantiates a bit-banged i2c-gpio bus and talks to it through /dev/i2c. A board that runs bare metal has none of that, and pulling the part to program it in a socket is not a workflow. The loader is already attached to such a board over serial and already reaches its hardware through rpi-hal, so it is where this belongs.eeprom-writetakes the.eepthateepmakeproduces and programs it;eeprom-readcopies it back off. Wire commands 9 and 10, both shaped like thesd-*ones -- arguments, a status byte, then the existing CRC-checked chunk stream in whichever direction. Both take--address(0x50 by default, what the HAT specification assigns) and--offset. With no--length,eeprom-readreads the 12-byte HAT header first and takes the image length out of it, so a dump is the couple of hundred bytes of atoms rather than 32 KiB of mostly 0xff.The device reads every page back after programming it, and this is not belt and braces. A write-protected part --
WPtied high, which on a board that puts write protect on a solder jumper is a state you will meet -- acknowledges every byte and stores none. Without the read-back this command would report a clean success and leave the EEPROM exactly as it was. That is error code 8, and it is deliberately distinct from 7 (nothing acknowledged the page write) and 10 (the page was written but the part never answered the read that checks it), because those three send you to different parts of the board.The wait between writing a page and reading it back is a fixed delay, not the acknowledge polling the datasheet also offers. Polling was the first implementation and it failed on hardware in a way worth recording: exactly one 32-byte page landed and the rest of the image stayed erased. The poll is issued tens of microseconds after the page write's STOP, which is the same moment the part begins the cycle being polled about -- so it can answer about a cycle that has not started. Waiting out the family's 5ms tWR, rounded up to 6, has no such race, and the read-back that follows is the real evidence a page took, so the wait only has to be long enough rather than exact. The read is retried a few times for a part slower than its own datasheet.
Page size comes from the host because the device cannot know what part is fitted, and getting it wrong is not a clean failure: a page write that runs past the part's page boundary wraps to the start of that page rather than carrying, overwriting data already programmed. The default is 32, the page of a 24C32 -- the smallest part the specification allows -- which divides every larger part's page and is therefore always safe. A 24C256 takes
--page-size 64and programs in half the time.send_chunkedgains an ACK-attempt count. Every other command stores a chunk and acknowledges immediately; this one programs it a page at a time, so a 4 KiB chunk takes seconds before its ACK, and one timeout window had the host resending chunks the device was still working through. The per-chunk ACK is flow control here as everywhere else in this protocol, so the fix is to wait longer for it, not to send ahead.Three protocol tests cover the new framing against the fake device: the image transfer, the verify failure naming write protect, and the header-driven read length.
Note the temporary
[patch.crates-io]in firmware/Cargo.toml, which is the one thing in this repository that assumes anything about what sits beside it on disk. Theeeprom-*commands need rpi-hal'sI2c::<BSC0>::init_id-- BSC0 muxed to GPIO0/1 -- which no published rpi-hal has yet. Until the release carrying it is on crates.io the firmware builds only against a sibling checkout, and CI, which clones this repository alone, cannot build it at all. At that release: raise the version and delete the section. Nothing else depends on the path.Verified against a Pi 2 carrying a HAT with a CAT24C256 at 0x50: an
eepmake -v1image written and reported verified, read back byte-for-byte, and round-tripped througheepdumpwith the vendor atom and GPIO map intact.