On startup, we may be running from read-only memory, and therefore
cannot zero the .bss section (or write to the .data section) until we
have parsed the system memory map and relocated ourselves to somewhere
suitable in RAM. The code that runs during this early initialisation
stage must be carefully written to avoid writing to the .data section
and to avoid reading from or writing to the .bss section.
Detecting code that erroneously writes to the .data or .bss sections
is relatively easy since running from read-only memory (e.g. via
QEMU's -pflash option) will immediately reveal the bug. Detecting
code that erroneously reads from the .bss section is harder, since in
a freshly powered-on machine (or in a virtual machine) there is a high
probability that the contents of the memory will be zero even before
we explicitly zero out the section.
Add the ability to fill the .bss section with an invalid non-zero
value to expose bugs in early initialisation code that erroneously
relies upon variables in .bss before the section has been zeroed. We
use the value 0xeb55eb55eb55eb55 ("EBSS") since this is immediately
recognisable as a value in a crash dump, and will trigger a page fault
if dereferenced since the address is in a non-canonical form.
Poisoning the .bss can be done only when the image is known to already
reside in writable memory. It will overwrite the relocation records,
and so can be done only on a system where relocation is known to be
unnecessary (e.g. because paging is supported). We therefore do not
enable this behaviour by default, but leave it as a configurable
option via the config/fault.h header.
Signed-off-by: Michael Brown <mcb30@ipxe.org>
Reuse the code that creates I/O device page mappings to create the
coherent DMA mapping of the 32-bit address space on demand, instead of
constructing this mapping as part of the initial page table.
Signed-off-by: Michael Brown <mcb30@ipxe.org>
Use PTEs 256-259 to create a mapping of the 32-bit physical address
space with attributes suitable for coherent DMA mappings.
Signed-off-by: Michael Brown <mcb30@ipxe.org>
The page table entries for the identity map vary according to the
paging level in use, and so must be constructed within the loop used
to detect the maximum supported paging level. Other page table
entries are invariant between paging levels, and so may be constructed
just once before entering the loop.
Signed-off-by: Michael Brown <mcb30@ipxe.org>
The early UART is an optional feature used to obtain debug output from
the prefix before iPXE is able to parse the device tree.
Extend this feature to also cover any console output that iPXE
attempts to send to the SBI console, on the basis that the purpose of
the early UART is to provide an output-only device for situations in
which there is no functional SBI console.
Signed-off-by: Michael Brown <mcb30@ipxe.org>
Xuantie/T-Head processors such as the C910 (as used in the Sipeed
Lichee Pi 4A) use the high bits of the PTE in a very non-standard way
that is incompatible with the RISC-V specification.
As per the "Memory Attribute Extension (XTheadMae)", bits 62 and 61
represent cacheability and "bufferability" (write-back cacheability)
respectively. If we do not enable these bits, then the processor gets
incredibly confused at the point that paging is enabled. The symptom
is that cache lines will occasionally fail to fill, and so reads from
any address may return unrelated data from a previously read cache
line for a different address.
Work around these hardware flaws by detecting T-Head CPUs (via the
"get machine vendor ID" SBI call), then reading the vendor-specific
SXSTATUS register to determine whether or not the vendor-specific
Memory Attribute Extension has been enabled by the M-mode firmware.
If it has, then set bits 61 and 62 in each page table entry that is
used to access normal memory.
Signed-off-by: Michael Brown <mcb30@ipxe.org>
Add a fence between the write to the UART transmit register and the
subsequent read from the transmit status register, to ensure that the
status correctly reflects the occurrence of the write.
Signed-off-by: Michael Brown <mcb30@ipxe.org>
The RISC-V specification states that "if SATP is written with an
unsupported mode, the entire write has no effect; no fields in SATP
are modified". We currently rely on this specified behaviour when
calculating the early UART base address: if SATP has a non-zero value
then we assume that paging must be enabled.
The XuanTie C910 CPU (as used in the Lichee Pi 4A) does not conform to
this specified behaviour. Writing SATP with an unsupported mode will
leave SATP.MODE as zero (i.e. bare physical addressing) but the write
to SATP.PPN will still take effect, leaving SATP with an illegal
non-zero value.
Work around this misbehaviour by explicitly writing zero to SATP if we
detect that the mode change has not taken effect (e.g. because the CPU
does not support the requested paging mode).
Signed-off-by: Michael Brown <mcb30@ipxe.org>
Some platforms (such as the Sipeed Lichee Pi 4A) choose to make early
debugging entertainingly cumbersome for the programmer. These
platforms not only fail to provide a functional SBI debug console, but
also choose to place the UART at a physical address that cannot be
identity-mapped under the only paging model supported by the CPU.
Support such platforms by creating a virtual address mapping for the
early UART (in the 2MB megapage immediately below iPXE itself), and
using this as the UART base address whenever paging is enabled.
Signed-off-by: Michael Brown <mcb30@ipxe.org>
Some platforms (such as the Sipeed Lichee Pi 4A) do not provide a
functional SBI debug console. We can obtain early debug messages on
these systems by writing directly to the UART used by the vendor
firmware.
There is no viable way to parse the UART address from the device tree,
since the prefix debug messages occur extremely early, before the C
runtime environment is available and therefore before any information
has been parsed from the device tree. The early UART model and
register addresses must be configured by editing config/serial.h if
needed. (This is an acceptable limitation, since prefix debugging is
an extremely specialised use case.)
Signed-off-by: Michael Brown <mcb30@ipxe.org>
Abstract out the SBI debug console calls into macros that can be
shared between print_message and print_hex_value.
Signed-off-by: Michael Brown <mcb30@ipxe.org>
Support debug consoles that do not automatically convert LF to CRLF by
including the CR character within the debug message strings.
Signed-off-by: Michael Brown <mcb30@ipxe.org>
Provide an implementation of the system memory map API based on the
system device tree, excluding any memory outside the size of the
accessible physical address space and defining an in-use region to
cover the relocated copy of iPXE and the system device tree.
Signed-off-by: Michael Brown <mcb30@ipxe.org>
The size of accessible physical address space will be required for the
runtime memory map, not just at relocation time. Make this size an
additional parameter to fdt_register() (matching the prototype for
fdt_relocate()), and record the value for future reference.
Note that we cannot simply store the limit in fdt_relocate() since it
is called before .data is writable and before .bss is zeroed.
Signed-off-by: Michael Brown <mcb30@ipxe.org>
If paging is not supported, then we will attempt to apply dynamic
relocations to fix up the runtime addresses. If the image is
currently executing directly from flash memory, this can result in
effectively sending an undefined sequence of commands to the flash
device, which can cause unwanted side effects.
Perform an explicit writability test before applying relocations,
using a write value chosen to be safe for at least any devices
conforming to the JEDEC Common Flash Interface (CFI01).
Signed-off-by: Michael Brown <mcb30@ipxe.org>
We do not currently describe the temporary page table or the temporary
stack as areas to be avoided during relocation of the iPXE image to a
new physical address.
Perform the copy of the iPXE image and zeroing of the .bss within
libprefix.S, after we have no futher use for the temporary page table
or the temporary initial stack. Perform the copy and registration of
the system device tree in C code after relocation is complete and the
new stack (within .bss) has been set up.
This provides a clean separation of responsibilities between the
RISC-V libprefix.S and the architecture-independent fdtmem.c. The
prefix is responsible only for relocating iPXE to the new physical
address returned from fdtmem_relocate(), and doesn't need to know or
care where fdtmem.c is planning to place the copy of the device tree.
Signed-off-by: Michael Brown <mcb30@ipxe.org>
On x86 BIOS, it has been useful to be able to build iPXE to resemble a
Linux kernel, so that it can be loaded by programs such as syslinux
which already know how to handle Linux kernel binaries.
Add an equivalent .lkrn build target for RISC-V SBI, allowing for
build targets such as:
make bin-riscv64/ipxe.lkrn
make bin-riscv64/cgem.lkrn
The Linux kernel header format allows us to specify a required length
(including uninitialised-data portions) and defines that the image
will be loaded at a fixed offset from the start of RAM. We can
therefore use known-safe areas of memory (within our own .bss) for the
initial temporary page table and stack.
Signed-off-by: Michael Brown <mcb30@ipxe.org>
On startup, we may be running from read-only memory. We need to parse
the devicetree to obtain the system memory map, and identify a safe
location to which we can copy our own binary image along with a
stashed copy of the devicetree, and then transfer execution to this
new location.
Parsing the system memory map realistically requires running C code.
This in turn requires a small temporary stack, and some way to ensure
that symbol references are valid.
We first attempt to enable paging, to make the runtime virtual
addresses equal to the link-time virtual addresses. If this fails,
then we attempt to apply the compressed relocation records.
Assuming that one of these has worked (i.e. that either the CPU
supports paging or that our image started execution in writable
memory), then we call fdtmem_relocate() to parse the system memory map
to find a suitable relocation target address.
After the copy we disable paging, jump to the relocated copy,
re-enable paging, and reapply relocation records (if needed). At this
point, we have a full runtime environment, and can transfer control to
normal C code.
Provide this functionality as part of libprefix.S, since it is likely
to be shared by multiple prefixes.
Signed-off-by: Michael Brown <mcb30@ipxe.org>
Always construct the page tables based on the link-time address values
even if relocations have already been applied, on the assumption that
relocations will be reapplied after paging has been enabled.
Signed-off-by: Michael Brown <mcb30@ipxe.org>
The address of the compressed relocation records is currently
calculated implicitly relative to the program counter. This requires
the relocation records to be copied as part of relocation to a new
physical address, so that they can be reapplied (if needed) after
copying iPXE to the new physical address.
Since the relocation destination will never overlap the original iPXE
image, and since the relocation records will not be needed further
after completing relocation, we can avoid the need to copy the records
by passing in a pointer to the relocation records present in the
original iPXE image.
Pass the compressed relocation record address as an explicit parameter
to apply_relocs(), rather than being implicit in the program counter.
Signed-off-by: Michael Brown <mcb30@ipxe.org>
Relocation requires knowledge of the size of the accessible physical
address space, which for 64-bit CPUs will vary according to the paging
level supported by the processor.
Update enable_paging_64() and enable_paging_32() to calculate and
return the size of the accessible physical address space.
Signed-off-by: Michael Brown <mcb30@ipxe.org>
Add code to parse the devicetree memory nodes, memory reservations
block, and reserved memory nodes to construct an ordered and
non-overlapping description of the system memory map, and use this to
identify a suitable address to which iPXE may be relocated at runtime.
We choose to place iPXE on a superpage boundary (as required by the
paging code), and to use the highest available address within
accessible memory. This mirrors the approach taken for x86 BIOS
builds, where we have long assumed that any image format that we might
need to support may require specific fixed addresses towards the
bottom of the memory map, but is very unlikely to require specific
fixed addresses towards the top of the memory map (since those
addresses may not exist, depending on the amount of installed RAM).
Signed-off-by: Michael Brown <mcb30@ipxe.org>
Ensure that the prefix_virt dynamic relocation ends up on a suitably
aligned boundary for a compressed relocation.
Signed-off-by: Michael Brown <mcb30@ipxe.org>
iPXE does not make use of any thread-local storage. Use the otherwise
unused thread pointer register ("tp") to hold the current value of
the virtual address offset, rather than using a global variable.
This ensures that virt_offset can be made valid even during very early
initialisation (when iPXE may be executing directly from read-only
memory and so cannot update a global variable).
Signed-off-by: Michael Brown <mcb30@ipxe.org>
The pattern of "load address to register" followed by "load value from
address in register" generally results in three instructions: two to
load the address and one to load the value.
This can be reduced to two instructions by allowing the assembler to
incorporate the low bits of the address within the load (or store)
instruction itself. In the case of a store, this requires specifying
a second register that can be temporarily used to hold the high bits
of the address. (In the case of a load, the destination register is
reused for this purpose.)
Signed-off-by: Michael Brown <mcb30@ipxe.org>
Once paging has been enabled, there is no direct way to determine the
virtual address offset without external knowledge. (The paging mode,
if needed, can be read directly from the SATP CSR.)
Change the return value from enable_paging() to provide the virtual
address offset.
Signed-off-by: Michael Brown <mcb30@ipxe.org>
If the virtual address offset is precisely one page (i.e. each virtual
address maps to a physical address one page higher), and if the 32-bit
transition code happens to end up at the end of a page (which would
require an unrealistic 2MB of content in .prefix), then it would be
possible for the program counter to cross into the portion of the
virtual address space still borrowed for use as the temporary physical
map.
Avoid this remote possibility by moving the restoration of the
temporarily modified PTE within the transition code block (which is
guaranteed to remain within a single page since it is aligned on its
own size).
This unfortunately requires increasing the alignment of the transition
code (and hence the maximum number of NOPs inserted). The assembler
syntax theoretically allows us to avoid inserting any NOPs via a
directive such as:
.balign PAGE_SIZE, , enable_paging_32_max_len
(i.e. relying on the fact that if the transition code is already
sufficiently far away from the end of a page, then no padding needs to
be inserted). However, alignment on RISC-V is implemented using the
R_RISCV_ALIGN relaxing relocation, which doesn't encode any concept of
a maximum padding length, and so the maximum padding length value is
effectively ignored.
Signed-off-by: Michael Brown <mcb30@ipxe.org>
The virtual offset memory model used for i386-pcbios and x86_64-pcbios
can be generalised to also cover riscv32-sbi and riscv64-sbi. In both
architectures, the 32-bit builds will use a circular map of the 32-bit
address space, and the 64-bit builds will use an identity map for the
relevant portion of the physical address space, with iPXE itself
placed in the negative (kernel) address space.
Generalise and document the virt_offset mechanism, and set it as the
default for both PCBIOS and SBI platforms.
Signed-off-by: Michael Brown <mcb30@ipxe.org>
Add millicode routines to print hexadecimal values (with any number of
digits), and macros to print register contents or symbol addresses.
Signed-off-by: Michael Brown <mcb30@ipxe.org>
RISC-V has a millicode calling convention that allows for the use of
an alternative link register x5/t0. With sufficient care, this allows
for two levels of subroutine call even when no stack is available.
Provide both standard and millicode entry points for print_message(),
and use the millicode entry point to allow for printing debug messages
from libprefix.S itself.
Signed-off-by: Michael Brown <mcb30@ipxe.org>
Create a prefix library function print_message() to print text to the
SBI debug console. Use the "write byte" SBI call (rather than "write
string") so that the function remains usable even after enabling
paging.
Signed-off-by: Michael Brown <mcb30@ipxe.org>
Use compressed relocation records instead of raw Elf_Rela records.
This saves around 15% of the total binary size for the all-drivers
image bin-riscv64/ipxe.sbi.
Signed-off-by: Michael Brown <mcb30@ipxe.org>
Add code to construct a 32-bit page table to map the whole of the
32-bit address space with a fixed offset selected to map iPXE itself
at its link-time address, and to return with paging enabled and the
program counter updated to a virtual address.
Signed-off-by: Michael Brown <mcb30@ipxe.org>
Paging provides an alternative to using relocations: instead of
applying relocation fixups to the runtime addresses, we can set up
virtual addressing so that the runtime addresses match the link-time
addresses.
This opens up the possibility of running portions of iPXE directly
from read-only memory (such as a memory-mapped flash device), subject
to the caveats that .data is not yet writable and .bss is not yet
zeroed. This should allow us to run enough code to parse the memory
map from the FDT, identify a suitable RAM block, and physically
relocate ourselves there.
Add code to construct a 64-bit page table (in a single 4kB buffer) to
identity-map as much of the physical address space as possible, to map
iPXE itself at its link-time address, and to return with paging
enabled and the program counter updated to a virtual address. We use
the highest paging level supported by the CPU, to maximise the amount
of the physical address space covered by the identity map.
Signed-off-by: Michael Brown <mcb30@ipxe.org>
Split out the runtime relocation logic from sbiprefix.S to a new
library libprefix.S.
Since this logically decouples the process of runtime relocation from
the _sbi_start symbol (currently used to determine the base address
for applying relocations), provide an alternative mechanism for the
relocator to determine the base address.
Signed-off-by: Michael Brown <mcb30@ipxe.org>