//----------------------------------------------------------------- // 'haltdemo.s' (inspired by our 'tickdemo.s' example) // // This demo was inspired by classmember Garrik Sturges, who // wanted to enhance the 'tickdemo.s' program to display the // elapsed-time in seconds, based on counting the timer-tick // interrupts (as we had suggested in an in-class exercise). // // Garrik's question was: Where should I place the code that // would calculate the 'elapsed-seconds' from 'timer-ticks'? // On the one hand, it could go inside the interrupt-handler // for timer-interrupts, but on the other hand, it might not // be wise to consume CPU time with computations that aren't // part of servicing the timer's interrupt. Instead, it may // be better if the main program takes care of calculations. // Which alternative is best? And how does a person decide? // // We think these questions are worthy of exploration, since // they illustrate the role played in systems programming by // a clear understanding of how a CPU performs its function. // Besides, constructing this compelling demonstration gives // us an opportunity to introduce some new programming ideas // which soon will aid us in understanding 'protected-mode'. // // // EXERCISE: Compare carefully this code and our 'tickdemo'. // // // assemble with: $ as haltdemo.s -o haltdemo.o // and link with: $ ld haltdemo.o -T ldscript -o haltdemo.b // install using: $ dd if=haltdemo.b of=/dev/sda seek=1 // // NOTE: This program begins executing with CS:IP = 1000:0002 // // programmer: ALLAN CRUSE // written on: 11 SEP 2008 //----------------------------------------------------------------- .code16 # for Pentium 'real-mode' # manifest constants .equ seg_base, 0x0000 # segment-address for IVT .equ seg_prog, 0x1000 # segment-address of code .equ seg_vram, 0xB800 # segment-address of VRAM .section .text #------------------------------------------------------------------ .short 0xABCD # our program 'signature' start: jmp main # jump over program data #------------------------------------------------------------------ sav: .word 0, 0 # save the original vector #------------------------------------------------------------------ ticks: .long 0 # keeps count of 'ticks' loops: .long 0 # keeps count of 'loops' secs: .long 0 # keeps count of seconds #------------------------------------------------------------------ #------------------------------------------------------------------ isr_tick: # Interrupt Service Routine for timer-tick interrupts push %ax # preverve AX contents incl %cs:ticks # increment tick-count mov $0x20, %al # send 'EOI' command to out %al, $0x20 # Interrupt Controller pop %ax # restore saved register iret # return-from-interrupt #------------------------------------------------------------------ msg1: .ascii "Tick-interrupts: " # title for screen-display buf1: .ascii " " # buffer for counter value len1: .word . - msg1 # length of output-message hue1: .byte 0x0E # colors: yellow-on-black row1: .word 10 # row-number for display col1: .word 25 # col-number for display #------------------------------------------------------------------ msg2: .ascii "Loop-iterations: " # title for screen-display buf2: .ascii " " # buffer for counter value len2: .word . - msg2 # length of output-message hue2: .byte 0x0E # colors: yellow-on-black row2: .word 12 # row-number for display col2: .word 25 # col-number for display #------------------------------------------------------------------ msg3: .ascii "Elapsed seconds: " # title for screen-display buf3: .ascii " " # buffer for counter value len3: .word . - msg3 # length of output-message hue3: .byte 0x0E # colors: yellow-on-black row3: .word 14 # row-number for display col3: .word 25 # col-number for display #------------------------------------------------------------------ ten: .long 10 # radix for decimal-system den: .long 182 # conversion's denominator #------------------------------------------------------------------ retptr: .word 0, 0 # save original stacktop #------------------------------------------------------------------ main: # preserve the stacktop address (so we can return later) mov %sp, %cs:retptr+0 # save register SP mov %ss, %cs:retptr+2 # save register SS # now switch to our own local stack-segment mov $seg_prog, %ax # address our data area mov %ax, %ss # using register SS lea tos, %sp # and set stack-pointer # setup segment-registers DS, ES, FS so that we can access # three memory-regions: our data, the IVT, and the display mov $seg_prog, %ax # address our variables mov %ax, %ds # using register DS mov $seg_vram, %ax # address screen-memory mov %ax, %es # using register ES mov $seg_base, %ax # address vector-table mov %ax, %fs # using register FS # save the default interrupt-vector for the timer (INT-0x08) mov %fs:0x08*4+0, %ax # copy vector's lo-word mov %ax, sav+0 # to our save-location mov %fs:0x08*4+2, %ax # copy vector's hi-word mov %ax, sav+2 # to our save-location # install a pointer to our own handler in the IVT (INT-0x08) cli # <---- ENTER 'CRITICAL SECTION' movw $isr_tick, %fs:0x08*4+0 # write vector's lo-word movw $seg_prog, %fs:0x08*4+2 # write vector's hi-word sti # <---- LEAVE 'CRITICAL SECTION' reinit: # reset all three of our three counter-variables to zero movl $0, ticks # variable #1 is cleared movl $0, loops # variable #2 is cleared movl $0, secs # variable #3 is cleared # main program-loop: it does calculations, conversions, # message-displays, and it checks for keyboard-input again: # Here we include a 'nop' instruction (no operation), which # later will be overwritten with a 'hlt' instruction (halt) # if a user hits the <'h'>-key, or else restored to a 'nop' # instruction if a user then hits the <'n'>-key. instr: nop # no operation (for now) # increment the loop-iteration counter incl loops # count this loop-pass # compute 'seconds' from 'ticks' mov ticks, %eax # setup the multiplicand mull ten # times our multiplier divl den # divided by denominator mov %eax, secs # and store the quotient # convert our three counters to strings of decimal numerals mov ticks, %eax # load counter-value in EAX lea buf1, %di # point DS:DI to its buffer mov $10, %cx # specify the buffer's size call erase # erase earlier characters call eax2dec # convert number to string call lj2rj # string is right-justified mov loops, %eax # load counter-value in EAX lea buf2, %di # point DS:DI to its buffer mov $10, %cx # specify the buffer's size call erase # erase earlier characters call eax2dec # convert number to string call lj2rj # string is right-justified mov secs, %eax # load counter-value in EAX lea buf3, %di # point DS:DI to its buffer mov $10, %cx # specify the buffer's size call erase # erase earlier characters call eax2dec # convert number to string call lj2rj # string is right-justified # write our three messages to the video screen's memory cld # direction is 'forward' mov $80, %ax # number of chars-per-row mulw row1 # times the row-number addw col1, %ax # plus column-number imul $2, %ax, %di # times bytes-per-pel lea msg1, %si # point DS:SI to string mov len1, %cx # string-length into CX mov hue1, %ah # with color-code in AH nxpel1: lodsb # fetch next character stosw # store char and color loop nxpel1 # write rest of string mov $80, %ax # number of chars-per-row mulw row2 # times the row-number addw col2, %ax # plus column-number imul $2, %ax, %di # times bytes-per-pel lea msg2, %si # point DS:SI to string mov len2, %cx # string-length into CX mov hue2, %ah # with color-code in AH nxpel2: lodsb # fetch next character stosw # store char and color loop nxpel2 # write rest of string mov $80, %ax # number of chars-per-row mulw row3 # times the row-number addw col3, %ax # plus column-number imul $2, %ax, %di # times bytes-per-pel lea msg3, %si # point DS:SI to string mov len3, %cx # string-length into CX mov hue3, %ah # with color-code in AH nxpel3: lodsb # fetch next character stosw # store char and color loop nxpel3 # write rest of string # check whether any key has been pressed mov $1, %ah # peek into keyboard queue int $0x16 # request BIOS service jz again # jump if queue is empty # if so, remove that item from the keyboard's input-queue xor %ah, %ah # return keyboard entry int $0x16 # request BIOS service # maybe change the instruction-byte cmp $'h', %al # was 'h' pressed? je wasH # yes, install 'hlt' cmp $'n', %al # was 'n' pressed? je wasN # yes, install 'nop' # else check to see if it was the -key cmp $0x011B, %ax # did user hit ? je finis # yes, demo is finished jmp again # else demo continues wasH: movb $0xF4, instr # change instr to 'hlt' jmp reinit # and restart the demo wasN: movb $0x90, instr # change instr to 'nop' jmp reinit # and restart the demo finis: # restore original interrupt-vector to the IVT (INT-0x08) # [We can do this safely, in a single 'atomic' operation] mov sav+0, %eax # fetch the saved vector mov %eax, %fs:0x08*4 # put it back in the IVT # recover the original stack-address and exit to loader lss %cs:retptr, %sp # back to loader's stack lret # for return to loader #------------------------------------------------------------------ erase: # fills the buffer found at DS:DI with CX blank-spaces push %cx push %di nxfill: movb $' ', (%di) # store an ascii blank inc %di # advance buffer index loop nxfill # exhaust buffer-space pop %di pop %cx ret #------------------------------------------------------------------ lj2rj: # rearranges the left-justified string found at DS:DI as # a right-justified string within a buffer of length CX push %ax # preserve registers that push %bx # we need to work with nxcmp: # see if our string has become right-justified yet mov %cx, %bx mov -1(%bx, %di), %al # get rightmost character cmp $' ', %al # it's now a blank space? jne nomov # no, already justified! # if not, we need to move buffer-characters to the right push %cx # preserve registers that push %di # we need to work with nxxch: xchg %al, (%di) # swap AL with memory-byte inc %di # advance memory pointer loop nxxch # everything gets moved pop %di # restore saved contents pop %cx # to 'work' registers jmp nxcmp # see if aim is achieved nomov: # OK, rightmost buffer-character is no longer a blank pop %bx # restore saved contents pop %ax # to 'work' registers ret # return to the caller #------------------------------------------------------------------ #------------------------------------------------------------------ eax2dec: # converts value in EAX to decimal digit-string at DS:DI pushal xor %cx, %cx # initialize digit-counter nxdiv: xor %edx, %edx # extend EAX to quadword divl ten # divide by decimal radix push %dx # save remainder on stack inc %cx # increment digit-counter or %eax, %eax # was the quotient zero? jnz nxdiv # no, do another division nxdgt: pop %dx # recover prior remainder add $'0', %dl # convert number to ascii mov %dl, (%di) # store numeral in buffer inc %di # advance buffer's index loop nxdgt # again for each remainder popal ret #------------------------------------------------------------------ .align 16 # insure stack-alignment .space 512 # reserved for stack use tos: # label for top-of-stack #------------------------------------------------------------------ .end # nothing more to assemble