1057 lines
29 KiB
ArmAsm
Executable file
1057 lines
29 KiB
ArmAsm
Executable file
#
|
|
#include <minix/config.h>
|
|
#include <minix/const.h>
|
|
#include "const.h"
|
|
#include "sconst.h"
|
|
#include "protect.h"
|
|
|
|
! This file contains a number of assembly code utility routines needed by the
|
|
! kernel. They are:
|
|
|
|
.define _monitor ! exit Minix and return to the monitor
|
|
.define _int86 ! make an 8086 interrupt call
|
|
.define real2prot ! switch from real to protected mode
|
|
.define prot2real ! switch from protected to real mode
|
|
.define _cp_mess ! copies messages from source to destination
|
|
.define _exit ! dummy for library routines
|
|
.define __exit ! dummy for library routines
|
|
.define ___exit ! dummy for library routines
|
|
.define .fat, .trp ! dummies for library routines
|
|
.define _phys_insw ! transfer data from (disk controller) port to memory
|
|
.define _phys_insb ! likewise byte by byte
|
|
.define _phys_outsw ! transfer data from memory to (disk controller) port
|
|
.define _phys_outsb ! likewise byte by byte
|
|
.define _enable_irq ! enable an irq at the 8259 controller
|
|
.define _disable_irq ! disable an irq
|
|
.define _phys_copy ! copy data from anywhere to anywhere in memory
|
|
.define _mem_rdw ! copy one word from [segment:offset]
|
|
.define _reset ! reset the system
|
|
.define _mem_vid_copy ! copy data to video ram
|
|
.define _vid_vid_copy ! move data in video ram
|
|
.define _idle_task ! executed when there is no work
|
|
.define _level0 ! call a function at level 0
|
|
.define klib_init_prot ! initialize klib functions for protected mode
|
|
|
|
! The routines only guarantee to preserve the registers the C compiler
|
|
! expects to be preserved (si, di, bp, sp, segment registers, and direction
|
|
! bit in the flags), though some of the older ones preserve bx, cx and dx.
|
|
|
|
#define DS_286_OFFSET DS_286_INDEX*DESC_SIZE
|
|
#define ES_286_OFFSET ES_286_INDEX*DESC_SIZE
|
|
# define EM_XFER_FUNC 0x87
|
|
#define JMP_OPCODE 0xE9 /* opcode used for patching */
|
|
#define OFF_MASK 0x000F /* offset mask for phys_b -> hclick:offset */
|
|
#define HCHIGH_MASK 0x0F /* h/w click mask for low byte of hi word */
|
|
#define HCLOW_MASK 0xF0 /* h/w click mask for low byte of low word */
|
|
|
|
! Exported variables
|
|
.extern kernel_cs
|
|
|
|
.text
|
|
!*===========================================================================*
|
|
!* monitor *
|
|
!*===========================================================================*
|
|
! PUBLIC void monitor();
|
|
! Return to the monitor.
|
|
|
|
_monitor:
|
|
call prot2real ! switch to real mode
|
|
mov sp, _mon_sp ! restore monitor stack pointer
|
|
mov bx, _mon_ss ! monitor data segment
|
|
mov ds, bx
|
|
mov es, bx
|
|
mov ss, bx
|
|
pop di
|
|
pop si
|
|
pop bp
|
|
retf ! return to the monitor
|
|
|
|
|
|
!*===========================================================================*
|
|
!* int86 *
|
|
!*===========================================================================*
|
|
! PUBLIC void int86();
|
|
_int86: ! make an 8086 interrupt call.
|
|
push bp
|
|
push si
|
|
push di ! save C variable registers
|
|
pushf ! save flags
|
|
|
|
call int86 ! make the actual call
|
|
|
|
popf ! restore flags
|
|
pop di ! restore C registers
|
|
pop si
|
|
pop bp
|
|
ret
|
|
|
|
! Do an 8086 interrupt from protected mode
|
|
p_int86:
|
|
push bp
|
|
push si
|
|
push di ! save C variable registers
|
|
pushf ! save flags
|
|
|
|
cli ! no interruptions
|
|
inb INT2_CTLMASK
|
|
movb ah, al
|
|
inb INT_CTLMASK
|
|
push ax ! save interrupt masks
|
|
mov ax, _irq_use ! map of in-use IRQs
|
|
and ax, #~[1<<CLOCK_IRQ] ! keep the clock ticking
|
|
outb INT_CTLMASK ! enable all unused IRQs and vv.
|
|
movb al, ah
|
|
outb INT2_CTLMASK
|
|
|
|
call prot2real ! switch to real mode
|
|
|
|
xor ax, ax
|
|
mov es, ax ! vector & BIOS data segments
|
|
eseg mov 0x046C, ax ! clear BIOS clock counter
|
|
eseg mov 0x046E, ax
|
|
|
|
sti ! enable interrupts
|
|
call int86 ! make the actual call
|
|
cli ! disable interrupts
|
|
|
|
xor ax, ax
|
|
mov es, ax
|
|
eseg mov ax, 0x046C
|
|
add _lost_ticks, ax ! record lost clock ticks
|
|
|
|
call real2prot ! back to protected mode
|
|
|
|
pop ax ! restore interrupt masks
|
|
outb INT_CTLMASK
|
|
movb al, ah
|
|
outb INT2_CTLMASK
|
|
|
|
popf ! restore flags
|
|
pop di ! restore C registers
|
|
pop si
|
|
pop bp
|
|
ret
|
|
|
|
int86:
|
|
mov bp, #_reg86 ! address of parameter block
|
|
movb al, #0xCD ! INT instruction
|
|
movb ah, (bp) ! Interrupt number?
|
|
testb ah, ah
|
|
jnz 0f ! nonzero if INT, otherwise far call
|
|
push cs
|
|
push #intret+2 ! far return address
|
|
push 6(bp)
|
|
push 4(bp) ! far driver address
|
|
mov ax, #0x90CB ! RETF; NOP
|
|
0: cseg mov intret, ax ! patch `INT n` or `RETF; NOP` into code
|
|
jmp .+2 ! clear instruction queue
|
|
|
|
mov ds, 8(bp) ! Load parameters
|
|
mov es, 10(bp)
|
|
mov ax, 12(bp) ! (sorry, only ax set, not eax)
|
|
mov bx, 16(bp)
|
|
mov cx, 20(bp)
|
|
mov dx, 24(bp)
|
|
mov si, 28(bp)
|
|
mov di, 32(bp)
|
|
mov bp, 36(bp)
|
|
|
|
intret: int 0xFF ! do the interrupt or far call
|
|
|
|
push bp
|
|
pushf
|
|
mov bp, #_reg86 ! address of parameter block
|
|
pop (bp) ! eflags
|
|
mov 8(bp), ds
|
|
mov 10(bp), es
|
|
mov 12(bp), ax
|
|
mov 16(bp), bx
|
|
mov 20(bp), cx
|
|
mov 24(bp), dx
|
|
mov 28(bp), si
|
|
mov 32(bp), di
|
|
pop 36(bp) ! bp
|
|
|
|
mov ax, ss
|
|
mov ds, ax ! restore ds and es
|
|
mov es, ax
|
|
ret
|
|
|
|
|
|
!*===========================================================================*
|
|
!* real2prot *
|
|
!*===========================================================================*
|
|
! Switch from real to protected mode.
|
|
real2prot:
|
|
lgdt _gdt+GDT_SELECTOR ! set global descriptor table
|
|
smsw ax
|
|
mov msw, ax ! save real mode msw
|
|
orb al, #0x01 ! set PE (protection enable) bit
|
|
lmsw ax ! set msw, enabling protected mode
|
|
|
|
jmpf csinit, CS_SELECTOR ! set code segment selector
|
|
csinit:
|
|
mov ax, #DS_SELECTOR ! set data selectors
|
|
mov ds, ax
|
|
mov es, ax
|
|
mov ss, ax
|
|
lidt _gdt+IDT_SELECTOR ! set interrupt vectors
|
|
andb _gdt+TSS_SELECTOR+DESC_ACCESS, #~0x02 ! clear TSS busy bit
|
|
mov ax, #TSS_SELECTOR
|
|
ltr ax ! set TSS register
|
|
|
|
movb ah, #0x02
|
|
jmp gate_A20 ! enable the A20 address line
|
|
|
|
|
|
!*===========================================================================*
|
|
!* prot2real *
|
|
!*===========================================================================*
|
|
! Switch from protected to real mode.
|
|
prot2real:
|
|
mov save_sp, sp ! save stack pointer
|
|
cmp _processor, #386 ! is this a 386?
|
|
jae p2r386
|
|
p2r286:
|
|
xor ax, ax
|
|
mov _gdt+ES_286_OFFSET+DESC_BASE, ax
|
|
movb _gdt+ES_286_OFFSET+DESC_BASE_MIDDLE, al
|
|
movb _gdt+ES_286_OFFSET+DESC_BASE_HIGH, al
|
|
mov ax, #ES_286_SELECTOR
|
|
mov es, ax ! BIOS data segment
|
|
eseg mov 0x0467, #real ! set return from shutdown address
|
|
cseg mov ax, kernel_cs
|
|
eseg mov 0x0469, ax
|
|
movb al, #0x8F
|
|
outb 0x70 ! select CMOS byte 0x0F (disable NMI)
|
|
jmp .+2
|
|
movb al, #0x0A
|
|
outb 0x71 ! set shutdown code to 0x0A "jump far"
|
|
jmp p_reset ! cause a processor shutdown
|
|
p2r386:
|
|
lidt idt_vectors ! real mode interrupt vectors
|
|
push _gdt+CS_SELECTOR+0
|
|
push _gdt+DS_SELECTOR+0 ! save CS and DS limits
|
|
mov _gdt+CS_SELECTOR+0, #0xFFFF
|
|
mov _gdt+DS_SELECTOR+0, #0xFFFF ! set 64k limits
|
|
jmpf cs64k, CS_SELECTOR ! reload selectors
|
|
cs64k: mov ax, #DS_SELECTOR
|
|
mov ds, ax
|
|
mov es, ax
|
|
mov ss, ax
|
|
pop _gdt+DS_SELECTOR+0
|
|
pop _gdt+CS_SELECTOR+0 ! restore CS and DS limits
|
|
.data1 0x0F,0x20,0xC0 ! mov eax, cr0
|
|
mov ax, msw ! restore real mode (16 bits) msw
|
|
.data1 0x0F,0x22,0xC0 ! mov cr0, eax
|
|
.data1 0xEA ! jmpf real, "kernel_cs"
|
|
.data2 real
|
|
kernel_cs:
|
|
.data2 0
|
|
real:
|
|
cseg mov ax, kernel_ds ! reload data segment registers
|
|
mov ds, ax
|
|
mov es, ax
|
|
mov ss, ax
|
|
mov sp, save_sp ! restore stack
|
|
|
|
xorb ah, ah
|
|
!jmp gate_A20 ! disable the A20 address line
|
|
|
|
! Enable (ah = 0x02) or disable (ah = 0x00) the A20 address line.
|
|
gate_A20:
|
|
cmp _ps_mca, #0 ! PS/2 bus?
|
|
jnz gate_PS_A20
|
|
call kb_wait
|
|
movb al, #0xD1 ! Tell keyboard that a command is coming
|
|
outb 0x64
|
|
call kb_wait
|
|
movb al, #0xDD ! 0xDD = A20 disable code if ah = 0x00
|
|
orb al, ah ! 0xDF = A20 enable code if ah = 0x02
|
|
outb 0x60
|
|
call kb_wait
|
|
movb al, #0xFF ! Pulse output port
|
|
outb 0x64
|
|
call kb_wait ! Wait for the A20 line to settle down
|
|
ret
|
|
kb_wait:
|
|
inb 0x64
|
|
testb al, #0x02 ! Keyboard input buffer full?
|
|
jnz kb_wait ! If so, wait
|
|
ret
|
|
|
|
gate_PS_A20: ! The PS/2 can twiddle A20 using port A
|
|
inb 0x92 ! Read port A
|
|
andb al, #0xFD
|
|
orb al, ah ! Set A20 bit to the required state
|
|
outb 0x92 ! Write port A
|
|
jmp .+2 ! Small delay
|
|
A20ok: inb 0x92 ! Check port A
|
|
andb al, #0x02
|
|
cmpb al, ah ! A20 line settled down to the new state?
|
|
jne A20ok ! If not then wait
|
|
ret
|
|
|
|
|
|
!*===========================================================================*
|
|
!* cp_mess *
|
|
!*===========================================================================*
|
|
! PUBLIC void cp_mess(int src, phys_clicks src_clicks, vir_bytes src_offset,
|
|
! phys_clicks dst_clicks, vir_bytes dst_offset);
|
|
! This routine makes a fast copy of a message from anywhere in the address
|
|
! space to anywhere else. It also copies the source address provided as a
|
|
! parameter to the call into the first word of the destination message.
|
|
!
|
|
! Note that the message size, "Msize" is in WORDS (not bytes) and must be set
|
|
! correctly. Changing the definition of message in the type file and not
|
|
! changing it here will lead to total disaster.
|
|
|
|
_cp_mess:
|
|
cld
|
|
push es ! save es
|
|
push ds ! save ds
|
|
mov bx,sp ! index off bx because machine cannot use sp
|
|
push si ! save si
|
|
push di ! save di
|
|
|
|
mov ax,12(bx) ! destination click
|
|
#if HCLICK_SHIFT > CLICK_SHIFT
|
|
#error /* Small click sizes are not supported (right shift will lose bits). */
|
|
#endif
|
|
#if HCLICK_SHIFT < CLICK_SHIFT
|
|
movb cl,#CLICK_SHIFT-HCLICK_SHIFT
|
|
shl ax,cl ! destination segment
|
|
#endif
|
|
mov es,ax
|
|
mov di,14(bx) ! offset of destination message
|
|
|
|
! Be careful not to destroy ds before we are finished with the bx pointer.
|
|
! We are using bx and not the more natural bp to save pushing bp.
|
|
|
|
mov ax,6(bx) ! process number of sender
|
|
mov si,10(bx) ! offset of source message
|
|
mov bx,8(bx) ! source click (finished with bx as a pointer)
|
|
#if HCLICK_SHIFT < CLICK_SHIFT
|
|
shl bx,cl ! source segment
|
|
#endif
|
|
mov ds,bx
|
|
|
|
stos ! copy process number of sender to dest message
|
|
add si,*2 ! do not copy first word
|
|
mov cx,*Msize-1 ! remember, first word does not count
|
|
rep ! iterate cx times to copy 11 words
|
|
movs ! copy the message
|
|
pop di ! restore di
|
|
pop si ! restore si
|
|
pop ds ! restore ds
|
|
pop es ! restore es
|
|
ret ! that is all folks!
|
|
|
|
|
|
!*===========================================================================*
|
|
!* exit *
|
|
!*===========================================================================*
|
|
! PUBLIC void exit();
|
|
! Some library routines use exit, so provide a dummy version.
|
|
! Actual calls to exit cannot occur in the kernel.
|
|
! Same for .fat & .trp.
|
|
|
|
_exit:
|
|
__exit:
|
|
___exit:
|
|
.fat:
|
|
.trp:
|
|
sti
|
|
jmp __exit
|
|
|
|
|
|
!*===========================================================================*
|
|
!* phys_insw *
|
|
!*===========================================================================*
|
|
! PUBLIC void phys_insw(Port_t port, phys_bytes buf, size_t count);
|
|
! Input an array from an I/O port. Absolute address version of insw().
|
|
|
|
_phys_insw:
|
|
call portio_intro
|
|
mov es, bx ! destination segment
|
|
mov di, ax ! destination offset
|
|
shr cx, #1 ! word count
|
|
rep ins ! input many words
|
|
jmp portio_return
|
|
|
|
portio_intro:
|
|
pop ax ! hold return address
|
|
push bp ! create stack frame
|
|
mov bp, sp
|
|
push si
|
|
push di
|
|
push ax ! retore return address
|
|
mov dx, 4(bp) ! port to do I/O
|
|
mov ax, 6(bp) ! source/destination address in bx:ax
|
|
mov bx, 8(bp)
|
|
mov cx, 10(bp) ! count in bytes
|
|
cld ! direction is UP
|
|
portio_setup:
|
|
push cx ! segment/offset setup
|
|
movb ch, bl
|
|
mov bx, ax
|
|
and ax, #0x000F ! ax = offset = address % HCLICK_SIZE
|
|
movb cl, #4
|
|
shr bx, cl
|
|
shlb ch, cl
|
|
orb bh, ch ! bx = segment = address / HCLICK_SIZE
|
|
pop cx
|
|
ret
|
|
|
|
portio_return:
|
|
mov ax, ss ! restore segment registers
|
|
mov ds, ax
|
|
mov es, ax
|
|
pop di ! unwind stack frame
|
|
pop si
|
|
pop bp
|
|
ret
|
|
|
|
|
|
!*===========================================================================*
|
|
!* phys_insb *
|
|
!*===========================================================================*
|
|
! PUBLIC void phys_insb(Port_t port, phys_bytes buf, size_t count);
|
|
! Input an array from an I/O port. Absolute address version of insb().
|
|
! Note: The 8086 doesn't have string I/O instructions, so a loop is used.
|
|
|
|
_phys_insb:
|
|
call portio_intro
|
|
mov es, bx ! destination segment
|
|
mov di, ax ! destination offset
|
|
jcxz 1f
|
|
0: inb dx ! input 1 byte
|
|
stosb ! write 1 byte
|
|
loop 0b ! many times
|
|
1:
|
|
jmp portio_return
|
|
|
|
|
|
!*===========================================================================*
|
|
!* phys_outsw *
|
|
!*===========================================================================*
|
|
! PUBLIC void phys_outsw(Port_t port, phys_bytes buf, size_t count);
|
|
! Output an array to an I/O port. Absolute address version of outsw().
|
|
|
|
_phys_outsw:
|
|
call portio_intro
|
|
mov ds, bx ! source segment
|
|
mov si, ax ! source offset
|
|
shr cx, #1 ! word count
|
|
rep outs ! output many words
|
|
jmp portio_return
|
|
|
|
|
|
!*===========================================================================*
|
|
!* phys_outsb *
|
|
!*===========================================================================*
|
|
! PUBLIC void phys_outsb(Port_t port, phys_bytes buf, size_t count);
|
|
! Output an array to an I/O port. Absolute address version of outsb().
|
|
|
|
_phys_outsb:
|
|
call portio_intro
|
|
mov ds, bx ! source segment
|
|
mov si, ax ! source offset
|
|
jcxz 1f
|
|
0: lodsb ! read 1 byte
|
|
outb dx ! output 1 byte
|
|
loop 0b ! many times
|
|
1:
|
|
jmp portio_return
|
|
|
|
|
|
!*==========================================================================*
|
|
!* enable_irq *
|
|
!*==========================================================================*/
|
|
! PUBLIC void enable_irq(irq_hook_t *hook)
|
|
! Enable an interrupt request line by clearing an 8259 bit.
|
|
! Equivalent code for irq < 8:
|
|
! if ((irq_actids[hook->irq] &= ~hook->id) == 0)
|
|
! outb(INT_CTLMASK, inb(INT_CTLMASK) & ~(1 << irq));
|
|
|
|
_enable_irq:
|
|
push bp
|
|
mov bp, sp
|
|
pushf
|
|
cli
|
|
mov bx, 4(bp) ! hook
|
|
mov cx, 4(bx) ! irq
|
|
mov ax, 6(bx) ! id bit
|
|
not ax
|
|
mov bx, cx
|
|
add bx, bx
|
|
and _irq_actids(bx), ax ! clear this id bit
|
|
jnz en_done ! still masked by other handlers?
|
|
movb ah, #~1
|
|
rolb ah, cl ! ah = ~(1 << (irq % 8))
|
|
mov dx, #INT_CTLMASK ! enable irq < 8 at the master 8259
|
|
cmpb cl, #8
|
|
jb 0f
|
|
mov dx, #INT2_CTLMASK ! enable irq >= 8 at the slave 8259
|
|
0: inb dx
|
|
andb al, ah
|
|
outb dx ! clear bit at the 8259
|
|
en_done:popf
|
|
leave
|
|
ret
|
|
|
|
|
|
!*==========================================================================*
|
|
!* disable_irq *
|
|
!*==========================================================================*/
|
|
! PUBLIC int disable_irq(irq_hook_t *hook)
|
|
! Disable an interrupt request line by setting an 8259 bit.
|
|
! Equivalent code for irq < 8:
|
|
! irq_actids[hook->irq] |= hook->id;
|
|
! outb(INT_CTLMASK, inb(INT_CTLMASK) | (1 << irq));
|
|
! Returns true iff the interrupt was not already disabled.
|
|
|
|
_disable_irq:
|
|
push bp
|
|
mov bp, sp
|
|
pushf
|
|
cli
|
|
mov bx, 4(bp) ! hook
|
|
mov cx, 4(bx) ! irq
|
|
mov ax, 6(bx) ! id bit
|
|
pushf
|
|
cli
|
|
mov bx, cx
|
|
add bx, bx
|
|
or _irq_actids(bx), ax ! set this id bit
|
|
movb ah, #1
|
|
rolb ah, cl ! ah = (1 << (irq % 8))
|
|
mov dx, #INT_CTLMASK ! disable irq < 8 at the master 8259
|
|
cmpb cl, #8
|
|
jb 0f
|
|
mov dx, #INT2_CTLMASK ! disable irq >= 8 at the slave 8259
|
|
0: inb dx
|
|
testb al, ah
|
|
jnz dis_already ! already disabled?
|
|
orb al, ah
|
|
outb dx ! set bit at the 8259
|
|
mov ax, #1 ! disabled by this function
|
|
popf
|
|
leave
|
|
ret
|
|
dis_already:
|
|
xor ax, ax ! already disabled
|
|
popf
|
|
leave
|
|
ret
|
|
|
|
|
|
!*===========================================================================*
|
|
!* phys_copy *
|
|
!*===========================================================================*
|
|
! PUBLIC void phys_copy(phys_bytes source, phys_bytes destination,
|
|
! phys_bytes bytecount);
|
|
! Copy a block of physical memory.
|
|
|
|
SRCLO = 4
|
|
SRCHI = 6
|
|
DESTLO = 8
|
|
DESTHI = 10
|
|
COUNTLO = 12
|
|
COUNTHI = 14
|
|
|
|
_phys_copy:
|
|
push bp ! save only registers required by C
|
|
mov bp,sp ! set bp to point to source arg less 4
|
|
|
|
push si ! save si
|
|
push di ! save di
|
|
push ds ! save ds
|
|
push es ! save es
|
|
|
|
mov ax,SRCLO(bp) ! dx:ax = source address (dx is NOT segment)
|
|
mov dx,SRCHI(bp)
|
|
mov si,ax ! si = source offset = address % 16
|
|
and si,#OFF_MASK
|
|
andb dl,#HCHIGH_MASK ! ds = source segment = address / 16 % 0x10000
|
|
andb al,#HCLOW_MASK
|
|
orb al,dl ! now bottom 4 bits of dx are in ax
|
|
movb cl,#HCLICK_SHIFT ! rotate them to the top 4
|
|
ror ax,cl
|
|
mov ds,ax
|
|
|
|
mov ax,DESTLO(bp) ! dx:ax = destination addr (dx is NOT segment)
|
|
mov dx,DESTHI(bp)
|
|
mov di,ax ! di = dest offset = address % 16
|
|
and di,#OFF_MASK
|
|
andb dl,#HCHIGH_MASK ! es = dest segment = address / 16 % 0x10000
|
|
andb al,#HCLOW_MASK
|
|
orb al,dl
|
|
ror ax,cl
|
|
mov es,ax
|
|
|
|
mov ax,COUNTLO(bp) ! dx:ax = remaining count
|
|
mov dx,COUNTHI(bp)
|
|
|
|
! copy upwards (cannot handle overlapped copy)
|
|
|
|
pc_loop:
|
|
mov cx,ax ! provisional count for this iteration
|
|
test ax,ax ! if count >= 0x8000, only do 0x8000 per iter
|
|
js pc_bigcount ! low byte already >= 0x8000
|
|
test dx,dx
|
|
jz pc_upcount ! less than 0x8000
|
|
pc_bigcount:
|
|
mov cx,#0x8000 ! use maximum count per iteration
|
|
pc_upcount:
|
|
sub ax,cx ! update count
|
|
sbb dx,#0 ! cannot underflow, so carry clear now for rcr
|
|
rcr cx,#1 ! count in words, carry remembers if byte
|
|
jnb pc_even ! no odd byte
|
|
movb ! copy odd byte
|
|
pc_even:
|
|
rep ! copy 1 word at a time
|
|
movs ! word copy
|
|
|
|
mov cx,ax ! test if remaining count is 0
|
|
or cx,dx
|
|
jnz pc_more ! more to do
|
|
|
|
pop es ! restore es
|
|
pop ds ! restore ds
|
|
pop di ! restore di
|
|
pop si ! restore si
|
|
pop bp ! restore bp
|
|
ret ! return to caller
|
|
|
|
pc_more:
|
|
sub si,#0x8000 ! adjust pointers so the offset does not
|
|
mov cx,ds ! overflow in the next 0x8000 bytes
|
|
add cx,#0x800 ! pointers end up same physical location
|
|
mov ds,cx ! the current offsets are known >= 0x8000
|
|
sub di,#0x8000 ! since we just copied that many
|
|
mov cx,es
|
|
add cx,#0x800
|
|
mov es,cx
|
|
jmp pc_loop ! start next iteration
|
|
|
|
|
|
!*===========================================================================*
|
|
!* mem_rdw *
|
|
!*===========================================================================*
|
|
! PUBLIC u16_t mem_rdw(u16_t segment, u16_t *offset);
|
|
! Load and return the word at the far pointer segment:offset.
|
|
|
|
_mem_rdw:
|
|
mov cx,ds ! save ds
|
|
pop dx ! return adr
|
|
pop ds ! segment
|
|
pop bx ! offset
|
|
sub sp,#2+2 ! adjust for parameters popped
|
|
mov ax,(bx) ! load the word to return
|
|
mov ds,cx ! restore ds
|
|
jmp (dx) ! return
|
|
|
|
|
|
!*===========================================================================*
|
|
!* reset *
|
|
!*===========================================================================*
|
|
! PUBLIC void reset();
|
|
! Reset the system.
|
|
! In real mode we simply jump to the reset address at F000:FFF0.
|
|
|
|
_reset:
|
|
jmpf 0xFFF0,0xF000
|
|
|
|
|
|
!*===========================================================================*
|
|
!* mem_vid_copy *
|
|
!*===========================================================================*
|
|
! PUBLIC void mem_vid_copy(u16 *src, unsigned dst, unsigned count);
|
|
!
|
|
! Copy count characters from kernel memory to video memory. Src is an ordinary
|
|
! pointer to a word, but dst and count are character (word) based video offset
|
|
! and count. If src is null then screen memory is blanked by filling it with
|
|
! blank_color.
|
|
|
|
_mem_vid_copy:
|
|
push bp
|
|
mov bp, sp
|
|
push si
|
|
push di
|
|
push es
|
|
mov si, 4(bp) ! source
|
|
mov di, 6(bp) ! destination
|
|
mov dx, 8(bp) ! count
|
|
mov es, _vid_seg ! segment containing video memory
|
|
cld ! make sure direction is up
|
|
mvc_loop:
|
|
and di, _vid_mask ! wrap address
|
|
mov cx, dx ! one chunk to copy
|
|
mov ax, _vid_size
|
|
sub ax, di
|
|
cmp cx, ax
|
|
jbe 0f
|
|
mov cx, ax ! cx = min(cx, vid_size - di)
|
|
0: sub dx, cx ! count -= cx
|
|
shl di, #1 ! byte address
|
|
add di, _vid_off ! in video memory
|
|
test si, si ! source == 0 means blank the screen
|
|
jz mvc_blank
|
|
mvc_copy:
|
|
rep ! copy words to video memory
|
|
movs
|
|
jmp mvc_test
|
|
mvc_blank:
|
|
mov ax, _blank_color ! ax = blanking character
|
|
rep
|
|
stos ! copy blanks to video memory
|
|
!jmp mvc_test
|
|
mvc_test:
|
|
sub di, _vid_off
|
|
shr di, #1 ! back to a word address
|
|
test dx, dx
|
|
jnz mvc_loop
|
|
mvc_done:
|
|
pop es
|
|
pop di
|
|
pop si
|
|
pop bp
|
|
ret
|
|
|
|
|
|
!*===========================================================================*
|
|
!* vid_vid_copy *
|
|
!*===========================================================================*
|
|
! PUBLIC void vid_vid_copy(unsigned src, unsigned dst, unsigned count);
|
|
!
|
|
! Copy count characters from video memory to video memory. Handle overlap.
|
|
! Used for scrolling, line or character insertion and deletion. Src, dst
|
|
! and count are character (word) based video offsets and counts.
|
|
|
|
_vid_vid_copy:
|
|
push bp
|
|
mov bp, sp
|
|
push si
|
|
push di
|
|
push es
|
|
mov si, 4(bp) ! source
|
|
mov di, 6(bp) ! destination
|
|
mov dx, 8(bp) ! count
|
|
mov es, _vid_seg ! segment containing video memory
|
|
cmp si, di ! copy up or down?
|
|
jb vvc_down
|
|
vvc_up:
|
|
cld ! direction is up
|
|
vvc_uploop:
|
|
and si, _vid_mask ! wrap addresses
|
|
and di, _vid_mask
|
|
mov cx, dx ! one chunk to copy
|
|
mov ax, _vid_size
|
|
sub ax, si
|
|
cmp cx, ax
|
|
jbe 0f
|
|
mov cx, ax ! cx = min(cx, vid_size - si)
|
|
0: mov ax, _vid_size
|
|
sub ax, di
|
|
cmp cx, ax
|
|
jbe 0f
|
|
mov cx, ax ! cx = min(cx, vid_size - di)
|
|
0: sub dx, cx ! count -= cx
|
|
call vvc_copy
|
|
test dx, dx
|
|
jnz vvc_uploop ! again?
|
|
jmp vvc_done
|
|
vvc_down:
|
|
std ! direction is down
|
|
add si, dx ! start copying at the top
|
|
dec si
|
|
add di, dx
|
|
dec di
|
|
vvc_downloop:
|
|
and si, _vid_mask ! wrap addresses
|
|
and di, _vid_mask
|
|
mov cx, dx ! one chunk to copy
|
|
lea ax, 1(si)
|
|
cmp cx, ax
|
|
jbe 0f
|
|
mov cx, ax ! cx = min(cx, si + 1)
|
|
0: lea ax, 1(di)
|
|
cmp cx, ax
|
|
jbe 0f
|
|
mov cx, ax ! cx = min(cx, di + 1)
|
|
0: sub dx, cx ! count -= cx
|
|
call vvc_copy
|
|
test dx, dx
|
|
jnz vvc_downloop ! again?
|
|
cld ! C compiler expect up
|
|
!jmp vvc_done
|
|
vvc_done:
|
|
pop es
|
|
pop di
|
|
pop si
|
|
pop bp
|
|
ret
|
|
|
|
! Copy video words. (Inner code of both the up and downcopying loop.)
|
|
vvc_copy:
|
|
shl si, #1
|
|
shl di, #1 ! byte addresses
|
|
add si, _vid_off
|
|
add di, _vid_off ! in video memory
|
|
push ds ! must set ds here, 8086 can't do
|
|
mov ds, _vid_seg ! 'rep eseg movs' with interrupts on
|
|
rep
|
|
movs ! copy video words
|
|
pop ds
|
|
sub si, _vid_off
|
|
sub di, _vid_off
|
|
shr si, #1
|
|
shr di, #1 ! back to word addresses
|
|
ret
|
|
|
|
|
|
!*===========================================================================*
|
|
!* level0 *
|
|
!*===========================================================================*
|
|
! PUBLIC void level0(void (*func)(void))
|
|
! Not very interesting in real mode, see p_level0.
|
|
!
|
|
_level0:
|
|
mov bx, sp
|
|
jmp @2(bx)
|
|
|
|
|
|
!*===========================================================================*
|
|
!* klib_init_prot *
|
|
!*===========================================================================*
|
|
! PUBLIC void klib_init_prot();
|
|
! Initialize klib for protected mode by patching some real mode functions
|
|
! at their starts to jump to their protected mode equivalents, according to
|
|
! the patch table. Saves a lot of tests on the "protected_mode" variable.
|
|
! Note that this function must be run in real mode, for it writes the code
|
|
! segment. (One otherwise has to set up a descriptor, etc, etc.)
|
|
|
|
klib_init_prot:
|
|
mov si,#patch_table
|
|
kip_next:
|
|
lods ! original function
|
|
mov bx,ax
|
|
cseg movb (bx),#JMP_OPCODE ! overwrite start of function by a long jump
|
|
lods ! new function - target of jump
|
|
sub ax,bx ! relative jump
|
|
sub ax,#3 ! adjust by length of jump instruction
|
|
cseg mov 1(bx),ax ! set address
|
|
cmp si,#end_patch_table ! end of table?
|
|
jb kip_next
|
|
kip_done:
|
|
ret
|
|
|
|
|
|
!*===========================================================================*
|
|
!* variants for protected mode *
|
|
!*===========================================================================*
|
|
! Some routines are different in protected mode.
|
|
! The only essential difference is the handling of segment registers.
|
|
! One complication is that the method of building segment descriptors is not
|
|
! reentrant, so the protected mode versions must not be called by interrupt
|
|
! handlers.
|
|
|
|
|
|
!*===========================================================================*
|
|
!* p_cp_mess *
|
|
!*===========================================================================*
|
|
! The real mode version attempts to be efficient by passing raw segments but
|
|
! that just gets in the way here.
|
|
|
|
p_cp_mess:
|
|
mov bx, sp ! bx -> arguments
|
|
push si
|
|
push di
|
|
push ds
|
|
push es
|
|
|
|
mov ax, 4(bx) ! Compute source descriptor base
|
|
mov dx, ax
|
|
shl ax, #CLICK_SHIFT
|
|
shr dx, #16-CLICK_SHIFT ! dx:ax = src_clicks * CLICK_SIZE
|
|
add ax, 6(bx)
|
|
adc dx, #0 ! dx:ax += src_offset
|
|
mov _gdt+DS_286_OFFSET+DESC_BASE, ax
|
|
movb _gdt+DS_286_OFFSET+DESC_BASE_MIDDLE, dl
|
|
movb _gdt+DS_286_OFFSET+DESC_BASE_HIGH, dh
|
|
|
|
mov ax, 8(bx) ! Compute destination descriptor base
|
|
mov dx, ax
|
|
shl ax, #CLICK_SHIFT
|
|
shr dx, #16-CLICK_SHIFT ! dx:ax = dst_clicks * CLICK_SIZE
|
|
add ax, 10(bx)
|
|
adc dx, #0 ! dx:ax += dst_offset
|
|
mov _gdt+ES_286_OFFSET+DESC_BASE, ax
|
|
movb _gdt+ES_286_OFFSET+DESC_BASE_MIDDLE, dl
|
|
movb _gdt+ES_286_OFFSET+DESC_BASE_HIGH, dh
|
|
|
|
mov bx, 2(bx) ! proc no
|
|
mov ax, #DS_286_SELECTOR
|
|
mov ds, ax
|
|
mov ax, #ES_286_SELECTOR
|
|
mov es, ax
|
|
|
|
eseg mov 0, bx ! proc no. of sender from arg, not msg
|
|
mov si, #2 ! src offset is now 2 relative to start of seg
|
|
mov di, si ! and destination offset
|
|
mov cx, #Msize-1 ! word count
|
|
cld ! direction is up
|
|
rep
|
|
movs ! copy message (except first word)
|
|
|
|
pop es
|
|
pop ds
|
|
pop di
|
|
pop si
|
|
ret
|
|
|
|
|
|
!*===========================================================================*
|
|
!* p_portio_setup *
|
|
!*===========================================================================*
|
|
! The phys_insw, phys_outsw, etc. functions need an address setup routine that
|
|
! uses a segment descriptor.
|
|
p_portio_setup:
|
|
mov _gdt+DS_286_OFFSET+DESC_BASE, ax
|
|
movb _gdt+DS_286_OFFSET+DESC_BASE_MIDDLE, bl
|
|
movb _gdt+DS_286_OFFSET+DESC_BASE_HIGH, bh
|
|
xor ax, ax ! ax = 0 = start of segment
|
|
mov bx, #DS_286_SELECTOR ! bx = segment selector
|
|
ret
|
|
|
|
|
|
!*===========================================================================*
|
|
!* p_phys_copy *
|
|
!*===========================================================================*
|
|
p_phys_copy:
|
|
cld
|
|
pop dx
|
|
pop _gdt+DS_286_OFFSET+DESC_BASE
|
|
pop ax ! pop source into base of source descriptor
|
|
movb _gdt+DS_286_OFFSET+DESC_BASE_MIDDLE,al
|
|
movb _gdt+DS_286_OFFSET+DESC_BASE_HIGH,ah
|
|
pop _gdt+ES_286_OFFSET+DESC_BASE
|
|
pop ax ! pop destination into base of dst descriptor
|
|
movb _gdt+ES_286_OFFSET+DESC_BASE_MIDDLE,al
|
|
movb _gdt+ES_286_OFFSET+DESC_BASE_HIGH,ah
|
|
pop cx ! byte count in bx:cx
|
|
pop bx
|
|
sub sp,#4+4+4
|
|
|
|
push di
|
|
push si
|
|
push es
|
|
push ds
|
|
sub si,si ! src offset is now 0 relative to start of seg
|
|
mov di,si ! and destination offset
|
|
jmp ppc_next
|
|
|
|
! It is too much trouble to align the segment bases, so word alignment is hard.
|
|
! Avoiding the book-keeping for alignment may be good anyway.
|
|
|
|
ppc_large:
|
|
push cx
|
|
mov cx,#0x8000 ! copy a large chunk of this many words
|
|
rep
|
|
movs
|
|
pop cx
|
|
dec bx
|
|
pop ds ! update the descriptors
|
|
addb _gdt+DS_286_OFFSET+DESC_BASE_MIDDLE,#1
|
|
adcb _gdt+DS_286_OFFSET+DESC_BASE_HIGH,#0
|
|
addb _gdt+ES_286_OFFSET+DESC_BASE_MIDDLE,#1
|
|
adcb _gdt+ES_286_OFFSET+DESC_BASE_HIGH,#0
|
|
push ds
|
|
ppc_next:
|
|
mov ax,#DS_286_SELECTOR ! (re)load the selectors
|
|
mov ds,ax
|
|
mov ax,#ES_286_SELECTOR
|
|
mov es,ax
|
|
test bx,bx
|
|
jnz ppc_large
|
|
|
|
shr cx,#1 ! word count
|
|
rep
|
|
movs ! move any leftover words
|
|
rcl cx,#1 ! restore old bit 0
|
|
rep
|
|
movb ! move any leftover byte
|
|
pop ds
|
|
pop es
|
|
pop si
|
|
pop di
|
|
jmp (dx)
|
|
|
|
!*===========================================================================*
|
|
!* p_reset *
|
|
!*===========================================================================*
|
|
! Reset the system by loading IDT with offset 0 and interrupting.
|
|
|
|
p_reset:
|
|
lidt idt_zero
|
|
int 3 ! anything goes, the 286 will not like it
|
|
|
|
!*===========================================================================*
|
|
!* idle_task *
|
|
!*===========================================================================*
|
|
_idle_task:
|
|
! This task is called when the system has nothing else to do. The HLT
|
|
! instruction puts the processor in a state where it draws minimum power.
|
|
mov ax, #halt
|
|
push ax
|
|
call _level0 ! level0(halt)
|
|
pop ax
|
|
jmp _idle_task
|
|
halt:
|
|
sti
|
|
hlt
|
|
cli
|
|
ret
|
|
|
|
|
|
!*===========================================================================*
|
|
!* p_level0 *
|
|
!*===========================================================================*
|
|
! PUBLIC void level0(void (*func)(void))
|
|
! Call a function at permission level 0. This allows kernel tasks to do
|
|
! things that are only possible at the most privileged CPU level.
|
|
!
|
|
p_level0:
|
|
mov bx, sp
|
|
mov ax, 2(bx)
|
|
mov _level0_func, ax
|
|
int LEVEL0_VECTOR
|
|
ret
|
|
|
|
|
|
!*===========================================================================*
|
|
!* data *
|
|
!*===========================================================================*
|
|
.data
|
|
patch_table: ! pairs (old function, new function)
|
|
.data2 _int86, p_int86
|
|
.data2 _cp_mess, p_cp_mess
|
|
.data2 _phys_copy, p_phys_copy
|
|
.data2 portio_setup, p_portio_setup
|
|
.data2 _reset, p_reset
|
|
.data2 _level0, p_level0
|
|
.data2 _restart, p_restart ! in mpx file
|
|
.data2 save, p_save ! in mpx file
|
|
end_patch_table: ! end of table
|
|
|
|
idt_vectors: ! limit and base of real mode interrupt vectors
|
|
.data2 0x3FF
|
|
idt_zero: ! zero limit IDT to cause a processor shutdown
|
|
.data2 0, 0, 0
|
|
|
|
.bss
|
|
save_sp: ! place to put sp when switching to real mode
|
|
.space 2
|
|
msw: ! saved real mode machine status word
|
|
.space 2
|