Hi,
Recently I wanted to add some code in my kernel to get the amount of memory installed. My kernel works on PC so I used the Bios int 15. In the future I will deal with the e820 function, but for now I decided to use the e801 function, which looks simpler.
The function works fine when the amount of memory is greater than 16 MiB. But when it is less, I don't get the correct amount. Nowadays most PC have more than 16 MiB so it doesn't really matter for real uses, but just for the sake of curiosity, I wanted to know why the e801 gives so weird results.
I tried my kernel with Virtual PC, using a setting with 4 MiB of RAM. The e801 function reported 3840 KiB of RAM (This counts the first MiB).
I tried the same kind of experiment with VMWare, and 3008 KiB was reported.
I tried with Bochs, and got 4096 KiB (this is what I was expecting from the start).
I didn't try with a real PC because... well, it's quite hard to find a PC with less than 16 MiB nowadays
I've been googling like hell to find more information about the e801 function but couldn't find more than small descriptions about parameters and returns.
Do you have any idea why I get results that are less than the installed memory for an amount less than 16 MiB ?
MemorySize and int 15 / e801 weirdness
- Combuster
- Member
- Posts: 9301
- Joined: Wed Oct 18, 2006 3:45 am
- Libera.chat IRC: [com]buster
- Location: On the balcony, where I can actually keep 1½m distance
- Contact:
E801 reports the amount of memory after the 1M mark. Depending on how the emulator works, this can very well be one of 3M, 4M-640k, or 4M.
In this case bochs would always put the given amount of memory past the 1MB mark, and add 640k by default, VMware reports 3MB indicating some memory is overshadowed by the video card and bioses, while MSVPC leaves the A0000-FFFFF hole free of memory so no memory is wasted.
Then there are some specific areas for ACPI and stuff which eat away some memory as well, causing the bios to report less in order to protect these areas.
So to me, the numbers dont sound crazy at all.
In this case bochs would always put the given amount of memory past the 1MB mark, and add 640k by default, VMware reports 3MB indicating some memory is overshadowed by the video card and bioses, while MSVPC leaves the A0000-FFFFF hole free of memory so no memory is wasted.
Then there are some specific areas for ACPI and stuff which eat away some memory as well, causing the bios to report less in order to protect these areas.
So to me, the numbers dont sound crazy at all.
- smiddy
- Member
- Posts: 127
- Joined: Sun Oct 24, 2004 11:00 pm
- Location: In my cube, like a good leming. ;-)
Here's some code you can use, adjust it to suite your tastes:Hadrien wrote:Thanks Combuster. It looks like I'll be using e820 sooner than expected
Code: Select all
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; E820.ASM - Devloped to see what E820 produces.
;;
;; To Assemble: fasm getmem.asm
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
format MZ ; For FASM EXE format
entry main:BeginE820 ; Entry is into the main segment, just below
segment main use16
BeginE820:
jmp Start
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Program data items
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ProgramMessage db 13,10,'E820 - 1.00.1 Attempt to see E820 information... -smiddy',13,10,13,10,0
RebootMessage db 13,10,'Press any key to reboot...',13,10,0
NotSupportedMessage db '<-- CF: Function is not supported (that sucks!).',13,10,0
TheKBytes db 'K bytes.',0
JustTheBytes db ' bytes.',0
TheDash db ' - ',0
TheSpace db ' ',0
TheBIOSE820Message db 'E820: ',0
TheBIOSE820Message1 db 'hSz Base Address - Memory Length - Memory Type - ExtAttr',13,10,0
TheBIOSE820Message2 db ' : ',0
TheHexAfter db 'h',0
E820Buffer: TIMES 32 db 0
E820BufferLength equ $ - E820Buffer
TotalMemoryMessage db '-------------------------------------------------------------------------------',13,10
TotalMemoryMessage2 db ' : Total Memory : ',0
TotalMemoryMessage3 db '-------------------------------------------------------------------------------',13,10,0
TheHexEndOfLine db 'h',13,10,0
TheEndOfLine db 13,10,0
TheBIOSE820MemoryType1 db 'Available to OS ',0
TheBIOSE820MemoryType2 db 'Reserved Memory ',0
TheBIOSE820MemoryType3 db 'ACPI Reclaim Memory',0
TheBIOSE820MemoryType4 db 'ACPI NVS Memory ',0
TheBIOSE820MemoryType5 db 'Unuseable Range ',0
TheBIOSE820MemoryType6 db 'Undefined Range ',0
IgnoreRangeMessage db 'IAR',0
NonVolatileRangeMessage db ' NVR',0
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; This is the start of the pragram
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Start:
mov ax,9000h ; Set up stack
mov ss,ax
mov sp,9500h ; Stack pointer setup
mov ax,cs ; Setup the rest of the segment registers
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
mov [CodeSegment],ax ; Save current codesegment for decisions later
mov si,ProgramMessage ; Load program message
call PrintString ; Display welcome message
call BIOSE820 ; INT 0x15 AX=0xE820
cmp [CodeSegment],0202h ; Compare for booted executeable
je .Reboot ; Yes, go to reboot routine
mov ax,4C00h ; Terminate program if in DOS
int 21h ; DOS interrupt call
.Reboot:
mov si,RebootMessage ; Load reboot message into SI
call PrintString
mov ax,0 ; Load INT 16h get key function
int 16h ; BIOS interrupt call
db 0EAh ; Machine language to jump to
; address FFFF:0000 (reboot)
dw 0
dw 0FFFFh ; No return required
; we're rebooting!
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Call BIOS INT 15h AX=0E820h
;;
;; Display memory mapping if available
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
BIOSE820:
pusha
mov [TotalMemoryInstalled],dword 0 ; Zero out variable
mov si,TheBIOSE820Message ; Load entry PrintString
call PrintString ; Display the message
mov si,TheEndOfLine ; Load end of the line
call PrintString ; Display the message
mov di,E820Buffer ; Load buffer into DI (uses ES:DI)
xor ebx,ebx ; INT 0x15 AX=0xE820 continuation value
mov edx,0534D4150h ; Place "SMAP" into EDX
mov ecx,E820BufferLength ; Save buffer size into ECX
mov eax,0E820h ; Load EAX with E820h
int 15h ; Call BIOS interrupt
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; CF on first call to INT 0x15 AX=0E820h is an error
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
jc .NotSupported ; On error jump to the not supported section
mov si,TheBIOSE820Message1 ; Load the setup string
call PrintString ; Print the string
.BIOSE820_1:
cmp eax,0534D4150h ; Compare EAX with "SMAP"
stc ; Set the carry flag
jne .BIOSE820_4 ; If it isn't SMAP finish off look at E820
push ebx ; Save EBX
push ecx ; Save ECX
mov eax,ecx ; Place ECX into EAX to display
mov cl,8 ; Choose 8 bits to display
call ToHex ; Turn binary into hex ASCII
mov si,HexBuffer ; Load hex buffer
call PrintString ; Print hex buffer (size of the record; all have been 14h)
mov si,TheBIOSE820Message2 ; Load the start of the record in tabular form
call PrintString ; Print the start
mov eax,[di + 4] ; Load EAX with BaseAddrHigh
mov CL,20h ; Choose 32 bits
call ToHex
mov si,HexBuffer
call PrintString
mov eax,[di + 0] ; Load EAX with BaseAddrLow
mov CL,20h
call ToHex
mov si,HexBuffer
call PrintString
mov si,TheDash
call PrintString
mov eax,[di + 12] ; Load EAX with LengthHigh
mov CL,20h
call ToHex
mov si,HexBuffer
call PrintString
mov eax,[di + 8] ; Load EAX with LengthLow
mov CL,20h
call ToHex
mov si,HexBuffer
call PrintString
mov si,TheHexAfter
call PrintString
mov si,TheDash
call PrintString
mov si,TheSpace
call PrintString
; mov eax,dword [di + 16] ; Load memory type into EAX
; mov CL,8 ; Only show the last 8 bits
; call ToHex
; mov si,HexBuffer
; call PrintString
mov si,TheBIOSE820MemoryType1
mov eax,dword [di + 16]
cmp eax,1
jg .NotTheOS
mov eax,[di + 8] ; Save low length for now (needs to include high later)
add eax,[TotalMemoryInstalled] ; Add already saved amount in variable
mov [TotalMemoryInstalled],eax ; Save variable
mov si,TheBIOSE820MemoryType1
jmp .DoPrintString
.NotTheOS:
cmp eax,2
jg .NotReserved
mov si,TheBIOSE820MemoryType2
jmp .DoPrintString
.NotReserved:
cmp eax,3
jg .NotReclaim
mov si,TheBIOSE820MemoryType3
jmp .DoPrintString
.NotReclaim:
cmp eax,4
jg .DoACPINVS
mov si,TheBIOSE820MemoryType4
jmp .DoPrintString
.DoACPINVS:
cmp eax,5
jg .DoPrintString
mov si,TheBIOSE820MemoryType5
.DoPrintString:
call PrintString
.BIOSE820_2:
pop ecx
cmp ecx,24 ; Is the record 24 bytes long
jne .EndLine
test dword [di + 20],1 ; Check for Ignore Address Range
je .IgnoreRange
jmp .CheckNonVolatile
.IgnoreRange:
mov si,IgnoreRangeMessage
call PrintString
.CheckNonVolatile:
test dword [di + 20],1 shl 1 ; Check for non volatile RAM
jne .EndLine
mov si,NonVolatileRangeMessage
call PrintString
.EndLine:
mov si,TheEndOfLine
call PrintString
pop ebx
or ebx,ebx
je .BIOSE820_3
mov edx,0534D4150h ; "SMAP"
mov ecx,E820BufferLength
mov eax,0E820h
int 15h
jnc .BIOSE820_1 ; No CF then continue looking at map
.BIOSE820_3:
clc
.BIOSE820_4:
mov si,TotalMemoryMessage
call PrintString
mov eax,[TotalMemoryInstalled]
mov cl,20h
call ToHex
mov edi,HexBuffer
mov eax,16
call AddForwardSpaces
mov si,HexBuffer
call PrintString
mov si,TheHexAfter
call PrintString
mov si,TheDash
call PrintString
mov eax,[TotalMemoryInstalled]
call ToDecimal
call AddCommas
mov edi,CommaBuffer
mov eax,14 ; String of 14, to compare
call AddForwardSpaces
mov si,CommaBuffer
call PrintString
mov si,JustTheBytes
call PrintString
mov si,TheEndOfLine
call PrintString
mov si,TotalMemoryMessage3
call PrintString
jmp .DoneE820
.NotSupported:
mov si,NotSupportedMessage
call PrintString
mov si,TheEndOfLine
call PrintString
.DoneE820:
popa
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Dumps DS:SI to the screen using video BIOS call or DOS call
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PrintString:
pusha
.DoItAgain:
lodsb ; Load byte at DS:SI into AL
or al,al ; Test if character is 0
; (end of string)
jz .Done ; Go to Done if Zero
cmp [CodeSegment],0202h ; Did we boot this puppy?
jne .DOS ; No, then do DOS routine
;;;;;;;;;;;;;;;;;;;;;;;
;; BIOS Print Character
;;;;;;;;;;;;;;;;;;;;;;;
.BIOS:
mov ah,0Eh ; Put character on screen at the
; currrent cursor location
mov bx,7 ; Video attribute for the character
int 10h ; Call BIOS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; DOS Print Character (added to use pipes)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.DOS:
mov dl,al ; Put character into DL
mov ah,2 ; Output Character (Interrupt 21h, service 2)
int 21h
jmp .DoItAgain
.Done:
popa
ret ; Return to caller
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; ToHex
;; Loads HexBuffer with ASCII corresponding to 8, 16, or 32 bit interger in hex.
;; Requires interger in AL, AX, or EAX depending on bit size
;; Requires the number of bits in the CL
;; Returns a full buffer or an empty buffer on error
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ToHex:
pusha
MOV BX,0 ; Load BX with pointer offset
MOV edx,EAX ; Save the EAX to EDX
CMP CL,8 ; Check for 8 bits
JNE .Check16
JMP .ConverterLoop ; Start loading the buffer
.Check16:
CMP CL,10h ; Check for 16 bits
JNE .Check32
JMP .ConverterLoop ; Start loading the buffer
.Check32:
CMP CL,20h ; Check for 32 bits
JNE .ErrorBits
.ConverterLoop:
MOV EAX,edx ; Reload EAX with the converter
SUB CL,4 ; Lower bit count by 4 bits
SHR EAX,CL
AND AL,0Fh
ADD AL,'0'
CMP AL,'9'
JBE .LoadBuffer
ADD AL,'A'-'0'-10 ; Convert to "A" to "F"
.LoadBuffer:
MOV [HexBuffer + BX],AL ; Load buffer with AL
INC BX ; Increment buffer pointer
CMP CL,0 ; Check if we're done
JNE .ConverterLoop ; Do next byte
.ErrorBits:
MOV byte [HexBuffer + BX],0 ; End the string with a zero
popa
RET
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; ToDecimal - converts whatever is in EAX to ASCII string, places it in a buffer
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
DivisorTable dd 1000000000,100000000,10000000,1000000,100000,10000,1000,100,10
ToDecimal:
pusha
mov byte [ZeroNotLoaded],0 ; Initialize variable
mov word [DivisorNumber],0 ; Initialize variable
MOV BX,0 ; Start BX at zero
.LoopToDecimal:
mov EDX,0
push eax
movzx eax,word [DivisorNumber]
mov ecx,4
mul ecx
push bx
mov bx,ax
mov ECX,DWORD [DivisorTable + bx]
pop bx
pop eax
DIV ECX
inc word [DivisorNumber]
cmp al,0
je .CheckZeroNotLoaded
.ReturnToLoop:
ADD AL,48 ; Update to an ASCII character representation
MOV [DecimalBuffer + BX],AL ; Load decimal buffer
mov byte [ZeroNotLoaded],1 ; Gone through the loop at least once, show it
INC BX ; Increment the BX
cmp ecx,dword [DivisorTable + (8 * 4)] ; Was that the last divisor?
je .MoveOut ; Yep, do the one's column. :D
mov EAX,EDX ; Put remainder in EAX
jmp .LoopToDecimal
.CheckZeroNotLoaded:
cmp byte [ZeroNotLoaded],1
je .ReturnToLoop
mov eax,edx
jmp .LoopToDecimal
.MoveOut:
mov EAX,EDX ; Put remainder in EAX
ADD AL,48 ; Update to an ASCII character representation
MOV [DecimalBuffer + BX],AL ; Load decimal buffer
INC BX ; Increment the BX
MOV DL,0
MOV [DecimalBuffer + BX],DL ; Load decimal buffer with ending zero
popa
RET
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; StringLength:
;;
;; Assumes string start at ES:EDI
;;
;; Returns the string length in the ECX
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
StringLength:
push edi
push eax
sub ecx,ecx
sub al,al
not ecx
cld
repne scasb
not ecx
dec ecx
pop eax
pop edi
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; AddCommas:
;;
;; Assumes string start at DS:ESI
;;
;; Populates CommasBuffer with at most three extra commas
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
AddCommas:
push eax
push ebx
push ecx
push edx
mov ecx,0 ; Set ECX to 0
mov edi,DecimalBuffer ; Place a pointer to DecimalBuffer in EDI
call StringLength ; Get the string length of DecimalBuffer
cmp ecx,4
jb .ZeroCommas
cmp ecx,7
jb .OneComma
cmp ecx,10
jb .TwoCommas
jmp .ThreeCommas
.ZeroCommas:
mov eax,0
jmp .GoOn
.OneComma:
mov eax,1
jmp .GoOn
.TwoCommas:
mov eax,2
jmp .GoOn
.ThreeCommas:
mov eax,3
.GoOn:
dec ecx ; Go to last character
mov [CommasNeeded],eax ; Save the number of need commas
mov ebx,eax ; Save EAX to EBX
add ebx,ecx ; Increase number for string (to new last character location)
mov al,0 ; Put 0 in AL for null termination on string
mov [CommaBuffer + ebx + 1],al ; End string with null from AL
mov edx,0 ; zero out EDX for placing commas
.Loop:
cmp edx,3 ; If so, add a comma
je .AddComma
cmp edx,6 ; If so, add a comma
je .AddComma
cmp edx,9 ; If so, add a comma
je .AddComma
jmp .NormalExchange
.AddComma:
mov [CommaBuffer + ebx],byte ','
dec ebx
.NormalExchange:
mov AL,byte [DecimalBuffer + ecx] ; Get character from ECX location in DecimalBuffer
mov [CommaBuffer + ebx],AL ; Put character into CommaBuffer
cmp ecx,0 ; Are we done?
je .Done ; Yes, goto done
dec ecx ; Subtract 1 from ECX to point to next needed character
dec ebx ; Decriment 1 from EBX into new CommaBuffer
inc edx ; Add number of characters moved
jmp .Loop ; Do it again
.Done:
pop edx
pop ecx
pop ebx
pop eax
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; AddForwardSpaces:
;;
;; Assumes string start at ES:EDI, total length in EAX
;;
;; Prints forward spaces to the screen given total length in EAX
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
AddForwardSpaces:
push edi
push esi
push eax
push ecx
call StringLength
cmp eax,ecx
jbe .Done ; If less or equal than total length, nothing to do.
.MainLoop:
mov si,TheSpace
call PrintString
inc ecx
cmp eax,ecx
ja .MainLoop
.Done:
pop ecx
pop eax
pop esi
pop edi
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Uninitiated variables
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
CodeSegment rw 1 ; Saves the codesegment for decisions
TotalMemoryInstalled rd 1 ; Saves the total useable memory of the system
HexBuffer rb 18 ; Sixteen bytes with the ending zero
DecimalBuffer rb 12 ; Eleven bytes with the ending zero
CommaBuffer rb 15 ; Fourteen bytes for DecimalBuffer information
CommasNeeded rd 1 ; Saving commas needed
TheRemainder rd 1 ; Saving the Remainder
TheResult rd 1 ; Saving the Result
OrderOfTens rd 1 ; Saving order of tens (10^n)
ZeroNotLoaded rb 1 ; Used for checking zero starting numbers
DivisorNumber rw 1 ; Used to keep track of division
E820MemoryMapBuffer rb 10240 ; Should be enough for entire map, more can be added later