Search code examples
x86kerneli386grub2

Get total avaiable RAM from the GRUB2 multiboot info structure


I managed to access the GRUB multiboot info structure (multiboot_info_t) in my i386 kernel, and there are two fields called mem_lower and mem_upper respectively. How do I use them to get the total available RAM (in bytes)?


Solution

  • Simply put, you can't.

    The mem_lower and mem_upper are obsolete fields, referring to the conventional memory and the extended memory.
    Quoting the specifications:

    If bit 0 in the flags word is set, then the mem_* fields are valid.
    mem_lower and mem_upper indicate the amount of lower and upper memory, respectively, in kilobytes kibibytes.

    Lower memory starts at address 0, and upper memory starts at address 1 megabyte mebibyte. The maximum possible value for lower memory is 640 kilobytes kibibytes.

    The value returned for upper memory is maximally the address of the first upper memory hole minus 1 megabyte mebibyte. It is not guaranteed to be this value.

    The two key aspects of this excerpt are:

    1. The flags field needs to be tested before accessing the mem_* fields.
    2. The mem_lower and mem_upper fields deal with memory holes very badly. Particularly mem_upper holds the size of the first continuous block of extended memory up to the first hole.

    The second point is important enough to deserve a further discussion.
    While the memory itself can be accessed, at the memory controller level1, as a continuous block it is not continuous at the memory subsystem level (what once was the north-bridge and now is the uncore).
    The memory subsystem creates holes in the continuous address range assigned to memory either by simply not reclaiming specific sub-ranges - thereby wasting that memory - or by shifting the sub-ranges at higher addresses.

    The reasons behind this seemingly strange behaviour have their roots deep in the historical evolution of the IBM PC.
    A full discussion is out of topic but a short version can be laid out. Initially, IBM reserved the first 640KiB of the 1MiB address space for the conventional memory and the remaining 384KiB for mapping the ROMs - including the BIOS ROM.
    Note that the memory controller must not respond to read/write accesses above the 640KiB in order for them to get to the ROMs.

    When the 1MiB barrier was broken (including or not the HMA at will) the range from 640KiB to 1MiB couldn't be used for backwards compatibility. This created the first hole: The standard hole.
    The 286 had only a 24-bit address bus, thus a 16MiB address space.
    At the same time, the ISA bus had already won against the MCA bus and took place in the IBM PC compatible hardware.
    Some ISA expansion cards came with expansion ROMs and the standard hole became exhausted, a 1MiB hole was reserved at the end of the 16MiB address space.
    I call this hole the ISA hole.
    Pretty much the same thing happened with PCI(e) when transitioning from 32-bit to 64-bit systems generating the PCI hole.

    In addition to this holes, there are memory ranges that are read/writable but carry precious information laid out by the BIOS - namely the ACPI tables.
    Such ranges cannot be carelessly overwritten by the OS and thus must be reported to it.

    All of this culminated in the e820 service that instead of returning the memory size (which in the light of the discussion above makes no sense) it returns a memory map made of ranges with their type (available, reserved, reclaimable, bad, NVS).

    This is also reflected in the GRUB multiboot_info_t structure fields mmap_* that you should use in place of the mem_lower and mem_upper as indicated in the OSDev wiki.


    1 The DIMMs are accessed with a rank, bank, column and row number but the memory controller usually makes this addressing linear and continuous.