;//--------------------------------------------------------------- ;// cpuslice.s ;// ;// This program uses the APIC Timer's periodic interrupt to ;// implement processor timesharing among several tasks. ;// ;// assemble with: $ as86 cpuslice.s -b cpuslice.b ;// install using: $ dd if=cpuslice.b of=/dev/fd0 seek=1 ;// ;// NOTE: This code begins executing with CS:IP = 1000:0002. ;// ;// programmer: ALLAN CRUSE ;// date begun: 08 MAY 2004 ;// completion: 10 MAY 2004 ;//--------------------------------------------------------------- .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 initialize_os_tables call prepare_task_structs call clear_display_screen call turn_off_drive_motor call xchg_interrupt_masks call enter_protected_mode call init_APIC_interrupts call execute_program_demo call stop_APIC_interrupts call leave_protected_mode call xchg_interrupt_masks push dword return_address ; recover our exit-address retf ; exit to program launcher ;----------------------------------------------------------------- return_address: .LONG 0 ; to store exit-address ;----------------------------------------------------------------- clear_display_screen: mov ax, #0x0003 ; set standard text mode int 0x10 ; request BIOS service ret ;----------------------------------------------------------------- turn_off_drive_motor: mov dx, #0x3F2 ; FDC Digital Output port mov al, #0xC0 ; command: all motors off out dx, al ; send command to the FDC ret ;----------------------------------------------------------------- ;----------------------------------------------------------------- ; EQUATES realCS EQU 0x1000 ; segment-address of code limGDT EQU 0x008F ; allocate 18 descriptors sel_es EQU 0x0008 ; vram-segment selector sel_cs EQU 0x0010 ; code-segment selector sel_ss EQU 0x0018 ; data-segment selector sel_fs EQU 0x0020 ; flat-segment selector sel_as EQU 0x0028 ; apic-segment selector sel_t0 EQU 0x0030 ; task-segment selector sel_t1 EQU 0x0038 ; task-segment selector sel_t2 EQU 0x0040 ; task-segment selector sel_t3 EQU 0x0048 ; task-segment selector sel_t4 EQU 0x0050 ; task-segment selector sel_t5 EQU 0x0058 ; task-segment selector sel_t6 EQU 0x0060 ; task-segment selector sel_t7 EQU 0x0068 ; task-segment selector sel_t8 EQU 0x0070 ; task-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 .WORD 0xFFFF, 0x0000, 0x9200, 0x00CF ; flat descriptor .WORD 0x0000, 0x0000, 0x92E0, 0xFE80 ; apic descriptor .WORD 0x0067, myTSS0, 0x8901, 0x0000 ; task descriptor .WORD 0x0067, myTSS1, 0x8901, 0x0000 ; task descriptor .WORD 0x0067, myTSS2, 0x8901, 0x0000 ; task descriptor .WORD 0x0067, myTSS3, 0x8901, 0x0000 ; task descriptor .WORD 0x0067, myTSS4, 0x8901, 0x0000 ; task descriptor .WORD 0x0067, myTSS5, 0x8901, 0x0000 ; task descriptor .WORD 0x0067, myTSS6, 0x8901, 0x0000 ; task descriptor .WORD 0x0067, myTSS7, 0x8901, 0x0000 ; task descriptor .WORD 0x0067, myTSS8, 0x8901, 0x0000 ; task descriptor .WORD 0x0000, 0x0000, 0x0000, 0x0000 ; null descriptor .WORD 0x0000, 0x0000, 0x0000, 0x0000 ; null descriptor .WORD 0x0000, 0x0000, 0x0000, 0x0000 ; null descriptor ;----------------------------------------------------------------- theIDT: .SPACE 2048 ; enough for 256 gate-descriptors ;----------------------------------------------------------------- mask1: .BYTE 0xFD mask2: .BYTE 0xFF ;----------------------------------------------------------------- xchg_interrupt_masks: in al, #0xA1 ; slave PIC mask bits xchg mask2, al ; swapped with storage out #0xA1, al ; setup revised masks in al, #0x21 ; master PIC mask bits xchg mask1, al ; swapped with storage out #0x21, al ; setup revised masks ret ;----------------------------------------------------------------- ;----------------------------------------------------------------- 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 ;----------------------------------------------------------------- ;----------------------------------------------------------------- initialize_os_tables: ; initialize IDT descriptor for gate 0x40 mov edi, #0x40 ; ID-number for the gate lea di, theIDT[edi*8] ; gate's offset-address mov 0[di], #isrTMR ; entry-point's loword mov 2[di], #sel_cs ; code-segment selector mov 4[di], #0x8E00 ; 386 interrupt-gate mov 6[di], #0x0000 ; entry-point's hiword ; initialize IDT descriptor for gate 0x0D mov edi, #0x0D ; ID-number for the gate lea di, theIDT[edi*8] ; gate's offset-address mov 0[di], #isrGPF ; entry-point's loword mov 2[di], #sel_cs ; code-segment selector mov 4[di], #0x8E00 ; 386 interrupt-gate mov 6[di], #0x0000 ; entry-point's hiword ; initialize IDT descriptor for gate 0x09 mov edi, #0x09 ; ID-number for the gate lea di, theIDT[edi*8] ; gate's offset-address mov 0[di], #isrKBD ; entry-point's loword mov 2[di], #sel_cs ; code-segment selector mov 4[di], #0x8E00 ; 386 interrupt-gate mov 6[di], #0x0000 ; entry-point's hiword ret ; back to main routine ;----------------------------------------------------------------- prepare_task_structs: ; create non-overlapping stack-areas for all the tasks mov ebx, #0x20000 ; address of stack areas ; initialize the TSS for each of the auxiliary tasks lea di, myTSS0 ; point to the TSS array mov cx, #8 ; number of other tasks nxtss: add di, #0x68 ; point to the next TSS add ebx, #0x1000 ; next stacktop address mov dword 0x20[di], #thread ; image for register EIP mov dword 0x24[di], #0x0200 ; image for register EFLAGS mov dword 0x38[di], ebx ; image for register ESP mov dword 0x48[di], #sel_es ; image for register ES mov dword 0x4C[di], #sel_cs ; image for register CS mov dword 0x50[di], #sel_fs ; image for register SS mov dword 0x54[di], #sel_ss ; image for register DS mov dword 0x58[di], #0 ; image for register FS mov dword 0x5F[di], #0 ; image for register GS mov dword 0x60[di], #0 ; image for register LDTR loop nxtss ret ;----------------------------------------------------------------- tossave: .WORD 0, 0, 0 ; stores 48-bit pointer ;----------------------------------------------------------------- ;----------------------------------------------------------------- execute_program_demo: ; save stack-address (so our fault-handler can exit) mov tossave+0, esp ; preserve 32-bit offset mov tossave+4, ss ; plus 16-bit selector ; establish this task's save-area mov ax, #sel_t0 ; address task segment ltr ax ; with TR register ; begin the "round-robin" multitasking jmpf [taskptr] ; transfer to first task ret ; return to main routine ;----------------------------------------------------------------- finish_up_main_thread: mov ax, #sel_ss ; address this segment mov ds, ax ; with DS register lss esp, tossave ; to reload SS and ESP ret ; back to main routine ;----------------------------------------------------------------- init_APIC_interrupts: ; program the Local-APIC Timer for periodic interrupts push ds mov ax, #sel_as ; address APIC segment mov ds, ax ; with DS register mov eax, #0x00020040 ; periodic int-0x40 mov [0x0320], eax ; APIC Timer LVT mov eax, #0x00000000 ; program zero value mov dword [0x03E0], #0 ; APIC Divisor Config mov eax, #0x01000000 ; 2**24 (16 million) mov dword [0x0380], eax ; APIC Initial Count pop ds ret ;----------------------------------------------------------------- stop_APIC_interrupts: ; mask the Local-APIC Timer's periodic interrupts push ds mov ax, #sel_as ; address APIC segment mov ds, ax ; with DS register mov eax, #0x00010040 ; mask interrupt 0x40 mov [0x0320], eax ; APIC Timer LVT mov eax, #0x00000000 ; program zero value mov [0x0380], eax ; APIC Initial Count pop ds ret ;----------------------------------------------------------------- elt: .ASCII " TR SS GS FS ES DS" ; field labels .ASCII " EDI ESI EBP ESP EBX EDX ECX EAX" .ASCII " err EIP CS EFL" ;----------------------------------------------------------------- buf: .ASCII " nnn=xxxxxxxx " len: .WORD * - buf ; length of output buffer att: .BYTE 0x70 ; reverse-video attribute ;----------------------------------------------------------------- ;----------------------------------------------------------------- draw_stack: push ds ; preserve registers push es mov ax, #sel_ss ; address this segment mov ds, ax ; with DS register mov ax, #sel_es ; address vram segment mov es, ax ; with ES register cld ; do forward processing xor ebx, ebx ; start element counter nxelt: mov eax, [ebp + ebx*4 - 24] ; get next stack-element lea di, buf+5 ; point to output-field call eax2hex ; convert to hexadecimal mov eax, elt[ebx*4] ; get next element-label mov buf, eax ; put next element-label mov edi, #3970 ; bottom line destination imul eax, ebx, #160 ; line-count times length sub edi, eax ; subtracted from bottom lea si, buf ; point to message buffer mov ah, att ; setup character colors mov cx, len ; setup buffer length .L1: lodsb ; fetch next character stosw ; store char and color loop .L1 ; again for other chars inc ebx ; increment element count cmp ebx, #18 ; all elements displayed? jb nxelt ; no, show another element pop es ; restore registers pop ds ret ;----------------------------------------------------------------- eax2hex: ; converts EAX to hexadecimal digit-string at DS:DI pushfd mov edx, eax ; copy element to EDX mov ecx, #8 ; number of nybbles .L0: rol edx, #4 ; next nybble into DL mov al, dl ; copy nybble into AL and al, #0x0F ; isolate nybble bits cmp al, #0x0A ; -- Lopez algorithm -- sbb al, #0x69 ; -- converts nybble -- das ; -- to ascii digit -- mov [di], al ; write digit to buffer inc di ; advance buffer index loop .L0 ; again for next nybble popfd ret ;----------------------------------------------------------------- ;----------------------------------------------------------------- isrGPF: ; fault-handler for General Protection Exceptions pushad ; preserve registers mov ebp, esp ; setup frame base push dword #0 mov [esp], ds ; store DS push dword #0 mov [esp], es ; store ES push dword #0 mov [esp], fs ; store FS push dword #0 mov [esp], gs ; store GS push dword #0 mov [esp], ss ; store SS push dword #0 str word [esp] ; store TR call draw_stack ; draw register values mov esp, ebp ; discard extra storage popad ; restore registers jmpf #finish_up_main_thread, #sel_cs ; to 16-bit code ;----------------------------------------------------------------- taskidx: .LONG 0 taskptr: .WORD 0, sel_t1 tasklst: .WORD sel_t1, sel_t2, sel_t3, sel_t4 .WORD sel_t5, sel_t6, sel_t7, sel_t8 ;----------------------------------------------------------------- isrTMR: ; interrupt-handler for APIC Timer's periodic interrupt pushad ; must preserve registers push ds ; issue End-Of-Interrupt to Local-APIC mov ax, #sel_as ; address Local-APIC mov ds, ax ; with DS register mov [0x00B0], eax ; issue EOI to APIC ; increment revolving counter and 'schedule' next task mov ax, #sel_ss ; address this segment mov ds, ax ; with DS register inc dword [taskidx] ; increment taskidx and dword [taskidx], #7 ; get remainder mod 8 mov esi, taskidx ; load new array-index mov ax, tasklst[esi*2] ; copy next tss-selector mov taskptr+2, ax ; into address pointer jmpf [taskptr] ; transfer to bext task ; upon return, check for termination-request cmp word [quit], #0 ; user wants to quit? je .OK ; no, resume the thread jmpf #0, #sel_t0 ; else back to task #0 .OK: pop ds ; restore registers popad iretd ; resume suspended task ;----------------------------------------------------------------- ;----------------------------------------------------------------- quit: .WORD 0 ; flag, set by -key ;----------------------------------------------------------------- isrKBD: ; interrupt-handler for keyboard-controller interrupts pushad push ds in al, #0x64 ; get controller status test al, #1 ; output buffer full? jz ignore ; no, disregard data in al, #0x60 ; else get new scancode cmp al, #0x81 ; -key released? jne ignore ; no, then disregard mov ax, #sel_ss ; address this segment mov ds, ax ; with DS register mov word [quit], #1 ; set the 'quit' flag ignore: mov al, #0x20 ; non-specific EOI out #0x20, al ; sent to Master PIC pop ds popad iretd ;----------------------------------------------------------------- qw2hex: ; draws quadword in (EDX,EAX) as a hex-string at DS:EDI pushad push eax ; push lo doubleword push edx ; push hi doubleword mov ebp, #2 ; number of doublewords .Q0: pop edx ; pop saved doubleword mov ecx, #8 ; number of nybbles .N0: rol edx, #4 ; next nybble to DL mov al, dl ; copy nybble to AL and al, #0x0F ; isolate nybble bits cmp al, #10 ; -- Lopez algorithm -- sbb al, #0x69 ; -- converts nybble -- das ; -- to hex numeral -- mov [edi], al ; store numeral to buffer inc edi ; advance buffer pointer loop .N0 ; again for other nybbles dec ebp ; decrement loop-count jnz .Q0 ; nonzero? do loop again popad ret ;----------------------------------------------------------------- thmsg: .ASCII " Task #" thbuf: .ASCII " : xxxxxxxxxxxxxxxx " thlen: .LONG * - thmsg thatt: .BYTE 0x70 ;----------------------------------------------------------------- ;----------------------------------------------------------------- MACRO rdtsc .BYTE 0x0F, 0x31 ; machine code for 'rdtsc' MEND ;----------------------------------------------------------------- thread: ; allocate 80-byte data-buffer on task's private stack sub esp, #80 ; allocate buffer space mov ebp, esp ; setup base pointer ; copy the message-template to this task's data-buffer xor edi, edi ; initialize the index mov ecx, thlen ; length of message .C0: mov al, thmsg[edi] ; fetch next character mov [ebp + edi], al ; store this character inc edi ; increment the index loop .C0 ; again for other chars ; compute this task's ID-number from its task-selector str ax ; get task's TSS-selector sub ax, #sel_t0 ; minus Task #0 selector shr ax, #3 ; divide difference by 8 movzx eax, ax ; extend result to dword mov [ebp+76], eax ; and save for use below or al, #0x30 ; convert to a numeral mov edi, #thbuf ; offset to the buffer sub edi, #thmsg ; less offset to message mov [ebp+edi], al ; put numeral in message nxmsg: push ds ; preserve DS mov ax, ss ; address stack segment mov ds, ax ; using DS register mov edi, #thbuf+3 ; offset to output-field sub edi, #thmsg ; minus message's offset lea edi, [ebp+edi] ; point EDI to the field rdtsc ; read TimeStamp Counter call qw2hex ; convert quadword to hex pop ds ; restore DS cld ; do forward processing imul edi, [ebp+76], #160 ; ID# times line-length add edi, #1334 ; plus indent (to center) xor esi, esi ; initialize index mov ah, thatt ; setup attribute mov cx, thlen ; setup counter .O1: mov al, [ebp+esi] ; get message character inc esi ; and increment index stosw ; write char and color loop .O1 ; again for full message jmp nxmsg ; draw TimeStamp again ;----------------------------------------------------------------- .ALIGN 16 ; alignment at paragraph .SPACE 512 ; reserved for stack use tos0: ; label fop top-of-stack ;----------------------------------------------------------------- ;----------------------------------------------------------------- myTSS0: .SPACE 0x68 ; Task-State Segment #0 myTSS1: .SPACE 0x68 ; Task-State Segment #1 myTSS2: .SPACE 0x68 ; Task-State Segment #2 myTSS3: .SPACE 0x68 ; Task-State Segment #3 myTSS4: .SPACE 0x68 ; Task-State Segment #4 myTSS5: .SPACE 0x68 ; Task-State Segment #5 myTSS6: .SPACE 0x68 ; Task-State Segment #6 myTSS7: .SPACE 0x68 ; Task-State Segment #7 myTSS8: .SPACE 0x68 ; Task-State Segment #8 ;----------------------------------------------------------------- .BYTE 0xFF ; to insure zero memory ;----------------------------------------------------------------- END