Please continue to ship lighter weight firmware images that leave 128-200K free space on the SPI flash as an alternative to the network recovery images
I've been working with the PFTF (https://github.com/pftf/), which provides a UEFI/ACPI compliant firmware for a few RPi models. One of our ongoing problems with this platform is implementing the UEFI variable store. The variable store is a standardized way for an OS to save non volatile variables used to control OS and firmware interactions. For example, its can be used by an OS installer to tell the firmware where a newly installed OS may be found, and how to boot it.
For a couple years now, we have been storing these values on a SD card as part of the firmware image we create. This has a few problems, starting with the requirement that an SD card is actually in the machine. Even when an SD card exists, we want to allow users of the firmware to potentially use the SD card as a rootfs/etc. This means that the OS running on the RPi is managing the SDHCI peripheral while its running. Meaning that we cannot actually perform variable updates when the OS is running. This results in an endless supply of user bug reports, for which the root cause is the lack of variable persistence after we hand off the SD to the OS.
So, as I understand it, there is a small SPI(?) flash available on these platforms, and it has a few tens/hundreds of K currently free. It would be nice if we had a simple interface (the existing mailbox interface can suffice), to read and write some raw data on that flash. Without a lot of details about the flash/etc I might propose something along the lines of:
` READ_STATE -> returns total available capacity, and a convenient page size for reading/writing the available flash, along with an "all writes complete flag"
READ_PAGES -> takes a buffer, buffer size (multiple of page size), and starting page offset. Fills the buffer with data from the flash.
WRITE_PAGES -> takes a buffer, buffer size (multiple of page size), and starting page. Returns immediately, and begins to async write the buffer to flash. The buffer will remain valid until a future READ_STATE is made which returns "all writes completed". Multiple WRITE_PAGES with unique buffers may be performed back to back, and will be serialized with READ_STATE. If an erase is required before the write, that is handled transparently by the firmware. `
I'm not sure if a "BLOCK UNTIL WRITES COMPLETE" that simply fails to complete either the READ_STATE or the WRITE_PAGES mailbox calls until the WRITE(s) are completed is useful yet. That may avoid hammering READ_STATE in a polling loop. I've also considered if having some kind of ownership reservation scheme is useful for allocating blocks for multiple users, but I can't come up with a legitimate use case. Mostly, because I assume the existing RPi start/etc firmware will be the only other user and the "total available capacity" would reflect its left overs rather than the actual flash capacity.
Comments?
Thanks for considering this.
The ‘spare’ space isn’t spare because it’s required for other bootloader features in the future. It’s also not possible to access the SPI without causing audio glitches once the firmware is running and realistically we aren’t going to add new APIs like this to the firmware.
However, you can access a read only copy of the EEPROM config via device three under the ‘chosen’ node. Variables in the ‘none’ section in a suitable namespace should be safe. The max size is about 4KB but we could increase that in the future. Since there’s already tools for the OS to update this and migrate settings this feels like the right place to store config data.
Well, i'm not sure we care about the audio glitching because at the moment the ACPI side doesn't expose any audio devices (although there is BT and HDMI). The topic of audio glitching came up some months ago, and I think at the time the discussion ended up concluding it was probably mostly a non issue. These variables tend to remain static except during installs/reboots/etc so the likely-hood of any audio applications running was really low when they were being updated. The OS interaction actually is with an in memory copy, so the read calls would strictly be done by the firmware during early boot. So, the user would have to be playing something while simultaneously doing some kind of bootloader/etc updates. AKA very infrequent if it happened.
I will look closer at how to update the EEPROM data at runtime, although 4k is probably a bit too small. IIRC we have 64k plus a backup reserved now, but its mostly just key value pairs with paths and UUIDs, secure boot keys, etc. So its not actually that large, but certs and keys can easily be 4k by themselves. Do you have a guess how large this could be?
Sort of the point of the available space bits, in the READ_STATE call would be that it could return "0" if needed, although how to handle size changes would probably be more complex.
Although some of the random ideas have been to use the space as a journal and apply it during boot to the SD/whatever is storing the full copy. That helps, but isn't ideal, although it potentially makes a few K of space useful if it can be persisted at runtime.
It's also worth noting we could, in a future revision of the board, reduce the size of the EEPROM on the board, for BOM reasons, so "spare space" could suddenly turn in to "No space".
The SPI CS pin is muxed with audio PWM so as soon as the audio LDO is enabled you will get noise out of the analog audio port.
I think 16K might be a reasonable maximum for the config file.
It really sounds like this data should be stored on the boot device. The GPU firmware already does this with config.txt, dt-blob etc so there's no need for this to be in SPI
Does the GPU have a back channel to the SD? Because. that is basically how we are handling the problem today (storing it on SD) but it conflicts with an OS trying manage the storage. If the GPU can access it through a simplified interface/etc then that would work too (at least for the case where an SD card exists) because we can pass buffers/disk offsets that need to be flushed.
There's no back-channel. start.elf loads the kernel/arm-stage plus any dt-overlay files. Before starting the ARM it amounts all SD, USB, Network file-systems and resets the hardware so that it's available to the OS. The same is true of SPI because the OS is free to change pinmux, use flashrom etc.
Ok, that is the assumption I was running under WRT the SD card.
OTOH, I'm fairly confident that UEFI/ACPI will reserve control over pinmux functions, so that won't be a problem. It is quite likely we will retain control over the SPI as well (as we currently do) as none of this is really the business of a general-purpose OS. That is the advantage of doing much of the base platform configuration in the firmware, the OS doesn't need to know about it, and any related functions frequently have standardized interfaces that call the firmware to perform the action. Like for example, capsule updates can be used to upgrade firmware images. The OS provides the image, but the firmware installs it, the details of what is being upgraded/etc is opaque to the OS. From that, you allow a dozen+ OS's the ability to upgrade the firmware without having to write platform-specific code for each one.
We have considered reserving the entire SD for firmware use, but that is a bit of a lift, given how many users expect to put the entire OS on SD.
So, its been a few months. Last year I implemented a direct interface to the SPI flash from edk2. That code walks the existing flash volume and if there appears to be space it utilizes the free space at the end of the volume for the FaultTolerantWriteDxe which assures SPI flash writes are properly journaled/persisted over power failure. During runtime the firmware has control over the GPIO pin mixing so it flips those pins/writes the SPI as needed (which is actually fairly rare in real use). The result is that the UEFI implementation behaves as one would expect and honors the usual set of standardized UEFI variables, which control things like installing/booting from multiple sources, rebooting to the firmware, hibernating, secure boot key enrollment, etc.
Anyway, I would close this request except for the fact that the latest beta firmware utilizes too much space for this to work properly (so it ends up falling back to the old method of persistence only when the firmware is active, against the Sd/etc card). Users running UEFI (and to a lesser extent uboot) don't need the lower level firmware providing network boot/restore services because that is one of the functions provided by EDK2 which not only provides PXE, but HTTPS secure boot over the network. Meaning for example. the machine can direct boot the fedora installer from the public fedora mirrors, validating the HTTPS certificate chains, and if the image is signed validate that against the common MS secure boot signing keys as well.
AKA, it follows the industry standard for HTTPS (secure) boot.
So, my request now, is, is it possible to continue to ship a lighter weight firmware that doesn't include this functionality (or for that matter, much of the other functionality is somewhat redundant as well too) or strips some of the other functionality out as well to assure that somewhere north of 128K of SPI space remains free? Otherwise, we end up encouraging people to utilize older pi firmware stacks, (or end up pushing the librepi project to finish the rpi4 port). Neither of which are particularly helpful.
Creating multiple bootloader variants is too much overhead and isn't going to happen. That's one reason why we've always said that the SPI EEPROM space is not available for general usage and there are no APIs to store arbitrary user data.
The SPI EEPROM is simply and augmentation of the 2711 bootrom.
The latest beta bootloader release (which will be stable/default very soon) supports loading of signed boot.img files over HTTP so you could load EDK using this mechanism. A future release might allow an additional CA to be stored so long as it doesn't require any additional crypto algorithms to be added.
Custom bootloaders (e.g. for SATA boot) should be loaded from local storage and should store state there e.g. on a separate partition or in a binary blob in an boot partition.