Getting started with the firmware development
Stefan Reinauer
Ron Minnich
Developed in 1999 at clustering research work in the Cluster Reseach Lab at the Advanced Computing Laboratory at Los Alamos National Laboratory (LANL).
The original BIOS did not boot, but as there was no VGA port on the cluster nodes, nobody knows why.
After hours of looking for the bug, soldering a VGA connector to the board and...
Keyboard error or no keyboard present
Press F1 to Resume
Basic hardware init
DRAM init
Load x86 GNU/Linux kernel from flash as firmware
Boot it
LinuxBIOS
First chromebooks shipped with " Insyde H2C UEFI"
Renamed from LinuxBIOS to coreboot
coreboot was used first in 2012 on the chromebooks
Open Source
CPU/RAM/SB code was available
Multiple boards were ported (Lenovo X220)
coreboot
www.coreboot.org
review.coreboot.org
doc.coreboot.org
qa.coreboot.org
Keyboard error or no keyboard present
Press F1 to Resume
Each stage is one file in CBFS
Each stage has a custom build target
Each file in CBFS has a custom compression
Allows any combination of compression
Supports loading arbitrary files from CBFS
Supported compressions:
LZ4
LZMA1
Developed in 2002
Very basic C compiler in 50k lines of code in a single file.
Generated code works without HEAP or STACK !
Inlines everything
Unrolls loops
No global heap usage
Emulates stack by CPU registers
Copy pastes code if OOM
Only required for x86 bootblock
Used on non-CAR platforms
Compiles the bootblock
Prepares everything for stages written in C
Set up DRAM
Set up what payload needs
Sometimes this is the same!
SeaBIOS
www.seabios.org
TianoCore
www.tianocore.org
Linux kernel
www.kernel.org
GRUB
www.gnu.org/software/grub/
LinuxBoot
www.linuxboot.org
SELF
www.coreboot.org/SELF
What you need:
Steps:
20min
Install the following packages (Debian naming conventions):
apt install git make build-essential gnat flex bison libncurses5-dev \ wget zlib1g-dev acpica-tools patch pciutils-dev ccache qemu
Clone coreboot repository and submodules:
git clone --recurse-submodules https://review.coreboot.org/coreboot.git
cd coreboot/
git submodule update --init --checkout
Download and build the toolchain:
make crossgcc-i386 CPUS=2 make iasl
Verify that the toolchain has been build:
$ ls util/crossgcc/xgcc/bin
$ make menuconfig
What you need:
Steps:
20min
Select the payload:
Emulation / Qemu Q35
You need the following BLOBs:
(None)
Select the mainboard:
SeaBIOS
Include generated Option ROM (x)
Run it in qemu:
$ qemu-system-x86_64 -m 2048 -bios build/coreboot.rom -M q35
Or with KVM enabled:
$ qemu-system-x86_64 -enable-kvm -m 2048 -bios build/coreboot.rom -M q35
static void qemu_nb_init(struct device *dev) { ... printk(BIOS_DEBUG, "Hello World\n"); }
Task:
10min
src/mainboard/
src/soc/
src/drivers/
src/lib/
src/include/
src/arch/
src/cpu/
src/northbridge/
src/southbridge/
src/vendorcode/
util/
build/
mainboard specific files
SoC specific files
platform independent (PCI) drivers
general helper functions CBFS, libc, printk, ...
includes of src/lib
architecture specific code (x86, arm, riscv, mips, ...)
CPU specific code
NB specific code (Open Source)
SB specific code (Open Source)
3rd Party code dump
Utilities useful for coreboot development
Object files and final image land here
Architecture support
Difficulty
SoC support
Mainboard support
Payload support
Bootblock support
MMU support
easy
difficult
FLASH 8M {
SI_ALL 2M {
# Firmware Descriptor section of the Intel Firmware Descriptor
# image.
SI_DESC 4K
# Intel Management Engine section of the Intel Firmware
# Descriptor image.
SI_ME
}
SI_BIOS 6M {
COREBOOT(CBFS)@0x200000 0x600000
}
}
$ ./util/cbfstool/cbfstool build/coreboot.rom print
FMAP REGION: COREBOOT
Name Offset Type Size Comp
cbfs master header 0x0 cbfs header 32 none
fallback/romstage 0x80 stage 79620 none
cpu_microcode_blob.bin 0x13800 microcode 25600 none
fallback/ramstage 0x19c80 stage 85153 none
config 0x2e980 raw 268 none
revision 0x2eb00 raw 581 none
BOOT_STATE_INIT_ENTRY(BS_DEV_INIT, BS_ON_ENTRY, init_cpus, NULL);
BOOT_STATE_INIT_ENTRY(BS_DEV_INIT, BS_ON_EXIT, post_cpus_init, NULL);
static const struct pci_driver r8168_driver __pci_driver = {
.ops = &r8168_ops,
.vendor = 0x10ec,
.device = 0x8168,
};
chip soc/cavium/cn81xx
device cpu_cluster 0 on end
device domain 0 on
chip soc/cavium/common/pci
register "secure" = "0"
device pci 01.0 on end # PCI bridge
end
end
end
example
chip soc/cavium/cn81xx
device cpu_cluster 0 on end
device domain 0 on
chip soc/cavium/common/pci
register "secure" = "0"
device pci 01.0 on end # PCI bridge
end
end
end
#include "soc/cavium/cn81xx/chip.h"
#include "soc/cavium/common/pci/chip.h"
example
chip soc/cavium/cn81xx
device cpu_cluster 0 on end
device domain 0 on
chip soc/cavium/common/pci
register "secure" = "0"
device pci 01.0 on end # PCI bridge
end
end
end
DEVTREE_CONST struct soc_cavium_cn81xx_config soc_cavium_cn81xx_info_1 = {};
example
chip soc/cavium/cn81xx
device cpu_cluster 0 on end
device domain 0 on
chip soc/cavium/common/pci
register "secure" = "0"
device pci 01.0 on end # PCI bridge
end
end
end
DEVTREE_CONST struct soc_cavium_common_pci_config soc_cavium_common_pci_info_1 = {
.secure = 0,
};
DEVTREE_CONST struct soc_cavium_cn81xx_config soc_cavium_cn81xx_info_1 = {};
example
chip soc/cavium/cn81xx
device cpu_cluster 0 on end
device domain 0 on
chip soc/cavium/common/pci
register "secure" = "0"
device pci 01.0 on # PCI bridge
end
end
end
static DEVTREE_CONST struct device _dev3 = {
.bus = &dev_root_links[0],
.path = {.type=DEVICE_PATH_DOMAIN,{.domain={ .domain = 0x0 }}},
.enabled = 1,
.on_mainboard = 1,
.link_list = &_dev3_links[0],
.chip_info = &soc_cavium_cn81xx_info_1,
.next=&_dev5
};
example
chip soc/cavium/cn81xx
device cpu_cluster 0 on end
device domain 0 on
chip soc/cavium/common/pci
register "secure" = "0"
device pci 01.0 on # PCI bridge
end
end
end
static DEVTREE_CONST struct device _dev5 = {
.bus = &_dev3_links[0],
.path = {.type=DEVICE_PATH_PCI,{.pci={ .devfn = PCI_DEVFN(0x1,0)}}},
.enabled = 1,
.on_mainboard = 1,
.link_list = &_dev5_links[0],
.sibling = &_dev84,
.chip_info = &soc_cavium_common_pci_info_1,
.next=&_dev84
};
example
chip soc/cavium/cn81xx
device cpu_cluster 0 on end
device domain 0 on
chip soc/cavium/common/pci
register "secure" = "0"
device pci 01.0 off # PCI bridge
end
end
end
static DEVTREE_CONST struct device _dev5 = {
.bus = &_dev3_links[0],
.path = {.type=DEVICE_PATH_PCI,{.pci={ .devfn = PCI_DEVFN(0x1,0)}}},
.enabled = 0,
.on_mainboard = 1,
.link_list = &_dev5_links[0],
.sibling = &_dev84,
.chip_info = &soc_cavium_common_pci_info_1,
.next=&_dev84
};
example
chip soc/cavium/cn81xx
device cpu_cluster 0 on end
device domain 0 on
chip soc/cavium/common/pci
register "secure" = "0"
device pci 01.0 off # PCI bridge
end
end
end
static DEVTREE_CONST struct device _dev2 = {
.bus = &dev_root_links[0],
.path = {.type=DEVICE_PATH_CPU_CLUSTER,{.cpu_cluster={ .cluster = 0x0 }}},
.enabled = 1,
.on_mainboard = 1,
.link_list = NULL,
.sibling = &_dev3,
.chip_info = &soc_cavium_cn81xx_info_1,
.next=&_dev3
};
example
#ifndef __SOC_CAVIUM_COMMON_PCI_CHIP_H
#define __SOC_CAVIUM_COMMON_PCI_CHIP_H
struct soc_cavium_common_pci_config {
u8 secure;
};
#endif /* __SOC_CAVIUM_COMMON_PCI_CHIP_H */
chip soc/cavium/cn81xx
device cpu_cluster 0 on end
device domain 0 on
chip soc/cavium/common/pci
register "secure" = "0"
device pci 01.0 on # PCI bridge
end
end
end
Accessing the device tree
#include "soc/cavium/common/pci/chip.h"
void func() {
const struct device *dev;
const struct soc_cavium_common_pci_config *cfg = NULL;
dev = dev_find_slot(0, PCI_DEVFN(1, 0));
if (dev)
cfg = dev->chip_info;
}
chip soc/cavium/cn81xx
device cpu_cluster 0 on end
device domain 0 on
chip soc/cavium/common/pci
register "secure" = "0"
device pci 01.0 on # PCI bridge
end
end
end
src/include/cbmem.h:
void *cbmem_add(u32 id, u64 size);
void *cbmem_find(u32 id);
...
The Advanced Configuration and Power Interface
Placed in reserved memory by firmware
Consists of tables
Describes the hardware
Register locations
DSDT / SSDT is an AML to be run by ACPI aware OS
Legacy power and fan control
ACPI power and fan control
Under firmware control, OS unaware if it
Provided by firmware, run by OS
Ring -1
Ring 0
Ring 0
Ring 0
?
Legacy power and fan control
ACPI power and fan control
Under firmware control, OS unaware if it
Obfuscated hardware access
Ring -1
Ring 0
Ring 0
Ring 0
?
what other firmware vendors do...
Ring -1
SMI TRAP
?
What does it looks like ?
DefinitionBlock( "dsdt.aml", "DSDT", 0x02, // DSDT revision: ACPI v2.0 "COREv4", // OEM id "COREBOOT", // OEM table id 0x20110725 // OEM revision ) { #include <southbridge/intel/bd82x6x/acpi/platform.asl> #include "acpi/platform.asl" #include <southbridge/intel/bd82x6x/acpi/globalnvs.asl> #include <cpu/intel/model_206ax/acpi/cpu.asl> Scope (\_SB) { Device (PCI0) { #include <northbridge/intel/sandybridge/acpi/sandybridge.asl> #include <southbridge/intel/bd82x6x/acpi/pch.asl> } } }
Sharing data between ACPI and SMM / firmware:
typedef struct global_nvs_t {
/* Miscellaneous */
uint8_t pcnt; /* 0x00 - Processor Count */
uint8_t ppcm; /* 0x01 - Max PPC State */
uint8_t lids; /* 0x02 - LID State */
uint8_t pwrs; /* 0x03 - AC Power State */
uint8_t dpte; /* 0x04 - Enable DPTF */
...
Field (GNVS, ByteAcc, NoLock, Preserve) {
/* Miscellaneous */
Offset (0x00),
PCNT, 8, /* 0x00 - Processor Count *
PPCM, 8, /* 0x01 - Max PPC State */
LIDS, 8, /* 0x02 - LID State */
PWRS, 8, /* 0x03 - AC Power State */
DPTE, 8, /* 0x04 - Enable DPTF */
...
AML
C
Sharing data between ACPI and SMM / firmware:
External (NVSA)
OperationRegion (GNVS, SystemMemory, NVSA, 0x1000)
gnvs = cbmem_add(CBMEM_ID_ACPI_GNVS, sizeof(*gnvs));
if (gnvs) {
acpi_create_gnvs(gnvs);
/* Add it to DSDT. */
acpigen_write_scope("\\");
acpigen_write_name_dword("NVSA", (u32)gnvs);
acpigen_pop_len();
}
C
AML
acpigen_write_scope("PCI0.DEV0");
acpigen_write_method("_ROM", 2);
struct opregion opreg = OPREGION("ROMS",
SYSTEMMEMORY, (uintptr_t)0xf0000, 0x10000);
acpigen_write_opregion(&opreg);
struct fieldlist l[] = { FIELDLIST_OFFSET(0),
FIELDLIST_NAMESTR("RBF0", 8 * 0x10000), };
acpigen_write_field(opreg.name, l, 2, FIELD_ANYACC |
FIELD_NOLOCK | FIELD_PRESERVE);
acpigen_write_store();
acpigen_emit_byte(ARG0_OP);
acpigen_emit_byte(LOCAL0_OP);
acpigen_pop_len(); /* pop method */
acpigen_pop_len(); /* pop scope */
Scope("PCI0.DEV0") {
Method (_ROM, 2, NotSerialized) {
OperationRegion("ROMS", SYSTEMMEMORY, 0xf0000, 0x10000)
Field (ROMS, AnyAcc, NoLock, Preserve) {
Offset (0),
RBF0, 0x80000
}
Store (Arg0, Local0)
}
}
$ dmidecode SMBIOS 3.0.0 present. Handle 0x000B, DMI type 0, 24 bytes BIOS Information Vendor: LENOVO Release Date: 03/07/2017 Runtime Size: 128 kB ROM Size: 16 MB Characteristics: PCI is supported UEFI is supported BIOS Revision: 1.10 Firmware Revision: 1.1
Wait what ?
/*
* Intel wifi driver expects this string to be in the table 0x85
* with PCI IDs enumerated below.
*/
t->str = smbios_add_string(t->eos, "KHOIHGIUCCHHII");
What you need:
Steps:
30min
Task:
$ qemu-system-x86_64 -m 4096 -enable-kvm -bios build/coreboot.rom -M q35 -cdrom live.iso -boot d
5. Dump SMBIOS with dmidecode
Hint:
static struct device_operations nb_operations = {
#if IS_ENABLED(CONFIG_GENERATE_SMBIOS_TABLES)
.get_smbios_strings = qemu_smbios_strings,
#endif
Task:
10min