Asrock X370 Killer Sli/AC Coreboot Port
Posted as-is, incomplete. Original date 2023-08-20
Next post will contain substantive information and hopefully more complete code. You can see the port in its current state here, but the process is much more interesting than the code.
Long time no post. Life has been a bit crazy lately, as I’m sure everyone in the USA is aware of in general, plus some big events in my own personal life.
I recently upgraded my PC, from an Asrock X370 Killer Sli/AC based machine to an ASUS Pro WS X570-ACE with a Ryzen 5950X. As such, the previous board is now free for tinkerage.
A quick rundown on the specs will inform you it has a Nuvoton NCT6779D SuperIO, who’s serial port is routed out to a COM1 header, which is thankfully already a first class citizen in Coreboot, and supports Picasso CPUs, which are (at least in the form of FP5 mobile packages) also supported in Coreboot.
The basic bootflow for desktop AM4 machines is as follows:
- Power on
- An immutable bootrom on the PSP (an ARM processor inside the Ryzen CPU)
searches a number of predefined offsets in the SPI flash for the magic bytes
0xaa55aa55that starts its ‘FET’ (Firmware Entry Table), which points to various firmware modules for the PSP itself, Microcode updates, and configuration files. - The bootrom uses this table to load public keys and firmware blobs, uses the keys to verify the firmware, and then executes the firmware.
- The PSP uses the firmware to initialize itself. During this point in the boot process release firmware builds do not output over serial (in fact, I’m not certain that it can output over the SuperIO’s serial as a general rule), but it does produce POST codes (more on this later).
- Once its done intializing itself, it will then train the x86 ram, a complicated process in of itself. At the end of this process, the machine is ready to start executing x86 code.
- At this point the PSP looks for a file in the FET with the type
0x62, known asAMD_BIOS_BINin coreboot, loads it to a specified address, and releases the x86 cores to start executing it. In vendor firmware, this usually takes the form of a UEFI filesystem, occasionally zlib compressed; in coreboot this is thebootblock.elffile, as processed by theamdcompresstool, stored in cbfs asapu/amdfw. - From this point onward bootup takes a more normal route.
This is a bit oversimplified, but it gets the point across.
One caveat is, due to the fact that any given AM4 board can usually support any AM4 cpu, the above mentioned FET includes many ‘directories’ which are tied to an internal PSP id (not readable without some extreme trickery) to select the correct firmware payloads, so unless you already know the PSP id for your cpu, you can’t tell by looking at the firmware which gets executed.
Fortunately, the folks over at PSPReverse have developed some tools to figure it out. PSPTool can, when fed a bios image, list all the directories in the FET (it cannot, as of this writing, handle coreboot.rom images) and give you a general idea of what’s there. Coupled with PSPTrace, and a logic analyzer trace of the bootup, it can show you exactly which PSP Directories are accessed.
Initial ideas
We already have a similar situation with regards to a cpu inside a cpu requiring
specific firmware to boot, that being modern intel platforms’ ME firmware.
My idea was to use the above-mentioned PSPTool to extract the PSP blobs from
the vendor firmware and insert them into the coreboot.rom, as I knew the used
blobs from the output of PSPTrace. This can be done by creating a fw.cfg file
along the lines of src/soc/amd/<soc>/fw.cfg in the coreboot tree and setting
CONFIG_AMDFW_CONFIG_FILE to point at it. This ‘works’ in that it incorporates
those blobs into the rom.
Difficulties
Producing a minimal source tree for the board (modeled after the amd/mandolin and google/zork trees, which are Picasso based Ryzen machines supported by coreboot) and using the vendor blobs, while producing a valid rom, provided me with no reasonable feedback over serial, and would reboot very shortly after power on.
Debugging
Without serial output, I had to look for other avenues of debugging. As the board in question lacked a 7seg display (more on that later) to display POST codes, I had to find an alternate means of getting them. After a few abortive attempts at using the Raspberry Pi Pico’s PIO subsystem to make a rudimentary POST card, a friend on IRC (thanks swiftgeek!) pointed me to a project which existed that did exactly what I needed (or so I thought), MrGreen’s PicoBiosPostCodeReader.
While it did work, and pulled the POST codes from IO port 0x80, there was
a new caveat: modern systems no longer use single-byte POST codes, instead posting
four-byte codes across ports 0x80-0x83. A discussion with the creator about
my needs led to him creating a branch in his repo
which grabbed all the data across all 0x8X addresses. The format of the output
is data: 0xXXYYzzzz, where XX is the data, YY is the IO Port (Nibbles reversed
but that’s no big deal; so 08 instead of 80), and zzzz is LPC related
data that’s irrelevant to our use.
A bit of post-processing with sed results in data in this form: "XX","YY",
which I then imported into libreoffice calc. As a general rule, the device would
post data to ports, least significant byte first, so each ‘full’ post code comprised
four lines of the post-processed csv. A bit of calc formula magic later and I
had compiled the data into a table like this:
| data | addr | 0x83 | 0x82 | 0x81 | 0x80 | POST | Code Meaning |
|---|---|---|---|---|---|---|---|
a0 |
08 |
00 |
00 |
00 |
a0 |
000000a0 |
<nothing> |
00 |
18 |
00 |
00 |
00 |
a0 |
000000a0 |
<nothing> |
00 |
28 |
00 |
00 |
00 |
a0 |
000000a0 |
<nothing> |
ee |
38 |
ee |
00 |
00 |
a0 |
ee0000a0 |
BL_SUCCESS_C_MAIN |
The bytes written to 0x83 on a full four-byte POST code generally indicate
the ‘namespace’ of the code. 0xEExxxxxx codes come from the bootloader,
0xEAxxxxxx codes come from the ABL firmware, and 0xB0xxxxxx codes come
from AGESA. For my benefit I bodged in a 0xCBxxxxxx prefix for POST codes
coming from the generic parts of Coreboot.
Attached is an ods1 file with my current dumps (I went ahead and grabbed the POST
codes for the stock firmware to have a baseline to work against).
During my investigation, I came across some post codes that ‘looked weird,’
and always had the following format 0x5f5354xx, where xx was sequentially
written to port 0x80 without updating the other ports’ contents. Curiosity
got the best of me, so I google it and found it to be how AMD’s Simnow console worked.
Basically, they write _STR to ports 0x80-0x83, then the message, one character
or byte at a time, to 0x80, and then the message is terminated with _END across
all four ports again.
-
nevermind, see future posts. ↩
