No matter the ultimate goal of any legacy X86 system, startup or prologue, should take into consideration the fundamentals. In this case, the intention before dropping into protected or long modes, the user has a means by which to explore the system and have a comprehensive initial screen displaying essential details.
As I don't really expect this code to be implemented by anyone else, the intent is to be informative and detailed enough to be critiqued. This is why the actual address where the code will be executed and opcodes are including in listing.
To facilitate this informative initial screen and for the most part, just for curiosity's sake, the 8 general purpose and 6 segment registers are preserved as passed by BIOS:
7C00 60 pushaw 7C01 06 push es 7C02 1E push ds 7C03 0E push cs 7C04 16 push ss 7C05 0FA0 push fs 7C07 0FA8 push gs
There is a lot to be said for backward compatibility, but as I want to populate conventional space (A0000 - 500) with as many even 512 byte sectors (1277) as possible, the boot sector is not really in a convenient spot. There are three significant features this next bit addresses:
- Extends boot loader beyond 1 sector.
CSto a known value.
- Moves code to lowest possible position in memory.
7C09 B8 5000 mov ax, 0x50 7C0C 8EC0 mov es, ax 7C0E 31DB xor bx, bx ; ES:BX = Destination 7C10 B9 0100 mov cx, 1 ; Re-read sector 0 7C13 B8 0402 mov ax, 0x204 ; 7C16 CD13 int DISKIO 7C18 7303 jnc 7c1d 7C1A F4 hlt 7C1B EBFE jmp $ 7C1D EA 6000 5000 jmp 0x50:0x60 ; Long jump, setting CS
Initially, I haven't implemented an error trap as if there wasn't a problem reading the first sector, then the likelihood of there being one in the next three is negligible.
The next thing is probably one of the most important, as the system needs a reasonably large space for stack and temporary data. My coding style is to utilize procedure frames as much as possible and they can be pretty large at times.
560 FA cli ; Disable interrupts 561 CD12 int 0x12 ; Get # of 1k blocks 563 B1 40 mov cl, 64 ; 1k Blocks to reserve 565 29C8 sub ax, cx 567 C1E0 06 shl ax, 6 ; AX = Bottom of stack segment 56A 8EC0 mov es, ax
Without implementing something that has too much overhead, filling the 64K stack frame with
-1 will allow the stack to be probed to see how deep it has been penetrated if necessary.
56C 31FF xor di, di 56E C1E1 09 shl cx, 9 ; 40 << 9 = 512 * 64 = 32768 571 83C8FF or ax, -1 574 F3AB rep stosw ; Fill frame with FFFFH
Before we can set
SS:SP, those 14 values saved right at the start need to be moved from the existing frame to a new one. There is also going to be a 160-byte scratch area reserved at the top of the frame that will be addressed by
576 BD 60FF mov bp, 0xff60 ; Base of scratch area 579 89EF mov di, bp 57B 83EF 1C sub di, 0x1c ; Space for 14 words 57E 89FB mov bx, di 580 89E6 mov si, sp 582 16 push ss 583 1F pop ds 584 B1 07 mov cl, 7 586 F366A5 rep movsd
Lastly, set new
589 89DC mov sp, bx 58B 06 push es 58C 17 pop ss 58D FB sti
Notice that some procedures are page aligned. This is not done for any sort of optimization, but I can make small changes in a routine with affecting the one below and as I don't use symbols in BOCH's even addresses are just a little easier to remember.
At this point, anything the BIOS has passed on the stack is gone, including the return address into it. I've never come across anything indicating this to be of any relevance but would need to be taken into account if so.
Anyone interested in experimenting with this can PM me and I'll zip up a tarball with the complete project.