(based on https://news.ycombinator.com/item?id=8781082 with permission by the original author, Chris Douglass)
Taking Intel Haswell processors as an example (because I know this code path) we can sketch the general process.
- Start at 0xfffffff0, the boot vector for x86, executing in 16-bit "I can run DOS 1.0" mode.
- It just jumps to the entry to 32-bit mode
- Turn on FPU
- Turn on SSE
- Configure the cache not to require a backing DRAM so that it can be used temporarily as RAM.
- Now that "RAM" is available for use as a stack, the next steps can be written in plain-ole C
- From there, mainboard-specific code sets up things like which SuperIO chip to configure, the i2c addresses to interrogate for information on RAM geometry and timing, and how the chipset is wired to connectors on the board. Commong chipset (northbridge and southbridge) init code is run using that configuration data.
- Then DRAM is initialized (the Haswell example is a bit lame in that currently a binary blob of compiled code from Intel does this job.) The Sandybridge DDR3 init was recently reverse-engineered and re-implemented and fully exemplifies the training processes required.
- Now that Gigabytes of RAM are available, another boot stage is fetched from flash. When generic framework gets back to cpu-specific stuff, power management is configured, Inter-Processor Interrupt handlers are installed, and other cores go through a quick init sequence.
- Then essentially the PCI tree is walked to setup all the chipset devices.
- Once hardware is running, Coreboot loads a "payload" that is in turn responsible for loading the OS.