//------------------------------------------------------------------- // xmitvram.s // // This program focuses on overcoming the problem we encounter // on our 'anchor' machines when we attempt to execute a boot- // time demo that enters protected-mode: we no longer are able // to view the screen output or type keystrokes to control our // program's actions because the 'console-redirection' feature // being utilized by the 'telnet' program running on 'pyramid' // has been disabled, being dependent on a real-mode interrupt // handler for the serial-UART device, which communicates with // the 'pyramid' server by means of a null-modem cable. If we // want to restore that communication in protected-mode, we'll // need to replicate the functionality of the UART's real-mode // interrupt handler. This program shows how you can do that. // // to assemble: $ as xmitvram.s -o xmitvram.o // and to link: $ ld xmitvram.o -T ldscript -o xmitvram.b // and install: $ dd if=xmitvram.b of=/dev/sda4 seek=1 // // NOTE: This program begins executing with CS:IP = 1000:0002. // // programmer: ALLAN CRUSE // written on: 30 OCT 2008 //------------------------------------------------------------------- # manifest constants .equ UART_BASE, 0x03F8 # base i/o-port for UART .equ LINE_STAT, 0x03FD # UART's Line Status port .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 configure_null_modem 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 0x08 mov $0x08, %ebx # ID-number for the gate lea theIDT(, %ebx, 8), %di # gate's offset-address movw $isrPIT, 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 # initialize IDT descriptor for gate 0x09 mov $0x09, %ebx # ID-number for the gate lea theIDT(, %ebx, 8), %di # gate's offset-address movw $isrKBD, 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 # initialize IDT descriptor for gate 0x0C mov $0x0C, %ebx # ID-number for the gate lea theIDT(, %ebx, 8), %di # gate's offset-address movw $isrCOM, 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 # 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 # initialize IDT descriptor for gate 0x0F mov $0x0F, %ebx # ID-number for the gate lea theIDT(, %ebx, 8), %di # gate's offset-address movw $isrNOP, 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 #------------------------------------------------------------------- execute_program_demo: mov %esp, %ss:tossave+0 # preserve 32-bit offset mov %ss, %ss:tossave+4 # plus 16-bit selector # setup segment-registers mov $sel_ds, %ax mov %ax, %ds mov $sel_es, %ax mov %ax, %es mov $sel_fs, %ax mov %ax, %fs # save interrupt controller masks in $0x21, %al mov %al, imrsave+0 in $0xA1, %al mov %al, imrsave+1 # enable selected interrupts mov $0xFF, %al out %al, $0xA1 mov $0xEC, %al out %al, $0x21 sti # transmit null-byte (to initiate UART interrupts) mov $UART_BASE, %dx mov $0, %al out %al, %dx update: mov %fs:0x046C, %eax lea buf1, %edi call eax2hex # draw message onto screen mov $0x4F, %ah lea msg1, %esi mov len1, %ecx cld mov $160*12, %edi nxpix: lodsb stosw loop nxpix cmpb $0, done je update fin: cli lss %cs:tossave, %esp # reload our saved SS:ESP lea norm, %si mov nlen, %cx call xmit_ansi_string lea show, %si mov slen, %cx call xmit_ansi_string mov %cs:imrsave+1, %al out %al, $0xA1 mov %cs:imrsave+0, %al out %al, $0x21 ret # return to main function #------------------------------------------------------------------- tossave: .long 0, 0 # stores a 48-bit pointer imrsave: .byte 0, 0 # stores PICs' IMR values #------------------------------------------------------------------- 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 #------------------------------------------------------------------- #=================================================================== #------------------------------------------------------------------- isrNOP: .code16 # trivial interrupt handler for spurious interrupts pushal pushl %es mov $sel_es, %ax mov %ax, %es mov $12*160+150, %bx mov %es:(%bx), %ax inc %ax and $0x7, %ax or $0x3030, %ax movw %ax, %es:(%bx) mov $0x20, %al out %al, $0x20 popl %es popal iretl #------------------------------------------------------------------- isrPIT: .code16 # trivial interrupt handler for timer interrupts push %ax pushl %fs mov $sel_fs, %ax mov %ax, %fs incl %fs:0x046C mov $0x20, %al out %al, $0x20 popl %fs pop %ax iretl #------------------------------------------------------------------- isrKBD: .code16 # trivial interrupt-handler for keyboard interrupts push %ax push %ds in $0x64, %al test $0x01, %al jz ignore in $0x60, %al test $0x80, %al jz ignore mov $sel_ds, %ax mov %ax, %ds incb done ignore: mov $0x20, %al out %al, $0x20 pop %ds pop %ax iretl #------------------------------------------------------------------- #=================================================================== #------------------------------------------------------------------- .macro out8 data, port mov $\port, %dx mov $\data, %al out %al, %dx .endm #------------------------------------------------------------------- .macro in8 port mov $\port, %dx in %dx, %al .endm #------------------------------------------------------------------- configure_null_modem: cli out8 0x00, UART_BASE+1 # interrupt enable out8 0xC7, UART_BASE+2 # FIFO control out8 0x83, UART_BASE+3 # Line control out8 0x01, UART_BASE+0 # Divisor-latch LSB out8 0x00, UART_BASE+1 # Divisor-latch MSB out8 0x03, UART_BASE+3 # Line control out8 0x03, UART_BASE+4 # Modem control in8 UART_BASE+6 # Modem status in8 UART_BASE+5 # Line status in8 UART_BASE+0 # Recv data in8 UART_BASE+2 # Interrupt ID out8 0x0B, UART_BASE+4 # Modem control out8 0x0F, UART_BASE+1 # Interrupt Enable ret #------------------------------------------------------------------- msg1: .ascii " This is a test of our UART interrupt-handler " buf1: .ascii "xxxxxxxx " len1: .int . - msg1 done: .byte 0 #------------------------------------------------------------------- #================== ANSI TERMINAL CONTROL-STRINGS ================ #------------------------------------------------------------------- hide: .ascii "\033[?25l" # ANSI: hide the cursor hlen: .short . - hide # length of the message #------------------------------------------------------------------- show: .ascii "\033[?25h" # ANSI: show the cursor slen: .short . - show # length of the message #------------------------------------------------------------------- move: .ascii "\033[01;01H" # ANSI: move the cursor mlen: .short . - move # length of the message #------------------------------------------------------------------- rend: .ascii "\033[37;40m" # ANSI: set attributes rlen: .short . - rend # length of the message #------------------------------------------------------------------- norm: .ascii "\033[0m" # ANSI: normal attributes nlen: .short . - norm # length of the message #------------------------------------------------------------------- char: .zero 1 # space for ASCII code clen: .short . - char # length of the message #------------------------------------------------------------------- xmit_ansi_string: # # EXPECTS: CS:SI = address of command-string # CX = length of command-string # cld nxtx: mov $LINE_STAT, %dx in %dx, %al test $0x20, %al jz nxtx mov $UART_BASE, %dx outsb %cs:(%si), %dx loop nxtx ret #------------------------------------------------------------------- row: .short 0 col: .short 0 prev: .byte 0 color: .byte 0, 4, 2, 6, 1, 5, 3, 7 #------------------------------------------------------------------- isrCOM: .code16 # interrupt-handler for the Serial UART interrupts pushal pushl %ds pushl %es # check UART's Interrupt-Identification register mov $0x03FA, %dx # Interrupt-ID i/o-port in %dx, %al # input Interrupt-ID and $0x0F, %al # isolate lowest nybble test $0x01, %al # check: false alarm? jnz doEOI # handle the reported interrupt-cause cmp $0x06, %al # Rx-Error je doRERR cmp $0x04, %al # RDR? je doRECV cmp $0x0C, %al # Timeout? je doRECV cmp $0x02, %al # THRE? je doTHRE cmp $0x00, %al # Modem-Status changed? je doMDEL jmp doEOI doRECV: mov $UART_BASE+0, %dx # Rx-Data in %dx, %al # write byte to keyboard controller's Output Buffer push %ax spin1: in $0x64, %al test $0x02, %al # input buffer full? jnz spin1 # yes, wait till empty mov $0xD2, %al # write to KB output buffer out %al, $0x64 spin2: in $0x64, %al test $0x02, %al # input buffer full? jnz spin2 # yes, wait till empty pop %ax or $0x80, %ax out %al, $0x60 jmp doEOI doMDEL: mov $UART_BASE+6, %dx # Modem-Status in %dx, %al jmp doEOI doRERR: mov $UART_BASE+5, %dx # Line-Status in %dx, %al jmp doEOI doTHRE: # check for start-of-row cmpb $0, %ss:col jne inrow # prepare to move the cursor mov %ss:row, %ax inc %ax mov $10, %bl div %bl or $0x3030, %ax mov %ax, %ss:move+2 # hide the cursor lea hide, %si mov hlen, %cx call xmit_ansi_string # move the cursor lea move, %si mov mlen, %cx call xmit_ansi_string inrow: # fetch current pel-value mov $sel_es, %ax mov %ax, %es imul $80, row, %bx add col, %bx shl $1, %bx mov %es:(%bx), %ax mov %al, %ss:char # is pel-attribute new? cmp %ah, %ss:prev je attok mov %ah, %ss:prev mov %ah, %bl and $0x7, %bx mov %cs:color(%bx), %dl or $'0', %dl mov %dl, %ss:rend+3 # foreground-color mov %ah, %bl rol $4, %bl and $0x7, %bx mov %cs:color(%bx), %dl or $'0', %dl mov %dl, %ss:rend+6 # background-color lea rend, %si mov rlen, %cx call xmit_ansi_string attok: lea char, %si mov clen, %cx call xmit_ansi_string # advance the screen-cell location incw %ss:col cmpw $80, %ss:col jb cok movw $0, %ss:col incw %ss:row lea show, %si mov slen, %cx call xmit_ansi_string cok: cmpw $24, %ss:row jb rok movw $0, %ss:row rok: doEOI: mov $0x20, %al out %al, $0x20 popl %es popl %ds popal iretl #------------------------------------------------------------------- .end