# ! This file contains the assembler startup code for Minix and the 16-bit ! interrupt handlers. It cooperates with cstart.c to set up a good ! environment for main(). ! This file is part of the lowest layer of the MINIX kernel. The other part ! is "proc.c". The lowest layer does process switching and message handling. ! Every transition to the kernel goes through this file. Transitions are ! caused by sending/receiving messages and by most interrupts. (RS232 ! interrupts may be handled in the file "rs2.s" and then they rarely enter ! the kernel.) ! Transitions to the kernel may be nested. The initial entry may be with a ! system call, exception or hardware interrupt; reentries may only be made ! by hardware interrupts. The count of reentries is kept in "k_reenter". ! It is important for deciding whether to switch to the kernel stack and ! for protecting the message passing code in "proc.c". ! For the message passing trap, most of the machine state is saved in the ! proc table. (Some of the registers need not be saved.) Then the stack is ! switched to "k_stack", and interrupts are reenabled. Finally, the system ! call handler (in C) is called. When it returns, interrupts are disabled ! again and the code falls into the restart routine, to finish off held-up ! interrupts and run the process or task whose pointer is in "proc_ptr". ! Hardware interrupt handlers do the same, except (1) The entire state must ! be saved. (2) There are too many handlers to do this inline, so the save ! routine is called. A few cycles are saved by pushing the address of the ! appropiate restart routine for a return later. (3) A stack switch is ! avoided when the stack is already switched. (4) The (master) 8259 interrupt ! controller is reenabled centrally in save(). (5) Each interrupt handler ! masks its interrupt line using the 8259 before enabling (other unmasked) ! interrupts, and unmasks it after servicing the interrupt. This limits the ! nest level to the number of lines and protects the handler from itself. ! For communication with the boot monitor at startup time some constant ! data are compiled into the beginning of the text segment. This facilitates ! reading the data at the start of the boot process, since only the first ! sector of the file needs to be read. ! Some data storage is also allocated at the end of this file. This data ! will be at the start of the data segment of the kernel and will be read ! and modified by the boot monitor before the kernel starts. #include <minix/config.h> #include <minix/const.h> #include <minix/com.h> #include "const.h" #include "sconst.h" #include "protect.h" ! The external entry points into this file are: ! Note: in assembly language the .define statement applied to a function name ! is loosely equivalent to a prototype in C code -- it makes it possible to ! link to an entity declared in the assembly code but does not create ! the entity. .define _idle_task ! executed when there is no work .define _int00 ! handlers for traps and exceptions .define _int01 .define _int02 .define _int03 .define _int04 .define _int05 .define _int06 .define _int07 .define _hwint00 ! handlers for hardware interrupts .define _hwint01 .define _hwint02 .define _hwint03 .define _hwint04 .define _hwint05 .define _hwint06 .define _hwint07 .define _hwint08 .define _hwint09 .define _hwint10 .define _hwint11 .define _hwint12 .define _hwint13 .define _hwint14 .define _hwint15 .define _restart ! start running a task or process .define save ! save the machine state in the proc table .define _s_call ! process or task wants to send or receive a message ! Imported functions. .extern _cstart .extern _main .extern _exception .extern _interrupt .extern _sys_call .extern _unhold .extern klib_init_prot .extern real2prot ! Exported variables. ! Note: when used with a variable the .define does not reserve storage, ! it makes the name externally visible so it may be linked to. .define kernel_ds .define begbss .define begdata .define _sizes ! Imported variables. .extern kernel_cs .extern _gdt .extern _code_base .extern _data_base .extern _held_head .extern _k_reenter .extern _pc_at .extern _proc_ptr .extern _protected_mode .extern _ps_mca .extern _irq_table .text !*===========================================================================* !* MINIX * !*===========================================================================* MINIX: ! this is the entry point for the MINIX kernel jmp over_kernel_ds ! skip over the next few bytes .data2 CLICK_SHIFT ! for the monitor: memory granularity kernel_ds: .data2 0x0024 ! boot monitor flags: (later kernel DS) ! make stack, will return over_kernel_ds: ! Set up a C stack frame on the monitor stack. (The monitor sets cs and ds ! right. The ss register still references the monitor data segment.) push bp mov bp, sp push si push di mov cx, 4(bp) ! monitor code segment test cx, cx ! nonzero if return possible jz noret inc _mon_return noret: mov _mon_ss, ss ! save stack location for later return mov _mon_sp, sp ! Locate boot parameters, set up kernel segment registers and stack. mov bx, 6(bp) ! boot parameters offset mov dx, 8(bp) ! boot parameters length mov ax, ds ! kernel data mov es, ax mov ss, ax mov sp, #k_stktop ! set sp to point to the top of kernel stack ! Real mode needs to get kernel DS from the code segment. Protected mode ! needs CS in the jump back to real mode. cseg mov kernel_cs, cs cseg mov kernel_ds, ds ! Call C startup code to set up a proper environment to run main(). push dx push bx push _mon_ss push cx push ds push cs call _cstart ! cstart(cs, ds, mcs, mds, parmoff, parmlen) add sp, #6*2 cmp _protected_mode, #0 jz nosw ! ok to switch to protected mode? call klib_init_prot ! initialize klib functions for protected mode call real2prot ! switch to protected mode push #0 ! set flags to known good state popf ! especially, clear nested task and int enable nosw: jmp _main ! main() !*===========================================================================* !* interrupt handlers * !*===========================================================================* !*===========================================================================* !* hwint00 - 07 * !*===========================================================================* ! Note this is a macro, it looks like a subroutine. #define hwint_master(irq) \ call save /* save interrupted process state */;\ inb INT_CTLMASK ;\ orb al, *[1<<irq] ;\ outb INT_CTLMASK /* disable the irq */;\ movb al, *ENABLE ;\ outb INT_CTL /* reenable master 8259 */;\ sti /* enable interrupts */;\ mov ax, *irq ;\ push ax /* irq */;\ call @_irq_table + 2*irq /* ax = (*irq_table[irq])(irq) */;\ pop cx ;\ cli /* disable interrupts */;\ test ax, ax /* need to reenable irq? */;\ jz 0f ;\ inb INT_CTLMASK ;\ andb al, *~[1<<irq] ;\ outb INT_CTLMASK /* enable the irq */;\ 0: ret /* restart (another) process */ ! Each of these entry points is an expansion of the hwint_master macro _hwint00: ! Interrupt routine for irq 0 (the clock). hwint_master(0) _hwint01: ! Interrupt routine for irq 1 (keyboard) hwint_master(1) _hwint02: ! Interrupt routine for irq 2 (cascade!) hwint_master(2) _hwint03: ! Interrupt routine for irq 3 (second serial) hwint_master(3) _hwint04: ! Interrupt routine for irq 4 (first serial) hwint_master(4) _hwint05: ! Interrupt routine for irq 5 (XT winchester) hwint_master(5) _hwint06: ! Interrupt routine for irq 6 (floppy) hwint_master(6) _hwint07: ! Interrupt routine for irq 7 (printer) hwint_master(7) !*===========================================================================* !* hwint08 - 15 * !*===========================================================================* ! Note this is a macro, it looks like a subroutine. #define hwint_slave(irq) \ call save /* save interrupted process state */;\ inb INT2_CTLMASK ;\ orb al, *[1<<[irq-8]] ;\ outb INT2_CTLMASK /* disable the irq */;\ movb al, *ENABLE ;\ outb INT_CTL /* reenable master 8259 */;\ jmp .+2 /* delay */;\ outb INT2_CTL /* reenable slave 8259 */;\ sti /* enable interrupts */;\ mov ax, *irq ;\ push ax /* irq */;\ call @_irq_table + 2*irq /* eax = (*irq_table[irq])(irq) */;\ pop cx ;\ cli /* disable interrupts */;\ test ax, ax /* need to reenable irq? */;\ jz 0f ;\ inb INT2_CTLMASK ;\ andb al, *~[1<<[irq-8]] ;\ outb INT2_CTLMASK /* enable the irq */;\ 0: ret /* restart (another) process */ ! Each of these entry points is an expansion of the hwint_slave macro _hwint08: ! Interrupt routine for irq 8 (realtime clock) hwint_slave(8) _hwint09: ! Interrupt routine for irq 9 (irq 2 redirected) hwint_slave(9) _hwint10: ! Interrupt routine for irq 10 hwint_slave(10) _hwint11: ! Interrupt routine for irq 11 hwint_slave(11) _hwint12: ! Interrupt routine for irq 12 hwint_slave(12) _hwint13: ! Interrupt routine for irq 13 (FPU exception) hwint_slave(13) _hwint14: ! Interrupt routine for irq 14 (AT winchester) hwint_slave(14) _hwint15: ! Interrupt routine for irq 15 hwint_slave(15) !*===========================================================================* !* save * !*===========================================================================* save: ! save the machine state in the proc table. ! In protected mode a jump to p_save is patched over the following ! code during initialization. cld ! set direction flag to a known value push ds push si cseg mov ds,kernel_ds incb _k_reenter ! from -1 if not reentering jnz push_current_stack ! stack is already kernel stack mov si,_proc_ptr mov AXREG(si),ax mov BXREG(si),bx mov CXREG(si),cx mov DXREG(si),dx pop SIREG(si) mov DIREG(si),di mov BPREG(si),bp mov ESREG(si),es pop DSREG(si) pop bx ! return adr pop PCREG(si) pop CSREG(si) pop PSWREG(si) mov SPREG(si),sp mov SSREG(si),ss mov dx,ds mov ss,dx mov sp,#k_stktop mov ax,#_restart ! build return address for interrupt handler push ax stack_switched: mov es,dx jmp (bx) push_current_stack: push es push bp push di push dx push cx push bx push ax mov bp,sp mov bx,18(bp) ! get the return adr; it becomes junk on stack mov ax,#restart1 push ax mov dx,ss mov ds,dx jmp stack_switched !*===========================================================================* !* s_call * !*===========================================================================* ! This is real mode version. Alternate (_p_s_call) will be used in ! protected mode _s_call: ! System calls are vectored here. ! Do save routine inline for speed, ! but do not save ax, bx, cx, dx, ! since C does not require preservation, ! and ax returns the result code anyway. ! Regs bp, si, di get saved by sys_call as ! well, but it is impractical not to preserve ! them here, in case context gets switched. ! Some special-case code in pick_proc() ! could avoid this. cld ! set direction flag to a known value push ds push si cseg mov ds,kernel_ds incb _k_reenter mov si,_proc_ptr pop SIREG(si) mov DIREG(si),di mov BPREG(si),bp mov ESREG(si),es pop DSREG(si) pop PCREG(si) pop CSREG(si) pop PSWREG(si) mov SPREG(si),sp mov SSREG(si),ss mov dx,ds mov es,dx mov ss,dx ! interrupt handlers may not make system calls mov sp,#k_stktop ! so stack is not already switched ! end of inline save ! now set up parameters for C routine sys_call push bx ! pointer to user message push ax ! src/dest push cx ! SEND/RECEIVE/BOTH sti ! allow SWITCHER to be interrupted call _sys_call ! sys_call(function, src_dest, m_ptr) ! caller is now explicitly in proc_ptr mov AXREG(si),ax ! sys_call MUST PRESERVE si cli ! Fall into code to restart proc/task running. !*===========================================================================* !* restart * !*===========================================================================* _restart: ! Flush any held-up interrupts. ! This reenables interrupts, so the current interrupt handler may reenter. ! This does not matter, because the current handler is about to exit and no ! other handlers can reenter since flushing is only done when k_reenter == 0. ! In protected mode a jump to p_restart is patched over the following ! code during initialization. cmp _held_head,#0 ! do fast test to usually avoid function call jz over_call_unhold call _unhold ! this is rare so overhead is acceptable over_call_unhold: mov si,_proc_ptr decb _k_reenter mov ax,AXREG(si) ! start restoring registers from proc table ! could make AXREG == 0 to use lodw here mov bx,BXREG(si) mov cx,CXREG(si) mov dx,DXREG(si) mov di,DIREG(si) mov bp,BPREG(si) mov es,ESREG(si) mov ss,SSREG(si) mov sp,SPREG(si) push PSWREG(si) ! fake interrupt stack frame push CSREG(si) push PCREG(si) ! could put si:ds together to use ! lds si,SIREG(si) push DSREG(si) mov si,SIREG(si) pop ds iret restart1: decb _k_reenter pop ax pop bx pop cx pop dx pop di pop bp pop es pop si pop ds add sp,#2 ! skip return adr iret !*===========================================================================* !* int00-07 * !*===========================================================================* ! These are entry points for exceptions (processor generated interrupts, ! usually caused by error conditions such as an attempt to divide by zero) _int00: ! interrupt through vector 0 push ax movb al,#0 jmp exception _int01: ! interrupt through vector 1, etc push ax movb al,#1 jmp exception _int02: push ax movb al,#2 jmp exception _int03: push ax movb al,#3 jmp exception _int04: push ax movb al,#4 jmp exception _int05: push ax movb al,#5 jmp exception _int06: push ax movb al,#6 jmp exception _int07: push ax movb al,#7 !jmp exception exception: cseg movb ex_number,al ! it is cumbersome to get this into dseg pop ax call save cseg push ex_number ! high byte is constant 0 call _exception ! do whatever is necessary (sti only if safe) add sp,#2 cli ret !*===========================================================================* !* level0_call * !*===========================================================================* _level0_call: call save jmp @_level0_func !*===========================================================================* !* idle_task * !*===========================================================================* _idle_task: ! executed when there is no work jmp _idle_task ! a "hlt" before this fails in protected mode !*===========================================================================* !* data * !*===========================================================================* ! NB some variables are stored in code segment. ex_number: ! exception number .space 2 !*===========================================================================* !* variants for 286 protected mode * !*===========================================================================* ! Most routines are different in 286 protected mode. ! The only essential difference is that an interrupt in protected mode ! (usually) switches the stack, so there is less to do in software. ! These functions are reached along jumps patched in by klib_init_prot(): .define p_restart ! replaces _restart .define p_save ! replaces save ! These exception and software-interrupt handlers are enabled by the new ! interrupt vector table set up in protect.c: .define _divide_error ! _int00 .define _single_step_exception ! _int01 .define _nmi ! _int02 .define _breakpoint_exception ! _int03 .define _overflow ! _int04 .define _bounds_check ! _int05 .define _inval_opcode ! _int06 .define _copr_not_available ! _int07 .define _double_fault ! (286 trap) .define _copr_seg_overrun ! (etc) .define _inval_tss .define _segment_not_present .define _stack_exception .define _general_protection .define _p_s_call ! _s_call .define _level0_call ! The hardware interrupt handlers need not be altered apart from putting ! them in the new table (save() handles the differences). ! Some of the intxx handlers (those for exceptions which do not push an ! error code) need not have been replaced, but the names here are better. #include "protect.h" /* Selected 286 tss offsets. */ #define TSS2_S_SP0 2 ! imported variables .extern _tss .extern _level0_func !*===========================================================================* !* p_save * !*===========================================================================* ! Save for 286 protected mode. ! This is much simpler than for 8086 mode, because the stack already points ! into process table, or has already been switched to the kernel stack. p_save: cld ! set direction flag to a known value pusha ! save "general" registers push ds ! save ds push es ! save es mov dx,ss ! ss is kernel data segment mov ds,dx ! load rest of kernel segments mov es,dx mov bp,sp ! prepare to return incb _k_reenter ! from -1 if not reentering jnz set_p1_restart ! stack is already kernel stack mov sp,#k_stktop push #p_restart ! build return address for interrupt handler jmp @RETADR-P_STACKBASE(bp) set_p1_restart: push #p1_restart jmp @RETADR-P_STACKBASE(bp) !*===========================================================================* !* p_s_call * !*===========================================================================* _p_s_call: cld ! set direction flag to a known value sub sp,#6*2 ! skip RETADR, ax, cx, dx, bx, st push bp ! stack already points into process table push si push di push ds push es mov dx,ss mov ds,dx mov es,dx incb _k_reenter mov si,sp ! assumes P_STACKBASE == 0 mov sp,#k_stktop ! end of inline save sti ! allow SWITCHER to be interrupted ! now set up parameters for C routine sys_call push bx ! pointer to user message push ax ! src/dest push cx ! SEND/RECEIVE/BOTH call _sys_call ! sys_call(function, src_dest, m_ptr) ! caller is now explicitly in proc_ptr mov AXREG(si),ax ! sys_call MUST PRESERVE si cli ! Fall into code to restart proc/task running. p_restart: ! Flush any held-up interrupts. ! This reenables interrupts, so the current interrupt handler may reenter. ! This does not matter, because the current handler is about to exit and no ! other handlers can reenter since flushing is only done when k_reenter == 0. cmp _held_head,#0 ! do fast test to usually avoid function call jz p_over_call_unhold call _unhold ! this is rare so overhead is acceptable p_over_call_unhold: mov si,_proc_ptr lldt P_LDT_SEL(si) ! enable segment descriptors for task lea ax,P_STACKTOP(si) ! arrange for next interrupt mov _tss+TSS2_S_SP0,ax ! to save state in process table mov sp,si ! assumes P_STACKBASE == 0 p1_restart: decb _k_reenter pop es pop ds popa add sp,#2 ! skip return adr iret ! continue process !*===========================================================================* !* exception handlers * !*===========================================================================* _divide_error: push #DIVIDE_VECTOR jmp p_exception _single_step_exception: push #DEBUG_VECTOR jmp p_exception _nmi: push #NMI_VECTOR jmp p_exception _breakpoint_exception: push #BREAKPOINT_VECTOR jmp p_exception _overflow: push #OVERFLOW_VECTOR jmp p_exception _bounds_check: push #BOUNDS_VECTOR jmp p_exception _inval_opcode: push #INVAL_OP_VECTOR jmp p_exception _copr_not_available: push #COPROC_NOT_VECTOR jmp p_exception _double_fault: push #DOUBLE_FAULT_VECTOR jmp errexception _copr_seg_overrun: push #COPROC_SEG_VECTOR jmp p_exception _inval_tss: push #INVAL_TSS_VECTOR jmp errexception _segment_not_present: push #SEG_NOT_VECTOR jmp errexception _stack_exception: push #STACK_FAULT_VECTOR jmp errexception _general_protection: push #PROTECTION_VECTOR jmp errexception !*===========================================================================* !* p_exception * !*===========================================================================* ! This is called for all exceptions which do not push an error code. p_exception: sseg pop ds_ex_number call p_save jmp p1_exception !*===========================================================================* !* errexception * !*===========================================================================* ! This is called for all exceptions which push an error code. errexception: sseg pop ds_ex_number sseg pop trap_errno call p_save p1_exception: ! Common for all exceptions. push ds_ex_number call _exception add sp,#2 cli ret !*===========================================================================* !* data * !*===========================================================================* ! These declarations assure that storage will be allocated at the very ! beginning of the kernel data section, so the boot monitor can be easily ! told how to patch these locations. Note that the magic number is put ! here by the compiler, but will be read by, and then overwritten by, ! the boot monitor. When the kernel starts the sizes array will be ! found here, as if it had been initialized by the compiler. .data begdata: _sizes: ! sizes of kernel, mm, fs filled in by boot .data2 0x526F ! this must be the first data entry (magic #) .space 16*2*2-2 ! monitor uses previous 2 words and this space ! extra space allows for additional servers .bss begbss: k_stack: .space K_STACK_BYTES ! kernel stack k_stktop: ! top of kernel stack ds_ex_number: .space 2 trap_errno: .space 2