//----------------------------------------------------------------- // twotasks.s // // This program demonstrates task-switching by the processor // between two tasks that both execute at privilege-level 0. // // to assemble: $ as twotasks.s -o twotasks.o // and to link: $ ld twotasks.o -T ldscript -o twotasks.b // and install: $ dd if=twotasks.b of=/dev/sda4 seek=1 // // NOTE: This code begins executing with CS:IP = 1000:0002. // // programmer: ALLAN CRUSE // written on: 16 SEP 2008 //----------------------------------------------------------------- .equ seg_prog, 0x1000 # segment-address of arena .section .text #------------------------------------------------------------------ .word 0xABCD # our program 'signature' #------------------------------------------------------------------ main: .code16 # starts in x86 real-mode mov %sp, %cs:retptr+0 # preserve loader's SP mov %ss, %cs:retptr+2 # preserve loader's SS mov %cs, %ax # address program data mov %ax, %ss # with SS register lea tos1, %sp # establish task1 stack call create_task2_context call enter_protected_mode call exec_taskswitch_demo call leave_protected_mode lss %cs:retptr, %sp # switch to loader's stack lret # and return to the loader #------------------------------------------------------------------ retptr: .word 0, 0 # for saving initial SS:SP #------------------------------------------------------------------ hello2: .ascii " Hello from our secondary task " # says task #2 .equ MSGLEN2, . - hello2 # length of message-string .equ MSGOFF2, (10*80+24)*2 # screen-offset for hello2 #------------------------------------------------------------------ hello1: .ascii " Goodbye from our primary task " # says task #1 .equ MSGLEN1, . - hello1 # length of message-string .equ MSGOFF1, (12*80+24)*2 # screen-offset for hello1 #------------------------------------------------------------------ myTSS1: .zero 0x68 # task-state segment #1 .equ lim_T1, (.-theGDT)-1 # segment-limit of TSS1 #------------------------------------------------------------------ myTSS2: .zero 0x68 # task-state segment #2 .equ lim_T2, (.-theGDT)-1 # segment-limit of TSS2 #------------------------------------------------------------------ #------------------------------------------------------------------ theGDT: .word 0x0000, 0x0000, 0x0000, 0x0000 # null-descriptor .equ sel_es, (.-theGDT) # vram-segment's selector .word 0x7FFF, 0x8000, 0x920B, 0x0000 # vram-descriptor .equ sel_ds, (.-theGDT) # data-segment's selector .word 0xFFFF, 0x0000, 0x9201, 0x0000 # data-descriptor .equ sel_cs, (.-theGDT) # code-segment's selector .word 0xFFFF, 0x0000, 0x9A01, 0x0000 # code-descriptor .equ sel_t1, (.-theGDT) # task#1 state's selector .word lim_T1, myTSS1, 0x8901, 0x0000 # task-descriptor .equ sel_t2, (.-theGDT) # task#2 state's selector .word lim_T2, myTSS2, 0x8901, 0x0000 # task-descriptor .equ limGDT, (.-theGDT)-1 # our GDT's segment-limit #------------------------------------------------------------------ regGDT: .word limGDT, theGDT, 0x0001 # image for GDTR register #------------------------------------------------------------------ # This is an example of a code-fragment that is being 'shared' by # more than one task: it draws a message directly to video memory myloop: lodsb # fetch the next character stosw # store char and attribute loop myloop # draw the entire message iret # back to the calling task jmp myloop # (in case we enter again) #------------------------------------------------------------------ create_task2_context: # initialize the Task-State Segment for task #2 mov %cs, %ax # address our program data mov %ax, %ds # with the DS register lea myTSS2, %bx # point DS:BX to structure movw $myloop, 32(%bx) # image for register EIP movw $0x0000, 36(%bx) # image for register EFLAGS movw $0x6E00, 40(%bx) # image for register EAX movw $MSGLEN2, 44(%bx) # image for register ECX movw $tos2, 56(%bx) # image for register ESP movw $hello2, 64(%bx) # image for register ESI movw $MSGOFF2, 68(%bx) # image for register EDI movw $sel_es, 72(%bx) # image for register ES movw $sel_cs, 76(%bx) # image for register CS movw $sel_ds, 80(%bx) # image for register SS movw $sel_ds, 84(%bx) # image for register DS movw $0x0000, 88(%bx) # image for register FS movw $0x0000, 92(%bx) # image for register GS movw $0x0000, 96(%bx) # image for register LDTR ret #------------------------------------------------------------------ #------------------------------------------------------------------ enter_protected_mode: cli # interrupts not allowed mov %cr0, %eax # current machine status bts $0, %eax # set the PE-bit's image mov %eax, %cr0 # turn on protected-mode lgdt %cs:regGDT # load the GDTR register ljmp $sel_cs, $pm # reload the CS register pm: mov $sel_ds, %ax # get selector for stack mov %ax, %ss # and load SS register xor %ax, %ax # erase 'stale' contents mov %ax, %ds # from the DS register mov %ax, %es # from the ES register mov %ax, %fs # from the FS register mov %ax, %gs # from the GS register ret # back to 'main' routine #------------------------------------------------------------------ exec_taskswitch_demo: # establish the Task-State Segment for this initial task mov $sel_t1, %ax # address task's TSS ltr %ax # with TR register # now prepare the other CPU registers for executing the # 'myloop' code-fragment, in order to draw a message on # the screen; but, before actually calling 'myloop', we # will do a 'task-switch' to our secondary task using a # 'lcall' instruction (so that task will 'return' here) mov $sel_ds, %ax # address program's data mov %ax, %ds # with DS register lea hello1, %si # point DS:SI to message mov $sel_es, %ax # address display memory mov %ax, %es # with ES register mov $MSGOFF1, %di # point ES:DI to display cld # use forward processing mov $0x3F, %ah # attribute-code into AH mov $MSGLEN1, %cx # message-length into CX # OK, now switch to secondary task (by using an 'lcall') lcall $sel_t2, $0 # performs a task-switch # now setup the stack to 'simulate' the occurrence of an # interrupt (so the 'iret' in 'myloop' will return here) pushf # 'push' image for FLAGS lcall $sel_cs, $myloop # far call pushes CS, IP # OK, having shown this task's message, our demo is done ret # return to main routine #------------------------------------------------------------------ #------------------------------------------------------------------ leave_protected_mode: # prepare data-segment registers for resuming 'real-mode' mov $sel_ds, %ax # 64K 'writable' access mov %ax, %ds # into the DS cache mov %ax, %es # into the ES cache mov %ax, %fs # into the FS cache mov %ax, %gs # into the GS cache # now we are ready to leave protected-mode mov %cr0, %eax # get current status btr $0, %eax # clear PE-bit image mov %eax, %cr0 # disable protection # restore 'real-mode' segment-addresses to CS and SS ljmp $seg_prog, $rm # reload CS register rm: mov %cs, %ax # address stack area mov %ax, %ss # with SS register sti # interrupts allowed ret # now back to 'main' #------------------------------------------------------------------ .align 16 # to insure word-alignment #------------------------------------------------------------------ .space 256 # reserved for task2 stack tos2: # label for task2 stacktop #------------------------------------------------------------------ .space 256 # reserved for task1 stack tos1: # label for task1 stacktop #------------------------------------------------------------------ .end # nothing more to assemble