//------------------------------------------------------------------- // satademo.s // // This example programs the Intel SATA Controller hardware in // protected-mode in order to read the Master Boot Record from // the Hard Disk and display its four Partition-Table entries. // // to assemble: $ as satademo.s -o satademo.o // and to link: $ ld satademo.o -T ldscript -o satademo.b // and install: $ dd if=satademo.b of=/dev/sda4 seek=1 // // NOTE: This program begins executing with CS:IP = 1000:0002. // // programmer: ALLAN CRUSE // written on: 20 NOV 2008 //------------------------------------------------------------------- # These I/O-ports are specific to our 'anchor' nodes .equ IDE_CMD_BLOCK, 0x30E8 .equ IDE_CTL_BLOCK, 0x30D8 .equ DMA_CTL_BLOCK, 0x30B0 .equ IDE_DATA, IDE_CMD_BLOCK+0 .equ IDE_FRATURES, IDE_CMD_BLOCK+1 .equ IDE_ERROR, IDE_CMD_BLOCK+1 .equ IDE_COUNT, IDE_CMD_BLOCK+2 .equ IDE_LBA_LOW, IDE_CMD_BLOCK+3 .equ IDE_LBA_MID, IDE_CMD_BLOCK+4 .equ IDE_LBA_HIGH, IDE_CMD_BLOCK+5 .equ IDE_DEVICE, IDE_CMD_BLOCK+6 .equ IDE_STATUS, IDE_CMD_BLOCK+7 .equ IDE_COMMAND, IDE_CMD_BLOCK+7 .equ IDE_ALT_STATUS, IDE_CTL_BLOCK+2 .equ IDE_CONTROL, IDE_CTL_BLOCK+2 .equ DMA_COMMAND, DMA_CTL_BLOCK+0 .equ DMA_STATUS, DMA_CTL_BLOCK+2 .equ DMA_POINTER, DMA_CTL_BLOCK+4 .section .text #------------------------------------------------------------------- .word 0xABCD # our application signature #------------------------------------------------------------------- main: .code16 # for Pentium 'real-mode' mov %sp, %cs:exit_pointer+0 # preserve the loader's SP mov %ss, %cs:exit_pointer+2 # preserve the loader's SS mov %cs, %ax # address program's data mov %ax, %ds # with DS register mov %ax, %es # also ES register mov %ax, %ss # also SS register lea tos, %sp # and setup new stacktop call initialize_os_tables call enter_protected_mode call execute_program_demo call leave_protected_mode lss %cs:exit_pointer, %sp # recover saved SS and SP lret # exit back to the loader #------------------------------------------------------------------- exit_pointer: .word 0, 0 # for loader's SS and SP #------------------------------------------------------------------- .align 8 # quadword alignment (for optimal access) theGDT: .word 0x0000, 0x0000, 0x0000, 0x0000 # null descriptor .equ sel_es, (.-theGDT)+0 # vram-segment's selector .word 0x0007, 0x8000, 0x920B, 0x0080 # vram descriptor .equ sel_cs, (.-theGDT)+0 # code-segment's selector .word 0xFFFF, 0x0000, 0x9A01, 0x0000 # code descriptor .equ sel_ds, (.-theGDT)+0 # data-segment's selector .word 0xFFFF, 0x0000, 0x9201, 0x0000 # data descriptor .equ sel_fs, (.-theGDT)+0 # flat-segment's selector .word 0xFFFF, 0x0000, 0x9200, 0x008F # flat descriptor .equ limGDT, (.-theGDT)-1 # our GDT-segment's limit #------------------------------------------------------------------- theIDT: .space 256 * 8 # enough for 256 gate-descriptors .equ limIDT, (.-theIDT)-1 # our IDT-segment's limit #------------------------------------------------------------------- regGDT: .word limGDT, theGDT, 0x0001 # register-image for GDTR regIDT: .word limIDT, theIDT, 0x0001 # register-image for IDTR regIVT: .word 0x03FF, 0x0000, 0x0000 # register-image for IDTR #------------------------------------------------------------------- initialize_os_tables: # initialize IDT descriptor for gate 0x0D mov $0x0D, %ebx # ID-number for the gate lea theIDT(, %ebx, 8), %di # gate's offset-address movw $isrGPF, 0(%di) # entry-point's loword movw $sel_cs, 2(%di) # code-segment selector movw $0x8E00, 4(%di) # 386 interrupt-gate movw $0x0000, 6(%di) # entry-point's hiword ret #------------------------------------------------------------------- enter_protected_mode: cli # no device interrupts lgdt regGDT # setup GDTR register lidt regIDT # setup IDTR register mov %cr0, %eax # get machine status bts $0, %eax # set PE-bit to 1 mov %eax, %cr0 # enable protection ljmp $sel_cs, $pm # reload register CS pm: mov $sel_ds, %ax mov %ax, %ss # reload register SS mov %ax, %ds # reload register DS xor %ax, %ax # use "null" selector mov %ax, %es # to purge invalid ES mov %ax, %fs # to purge invalid FS mov %ax, %gs # to purge invalid GS ret #------------------------------------------------------------------- leave_protected_mode: mov $sel_ds, %ax # address 64K r/w segment mov %ax, %ds # using DS register mov %ax, %es # and ES register mov $sel_fs, %ax # address 4GB r/w segment mov %ax, %fs # using FS register mov %ax, %gs # and GS register mov %cr0, %eax # get machine status btr $0, %eax # reset PE-bit to 0 mov %eax, %cr0 # disable protection ljmp $0x1000, $rm # reload register CS rm: mov %cs, %ax mov %ax, %ss # reload register SS mov %ax, %ds # reload register DS mov %ax, %es # reload register ES lidt regIVT # restore vector table sti # and allow interrupts ret #------------------------------------------------------------------- # Physical Region Descriptor (PRD) prd_table: .int 0x00020000 # physical region start .short 512 # region-length (bytes) .short 0x8000 # EOT-bit for interrupt dma_status: .byte 0 msg0: .ascii " Master Boot Record: Partition Table " len0: .int . - msg0 att0: .byte 0x2E msg1: .ascii " xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx " len1: .int . - msg1 att1: .byte 0x2E #------------------------------------------------------------------- execute_program_demo: mov %esp, tossave+0 # preserve 32-bit offset mov %ss, tossave+4 # plus 16-bit selector #------------------ # device-selection #------------------ # await controller-status: BSY==0, DRQ==0 mov $IDE_STATUS, %dx xor %cx, %cx spin1: in %dx, %al test $0x88, %al loopnz spin1 # write to Drive/Head register to select device mov $IDE_DEVICE, %dx # port-address in DX mov $0xE0, %al # use LBA=1, Drive=0 out %al, %dx # output to controller # await controller-status: BSY==0, DRQ==0 mov $IDE_STATUS, %dx xor %cx, %cx spin2: in %dx, %al test $0x88, %al loopnz spin2 #---------------------- # setup the Bus Master #---------------------- # load address of the Physical Region Descriptor mov $DMA_POINTER, %dx mov $prd_table, %eax add $0x10000, %eax out %eax, %dx # specify direction for the data-transfer (i.e., to memory) mov $DMA_COMMAND, %dx mov $0x08, %al # set 'write-to-memory' out %al, %dx # clear status interrupt-bit (bit 2) and error-bit (bit 1) mov $DMA_STATUS, %dx mov $0x06, %al out %al, %dx #------------------ # write parameters #------------------ mov $IDE_COUNT, %dx mov $1, %al out %al, %dx mov $IDE_LBA_LOW, %dx mov $0, %al out %al, %dx mov $IDE_LBA_MID, %dx mov $0, %al out %al, %dx mov $IDE_LBA_HIGH, %dx mov $0, %al out %al, %dx mov $IDE_DEVICE, %dx mov $0, %al and $0x0F, %al or $0xE0, %al out %al, %dx #------------------ # initiate command #------------------ mov $IDE_COMMAND, %dx mov $0xC8, %al # 'Read Sector DMA' out %al, %dx #---------------------- # trigger the transfer #---------------------- # engage the Bus Master by writing '1' to command-bit 0 mov $DMA_COMMAND, %dx in %dx, %al or $0x01, %al out %al, %dx #--------------------------- # await transfer completion #--------------------------- # ok, our processor can spin while the DMA remains active mov $100, %bp # timeout-counter spin3: mov $DMA_STATUS, %dx in %dx, %al test $0x01, %al jz cease loopnz spin3 dec %bp jnz spin3 cease: # stop the DMA function by writing '1' to command-bit 0 mov $DMA_COMMAND, %dx in %dx, %al and $0xFE, %al out %al, %dx # check DMA status for errors (and to clear interrupt) mov $DMA_STATUS, %dx in %dx, %al out %al, %dx mov %al, dma_status # see if the MBR's boot-signature was read successfully mov $sel_fs, %ax mov %ax, %fs mov $0x00020000, %ebx mov %fs:510(%ebx), %dx # display the Partition Table mov $sel_ds, %ax mov %ax, %ds mov $sel_es, %ax mov %ax, %es mov $1600, %edi lea msg0, %esi mov len0, %ecx mov att0, %ah cld nxch0: lodsb stosw loop nxch0 xor %ebp, %ebp nxpte: # format the next partition-table entry imul $16, %ebp, %eax lea 446(%ebx, %eax), %esi lea msg1, %edi mov $4, %ecx nxlwd: inc %edi lodsl %fs:(%esi) call eax2hex add $8, %edi loop nxlwd imul $160, %ebp, %eax lea 1760(%eax), %edi lea msg1, %esi mov len1, %ecx mov att1, %ah cld nxch1: lodsb stosw loop nxch1 inc %ebp cmp $4, %ebp jb nxpte # your application-specific demo-routines would go here ### int $0xFF #<-- this instruction is only for testing fin: lss %cs:tossave, %esp # reload our saved SS:ESP ret # return to main function #------------------------------------------------------------------- tossave: .long 0, 0 # stores a 48-bit pointer #------------------------------------------------------------------- isrGPF: # our fault-handler for General Protection Exceptions pushal # preserve registers pushl $0 mov %ds, (%esp) # store DS pushl $0 mov %es, (%esp) # store ES pushl $0 mov %fs, (%esp) # store FS pushl $0 mov %gs, (%esp) # store GS pushl $0 mov %ss, (%esp) # store SS pushl $0 strw (%esp) # store TR mov %esp, %ebp # setup frame base call draw_stack # display registers ljmp $sel_cs, $fin # now transfer to demo finish #------------------------------------------------------------------- hex: .ascii "0123456789ABCDEF" # array of hex digits names: .ascii " TR SS GS FS ES DS" .ascii " EDI ESI EBP ESP EBX EDX ECX EAX" .ascii " err EIP CS EFL" .equ NELTS, (. - names)/4 # number of elements buf: .ascii " nnn=xxxxxxxx " # buffer for output len: .int . - buf # length of output att: .byte 0x70 # color attributes #------------------------------------------------------------------- eax2hex: # converts value in EAX to hexadecimal string at DS:EDI pushal mov $8, %ecx # setup digit counter nxnyb: rol $4, %eax # next nybble into AL mov %al, %bl # copy nybble into BL and $0xF, %ebx # isolate nybble's bits mov hex(%ebx), %dl # lookup ascii-numeral mov %dl, (%edi) # put numeral into buf inc %edi # advance buffer index loop nxnyb # back for next nybble popal ret #------------------------------------------------------------------- draw_stack: pushal # preserve registers mov $sel_es, %ax # address video memory mov %ax, %es # with ES register mov $sel_ds, %ax # address program data mov %ax, %ds # with DS register cld # do forward processing xor %ebx, %ebx # initial element index nxelt: # put element's label into buf mov names(, %ebx, 4), %eax # fetch element's label mov %eax, buf # store label into buf # put element's value into buf mov (%ebp, %ebx, 4), %eax # fetch element's value lea buf+5, %edi # point to value field call eax2hex # convert value to hex # compute element's screen-offset imul $160, %ebx, %eax # offset to screen line mov $3810, %edi # from starting location sub %eax, %edi # destination goes in EDI # write buf to screen memory lea buf, %esi # point DS:ESI to buffer mov len, %ecx # setup buffer's length mov att, %ah # setup color attribute nxpel: lodsb # fetch next character stosw # store char and color loop nxpel # again if more chars inc %ebx # increment element number cmp $NELTS, %ebx # more elements to show? jb nxelt # yes, back for next one popal # restore registers ret # and return to caller #------------------------------------------------------------------- .align 16 # assure stack alignment .space 512 # reserved for stack use tos: # label for top-of-stack #------------------------------------------------------------------- .end # nothing more to assemble