/*++

Copyright (c) 2012 Minoca Corp. All Rights Reserved

Module Name:

    vbr.S

Abstract:

    This module implements the Volume Boot Record code used to bootstrap the
    operating system. This code lives on the first sector of the partition or
    disk.

Author:

    Evan Green 19-Jun-2012

Environment:

    MBR

--*/

##
## ---------------------------------------------------------------- Definitions
##

##
## Define the segment where the BIOS loads this MBR code.
##

.equ BOOT_ADDRESS,          0x7C00

##
## Define the boot stack, with some room for locals.
##

.equ BOOT_STACK,            (BOOT_ADDRESS - 0x8)
.equ STACK_SIZE,            0x3000

##
## Define the size of a disk sector.
##

.equ SECTOR_SIZE,           0x200

##
## Define the physical address where the second stage loader is loaded.
##

.equ LOADER_ADDRESS,        0x7E00

##
## Define the segment descriptors within the initial GDT.
##

.equ CS_DESCRIPTOR,         0x08
.equ DS_DESCRIPTOR,         0x10

##
## ----------------------------------------------------------------------- Code
##

##
## .text specifies that this code belongs in the executable section. This is
## the only section in the MBR code, data also lives in the text section.
##
## .code16 specifies that this is 16-bit real mode code.
##

.text
.code16

##
## .globl allows this label to be visible to the linker.
##

.globl _start

_start:
    jmp     AfterFileSystemParameters   # Jump over the FS parameters next.
    nop                                 # A free byte. Go nuts!

##
## These are the well defined locations where the LBA of this sector lives. If
## this is a partition, then this contains the partition offset.
##

.org 0x5C

BootCodeStartSector:
    .long 0

BootCodeSectorCount:
    .byte 0

AfterFileSystemParameters:
    cli                                 # Disable interrupts for stack setup.
    xorl    %eax, %eax                  # Zero out AX.
    movl    %eax, %esp                  # Zero out ESP.
    movw    %ax, %ds                    # Zero out DS.
    movw    %ax, %es                    # Zero out ES.
    movw    %ax, %ss                    # Zero out SS.
    movw    $BOOT_STACK, %si            # Load SI with the stack pointer.
    movw    %si, %sp                    # Set it as the stack.
    movw    %si, %bp                    # Also set it as the base.
    sti                                 # Re-enable interrupts.

    ##
    ## Check to see if INT 13 extensions are supported. If CF is cleared, BX
    ## changes to 0xAA55, and the lowest bit in CX is set, then they are
    ## supported.
    ##

    movb    %dl, (%bp)                  # Save the drive number.
    movb    $0x41, %ah                  # Set AH.
    movw    $0x55AA, %bx                # Set BX to the magic value.
    int     $0x13                       # Call the BIOS.
    jb      DiskReadError               # Fail if CF is set.
    cmpw    $0xAA55, %bx                # See if BX changed to 0xAA55.
    jnz     DiskReadError               # Fail if BX wasn't set to the magic.
    test    $0x0001, %cx                # See if the lowest bit of CX is set.
    jz      DiskReadError               # Fail if lowest bit of CX is not set.

    ##
    ## Get the boot sector offset and count.
    ##

    xorw    %cx, %cx
    movb    BootCodeSectorCount, %cl    # Get the sector count.
    cmpb    $0, %cl                     # Compare to zero.
    jz      BootCodeFailure             # Fail if a count is not set.
    movl    BootCodeStartSector, %eax   # Load the start sector.
    incl    %eax

    ##
    ## Perform an INT 13 extended read of the boot sectors. The extended read
    ## takes a disk packet that looks like this:
    ## Offset 0, size 1: Size of packet (16 or 24 bytes).
    ## Offset 1, size 1: Reserved (0).
    ## Offset 2, size 2: Number of blocks to transfer.
    ## Offset 4, size 4: Transfer buffer.
    ## Offset 8, size 8: Absolute starting sector.
    ## Offset 0x10, size 8: 64-bit flat address of transfer buffer. Only used
    ## if the value at offset 4 is 0xFFFFFFFF (which it is not in this case).
    ##
    ## Remember that things need to be pushed on in reverse.
    ##

    pushl   $0                          # Push starting sector high.
    pushl   %eax                        # Push the starting sector low.
    pushl   $LOADER_ADDRESS             # Push the transfer buffer.
    pushw   %cx                         # Push the sector count.
    pushw   $0x0010                     # Push reserved and packet size.
    movb    $0x42, %ah                  # Function 0x42, extended read.
    movb    (%bp), %dl                  # Load the drive number.
    movw    %sp, %si                    # SI points to the disk packet address.
    int     $0x13                       # Read the first sector.
    jb      DiskReadError               # Fail if it didn't read.
    addw    $0x10, %sp                  # Pop the parameters off.

    ##
    ## Enable the A20 address line. The 8088 processor in the original PC only
    ## had 20 address lines, and when it reached its topmost address at 1MB it
    ## would silently wrap around. When the 80286 was released it was designed
    ## to be compatible with programs written for the 8088, which had come to
    ## rely on this wrapping behavior. The 8042 keyboard controller had an
    ## extra pin, so the gate to control the A20 line behavior on newer (286)
    ## processors was stored there. The keyboard status register is read from
    ## port 0x64. If bit 2 is set (00000010b) the input buffer is full, and the
    ## controller is not accepting commands. The CPU can control the keyboard
    ## controller by writing to port 0x64. Writing 0xd1 corresponds to "Write
    ## Output port". Writing 0xDF to port 0x60, the keyboard command register,
    ## tells the controller to enable the A20 line (as it sets bit 2, which
    ## controlled that line).
    ##

    call    WaitForKeyboardController   # Wait for not busy.
    jnz     AfterA20Line                # Jump out if stuck.
    cli                                 # Disable interrupts.
    movb    $0xD1, %al                  # Send command 'Write output port'.
    outb    %al, $0x64                  # Write 0xD1 to port 0x64.
    call    WaitForKeyboardController   # Wait for not busy.
    movb    $0xDF, %al                  # Set the mask.
    outb    %al, $0x60                  # Write 0xDF to port 0x60.
    call    WaitForKeyboardController   # Wait for not busy again.
    movb    $0xFF, %al                  # Clear the command.
    outb    %al, $0x64                  # Write 0xFF to port 0x60.
    call    WaitForKeyboardController   # One last time.
    sti                                 # Re-enable interrupts.

AfterA20Line:

    ##
    ## Jump off to the remainder of the boot sectors.
    ##

    movb    (%bp), %dl                  # Load the drive number.
    jmp     $0, $LOADER_ADDRESS         # Rock and roll.

    ##
    ## In error cases down here, print a message and die sadly.
    ##

DiskReadError:
    movw    $ReadFailureMessage, %si
    jmp     PrintStringAndDie

BootCodeFailure:
    movw    $BootCodeFailureMessage, %si
    jmp     PrintStringAndDie

    ##
    ## Print a null-terminated string, and then end it.
    ##

PrintStringAndDie:
    cld
    lodsb                               # Load SI into AL.
    cmp     $0, %al                     # Is this the null terminator?
    jz      Die                         # If so, then go quietly into the night.
    movw    $0x0007, %bx                # Display normal white on black.
    movb    $0x0E, %ah                  # Print character function.
    int     $0x10                       # Print the character.
    jmp     PrintStringAndDie           # Loop printing more characters.

    ##
    ## This is the end of the line.
    ##

Die:
    hlt                                 # Stop.
    jmp     Die                         # Die again forever.

##
## This routine waits for the keyboard controller's busy bit to clear.
##

WaitForKeyboardController:
    xorw    %cx, %cx                    # Zero out CX.

WaitForKeyboardControllerLoop:
    inb     $0x64, %al                  # Read port 0x64.
    jmp     WaitForKeyboardControllerDelay  # Add in a tiny bit of delay.

WaitForKeyboardControllerDelay:
    and     $0x2, %al                   # Isolate the second bit.
    loopne  WaitForKeyboardControllerLoop   # Loop if it's still set.
    and     $0x2, %al                   # Set the flags for return.
    ret                                 # Return.

##
## Define the messages that are printed from the MBR.
##

ReadFailureMessage:
    .asciz "Disk Read Error"

BootCodeFailureMessage:
    .asciz "Boot code lost"

##
## Define the signature that the BIOS uses to determine this disk is bootable.
##

.org 0x1FE

    .byte   0x55
    .byte   0xAA

##
## This portion onward is on the second sector. It is loaded by the first
## sector above, it is the destination of LOADER_ADDRESS.
##

.org 0x200

    ##
    ## Clear the screen, since it's easy here.
    ##

    pushw   %dx
    movw    $0x0003, %ax                # BH = Clear Screen Function. BL = Text.
    int     $0x10                       # Clear the screen.
    popw    %dx

##
## Prepare to switch from 16-bit Real Mode to 32-bit Protected Mode.
## Segment registers turn into descriptors (indices into the Global
## Descriptor Table) which must be set up prior to switching into protected
## mode.
##

    cli                                 # Disable interrupts.
    lgdt    Gdt                         # Load the GDT.
    movl    %cr0, %eax                  # Get the CR0 control register.
    orl     $0x1, %eax                  # Set bit 0 to enable Protected Mode.
    movl    %eax, %cr0                  # Set CR0.
    movw    $DS_DESCRIPTOR, %ax         # Load the data descriptor.
    movw    %ax, %ds                    # Set the data segment.
    movw    %ax, %ss                    # Set the stack segment as well.
    movw    %ax, %es                    # Set up ES.
    movw    %ax, %fs                    # Set up FS.
    movw    %ax, %gs                    # Set up GS.

##
## A long jump is required to complete the switch to protected mode. Long jumps
## specify the segment descriptor and an offset from that segment.
##

    ljmp   $CS_DESCRIPTOR, $ProtectedModeCode

##
## The .code32 assembler directive lets the compiler know that it should
## generate 32-bit instructions. Load the segment descriptors, set up the
## initial stack, and jump to loader code.
##

.code32

ProtectedModeCode:
    andl    $0x000000FF, %edx           # Clear all but dl.
    pushl   %edx                        # Push boot drive number.
    movl    BootCodeStartSector, %eax   # Load the start sector.
    pushl   $0                          # Push the high partition offset.
    pushl   %eax                        # Push the low partition offset.
    pushl   $STACK_SIZE                 # Push stack size parameter.
    pushl   $BOOT_STACK                 # Push stack location.
    xorl    %ebp, %ebp                  # Zero out stack base.

##
## Jump to the loader image. Control will not return from here.
##

    call    BoMain                      # Control should not return... but
    int     $0x18                       # if it does, "Press a key to reboot".
    jmp     Die                         # And loop forever.

##
## --------------------------------------------------------- Internal Functions
##

##
## ---------------------------------------------------------------- Definitions
##

##
## The GDT must be loaded from an address aligned on 8 bytes.
##

.align 8

##
## GDT Table. The GDT table sets up the segmentation features of the processor
## and privilege levels. In this case set up two descriptors, a code segment
## and a data segment, both of which simply span the entire address space.
## This way the kernel essentially has full control over the whole address
## space. A GDT entry is 8 bytes long, and looks as follows:
##     USHORT limit_low;       // The lower 16 bits of the limit.
##     USHORT base_low;        // The lower 16 bits of the base.
##     UCHAR base_middle;      // The next 8 bits of the base.
##     UCHAR access;           // Access flags, described below.
##     UCHAR granularity;      // Defined below.
##     UCHAR base_high;        // The high 8 bits of the base.
##
## The granularity byte has the following fields:
##     |  7  |  6  |  5  |  4  |  3  |  2  |  1  |  0  |
##     |     |     |     |     |                       |
##     |  G  |  D  |  0  |  A  | Segment length 19:16  |
##
##     G - Granularity. 0 = 1 byte, 1 = 1 KByte.
##
##     D - Operand Size. 0 = 16 bit, 1 = 32 bit.
##
##     0 - Always zero.
##
##     A - Available for system use (always zero).
##
## The access byte has the following fields:
##     |  7  |  6  |  5  |  4  |  3  |  2  |  1  |  0  |
##     |     |           |     |                       |
##     |  P  |    DPL    | DT  |         Type          |
##
##     P - Is segment present (1 = Yes)
##
##     DPL - Descriptor privilege level: Ring 0-3. Zero is the highest
##           privilege, 3 is the lowest (least privileged).
##
##     Type - Segment type: code segment / data segment.
##

Gdt:
    .word   (3 * 8) - 1                         # GDT table limit
    .long   GdtTable                            # GDT table location

GdtTable:
    .long   0x0                         # The first GDT entry is called the
    .long   0x0                         # null descriptor, it is essentially
                                        # unused by the processor.

##
## Kernel code segment descriptor
##

    .word   0xFFFF                      # Limit 15:0
    .word   0x0                         # Base 15:0
    .byte   0x0                         # Base 23:16
    .byte   0x9A                        # Access: Present, Ring 0, Code Segment
    .byte   0xCF                        # Granularity: 1Kb, 32-bit operands
    .byte   0x00                        # Base 31:24

##
## Kernel data segment descriptor
##

    .word   0xFFFF                      # Limit 15:0
    .word   0x0                         # Base 15:0
    .byte   0x0                         # Base 23:16
    .byte   0x92                        # Access: Present, Ring 0, Data Segment
    .byte   0xCF                        # Granularity: 1Kb, 32-bit operands
    .byte   0x00                        # Base 31:24

