Search code examples
assemblyx86reverse-engineeringbios

How can early BIOS use CALL?


I am, purely for hobby reasons, trying to understand some low-level code in the PC. I downloaded an outdated BIOS ROM image for a random old Gigabyte MB (https://www.gigabyte.com/Motherboard/GA-8I845GE775-G-rev-10/support#support-dl-bios), it's almost 15 years old so I hope it doesn't harm anyone's IP rights. I'll be using this file for reference.

I'm stumbled right at the beginning. These seem to be the very first instructions the processor sees after power up:

f000:fff0  ljmp 0xf000:0xe05b
f000:e05b  jmp 0xf46c
f000:f46c  cli
f000:f46d  cld
f000:f470  smsw ax       ; read CR0
f000:f473  test al, 1    ; test Protected Mode Enable
f000:f475  je 0xf480
[assuming PE is zero – jump:]
f000:f480  jmp 0xe043
f000:e043  mov al, 0x8f
f000:e045  out 0x70, al  ; CMOS controller: disable NMI, set index 0xf
f000:e047  out 0xeb, al  ; this port is presumably unoccupied: just a delay mechanism
f000:e049  in al, 0x71   ; read 0xf (CMOS Shutdown Status)
f000:e04b  out 0xeb, al  ; more delay
f000:e04d  or al, al
f000:e04f  jmp 0xf483
f000:f483  jne 0xf488
[assuming status = 0 (Power on or soft reset) – pass:]
f000:f485  call 0x4dee

Assuming the CMOS shutdown status is zero upon powering the computer on, the BIOS issues a call at f000:f485. At that early moment, no attempt would have been done at detecting whether any RAM is even present. The stack segment and stack pointer have not been set up, either. The code at f000:f485 indeed looks like a function and ends with a ret. How is this possible, where does the call store the return address?

Or am I misunderstanding the values returned from port 0x71? I used these two documents for reference:

The meaning of writing 0x8f to 0x70: https://wiki.osdev.org/CMOS#CMOS_Registers

The meaning of the values subsequently read from 0x71: http://www.bioscentral.com/misc/cmosmap.htm


Solution

  • There are 2 cases:

    a) "cold boot" (e.g. when the computer wasn't running and you turn it on). In this case; the memory controllers won't be initialized, the shutdown status won't be set, the jne 0xf488 will cause the code to jump somewhere else, and the call won't be executed.

    b) "warm boot" (e.g. when you reset the computer after it's already running). In this case; the memory controllers are still initialized (from when "cold boot" happened previously), shutdown status will be set, the jne 0xf488 won't do anything, and the call will be executed (but that's fine, because the memory controllers are initialized).

    Assuming the CMOS shutdown status is zero upon powering the computer on...

    No. The shutdown status byte values are (according to Ralph Brown's Interrupt List):

    • 0x00 = software reset or unexpected reset
    • 0x01 = reset after memory size check in virtual mode
    • 0x02 = reset after successful memory test in real/virtual mode
    • 0x03 = reset after failed memory test in real/virtual mode
    • 0x04 = INT 0x19 reboot
    • 0x05 = flush keyboard and jump via. 0x0040:0x0067
    • 0x06 = reset (after successful test in virtual mode)
    • 0x07 = reset (after failed test in virtual mode)
    • 0x08 = used by POST during protected=mode RAM test
    • 0x09 = used for INT 0x15/0x87 (block move) support
    • 0x0A = resume execution by JMP via. 0x0040:0x0067
    • 0x0B = resume execution by IRET via. 0x0040:0x0067
    • 0x0C = resume execution by RETF via. 0x0040:0x0067
    • 0x0D to 0xFF = perform power-on reset ("cold boot")

    Note: Ralph Brown's Interrupt List hasn't been maintained for ages. I'd expect some of this is old and/or only applies to some very old computers (e.g. 80286, which didn't have any way to leave protect mode other than reset).