Focus on that, and good luck! I have 4 picos 2 that I can destroy (test) as well.
Checking it out now.
Focus on that, and good luck! I have 4 picos 2 that I can destroy (test) as well.
Checking it out now.
Wow, that is an incredibly thorough treatment of OTP.
I may do a minimal port directly into the Bus Pirate firmware to test it out.
always good to have a few scratch monkeys to mount
Just drop the bp-whitelabel.cpp
and bp-whitelabel.h` files as-is into the firmware. Those are the only ones that matter for whitelabel purposes, and they only expose two functions, so the external API surface is small.
I would not recommend migrating the other parts into the firmware. Each of the abstractions (esp. the N-of-M) in the whitelabel file came up due to real needs with fresh, never-used Pico2 boards. The OTP fuses come with errors as-shipped, and they donāt consider errors in the N-of-M rows to be enough to bin the chip. Thus, I do recommend dropping the entirety of the bp-whitelabel.*
files into the firmware.
Sometimes, Microsoftās copilot can be helpful. I hadnāt heard this phrase before, and it said:
It sounds like youāre referring to the concept of āscratch monkeysā in a tech context. In computing, a āscratch monkeyā is a sacrificial system or hardware component used to test software or procedures before deploying them on critical systems. Itās a way to safely experiment without risking damage to important infrastructure.
TIL, huh.
I wholeheartedly agree! This is why I purchased ten Pico2 boards ā¦ I thought Iād burn (pun intended) through more of them. Instead, I just tip-toed carefully enough to avoid most problems. It was slow, but hopefully Ian wonāt waste many (any?) real boardsā¦
thats the story behind that term (also: i donāt touch those AI āassistantsā ever, i hate information without a direct source attribution since that prevents me from blacklisting sus sources. my own code is clean from that stuff, too)
I donāt like it in everything as currently applied and I think the hype is a huge scam and confused why governments are falling for it. In general I am not a fan.
That said, Copilot can sometimes speed my development by 10 to 100x. It sounds insane, but if the wind is blowing in the right direction it can make huge data structures for common file formats (see the image command, that took 5 minutes to get going), itās so useful for understanding how to pass complicated pointers, and the auto complete of variable names makes me feel like I have super memory.
It will not, at least in our code base, actually write code. Some of the code it does try to writes is just awful and pointless. In Python itās very amazing though. Iāve also described web admin interfaces and it scaffolds exactly what I want in HTML, CSS, and JavaScript, complete with nice comments and cursory stuff Iād never bother with for such a project.
It is inconsistent. I do not consider it a tool, because it isnāt repeatable. Iāve found some ways to bait it into doing what I want, but it also changes from day to day so you canāt really learn it.
Itās only as good as you are. You still need to know how all the bits fit together and how to structure things. Iām an okay programmer. In the bitmap structs example - Iāve had previous experience and could see the struts were ok - it saved me an afternoon refreshing myself on specs and hand typing all that stuff in. 10 seconds vs 3 or 4 hours. You see posts about āI made this whole program with AIā, but itās just a little tutorial and not going to get very far.
I have serious concerns about energy use. Period. I think this is one area that will rapidly improve over time, even if the actual quality of the LLMs hits a ceiling (I think weāre getting close).
Iām not a fan of the LLM enshitification of everything, I get the impression the vast majority of people feel the same way. Copilot is hugely flawed, but if you know how to code it can make really time consuming tasks super fast with the benefit of nice formatting, variable names, and comments Iād never take the time to do myself.
Probably my favorite thing is US courts have ruled the output is public domain. This expansion of the copyright free zone is probably the first in my lifetime.
Iām going to have to study the inclusion of CPP in C projects. On my last encounter I had to convert all the c files to have some kind of beginning and end braces. Maybe thatās not needed on PICO and arm GCC?
Edit: also wild that they ship with errors in the fuses!
Iām really curious about the lock issue too.
I am only recommending inclusion of the following two files:
bp_whitelabel.c
bp_whitelabel.h
Those are in straight-up C11, exposing only two APIs for simplicity.
ā¦ Yes, I can confirm that it is possible to include C++ and C files in one project, at least when using GCC. Relevant references / examples
GCC has made statements to (and done the necessary work to) ensure this works. It works on embedded projects also ā¦ but all the embedded C++ rules come into play (lots of compiler settings, special init may be needed if not following the FAQās first answer, etc.). Embedded-safe C++ subset is a whole topic in itself. The external API cannot expose anything C++ related.
But it does work. And there are even techniques to prevent accidental dynamic allocation (e.g., allowing only placement new()), ensuring things are either globals or stack allocated. But generally, view this as C11 with some very minor features allowed. Allowing only features that cause pain when trying to write them in C11 (e.g., type traits, constexpr
, useless static_assert()
restrictions such as strlen("Bus Pirate")
, etc.). Great care must be taken for a firmware doing real-time things.
As an example, they designed OTP rows 0x059 .. 0x05B
to store information in RBIT-3 ā¦ meaning all three rows are encoded as RAW 24-bit information, so as to allow individual bits to be flipped over time, and a majority vote determines the final value of each bit. So long as two rows encode each of the bits as zero at manufacturing, itās āgood enoughā for the vote result to be 0x000000u
. It also is fully functional when wanting a bit to flip ā¦ the same operations apply as any other OTP row ā¦ except that the firmware has to ignore the fact that some extra bits might be set in one of the rows when updating them. So, itās a Read-Modify-Write cycle.
Except that itās really:
Based on the devices behavior, every one of the above steps is necessary to ensure a successful update.
Although Iāve not written it, a similar function / procedure applies for reading and/or updating RBIT-8 data, which is used for some critical data. But, the function would be more complex than my best-of-three:
uint32_t read_RBIT_N(uint16_t start_row, uint8_t redundant_copies, uint8_t required_agreement_count) {
uint32_t valid_raw[8]; // max supported, such as 8 for RBIT-8
uint8_t valid_rows = 0u;
uint32_t result = 0x0u;
// read each row ...
// if successful, save to valid_raw[valid_rows++]
// check if minimum required agreement count rows have been reached
// for each bitmask 0x00800000u to zero....
// count how many valid rows have the bit set to 1
// which also tells how many valid rows have it set to 0
// if neither one hits the required threshold ... return 0xFFFFFFFFu;
// else if more rows set the bit to one, set the bit in the result
// return the result
}
The same process as writing RBIT-3 or 3x_byte encoded data would also apply to RBIT-8 data. Again, not written because nothing we do touches that data. But the concept is the same, and the extra steps for RBIT-8 write is similar to the extension for RBIT-8 read.
Externally, however, the API is simply:
P.S. - The product-line specific whitelabel data can be applied without ever adding the manufacturing data string. No ill effects occur.
Had a bit of downtime, so I started the integration.
@Ian ā¦ The code is now integrated (in my otp_whitelabel_dev
) branch, and builds successfully.
Note that this is just getting the required functions into the build ā¦ the firmware does not call these new functions (to update the whitelabel data and/or manufacturing data string).
Still, the integration was happily pain-free!
Feel free to do nothing with this. Iām still happy to dig deeper in a couple weeksā¦
@ian ā¦ I think itās ready for you to test.
I have not run this on any BP6 hardware. It should work (or at least get far enough along) by loading onto a Pico2 board ā¦ I just ran out of time to check. Once validated that the whitelabel changes are auto-applied, the next step is trying to enable it on real hardware.
The firmware MUST be built with BP_MANUFACTURING_TEST_MODE
defined. Then it will automatically perform the whitelabel process very early in boot.
To single-step through some or all of the whitelabel process, just set the global variable in the ./src/otp/bp_whitelabel.c
file to true at the start of the function (or wherever you want to start single-stepping). This will use input from RTT ā¦ so no USB connection required (just power).
hacks/rtt_debugging.md
documents setup, if a refresher is helpful. Works with the Raspberry Pi Debug Probe Kit, so no expensive things required ā¦ including providing the input needed to single-step.
Thank you so much.
This is our first week back from Spring Festival, so manufacturing is spinning back up and Iāll be a little slower than usual.
The BP6 manufacturing string is ā5XLā
How on earth was it working without that missing bp5xl-rev0.c? Good catch. I will push some further cleanup to the platform folder.
It compiles. I will run it after some further study.
The index.html links to buspirate.com.
HiZ> otpdump
Row 0x000: DEB0 === DEB0 === B0 DE [2C] (..)
Row 0x001: C927 === C927 === 27 C9 [17] ('.)
Row 0x002: 24BC === 24BC === BC 24 [32] (.$)
Row 0x003: 0507 === 0507 === 07 05 [02] (..)
Row 0x004: 3C48 === 3C48 === 48 3C [33] (H<)
Row 0x005: 25A4 === 25A4 === A4 25 [11] (.%)
Row 0x006: F206 === F206 === 06 F2 [0D] (..)
Row 0x007: F0C5 === F0C5 === C5 F0 [22] (..)
Row 0x008: 1906 === 1906 === 06 19 [0D] (..)
Row 0x009: E962 === E962 === 62 E9 [0A] (b.)
Row 0x00A: 2A92 === 2A92 === 92 2A [0C] (.*)
Row 0x00B: 1093 === 1093 === 93 10 [31] (..)
Row 0x010: 5C1B === 5C1B === 1B 5C [30] (.\)
Row 0x011: EA45 === EA45 === 45 EA [03] (E.)
Row 0x018: 001E === 001E === 1E 00 [2D] (..)
Row 0x036: 7104 === 7104 === 04 71 [3E] (.q)
Row 0x037: 59FA === 59FA === FA 59 [3C] (.Y)
Row 0x059: 7733 === 7733 === 33 77 [40] (3w)
Row 0x05A: 7733 === 7733 === 33 77 [40] (3w)
Row 0x05B: 7733 === 7733 === 33 77 [40] (3w)
Row 0x05C: 00C0 === 00C0 === C0 00 [27] (..)
Row 0x0C0: 1209 === 1209 === 09 12 [18] (..)
Row 0x0C1: 7332 === 7332 === 32 73 [30] (2s)
Row 0x0C4: 230A === 230A === 0A 23 [32] (.#)
Row 0x0C5: 230C === 230C === 0C 23 [31] (.#)
Row 0x0C8: 1B08 === 1B08 === 08 1B [07] (..)
Row 0x0C9: 1F08 === 1F08 === 08 1F [28] (..)
Row 0x0CA: 230C === 230C === 0C 23 [31] (.#)
Row 0x0CC: 1016 === 1016 === 16 10 [18] (..)
Row 0x0CD: 140D === 140D === 0D 14 [1F] (..)
Row 0x0CE: 230C === 230C === 0C 23 [31] (.#)
Row 0x0D0: 7468 === 7468 === 68 74 [1C] (ht)
Row 0x0D1: 7074 === 7074 === 74 70 [3B] (tp)
Row 0x0D2: 3A73 === 3A73 === 73 3A [10] (s:)
Row 0x0D3: 2F2F === 2F2F === 2F 2F [03] (//)
Row 0x0D4: 7562 === 7562 === 62 75 [33] (bu)
Row 0x0D5: 7073 === 7073 === 73 70 [1B] (sp)
Row 0x0D6: 7269 === 7269 === 69 72 [1E] (ir)
Row 0x0D7: 7461 === 7461 === 61 74 [38] (at)
Row 0x0D8: 2E65 === 2E65 === 65 2E [27] (e.)
Row 0x0D9: 6F63 === 6F63 === 63 6F [1D] (co)
Row 0x0DA: 2F6D === 2F6D === 6D 2F [2D] (m/)
Row 0x0DB: 5042 === 5042 === 42 50 [28] (BP)
Row 0x0DC: 5F5F === 5F5F === 5F 5F [1E] (__)
Row 0x0DD: 4F42 === 4F42 === 42 4F [07] (BO)
Row 0x0DE: 544F === 544F === 4F 54 [05] (OT)
Row 0x0DF: 7542 === 7542 === 42 75 [19] (Bu)
Row 0x0E0: 2073 === 2073 === 73 20 [1D] (s )
Row 0x0E1: 6950 === 6950 === 50 69 [39] (Pi)
Row 0x0E2: 3872 === 3872 === 72 38 [3D] (r8)
Row 0x0E3: 7542 === 7542 === 42 75 [19] (Bu)
Row 0x0E4: 2073 === 2073 === 73 20 [1D] (s )
Row 0x0E5: 6950 === 6950 === 50 69 [39] (Pi)
Row 0x0E6: 6172 === 6172 === 72 61 [07] (ra)
Row 0x0E7: 6574 === 6574 === 74 65 [2B] (te)
Row 0x0E8: 3620 === 3620 === 20 36 [2A] ( 6)
Row 0xF80: 3F37 === 3F37 =?= 3F 3F [3F] (7?)
Row 0xF81: 1505 === 1505 =?= 15 15 [15] (..)
Row 0xF83: 0504 === 0504 =?= 04 04 [04] (..)
Row 0xF85: 0504 === 0504 =?= 04 04 [04] (..)
Row 0xFFD: 0504 === 0504 =?= 04 04 [04] (..)
Row 0xFFF: 1410 === 1410 =?= 14 14 [14] (..)
Nothing at the 0xf87 for locking OTP 0xc0 to 0xff.
I believe what we want is 0b010101, but repeated 3 times: 0x151515? I will test this and the manufacturing ID shortly. Then check out the CERT.
// offset 0x1B, chars 0x08: āBP__BOOTā
Weāve got that extra _, maybe it could be X and 6 so it is clear from the bootloader what to load?
EDIT: I see it is trying to lock, but only after manufacturer string is written
if (!write_otp_byte_3x(0xF87, 0x15u)) {
Edit: Are these the remaining datafields?
// NOTE: Reserved for product string:
// // Rows 0x0e8 .. 0x0eb (space character + 7 additional characters maximum)
// NOTE: Reserved for manufacturing data:
// // Rows 0x0ec .. 0x0ff (40 characters maximum)
5.7.2. USB Device Strings
ā¢ MANUFACTURER (default āRaspberry Piā, max-length 30 UTF-16 or ASCII chars)
ā¢ PRODUCT (default āRP2350 Bootā, max-length 30 UTF-16 or ASCII chars)
5.7.6. UF2 INFO_UF2.TXT File
ā¢ BOARD_ID (default āRP2350ā, max-length 127 ASCII chars)
Ah, this is the manufacturing string?
Oops! Thatās gotta be a copy/paste error on my part. Good catch! Edit in platform file?
Yes, I think thatās rightā¦ at least itās what I also calculated. NOTE: to write the manufacturing string, youāll have to NOT apply the soft-lock to OTP page 3 ā¦ which is currently done right after whitelabel. Simple enough ā¦ just skip that pageā¦
This is your product line, you can choose to do that. But, itās a very limited space (8 characters for sure, 11 characters may be possible). Isnāt the model already in the INFO_UF2.TXT file? In combination with the manufacturing string, that file would then have all the information needed to identify what firmware to load, as well as user-accessible traceability data.
Yes, precisely. The BOARD_ID provided a simple place to expose a variable-length string of arbitrary data via the bootloader. That function (not currently used) writes the string to the reserved 20 OTPs (<= 40 chars), then sets the WHITELABEL_INFO field (row offset 15) to the STRDEF indicating the stringās offset and length, and then modifies the USB_BOOT_FLAGS to indicate this field is now valid. Next time you boot, that string will magically appear in that text file.
At least, it did in my tests.
[[EDIT - P.S. - I note you excluded the header otp/bp_otp.h
for BP5 devices. I had intended to allow the ECC calculation and similar (which donāt touch the board) to be available. Itās OK either way ā¦ can always re-enable if exposing those cmds via the terminal later.]]
First, I verified that the soft lock works.
Then, programmed without soft lock:
programming manufacturing data
Whitelabel Debug: found USB BOOT FLAGS: 407733
Whitelabel Debug: WHITE_LABEL_ADDR points to row index 0x0c0
Whitelabel Debug: Static portion of the strings look ok
Whitelabel Debug: Writing the manufacturing string data
Whitelabel Debug: Writing row 0x0ec: 0x7542 (Bu
)
Whitelabel Debug: Writing row 0x0ed: 0x2073 (s
)
Whitelabel Debug: Writing row 0x0ee: 0x6950 (Pi
)
Whitelabel Debug: Writing row 0x0ef: 0x6172 (ra
)
Whitelabel Debug: Writing row 0x0f0: 0x6574 (te
)
Whitelabel Debug: Writing row 0x0f1: 0x3620 ( 6
)
Whitelabel Debug: Writing row 0x0f2: 0x5220 ( R
)
Whitelabel Debug: Writing row 0x0f3: 0x5645 (EV
)
Whitelabel Debug: Writing row 0x0f4: 0x3220 ( 2
)
Whitelabel Debug: Writing row 0x0cf: STRDEF 2c12
Whitelabel Debug: Updating the USB_BOOT_FLAGS to mark the manufacturing string as valid
Whitelabel Debug: Writing rows 0x059ā¦0x05B: 0x407733 ā 0x40f733
Whitelabel Debug: Setting PAGE3_LOCK0 (0xF86) to 0x3Fu
(no keys; read-only without key)
Whitelabel Debug: Setting PAGE3_LOCK1 (0xF87) to 0x151515
(read-only for all three)
Whitelabel Debug: Manufacturing string successfully applied and locked down.
Row 0xF86: 3F37 === 3F37 =?= 3F 3F [3F] (7?)
Row 0xF87: 1505 === 1505 =?= 15 15 [15] (ā¦)
The protection bits are indeed set.
programming manufacturing data
Whitelabel Debug: found USB BOOT FLAGS: 40f733
Whitelabel Debug: WHITE_LABEL_ADDR points to row index 0x0c0
Whitelabel Debug: Static portion of the strings look ok
Whitelabel Error: Old manufacturing STRDEF (0x002c12) does not match new (0x002c18)
Programming again with different manufacturing string fails.
Board-ID: Bus Pirate 6 REV 2
That is indeed the string I set
This is lovely functionality, but Iām uncertain what to use it for at this point.
In the cert thread there was talk of a unique manufacturing ID, but then we doubled back around to relying on the OTP unique ID.
To test it out, I burned āBus Pirate 6 REV 2ā, but that could probably be better added to the model string if we want to include the REV. The board ID is the unique RPi ID now, would it make sense to burn that as part of the initial whitelabeling?
I would propose adding the initial whitelabeling to the self-test, and printf-ing at least any failure messages so they can pass on debugging info from the factory to us.
I believe the next step is to add the entry table you prototyped, starting from the last record and moving forward. This will include for instance the location of the cert.
Iām going to play around with this now. Especially:
static_assert(ARRAY_SIZE(_product_string) <= USB_WHITELABEL_MAX_CHARS_BP_VERSION + 1);
If the OTP string (5XL, 6 REV2) is more than one character a static_assert seems to be happening and the firmware freezes during startup. I have not located it yet, but I have installed a cert to otp and can retrieve and verify it.
Hope all is well @henrygab - Iāve gone about as far on OTP as want to without getting your feedback when your busy period is over. I also need a break from it
Every test is walking on eggshells, as I watch the Pico bodies drop.
Hereās some markdown formatted draft docs so you can see what I did in a consolidated place, and Iāll move these to the actual docs later.
UF2 Bootloader v1.0
Model: Bus Pirate 6
Board-ID: F9:04:9D:32:5E:76:45:4F
Summary of locations and values from above
The first usable page of OTP (64 rows, 128 bytes with ECC) contain clear text strings describing the hardware.
Strings begin with a token and end with null termination.
:0x01:Bus Pirate:0x00::0x02:6:0x00::0x03:2:0x00::0x04:CN:0x00::0x05:Where Labs LLC:0x00::0x06:2025-02-16 13:01:03:0x00:
Example entry substituting HEX numbers for decimal byte values.
This information is actually extracted from the x509 cert during cert programming. It is redundant to the information in the x509 cert, but I would prefer not be required to parse the cert to get at that info.
There are 128bytes in the page, any other info that should go in there?
The next page is a x509 certificate verifying the authenticity of the hardware against the unique serial number in each RP2350 chip.
The certificate is signed with the Bus Pirate private key and burned during manufacturing along with the whitelabel data.
A public key can be used to validate the authenticity of the certificate. An attacker could attempt to change the public key through a sneaky pull request on GitHub. To protect the public key from modification, the key is checked into a separate git repo and is added to the Bus Pirate firmware as a submodule.
:::info
Why not depend on the certificate for the board specifications? If there are homebrew Bus Pirates (say on a Pico2 board) or clones, they will not have a valid certificate. In that case the otp
command can be used to burn the correct Info strings and the firmware will be able successfully identify the board.
The firmware has sane fallback values if the OTP is not programmed, but the user will need to pay extra attention to the firmware loaded on their board. The firmware will not be able to automatically detect if it is running on the correct hardware version, or accommodate minor revision differences, without attention from the user.
:::
typedef struct _OTP_DIRECTORY_ITEM {
uint16_t EntryType; // with 0x0000 defined as "end of list"
uint16_t StartRow; // row where that entry is stored
uint16_t RowCount; // count of consecutive rows
uint16_t CRC16; // Validates the prior three entries.
} OTP_DIRECTORY_ITEM;
Directory entries are a list of the contents of OTP and the location/length of each item.
The code (otp.c) currently looks backwards for 2 pages (64 row/4 =16 *2=32 max entries). This can be increased as needed as long as we leave free pages in front of it.
An page should probably be hardware locked when full.
enum {
OTP_DIRECTORY_ITEM_TYPE_END = 0x0000,
OTP_DIRECTORY_ITEM_TYPE_USB_WHITELABEL = 0x0001,
OTP_DIRECTORY_ITEM_TYPE_DEVICE_INFO = 0x0002,
OTP_DIRECTORY_ITEM_TYPE_CERT = 0x0003,
};
So far we have identified four entry types.
OTP_DIRECTORY_ITEM directory_item[] = {
{OTP_DIRECTORY_ITEM_TYPE_USB_WHITELABEL, 0xd0, 1*64, 0},
{OTP_DIRECTORY_ITEM_TYPE_DEVICE_INFO, 0x100, 1*64, 0},
{OTP_DIRECTORY_ITEM_TYPE_CERT, 0x100+64, 7*64, 0},
};
The location of the current directory items is fixed and known by the firmware. These are key pieces of info. The directory page will be hardware unlocked so we can keep adding entries, if it is corrupted we still want to be able to find these info and cert (and whitelabel).
The order shown above is not quite what happens in the current code, I changed my mind about some of the locations.
Wow! Itās great to see so much progress this past week.
Note 1: I have not reviewed any recent commits / code.
Note 2: I will be treating the cert data as an opaque blob of data.
I think it was a mistake to encode the length of the data in the directory items as .RowCount
instead of actual number of bytes.
@Ian ā Do you approve this change to the OTP directory item structure?
An early statement was that whitelabel starts at 0x80
, and later statements and code show it as 0xD0
. The first available page for post-manufacturing use per datasheet was 0xC0
. For the code I tested, the whitelabel structure was at 0xC0
, although the STRDEF
s were stored starting at 0xD0
. This may be where the thought of 0xD0
came from?
Thus, I think that OTP_DIRECTORY_ITEM
entry needs to change from starting at page 0x0D0
to starting at page 0x0C0
. Easy enough!
There is no need to worry about corruption of the directory, with just a couple small changes:
Change each individual āINFO PAGEā item into its own unique OTP_DIRECTORY_ITEM_TYPE_xxx
enumeration.
Also define OTP_DIRECTORY_ITEM_TYPE_BLANK
ā¦ whose primary use is to fill the remainder of the page, so that page can be locked, while still allowing for appending more entries.
Keep a single, well-tested API for reading OTP data. The API will also be much easier to use than parsing the variable-length structure. Addresses your concern about the directory being corrupted, by allowing blank entries to fill the page and thus locking the manufacturing-critical data.
TODO:
typdef enum _OTP_DIRTYPE {
OTP_DIRTYPE_END = 0x0000,
OTP_DIRTYPE_USB_WHITELABEL = 0x0001,
OTP_DIRTYPE_CERT = 0x0003,
OTP_DIRTYPE_BLANK = 0x0004,
OTP_DIRTYPE_DEVICE_NAME = 0x0005,
OTP_DIRTYPE_DEVICE_VERSION = 0x0006,
OTP_DIRTYPE_DEVICE_REVISION = 0x0007,
OTP_DIRTYPE_PRODUCTION_LOCATION = 0x0008,
OTP_DIRTYPE_MANUFACTURER = 0x0009,
OTP_DIRTYPE_PRODUCTION_DATE = 0x000A,
} OTP_DIRTYPE;
Will look more in the next couple daysā¦ just wanted to give initial thoughts ā¦ which might be wrong / need adjustment after I review the code.
And ā¦ WOW! So much in just a week!