//----------------------------------------------------------------- // pmtimer.s // // This program handles timer-interrupts in protected-mode. // It continuously displays the tick-count for ten seconds. // // to assemble: $ as pmtimer.s -o pmtimer.o // and to link: $ ld pmtimer.o -T ldscript -o pmtimer.b // and install: $ dd if=pmtimer.b of=/dev/sda4 seek=1 // // NOTE: This code begins executing with CS:IP = 1000:0002. // // programmer: ALLAN CRUSE // written on: 20 SEP 2008 //----------------------------------------------------------------- .equ seg_prog, 0x1000 # segment-address of arena .section .text #----------------------------------------------------------------- .word 0xABCD # our program 'signature' #----------------------------------------------------------------- main: .code16 # starts from 'real-mode' mov %sp, %cs:exit_pointer+0 # preserve the pointer to mov %ss, %cs:exit_pointer+2 # our launcher's stacktop mov %cs, %ax # address this segment mov %ax, %ss # with SS register lea tos0, %sp # and set new stacktop call build_interrupt_gate call enter_protected_mode call exec_timer_tick_demo call leave_protected_mode lss %cs:exit_pointer, %sp # recover our exit-address lret # exit to program launcher #----------------------------------------------------------------- exit_pointer: .word 0, 0 # to store loader's SS:SP #----------------------------------------------------------------- theGDT: .word 0x0000, 0x0000, 0x0000, 0x0000 # null descriptor .equ sel_es, (.-theGDT) # vram-segment's selector .word 0x7FFF, 0x8000, 0x920B, 0x0000 # vram descriptor .equ sel_cs, (.-theGDT) # code-segment's selector .word 0xFFFF, 0x0000, 0x9A01, 0x0000 # code descriptor .equ sel_ss, (.-theGDT) # data-segment's selector .word 0xFFFF, 0x0000, 0x9201, 0x0000 # data descriptor .equ sel_bs, (.-theGDT) # bios-segment's selector .word 0x0100, 0x0400, 0x9200, 0x0000 # bios descriptor .equ limGDT, (.-theGDT)-1 # our GDT's segment-limit #----------------------------------------------------------------- #----------------------------------------------------------------- # # Here we are reserving an 'unitialized' storage-area for an IDT. # In this demo, only ONE of the 256 entries actually is used (and # here it will get initialized at run-time), whereas an actual OS # would need to use many more (perhaps all) of these IDT entries. # theIDT: .space 256*8 # enough for 256 gate-descriptors .equ limIDT, (.-theIDT)-1 # our IDT's segment-limit #----------------------------------------------------------------- build_interrupt_gate: # # Here, while the CPU is in 'real-mode' (where code-segments are # 'writable'), we initialize our 'interrupt-gate' descriptor for # the programmable timer's interrupt (i.e., interrupt number 8). # # setup register DI with our gate-descriptor's offset mov $0x08, %eax # ID-number for the gate lea theIDT(,%eax,8), %di # offset-address of gate # initialize the four words of that descriptor (type 6) movw $isrPIT, %cs:0(%di) # loword of ISR's offset movw $sel_cs, %cs:2(%di) # code-selector for ISR movw $0x8600, %cs:4(%di) # gate-type = 6 (80268) movw $0x0000, %cs:6(%di) # hiword of ISR's offset ret #----------------------------------------------------------------- # Here are the images that GDTR and IDTR need for protected-mode regGDT: .word limGDT, theGDT, 0x0001 # image for register GDTR regIDT: .word limIDT, theIDT, 0x0001 # image for register IDTR #----------------------------------------------------------------- # Here is the image that IDTR will need upon return to real-mode regIVT: .word 0x03FF, 0x0000, 0x0000 # image for register IDTR #----------------------------------------------------------------- # Here are the images for the 8259 Interrupt Controllers' masks pic_mask_bits: .byte 0xFE, 0xFF # 8259 Master and Slave #----------------------------------------------------------------- enter_protected_mode: cli # no device interrupts mov %cr0, %eax # get machine status bts $0, %eax # set PE-bit to 1 mov %eax, %cr0 # enable protection lgdt %cs:regGDT # establish the GDT lidt %cs:regIDT # and establish IDT mov $sel_ss, %ax # address program area mov %ax, %ss # with SS register ljmp $sel_cs, $pm # and with CS register pm: ret # back to main routine #----------------------------------------------------------------- #================================================================= #----------------------------------------------------------------- # EQUATES for timing-constants and for ROM-BIOS address-offsets .equ HOURS24, 0x180000 # number of ticks-per-day .equ N_TICKS, 0x006C # offset for tick-counter .equ TM_OVFL, 0x0070 # offset of rollover-flag .equ MOTOR_COUNT, 0x0040 # offset of motor-counter .equ MOTOR_STAT, 0x003F # offset for motor-status #----------------------------------------------------------------- isrPIT: # # Here is our code for handing timer-interrupts while the CPU is # executing in 'protected-mode'; it follows closely the original # 'real-mode' Interrupt-Service Routine used in the IBM-PC BIOS, # # an ISR must preserve any registers it needs to modify push %ax # save working registers push %dx push %ds # setup register DS to address the ROM-BIOS DATA-AREA mov $sel_bs, %ax # address rom-bios data mov %ax, %ds # using DS register # increment the 32-bit counter for timer-tick interrupts incl N_TICKS # increment tick-count cmpl $HOURS24, N_TICKS # past midnight? jl isok1 # no, don't rollover yet movl $0, N_TICKS # else reset count to 0 movb $1, TM_OVFL # and set rollover flag isok1: # turn off the motors on any floppy diskette drives decb MOTOR_COUNT # decrement motor-count jnz isok2 # nonzero? motor stays on mov $0x03F2, %dx # else turn off motors mov $0x0C, %al # command: turn off motors out %al, %dx # sent to disk-controller andb $0xF0, MOTOR_STAT # mark the motors as 'off' isok2: # issue a 'software' interrupt (as a system 'event-hook') # int $0x1C # the 'User Timer-Tick' hook # TODO: Add a suitable interrupt-gate that support this # send an 'End-of-Interrupt' command to the Master PIC mov $0x20, %al # non-specific EOI command out %al, $0x20 # sent to the Master-PIC # restore the values to the registers we've modified here pop %ds # restore saved registers pop %dx pop %ax # resume execution of whichever procedure got interrupted iret # resume interrupted task #----------------------------------------------------------------- #================================================================= #----------------------------------------------------------------- exec_timer_tick_demo: # modify PIC masks to block all but the timer's interrupt call reprogram_8259_masks # mask all but timer-tick sti # allow device interrupts # initialize the DS and ES segment-registers mov $sel_bs, %ax # address rom-bios data mov %ax, %ds # using DS register mov $sel_es, %ax # address video memory mov %ax, %es # using ES register # initialize register EBP to contain our 'timeout' value mov N_TICKS, %ebp # get current tick-count add $182, %ebp # increment by 10-secs again: call write_ticks_onscreen # display current 'ticks' cmpl %ebp, N_TICKS # ten-seconds elapsed? jle again # no, reenter this loop # restore PIC masks (to allow other devices' interrupts) cli # discontinue interrupts call reprogram_8259_masks # restore device-masks ret # back to main routine #----------------------------------------------------------------- write_ticks_onscreen: # # Writes 'tick-counter' as a string of decimal-numerals onscreen. # # EXPECTS: DS = selector for the ROM-BIOS DATA-AREA # ES = selector for the Video-Memory segment # mov N_TICKS, %eax # load current tick-count mov $10, %ebx # decimal-system's radix xor %cx, %cx # initialize digit-count nxdiv: xor %edx, %edx # extend EAX to quadword div %ebx # divide by number-base push %dx # push remainder on stack inc %cx # and count the remainder or %eax, %eax # was the quotient zero? jnz nxdiv # no, do another divide mov $156, %di # final screen-position sub %cx, %di # back up for digits sub %cx, %di # back up for colors mov $0x3020, %ax # initial blank space stosw # written to screen nxdgt: pop %ax # recover saved remainder or $0x3030, %ax # convert to numeral/color stosw # and write it to screen loop nxdgt # process all the digits mov $0x3020, %ax # final blank space stosw # written to screen ret #----------------------------------------------------------------- #----------------------------------------------------------------- reprogram_8259_masks: # # This helper-function gets called twice, to 'swap' the values in # the two Programmable Interrupt Controllers' mask registers with # some values which currently are being held in memory-locations. # in $0x21, %al # read Master-PIC mask xchg %al, %ah in $0xA1, %al # read Slave-PIC mask xchg %al, %ah xchg %ax, %ss:pic_mask_bits # swap old w/new masks out %al, $0x21 # write Master-PIC mask xchg %al, %ah out %al, $0xA1 # write Slave-PIC mask ret #----------------------------------------------------------------- leave_protected_mode: mov %ss, %ax # address 64KB r/w segment mov %ax, %ds # using DS register mov %ax, %es # and ES register mov %cr0, %eax # get machine status btr $0, %eax # reset PE-bit to 0 mov %eax, %cr0 # disable protection ljmp $seg_prog, $rm # reload register CS rm: mov %cs, %ax # address stack area mov %ax, %ss # with SS register lidt %cs:regIVT # real-mode vector-table sti # ok, interrupts allowed ret # back to main routine #----------------------------------------------------------------- .align 16 # assure stack alignment .space 512 # space for stack to use tos0: # label for top-of-stack #----------------------------------------------------------------- .end # no more to be assembled