Category Archives: boot process

oemboot

To build on the vdi_info post, I am throwing in my oemboot file. I have used this several times over the years in various forms, in general just to get a loader binary into memory. In its current form it uses LBA after doing an int 13 0x41 check error. Are there bugs? Maybe. It works for my purposes.

Here is a brief description of how it works:

  • 1. Scan directory for a loader file with a name defined by LOADFILE, 8.3 format
  • 2. Loads LOADFILE to memory defined by LOAD_SEG and LOAD_OFF, I use 0x7C000.
  • 3. Far jump to start of loader. The jmp far ptr loader_start is around line 470 and before the jump I load registers with “stuff” I want to pass to the loader.

Some simple BPB Error Codes:
1 – No bios extensions – IBM/MS INT 13 Extensions not supported
2 – Root directory read – could not read root directory
3 – LOADFILE not found
5 – FAT error – bad read or sig error

That is about it. The oemboot.asm file is very commented with good info and random thoughts, so I could remember why I had done certain things. The example files are located:

Code repo: Gitlab or My Gitlab

Okay, let’s put my simple test environment together. I make a raw vdi image then run it attached to a VM so I can format it to FAT16 and make the partition active. Next, I use vdi_info.exe to create bootpbp.inc which will be an include for oemboot.asm. I use uasm to compile oemboot.asm with the following commands:

uasm32 -bin [path to includes] -Fo oemboot.bin oemboot.asm

I write the oemboot.bin back to the vdi using the vdi_info.exe -w option. Next, the loader I am using just gets copied to the FAT16 vdi and boot with bochs.

vdi_info

General

This is a utility of need. I needed to get info from a virtualbox vdi image so that I could modify the boot sector. By modify, I mean to dump the required BPB info to an include file and graft my compiled oemboot boot sector on to the vdi. I did this a year ago and it has been used via a makefile since. What this means is I need to go back and figure out everything it does.

Specifics

Again, this tool is to make my life easier. Currently, I use Open Watcom v2 maintained by Jiří Malák. I compile it on a 32bit system, either an old Win XP image or a ReactOS image. I assume it would compile on AcraOS, I just have not tried to do so. I have only been using FAT16 because it is simple, and I just wanted to get things running.

My build flow is to compile my boot loader on a 32bit system, transfer the boot loader to a vdi image, and boot via bochs from Linux. To get the boot loader in memory, I use a custom boot sector named oemboot (I know, not very original). Without going into details, a vdi has a different structure than a normal hard drive image and it is tedious to extract the vdi information to an include file (BPB include) and, after compiling oemboot, writing the boot sector back to the vdi. The result was this tool that made the task easier when swapped to a new vdi or write a new boot sector to my dev image.

Code repo: Gitlab or My Gitlab

Be careful, while I put the repo together, I already see things I need to fix. Not big things, but mistakes that happen in a hurry. For example, in the v1.0 source if WRITEBPB is true I should skip the input file check until later. Oops.

How it works

The command line options are if you run vdi_info -h :

vdi_info: get dev image vdi information.
Version 1.0, compiled Nov  5 2022 with OpenWatcom 2.0

usage: vdi_info [-f alt filename][-i][-m][-p][-w] vdi_filename

   -f #  alternate input filename for -w option.
   -i    Dump BPB inc to bootpbp.inc.
   -m    Dump MBR to mbr.bin.
   -b    Dump active BPB to bpb.bin.
   -w    Write newmbr.bin to active BPB.
   -h    help message.

Valid option combinations:
  1. -m, -b, or -i single or all at once to dump MBR, BPB and INC.
  2. -w only or with optional -f <filename> for alternate BPB image to add.

The output with vdi name input results in some info about the image:

vdi_info fat16drive.vdi

Info: Input file found (fat16drive.vdi) and opened.

<<< Oracle VM VirtualBox Disk Image >>>
Image Version: 1.1
Image Sig: BEDA107F
Header size: 190
Drive Start: 200000
MBR Sig: AA55 Valid MBR signature!

Part num  1
State: 80
Head:  1
End:   1
Type:  6
Head:  F
End:   CD3F
Off:   3F
Sect:  32AE1

Part num  2
State: 0
Head:  0
End:   0
Type:  0
Head:  0
End:   0
Off:   0
Sect:  0

Part num  3
State: 0
Head:  0
End:   0
Type:  0
Head:  0
End:   0
Off:   0
Sect:  0

Part num  4
State: 0
Head:  0
End:   0
Type:  0
Head:  0
End:   0
Off:   0
Sect:  0

Active partition 1 found at offset 3F
Boot sector VDI offset:  2129408

All done!

-m

Read the vdi and get what is expected to be the MBR and check for a valid signature. If the -m option is passed then write the MBR to a file named mbr.bin.

-b

The MBR is scanned for the first active partition and extracted. If the -b option is passed, then write the BPB of the first active partition to a file named bpb.bin.

-i

So, here is the real use for this tool. When vdi_info -i <vdi file> is executed it outputs bootpbp.inc. Why did I name the output bootpbp.inc ? Who knows, I was probably tired or dyslexic. Anyway, I should really change it to bootbpb.inc. Passing -i goes through all the step above, read MBR, scan for first active partition, load the BPB of that partition, and dump it to an include file. The following is the output for my current development image.

; *** BPB output of vdi_info *** 

BS_OEMName      db  'MSDOS5.0'

BPB_BytsPerSec 	dw  0X0200
BPB_SecPerClus 	db  0X04
BPB_RsvdSecCnt 	dw  0X0001
BPB_NumFATs    	db  0X02
BPB_RootEntCnt 	dw  0X0200
BPB_TotSec16    dw  000000
BPB_Media       db  0XF8
BPB_FATSz16 	dw  0X00CB
BPB_SecPerTrk   dw  0X003F
BPB_NumHeads    dw  0X0010
BPB_HiddSec     dd  0X0000003F
BPB_TotSec32 	dd  0X00032AE1

BS_DrvNum       db  0X80
BS_Reserved1    db  0000
BS_BootSig      db  0X29
BS_VolID        dd  0X2A6316E8
BS_VolLab       db  'TESTDRV    '
BS_FilSysType   db  'FAT16   '

; *** end of BPB output ***

-w

This finds the active partition and writes a new boot sector from the file named newbpb.bin or the file name passed with the -f option.

Disk Address Packet (DAP)

;***********************************************************************
; *
; *  daptable.inc -- Disk Address Packet
; *
; *  ===================================================================
; *
; *    Version 1.0       Michael K Greene 
; *                      March 2019
; *
; *  ===================================================================
; *
; *  Description: DAP : LBA Disk Address Packet
; *
; *  ===================================================================
; *
; *   This program is free software; you can redistribute it and/or modify
; *   it under the terms of the GNU General Public License as published by
; *   the Free Software Foundation; either version 2 of the License, or
; *   (at your option) any later version.
; *
; *   This program is distributed in the hope that it will be useful,
; *   but WITHOUT ANY WARRANTY; without even the implied warranty of
; *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
; *   GNU General Public License for more details.
; *
; *   You should have received a copy of the GNU General Public License
; *   along with this program; if not, write to the Free Software
; *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
; *
; **********************************************************************


DAP_TABLE    struc

DAP_size        db  0x10        ; packet size
DAP_unused      db  0x00        ; unused
DAP_num_sectors	dw  0x0000      ; number of sectors, I usually set to cluster size
DAP_buffer_off	dw  0x7E00      ; read to buffer offset
DAP_buffer_seg	dw  0x0000      ; read to buffer segment
DAP_sector_low	dd  0x00000000	; lower LBA	
DAP_sector_high	dd  0x00000000	; upper LBA

DAP_TABLE    ends


OS/2 Loader References

Misc notes:

  1. MiscLdrNotes1
  2. MiscLdrNotes2
  3. MiscLdrNotes3
  4. BootableJFS boot block

The Sandpile pure technical x86 processor information
cpuid
A20 Line
Wasm
Wasm v1.7j
ReactOS
FreeDos
BonAFide OS Development
x86 Assembly Language FAQ – Microsoft MASM
x86 Assembly Language for Beginners – Part 1
Intel 80386 Reference Programmer’s Manual
OS/2 Docs online
http://es.ecomstation.ru/showarticle.php?id=177
Linux early setup
Interrupt Jump Table
Better int info
Dude, Where’s My 4 Gigabytes of RAM?
BIOS Central
A20 Line
EDM/2 Info:

  1. OS/2 2.0 Memory Initialization
  2. System dump
  3. Overall Architecture
  4. IPL Background
  5. DosHlp Module Content
  6. 16 bit support
  7. 32 bit support
  8. 16-32 bit support
  9. DosHlp Function Definitions
  10. OEMHLP$ Module Content

os2ldr entry values

Using the Bochs image the entry values for os2ldr are:

eax: 0x00000000
ecx: 0x00000000
edx: 0x00001480
ebx: 0x00000000
esp: 0x00005000
ebp: 0x00000000
esi: 0xFFFF000B
edi: 0x0000124A
eip: 0x00000000
eflags 0x00000246
IOPL=0 id vip vif ac vm rf nt of df IF tf sf ZF af PF cf
cs:s=0x1000, dl=0x0000ffff, dh=0x00009b01, valid=1
ds:s=0x8800, dl=0x8000ffff, dh=0x00009308, valid=7
ss:s=0x8800, dl=0x8000ffff, dh=0x00009308, valid=7
es:s=0x8800, dl=0x8000ffff, dh=0x00009308, valid=1
fs:s=0x3000, dl=0x0000ffff, dh=0x00009303, valid=7
gs:s=0x0000, dl=0x0000ffff, dh=0x00009300, valid=1
ldtr:s=0x0000, dl=0x0000ffff, dh=0x00008200, valid=1
tr:s=0x0000, dl=0x0000ffff, dh=0x00008300, valid=1
gdtr:base=0x000faeb2, limit=0x30
idtr:base=0x00000000, limit=0x3ff

DH boot mode flags:

  • bit 0 (NOVOLIO) on indicates that the mini-FSD does not use MFSH_DOVOLIO.
  • bit 1 (RIPL) on indicates that boot volume is not local (RIPL boot)
  • bit 2 (MINIFSD) on indicates that a mini-FSD is present.
  • bit 3 (RESERVED)
  • bit 4 (MICROFSD) on indicates that a micro-FSD is present.
  • bits 5-7 are reserved and MUST be zero.
DH = b X X X 1 X 1 0 0 = MINIFSD | MICROFSD

DL drive number for the boot disk. This parameter is ignored if either the NOVOLIO or MINIFSD bits are zero. 

DL = b 10000000 = 0x80

DS:SI is a pointer to the BOOT Media’s BPB. This parameter is ignored if either the NOVOLIO or MINIFSD bits are zero. 

8800:000B (0x8800B)

ES:DI is a pointer to a filetable structure. 

8800:124A (0x8924A)

The filetable structure has the following format:

struct FileTable {
    unsigned short ft_cfiles; /* # of entries in this table             */
    unsigned short ft_ldrseg; /* paragraph # where OS2LDR is loaded     */
    unsigned long  ft_ldrlen; /* length of OS2LDR in bytes              */
    unsigned short ft_museg;  /* paragraph # where microFSD is loaded   */
    unsigned long  ft_mulen;  /* length of microFSD in bytes            */
    unsigned short ft_mfsseg; /* paragraph # where miniFSD is loaded    */
    unsigned long  ft_mfslen; /* length of miniFSD in bytes             */
    unsigned short ft_ripseg; /* paragraph # where RIPL data is loaded  */
    unsigned long  ft_riplen; /* length of RIPL data in bytes           */
    /* The next four elements are 16:16 pointers to microFSD entry points     */
    unsigned short (far *ft_muOpen) (char far *pName, unsigned long far *pulFileSize);
    unsigned long (far *ft_muRead) (long loffseek, char far *pBuf, unsigned long cbBuf);
    unsigned long (far *ft_muClose)(void);
    unsigned long (far *ft_muTerminate)(void);
}

Bochs values:

filetable structure value
ft_cfiles 0x0003
ft_ldrseg 0x1000
ft_ldrlen 0x0000A800
ft_museg 0x8800
ft_mulen 0x00005000
ft_mfsseg 0x007C
ft_mfslen 0x0000EAE9
ft_ripseg 0x0000
ft_riplen 0x00000000
ft_muOpen 8800:1A9C
ft_muRead 8800:1BD4
ft_muClose 8800:1DAE
ft_muTerminate 8800:1DD4

The microFSD entry points interface is defined as follows:
mu_Open – is passed a far pointer to name of file to be opened and a far pointer to a ULONG to return the file’s size. The returned value (in AX) indicates success(0) or failure(non-0).
mu_Read – is passed a seek offset, a far pointer to a data buffer, and the size of the data buffer. The returned value(in DX:AX) indicates the number of bytes actually read.
mu_Close – has no parameters and expects no return value. It is a signal to the micro-FSD that the loader is done reading the current file.
mu_Terminate – has no parameters and expects no return value. It is a signal to the micro-FSD that the loader has finished reading the boot drive.
The loader will call the micro-FSD in a Open-Read-Read-….-Read-Close sequence with each file read in from the boot drive.

Protected Mode Basics by Robert Collins

I remember when I was first learning protected mode. I had barely taught myself assembly language, and I got this crazy idea that I wanted to teach myself protected mode. I went out and purchased an 80286 assembly language book that included some protected mode examples, and I was off to learn. Within a few hours, I realized that the book I had purchased didn’t have any usable examples, since the examples in the book were intended to be programmed in EPROM CHIPS. So I hit the bulletin boards in search of something I could use as a guiding example.

The only example I found was so poorly documented and convoluted with task switching that even now, many years later, I haven’t figured it out. So with my IBM Technical Reference Manual and my 80286 book, I sat down and tried to figure out protected mode. After spending forty hours in three days of trying, I finally copied some source code out of the IBM Technical Reference Manual, and I was able to enter protected mode and then return to DOS.

Since that time, I have learned much about protected mode and how the CPU handles it internally. I discovered that the CPU has a set of hidden registers that are inaccessible to applications. I also learned how these registers get loaded, their role in memory management, and most importantly, their exact contents. Even though these registers are inaccessible, understanding the role they play in memory management can be applied to application’s programming. Applying this knowledge to programming can result in applications that use less data, less code, and execute faster.

PROTECTED MODE BASICS

From an applications’ point of view, protected mode and real mode aren’t that different. Both use memory segmentation, interrupts, and device drivers to handle the hardware. But there are subtle differences that make porting DOS applications to protected mode non-trivial. In real mode, memory segmentation is handled automatically through the use of an internal mechanism, in conjunction with segment registers. The contents of these segment registers form part of the physical address that the CPU presents on the address bus (see figure 1a). The physical address is generated by multiplying the segment register by 16, then adding a 16-bit offset. Using 16-bit offsets implicitly limits the CPU to 64k segment sizes. Some programmers have programmed around this 64k segment size limitation by incrementing the contents of the segment registers. Their programs can point to 64k segments in 16-byte increments. Any program using this technique in protected mode would generate an exception (CPU-generated interrupt) — since segment registers aren’t used in the same manner. In protected mode, memory segmentation is defined by a set of tables (called descriptor tables) and the segment registers contain pointers into these tables. Each table entry is 8-bytes wide; therefore the values contained in the segment registers are defined in multiples of 8 (08h, 10h, 18h, etc.). The lower three bits of the segment register are defined, but for simplicity’s sake, let’s say that any program that loads a segment register with a value that isn’t a multiple of 8 will generate a protection error. There are two types of tables used to define memory segmentation: the Global Descriptor Table (GDT), and the Local Descriptor Table (LDT). The GDT contains segmentation information that all applications can access. The LDT contains segmentation information specific to a task or program. As previously mentioned, segment registers don’t form part of the physical address in protected mode, but instead are used as pointers to table entries in the GDT or LDT (see figure 1b). Each time a segment register is loaded, the base address is fetched from the table entry and stored in an internal, programer-invisible, register called the “segment descriptor cache.” The physical address presented on the CPU address bus is formed by adding the 16 or 32-bit offset to the base address in the descriptor cache.

Another major concern for porting real-mode applications to protected mode is the use of interrupts. In real mode, double-word pointers to interrupt routines lie at physical address 0 (‘386 specific: unless the IDTR has been changed).Figure 4a illustrates interrupt service addressing in real mode. When an interrupt is called or generated, the CPU looks up the address of the Interrupt Service Routine (ISR) in this interrupt vector table. After the CPU pushes the flags on the stack, it performs a far call to the address in the table. The information pushed on the stack is the same for software, hardware, or CPU generated interrupts.

In protected mode, the information pushed on the stack can vary, as can the base address of the interrupt vector table and the size of the interrupt table. The interrupt vector look up mechanism is also quite different from its real-mode counterpart. Figure 4b shows how interrupts are called from protected mode. After an interrupt is generated, the CPU compares the interrupt number (x8) against the size of the IDT — stored in the interrupt descriptor cache register. If the INT# x 8 doesn’t exceed the IDT size, then the interrupt is considered invokable, and the IDT base address is fetched from the descriptor cache; then the ISR’s protected mode address is fetched from the IDT. The ISR’s address is not a physical address but a protected mode, segmented address. Using the segment selector specified in the IDT, the CPU must perform the same limit-checking process again on the GDT to calculate the physical address of the ISR. Once the physical address is calculated, the CPU pushes the FLAGS, SEGMENT (selector), OFFSET, and possibly an ERROR CODE on the stack before branching to the ISR. ISRs for software and hardware interrupts needn’t be any different from their real-mode counterparts, but ISRs to service CPU generated interrupts and faults must be different.

The CPU generates three categories of interrupts: traps, faults, and aborts. The stack image varies from category to category, as an error code may, or may not, be pushed on the stack. Traps never push an error code; faults usually do; and aborts always do. Traps are similar to and include software interrupts. This type of interrupt is appropriately named, as the CPU is “trapping” the occurrence of an event. The CPU doesn’t know the event occurred until after the fact; thus it must trap the event before signalling the interrupt. Therefore, the return address of these ISR’s point to instruction following the occurrence of the event. Traps include division by 0, data breakpoints, and INT03. Faults occur because something went wrong — something that should be fixed. The CPU knows instantly that something is wrong and signals the interrupt-generating mechanism. The primary purpose of this type of ISR, is to correct the problem and restart the program exactly where it left off. For this reason, the return address of the ISR points to the faulting instruction — thus making the fault restartable. Aborts are the most severe type of interrupt and are considered non-restartable. An error code is pushed on the stack, but will always be 0. The CPU’s stack segment, and state machines, may be in an
indeterminate state, and attempting to restart an abort may cause unpredictable behavior.Table 1 categorizes the list of interrupts generated by the CPU for protected mode. In most cases, the CPU will also generate the same interrupt in real mode, but no error code is ever pushed on the stack.

I used to wonder why the BIOS can’t be used in protected mode. At that time, I thought it would be easy to write mode-independent code: just don’t do any FAR JUMPs, or FAR CALLS. But it’s not as simple as following these conventions. In addition to avoiding the use of far jumps and calls, the ISR must remove any error code pushed on the stack. This is where the impossibilities begin. Since the error code is placed on the stack only in protected mode, we need to detect whether or not we are in protected mode before the error code is removed. To determine this, we need access to the machine status work (MSW), or the system register CR0. Accessing the MSW can be done in any priviledge level, but accessing CR0 can only be done at the highest privilege level — level 0. If the user program is executing at any level less than 0, then we might not be able to access these registers. It can be done through the use of a special call gate that allows us to switch privilege levels before calling the ISR. This isn’t needed if we use the SMSW instruction. But even with that problem solved, let’s suppose the program left a real-mode value in any one of the segment registers. If the ISR pushes and subsequently pops any of these registers, the pop will cause the CPU to look for a selector in the GDT, or LDT. More than likely, using a real-mode value will cause a protection error. Therefore, using the BIOS in protected mode is nearly impossible. If there were a defined set of rules (a standard) that all programmers and operating systems followed, it could be done.

ENTERING PROTECTED MODE

Our goal is to enter protected mode, and leave protected mode and return to DOS. The ‘286 has no internal mechanism to exit protected mode: once you are in protected mode, you are there to stay. IBM recognized this, and implemented a hardware solution that would take the ‘286 out of protected mode by resetting the CPU. Since the power-on state of the ‘286 is real mode, simply resetting the CPU will return to real mode. But this introduces a slight problem, as the CPU won’t continue executing where it left off. At reset, the CPU starts executing at the top of memory, in the BIOS. Without a protocol to tell the BIOS that we reset the CPU for the purpose of exiting protected mode, the BIOS would have no way to return control back to the user program. IBM implemented a very simple protocol by writing a code to CMOS RAM (CMOS) where the BIOS can check this code and decide what to do. Immediately after the BIOS starts executing from the reset vector, it checks this code in CMOS to determine if the CPU was reset for the purpose of exiting protected mode. Depending on the code in CMOS, the BIOS can return control back to the user program and continue executing.

Resetting the CPU isn’t without its ramifications; all the CPU registers are destroyed, and the interrupt mask in the Programmable Interrupt Controller (PIC) is sometimes re-programmed by the BIOS (depending on the shutdown type). Therefore, it is the program’s responsibility to save the PIC mask, stack pointer, and return address before entering protected mode. The PIC mask and stack pointer must be stored in the user’s data segment, but the return address must be stored at a fixed location defined in the BIOS data segment — at 40:67h.

Next, we set the code in CMOS that tells BIOS we will exit protected mode and return to the user’s program. This is simply done by writing a value to the two CMOS I/O ports. After the CPU gets reset, and BIOS checks the CMOS code, BIOS will clear the CMOS code, so subsequent resets won’t cause unexpected results. After setting the code in CMOS, the program must build the GDT. (See the appropriate Intel programmer’s reference manual for a description of the GDT.) The limit, and access rights may be filled in by the compiler, as these values are static. But the base addresses of each segment aren’t known until run-time; therefore the program must fill them in the GDT. Our program will build a GDT containing the code, data, and stack segments addressed by our program. One last GDT entry will point to 1M for illustrative purposes.

Accessing memory at 1M isn’t as simple as creating a GDT entry and using it. The 8086 has the potential to address 64k (minus 16 bytes) beyond the maximum addressability of 1M — all it lacks is a 21st address line. The 8086 only has 20 address lines (A00..A19), and any attempt to address beyond 1M will wrap around to 0 because of the absence of A20. The ‘286 has 24 bits of addressability (A00..A23) and doesn’t behave like the 8086 in this respect. Any attempt to address beyond 1M (FFFF:0010 – FFFF:FFFF) will happily assert A20, and not wrap back to 0. Any program that relies on the memory wrapping “feature” of the 8086, will fail to run properly. As a solution to this compatibility problem, IBM decided to AND the A20 output of the CPU with a programmable output pin on some chip in the computer. The output of the AND gate is connected to the address bus, thus propogating or not, A20. Based on the input from the CPU A20, ANDed with an externally programmable source, address bus A20 gets asserted. The keyboard controller was chosen as this programmable source because it contained some available pins that can be held high, low, or toggled under program control. When the output of this pin is programmed to be high, the output of the AND gate is high when the CPU asserts A20. When the output is low,A20 is always low on the address bus — regardless of the state of the CPU A20. Thus by inhibiting A20 from being asserted on the address bus, ‘286- class machines can emulate the memory wrapping attributes of their 8086 predecessors.

Notice that only A20 is gated to the address bus. Therefore, without enabling the input to the A20 gate, the CPU can address every even megabyte of memory as follows: 0-1M, 2-3M, 4-5M, etc. In fact, duplicates of these memory blocks appear at 1-2M, 3-4M, 5-6M, etc. as a result of holding A20 low on the address bus. To enable the full 24-bits of addressability, a command must be sent to the keyboard controller (KBC). The KBC will enable the output on its pin to high, as input to the A20 gate. Once this is done, memory will no longer wrap, and we can address the full 16M of memory on the ‘286, or all 4G on 80386-class machines. All that remains in order to enter protected mode is changing the CPU state to protected mode and jumping to clear the prefetch queue (not necessary on the Pentium).

The following table summarizes the steps required to enter (with the intention of leaving) protected mode on the ‘286:

  1. Save the 8259 PIC mask in the program data segment
  2. Save SS:SP in the program data segment
  3. Save the return address from protected mode at 40:67
  4. Set the shutdown code in CMOS to tell BIOS that upon reset we will be returning to our program
  5. Build the GDT
  6. Enable A20 on the address bus
  7. Enable protected mode in the CPU machine status word (MSW)
  8. JUMP to clear the prefetch queue

Steps 1-6 can be done in any order.

The minimum number of steps required to enter protected mode on the ‘386 and ‘486 are far fewer, as the ‘386 can exit protected mode without resetting the CPU. For compatibility purposes, all ‘386 BIOS’s will recognize the CPU shutdown protocol defined on ‘286-class machines, but following this protocol isn’t necessary. To exit protected mode on a ‘386, the program simply clears a bit in a CPU control register. There is no need to save the PIC mask, SS:SP, a return address, or set a CMOS code. The requisite steps for entering protected mode on a ‘386 simply become:

  1. Build the GDT
  2. Enable A20 on the address bus
  3. Enable protected mode in the CPU control register (CR0, or MSW)
  4. JUMP to clear the prefetch queue

Of these requisite steps, building the GDT is the only step that may differ. In the ‘386 the base address is expanded to 32-bits, the limit is expanded to 20-bits, and two more control attribute bits are present. Listing 1 lists all the auxiliary subroutines to enter protected mode.

EXITING PROTECTED MODE

Like entering protected mode, exiting it differs from the ‘286 to 80386-class machines. The ‘386 simply clears a bit in the CPU control register CR0, while the ‘286 must reset the CPU. Resetting the CPU isn’t without its costs, as many hundred — if not thousands — of clock cycles pass in the time it takes to reset the CPU and return control back to the use program. The original method employed by IBM used the keyboard controller by connecting another output pin to the CPU RESET line. By issuing the proper command, the KBC would toggle the RESET line on the CPU. This method works, but it is very slow. Many new generation ‘286 chip sets have a “FAST RESET” feature. These chip sets toggle the RESET line by simply writing to an I/O port. When available, FAST RESET is the preferred method. But there is a third, obscure, but efficient method for resetting the CPU without using the KBC or FAST RESET. This method is elegant, faster than using the KBC, and works on the ‘386 WITHOUT resetting the CPU! It is truly the most elegant, comprehensive way to exit protected mode, since it works on both the ‘286, and ‘386 — in the most efficient way possible for each CPU. Listing 2 provides the code necessary to use the KBC and this elegant technique.

Using the KBC to reset the CPU is a straightforward technique, but in order to understand the elegant technique, some explanation is required. Recall that in our discussion of interrupts, the CPU checks the interrupt number (x8) against the limit field in the interrupt descriptor cache register (IDTR). If this test passes, then the next phase of interrupt processing begins. But if the test fails, then the CPU generates a DOUBLE FAULT (INT08). For example, let us suppose the limit field in the IDTR=80h: our IDT will service 16 interrupts, 00-15. If interrupt 16 or above was generated, the CPU would DOUBLE FAULT because a fault was generated at the inception of the interrupt calling sequence. Now, suppose the limit field in the IDTR=0, thus inhibiting all interrupts from being serviced. Any interrupt generation would cause the DOUBLE FAULT. But the DOUBLE FAULT itself would cause a fault, due to the limit being less than 40h. This ultimately would cause a TRIPLE FAULT, and the CPU would enter a shutdown cycle. The shutdown cycle doesn’t reset the CPU, as a shutdown cycle is considered a BUS cycle. External hardware is attached to the CPU to recognize the shutdown cycle. When a shutdown cycle is observed, the external hardware toggles the RESET input of the CPU. Therefore, all we need to do to cause the RESET is set the IDTR.LIMIT=0, then generate an interrupt. For elegance, we don’t just INT the CPU, we generate an invalid opcode. Our opcode is a carefully chosen opcode that doesn’t exist on the ‘286, but does exist on the ‘386. The elegance in the algorithm is in the opcode chosen for this purpose: MOV CR0,EAX. This will generate the desired invalid opcode exception on the ‘286, but is the first instruction in a sequence to exit protected mode on the ‘386. Thus the ‘286 gets RESET, and the ‘386 falls through and exits protected mode gracefully.

Exiting protected mode on the ‘286, and ‘386 closely resemble reversing the steps for entering protected mode. On the ‘286, you must:

  1. Reset the CPU to get into real mode
  2. Load the segment registers with real mode compatible values
  3. Restore SS:SP
  4. Inhibit A20 from the address bus (gate A20 off)
  5. Restore the PIC masks

And on the ‘386, the steps are simply:

  1. Load the segment registers with real-mode compatible values
  2. Reset the Protection Enable (PE) bit in CR0
  3. Load the segment registers with real mode values
  4. Inhibit A20 from the address bus (gate A20 off)

(Listing 3 includes the subroutines needed to restore the machine state after exiting protected mode).

Notice that exiting protected mode on the ‘386 requires loading the segment registers twice. The segment registers are loaded the first time to assure that real-mode compatible values are stored in the hidden descriptor cache registers — as the descriptor cache registers “honor” the access attributes, and segment size limit, from protected mode, even when loaded in real mode. The segment registers are loaded the second time to define them with real-mode segment values.

Now that we have all the tools and theory necessary to enter and exit protected mode, we can apply this knowledge to write a program that enters protected mode, moves a block of data from extended memory, and exits protected mode — returning to DOS. Listing 4 shows a program that consists of these basic steps and can be used to move a 1k block of data from 1M to our program’s data segment.

CONCLUSION

Applications programming for real mode and protected mode aren’t that different. Both modes use memory segmentation, interrupts, and device drivers to support the hardware. Whether in real mode or protected mode, a set of user-inaccessible registers — called descriptor cache registers — play a major role in memory segmentation and memory management. The descriptor cache registers contain information defining the segment base address, segment size limit, and segment access attributes, and are used for all memory references — regardless of the values in the segment registers.

Entering and exiting protected mode requires nothing more than following the mechanics necessary for the proper mode transition: entering protected mode requires saving the machine state that needs to be restored upon exiting protected mode. The mechanics of entering real mode depend on the type of the CPU: the ‘286 requires a reset to enter real mode, and the ‘386 can enter real mode under program control. By applying our knowledge of how the CPU internally operates, we can write source code that exits protected mode in the manner best suited, and most elegant, for the given CPU.


View source code for PMBASICS:
ftp://ftp.x86.org/source/pmbasics/tspec_a1.asm
ftp://ftp.x86.org/source/pmbasics/tspec_a1.l1
ftp://ftp.x86.org/source/pmbasics/tspec_a1.l2
ftp://ftp.x86.org/source/pmbasics/tspec_a1.l3
ftp://ftp.x86.org/source/pmbasics/tspec_a1.l4

Download entire source code archive:
ftp://ftp.x86.org/dloads/PMBASICS.ZIP


Back to Books and Articles home page

BIOS Data Segment

Format of BIOS Data Segment at segment 40h:
        {items in curly braces not documented by IBM}

Offset  Size    Description
 00h    WORD    Base I/O address of 1st serial I/O port, zero if none
 02h    WORD    Base I/O address of 2nd serial I/O port, zero if none
 04h    WORD    Base I/O address of 3rd serial I/O port, zero if none
 06h    WORD    Base I/O address of 4th serial I/O port, zero if none
            Note: Above fields filled in turn by POST as it finds serial
            ports. POST never leaves gaps. DOS and BIOS serial device
            numbers may be redefined by re-assigning these fields.
 08h    WORD    Base I/O address of 1st parallel I/O port, zero if none
 0Ah    WORD    Base I/O address of 2nd parallel I/O port, zero if none
 0Ch    WORD    Base I/O address of 3rd parallel I/O port, zero if none
 0Eh    WORD    [non-PS] Base I/O address of 4th parallel I/O port, zero if none
        [PS] Segment of Extended BIOS Data Segment
            Note: Above fields filled in turn by POST as it finds
            parallel ports. POST never leaves gaps. DOS and BIOS
            parallel device numbers may de redefined by re-assigning
            these fields.
 10h    WORD    Installed hardware:
            bits 15-14: number of parallel devices
            bit     13: [Conv] Internal modem
            bit     12: reserved
            bits 11- 9: number of serial devices
            bit      8: reserved
            bits  7- 6: number of diskette drives minus one
            bits  5- 4: Initial video mode:
                    00b = EGA,VGA,PGA
                    01b = 40 x 25 color
                    10b = 80 x 25 color
                    11b = 80 x 25 mono
            bit      3: reserved
            bit      2: [PS] =1 if pointing device
                [non-PS] reserved
            bit      1: =1 if math co-processor
            bit      0: =1 if diskette available for boot
 12h    BYTE    [Conv] POST status
        [AT] {Manufacturing test initialisation flags}
 13h    WORD    Base memory size in kbytes (0-640)
 15h    BYTE    [AT] {Manufacturing test scratch pad}
 16h    BYTE    [AT] {Manufacturing test scratch pad}
        [PS/2 Mod 30] BIOS control flags
 17h    BYTE    Keyboard status flags 1:
            bit 7 =1 INSert active
            bit 6 =1 Caps Lock active
            bit 5 =1 Num Lock active
            bit 4 =1 Scroll Lock active
            bit 3 =1 either Alt pressed
            bit 2 =1 either Ctrl pressed
            bit 1 =1 Left Shift pressed
            bit 0 =1 Right Shift pressed
 18h    BYTE    Keyboard status flags 2:
            bit 7 =1 INSert pressed
            bit 6 =1 Caps Lock pressed
            bit 5 =1 Num Lock pressed
            bit 4 =1 Scroll Lock pressed
            bit 3 =1 Pause state active
            bit 2 =1 Sys Req pressed
            bit 1 =1 Left Alt pressed
            bit 0 =1 Left Ctrl pressed
 19h    BYTE    Keyboard: Alt-nnn keypad workspace
 1Ah    WORD    Keyboard: ptr to next character in keyboard buffer
 1Ch    WORD    Keyboard: ptr to first free slot in keyboard buffer
 1Eh 16 WORDs   Keyboard circular buffer (but see 80h, 82h for override)
 3Eh    BYTE    Diskette recalibrate status:
            bit 7 =1 Diskette hardware interrupt occurred
            bits 6-4 reserved
            bit 3 =1 Recalibrate diskette 3
            bit 2 =1 Recalibrate diskette 2
            bit 1 =1 Recalibrate diskette 1
            bit 0 =1 Recalibrate diskette 0
 3Fh    BYTE    Diskette motor status:
            bit 7 =1 current operation is write or format
              =0 current operation is read or verify
            bit 6    reserved
            bits 5-4 diskette drive number selected (0-3)
            bit 3 =1 diskette 3 motor on
            bit 2 =1 diskette 2 motor on
            bit 1 =1 diskette 1 motor on
            bit 0 =1 diskette 0 motor on
 40h    BYTE    Diskette motor turn-off time-out count
 41h    BYTE    Diskette last operation status (0 = OK)
            bit 7 =1 drive not ready
            bit 6 =1 seek error
            bit 5 =1 general controller failure
            bits 4-0:
                00h no error
                01h invalid request
                02h address mark not found
                03h write-protect error
                04h sector not found
                06h diskette change line active
                08h DMA overrun
                09h DMA across 64k boundary
                0Ch media type unknown
                10h CRC error on read
 42h  7 BYTEs   Diskette/Fixed disk status/command bytes
 49h    BYTE    Video current mode
 4Ah    WORD    Video columns on screen
 4Ch    WORD    Video page (regen buffer) size in bytes
 4Eh    WORD    Video current page start address in regen buffer
 50h 16 BYTEs   Video cursor position (col, row) for eight pages, 0 based
 60h    WORD    Video cursor type, 6845 compatible, hi=startline, lo=endline
 62h    BYTE    Video current page number
 63h    WORD    Video CRT controller base address: color=03D4h, mono=03B4h
 65h    BYTE    Video current setting of mode select register 03D8h/03B8h
 66h    BYTE    Video current setting of CGA palette register 03D9h
 67h    DWORD   POST real mode re-entry point after certain resets
 6Bh    BYTE    POST last unexpected interrupt
 6Ch    DWORD   Timer ticks since midnight
 70h    BYTE    Timer overflow, non-zero if has counted past midnight
 71h    BYTE    Ctrl-Break flag: bit 7=1
 72h    WORD    POST reset flag:
            = 1234h if to bypass memory test (warm boot)
            = 4321h [PS/2 MCA only] if to preserve memory
            = 5678h [Conv] system suspended
            = 9ABCh [Conv] manufacturing test mode
            = ABCDh [Conv] POST loop mode
            =   64h Burn-in mode
 74h    BYTE    Fixed disk last operation status: {except ESDI drives}
            00h no error
            01h invalid function request
            02h address mark not found
            03h write protect error
            04h sector not found
            05h reset failed
            07h drive parameter activity failed
            08h DMA overrun
            09h DMA data boundary error
            0Ah bad sector flag detected
            0Bh bad track detected
            0Dh invalid number of sectors for Format
            0Eh control data address mark detected
            0Fh DMA arbitration level out of range
            10h uncorrectable ECC or CRC error
            11h ECC corrected data error
            20h general controller failed
            40h seek failed
            80h time out
            AAh drive not ready
            BBh undefined error
            CCh write fault on selected drive
            E0h status error/error register is zero
            FFh sense failed
 75h    BYTE    Fixed disk: number of fixed disk drives
 76h    BYTE    Fixed disk: control byte    {IBM document only for XT}
 77h    BYTE    Fixed disk: I/O port offset {IBM document only for XT}
 78h  3 BYTEs   Parallel devices 1-3 time-out counters
 7Bh    BYTE    parallel device 4 time-out counter [non-PS]
        bit 5 set if Virtual DMA Spec supported [PS] (see INT 4B)
 7Ch  4 BYTEs   Serial devices 1-4 time-out counters
 80h    WORD    Keyboard buffer start as offset from segment 40h (normally 1Eh)
 82h    WORD    Keyboard buffer end+1 as offset from segment 40h (normally 3Eh)
        [XT BIOS dated 11/08/82 ends here]
 84h    BYTE    Video EGA/MCGA/VGA rows on screen minus one
 85h    WORD    Video EGA/MCGA/VGA character height in scan-lines
 87h    BYTE    Video EGA/VGA control: [MCGA: =00h]
            bit 7:  =1 if not to clear RAM (see INT 10h, AH=00h)
            bits 6-5: RAM on adapter = (this field + 1) * 64K
            bit 4:  reserved
            bit 3:  =0 if EGA/VGA video system active, =1 if inactive
            bit 2:  =1 if to wait for display enable (what means this?)
            bit 1:  =0 for color or ECD monitor, =1 for mono monitor
            bit 0:  =0 alphanumeric cursor emulation enabled, =1 not.
                When enabled, text mode cursor size (INT 10,AH=01h)
                settings looking like CGA ones are translated to
                equivalent EGA/VGA ones.
 88h    BYTE    Video EGA/VGA switches: [MCGA: reserved]
            bits 7-4: power-on state of feature connector bits 3-0
            bits 3-0: configuration switches 4-1 (=0 on, =1 off)
              Values as read:
            0h Pri MDA,     Sec EGA+old color display 40 x 25
            1h Pri MDA,     Sec EGA+old color display 80 x 25
            2h Pri MDA,     Sec EGA+ECD normal mode (CGA emul)
            3h Pri MDA,     Sec EGA+ECD enhanced mode
            4h Pri CGA 40 x 25, Sec EGA mono display
            5h Pri CGA 80 x 25, Sec EGA mono display
            6h Pri EGA+old color display 40 x 25,  Sec MDA
            7h Pri EGA+old color display 80 x 25,  Sec MDA
            8h Pri EGA+ECD normal mode (CGA emul), Sec MDA
            9h Pri EGA+ECD enhanced mode,          Sec MDA
            Ah Pri EGA mono display,           Sec CGA 40 x 25
            Bh Pri EGA mono display,           Sec CGA 80 x 25
            When bit4 of 40h:89h is 0, VGA emulates 350-line EGA if
            this byte is x3h or x9h, otherwise emulates 200-line CGA in
            400-line double scan. VGA resets this byte to x9h after the
            mode set.
 89h    BYTE    Video MCGA/VGA mode-set option control:
            bits 7 and 4:
            0 0  350-line mode requested
            0 1  400-line mode at next mode set
            1 0  200-line mode requested
            1 1  reserved
            Apparently VGA BIOS mode set disregards bit 7 and uses
            byte 40h:88h to determine 200/350 selection when bit 4
            is zero. Presumably bit 7 is a convenience for other
            purposes. Bit 7 is reset to zero after the mode set.
            bit 6:  =1 if display switching enabled, =0 if disabled
            bit 5:  reserved
            bit 4:  [VGA]  =1 if to use 400-line mode at next mode set
                   =0 if to emulate EGA at next mode set
                      This bit set to 1 after the mode set.
                [MCGA] =1 use 400-line mode at next mode set
                   =0 emulate CGA, digital monitor, 200 lines,
                      8 x 8 text font at next mode set
                      Bit unchanged by mode set.
            bit 3:  =0 if default palette loading enabled at mode set
            bit 2:  =1 if mono display, =0 if color display
            bit 1:  =1 if gray scale summing enabled, =0 if disabled
            bit 0:  [VGA] =1 if VGA active, =0 if not
                [MCGA] reserved, zero
 8Ah    BYTE    Video [MCGA/VGA]: index into Display Combination Code table
 8Bh    BYTE    Diskette media control [not XT]:
            bits 7-6: Last data rate set by controller:
                  00=500kbps, 01=300kbps, 10=250kbps, 11=reserved
            bits 5-4: Last diskette drive step rate selected
            bits 3-2: {Data rate at start of operation}
            bits 1-0: reserved
 8Ch    BYTE    Fixed disk controller status [not XT]
 8Dh    BYTE    Fixed disk controller Error Status [not XT]
 8Eh    BYTE    Fixed disk Interrupt Control [not XT]
 8Fh    BYTE    Diskette controller information [not XT]:
            bit 7:  reserved
            bit 6:  =1 drive 1 determined
            bit 5:  =1 drive 1 is multi-rate, valid if drive determined
            bit 4:  =1 drive 1 supports 80 tracks, always valid
            bit 3:  reserved
            bit 2:  =1 drive 0 determined
            bit 1:  =1 drive 0 is multi-rate, valid if drive determined
            bit 0:  =1 drive 0 supports 80 tracks, always valid
 90h    BYTE    Diskette drive 0 media state
 91h    BYTE    Diskette drive 1 media state
            bits 7-6: Data rate: 00=500kbps, 01=300kbps, 10=250kbps
            bit    5: =1 if double stepping reqd (e.g. 360kB in 1.2MB)
            bit    4: =1 if media established
            bit    3: reserved
            bits 2-0: on exit from BIOS, contain:
                000 trying 360kB in 360kB
                001 trying 360kB in 1.2MB
                010 trying 1.2MB in 1.2MB
                011 360kB in 360kB established
                100 360kB in 1.2MB established
                101 1.2MB in 1.2MB established
                110 reserved
                111 all other formats/drives
 92h    BYTE    Diskette drive 0 media state at start of operation
 93h    BYTE    Diskette drive 1 media state at start of operation
 94h    BYTE    Diskette drive 0 current track number
 95h    BYTE    Diskette drive 1 current track number
 96h    BYTE    Keyboard status byte 3
            bit 7 =1 read-ID in progress
            bit 6 =1 last code read was first of two ID codes
            bit 5 =1 force Num Lock if read-ID and enhanced keyboard
            bit 4 =1 enhanced keyboard installed
            bit 3 =1 Right Alt pressed
            bit 2 =1 Right Ctrl pressed
            bit 1 =1 last code read was E0h
            bit 1 =1 last code read was E1h
 97h    BYTE    Keyboard status byte 2
            bit 7 =1 keyboard transmit error flag
            bit 6 =1 LED update in progress
            bit 5 =1 RESEND received from keyboard
            bit 4 =1 ACK received from keyboard
            bit 3 reserved, must be zero
            bit 2 Caps Lock LED
            bit 1 Num Lock LED
            bit 0 Scroll Lock LED
 98h    DWORD   Timer2: [AT, PS exc Mod 30] ptr to user wait-complete flag
                        (see INT 15, AX=8300h)
 9Ch    DWORD   Timer2: [AT, PS exc Mod 30] user wait count in microseconds
 A0h    BYTE    Timer2: [AT, PS exc Mod 30] Wait active flag:
            bit 7 =1 wait time elapsed
            bits 6-1 reserved
            bit 0 =1 INT 15h, AH=86h has occurred
 A1h  7 BYTEs   reserved for network adapters (oh really?)
 A4h    DWORD   [PS/2 Mod 30] Saved Fixed Disk Interrupt Vector
 A8h    DWORD   Video: EGA/MCGA/VGA ptr to Video Save Pointer Table (see below)
 ACh-AFh    reserved
 B0h    DWORD   ptr to 3363 Optical disk driver or BIOS entry point.
            When 3363 BIOS present, the signature "OPTIC ",00h occurs 3
            bytes beyond this entry point.
            When 3363 BIOS and 3363 File System Driver present, the
            signature "FILE SYSTEM DRIVER",00h occurs 3 bytes beyond
            this entry point.
 B4h    WORD    reserved
 B6h  3 BYTEs   reserved for POST?
 B9h  7 BYTEs   ???
 C0h 14 BYTEs   reserved
 CEh    WORD    count of days since last boot?
 D0h-EFh    reserved
 F0h-FFh    reserved for user
100h    BYTE    Print Screen Status byte
Format of Extended BIOS Data Area (see 40:0Eh for ptr) [PS only]
Offset  Size    Description
 00h    BYTE    Length of EBDA in kilobytes
 01h 15 BYTEs   reserved
 17h    BYTE    Number of entries in POST error log (0-5)
 18h  5 WORDs   POST error log (each word is a POST error number)
 19h-21h    reserved
 22h    DWORD   Pointing Device Driver entry point
 26h    BYTE    Pointing Device Flags 1
           bit 7:    =1 command in progress
           bit 6:    =1 resend
           bit 5:    =1 acknowledge
           bit 4:    =1 error
           bit 3:    =0 reserved
           bits 2-0: index count
 27h    BYTE    Pointing Device Flags 2
           bit 7:    =1 device driver far call flag
           bits 6-3: reserved
           bits 2-0: package size
 28h  7 BYTEs   Pointing Device Auxiliary Device Data
 2Fh    BYTE    reserved
 30h    DWORD   Vector for INT 07h stored here during 80387 interrupt
 34h    DWORD   Vector for INT 01h stored here during INT 07h emulation
 38h    BYTE    Scratchpad for 80287/80387 interrupt code
 39h    WORD    Timer3: Watchdog timer initial count
 3Bh    BYTE    ??? seen non-zero on Model 30
 3Ch    BYTE    ???
 3Dh 16 BYTEs   Fixed Disk parameter table for drive 0 (oh really?)
 4Dh 16 BYTEs   Fixed Disk parameter table for drive 1 (oh really?)
            Neither of above seen on any Model 30, 50, 60 yet.
 5Dh-6Bh    ???
 6Ch    BYTE    Fixed disk: (=FFh on ESDI systems)
            bits 7-4: Channel number 00-0Fh
            bits 3-0: DMA arbitration level 00-0Eh
 6Dh and up:    ??? seen non-zero on Model 60
3F0h    BYTE    Fixed disk buffer (???!!!)
Format of Video Save Pointer Table [EGA/VGA/MCGA only]:
Offset  Size    Description
 00h    DWORD   ptr to Video Parameter Table
 04h    DWORD   ptr to Parameter Dynamic Save Area, else 0 [EGA/VGA only]
 08h    DWORD   ptr to Alphanumeric Character Set Override, else 0
 0Ch    DWORD   ptr to Graphics Character Set Override, else 0
 10h    DWORD   [VGA only] ptr to Secondary Save Pointer Table, must be valid
 14h    DWORD   reserved, zero
 18h    DWORD   reserved, zero
Note: table initially in ROM, copy to RAM to alter, then update 40h:A8h.

Format of Secondary Video Save Pointer Table [VGA only]:
Offset  Size    Description
 00h    WORD    Length of this table in bytes, including this word (1Ah)
 02h    DWORD   ptr to Display Combination Code Table, must be valid
 06h    DWORD   ptr to second Alphanumeric Character Set Override, else 0
 0Ah    DWORD   ptr to User Palette Profile Table, else 0
 0Eh    DWORD   reserved, zero
 12h    DWORD   reserved, zero
 16h    DWORD   reserved, zero
Note: table initially in ROM, copy to RAM to alter, then alter Save Ptr Table.

Format of Video Parameter Table [EGA, VGA only]:
An array of 23 [EGA] or 29 [VGA] elements, each element being 64 bytes long.
Elements appear in the order:
 00h-03h    Modes 00h-03h in 200-line CGA emulation mode
 04h-0Eh    Modes 04h-0Eh
 0Fh-10h    Modes 0Fh-10h when only 64kB RAM on adapter
 11h-12h    Modes 0Fh-10h when >64kB RAM on adapter
 13h-16h    Modes 00h-03h in 350-line mode
 17h        VGA Modes 00h or 01h in 400-line mode
 18h        VGA Modes 02h or 03h in 400-line mode
 19h        VGA Mode  07h in 400-line mode
 1Ah-1Ch    VGA Modes 11h-13h
Format of Video Parameter Table element [EGA, VGA only]:
Offset  Size    Description
 00h    BYTE    Columns on screen         (see 40h:4Ah)
 01h    BYTE    Rows on screen minus one      (see 40h:84h)
 02h    BYTE    Height of character in scan lines (see 40h:85h)
 03h    WORD    Size of video buffer          (see 40h:4Ch)
 05h  4 BYTEs   Values for Sequencer Registers 1-4
 09h    BYTE    Value for Miscellaneous Output Register
 0Ah 25 BYTEs   Values for CRTC Registers 00h-18h
 23h 20 BYTEs   Values for Attribute Controller Registers 00h-13h
 37h  9 BYTEs   Values for Graphics Controller Registers 00h-08h
Format of Video Parameter Table [MCGA only] {guesswork from inspection}:
    - 16 triplet BYTEs of R,G,B DAC info for 16 colors;
    - An array of 11 elements, each element being 32 bytes long.
      Elements appear in the order:
        Modes 00h,01h in 200-line mode for digital displays
        Modes 00h,01h in 400-line mode for analog displays
        Modes 02h,03h in 200-line mode for digital displays
        Modes 02h,03h in 400-line mode for analog displays
        Modes 04h,05h in 200-line mode for digital displays
        Modes 04h,05h in 400-line mode for analog displays
        Mode  06h in 200-line mode for digital displays
        Mode  06h in 400-line mode for analog displays
        Mode  11h
        Mode  13h in 200-line mode for digital displays
        Mode  13h in 400-line mode for analog displays

Format of Video Parameter Table element [MCGA only]:
Offset  Size    Description
 00h    BYTE    Columns on screen         (see 40h:4Ah)
 01h    BYTE    Rows on screen minus one      (see 40h:84h)
 02h    BYTE    Height of character in scan lines (see 40h:85h)
 03h    WORD    Size of video buffer          (see 40h:4Ch)
 05h    WORD    ??? always zero
 07h 21 BYTEs   Video data registers 00h-14h to port 3D5h indexed by 3D4h
 1Ch    BYTE    PEL Mask to port 3C6h
 1Dh    BYTE    CGA Mode Control to port 3D8h
 1Eh    BYTE    CGA Border Control to port 3D9h
 1Fh    BYTE    Extended Mode Control to port 3DDh
Format of Video Parameter Dynamic Save Area [EGA, VGA only]:
Offset  Size    Description
 00h 16 BYTEs   Last data written to Attribute Controller Palette Registers 0-15
 10h    BYTE    Last data written to Attribute Controller Overscan Register
 11h-FFh    Reserved
        Note: Need for table was that EGA registers were write-only.
        Note: If default values (from the Video Parameter Table) are
              over-ridden at a mode set by the VGA User Palette Profile
              Table, then the Dynamic Save Area is updated with the
              default values, not the User Profile ones.

Format of Alphanumeric Character Set Override:
Offset  Size    Description
 00h    BYTE    Length in bytes of each character in font table
 01h    BYTE    Character generator RAM bank to load, 0=normal
 02h    WORD    Number of characters in font table, normally 256
 04h    WORD    Code of first character in font table, normally 0
 06h    DWORD   ptr to font table
 0Ah    BYTE    Displayable rows (FFh=use maximum calculated value)
 0Bh    BYTEs   Array of mode values to which this font is to pertain
    BYTE    FFh end of array
Format of Second Alphanumeric Character Set Override:
Authorities differ, some say same as first override above, but IBM say:
Offset  Size    Description
 00h    BYTE    Length in bytes of each character in font table
 01h    BYTE    Character generator RAM bank to load, normally non-zero
 02h    BYTE    reserved
 03h    DWORD   ptr to font table
 07h    BYTEs   Array of mode values to which this font is to pertain
    BYTE    FFh end of array
Format of Graphics Character Set Override:
Offset  Size    Description
 00h    BYTE    Number of displayable character rows
 01h    WORD    Length in bytes of each character in font table
 03h    DWORD   ptr to font table
 07h    BYTEs   Array of mode values to which this font is to pertain
    BYTE    FFh end of array
Format of Display Combination Code Table [VGA only]:
Offset  Size    Description
 00h    BYTE    Number of entries in the DCC table at offset 04h
 01h    BYTE    Version number
 02h    BYTE    Maximum display type code that can appear in DCC table
 03h    BYTE    reserved
 04h    ARRAY OF 2 BYTEs Each pair of bytes gives a valid display combination
            Meaning of each byte:
            00h no display
            01h MDA with mono display
            02h CGA with color display
            03h reserved
            04h EGA with color display
            05h EGA with mono display
            06h Professional Graphics Controller
            07h VGA with mono display
            08h VGA with color display
            09h reserved
            0Ah MCGA with digital color display
            0Bh MCGA with analog mono display
            0Ch MCGA with analog color display
            FFh unrecognised video system
Format of User Palette Profile Table [VGA only]:
Offset  Size    Description
 00h    BYTE    Underlining: 01h=enable in all alphanumeric modes
                 00h=enable in monochrome alphanumeric modes only
                 FFh=disable in all alphanumeric modes
 01h    BYTE    reserved
 02h    WORD    reserved
 04h    WORD    Number (0-17) of Attribute Controller registers in table
 06h    WORD    Index (0-16) of first Attribute Controller register in table
 08h    DWORD   ptr to table of Attribute Controller registers to override
            Table is an array of BYTEs.
 0Ch    WORD    Number (0-256) of video DAC Color registers in table
 0Eh    WORD    Index (0-255) of first video DAC Color register in table
 10h    DWORD   ptr to table of video DAC Color registers to override
            Table is ??? triplets ??? of BYTEs???
 14h    BYTEs   array of mode values to which this profile is to pertain
    BYTE    FFh end of array
From http://www.nondot.org/sabre via the Wayback Machine

PIC–ing

Ref: www.jamesmolloy.co.uk https://wiki.osdev.org/8259_PIC / http://www.brokenthorn.com/Resources/OSDevPic.html

The IBM PC 8259 PIC Architecture
In the beginning (IBM PC and XT), only a single 8259 PIC chip was used, which provided 8 IRQs to the system. These were traditionally mapped by the BIOS to interrupts 8 to 15 (0x08 to 0x0F). It is unlikely that any of these single-PIC machines will be encountered these days.


The IBM PC/AT 8259 PIC Architecture
The IBM PC/AT extended the PC architecture by adding a second 8259 PIC chip. This was possible due to the 8259A’s ability to cascade interrupts, that is, have them flow through one chip and into another. This gives a total of 15 interrupts. Why 15 and not 16? That’s because when you cascade chips, the PIC needs to use one of the interrupt lines to signal the other chip.
The low-level concepts behind external interrupts are not very complex. All devices that are interrupt-capable have a line connecting them to the PIC (programmable interrupt controller). The PIC is the only device that is directly connected to the CPU’s interrupt pin. It is used as a multiplexer, and has the ability to prioritize between interrupting devices. It is, essentially, a glorified 8-1 multiplexer. At some point, someone somewhere realized that 8 IRQ lines just wasn’t enough, and they daisy-chained another 8-1 PIC beside the original. So in all modern PCs, you have 2 PICs, the master and the slave, serving a total of 15 interruptable devices (one line is used to signal the slave PIC).
Only hardware interrupts are handled through the Programmable Interrupt Controller. The special, CPU-dedicated interrupts are shown below.

0 – Division by zero exception
1 – Debug exception
2 – Non maskable interrupt
3 – Break-point exception
4 – ‘Into detected overflow’
5 – Out of bounds exception
6 – Invalid opcode exception
7 – No co-processor exception
8 – Double fault (pushes an error code)
9 – Co-processor segment overrun
10 – Bad TSS (pushes an error code)
11 – Segment not present (pushes an error code)
12 – Stack fault (pushes an error code)
13 – General protection fault (pushes an error code)
14 – Page fault (pushes an error code)
15 – Unknown interrupt exception
16 – Co-processor fault
17 – Alignment check exception
18 – Machine check exception
19-31 – Reserved

See: Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 3 (3A, 3B, 3C & 3D): System Programming Guide – 6.3.1 External Interrupts
WR Pin: This pin connects to a write strobe signal (One of 8 on a Pentium)
RD Pin: This connects to the IOCR (Input Output Control Routine) signal.
INT Pin: Connects to the INTR pin on the microprocessor.
INTA Pin: Connects to the INTA pin on the microprocessor.
A0 Pin: Selects different Command WORDS
CS Pin: Enables the chip for programming and control.
SP/EN Pin: Slave program (SP) / Enable Buffer (EN).
Slave Program (1=Master, 0=Slave)
Enable Buffer (Controls data bus transievers when in buffered mode)
CAS0, CAS1, CAS2 Pins: Used to output from master to slave PIC controllers in cascaded systems.
D0 – D7 Pins: 8 bit Data connector pins.

x86 Hardware Interrupts
8259A Input pinInterrupt NumberDescription
IRQ00x08Timer
IRQ10x09Keyboard
IRQ20x0ACascade for 8259A Slave controller
IRQ30x0BSerial port 2
IRQ40x0CSerial port 1
IRQ50x0DAT systems: Parallel Port 2. PS/2 systems: reserved
IRQ60x0EDiskette drive
IRQ70x0FParallel Port 1
IRQ8/IRQ00x70CMOS Real time clock
IRQ9/IRQ10x71CGA vertical retrace
IRQ10/IRQ20x72Reserved
IRQ11/IRQ30x73Reserved
IRQ12/IRQ40x74AT systems: reserved. PS/2: auxiliary device
IRQ13/IRQ50x75FPU
IRQ14/IRQ60x76Hard disk controller
IRQ15/IRQ70x77Reserved

8259A Registers

  • Command Register – This is a write only register that is used to send commands to the microcontroller.
  • Status register – This is a read only register that can be accessed to determin the status of the PIC.
  • Interrupt Request Register (IRR) – This register specifies which interrupts are pending acknowledgment. Note: This register is internal, and cannot be accessed directly.
  • In-Sevice Register (ISR) – This register specifies which interrupts have already been acknowledged, but are awaiting for the End of Interrupt (EOI) signal.
  • Interrupt Mask Register (IMR) – This specifies what interrupts are to be ignored, and not acknowledged.

Programming with the 8259 PIC
When the computer boots, the default interrupt mappings are:
IRQ 0..7 – INT 0x8..0xF
IRQ 8..15 – INT 0x70..0x77
Each chip (master and slave) has a command port and a data port (given in the table below). When no command is issued, the data port allows us to access the interrupt mask of the 8259 PIC.

Chip – Purpose I/O port
Master PIC -Command0x0020
Master PIC – Data0x0021
Slave PIC -Command0x00A0
Slave PIC – Data0x00A1

Boot Sector

The boot sector is compiled and inserted on to the test hard drive image. My manual BPB compiled with UASM and use dd to manually install on my 50 Meg DOS vdi. I cannot remember why I used UASM instead of wasm or jwasm, but maybe it will come to me.

Overall Purpose

Only does LBA reads – int 13 0x41 check error if no extensions. Assumes a modern system has more than 1 meg RAM so no TOM check. The basic function this BPB:

  1. Loads watload.bin to 0xC000
  2. Overlay BIOS parameter block (BPB) data to watload.bin
  3. Far jump to watload.bin

This is the big start and the following is a memory map I threw together to help track of what memory areas I was using. The example is a simple old 32bit system boot which starts in real mode (16bit). The following represents the boot block found by the system as the machine initializes and loaded to 0x0000:0x7C00. Once the BIOS initializes, it jumps to 0x0000:0x7C00 and it is all you at that point, no safety nets. 

;	 --------  
;	|        | 
;	|watload | at 0xC000 (0x800 bytes)
;	+--------+
;	|        | 
;	| Buffer | Use start 0x7E00 for Root Dir and FAT load buffer
;	|--------| 0000:7E00  (0:BP+200)
;	|BOOT SEC| contains BPB
;	|ORIGIN  |
;	|--------| 0000:7C00  (0:BP)
;	|VARS    | only known is 1st data sector (start of cluster 2)
;	|--------| 0000:7BFC  (DS:[BP-4])
;	|STACK   | minimal 256 bytes (1/2 sector)
;	|- - - - |
;	|KERNEL  | kernel loaded here (max 58 sectors, 29KB)
;	|LOADED  | also used as FAT buffer
;	|--------| 0070:0000 (0:0700)
;	|KERNEL  | 
;	|LOADED  | 
;	|--------| 0000:0600 
;	|DOS DA  | DOS Data Area,
;	|        | Cluster list temp from FAT read
;	|********| 0000:0500
;	|BDA     | BIOS Data Area
;	+--------+ 0000:0400
;	|IVT     | Interrupt Vector Table
;	+--------+ 0000:0000

It’s All You

This is the most rewarding thing about learning the low level stuff, it is all you from this point. When it works, there is nothing like. When it doesn’t work, there is nothing like it. In the next snippet, I set up some address for use and add  daptable.inc. Note the nop to keep the short jmp.

;
.8086 ; enable assembler warnings to limit instruction set

include daptable.inc

BASE	    equ	0x7C00      ; boot sector originally at 0x0:BASE
BUFFER_SEG  equ 0x07E0
BUFFER_OFF  equ 0x0000      ; buffer at 0x7E00 for Root and FAT load
BIOS_SEG    equ 0x0C00      ; watload.bin load at 0xC000
CLUSTERLIST equ 0x0500      ; location to store bios cluster list

;-----------------------------------------------------------------------
; Entry point after MBR hand off. Stock MBR is located at 0000:0x0600

_TEXT segment use16 'code'
            org     BASE

BS_jmpBoot:     
    jmp     short start
    nop

BIOS parameter block (BPB)

So, this is the BPB and some form of BPB, for whatever filesystem in use, will be written when the media is formatted. In my case, I made my test drive image, FAT, and pulled the BPB for use in my boot sector. 

BPB_Start:
BS_OEMName      db 'IBM  7.0'   ; OEM label

BPB_BytsPerSec 	dw 0x200        ; Number of bytes per sector (512) Must be one of 512, 1024, 2048, 4096.
BPB_SecPerClus 	db 0x4          ; Number of sectors per cluster Must be one of 1, 2, 4, 8, 16, 32, 64, 128.
BPB_RsvdSecCnt 	dw 0x1          ; reserved sectors, in 12/16 usually 1 for BPB, FAT32 uses 32
BPB_NumFATs    	db 0x2          ; number of FATs, 
BPB_RootEntCnt 	dw 0x200        ; root directory entries, 0 for FAT32. 512 is recommended for FAT16.
BPB_TotSec16   	dw 0x0          ; 16-bit total count of sectors on the volume, if 0 see BPB_TotSec32
BPB_Media       db 0xF8         ; is no longer usually used, F8 HD FA Ram Disk
BPB_FATSz16    	dw 0x64         ; sectors per 1 FAT copy
BPB_SecPerTrk  	dw 0x3F         ; sectors per track
BPB_NumHeads    dw 0x10         ; number of heads
BPB_HiddSec    	dd 0x3F         ; hidden sectors
BPB_TotSec32   	dd 0x18D71      ; big total sectors  BPB_TotSec32 * BPB_BytsPerSec = HD size

BS_DrvNum      	db 0x80         ; boot unit
BS_Reserved1   	db 0            ; Reserved (used by Windows NT). FAT always 0
BS_BootSig     	db 0x29         ; 0x29 indicates next 3 fields in the boot sector present
BS_VolID       	dd 0x30E1671C   ; volume serial number
BS_VolLab      	db 'PCDOS_DEV  '; volume label
BS_FilSysType  	db 'FAT16   '   ; filesystem id

fat_start       dd  ?           ; first FAT sector
data_start      dd  ?           ; first Data sector
bios_cluster    dw  ?           ; bios cluster from root dir 

Disk Address Packet (DAP)

The DAP is used for LBA reads and was included early. This link: daptable.inc shows the DAP with comments.

; DAP : Disk Address Packet 
DAP DAP_TABLE <0x10,0x0,0x0,0x7E00,0x0,0x0,0x0>

BPB Code

This is where the previous short jump leads us to, the start of the code.

 ********************************************************************
; * Start of BPB code
; ********************************************************************
start:
    cli             		
    cld             		
    mov     BS_DrvNum, dl   ; save BIOS drive number
    xor     ax, ax  		; segment registers 0x0000
    mov     ds, ax  		
    mov     es, ax
    mov     ss, ax
    mov     bp, BASE        ; setup stack
    mov     sp, BASE
    sti             		

; Volume Structure:
;      |       |       |            |
;  BPB |  FAT  |  FAT  |  Root Dir  |  Data
;      |       | Copy  |            |
;      |       |       |            |
;   fat_start       dir_start   data_start

    ; fat_start = BPB_HiddSec + BPB_RsvdSecCnt
    mov    si, word ptr BPB_HiddSec
    mov    di, word ptr BPB_HiddSec+2
    add    si, word ptr BPB_RsvdSecCnt
    adc    word ptr fat_start+2, di  ; DI:SI = first FAT sector
    mov    word ptr fat_start, si

    ; dir_start = (BPB_NumFATs * BPB_FATSz16) + fat_start
    mov    al, BPB_NumFATs
    cbw
    mul    BPB_FATSz16       	   ; DX:AX = total number of FAT sectors
    add    si, ax
    adc    di, dx                  ; DI:SI = first root directory sector
    mov	   word ptr DAP.DAP_sector_low, si		
    mov	   word ptr DAP.DAP_sector_low+2, di  ; root dir start in DAP will be 
                                              ; first read in
                                           
    ; RootDirSectors = (BPB_RootEntCnt * 32) / BPB_BytsPerSec;
    mov    ax, 32
    xor    dx, dx
    mul    word ptr BPB_RootEntCnt
    div    word ptr BPB_BytsPerSec        ; Divide (dx:ax,sectsize) to (ax,dx)
    mov    DAP.DAP_num_sectors, ax        ; number of root dir sectors to DAP
                                               
    ; where the data starts
    add    si, ax
    mov    word ptr data_start, si
    adc    word ptr data_start+2, di      ; DI:SI = first data sector

    ; First, read the root directory into buffer.
    ; IBM/MS INT 13 Extensions - INSTALLATION CHECK
    ; will not work without LBA extensions
    mov    ah,041h             
    mov    bx,055aah           
    mov    dl, [BS_DrvNum]     ; BIOS drive, 0=A:, 80=C:
    int    0x13
    jnc    root_read
    mov    ax, 0x0E31
    jmp    print_error         ; Error 1 - No bios extensions
    
    ; yep, damn - int 13  0x42 is supported, read in full root dir at
    ; 0x7E00 - big read
root_read:
    call   readdrive           ; read in root directory
    jnc	   get_biosname        
    mov    ax, 0x0E32
    jmp    print_error         ; Error 2 - Root directory read
    
    ; read through root dir directory for watload.bin 
get_biosname:
    lea    si, filename        ;Starting address of first buffer
    lea    di, buffer	       ;Starting address of first buffer
    push   si
    push   di
    mov	   ax, BPB_RootEntCnt  ;Count FAT 16 directory entries max
    
next_dir:
    mov	   cx, 0x0B            ;Scanning 11 bytes (CX is used by REPE)
    repe   cmpsb               ;   ...and compare it.
    je     bios_found 
    pop	   di
    add	   di, 0x20
    pop	   si
    push   si
    push   di
    cmp    byte ptr [es:di], 0x00
    jz	   no_bios	           ; ax counts max dir entries, but if first 
    dec	   ax                  ; char di is zero, no more dir entries
    jne	   next_dir            ; so bail early

    ; get here if entry not found 0x00, first free entry or for some 
    ; reason you read through all entries not found or 0xE5 and 
    ; ax counts down to zero
no_bios:
    mov    ax, 0x0E33
    jmp    print_error         ; Error 3 - watload.bin
bios_found:
    pop    di                  ; offset of found dir entry
    pop    si                  ; empty stack

    ; watload directory entry found, pull cluster, store in data
    mov    ax, [di+0x1A]       ; first cluster
    mov    [bios_cluster], ax

    ; reuse DAP structure - setup DAP for FAT read. Read in full FAT
    ; at 0x7E00
    lea    bx, buffer			
    mov    ax, [BPB_FATSz16]
    mov	   DAP.DAP_num_sectors, ax    
    mov    ax, word ptr [fat_start]
    mov    dx, word ptr [fat_start+2] 
    mov	   word ptr DAP.DAP_sector_low, ax		
    mov	   word ptr DAP.DAP_sector_low+2, dx 
    call   readdrive           ; read FAT into memory at 0x7E00
    jnc	   read_fat
    mov    ax, 0x0E35
    jmp    print_error          ; Error 5 - FAT read error

read_fat:
    ; set up ds:di to the FAT buffer
    mov    si, BUFFER_OFF
    mov    ax, BUFFER_SEG
    mov    ds, ax
    mov    ax, [ds:si]
    sub    ax, 0xFFF8
    je     good_fat_table      ; first word should be 0xFFF8 or 
    mov    ax, 0x0E36
    jmp    print_error         ; Error 6 - FAT table bad

; At this point, the entire FAT is loaded at 0x7E00 and ds:si are set
; to that seg:off. Assume es - 0, set es:di 0000:0500

good_fat_table:
    lea    di, [CLUSTERLIST]
    mov    ax, bios_cluster    ; cluster number from root dir
next_clust:
    stosw                       ; store cluster number, inc next after store
    mov    si, ax
    add    si, si              ; cluster * 2
    mov    ax, [ds:si]
   
    ; In some docs the end-of-clusterchain marker is listed as FFF8, but
    ; in others FFFF (FAT16). From what I have seen FFFF is the correct 
    ; marker and FFF8 is only at cluster 1 position.
    cmp    ax, 0xFFFF
    jne    next_clust
    xor    ax, ax              ; mark end of temp cluster list 0x0000
    stosw 
    
    ; set ds back to 0x0000 so my next int 13 0x42 reads the DAP at the
    ; correct address
    mov    ds, ax   
    
    ; ************************************************************** 
    ; ****      Note: buffer at 0x7E00 no longer needed         **** 
    ; ************************************************************** 
    
    ; The issue is that the stock IBMBIOS loads at 0x0700 and a full 
    ; load will over write 
    mov    DAP.DAP_buffer_off, ax ; load to DAP off -- 0x0000
    mov    ax, BIOS_SEG           ; load segment
    mov    DAP.DAP_buffer_seg, ax ; load to DAP seg -- 0x0D00
    mov    al, [BPB_SecPerClus]   ; each read will be size of cluster
    cbw
    mov    DAP.DAP_num_sectors, ax ; load in DAP
    
    ; LBA_sector = ((cluster_number - 2) * BPB_SecPerClus) + data_start
    ; Start read at LBA_sector for BPB_SecPerClus
    ; then read next cluster number and repeat until loaded    
    lea    di, [CLUSTERLIST]

    mov    ax, word ptr [data_start]    ; low word data_start
    mov	   word ptr DAP.DAP_sector_low, ax		
    mov    ax, word ptr [data_start+2]  ; high word data_start
    mov	   word ptr DAP.DAP_sector_low+2, ax  
    xor    dx, dx
    mov    ax, [es:di]
    sub    ax, 0x0002             ; subtract 2, comp for FAT position
    mul    [BPB_SecPerClus]       ; dx:ax
    add    word ptr [DAP.DAP_sector_low], ax
    add    word ptr [DAP.DAP_sector_low+2], dx
    call   readdrive    

    mov    cx, word ptr [fat_start]
    mov    dx, word ptr [fat_start+2]    
    mov    si, word ptr data_start
    mov    di, word ptr data_start+2 

    ; far jump to the start of watload.bin	
    jmp far ptr loader_start

; Print string pointed to by DS:SI using
; BIOS TTY output via int 10h/AH=0eh
print_error:
    int    0x10
    jmp $

readdrive:
    mov    ah, 0x42
    mov    dl, BS_DrvNum
    lea	   si, DAP
    int    0x13                
	ret

filename     db  "WATLOAD BIN"			

; Fill free space between code/data and signature with zero
             db	 ((0x200 - 2) - ($ - BS_jmpBoot)) dup(0)

; True, the MBR checks at 0x7DFE after BPB loaded for signature before
; jumping to start. -MKG
signature    dw  0xAA55

; This will be 0x7E00 - where FAT16 RootDir will load and reuse for
; FAT read - for FAT I set ds:di == 0x7E00 
buffer:			

_TEXT ends

loader segment use16 'code' at 0x0000
            org     0xC000

    loader_start label near 

loader ends

The micro-FSD

This module contains the micro-FSD which is located between the partition bootsector and the actual data on the hard drive. The load segment was calculated in Phase 2 and for the Bochs drive image is 0x8800. The entry point is 8800:199C or 0x8999C with the registers (Bochs image) loaded as follows:

 

	eax: 0x0000199C
	ecx: 0x00007FBE
	edx: 0x00000080
	ebx: 0x00000000
	esp: 0x00007C00
	ebp: 0x00000000
	esi: 0xFFFF0046
	edi: 0x00000000
	eflags 0x00000246
	IOPL=0 id vip vif ac vm rf nt of df IF tf sf ZF af PF cf

 

Segment registers:

 

cs:s=0x8800    ds:s=0x8800    ss:s=0x0000    es:s=0x07C0    fs:s=0x3000

 

This is somewhat irrelevant because es, ds, and ss will be set to cs (0x8800 in this example) and sp set to 0x5000.

Items from previous loads:

 

  • If INT13 Ext functions are supported then 3000:0000, segment contain in fs, has 58333149h, stored as 49 31 33 58. If not the location is zero.
  • The storage area contains the results of Phase 2 — (Bochs disk) 0x8803E 3F, 0, 0 0, 0, 0, 0, 0 or 0x000000000000003F.

The JFS superblock procedure loads the super block and stores values at the following locations:

 

superblock 8800:160C (0x8960C to 0x8980C)
offset 1858 to 185B (0x89858 to 0x89858)
offset 185C to 185F (0x8985C to 0x8985F)

; module locations
ft_cfiles dw 3
ft_ldrseg dw 0
ft_ldrlen dd 0
ft_museg dw 0
ft_mulen dd 0x5000
ft_mfsseg dw 0
ft_mfslen dd 0
ft_ripseg dw 0
ft_riplen dd 0x0

; microFSD vector table
ft_muOpen dd seg:1A9C
ft_muRead dd seg:1BD4
ft_muClose dd seg:1DAE
ft_muTerminate dd seg:1DD4

 

88000	jmp	short near ptr _entry  ;  Entry point from MBR code
88002	nop
;  BIOS parameter block (BPB)
88003	db    'IBM 4.50'    ; Partition creator
8800B	db    0, 2    ; 0x0200 size of sector in bytes
8800D	db    0
8800E	db    0
8800F	db    0
88010	db    0
88011	db    0
88012	db    0
88013	db    0
88014	db    0
88015	db   F8    ; media type - hard disk
88016	db    0
88017	db    0
88018	db   3F, 0		; BPB formatted geo: Sectors - 63
8801A	db    20, 0		; BPB formatted geo: Heads - 32
8801C	db    3F, 0, 0 ,0	; 0x0000003F hidden sectors
88020	db    41, 12, 13, 0    ; 0x00131241 Big number of sectors
88024	db    80    		;  physical drive number
88025	db    80    		;  Boot drive letter
88026	db    29    		;  Ext-BPB signature
88027	db    BD , 55,  9C, 69    ;  Partition serial number 0x699c55bd
8802B	db    bochs, 0, 0, 0, 0, 0, 0    ; Partition label (11)
88036	db    "JFS     "    ; Filesystem type (8)
; Used as temp storage
8803E	db    0, 0, 0, 0    ; absolute number of the start of the sectors
88042	db    0, 0, 0, 0
; DAP : Disk Address Packet (16 bytes)
88046	db    10		; size of DAP = 16 = 10h
88047	db    0			; unused, should be zero
88048	db    20		; number of sectors to be read
88049	db    0			; unused, should be zero
8804A	db    0, 0, 0, 0	;segment:offset pointer to the memory buffer
8804E	db    0, 0, 0, 0, 0, 0, 0, 0

 

 

; eax: 0x00000001 ecx: 0x00007fbe edx: 0x00000080 ebx: 0x00000000
; esp: 0x00004ffe ebp: 0x00000000 esi: 0xffff160c edi: 0x00000040
; IOPL=0 id vip vif ac vm rf nt of df IF tf sf ZF af PF cf

 

; readdrive
;
;  entry:
;	ax contains number of sectors to read
;       es segment for DAP structure
;	ds segment for transfer buffer
;	si offset for transfer buffer
;       es:003E + 4 and es:0042 + 4 absolute number start sectors to read
;	es:0024 drive index
;	di (L) and bx (H) contain offset to absolute start for begin read
readdrive  proc near
	push	ds		; save ds and dx
	push	dx
	mov	dx, ds
	push	es
	pop	ds		; set ds to entry es value
	; DAP : Disk Address Packet (16 bytes)
	; offset range 	size 	description
	; 00h 		1 byte 	size of DAP = 16 = 10h
	; 01h 		1 byte 	unused, should be zero
	; 02h 		1 byte 	number of sectors to be read, 0..127 (= 7Fh)
	; 03h 		1 byte 	unused, should be zero
	; 04h..07h    4 bytes 	segment:offset pointer to the memory buffer
		;			to which sectors will be transferred
	; 08h..0Fh    8 bytes 	absolute number of the start of the sectors to be read
	; This routine DAP structure:
		; ds:0046		size of DAP - 16 bytes always
		; ds:0047		always zero
		; ds:0048		number of sectors to read
		; ds:0049		always zero
		; ds:004A to 004D       segment:offset pointer transfer buffer
		; ds:004E to 0055	absolute number of the start of the sectors to be
		;                       read (1st sector of drive has number 0)
		; Load DAP
	mov	ds:48h,	ax	; number of sectors to read, ax contains on entry
	mov	ds:4Ch,	dx	; Buffer segment
	mov	ds:4Ah,	si	; Buffer offset
	mov	si, 46h         ; DAP offset
	mov	eax, ds:3Eh	; move sector read start from storage area
	mov	ds:4Eh,	eax	; ds:003E to ds:0055 to DAP
	mov	eax, ds:42h
	mov	ds:52h,	eax
	add	ds:4Eh,	edi
	adc	ds:52h,	ebx
	; DAP located at ds:0046
	mov	ah, 42h         ; 42h = function number for extended read
	mov	dl, ds:24h      ; drive index
	mov	al, 0
	int	13h		; cf  Set On Error, Clear If No Error
				; ah  Return Code
	jnb	short goodread
	or	ah, ah
	jnz	short readerror
goodread:
	pop	dx		; restore entry dx and ds before returning
	pop	ds
	retn
readerror:                      ; display some DAP info
	push	ax
	mov	eax, ds:52h
	shr	eax, 10h
	call	dispaddress
	mov	eax, ds:52h
	call	dispaddress
	mov	eax, ds:4Eh
	shr	eax, 10h
	call	dispaddress
	mov	eax, ds:4Eh
	call	dispaddress
	mov	ax, ds:48h
	shl	eax, 10h
	pop	ax
	mov	al, dl
	mov	si, 0DEh	; SYS02027  message
	call	$+3		; really a jump to displayerr - never returns
readdrive  endp
; displayerr
;  Display error message pointed to by ds:(e)si and address
;  then hang the system
displayerr	proc near
	cld
	push	eax
_dispnextchar:
	lodsb			; Load byte at address DS:(E)SI into AL
	test	al, 0FFh
	jz	short   _endmessage
	mov	ah, 0Eh
	mov	bx, 7
	int	10h
	jmp	short _dispnextchar
_endmessage:
	sti
	pop	eax
	push	eax
	and	eax, 0FFFF0000h
	shr	eax, 10h
	call	dispaddress
	mov	al, 3Ah
	mov	ah, 0Eh
	mov	bx, 7
	int	10h
	pop	eax
	call	dispaddress
_hangsystem:
	jmp	short   _hangsystem
displayerr	endp
; dispaddress
;  Display address in hex
 dispaddress	proc near
	push	ax
	mov	al, ah
	and	al, 0F0h
	mov	cl, 4
	shr	al, cl
	call	dispchar
	pop	ax
	push	ax
	mov	al, ah
	and	al, 0Fh
	call	dispchar
	pop	ax
	push	ax
	and	al, 0F0h
	mov	cl, 4
	shr	al, cl
	call	dispchar
	pop	ax
	push	ax
	and	al, 0Fh
	call	dispchar
	pop	ax
	retn
dispaddress  endp
; dispchar
;  Output char from dispaddress
dispchar	proc near
	add	al, 30h
	cmp	al, 39h
	jle	short   _dispchar1
	add	al, 7
_dispchar1:
	mov	ah, 0Eh
	mov	bx, 7
	int	10h
	retn
dispchar	endp

 

eax: 0x0000199c 6556
ecx: 0x00007fbe 32702
edx: 0x00000080 128
ebx: 0x00000000 0
esp: 0x00007c00 31744
ebp: 0x00000000 0
esi: 0xffff0046 -65466
edi: 0x00000000 0
eip: 0x0000199c
eflags 0x00000246
IOPL=0 id vip vif ac vm rf nt of df IF tf sf ZF af PF cf

; cs:s=0x8800 ds:s=0x8800 ss:s=0x0000 es:s=0x07c0 fs:s=0x3000

0x0008924a <bogus+ 0>: 0x03 0x00 0x00 0x10 0x00 0xae 0x00 0x00
0x00089252 <bogus+ 8>: 0x00 0x88 0x00 0x50 0x00 0x00 0x7c 0x00
0x0008925a <bogus+ 16>: 0xe9 0xea 0x00 0x00 0x00 0x00 0x00 0x00
0x00089262 <bogus+ 24>: 0x00 0x00 0x9c 0x1a 0x00 0x88

 

8924A ft_cfiles       dw 0x0003
8924C ft_ldrseg       dw 0x1000
8924E ft_ldrlen       dd 0xAE ; will vary with version of os2ldr used
89252 ft_museg        dw 0x0000
89254 ft_mulen        dd 0
89258 ft_mfsseg       dw 0
8925A ft_mfslen       dd 0
8925E ft_ripseg       dw 0
89260 ft_riplen       dd 0
; microFSD vector table
89264 ft_muOpen_OFF   		dw 0
89266 ft_muOpen_SEG   		dw 0
89268 ft_muRead_OFF   		dw 0
8926A ft_muRead_SEG   		dw 0
8926C ft_muClose_OFF  		dw 0
8926E ft_muClose_SEG  		dw 0
89270 ft_muTerminate_OFF 	dw 0
89272 ft_muTerminate_SEG 	dw 0

 

This is the main entry point from Phase 2:

 

(SEG:199C)
_entry	proc far
	push	cs
	pop	ax
	mov	es, ax		; swap cs to es
	mov	ds, ax		; set ds to cs
	; setup	stack
	cli
	mov	ss, ax		; set ss to cs
	mov	sp, 5000h
	sti
	; es / ds / ss	set to entry cs
	call	readsuperblock
	mov	ax, 202h
	push	ds
	push	ax
	push	ds
	mov	si, 2B2Fh	; ALTF2ON.$ location
	push	si
	mov	ax, cs
	mov	word ptr cs:loc_899BC+3, ax
loc_899BC:
	call	ft_muOpen
	add	sp, 8		; clean	up the stack - post C function?
	or	ax, ax
	jnz	short MAIN_JMP_1 ; ?? zero return is good result
	mov	al, 1
	mov	ds:byte_8AB2E, al
	mov	ax, cs
	mov	word ptr cs:loc_899D3+3, ax
loc_899D3:
	call	ft_muClose
MAIN_JMP_1:
	mov	si, 1D6h		; os2boot file name
	mov	di, 7Ch			; os2boot load address
	mov	ds:ft_mfsseg, di  ; miniFSD location
	call	LoadFile
	test	bx, 1		; test if good load
	jz	short good_os2boot
	mov	si, 1274h	; SYS1475: The file OS2BOOT cannot be found
	call	ErrorHangSys    ; does not return
good_os2boot:
	mov	eax, ds:dword_88202
	mov	ds:ft_mfslen, eax    ; miniFSD length
	mov	si, 1CFh	; os2ldr file name
	mov	di, 1000h	; os2boot load address
	mov	ds:ft_ldrseg, di    ; os2ldr location
	call	LoadFile
	test	bx, 1		; test if good load
	jz	short good_os2ldr
	mov	si, 12ABh	; Missing OS2LDR
	call	ErrorHangSys    ; does not return
good_os2ldr:
	mov	eax, ds:dword_88202
	mov	ds:ft_ldrlen, eax    ; os2ldr length
	; loading 0Fh to DispCopyInd stops display
	; of copyright message
	mov	al, 0Fh
	mov	ds:DispCopyRInd, al
	; setup MicroFSD entry
	push	ds
	pop	ax
	mov	ds:ft_museg, ax    ; MicroFSD location
	mov	eax, 5000h
	mov	ds:ft_mulen, eax    ; MicroFSD length
	; number of entries
	mov	ds:ft_cfiles, 3
	; sets up a function list
	mov	ds:ft_muOpen_SEG, ds
	mov	ds:ft_muOpen_OFF, 1A9Ch	; ds:1A9C
	mov	ds:ft_muRead_SEG, ds
	mov	ds:ft_muRead_OFF, 1BD4h	; ds:1BD4
	mov	ds:ft_muClose_SEG, ds
	mov	ds:ft_muClose_OFF, 1DAEh ; ds:1DAE
	mov	ds:ft_muTerminate_SEG, ds
	mov	ds:ft_muTerminate_OFF, 1DAEh ; ds:1DD4
	mov	ds:ft_ripseg, 0
	mov	ds:ft_riplen, 0
	push	ds
	push	cs
	pop	ds
	mov	eax, dword ptr ds:aJfs+8
	mov	ds:dword_8801C,	eax
	pop	ds
	assume ds:nothing
	xor	di, di		; zero di
	mov	es:[di], eax
	mov	dl, ds:24h
	mov	dh, dl
	mov	ds:24h,	dx
	mov	dh, 14h
	mov	si, 0Bh
	push	ds
	pop	es
	mov	di, 124Ah
	mov	ax, ds:124Ch
	push	ax
	xor	ax, ax
	push	ax
	retf	; Should be a return which enters os2ldr
_entry	endp

 

 

readsuperblock  proc near
	mov	si, 160Ch    ; 8800:160C buffer
	mov	ax, 1		; read 1 sector
	mov	edi, 40h      ; offset from beginning of partition
	mov	ebx, 0         ; for read
	; read 1 sector from offset of partition 0x40 (64) or 32,768
	; from the IBM docs this would be the superblock and it gets
	; read into memory at offset 160C buffer
	; see openJFS -- jfs_superblock.h
	call	readdrive      ; load superblock to 0x8960C
	mov	eax, ds:161Ch
	bsf	ecx, eax
	mov	ds:1942h, cx     ; 0x89942
	mov	edx, ds:163Ch
	and	edx, 0FF000000h
	shr	edx, 18h
	mov	eax, ds:1640h
	shld	edx, eax, cl
	shl	eax, cl
	mov	ds:1858h, eax         ; 0x89858
	mov	ds:185Ch, edx         ; 0x8985C
	mov	dword ptr ds:23Eh, 2  ; 0x8823E
	push	bp
	mov	bp, sp
	xor	ecx, ecx
	mov	di, 3D45h
	call	sub_8AA59
	mov	cl, al
	mov	ch, 0
	push	cx
	mov	di, 1509h
	call	sub_8AA59
	mov	bx, 1
	pop	cx
	cmp	cl, al
	jg	short loc_8AA55
	mov	si, 3D45h
	mov	di, 1509h
	mov	bx, 1
	repe cmpsb
	or	cl, cl
	jnz	short loc_8AA55
	cmp	byte ptr es:[di], 5Ch ;	'\'
	jz	short loc_8AA3E
	cmp	byte ptr es:[di], 0
	jnz	short loc_8AA55
loc_8AA3E:
	mov	al, [si-1]
	cmp	al, es:[di-1]
	jnz	short loc_8AA55
	mov	di, si
	mov	al, 5Ch	; '\'
	stosb
	mov	ds:14EFh, di
	xor	ax, ax
	stosb
	xor	bx, bx
loc_8AA55:
	mov	ax, bx
	pop	bp
	retn
readsuperblock  endp