Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit edce2121 authored by Ingo Molnar's avatar Ingo Molnar
Browse files

x86/boot: Reorganize and clean up the BIOS area reservation code



So the reserve_ebda_region() code has accumulated a number of
problems over the years that make it really difficult to read
and understand:

- The calculation of 'lowmem' and 'ebda_addr' is an unnecessarily
  interleaved mess of first lowmem, then ebda_addr, then lowmem tweaks...

- 'lowmem' here means 'super low mem' - i.e. 16-bit addressable memory. In other
  parts of the x86 code 'lowmem' means 32-bit addressable memory... This makes it
  super confusing to read.

- It does not help at all that we have various memory range markers, half of which
  are 'start of range', half of which are 'end of range' - but this crucial
  property is not obvious in the naming at all ... gave me a headache trying to
  understand all this.

- Also, the 'ebda_addr' name sucks: it highlights that it's an address (which is
  obvious, all values here are addresses!), while it does not highlight that it's
  the _start_ of the EBDA region ...

- 'BIOS_LOWMEM_KILOBYTES' says a lot of things, except that this is the only value
  that is a pointer to a value, not a memory range address!

- The function name itself is a misnomer: it says 'reserve_ebda_region()' while
  its main purpose is to reserve all the firmware ROM typically between 640K and
  1MB, while the 'EBDA' part is only a small part of that ...

- Likewise, the paravirt quirk flag name 'ebda_search' is misleading as well: this
  too should be about whether to reserve firmware areas in the paravirt case.

- In fact thinking about this as 'end of RAM' is confusing: what this function
  *really* wants to reserve is firmware data and code areas! Once the thinking is
  inverted from a mixed 'ram' and 'reserved firmware area' notion to a pure
  'reserved area' notion everything becomes a lot clearer.

To improve all this rewrite the whole code (without changing the logic):

- Firstly invert the naming from 'lowmem end' to 'BIOS reserved area start'
  and propagate this concept through all the variable names and constants.

	BIOS_RAM_SIZE_KB_PTR		// was: BIOS_LOWMEM_KILOBYTES

	BIOS_START_MIN			// was: INSANE_CUTOFF

	ebda_start			// was: ebda_addr
	bios_start			// was: lowmem

	BIOS_START_MAX			// was: LOWMEM_CAP

- Then clean up the name of the function itself by renaming it
  to reserve_bios_regions() and renaming the ::ebda_search paravirt
  flag to ::reserve_bios_regions.

- Fix up all the comments (fix typos), harmonize and simplify their
  formulation and remove comments that become unnecessary due to
  the much better naming all around.

Signed-off-by: default avatarIngo Molnar <mingo@kernel.org>
parent 4ff53087
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -17,7 +17,7 @@ static inline unsigned int get_bios_ebda(void)
	return address;	/* 0 means none */
}

void reserve_ebda_region(void);
void reserve_bios_regions(void);

#ifdef CONFIG_X86_CHECK_BIOS_CORRUPTION
/*
+2 −2
Original line number Diff line number Diff line
@@ -168,14 +168,14 @@ struct x86_legacy_devices {
 * struct x86_legacy_features - legacy x86 features
 *
 * @rtc: this device has a CMOS real-time clock present
 * @ebda_search: it's safe to search for the EBDA signature in the hardware's
 * @reserve_bios_regions: it's safe to search for the EBDA signature in the hardware's
 * 	low RAM
 * @devices: legacy x86 devices, refer to struct x86_legacy_devices
 * 	documentation for further details.
 */
struct x86_legacy_features {
	int rtc;
	int ebda_search;
	int reserve_bios_regions;
	struct x86_legacy_devices devices;
};

+81 −43
Original line number Diff line number Diff line
@@ -6,66 +6,104 @@
#include <asm/bios_ebda.h>

/*
 * This function reserves all conventional PC system BIOS related
 * firmware memory areas (some of which are data, some of which
 * are code), that must not be used by the kernel as available
 * RAM.
 *
 * The BIOS places the EBDA/XBDA at the top of conventional
 * memory, and usually decreases the reported amount of
 * conventional memory (int 0x12) too. This also contains a
 * workaround for Dell systems that neglect to reserve EBDA.
 * The same workaround also avoids a problem with the AMD768MPX
 * conventional memory (int 0x12) too.
 *
 * This means that as a first approximation on most systems we can
 * guess the reserved BIOS area by looking at the low BIOS RAM size
 * value and assume that everything above that value (up to 1MB) is
 * reserved.
 *
 * But life in firmware country is not that simple:
 *
 * - This code also contains a quirk for Dell systems that neglect
 *   to reserve the EBDA area in the 'RAM size' value ...
 *
 * - The same quirk also avoids a problem with the AMD768MPX
 *   chipset: reserve a page before VGA to prevent PCI prefetch
 * into it (errata #56). Usually the page is reserved anyways,
 * unless you have no PS/2 mouse plugged in.
 *   into it (errata #56). (Usually the page is reserved anyways,
 *   unless you have no PS/2 mouse plugged in.)
 *
 * This functions is deliberately very conservative.  Losing
 * memory in the bottom megabyte is rarely a problem, as long
 * as we have enough memory to install the trampoline.  Using
 * memory that is in use by the BIOS or by some DMA device
 * the BIOS didn't shut down *is* a big problem.
 * - Plus paravirt systems don't have a reliable value in the
 *   'BIOS RAM size' pointer we can rely on, so we must quirk
 *   them too.
 *
 * Due to those various problems this function is deliberately
 * very conservative and tries to err on the side of reserving
 * too much, to not risk reserving too little.
 *
 * Losing a small amount of memory in the bottom megabyte is
 * rarely a problem, as long as we have enough memory to install
 * the SMP bootup trampoline which *must* be in this area.
 *
 * Using memory that is in use by the BIOS or by some DMA device
 * the BIOS didn't shut down *is* a big problem to the kernel,
 * obviously.
 */

#define BIOS_LOWMEM_KILOBYTES	0x413
#define LOWMEM_CAP		0x9f000U	/* Absolute maximum */
#define INSANE_CUTOFF		0x20000U	/* Less than this = insane */
#define BIOS_RAM_SIZE_KB_PTR	0x413

#define BIOS_START_MIN		0x20000U	/* 128K, less than this is insane */
#define BIOS_START_MAX		0x9f000U	/* 640K, absolute maximum */

void __init reserve_ebda_region(void)
void __init reserve_bios_regions(void)
{
	unsigned int lowmem, ebda_addr;
	unsigned int bios_start, ebda_start;

	/*
	 * To determine the position of the EBDA and the
	 * end of conventional memory, we need to look at
	 * the BIOS data area. In a paravirtual environment
	 * that area is absent. We'll just have to assume
	 * that the paravirt case can handle memory setup
	 * correctly, without our help.
	 * NOTE: In a paravirtual environment the BIOS reserved
	 * area is absent. We'll just have to assume that the
	 * paravirt case can handle memory setup correctly,
	 * without our help.
	 */
	if (!x86_platform.legacy.ebda_search)
	if (!x86_platform.legacy.reserve_bios_regions)
		return;

	/* end of low (conventional) memory */
	lowmem = *(unsigned short *)__va(BIOS_LOWMEM_KILOBYTES);
	lowmem <<= 10;

	/* start of EBDA area */
	ebda_addr = get_bios_ebda();
	/* Get the start address of the EBDA page: */
	ebda_start = get_bios_ebda();

	/*
	 * Note: some old Dells seem to need 4k EBDA without
	 * reporting so, so just consider the memory above 0x9f000
	 * to be off limits (bugzilla 2990).
	 * Quirk: some old Dells seem to have a 4k EBDA without
	 * reporting so in their BIOS RAM size value, so just
	 * consider the memory above 640K to be off limits
	 * (bugzilla 2990).
	 *
	 * We detect this case by filtering for nonsensical EBDA
	 * addresses below 128K, where we can assume that they
	 * are bogus and bump it up to a fixed 640K value:
	 */
	if (ebda_start < BIOS_START_MIN)
		ebda_start = BIOS_START_MAX;

	/* If the EBDA address is below 128K, assume it is bogus */
	if (ebda_addr < INSANE_CUTOFF)
		ebda_addr = LOWMEM_CAP;
	/*
	 * BIOS RAM size is encoded in kilobytes, convert it
	 * to bytes to get a first guess at where the BIOS
	 * firmware area starts:
	 */
	bios_start = *(unsigned short *)__va(BIOS_RAM_SIZE_KB_PTR);
	bios_start <<= 10;

	/* If lowmem is less than 128K, assume it is bogus */
	if (lowmem < INSANE_CUTOFF)
		lowmem = LOWMEM_CAP;
	/*
	 * If bios_start is less than 128K, assume it is bogus
	 * and bump it up to 640K:
	 */
	if (bios_start < BIOS_START_MIN)
		bios_start = BIOS_START_MAX;

	/* Use the lower of the lowmem and EBDA markers as the cutoff */
	lowmem = min(lowmem, ebda_addr);
	lowmem = min(lowmem, LOWMEM_CAP); /* Absolute cap */
	/*
	 * Use the lower of the bios_start and ebda_start
	 * as the starting point, but don't allow it to
	 * go beyond 640K:
	 */
	bios_start = min(bios_start, ebda_start);
	bios_start = min(bios_start, BIOS_START_MAX);

	/* reserve all memory between lowmem and the 1MB mark */
	memblock_reserve(lowmem, 0x100000 - lowmem);
	/* Reserve all memory between bios_start and the 1MB mark: */
	memblock_reserve(bios_start, 0x100000 - bios_start);
}
+1 −1
Original line number Diff line number Diff line
@@ -26,7 +26,7 @@ static void __init i386_default_early_setup(void)
	x86_init.resources.reserve_resources = i386_reserve_resources;
	x86_init.mpparse.setup_ioapic_ids = setup_ioapic_ids_from_mpc;

	reserve_ebda_region();
	reserve_bios_regions();
}

asmlinkage __visible void __init i386_start_kernel(void)
+1 −1
Original line number Diff line number Diff line
@@ -183,7 +183,7 @@ void __init x86_64_start_reservations(char *real_mode_data)
		copy_bootdata(__va(real_mode_data));

	x86_early_init_platform_quirks();
	reserve_ebda_region();
	reserve_bios_regions();

	switch (boot_params.hdr.hardware_subarch) {
	case X86_SUBARCH_INTEL_MID:
Loading