Detecting Memory (x86): Difference between revisions

m
Fix lint errors
[unchecked revision][unchecked revision]
(→‎BIOS Function: INT 0x15, AX = 0xE881: no, that link doesn't help)
m (Fix lint errors)
 
(5 intermediate revisions by 3 users not shown)
Line 31:
 
Usage:
<sourcesyntaxhighlight lang="asm">
; Clear carry flag
clc
Line 42:
 
; AX = amount of continuous memory in KB starting from 0.
</syntaxhighlight>
</source>
 
Note: this function is supposed to be always present, and may not modify the carry flag. If an emulator doesn't support it, the carry flag will be set, indicating error.
Line 60:
In reality, this function returns an unsorted list that may contain unused entries and (in rare/dodgy cases) may return overlapping areas.
Each list entry is stored in memory at ES:DI, and DI is <b>not</b> incremented for you. The format of an entry is 2 uint64_t's and a uint32_t in the 20 byte version,
plus one additional uint32_t in the 24 byte ACPI 3.0 version (but nobody has ever seen a 24 byte one).
It is probably best to always store the list entries as 24 byte quantities -- to preserve uint64_t alignments, if nothing else. (Make sure to set
that last uint64_t to 1 before each call, to make your map compatible with ACPI).
Line 151:
 
Linux Usage:
<sourcesyntaxhighlight lang="asm">
XOR CX, CX
XOR DX, DX
Line 168:
; AX = number of contiguous Kb, 1M to 16M
; BX = contiguous 64Kb pages above 16M
</syntaxhighlight>
</source>
 
====BIOS Function: INT 0x15, AX = 0xDA88====
Line 182:
 
Usage:
<sourcesyntaxhighlight lang="asm">
CLC ; CF bug workaround
MOV AH, 0x88
Line 190:
JE SHORT .ERR
; AX = number of contiguous KB above 1M
</syntaxhighlight>
</source>
 
====BIOS Function: INT 0x15, AH = 0x8A====
Line 237:
 
Usage:
<sourcesyntaxhighlight lang="c">
unsigned short total;
unsigned char lowmem, highmem;
Line 248:
total = lowmem | highmem << 8;
return total;
</syntaxhighlight>
</source>
 
====E820h====
Line 305:
 
{{Quotation
| If bit 6 in the flags uint16_t is set, then the mmap_* fields are valid, and indicate the address and length of a buffer containing a memory map of the machine provided by the BIOS. mmap_addr is the address, and mmap_length is the total size of the buffer. The buffer consists of one or more of the following size/structure pairs (size is really used for skipping to the next pair<nowiki>):''</nowiki>
}}
 
Line 311:
Taking this into account, our example code would look like the following. Note that if you prefer a version that does not require the multiboot.h header downloaded from the link above, there is a version listed in the code examples section of this article.
 
<sourcesyntaxhighlight lang="c">
 
 
Line 348:
}
}
</syntaxhighlight>
</source>
 
'''WARNING:''' If you downloaded the multiboot header from gnu.org (linked above) you probably got a version which defines the base address and length fields as one 64-bit unsigned integer each, rather than two 32-bit unsigned integers each. [https://forum.osdev.org/viewtopic.php?t=30318 This may cause gcc to pack the structure incorrectly] which can lead to nonsensical values when you try to read it.
Line 355:
Alternatively, you can modify the multiboot header, specifically the multiboot_mmap_entry struct, to the following to get the correct values:
 
<sourcesyntaxhighlight lang="c">
struct multiboot_mmap_entry
{
Line 371:
} __attribute__((packed));
typedef struct multiboot_mmap_entry multiboot_memory_map_t;
</syntaxhighlight>
</source>
 
Each multiboot mmap entry is stored as the following:
Line 413:
== What about on UEFI? ==
On UEFI, you have 'BootServices->GetMemoryMap'. This function is similar to E820 and is the only solution on new UEFI machines. Basically, to use, first you call it once to get the size of the memory map. Then you allocate a buffer of that size, and then call again to get the map itself. Watch out, by allocating memory you could increase the size of the memory map. Considering that a new allocation can split a free memory area into two, you should add space for 2 additional memory descriptors. It returns an array of EFI_MEMORY_DESCRIPTORs. They have the following format (taken from GNU EFI):
<sourcesyntaxhighlight lang="c">
typedef struct {
UINT32 Type; // EFI_MEMORY_TYPE, Field size is 32 bits followed by 32 bit pad
Line 422:
UINT64 Attribute; // Field size is 64 bits
} EFI_MEMORY_DESCRIPTOR;
</syntaxhighlight>
</source>
To traverse them, you can use the NEXT_MEMORY_DESCRITOR macro.
 
Memory types are different to the E820 codes. For converting, see [https://github.com/tianocore/edk2/blob/70d5086c3274b1a5b099d642d546581070374e6e/OvmfPkg/Csm/LegacyBiosDxe/LegacyBootSupport.c#L1601 CSM E820 compatibility].
<sourcesyntaxhighlight lang="c">
typedef enum {
EfiReservedMemoryType,
Line 445:
EfiMaxMemoryType
} EFI_MEMORY_TYPE;
</syntaxhighlight>
</source>
 
==Code Examples==
Line 453:
Declare the appropriate structure, get the pointer to the first instance, grab whatever address and length information you want, and finally skip to the next memory map instance by adding size+sizeof(mmap->size) to the pointer, because mmap->size does not take itself into account and because GRUB treating base_addr_low as offset 0 in the structure. You must also use mmap_length to make sure you don't overshoot the entire buffer.
 
<sourcesyntaxhighlight lang="c">
typedef struct multiboot_memory_map {
unsigned int size;
Line 475:
...
}
</syntaxhighlight>
</source>
 
===Getting an E820 Memory Map===
 
<sourcesyntaxhighlight lang="asm">
; use the INT 0x15, eax= 0xE820 BIOS function to get a memory map
; note: initially di is 0, be sure to set it to a value so that the BIOS code will not be overwritten.
Line 531:
stc ; "function unsupported" error exit
ret
</syntaxhighlight>
</source>
 
Sample in C (assuming we are in a bootloader environment, real mode, DS and CS = 0000):
<sourcesyntaxhighlight lang="c">
// running in real mode may require:
__asm__(".code16gcc\n");
Line 592:
}
}
</syntaxhighlight>
</source>
 
===Getting an UEFI Memory Map===
<sourcesyntaxhighlight lang="c">
EFI_STATUS Status;
EFI_MEMORY_DESCRIPTOR *EfiMemoryMap;
Line 641:
EfiEntry = NEXT_MEMORY_DESCRIPTOR (EfiEntry, EfiDescriptorSize);
} while((UINT8*)EfiEntry < (UINT8*)EfiMemoryMap + EfiMemoryMapSize);
</syntaxhighlight>
</source>
 
===Manual Probing in C===
Line 649:
* the assembly language manual probing code that follows this example is better
 
<sourcesyntaxhighlight lang="c">
/*
* void count_memory (void)
Line 724:
outb(0xA1, irq2);
}
</syntaxhighlight>
</source>
 
===Manual Probing in ASM===
Line 741:
* it's better to assume that memory holes are present (and risk skipping some RAM) than to assume that memory holes aren't present (and risk crashing). This means assuming that the area from 0x00F00000 to 0x00FFFFFF can't be used and not probing this area at all (it's possible that some sort of ISA device is in this area, and that any write to this area can cause problems).
 
<sourcesyntaxhighlight lang="asm">
;Probe to see if there's RAM at a certain address
;
Line 798:
pop eax
ret
</syntaxhighlight>
</source>
 
Further Notes:
Line 813:
 
[[Category:X86]]
[[Category:Physical Memory]]
[[Category:Hardware Detection]]