;$include xa-g3.equ ;will implement included later on ; Include file for XA-G3 SFR Definitions ; Philips Semiconductors. Revision 1.8, 1/21/97 ; $nolist ;will implement this later on ; Register equates. acc reg R4L ; for translated 80C51 code dpl reg R6L ; for translated 80C51 code dph reg R6H ; for translated 80C51 code sp reg R7 ; for translated 80C51 code ; SFR byte and bit definitions. bcr sfr 46ah btrh sfr 469h btrl sfr 468h cs sfr 443h ds sfr 441h es sfr 442h ieh sfr 427h eri0 bit ieh.0 eti0 bit ieh.1 eri1 bit ieh.2 eti1 bit ieh.3 iel sfr 426h ex0 bit iel.0 et0 bit iel.1 ex1 bit iel.2 et1 bit iel.3 et2 bit iel.4 ea bit iel.7 ipa0 sfr 4a0h ipa1 sfr 4a1h ipa2 sfr 4a2h ipa4 sfr 4a4h ipa5 sfr 4a5h p0 sfr 430h p1 sfr 431h p2 sfr 432h p3 sfr 433h p0cfga sfr 470h p1cfga sfr 471h p2cfga sfr 472h p3cfga sfr 473h p0cfgb sfr 4f0h p1cfgb sfr 4f1h p2cfgb sfr 4f2h p3cfgb sfr 4f3h pcon sfr 404h idl bit pcon.0 pd bit pcon.1 pswh sfr 401h im0 bit pswh.0 im1 bit pswh.1 im2 bit pswh.2 im3 bit pswh.3 rs0 bit pswh.4 rs1 bit pswh.5 tm bit pswh.6 sm bit pswh.7 pswl sfr 400h z bit pswl.0 n bit pswl.1 v bit pswl.2 ac bit pswl.6 cy bit pswl.7 psw51 sfr 402h rth0 sfr 455h rth1 sfr 457h rtl0 sfr 454h rtl1 sfr 456h s0con sfr 420h ri_0 bit s0con.0 ti_0 bit s0con.1 rb8_0 bit s0con.2 tb8_0 bit s0con.3 ren_0 bit s0con.4 sm2_0 bit s0con.5 sm1_0 bit s0con.6 sm0_0 bit s0con.7 scon sfr 420h ; duplicate label for translated 80C51 code. ri bit scon.0 ; duplicate label for translated 80C51 code. ti bit scon.1 ; duplicate label for translated 80C51 code. rb8 bit s0con.2 ; duplicate label for translated 80C51 code. tb8 bit s0con.3 ; duplicate label for translated 80C51 code. ren bit s0con.4 ; duplicate label for translated 80C51 code. sm2 bit s0con.5 ; duplicate label for translated 80C51 code. sm1 bit s0con.6 ; duplicate label for translated 80C51 code. sm0 bit s0con.7 ; duplicate label for translated 80C51 code. s0stat sfr 421h stint0 bit s0stat.0 oe0 bit s0stat.1 br0 bit s0stat.2 fe0 bit s0stat.3 s0buf sfr 460h sbuf sfr 460h ; duplicate label for translated 80C51 code. s0addr sfr 461h s0aden sfr 462h s1con sfr 424h ri_1 bit s1con.0 ti_1 bit s1con.1 rb8_1 bit s1con.2 tb8_1 bit s1con.3 ren_1 bit s1con.4 sm2_1 bit s1con.5 sm1_1 bit s1con.6 sm0_1 bit s1con.7 s1stat sfr 425h stint1 bit s1stat.0 oe1 bit s1stat.1 br1 bit s1stat.2 fe1 bit s1stat.3 s1buf sfr 464h s1addr sfr 465h s1aden sfr 466h scr sfr 440h ssel sfr 403h r0seg bit ssel.0 r1seg bit ssel.1 r2seg bit ssel.2 r3seg bit ssel.3 r4seg bit ssel.4 r5seg bit ssel.5 r6seg bit ssel.6 eswen bit ssel.7 swe sfr 47ah swr sfr 42ah swr1 bit swr.0 swr2 bit swr.1 swr3 bit swr.2 swr4 bit swr.3 swr5 bit swr.4 swr6 bit swr.5 swr7 bit swr.6 swe1 equ 00000001q ; bit masks for software interrupt enables. swe2 equ 00000010q swe3 equ 00000100q swe4 equ 00001000q swe5 equ 00010000q swe6 equ 00100000q swe7 equ 01000000q t2con sfr 418h cprl2 bit t2con.0 ct2 bit t2con.1 tr2 bit t2con.2 exen2 bit t2con.3 tclk0 bit t2con.4 rclk0 bit t2con.5 tclk bit t2con.4 ; duplicate label for translated 80C51 code. rclk bit t2con.5 ; duplicate label for translated 80C51 code. exf2 bit t2con.6 tf2 bit t2con.7 t2mod sfr 419h dcen bit t2mod.0 t2oe bit t2mod.1 t2rd bit t2mod.2 tclk1 bit t2mod.4 rclk1 bit t2mod.5 th2 sfr 459h tl2 sfr 458h t2caph sfr 45bh t2capl sfr 45ah tcon sfr 410h it0 bit tcon.0 ie0 bit tcon.1 it1 bit tcon.2 ie1 bit tcon.3 tr0 bit tcon.4 tf0 bit tcon.5 tr1 bit tcon.6 tf1 bit tcon.7 th0 sfr 451h th1 sfr 453h tl0 sfr 450h tl1 sfr 452h tmod sfr 45ch tstat sfr 411h t0oe bit tstat.0 t0rd bit tstat.1 t1oe bit tstat.2 t1rd bit tstat.3 wdcon sfr 41fh wdtof bit wdcon.1 wdrun bit wdcon.2 pre0 bit wdcon.5 pre1 bit wdcon.6 pre2 bit wdcon.7 wdl sfr 45fh wfeed1 sfr 45dh wfeed2 sfr 45eh ; Port pin name definitions. a4d0 bit P0.0 a5d1 bit P0.1 a6d2 bit P0.2 a7d3 bit P0.3 a8d4 bit P0.4 a9d5 bit P0.5 a10d6 bit P0.6 a11d7 bit P0.7 a0 bit P1.0 wrh bit P1.0 a1 bit P1.1 a2 bit P1.2 a3 bit P1.3 rxd1 bit P1.4 txd1 bit P1.5 t2 bit P1.6 t2ex bit P1.7 a12d8 bit P2.0 a13d9 bit P2.1 a14d10 bit P2.2 a15d11 bit P2.3 a16d12 bit P2.4 a17d13 bit P2.5 a18d14 bit P2.6 a19d15 bit P2.7 rxd0 bit P3.0 txd0 bit P3.1 int0 bit P3.2 int1 bit P3.3 t0 bit P3.4 t1 bit P3.5 busw bit P3.5 wrl bit P3.6 rd bit P3.7 ; End of XA-G3 SFR definitions. ;$list ;handle this later on trap_delay equ 0 trap_cout equ 1 org $8040 ;paulmon2 will jump here on trap #0 jmp syscall_sleep org $8044 ;paulmon2 will jump here on trap #1 jmp syscall_cout org $8084 ;paulmon2 will jump here on timer0 interrupt jmp timer0 org $80A0 ;paulmon2 will jump here on uart0 rx interrupt jmp uart0_recv org $80A4 ;paulmon2 will jump here on uart0 tx interrupt jmp uart0_xmit org $80A8 ;paulmon2 will jump here on uart1 rx interrupt jmp uart1_recv org $80AC ;paulmon2 will jump here on uart1 tx interrupt jmp uart1_xmit org $8100 ;paulmon2 will jump here on software int1 jmp context_switch org $8104 ;paulmon2 will jump here on software int2 jmp do_events org $8120 jmp poweron request_do_events bit swr2 request_context_switch bit swr1 max_num_ticks equ 7 ;max time a process can run if other ;processes are waiting in the run queue ;this memory allocation should be done with "ds" directives to ;save space, but for now it's done with "equ" with lots of gaps, ;so that things will stay put and aligned on 16 byte boundries ;where they can be easily viewed with the paulmon2 hex dump. When ;the kernel code is more mature this will have to be changed. sys_stack equ $FB00 rx0_buf_head equ $FB00 ;2 bytes rx0_buf_tail equ $FB02 ;2 bytes rx0_buf_size equ $20 rx0_buf equ $FB10 ;rx0_buf_size bytes rx1_buf_head equ $FB40 ;2 bytes rx1_buf_tail equ $FB42 ;2 bytes rx1_buf_size equ $20 rx1_buf equ $FB50 ;rx1_buf_size bytes tx0_buf_head equ $FB80 ;2 bytes tx0_buf_tail equ $FB82 ;2 bytes tx0_buf_size equ $20 tx0_buf_threshold equ 9 tx0_buf equ $FB90 ;tx0_buf_size bytes tx1_buf_head equ $FBC0 ;2 bytes tx1_buf_tail equ $FBC2 ;2 bytes tx1_buf_size equ $20 tx1_buf_threshold equ 9 tx1_buf equ $FBD0 ;tx1_buf_size bytes current_proc equ $FEF0 ;2 bytes, ptr to proc_table entry of ;currently running process, 0 if none run_queue equ $FEF2 ;2 bytes, pointer to a linked list ;of processes waiting to run sleep_queue equ $FEF4 ;2 bytes, pointer to a linked list ;of sleeping processes time_running equ $FEF6 ;1 byte, number of ticks that the ;current process has run. event_queue equ $FCF0 ;2 bytes, pointer to a linked list ;of pending events (event_table) max_num_proc equ 14 proc_table equ $FE00 ;(max_num_proc*pt_entry_size) bytes pt_entry_size equ 16 ;16 bytes per table entry pt_usp equ 0 ;2 bytes, User Stack Pointer pt_q_next equ 2 ;2 bytes, ptr to next in queue pt_q_prev equ 4 ;2 bytes, ptr to prev in queue (+pt_q_next) pt_wait equ 6 ;2 bytes, what are we waiting for? pt_wakeup equ 8 ;2 bytes, where do we go on wakeup? pt_prog_addr equ 10 ;2 bytes, location of program header pt_prog_cs equ 12 ;1 byte, code segment of program header pt_priority equ 13 ;1 byte, execution priority pt_pid equ 14 ;1 byte, id num for this process ;need to define a program header, which will include various ;info about the program, including the req'd stack space, max ;expect memory to malloc, range of directly addressed memory ;(which must be saved during a context switch), max number of ;open file descriptors reqd, etc. max_num_event equ 15 ;(max_num_proc*2) event_table equ $FC00 ;(max_num_event*evt_entry_size) evt_entry_size equ 16 ;8 bytes per event entry evt_ticks equ 0 ;# of ticks until event (delta from prev) evt_next equ 2 ;pointer to next event in list evt_function equ 4 ;place to call (0 if evt slot unused) evt_arg1 equ 6 ;optional argument ;the file descriptors will be allocated before the stack in the ;block of memory which is allocated when the process is created. ;each individual process can specify the max number of file ;descriptors that it will require file_desc_table equ $FE80 ;128 bytes fd_entry_size equ 7 ;7 bytes per table entry fd_offset equ 0 ;4 bytes, offset in file fd_flags equ 4 ;1 byte, read/write/append fd_routines equ 5 ;2 bytes, ptr to i/o routines ;single character debug symbols: ; space context switcher is idle ; * contect switcher entered ; # timer interrupt (normal) ; % timer interrupt requested context switch ; ! timer interrupt requested event service ; @ entered event service interrupt routine ; ^ event service function called ; ~ leaving event service interrupt routine ;this timer interrupt routine is responsible for monitoring the ;time that the current process has been running and adjusting the ;process priorities accordingly. It also checks if any events ;in the event queue need to be processed. This code runs at a ;high cpu priority and it runs often, so it should get done as ;quickly as possible... if anything lengthy might need to be done, ;a software interrupt (low priority) should be set, so it can be ;done later on without blocking other important interrupts, such ;as low-level i/o. timer0: push r0, r1, r2, r3, r4, r5, r6 ;other stuff can be added here, such as maintaining a ;system clock... ;mov r4l, #'#' check_events: mov r0, #event_queue clr ea mov r1, [r0] beq ckevt_end ;no events if queue is empty mov r2, [r1+evt_ticks] beq ckevt_do_em ;if we get here, there are events pending but it's not ;time to do any of them yet, so we'll just decrement the ;ticks of the first one, which will decrement the effective ;time for all of them since they use a delta-time between ;each entry in the linked list adds.w [r1+evt_ticks], #-1 br ckevt_end ckevt_do_em: ;if we get here, there is at least one event which needs ;to be serviced... but we won't do that here, just set the ;software interrupt so it can be handled by "do_event" ;setb request_do_events ;mov r4l, #'!' ;call cout ;call do_events ;not nice, but sw int priority not working setb request_do_events ckevt_end: setb ea nop schedule: mov r0, #current_proc clr ea mov r1, [r0] beq sch_end ;don't bother if no process is running sub.b [r1+pt_priority], #1 ;decrease priority by one bcc sch_priority_ok movs.b [r1+pt_priority], #0 ;but don't go less than zero sch_priority_ok: mov r0, #time_running mov r3l, [r0] adds r3l, #1 ;increment time_running mov [r0], r3l cmp r3l, #max_num_ticks bl sch_end ;if we get here, the currently running process is has been ;using the cpu for at least the max number ticks, so it's ;time to force it to take a rest and let someone else have ;a chance. mov r0, #run_queue mov r1, [r0] beq sch_end ;don't preempt if nothing in run queue ;mov r4l, #'%' setb request_context_switch ;make something else run sch_end: setb ea ;cmp r4l, #'!' ;beq t0_skip_print ;call cout nop t0_skip_print: pop r0, r1, r2, r3, r4, r5, r6 reti ;this routine actually calls the event handlers for all ;events which are supposed to happen NOW. The main timer ;routine must decrement the time of the events and arrange ;for this code to be run when any events are supposed to ;happen. The event handler gets the address of the event ;in r1. do_events: push r0, r1, r2, r3, r4, r5, r6 do_events_begin: ;mov r4l, #'@' ;call cout doevt_loop: mov r0, #event_queue mov r1, [r0] beq doevt_end ;no events to do if queue is empty mov r2, [r1+evt_ticks] bne doevt_end ;done if event is in the future mov r2, [r1+evt_next] mov [r0], r2 ;remove event from queue mov r2, [r1+evt_function] movs.w [r1+evt_function], #0 ;mov r4l, #'^' ;call cout call [r2] ;call the event handler specified br doevt_loop doevt_end: ;mov r4l, #'~' ;call cout clr request_do_events pop r0, r1, r2, r3, r4, r5, r6 reti ;interrupt receive routine for uart #0 uart0_recv: push r0, r1, r2, r5, r6 clr ri_0 mov r5l, s0buf mov r2, #rx0_buf mov r0, #rx0_buf_head mov r6, #rx0_buf_tail mov r1, [r0] adds r1, #1 cjne r1, #rx0_buf+rx0_buf_size, uart_recv_check2 mov r1, #rx0_buf br uart_recv_check2 ;interrupt receive routine for uart #1 uart1_recv: push r0, r1, r2, r5, r6 clr ri_1 mov r5l, s1buf mov r2, #rx1_buf mov r0, #rx1_buf_head mov r6, #rx1_buf_tail mov r1, [r0] adds r1, #1 cjne r1, #rx1_buf+rx1_buf_size, uart_recv_check2 mov r1, #rx1_buf ;this remaining code is shared by both uart receive routines uart_recv_check2: cmp r1, [r6] bne uart_recv_ok ;if we get here, the buffer is full, discard data call wakeup_intr_io pop r0, r1, r2, r5, r6 reti uart_recv_ok: mov.b [r1], r5l ;store byte into buffer mov [r0], r1 ;update buffer pointer ;perhaps there should be a way for us to know how much data a ;sleeping process wants to extract from the buffer, so we can ;avoid waking it up until there is at least that much data. call wakeup_intr_io pop r0, r1, r2, r5, r6 reti ;interrupt transmit routine for uart #0 uart0_xmit: push r0, r1, r2, r5, r6 clr ti_0 mov r0, #tx0_buf_tail mov r2, #tx0_buf_head mov r1, [r0] cmp r1, [r2] beq uart_no_xmit adds r1, #1 cjne r1, #tx0_buf+tx0_buf_size, uart0_xmit2 mov r1, #tx0_buf uart0_xmit2: mov.b s0buf, [r1] mov [r0], r1 ;now figure out if we want to wake up processes which are ;waiting to put more data into this buffer mov r1, [r2] sub r1, [r0] bcc uart0_xmit3 add r1, #tx0_buf_size uart0_xmit3: cmp r1, #(tx0_buf_threshold) bcc uart0_xmit_end ;don't wake proc unless over threshold mov r2, #tx0_buf call wakeup_intr_io uart0_xmit_end: pop r0, r1, r2, r5, r6 reti ;interrupt transmit routine for uart #1 uart1_xmit: push r0, r1, r2, r5, r6 clr ti_1 mov r0, #tx1_buf_tail mov r2, #tx1_buf_head mov r1, [r0] cmp r1, [r2] beq uart_no_xmit adds r1, #1 cjne r1, #tx1_buf+tx1_buf_size, uart1_xmit2 mov r1, #tx1_buf uart1_xmit2: mov.b s1buf, [r1] mov [r0], r1 ;now figure out if we want to wake up processes which are ;waiting to put more data into this buffer mov r1, [r2] sub r1, [r0] bcc uart1_xmit3 add r1, #tx1_buf_size uart1_xmit3: cmp r1, #tx1_buf_threshold bcc uart1_xmit_end ;don't wake proc unless over threshold mov r2, #tx1_buf call wakeup_intr_io uart1_xmit_end: pop r0, r1, r2, r5, r6 reti ;this is shared by both transmit interrupt routines uart_no_xmit: ;if we got here, there is no data waiting to transmit ;load the _head pointer with 0, so a routine which loads ;data into the buffer will know to set the ti bit. movs.w [r2], #0 pop r0, r1, r2, r5, r6 reti ;This routine wakes up any sleeping processes that are waiting ;for i/o in the buffer pointed to by r2. Interrupt routines ;should call here after doing i/o one their buffers, so that ;any processes which are waiting for that i/o will be awakened. wakeup_intr_io: mov r0, #sleep_queue mov r1, [r0] beq wkintio_empty ;sleep_queue is empty clr ea ;disable interrupts while we look at the queue wkintio_loop: cmp [r1+pt_wait], r2 beq wkintio_wakeup ;wake proc if waiting for i/o on this buffer mov r1, [r1+pt_q_next] bne wkintio_loop setb ea ;ok for other interrupts now wkintio_empty: ret wkintio_wakeup: ;if we get here, this sleeping processes was waiting for ;i/o on the buffer pointed to by r2, so let's wake it up. ;d_que_proc: (remove from sleep queue) mov r6, [r1+pt_q_next] push r6 ;save ptr to next in sleep queue mov r5, [r1+pt_q_prev] mov [r5], r6 mov [r6+pt_q_prev], r5 ;en_que_proc: (add to run queue) mov r0, #run_queue mov [r1+pt_q_prev], r0 mov r5, [r0] mov [r1+pt_q_next], r5 lea r6, r1+pt_q_next mov [r5+pt_q_prev], r6 mov [r0], r1 adds.b [r1+pt_priority], #5 ;give it a bit of priority boost... ;need to check here for overrange on user priority setb request_context_switch ;get it running asap. ;continue looking for processes waiting on this i/o buffer, ;because there may be more than one, and we need to get them ;all into the run queue so that the context switcher can choose ;the one with the highest priority. pop r1 cmp r1, #0 bne wkintio_loop setb ea ret ;************************************************************** ;The context switcher is responsible for figuring out which process ;is supposed to run next (looks at pt_priority) and if a context ;change is required this is where it will happen. If any ;interrupt driven part of the kernel wants to make a particular ;process run, it must change the priority for that process and ;then set the software interrupt to run the context switcher later. ;For system calls that want to make their calling process sleep, ;they should jump to the ksleep routine, which will jump to ;"ctsw_begin" to allow something else to run. ;Note: the context switcher doesn't enforce preemptive multitasking... ;a timer routine must count how long the currently running process ;has be running since a context switch ("time_running" variable) ;and if it's being a hog the timer routine that detects it must ;lower the priority and set the software interrupt to cause this ;context switcher to make a different process run. The context ;switcher also depends on interrupt routines to change the ;"pt_priority" field of each process table entry, all the context ;switcher does is pick the process with highest priority. The ;currently running process is always switched out if there is ;something in the run queue, even if the item in the run queue ;has a lower priority. context_switch: pushu r0, r1, r2, r3, r4, r5, r6 ;save registers pop r2, r3, r4 ;get pc and psw pushu r2, r3, r4 ;save them on user stack pushu.b ds pushu.b es pushu.b ssel ctsw_begin: ;mov r4l, #'*' ;call cout ctsw_retry: ;find the process on the run queue with highest priority mov r0, #run_queue mov r1, [r0] beq ctsw_queue_empty ;run_queue is empty movs r2, #0 ;r2 is proc w/ highest priority mov r3l, #0 ;r3l is highest priority we've seen clr ea ;disable interrupts while we look at the ;run queue, since interrupt routines will be ;changing priorities to influence which process ;should be run next ctsw_loop: cmp [r1+pt_priority], r3l bcs ctsw_skip ;if we get here, this process is the highest ;priority so far, so forget about any other mov r3l, [r1+pt_priority] mov r2, r1 ctsw_skip: mov r1, [r1+pt_q_next] bne ctsw_loop ;now r2 points to the process which should be running... ;check to see if "current_proc" is valid... if it is zero then ;nothing was running (a sys call probably put the process to ;sleep). If "current_proc" has a valid process pointer, then we ;need to save that process's context (apart from the pushed regs) ;and put that process back into the run queue mov r0, #current_proc mov r1, [r0] beq ctsw_switch call save_context movs.w [r1+pt_wakeup], #0 ;wants to go back to user mode mov r0, #run_queue call en_que_proc ;put back into run queue ctsw_switch: ;now it's time to switch to the new process that is about ;to start running. mov r1, r2 call d_que_proc ;remove new proc from run queue call restore_context mov r0, #time_running movs.b [r0], #0 ;reset "time_running" mov r0, #current_proc mov [r0], r1 ;set "current_proc" to new process ;we may need to return to executing the process in user mode, ;or we may need to jump to a location in the kernel (to continue ;a system call). If pt_wakup for this process is not zero, then ;some kernel routine caused this process to sleep or be preempted ;and wants to do more work before finally returning to the user ;mode and running the program again. Usually this is due to a ;system call which needed to put the process to sleep while ;waiting for I/O. mov r6, [r1+pt_wakeup] setb ea beq ctsw_return movs.w [r1+pt_wakeup], #0 ;don't do this again unless set again jmp [r6] ctsw_return: clr request_context_switch popu.b ssel popu.b es popu.b ds popu r2, r3, r4 push r2, r3, r4 popu r0, r1, r2, r3, r4, r5, r6 reti ctsw_queue_empty: ;if we get here, there was no process waiting in the run ;queue. However, the currently running process isn't ;normally in the run queue, so check if there was a process ;running... if so reset its time slice and let it continue mov r0, #current_proc mov r1, [r0] beq ctsw_idle ;idle if nothing was running mov r0, #time_running movs.b [r0], #0 jmp ctsw_return ;if we get here, the run queue is empty and there in no ;process running now, so we just keep looping inside the ;context switcher until something appears in the run queue ctsw_idle: ;need to make a special user-mode idle process... someday ;that could put the processor into idle-mode to save power ;mov r4l, #' ' ;call cout jmp ctsw_retry ;This "ksleep" routine causes the current process to sleep, afterwhich ;it jumps to the context switcher, which tries to pick something else to ;run, if anything is ready. This code should only be called from ;within the service code of system calls, which often times need to ;make their calling process sleep. R3 should have a value to stuff ;into "pt_wait" for the process. ksleep should be CALLed, because ;it will arrange for the scheduler to return back to the kernel code ;which called ksleep. The call to ksleep should be followed by a ;single NOP, for jump alignment. Note, before calling ksleep some mechanism ;to wake the sleeping process back up again must be in place... there ;is nothing within ksleep which arranges for the process to become ;awake again, though in the case of i/o the value in r3 should cause it ;to wake up of the i/o interrupt routine checks for that value. ksleep: clr ea mov r0, #current_proc mov r1, [r0] ;get pointer to process ;cmp r1, #$FE10 ;beq ksleep_die movs.w [r0], #0 ;no current process anymore nop ksleep_any: clr ea mov [r1+pt_wait], r3 ;record what we're waiting for pop r5, r6 ;get return address adds r6, #1 ;make sure return addr is word addr and r6, #$FFFE mov [r1+pt_wakeup], r6 ;set return addr when it wakes up adds.b [r1+pt_priority], #3 ;increase priority a bit ;need to check here for overrange on user priority mov r0, #sleep_queue call en_que_proc ;add it to the sleep queue setb ea call save_context ;cmp r1, #$FE10 ;beq ksleep_die jmp ctsw_begin ;scheduler will pick a new ;process to run (or run idle loop) ksleep_die: jmp die syscall_sleep: ;all system calls should run at the same priority as the ;context switcher (level #1), but the code in paulmon2 ;sets the trap priority to #8... need to change that. mov.b pswh, #10000001q pushu r0, r1, r2, r3, r4, r5, r6 ;save registers pop r4, r5, r6 ;get pc and psw pushu r4, r5, r6 ;save them on user stack pushu.b ds pushu.b es pushu.b ssel ;r0 is number of ticks to sleep, from caller mov r4, #wake_process mov r2, #current_proc mov r5, [r2] ;"wake_process" will need to know which proc call add_event mov r3, #0 ;no other routines will need to know it's sleeping call ksleep nop popu.b ssel popu.b es popu.b ds popu r2, r3, r4 push r2, r3, r4 popu r0, r1, r2, r3, r4, r5, r6 reti ;do_events is supposed to call here when the process is supposed ;to wake up again... all we have to do it remove the process ;from the sleep queue, add it to the run queue, and set the ;software interrupt for the context switcher so it will select ;a new process to run, and it will likely be this one since we ;gave the priority a boost when it was put to sleep. nop wake_process: ;mov r4l, #'$' ;call cout ;r1 should point to event table entry created earlier clr ea mov r1, [r1+evt_arg1] ;now r1 points to process entry call d_que_proc ;remove it from the sleep queue mov r0, #run_queue call en_que_proc ;add it to the run queue setb ea ;should really test the priority of this waking process and ;the priority of the currently running one, and only do the ;context switch if the waking one has a higher priority setb request_context_switch ret ;transmit the character in r4l syscall_cout: mov.b pswh, #10000001q pushu r0, r1, r2, r3, r4, r5, r6 ;save registers pop r3, r5, r6 ;get pc and psw pushu r3, r5, r6 ;save them on user stack pushu.b ds pushu.b es pushu.b ssel sc_cout_begin: mov r0, #tx0_buf_head mov r2, #tx0_buf_tail clr ea mov r1, [r0] bne sc_cout2 ;if we get here, the transmit buffer is empty and the interrupt ;routine cleared the ti bit so no more interrupts are expected setb ti_0 ;give the transmitter a kick-start mov r1, #tx0_buf mov [r0], r1 ;restore buffer pointers to useful values mov [r2], r1 sc_cout2: adds r1, #1 cjne r1, #tx0_buf+tx0_buf_size, sc_cout3 mov r1, #tx0_buf sc_cout3: cmp r1, [r2] bne sc_cout4 ;if we get here, the buffer is full, so let's just wait ;in a loop... eventually this routine will be replaced ;with "write" which will put the process to sleep and set ;the pt_wait so the context switcher will return here when ;there is space available. How do we save the variables ;associated with the system call?? Push them onto the ;user's stack for this process? setb ea mov r3, #tx0_buf pushu r4 call ksleep nop popu r4 br sc_cout_begin ;try again now that the intr i/o woke us up sc_cout4: mov.b [r1], r4l ;put data into buffer mov [r0], r1 ;update pointer setb ea popu.b ssel popu.b es popu.b ds popu r2, r3, r4 push r2, r3, r4 popu r0, r1, r2, r3, r4, r5, r6 reti ;this function adds an event to the event queue. These ;parameters are required: ; r0 number of ticks until the event should occur ; r4 function to call when event happens (not zero!!) ; r5 optional argument ;the event list uses delta time between each entry, so that ;the timer interrupt will only have to deal with the front ;of the queue... but it makes adding an entry here much more ;difficult, but we only have to do this once whereas the ;timer will have to look at the queue however many times the ;evt_ticks value specifies. add_event: ;the first thing to do is find a empty place in the table ;to store this event mov r1, #event_table mov r2, #max_num_event clr ea aevt_find_slot: mov r3, [r1+evt_function] beq aevt_found_slot add r1, #evt_entry_size djnz r2, aevt_find_slot ;if we get here there were no slots available, which is a ;very bad thing... jmp kernel_panic aevt_found_slot: ;now that we've located the slot we'll use, let's dump the ;data into it before making a mistake and reusing one of the ;registers that are holding the input parameters mov [r1+evt_ticks], r0 ;store time value mov [r1+evt_function], r4 ;store function to be called mov [r1+evt_arg1], r5 ;store argument ;now we've got to add this event into the linked list in ;the correct time sequence position, and also subtract the ;sum of all the previous evt_ticks values from this one. mov r6, #event_queue mov r2, [r6] bne aevt_check_first ;if we get here, it's easy because the list is empty... mov [r6], r1 movs.w [r1+evt_next], #0 setb ea ret aevt_check_first: ;it may be the case that our new event is supposed to happen ;before the events that are already on the queue cmp r0, [r2+evt_ticks] bcc aevt_search mov [r6], r1 mov [r1+evt_next], r2 aevt_adjust: ;now that we've added an entry (at r1), we need to adjust the ;evt_ticks values stored in the entry that comes after it. mov r3, [r1+evt_next] ;r3 points to next event after ours mov r4, [r1+evt_ticks] sub [r3+evt_ticks], r4 setb ea ret aevt_search: ;r4 is the adjusted value of ticks for our event as we progress ;through the linked list mov r4, [r1+evt_ticks] aevt_search_loop: sub r4, [r2+evt_ticks] mov r3, [r2+evt_next] beq aevt_add cmp r4, [r3+evt_ticks] bcs aevt_add mov r2, r3 jmp aevt_search_loop aevt_add: ;r2 points to the event before the place where we will add ;and r3 points to the place after it in the list mov [r2+evt_next], r1 mov [r1+evt_next], r3 mov [r1+evt_ticks], r4 bne aevt_adjust ;adjust rest of list if it exists setb ea ret save_context: ;process pointer in r1 mov r0, usp mov [r1+pt_usp], r0 ;save user stack pointer ;save any directly addressed memory range ;assigned to this process ret restore_context: ;process pointer in r1 mov r0, [r1+pt_usp] mov usp, r0 ;restore usp to value for new process ;copy contents of directly addresses memory ;back if necessary ret ;This routine removes a process (at r1) from a process queue ;(run/sleep). This might be better as an assembler macro, but ;it's in a subroutine because of the difficulty of figuring out ;how to correctly manipulate the pointers of a doubly-linked list d_que_proc: mov r6, [r1+pt_q_next] ;<-- should be macro (begin) mov r5, [r1+pt_q_prev] mov [r5], r6 mov [r6+pt_q_prev], r5 ;<--- should be macro (end) ret ;this routine takes a process (at r1) and adds it to a queue, where ;the location of the head of the queue is in r0. This is more or ;less the opposite of "d_que_proc" except that the new process is ;always added to the head of the list. en_que_proc: mov [r1+pt_q_prev], r0 ;<-- should be macro (begin) mov r5, [r0] mov [r1+pt_q_next], r5 lea r6, r1+pt_q_next mov [r5+pt_q_prev], r6 mov [r0], r1 ;<--- should be macro (end) ret ;pt_usp equ 0 ;2 bytes, User Stack Pointer ;pt_q_next equ 2 ;2 bytes, ptr to next in queue ;pt_q_prev equ 4 ;2 bytes, ptr to prev in queue (+pt_q_next) ;this routine creates a new process, and returns a pointer to it ;in r1. If the process can't be created, r1 is zero. Data about ;the new process is required: ; r4 = location of executable program ; r5 = location for user stack (eventually malloc will do this) create_process: mov r1, #proc_table mov r2, #max_num_proc cproc_find_slot: mov r0, [r1+pt_usp] beq cproc_found_slot ;ok to use this process entry add r1, #pt_entry_size djnz r2, cproc_find_slot ;if we get here, there is no more room in the process ;table so we can't create a new process. movs r1, #0 ret cproc_found_slot: push r4, r5 ;first initialize the various kernel data required mov r0, #run_queue clr ea call en_que_proc ;add it to the run queue mov.b [r1+pt_priority], #100 ;start at priority 100 movs.w [r1+pt_wait], #0 movs.w [r1+pt_wakeup], #0 ;next we need to set up the process's context pop r4, r5 sub r5, #26 ;advance r5 to context storage area mov [r1+pt_usp], r5 movs.w [r5+], #0 ;segment select is 0 movs.w [r5+], #0 ;es is 0 movs.w [r5+], #0 ;ds is 0 movs.w [r5+], #0 ;psw is zero (user mode) movs.w [r5+], #0 ;run is page zero only (for now) mov [r5+], r4 ;set return addr to beginning ;could create initial values for the registers here... setb ea ret ;user stack while in kernel mode: ; byte ; offset Variable ; -2 r6 ; -4 r5 ; -6 r4 ; -8 r3 ; -10 r2 ; -12 r1 ; -14 r0 ; -16 program counter (lsw) ; -18 program counter (msb) ; -20 program status word ; -22 ds ; -24 es ; -26 ssel <-- usp points here poweron: mov r6, #$F000 clear_memory_loop: movs.w [r6+], #0 cjne r6, #0, clear_memory_loop setb p1.6 setb p1.7 mov r7, #sys_stack ;set sys stack pointer clr ea ;initialize serial port buffers mov r0, #rx0_buf_head mov.w [r0], #rx0_buf mov r0, #rx0_buf_tail mov.w [r0], #rx0_buf mov r0, #tx0_buf_head movs.w [r0], #0 mov r0, #tx0_buf_tail movs.w [r0], #0 mov r0, #rx1_buf_head mov.w [r0], #rx1_buf mov r0, #rx1_buf_tail mov.w [r0], #rx1_buf mov r0, #tx1_buf_head movs.w [r0], #0 mov r0, #tx1_buf_tail movs.w [r0], #0 ;clear serial port flags clr ri_0 clr ti_0 clr ri_1 clr ti_1 ;initialize interrupts mov.b swe, #255 ;allow all software interrupts mov.b ipa0, #10010000q ;priority #9: timer0 interrupt mov.b ipa4, #10011001q ;priority #9: uart0 rx and tx interrupts mov.b ipa5, #10011001q ;priority #9: uart1 rx and tx interrupts mov.b ieh, #00001111q ;enable uart interrupts ;set up timer 0 to 16-bit auto reload (10 ms) clr et0 ;no timer 0 interrupt yet clr tr0 ;stop timer 0 clr tf0 mov.b rtl0, #low 37798 ;set 10 ms rate mov.b rth0, #high 37798 movs.b tl0, #0 ;zero timer movs.b th0, #0 and.b tmod, #11110000q ;set for 16 bit auto reload ;now set up the variables so we're in idle mode mov r0, #current_proc movs.w [r0], #0 ;set no process running mov r0, #run_queue movs.b [r0], #0 ;all queues empty mov r0, #sleep_queue movs.b [r0], #0 mov r0, #time_running movs.b [r0], #0 ;initialize pt_usp of all processes to zero, so that ;there won't appear to be any processes nop clr_proc_tbl: mov r1, #proc_table mov r2, #max_num_proc clr_proc_tbl_loop: movs.w [r1+pt_usp], #0 add r1, #pt_entry_size djnz r2, clr_proc_tbl_loop clr_events: mov r1, #event_table mov r2, #max_num_event clr_events_loop: movs.w [r1+evt_function], #0 add r1, #evt_entry_size djnz r2, clr_events_loop ;now create process table entries for the test programs mov r4, #program1 mov r5, #$F000 call create_process ;create process for program1 mov r4, #program2 mov r5, #$EC00 call create_process ;create process for program2 mov r4, #program3 mov r5, #$E800 ;call create_process ;create process for program3 ;now how can we get into user mode for the first time??? ;normally the kernel is entered by an interrupt (hardware ;or software) or a trap (sys call), so we get back to a ;user program with reti... but the first time has to be ;handled a bit differently mov r1, #proc_table call d_que_proc ;remove new proc to run from run queue call restore_context mov r0, #time_running movs.b [r0], #0 ;reset "time_running" mov r0, #current_proc mov [r0], r1 ;set "current_proc" to new process popu.b ssel popu.b es popu.b ds popu r0, r1, r2 ;r0=psw, r1=pc_high, r2=pc_low popu r3, r4, r5 ;extract r0/r2 reg from stack popu r3, r4, r5, r6 ;extract r3/r6 reg from stack mov cs, r1l ;set code segment clr tf0 ;clear timer0 flag setb tr0 ;start timer0 setb et0 ;enable timer0 interrupt setb ea mov pswl, r0l mov pswh, r0h ;this makes the switch to user mode ;and lower priority to 0 (from max) jmp [r2] ;jump to the user program kernel_panic: die: clr ea clr ri_0 die_loop: jnb ri_0, die_loop clr ri_0 jmp $120 ;reboot! ;unix system calls: (which ones to try first??) ;process mgt: fork, exec/execve, exit, wait/wait4 ;file i/o: open, read, write, select, ioclt, lseek, close, fcntl ;signals: kill, signal, alarm, pause ;pipes: pipe, dup/dup2 ;network/ipc: socket, bind, connect/listen/accept, sendto, recvfrom ;network/ipc: socketpair, getsockname/getpeername, setsockopt/getsockopt ;filesystems: chroot/chdir, link, unlink, mkdir, rmdir, ;filesystems: chown/chmod, stat, rename, truncate ;************************************************************** ;** ** ;** User Programs ** ;** ** ;************************************************************** cout: trap #trap_cout ret newline: push r4l mov r4l, #13 call cout mov r4l, #10 call cout pop r4l ret pstr: PUSH r4 pstr1: MOVC r4l,[r6+] beq pstr2 AND R4L,#0x7F CALL cout BR pstr1 pstr2: POP r4 RET phex: phex8: PUSH.B acc RL.B R4L,#4 AND.B R4L,#15 ADD.B R4L,#246 BCC phex_b ADD.B R4L,#7 phex_b: ADD.B R4L,#58 CALL cout POP.B acc phex1: PUSH.B acc AND.B R4L,#15 ADD.B R4L,#246 BCC phex_c ADD.B R4L,#7 phex_c: ADD.B R4L,#58 CALL cout POP.B acc RET phex16: PUSH.B acc MOV.B R4L,dph CALL phex MOV.B R4L,dpl CALL phex POP.B acc RET ;program #1 prints "Paul" to serial port #0, with a ;fairly long delay between each printing program1: mov r6, #str_paul call pstr mov r0, #130 trap #trap_delay jmp program1 str_paul: db "Paul",0 pgm2_speed equ 10 ;number of ticks between led blinks nop ;program #2 blinks the LEDs and prints a "." to the ;serial port #0 after every sequence of blinks. program2: or.b p1cfga, #$C0 ;p1.6 and p1.7 outputs or.b p1cfgb, #$C0 pg2_loop: ;br pg2_skip_blink setb p1.6 setb p1.7 mov r0, #pgm2_speed trap #trap_delay clr p1.6 setb p1.7 mov r0, #pgm2_speed trap #trap_delay setb p1.6 setb p1.7 mov r0, #pgm2_speed trap #trap_delay setb p1.6 clr p1.7 mov r0, #pgm2_speed trap #trap_delay setb p1.6 setb p1.7 pg2_skip_blink: mov r4l, #'.' call cout br pg2_loop program3: mov r4l, #'3' call cout ;mov r6, #test_str ;call pstr jmp program3 test_str: db "This_is_a_test", 0