Join GitHub today
Universal BIOS Recovery console for x86 PCs
Fetching latest commit…
Cannot retrieve the latest commit at this time.
|Failed to load latest commit information.|
******************************************************************************* * UBRX README * ******************************************************************************* UBRX is a Universal Bootblock Recovery serial console utility, targeted at x86 platform BIOSes, such as the one found on most AMD or Intel based PCs. It aims at providing a universal way of performing system recovery operations such as flashing a BIOS or running a low level debugger, for x86 systems, through the provision of a serial console. This mode of operation is also called "panic room" mode. The main goal of UBRX is to provide a base that is hardware agnostic, meaning for instance that the same UBRX binary can provide a recovery console regardless of the CPU or chipset being used. In practice, this means that you should be able to pick an UBRX BIOS from one machine, put it in another, and be able to enter the UBRX console to reflash the BIOS payload without having to change anything, even if the CPUs, chipsets and Super I/O of the 2 computers are entirely different. The advantage we see in such a feature, besides the advantage of having a single codebase, is that it can help with coreboot development for platforms that are not yet supported by the project, through the provision of a recovery and development console that should work out of the box, and where a development BIOS can easily be tested. another advantage has of course to do with in-situ BIOS recovery for any system relying on an UBRX enabled BIOS. Current Status -------------- UBRX is currently alpha software. At this stage to be fully useful, UBRX is missing parts such as CAR (Cache As RAM) script initialization examples as as well as Y-modem upload. Limitations ----------- Because of the diversity and history of the x86 PC platform, it should be fairly obvious that there exists some restrictions with regards to our "universal" qualifier. They are currently as follow: * The motherboard must have a physical serial port, provided by a Super I/O chip. Console over USB-RS232 adapters or legacy-free hardware is not supported. * Only AMD and Intel x86 CPUs are supported for the time being. * The CPU must support the MMX instruction set (Intel Pentium MMX or later, AMD K6 or later). This is because we use MMX for stack operations. * Only Super I/O chipsets conforming to the ISA PnP specifications, and starting in PnP mode on reset are supported (except if VMware compatible) * Some non widespread PnP Super I/O chips with complex config mode (eg. IT8671) have voluntarily been dropped. * Powering up of Super I/O Logical Devices is not currently conducted. We assert that UARTs are powered up by default (but may review that assertion). * With regards to Super I/O chips that require extra configuration from the Southbridge for LPC bus access, only Intel ICH# (any version that requires it) and AMD SB6x0-SB9x0 are supported. The limitations above are unlikely to be much of an impact, unless you have a an old system. Moreover, these limitations are mostly a result of a design choice (or lack of datasheets in the case of nVidia chipset support), and can be addressed in future versions of UBRX. Build Instructions ------------------ To build UBRX you need a GNU C compiler toolchain that can produce x86 code: * On x86 Linux (32 or 64 bit), the standard toolchain should do. Cross compilation should also work for non x86 architectures. * On Windows, either of cygwin, MinGW32 or MinGW-w64 will do. * On OSX, you will need to install as proper GNU toolchain as XCode cannot be used. To compile, simply invoke: make The above will build a 512 KB BIOS ROM that only contains the UBRX bootblock. To build a BIOS ROM of a different size, you can either invoke 'make' with one of the 128k, 256k, 512k, 1m or 2m options. Or you can define a ROM_SIZE environment variable to one of these parameters. The size of a bootblock, which currently defaults to 8K, can also be set through a BB_SIZE environment variable or by editing the Makefile directly. The generated file is called 'bios.rom', which you can then flash directly onto a BIOS chip. In case this needs to be pointed out, remember that this BIOS does not provide anything, not even an actually functional console, so you MUST have means to restore your BIOS, with an external programmer, before you attempt to flash an UBRX one. Please consult the Makefile to find out how you can also invoke the building and flashing of target specific UBRX BIOS ROMs. Build options can be modified in "config.inc". Testing ------- Once UBRX has been flashed onto your BIOS chip, you should connect one of its serial port to another computer with a Null Modem cable, open a console (115200 bauds, 8N1, XON/XOFF a.k.a. "Software" handshaking) and then power up the machine with the UBRX BIOS while pressing the Space Bar in the console window. If detection of the serial port was successful, you should end up with a prompt where recovery commands are available. Since UBRX can easily be tested on VMware, we encourage first running a test there, before trying on real hardware. For more information on VMware testing you can check http://pete.akeo.ie/2011/06/crafting-bios-from-scratch.html. Usage ----- Once in console mode, the typical usage scenario is as follows: 1. User enters script mode ('s') to set up XIP (Execute in Place) on the ROM flash. See the ####_xip.txt for an example of such a script. This is done by mapping the UBRX BIOS region (FE000-FFFFF for an 8 KB bootblock) as WriteBack cache in the MTRRs, enabling caching, and then triggering XIP with a far ret instruction (which is done transparently with '%') The reason one wants to have XIP early is that, even with a very aggressive XON/XOFF policy and a 16 byte 16550 buffer, uncached code running from a slow flash chip will result in serial characters being dropped, even at lower baudrates, which wrecks havoc on the ability to copy/paste scripts. Serial transfers are also much *slower* without XIP. 2. User continues cache initialization by enabling WriteBack caching for L2-Unified as well as L1-Data, respectively for executables and stack. The reason L2 cache needs to be enabled is that the L1-Instruction cache is separate from L1-Data and non writable, therefore any code that is uploaded must reside in L2-Unified (which serves for both instruction and data). Once L1 and L2 cache are initialized (which would tipically include a memory fill instruction such as '<' or '>' so that subsequent attempts to read/write to RAM are not carried out), the user provides the base address for uploads, in EAX and the bade address for the stack in EBX. 3. With cache and stack setup, the user can now invoke the Y-modem upload command ('u') which transfers a binary executable into the L2-Unified cache for execution. Note that the Y-modem upload requires a working stack, and of course a working base address to upload to. 4. Once the executable has been transferred, simply invoke the 'r' command to run it. The uploaded executable can of course make use of the stack and should end with a far ret instruction to return to the UBRX console. Be mindful that the program starts in real address rather than protected mode. Both steps #1 and #2 are CPU specific, and therefore must be carried out in accordance with the CPU specifications with regards to MTRR access, total L1 and L2 cache available, etc. For more details on the available UBRX commands, see the USAGE text file. Known issues ------------ * As long as you have not enabled XIP (see above), you are likely to lose characters when copy/pasting scripts. This is due to uncached flash ROM being very slow to access, and is resolved as soon as XIP is setup. * When using com0com with VMWare, the baudrate you select doesn't seem to apply and 115200 bauds is being used regardless. Detection primer ---------------- The paragraphs below highlight the generic Super I/O and 16550 UART detection process as performed by UBRX. We believe that this process is safe to be executed at every boot, irrespective of the hardware and without side effects. The 2 main components UBRX needs to detect are: 1. a potential PnP Super I/O candidate. 2. a potential 16650 UART Logical Device (LD) on the Super I/O chip. As we start with absolutely no knowledge of the hardware, and must avoid writing data at random, since doing so can damage the hardware, our detection process has been designed with safety in mind from the ground up, by ensuring that write operations were limited to the bare minimum, and only executed after we had some assurance that the destination for the write would match the expectation. Below is a detailed description of how we ensure that our generic detection process is as innocuous to the hardware as possible. 1. The Super I/O chip is accessed through the LPC bus, which is not always accessible after reset, so we may have to enable LPC/SuperIO access first. Currently we only support Intel ICH# (all versions) and AMD SB6x0-9x0. The detection and initialisation of the chipset for LPC access is safe, since it is PCI based, and the PCI VID:PID of the South Bridge can be read beforehand to unconditionally identify a supported chip before we proceed with LPC initiation. You will notice that we use a blanket LPC initialization, as we don't distinguish between versions of the SouthBridge (eg: ICH6 is initialized the same as ICH9), but this is the result of a *thorough* review of all the Intel ICH# and AMD SBxx0 datasheets, to confirm that LPC init could indeed be factorized. Even for the chips that don't require LPC initialization(such as Intel ICH5 or earlier) and for which we do send the LPC initialization command, we have confirmed from the datasheet that we can simply let the PCI transaction fail as no register conflict in the destination config space. As such our LPC bus access initialization is deemed safe. 2. With the LPC bus accessible, we must probe a few common Super I/O ports. Currently, these are 0x2E, 0x4E, 0x370, 0x3f0, as well as their +1 data port. The last two I/O ports are commonly assigned to Tape and FDC so we expect any chip there to withstand unintended writes (plus these can be disabled through the bios.S build options). But even then, the extra checks we apply to the 0x2E and 0x4E ports ensure safe access. With regards to 0x2E and 0x4E, these are more problematic as a non PnP Super I/Os are expected to reside there on older machines, and unchecked write access (such as trying to configure PnP access on a non PnP aware chip) could very well have unintended consequences. To alleviate this problem we: a) always keep a copy of the original value at base and base+1 b) perform PnP enter conf (write to base only) and attempt to read the Super I/O ID (at base +1). If the id is either 0x00 of 0xff, we consider that the address is not one of a PnP Super I/O chip and restore the base data => only the base register will have been accessed, then restored. c) attempt to write 8 LDN values, read them back and check that at least 2 of them stick, indicating that the potential PnP Super I/O chip has at least 2 LDs. If this isn't the case, we also restore the content from base+1 and declare the PnP access to have failed. At most, since we are only writing LDNs, this modifies the 3 lowest bits of base +1. Considering that both superiotool and sensors-detect have let user perform similar Super I/O probing (without the extra restore step), and we are not aware of problems, as well as the fact that any recent PC from our targeted audience would have a Super I/O running in PnP mode at either 0x2e or 0x4e, we consider this approach safe to be executed at every boot. 3. Even with a possible PnP Super I/O chip accessible (and with the current POC assertion that the UART LD we want to access is powered up by default on reset), we do not have any knowledge of the LDN of the potential UART. With other LDNs being set up for GPIO or hardware monitoring or control, trying to access each LDN as an UART, without exerting any form of caution, is not a viable option. To alleviate this issue, we perform an extensive yet non intrusive detection of a 16550 UART LD by first making sure, through read- only accesses, that the registers match the reset value of a 16550 compliant unit. Then, we try to flip the furthest 'safe' bit in the I/O range (register 7, bit 6, with backup), to eliminate any LD that has less than 7 registers. Then we check a significant unflippable bit from the 16550 register range (which we also restore in case of failure), and finish our testing with a complete UART loopback test. In all, we perform no less than 16 tests to confirm that an LD is indeed a 16550 UART, with more than 30 bits being tested in read-only mode, before we even start trying to flip a single bit. As such, we seem this form of detection both safe and conclusive. At the moment, we test up to 32 LDNs per potential PnP Super I/O for UART access. The 16550 tests are heavily documented in bios.S/check_16550. Only once we have successfully identified a 16650 UART do we attempt to fully configure it and read the console request key. If multiple UARTs have been identified, these will be checked in sequence, meaning that any serial port available on the motherboard can be used for console access.