//----------------------------------------------------------------- // usleep.s // // This code tests a procedure that is intended to perform a // timed delay for the number of microseconds which it finds // in the EAX register. The time is measured with reference // to the CPU's clock-speed, as reported by the value in its // TimeStamp Counter register, where the system's 8254 Timer // is used to determine the number of cpu cycles-per-second. // // to assemble: $ as usleep.s -o usleep.o // and to link: $ ld usleep.o -T ldscript -o usleep.b // and install: $ dd if=usleep.b of=/dev/sda4 seek=1 // // NOTE: This code begins execution with CS:IP = 1000:0002. // // programmer: ALLAN CRUSE // written on: 18 JUN 2010 // revised on: 13 JUL 2010 -- to cleanup comments and output //----------------------------------------------------------------- .section .text #------------------------------------------------------------------ .short 0xABCD # application's signature #------------------------------------------------------------------ main: .code16 # for real-mode execution mov %sp, %cs:ldrtos+0 # save loader's SP-value mov %ss, %cs:ldrtos+2 # also loader's SS-value mov %cs, %ax # address program arena mov %ax, %ds # using DS register mov %ax, %es # and ES register mov %ax, %ss # and SS register lea tos, %sp # establish local stack call erase_the_screen call detect_cpu_speed call report_cpu_speed call await_user_input call test_usleep_proc call await_user_input lss %cs:ldrtos, %sp # recover loader's SS:SP lret # give control to loader #------------------------------------------------------------------ ldrtos: .short 0, 0 # hold loader's stacktop #------------------------------------------------------------------ progid: .ascii " USLEEP -- 7/13/2010 " # to recognize binary file #------------------------------------------------------------------ #================================================================== #------------------------------------------------------------------ await_user_input: mov $0, %ah # get_keyboard_input int $0x16 # invoke BIOS servoice ret #------------------------------------------------------------------ #------------------------------------------------------------------ erase_the_screen: mov $0x0003, %ax # set standard 80x25 text int $0x10 # invoke BIOS service ret #------------------------------------------------------------------ show_text_string: # # EXPECTS: ES:BP = address of text string # CX = length of text string # BL = color attribute for text pushal push %ecx push %ebx mov $0x0F, %ah # get_display_page int $0x10 # invoke BIOS service mov $0x03, %ah # get_cursor_location int $0x10 # invoke BIOS service pop %ecx mov %cl, %bl # BL = text-attribute pop %ecx # CX = length of text mov $0x1301, %ax # write_string int $0x10 # invoke BIOS service popal ret #------------------------------------------------------------------ ten: .int 10 # radix of decimal system #------------------------------------------------------------------ cvtud: # convert EAX to a string of decimal numerals at DS:EDI pushal xor %ecx, %ecx # initialize digit-count nxdiv: xor %edx, %edx # extend EAX for division divl ten # divide EDX:EAX by radix push %edx # save remainder on stack inc %ecx # increment digit-count or %eax, %eax # test: quotient is zero? jnz nxdiv # no, do another division nxdgt: pop %edx # else recover remainder add $'0', %dl # convert binary-to-ascii mov %dl, (%edi) # put numeral into buffer inc %edi # advance buffer-pointer loop nxdgt # again for other digits popal ret #------------------------------------------------------------------ hex: .ascii "0123456789ABCDEF" # table of hex numerals #------------------------------------------------------------------ eax2hex: # convert EAX to string of hexadecimal numerals at DS:EDI pushal mov $8, %ecx # number of hex digits nxnyb: rol $4, %eax # next nybble into AL mov %al, %bl # copy nybble into BL and $0xF, %ebx # extend nybble to EBX mov hex(%ebx), %dl # convert binary to ascii mov %dl, (%edi) # put numeral into buffer inc %edi # advance buffer-pointer loop nxnyb # again for other digits popal ret #------------------------------------------------------------------ tmp: .ascii "xxxxxxxx" # temporary output buffer #------------------------------------------------------------------ ax2hex: # convert AX to a string of hexadecimal numerals at DS:EDI push %eax push %edi # preserve buffer-offset lea tmp, %edi # use a temporary buffer call eax2hex # convert EAX to string pop %edi # recover buffer-offset mov tmp+4, %eax # fetch lowest 4 numerals mov %eax, (%edi) # copy numerals to buffer pop %eax ret #------------------------------------------------------------------ al2hex: # convert AL to a string of hexadecimal numerals at DS:EDI push %eax push %edi # preserve buffer-offset lea tmp, %edi # use a temporary buffer call eax2hex # convert EAX to struing pop %edi # recover buffer-offset mov tmp+6, %ax # fetch lowest 2 numerals mov %ax, (%edi) # copy numerals to buffer pop %eax ret #------------------------------------------------------------------ #================================================================== #------------------------------------------------------------------ tsc0: .quad 0 # first TimeStamp Counter tsc1: .quad 0 # final TimeStamp Counter diff: .quad 0 # difference in counters cpuhz: .int 0 # processor speed (Hertz) #------------------------------------------------------------------ detect_cpu_speed: # # This procedure counts the number of cpu-cycles which occur while # the 8254 Timer/Counter is counting down at a known rate from its # maximum count-value of 65536, then converts that rate to Hertz. # pushal # disable channel 2 counter in $0x61, %al and $0x0C, %al out %al, $0x61 # program channel 2 for one-shot mov $0xB0, %al out %al, $0x43 xor %al, %al out %al, $0x42 out %al, $0x42 # start channel 2 countdown in $0x61, %al or $0x01, %al out %al, $0x61 # save current timestamp-counter rdtsc mov %eax, tsc0+0 mov %edx, tsc0+4 # await channel 2 countdown completion spin0: in $0x61, %al test $0x20, %al jz spin0 # save current timestamp-counter rdtsc mov %eax, tsc1+0 mov %edx, tsc1+4 # disable channel 2 counter in $0x61, %al and $0x0C, %al out %al, $0x61 # compute number of elapsed CPU cycles in 65536/1193182 seconds mov tsc1+0, %eax mov tsc1+4, %edx sub tsc0+0, %eax sbb tsc0+4, %edx mov %eax, diff+0 mov %edx, diff+4 # compute CPU cycles-per-second mov $1193182, %eax # counter input-frequency mull diff # times elapsed cpu-clcles mov $65536, %ecx # divided by latch-value div %ecx mov %eax, cpuhz # gives CPU cycles-per-second popal ret #------------------------------------------------------------------ report_cpu_speed: pushal mov cpuhz, %eax lea buf0, %edi call cvtud lea msg0, %bp mov len0, %cx mov att0, %bl call show_text_string popal ret #------------------------------------------------------------------ msg0: .ascii "\r\n\n\n Number of CPU-cycles-per-second = " buf0: .ascii " \r\n\n\n" .ascii " Press key to begin a test of our 'usleep'" .ascii " procedure's accuracy ... " len0: .short . - msg0 att0: .byte 0x07 #------------------------------------------------------------------ #================================================================== #------------------------------------------------------------------ cycles: .int 0 # cycles in desired delay expire: .quad 0 # timestamp at expiration #------------------------------------------------------------------ usleep: # this procedure returns after a delay of EAX microseconds pushal # compute the number of clock-cycles in the desired delay # cycles = cycles-per-sec * secs-per-us * EAX us # or, equivalently: # cycles = EAX us * cycles-per-sec / us-per-sec mov cpuhz, %ecx # load cycles-per-second mul %ecx # times EAX microseconds mov $1000000, %ecx # microseconds-per-second div %ecx # quotient = delay-cycles mov %eax, cycles # save this cycle-count # compute the TimeStamp value when the delay will expire rdtsc # current timestamp value add cycles, %eax # add cpu-cycles to delay adc $0, %edx # as 64-bit sum (EDX,EAX) mov %eax, expire+0 # store the quadword sum mov %edx, expire+4 # at location 'expire' # spin here until current TimeStamp exceeds 'expire' value spin: rdtsc # reread current TimeStamp sub expire+0, %eax # subtract quadword expire sbb expire+4, %edx # from current TimeStamp jb spin # again if not yet expired popal ret #------------------------------------------------------------------ #================================================================== #------------------------------------------------------------------ #------------------------------------------------------------------ test_usleep_proc: # announce what test is about to be performed lea msg1, %bp mov len1, %cx mov att1, %bl call show_text_string # wait for the next timer-tick interrupt xor %ax, %ax mov %ax, %fs sti mov %fs:0x46C, %eax inc %eax spin1: cmp %fs:0x46C, %eax je spin1 # save the timer-tick count as 'tick0' mov %fs:0x46C, %eax mov %eax, ticks0 # perform a 20-microsecond delay two million times mov $2000000, %ecx nxtry: mov $20, %eax call usleep call visible_feedback dec %ecx jnz nxtry # save the timer-tick count as 'tick1' mov %fs:0x46C, %eax mov %eax, ticks1 # format the number of elapsed timer-ticks mov ticks1, %eax sub ticks0, %eax lea buf2, %edi call eax2hex # compute the elapsed time in seconds mov $10, %ecx mul %ecx mov $182, %ecx div %ecx lea sec$, %edi call cvtud # display the report of elapsed time lea msg2, %bp mov len2, %cx mov att2, %bl call show_text_string ret #------------------------------------------------------------------ #------------------------------------------------------------------ msg1: .ascii "\r\n\n" .ascii " Now we will perform two million repetitions " .ascii "of a 20-microsecond delay... \r\n ..." .ascii " which ought to take about 40 seconds, because: " .ascii "\r\n\n " .ascii " 20 x 2-million = 40-million microseconds \r\n\n" .ascii " OK, here we go... please be patient! \r\n\n " len1: .short . - msg1 att1: .byte 0x07 #------------------------------------------------------------------ ticks0: .int 0 # first timer-tick count ticks1: .int 0 # final timer-tick count #------------------------------------------------------------------ msg2: .ascii "\r\n\n Delay was 0x" buf2: .ascii "xxxxxxxx timer-ticks, " .ascii "times 1/18.2 gives the number of seconds: " sec$: .ascii " " .ascii "\r\n\n" len2: .short . - msg2 att2: .byte 0x07 #------------------------------------------------------------------ visible_feedback: # # This procedure provides some perceptible visual feedback during # an otherwise boring delay while a timing-test is taking place. # test $0xFFFF, %ecx # multiple of 65536? jnz feedx # no, no new feedback pushal xor %bx, %bx # display-page zero mov $0x0E, %ah # write TTY-character mov $'*', %al # show an asterick int $0x10 # invoke BIOS service popal feedx: ret #------------------------------------------------------------------ #================================================================== #------------------------------------------------------------------ .align 16 # assures word alignment .space 512 # space for stack to use tos: # label for top-of-stack #------------------------------------------------------------------ .end