bootblock.s
Skip to line: 2025 - 2050 - 2075 - 2100 - 2125 - 2150 - 2175 - 2206


Highlighted entries were made in the last day
Select a different time increment to highlight entries
Current GMT time: Dec 03 2024 23:55:44

If you have a comment for bootblock.s, please click here.
There was an error! 2002: php_network_getaddresses: getaddrinfo failed: Name or service not known
2000   !       Bootblock 1.4 - Minix boot block.               Author: Kees J. Bot
2001   !                                                               21 Dec 1991
2002   !
2003   ! When the PC is powered on, it will try to read the first sector of floppy
2004   ! disk 0 at address 0x7C00.  If this fails due to the absence of flexible
2005   ! magnetic media, it will read the master boot record from the first sector
2006   ! of the hard disk.  This sector not only contains executable code, but also
2007   ! the partition table of the hard disk.  When executed, it will select the
2008   ! active partition and load the first sector of that at address 0x7C00.
2009   ! This file contains the code that is eventually read from either the floppy
2010   ! disk, or the hard disk partition.  It is just smart enough to load the
2011   ! secondary boot code from the boot device into memory at address 0x10000 and
2012   ! execute that.  The disk addresses for this secondary boot code are patched
2013   ! into this code by installboot as 24-bit sector numbers and 8-bit sector
2014   ! counts above enddata upwards.  The secondary boot code is in turn smart
2015   ! enough to load the different parts of the Minix kernel into memory and
2016   ! execute them to finally get Minix started.
Expand/Collapse Item2017   !
It's also important to know that dl contains the drive that was booted (in other words, the drive from which this code originated).  This is 0x00 or 0x01 for the first or second floppy drives or 0x80, 0x81, 0x82, or 0x83 for the first through fourth hard drives.

If this code originated from a hard drive and the master boot code loaded this code, then the partition table entry that corresponds to the booted partition is passed in es:si.

2018
2019           LOADOFF    =    0x7C00  ! 0x0000:LOADOFF is where this code is loaded
2020           BOOTSEG    =    0x1000  ! Secondary boot code segment.
2021           BOOTOFF    =    0x0030  ! Offset into secondary boot above header
Expand/Collapse Item2022           BUFFER     =    0x0600  ! First free memory
Expand/Collapse Item2023            LOWSEC     =         8  ! Offset of logical first sector in partition
2024                                   ! table
2025
Expand/Collapse Item2026  ! Variables addressed using bp register
Expand/Collapse Item2027            device     =         0  ! The boot device
Expand/Collapse Item2028            lowsec     =         2  ! Offset of boot partition within drive
Expand/Collapse Item2029            secpcyl    =         6  ! Sectors per cylinder = heads * sectors
This is fairly common practice in assembler.  Several values are pushed on the stack (lines 2043, 2044, and 2046) and then the stack pointer, sp , is saved in bp (mov bp, sp - line 2047).  If 2 byte values are pushed on the stack, then the last 2 bytes are accessed by 0(bp) , the next-to-last 2 bytes are accessed by 2(bp) , and so on (see line 2095).

While these 3 variables take up 8 bytes, only 6 bytes are pushed onto the stack.  lowsec (lines 2043 and 2044) and device (line 2046) are pushed, but what about secpcyl ?  When a value is finally stored in secpcyl (line 2110), the first 2 bytes of the bootblock code are overwritten - the first xor ax, ax instruction (line 2036) is overwritten.  This doesn't matter since the code never jumps back to the beginning.  This saves not only the 2 bytes needed for the variable but also the memory required for a push instruction.

2030
2031   .text
2032
2033   ! Start boot procedure.
2034
2035   boot:
Expand/Collapse Item2036            xor     ax, ax          ! ax = 0x0000, the vector segment
Expand/Collapse Item2037            mov     ds, ax
The first instruction zeroes the ax register (any number xor'ed with itself is zero).  This is a pretty common practice.  The instruction

mov ax, #0

is slower and is 3 bytes compared with xor's 2 bytes.

One thing that initially confuses people with assembler is the order of the operands.  The syntax of the mov instruction is:

mov destination, source

This seems a little strange to me but most assemblers use this syntax.

Expand/Collapse Item2038            cli                     ! Ignore interrupts while setting stack
Expand/Collapse Item2039            mov     ss, ax          ! ss = ds = vector segment
Expand/Collapse Item2040            mov     sp, #LOADOFF    ! Usual place for a bootstrap stack
Expand/Collapse Item2041            sti
Whenever a value is moved to the stack register (ss) or the stack pointer (sp), the interrupts must be disabled first.  The stack holds the address to which an interrupt returns after its completion.  If the ss and sp registers are in flux and an interrupt occurs, it's impossible to predict where the code will return.

The interrupts are disabled with the cli (clear interrupts) instruction and reenabled with the sti (set interrupts) instruction.

What's the pound (#) sign all about?  The pound sign indicates that the value of LOADOFF (0x7C00 - see line 2019) is moved to the register rather than the contents of the memory location LOADOFF.

2042
2043           push    ax
2044           push    ax              ! Push a zero lowsec(bp)
2045
2046           push    dx              ! Boot device in dl will be device(bp)
Expand/Collapse Item2047           mov     bp, sp          ! Using var(bp) is one byte cheaper then var.
This is a little technical, but I believe the idea is that an immediate instruction is one byte larger than a special address mode instruction (ss:disp[bp]).  For example, the instruction on line 2095 is one byte smaller than it would have been if 2 bytes had been set aside (using .data2) for lowsec and the mov es, LOADOFF+lowsec instruction were used.  Since the bootblock code and sector addresses (see lines 2204-2206) must fit in a single block (1 block = 2 sectors = 2 * 512 bytes = 1024 bytes), space needs to be conserved.  Since a stack doesn't take up any hard drive space, hard drive space is saved by not allocating space within the code (using .data1 or .data2).
2048
2049           push    es
2050           push    si              ! es:si = partition table entry if hard disk
2051
Expand/Collapse Item2052            mov     di, #LOADOFF+sectors    ! char *di = sectors;
di now points to sectors (line 2200).
2053
Expand/Collapse Item2054            testb   dl, dl          ! Winchester disks if dl >= 0x80
Expand/Collapse Item2055            jge     floppy
testb sets the sign flag if the value in dl is negative.  If dl is not negative, jge jumps to floppy.  When is the value negative?  In two's complement notation, if the rightmost bit is a 1 then the value is negative.  So for a one byte value, anything greater than 0x7F (=0111111) is negative.  Remember that 0x00 and 0x01 correspond to the first and second floppy drives and 0x80, 0x81, 0x82, and 0x83 correspond to hard drives 1-4.
2056
2057   winchester:
2058
2059   ! Get the offset of the first sector of the boot partition from the partition
2060   ! table.  The table is found at es:si, the lowsec parameter at offset LOWSEC.
2061
Expand/Collapse Item2062            eseg
Expand/Collapse Item2063            les     ax, LOWSEC(si)    ! es:ax = LOWSEC+2(si):LOWSEC(si)
Expand/Collapse Item2064            mov     lowsec+0(bp), ax  ! Low 16 bits of partition's first sector
Expand/Collapse Item2065            mov     lowsec+2(bp), es  ! High 16 bits of partition's first sector

An instruction prepended by
eseg uses es (instead of ds) as the segment register.

The les instruction loads the register specified by the first operand (in this case ax) with an offset address and es with a segment register.  The value at es:LOWSEC(si) is loaded in ax and the value at es:LOWSEC+2(si) is loaded in es.  The eseg prefix is needed since es:LOWSEC(si), not ds:LOWSEC(si),  is the memory address that holds the lowest sector (see comment for line 2017). Note that the es and ax registers are overwritten by the eseg les ax, LOWSEC(si) instruction. For this reason, the es and ax registers were pushed onto the stack on lines 2049-2050.

2066
2067   ! Get the drive parameters, the number of sectors is bluntly written into the
2068   ! floppy disk sectors/track array.
2069
2070           movb    ah, #0x08       ! Code for drive parameters
Expand/Collapse Item2071            int     0x13            ! dl still contains drive
int 0x13, ah=0x08 returns the device geometry of the drive specified by dldl will be 0x80 for the first drive, 0x81 for the second, 0x82 for the third and 0x83 for the fourth. int 0x13, ah=0x08 returns the maximum sector number in bits 0-6 of cl and the maximum head number in dh.  Adding to the confusion, the value that int 0x13, ah=0x08 returns for the maximum head number has a 0-origin.  This means that if int 0x13, ah=0x08 returns a 15 for the maximum head number, there are actually 16 heads; dh is incremented on line 2074 to move to a 1-origin.
2072           andb    cl, #0x3F       ! cl = max sector number (1-origin)
2073           movb    (di), cl        ! Number of sectors per track
2074           incb    dh              ! dh = 1 + max head number (0-origin)
2075           jmp     loadboot
2076
Expand/Collapse Item2077    ! Floppy:
Expand/Collapse Item2078    ! Execute three read tests to determine the drive type.  Test for each floppy
Expand/Collapse Item2079    ! type by reading the last sector on the first track.  If it fails, try a type
Expand/Collapse Item2080    ! that has less sectors.  Therefore we start with 1.44M (18 sectors) then 1.2M
Expand/Collapse Item2081    ! (15 sectors) ending with 720K/360K (both 9 sectors).
An attempt to read the 18th sector on the first track of the floppy is first made.  If that attempt fails, the drive specified by dl (either 0x00 or 0x01) is not a functioning 1.44M floppy drive.  An attempt to read the 15th sector on the first track is next made.  If that fails, the drive specified by dl is not a functioning 1.2M floppy drive.  An attempt to read the 9th sector is the final attempt (for 720K/360K floppy drives).
2082
Expand/Collapse Item2083    next:   inc     di              ! Next number of sectors per track
di points to sectors (see lines 2052 and 2200).
2084
Expand/Collapse Item2085    floppy: xorb    ah, ah          ! Reset drive
int 0x13, ah=0x00 resets the drive specified by dl.
2086           int     0x13 2087
2088           movb    cl, (di)        ! cl = number of last sector on track
2089
2090           cmpb    cl, #9          ! No need to do the last 720K/360K test
2091           je      success
2092
2093   ! Try to read the last sector on track 0
2094
2095           mov     es, lowsec(bp)  ! es = vector segment (lowsec = 0)
Expand/Collapse Item2096            mov     bx, #BUFFER     ! es:bx buffer = 0x0000:0x0600
The memory region beginning at address BUFFER is not being used for anything now, so this region can be used as scratch area and hard drive sectors can be copied there to test out the floppy drives.
2097           mov     ax, #0x0201     ! Read sector, #sectors = 1
2098           xorb    ch, ch          ! Track 0, last sector
2099           xorb    dh, dh          ! Drive dl, head 0
Expand/Collapse Item2100            int     0x13
int 0x13, ah=0x02 copies sectors from a hard drive or floppy to memory.  al specifies the number of sectors to copy, bits 0-5 of cl specify the sector number, dh specifies the head number and ch specifies the low 8 bits of the cylinder number and bits 6-7 of cl specify the high bits of the cylinder number.  es:bx specifies where in memory to load the sectors.  If the int 0x13, ah=0x02 call fails, the carry (C) flag is set.  If the carry flag is set, the jc instruction (line 2101) jumps to memory address next (line 2083).
2101           jc      next            ! Error, try the next floppy type
2102
Expand/Collapse Item2103    success:movb    dh, #2          ! Load number of heads for multiply
A floppy is a single disk with 2 heads, one for each side.
2104
Expand/Collapse Item2105    loadboot:
Lines 2105-2153 are the heart of bootblock.  The sectors listed at memory address addresses (line 2204) are loaded; these sectors make up the secondary boot.  The sector numbers were patched at addresses by the installboot utility program.

Grinding through this code isn't fun.  Everything until line 2141 sets things up for the int 0x13 bios call.

2106   ! Load the secondary boot code from the boot device
2107
2108           movb    al, (di)        ! al = (di) = sectors per track
Expand/Collapse Item2109            mulb    dh              ! dh = heads, ax = heads * sectors
mulb multiplies al by the operand (in this case dh).  The result is placed in ax.
2110           mov     secpcyl(bp), ax ! Sectors per cylinder = heads * sectors
2111
2112           mov     ax, #BOOTSEG    ! Segment to load secondary boot code into
2113           mov     es, ax
2114           xor     bx, bx          ! Load first sector at es:bx = BOOTSEG:0x0000
2115           mov     si, #LOADOFF+addresses  ! Start of the boot code addresses
2116   load:
Expand/Collapse Item2117            mov     ax, 1(si)       ! Get next sector number: low 16 bits
Expand/Collapse Item2118            movb    dl, 3(si)       ! Bits 16-23 for your 8GB disk
Expand/Collapse Item2119            xorb    dh, dh          ! dx:ax = sector within partition
The installboot utility patches in 2 values for each 4 byte entry at addresses.  It uses the 1st byte of the entry for the count and the 2nd, 3rd and 4th bytes of the entry for the sector number (actually, it's the offset of the sector within the partition - see comments for 2120-2121).  The best way to describe the count is with an example.  If the secondary boot code is found at sectors 500, 501, 502, 509, and 510, there would be two entries at addresses - the first entry would be count=3, sector number=500 and the second entry would be count=2, sector number=509.
Expand/Collapse Item2120            add     ax, lowsec+0(bp)
Expand/Collapse Item2121            adc     dx, lowsec+2(bp)! dx:ax = sector within drive
The value patched at addresses is the offset of the sector within the partition, not the absolute sector number.  The int 0x13, ah=0x02 bios call needs the absolute sector number so lowsec is added to the offset (lowsec is the first sector of the partition).
2122           div     secpcyl(bp)     ! ax = cylinder, dx = sector within cylinder
2123           xchg    ax, dx          ! ax = sector within cylinder, dx = cylinder
2124           movb    ch, dl          ! ch = low 8 bits of cylinder
Expand/Collapse Item2125            divb    (di)            ! al = head, ah = sector (0-origin)
The divb instruction divides ax by the operand (in our example (di) - di points to the memory address that holds the number of sectors per track) and puts the quotient into al and the remainder into ah.
2126           xorb    dl, dl          ! About to shift bits 8-9 of cylinder into dl
2127           shr     dx, #1
2128           shr     dx, #1          ! dl[6..7] = high cylinder
2129           orb     dl, ah          ! dl[0..5] = sector (0-origin)
2130           movb    cl, dl          ! cl[0..5] = sector, cl[6..7] = high cyl
2131           incb    cl              ! cl[0..5] = sector (1-origin)
2132           movb    dh, al          ! dh = al = head
2133           movb    dl, device(bp)  ! dl = device to read
Expand/Collapse Item2134            movb    al, (di)        ! Sectors per track - Sector number (0-origin)
Expand/Collapse Item2135            subb    al, ah          ! = Sectors left on this track
Expand/Collapse Item2136            cmpb    al, (si)        ! Compare with # sectors to read
Expand/Collapse Item2137            jbe     read            ! Can't read past the end of a cylinder?
Expand/Collapse Item2138            movb    al, (si)        ! (si) < sectors left on this track
The int 0x13, ah=0x02 bios call can only read sectors from a single track at a time.  Lines 2134-2138 calculate the maximum numbers of sectors that can be read without crossing a track boundary.

cmp is a subtraction that only affects the flag register. jbe (line 2137) jumps if the first operand in cmp is below or equal to the second operand.

2139   read:   push    ax              ! Save al = sectors to read
2140           movb    ah, #2          ! Code for disk read (all registers in use now!)
Expand/Collapse Item2141            int     0x13            ! Call the BIOS for a read
int 0x13, ah=0x02 copies sectors from a hard drive or floppy to memory.  al specifies the number of sectors to copy, bits 0-5 of cl specify the sector number, dh specifies the head number and ch specifies the low 8 bits of the cylinder number and bits 6-7 of cl specify the high bits of the cylinder number.  es:bx specifies the memory address where the sectors are loaded.  If the int 0x13, ah=0x02 call fails, the carry (C) flag is set and an error code is placed in ah.  If the carry flag is set, the jc instruction jumps to memory address error (line 2170).
2142           pop     cx              ! Restore al in cl
2143           jc      error           ! Jump on disk read error
2144           movb    al, cl          ! Restore al = sectors read
Expand/Collapse Item2145            addb    bh, al          ! bx += 2 * al * 256 (add bytes read)
Expand/Collapse Item2146            addb    bh, al          ! es:bx = where next sector must be read
int 0x13, ah=0x02 writes the sectors to es:bx.

What's done here is somewhat tricky.  If 1 is added to bh, it is equivalent to adding 256 to bx.  If 2 is added to bh, it is equivalent to adding 512 to bx (remember that 1 sector = 512 bytes).

Expand/Collapse Item2147            add     1(si), ax       ! Update address by sectors read
Expand/Collapse Item2148            adcb    3(si), ah       ! Don't forget bits 16-23 (add ah = 0)
Expand/Collapse Item2149            subb    (si), al        ! Decrement sector count by sectors read
Lines 2147-2149 modify the count and sector number in case the consecutive sectors crossed a track boundary and all the sectors could not be read.
2150           jnz     load            ! Not all sectors have been read
Expand/Collapse Item2151            add     si, #4          ! Next (address, count) pair
si points to the current entry.  To move to the next entry, 4 is added to si (each entry is 4 bytes).
2152           cmpb    ah, (si)        ! Done when no sectors to read
2153           jnz     load            ! Read next chunk of secondary boot code
2154
Expand/Collapse Item2155    done:
When the entire secondary boot has been loaded, the code falls through to done .
2156
2157   ! Call secondary boot, assuming a long a.out header (48 bytes).  The a.out
2158   ! header is usually short (32 bytes), but secondary boot has two entry points:
2159   ! One at offset 0 for the long, and one at offset 16 for the short header.
Expand/Collapse Item2160    ! Parameters passed in registers are:
Expand/Collapse Item2161    !
Expand/Collapse Item2162    !       dl      = Boot-device.
Expand/Collapse Item2163    !       es:si   = Partition table entry if hard disk.
Expand/Collapse Item2164    !
Click here to look at boothead.s.

If the secondary boot has either a short header or a long header, the instruction at BOOTSEG:BOOTOFF jumps to the boot address.

These are the same parameters that were passed to the bootstrap (this code).

Keep three things in mind: 1) BOOTSEG (defined in this file) and LOADSEG (defined in boothead.s) are both defined as 0x1000. 2) Adding 2 to the segment register adds 32 to the address formed by the segment:offset registers and adding 3 to the segment adds 48 to the address formed by the segment:offset registers. 3) boot is the offset from the end of the header.

2165           pop     si              ! Restore es:si = partition table entry
2166           pop     es              ! dl is still loaded
2167           jmpf    BOOTOFF, BOOTSEG  ! jmp to sec. boot (skipping header).
2168
2169   ! Read error: print message, hang forever
Expand/Collapse Item2170    error:
If something goes wrong, the code ends up here.  After a failed int0x13, ah=0x02 bios call, ah holds the error code.

It helps to look at an ascii chart before trying to understand the following code.  The ascii string at errno (line 2196) is modified.  The *second* digit is first incremented to reflect (in ascii representation) the value in the left-most 4 bits of ah.  If the right-most 4 bits of ah are nonzero, the code loops back to prnum and increments the *first* digit to reflect (in ascii representation) the value in the right-most 4 bits of ah .

2171           mov     si, #LOADOFF+errno+1
2172   prnum:  movb    al, ah          ! Error number in ah
2173           andb    al, #0x0F       ! Low 4 bits
2174           cmpb    al, #10         ! A-F?
2175           jb      digit           ! 0-9!
2176           addb    al, #7          ! 'A' - ':'
2177   digit:  addb    (si), al        ! Modify '0' in string
2178           dec     si
2179           movb    cl, #4          ! Next 4 bits
2180           shrb    ah, cl
2181           jnz     prnum           ! Again if digit > 0
2182
Expand/Collapse Item2183            mov     si, #LOADOFF+rderr  ! String to print
Note that the strings on lines 2195-2196 occupy consecutive memory addresses and end at errend.  One character at a time is printed until errend is reached.
2184   print:  lodsb                   ! al = *si++ is char to be printed
2185           movb    ah, #0x0E       ! Print character in teletype mode
2186           mov     bx, #0x0001     ! Page 0, foreground color
Expand/Collapse Item2187            int     0x10            ! Call BIOS VIDEO_IO
int 0x10, ah=0x10 prints the character in al to the current cursor position and advances the cursor.  bh holds the active page and bl holds the foreground color.
2188           cmp     si, #LOADOFF+errend  ! End of string reached?
Expand/Collapse Item2189            jb      print
Note that after reaching errend, the code falls through to hang.
2190
2191   ! Hang forever waiting for CTRL-ALT-DEL
2192   hang:   jmp     hang
2193
2194 .data
2195   rderr:  .ascii  "Read error "
2196   errno:  .ascii  "00 "
2197   errend:
2198
2199   ! Floppy disk sectors per track for the 1.44M, 1.2M and 360K/720K types:
2200 sectors:
2201           .data1  18, 15, 9
2202
2203           .align  2
Expand/Collapse Item2204    addresses:
Expand/Collapse Item2205    ! The space below this is for disk addresses for a 66K secondary boot
Expand/Collapse Item2206    ! program (worst case, i.e. file is fragmented).  It should be enough.
The installboot utility patches (count, sector address) pairs here.  Look at line 2105 and lines 2117-2119 for a detailed explanation.

This code and the the secondary boot addresses have to fit into a single block (hence the name "boot block").  1 block = 2 sectors = 2 * 512 bytes = 1024 bytes.  If there is space for a 66K secondary boot when the secondary boot is fragmented (meaning that no 2 sectors are consecutive) then that means that there is space for (66K * 2 sectors/K=) 132 (count, sector address) entries.  Each entry takes up 4 bytes so the total space taken up by the entries is 528 bytes.  So the actual code (everything but the entries) takes up the remaining (1024-528=) 496 bytes.

 
end of fileback up