;//--------------------------------------------------------------- ;// rtcdemo.s ;// ;// This program directly accesses the Real-Time Clock chip ;// in order to obtain and display the current time-of-day. ;// The RTC is programmed to generate an interrupt whenever ;// its current time-of-day changes (i.e., once per second) ;// and the RTC interrupt service routine enqueues a record ;// of the updated time; the main routine continously loops ;// until it finds a new record in the queue, then dequeues ;// it, and draws its time information on the video screen. ;// ;// assemble with: $ as86 rtcdemo.s -b rtcdemo.b ;// install using: $ dd if=rtcdemo.b of=/dev/fd0 seek=1 ;// ;// NOTE: This code begins executing with CS:IP = 1000:0002. ;// ;// programmer: ALLAN CRUSE ;// written on: 04 APR 2004 ;// correction: 08 APR 2004 -- correctly masked IRQ9-IRQ15 ;//--------------------------------------------------------------- .SECT .TEXT ;----------------------------------------------------------------- .WORD 0xABCD ; programming signature ;----------------------------------------------------------------- main: mov ax, cs ; address this segment mov ds, ax ; with DS register pop dword return_address ; store return-address mov ss, ax ; adjust SS register lea esp, tos0 ; and set new stacktop call enter_protected_mode call execute_our_rtc_demo call leave_protected_mode push dword return_address ; recover our exit-address retf ; exit to program launcher ;----------------------------------------------------------------- return_address: .LONG 0 ; to store exit-address ;----------------------------------------------------------------- ; EQUATES realCS EQU 0x1000 ; segment-address of code limGDT EQU 0x001F ; allocates 4 descriptors sel_es EQU 0x0008 ; vram-segment selector sel_cs EQU 0x0010 ; code-segment selector sel_ss EQU 0x0018 ; data-segment selector ;----------------------------------------------------------------- .ALIGN 8 theGDT: .WORD 0x0000, 0x0000, 0x0000, 0x0000 ; null descriptor .WORD 0x7FFF, 0x8000, 0x920B, 0x0000 ; vram descriptor .WORD 0xFFFF, 0x0000, 0x9A01, 0x0000 ; code descriptor .WORD 0xFFFF, 0x0000, 0x9201, 0x0000 ; data descriptor ;----------------------------------------------------------------- theIDT: .SPACE 2048 ; for 256 gate-descriptors ;----------------------------------------------------------------- ;----------------------------------------------------------------- enter_protected_mode: cli ; no device interrupts mov eax, cr0 ; get machine status bts eax, #0 ; set PE-bit to 1 mov cr0, eax ; enable protection push #0x0001 ; push base-address hiword push #theGDT ; push base-address loword push #limGDT ; push GDT's segment-limit lgdt [esp] ; load GDTR register-image add sp, #6 ; discard three stackwords push #0x0001 ; push base-address hiword push #theIDT ; push base-address loword push #0x07FF ; push IDT's segment-limit lidt [esp] ; load IDTR register-image add sp, #6 ; discard three stackwords jmpf #pm, #sel_cs ; reload register CS pm: mov ax, #sel_ss mov ss, ax ; reload register SS mov ds, ax ; reload register DS xor ax, ax ; use 'null' selector mov es, ax ; to purge invalid ES mov fs, ax ; to purge invalid FS mov gs, ax ; to purge invalid GS ret ; back to main routine ;----------------------------------------------------------------- leave_protected_mode: mov ax, ss ; address 64KB r/w segment mov ds, ax ; using DS register mov es, ax ; and ES register mov eax, cr0 ; get machine status btr eax, #0 ; reset PE-bit to 0 mov cr0, eax ; disable protection push #0x0000 ; push base-address hiword push #0x0000 ; push base-address loword push #0x03FF ; push IDT's segment-limit lidt [esp] ; load IDTR register-image add sp, #6 ; discard three stackwords jmpf #rm, #realCS ; reload register CS rm: mov ax, cs mov ss, ax ; reload register SS mov ds, ax ; reload register DS sti ; interrupts allowed ret ; back to main routine ;----------------------------------------------------------------- ;----------------------------------------------------------------- isrRTC: ; ; This routine handles the 'update' events triggered by the RTC. ; pushad ; preserve registers push ds ; verify that this was an RTC update interrupt mov al, #0x8C ; RTC register C is out #0x70, al ; selected for access in al, #0x71 ; read the RTC status test al, #0x10 ; was an RTC update? jz resume ; no, then disregard ; read the RTC's current-time registers mov al, #0x84 ; 'hours' register is out #0x70, al ; selected for access in al, #0x71 ; read rtc register shl eax, #8 ; and shift out of AL mov al, #0x82 ; 'mins' register is out #0x70, al ; selected for access in al, #0x71 ; read rtc register shl eax, #8 ; and shift out of AL mov al, #0x80 ; 'secs' register is out #0x70, al ; selected for access in al, #0x71 ; read rtc register ; insert current-time as a new record in event-queue mov cx, #sel_ss ; address this segment mov ds, cx ; with DS register mov ebx, [rttail] ; point to record entry mov [ebx], eax ; store the record info call advEBX ; advance record pointer cmp ebx, [rthead] ; check: queue was full? je resume ; yes, record discarded mov [rttail], ebx ; else commit the insert resume: ; reenable NMI (Non-Maskable Interrupts) mov al, #0x0D ; RTC register D is out #0x70, al ; selected for access ; issue EOI-command to the master and slave PICs mov al, #0x20 ; send EOI command out #0xA0, al ; to slave PIC out #0x20, al ; to master PIC pop ds ; restore registers popad iret ; resume suspended task ;----------------------------------------------------------------- ;----------------------------------------------------------------- enough: .WORD 10 ; timeout for demo's loop ;----------------------------------------------------------------- execute_our_rtc_demo: call turn_off_drive_motors call save_pic_mask_setting call create_interrupt_gate call program_new_pic_masks call enable_rtc_interrupts nxevt: call get_next_event_record call show_the_updated_time dec word [enough] jnz nxevt call disable_rtc_interrupt call restore_pic_mask_bits ret ;----------------------------------------------------------------- rtq: .SPACE 64 ; holds sixteen longwords rthead: .LONG rtq ; pointer to queue-head rttail: .LONG rtq ; pointer to queue-tail rtbase: .LONG rtq ; pointer to queue-base rtedge: .LONG rtq+64 ; pointer to queue-edge ;----------------------------------------------------------------- advEBX: ; advances pointer to next event-record in circular queue push ax ; save working registers push ds mov ax, #sel_ss ; address this segment mov ds, ax ; with DS register add ebx, #4 ; add record-size to EBX cmp ebx, [rtedge] ; check: end-of-array? jl advok ; no, new EBX value is ok mov ebx, [rtbase] ; else EBX wraps to start advok: pop ds ; restore saved registers pop ax ret ;----------------------------------------------------------------- turn_off_drive_motors: mov dx, #0x03F2 ; Digital Output Port mov al, #0xC0 ; drive-motors off out dx, al ; write to controller ret ;----------------------------------------------------------------- create_interrupt_gate: mov edi, #0x70 lea di, theIDT[edi*8] ; point to descriptor mov 0[di], #isrRTC ; entry-point loword mov 2[di], #sel_cs ; 16bit code selector mov 4[di], #0xE600 ; 16bit interrupt-gate mov 6[di], #0x0000 ; entry-point loword ret ;----------------------------------------------------------------- mask1: .BYTE 0 ; holds mask for 8259A #1 mask2: .BYTE 0 ; holds mask for 8259A #2 ;----------------------------------------------------------------- ;----------------------------------------------------------------- att: .BYTE 0x30 ; display attribute-byte win: .WORD 1978, sel_es ; pointer to video window msg: .ASCII " Time now is " ; legend for clock report hour: .ASCII "xx:" ; stores a 2-digit string mins: .ASCII "xx:" ; stores a 2-digit string secs: .ASCII "xx " ; stores a 2-digit string len: .WORD * - msg ; total length of message ;----------------------------------------------------------------- show_the_updated_time: ; format our message-string with the updated information lea di, secs ; address seconds field call al2num ; convert AL to ascii lea di, mins ; address minutes field shr eax, #8 ; shift minutes into AL call al2num ; convert AL to ascii lea di, hour ; address hours field shr eax, #8 ; shift hours into AL call al2num ; convert AL to ascii ; write our message-string directly to video memory lea si, msg ; point DS:SI to string les di, win ; point ES:DI to window cld ; do forward processing mov ah, att ; setup color attribute mov cx, len ; setup character count nxchr: lodsb ; fetch next character stosw ; store char and color loop nxchr ; process entire message ret ;----------------------------------------------------------------- al2num: ; convert packed-BCD value in AL to ascii string at DS:DI push ax xor ah,ah ; clear accumulator MSB ror ax, #4 ; hi-nybble into AL ror ah, #4 ; lo-nybble into AH or ax, #0x3030 ; convert into numerals mov [di], ax ; store the digit-pair pop ax ret ;----------------------------------------------------------------- save_pic_mask_setting: in al, #0x21 ; get master PIC mask mov mask1, al ; save for use later in al, #0xA1 ; get slave PIC mask mov mask2, al ; save for use later ret ;----------------------------------------------------------------- program_new_pic_masks: ; mask devices except RTC and slave PIC mov al, #0xFE ; mask IRQ9-IRQ15 <-- corrected 4/8/04 out #0xA1, al ; from slave PIC mov al, #0xFB ; mask all but IRQ2 out #0x21, al ; from master PIC ret ;----------------------------------------------------------------- ;----------------------------------------------------------------- restore_pic_mask_bits: # restore original masks to the master and slave PICs mov al, mask2 ; recover slave's mask out #0xA1, al ; and restore to PIC mov al, mask1 ; recover master's mask out #0x21, al ; and restore to PIC ret ;----------------------------------------------------------------- enable_rtc_interrupts: ; program the RTC to generate 'update' interrupts mov al, #0x8B ; rtc's register B is out #0x70, al ; selected for access in al, #0x71 ; read rtc register B and al, #0x03 ; retain data-format bits or al, #0x10 ; enable update interrupt out #0x71, al ; write to register B mov al, #0x0D ; rtc's register D is out #0x70, al ; selected for access sti ; allow interrupts now ret ;----------------------------------------------------------------- disable_rtc_interrupt: ; reprogram the RTC to disable its 'update' interrupt mov al, #0x8B ; rtc's register B is out #0x70, al ; selected for access in al, #0x71 ; read rtc register B and al, #0x03 ; keep data-format bits out #0x71, al ; write to register B mov al, #0x0D ; rtc's register D is out #0x70, al ; selected for access cli ; no device interrupts ret ;----------------------------------------------------------------- get_next_event_record: ; spin here until our event-queue has a new record spin: mov ebx, [rthead] ; get queue's tail-ptr cmp ebx, [rttail] ; is same as head-ptr? je spin ; yes, continue testing ; remove the new 'update' record from our event-queue mov eax, [ebx] ; else fetch new record call advEBX ; advance queue tail-ptr mov [rthead], ebx ; to effect its removal ret ;----------------------------------------------------------------- .ALIGN 16 ; alignment at paragraph .SPACE 512 ; reserved for stack use tos0: ; label fop top-of-stack ;----------------------------------------------------------------- END