Digital Research
This commit is contained in:
2020-11-06 18:50:37 +01:00
parent 621ed8ccaf
commit 31738079c4
8481 changed files with 1888323 additions and 0 deletions

View File

@@ -0,0 +1,822 @@
; *** SERIN.RSP ***
; A Concurrent CP/M-86
; Resident System Process for
; Queue Driven Serial Character Input
; Oct 19, 1983 Dean Ballard
; This program reads characters from a circular buffer and
; writes them to the serial input queue for consumption by
; an application program. The characters are put into the
; buffer by an interrupt routine which is contained within
; this module. The interrupt routine also serves to alert
; the serial output routine when it is waiting on transmit
; buffer empty.
; First the hardware independent equate values.
; These have to do with the queue management, and generally
; pertain to the application program side of things.
q_mes_size equ 17 ; byte count plus 16 characters
in_q_num equ 4 ; number of in queue messages
out_q_num equ 2 ; number of out queue messages
c_buf_size equ 300 ; receive int circular buffer
stack_size equ 64 ; for interrupt routine
bdos equ 0E0h ; bdos interrupt number
c_detach equ 93h ; detach console
dev_flag_wait equ 84h ; wait for a flag
dev_flag_set equ 85h ; release a flag
p_delay equ 8Dh ; delay dx ticks
p_priority equ 91h ; set process priority
p_term equ 8Fh ; terminate a process
q_make equ 86h ; create queue code
q_open equ 87h ; open queue code
q_read equ 89h ; read queue code
q_write equ 8Bh ; write queue code
s_sysdat equ 9Ah ; get system data seg
; RSP data origin addresses
rsp_header equ 00h ; start of data segment
rsp_pd equ 10h ; process descriptor offset
rsp_uda equ 40h ; user data area offset
rsp_stack_top equ 13Ah ; at the end of uda
rsp_data_end equ 140h ; end of rsp stuff
; protocol constants
dtr_dsr equ 01h ; bit for dsr/dtr protocol
rts_cts equ 02h ; bit for rts/cts protocol
xon_xoff equ 04h ; bit for xon/xoff protocol
xon equ 11h ; start transmission
xoff equ 13h ; stop transmission
; system data page address pointers
dseg
org 00h
sup_o rw 1 ; supervisor offset
sup_s rw 1 ; supervisor segment
org 38h
disp_o rw 1 ; dispatcher offset
disp_s rw 1 ; dispatcher segment
; Now the hardware specific equates
; These have to do with the I/O port and interrupt management,
; and are, for the most part, specific to the IBM PC
; async port bit patterns
dr equ 01h ; data ready
tbe equ 20h ; xmit buf empty
r_t_mask equ 03h ; rec/trans int en
rec_int equ 04h ; from int id reg
tran_int equ 02h ; from int id reg
no_int equ 01h ; no hardware interrupt
dsr_bit equ 20h ; from modem status
cts_bit equ 10h ; from modem status
rts_bit equ 02h ; also with int en
dtr_bit equ 01h ; to modem control
i_en equ 08h ; interrupt enable
; 8259 programmable interrupt ports and patterns
pic_ack equ 20h ; 8259 acknowledge address
pic_mask equ pic_ack + 1 ; 8259 int mask address
ns_eoi equ 20h ; non specific end of int
; **************************
; *** Code begins here ***
; **************************
cseg
org 00h ; small model
serial_in:
call which_port ; are we port 0 or port 1 ?
call is_it_there ; if the port is not present
jnz terminate_in ; then terminate the process
call get_sys_data ; read supervisor, dispatcher
call make_queues ; input, output, and mx queues
call open_queues ; open all queues
call write_mx_queue ; set up the mx queue
call write_t_block ; send address to serial out
call install_int ; prepare for interrupts
in_loop:
call read_c_buf ; get char(s) from buffer
call pause ; wait if there's no hurry
call write_q ; write message to queue
jmps in_loop ; go forever
terminate_in:
mov pd_flag,0 ; turn off the keep flag
mov cl,p_term
mov dl,0
int bdos ; back to bdos
; ***************************
; *** Setup Subroutines ***
; ***************************
; see which port we are, and adjust params if necessary
which_port:
mov al,ncp ; get copy number
cmp al,0 ; if port = 0
jz which_port_done ; then leave params alone
push ds
pop es ; local move
mov si,offset qn_list ; list of queue names
mov cx,qn_list_size ; count of names to change
add al,'0' ; change port number to ascii
qn_list_loop:
mov di,[si]
stosb ; change to '1'
inc si ! inc si
loop qn_list_loop
mov si,offset port1_params ; now adjust parameters
mov di,offset port0_params
mov cx,port_param_size
cld
rep movsb ; copy port1 over port0
which_port_done:
ret
; check to see if the port is present
; exit: zf set if port is present
is_it_there:
mov dx,port_int_id ; interrupt ident port
in al,dx
test al,0F8h ; should be zero if present
ret
; fetch pointers to supervisor and dispatcher entries
get_sys_data:
mov cl,s_sysdat
int bdos ; es:bx points to sysdat
mov sys_data_s,es ; save system data segment
mov ax,es:sup_o[bx]
mov i_sup_o,ax ; save supervisor offset
mov ax,es:sup_s[bx]
mov i_sup_s,ax ; save supervisor segment
mov ax,es:disp_o[bx]
mov i_disp_o,ax ; save dispatcher offset
mov ax,es:disp_s[bx]
mov i_disp_s,ax ; save dispatcher segment
ret
; create input, output, and mx queues
make_queues:
mov dx,offset qd_in
call make_one ; make the input queue
mov dx,offset qd_out
call make_one ; make the output queue
mov dx,offset qd_mx ; and the mx queue too
make_one:
mov cl,q_make
int bdos ; make it
ret
; open all three queues
open_queues:
mov dx,offset qpb_in
call open_one ; open the input queue
mov dx,offset qpb_out
call open_one ; open the output queue
mov dx,offset qpb_mx ; and the mx queue too
open_one:
mov cl,q_open
int bdos ; open it
ret
; perform the initial write to the mx queue
write_mx_queue:
mov cl,q_write
mov dx,offset qpb_mx
int bdos ; port is now available
ret
; write the address of the shared data block to the out queue
; to enable the Serial Out process access
write_t_block:
mov t_block_s,ds ; our data segment
mov cl,q_write ; offset is already there
mov dx,offset qpb_out
int bdos ; start up the out routine
ret
; set up interrupt vector and hardware
install_int:
mov di,ser_ds_ptr ; point to the place to
mov cs:[di],ds ; store our data segment
; store it in the code seg
sub ax,ax
mov es,ax ; base page extra segment
mov di,ser_int_vect ; vector destination
mov ax,ser_int_entry ; for port 0 or port 1
stosw ; store offset
mov ax,cs
stosw ; store segment
in al,pic_mask ; now program the 8259
and al,ser_pic_mask ; to allow this interrupt
out pic_mask,al
mov dx,port_int_en ; which comm ints to use
mov al,r_t_mask ; allow receive and transmit
out dx,al
mov dx,port_modem_ctrl
mov al,dtr_bit + rts_bit + i_en
out dx,al ; set up modem control
mov dx,port_int_id
in al,dx ; clear a pending xmit int
ret ; ready to go
; **************************
; *** Loop Subroutines ***
; **************************
; read characters from the circular buffer
read_c_buf:
cli ;; critical section
mov si,buf_out_ptr ;; for reading
mov ax,buf_in_ptr ;; used by int routine
cmp ax,si ;; if they're different
jnz read_now ;; then something's there
mov r_wait,0FFh ;; if not, then wait
sti ; interrupts are ok
call rec_prot_on ; enable handshakes
mov cl,dev_flag_wait
mov dl,r_flag
int bdos ; wait for c_buf to fill
jmps read_c_buf ; go back and set it up
read_now:
sti
push ds
pop es ; local extra seg
mov cx,q_mes_size - 1 ; max number of chars
mov di,offset msg_in + 1 ; start of char message
read_loop:
movsb ; from c_buf to message
cmp si,c_buf_end ; check for wrap around
jb read_no_wrap
mov si,offset c_buf
read_no_wrap:
cmp ax,si ; are there more chars?
loopnz read_loop ; if so, loop up to max
mov buf_out_ptr,si ; update the pointer
mov al,q_mes_size - 1
sub al,cl ; al = char count
mov msg_in,al ; store in the message
ret
; pause to fill the buffer a bit
; this maximizes the use of full queue messages
; and frees up the processor for other tasks
pause:
cmp msg_in,q_mes_size - 1 ; if last message was full
jz pause_done ; then don't wait
mov cl,p_delay
mov dx,1 ; wait at least 1 tick
int bdos
pause_done:
ret
; write one message to the queue
write_q:
mov cl,q_write
mov dx,offset qpb_in ; input queue param block
int bdos ; wait here if not ready
ret ; return when ready to send
; receive protocol handler
rec_prot_on:
mov bx,offset r_on_prot ; receive on data
mov al,0FFh ; state = true
xchg al,r_prot_state ; test and set state
test al,al ; if protocol if off now
jz receive_prot ; then turn it on
ret ; else just return
rec_prot_off:
mov bx,offset r_off_prot ; receive stop data
mov r_prot_state,0 ; protocol is off now
; jmps receive_prot
receive_prot:
mov al,rec_prot_code ; code for which protocol
test al,al ; if none being used
jz rec_prot_done ; then just skip it
and al,03h ; if not hardware handshake
jz rec_prot_x ; then do xon/xoff
xlat bx ; turn code into control bits
mov dx,port_modem_ctrl ; to control dtr, rts
out dx,al ; set modem lines
jmps rec_prot_done
rec_prot_x:
mov al,[bx]
mov send_x,al ; either xon or xoff
cmp al,xon
jnz rec_prot_done ; if we're sending an xon
call fake_int ; then fake an interrupt
; to kick it off
rec_prot_done:
ret
; Interrupt entries to kick off an xon transmit
fake_int0:
int 12 ; port 0 interrupt
ret
fake_int1:
int 11 ; port 1 interrupt
ret
serin_code_top equ offset $
; *****************************
; *** RSP header, pd, uda ***
; *****************************
dseg
org rsp_header ; header start
dw 0,0
ncp db 1,0 ; one copy
dw 0,0,0
dw 0,0
org rsp_pd ; process descriptor
dw 0,0 ; link, thread
db 0 ; ready to run
db 180 ; priority better than PIN's
pd_flag dw 2 ; process flag "keep"
db 'SerIn ' ; process name
dw rsp_uda/10h ; uda segment
dw 0,0 ; disk, user, reserved
dw 1 ; for shared code
dw 0,0,0 ; and a mess of zeros
dw 0,0,0
dw 0,0,0
dw 0,0,0
org rsp_uda ; user data area
dw 0,0,0,0,0,0 ; no dma buffer
dw 0,0,0,0,0,0
dw 0,0,0,0,0,0
dw 0,0,0,0,0,0
dw 0,0,rsp_stack_top,0,0,0
dw 0,0,0,0,0,0
dw 0,0,0,0,0,0
dw 0,0,0,0,0,0
db 1,0,0,0,0,0 ; don't switch from UDA
; stack at SUP entry
org rsp_stack_top
dw offset serial_in ; code start
dw 0 ; code seg (genccpm)
dw 0 ; flags (genccpm)
; ******************************
; *** Our data begins here ***
; ******************************
org rsp_data_end ; above the rsp stuff
; first we have parameters for port 0
port0_params rb 0
p0_base equ 03F8h ; port base address
port_data dw p0_base ; rw data
port_int_en dw p0_base + 1 ; wo int enable
port_int_id dw p0_base + 2 ; ro int ident
port_modem_ctrl dw p0_base + 4 ; wo bit 3 = en int
port_status dw p0_base + 5 ; ro tbe and da
port_modem_stat dw p0_base + 6 ; ro dsr cts
fake_int dw fake_int0 ; for software interrupt
ser_int_vect dw 12 * 4 ; interrupt vector location
ser_int_entry dw i_serial_0 ; interrupt entry point
ser_ds_ptr dw data_seg_0 ; port 0's data segment
ser_pic_mask db 0EFh ; enable pic interrupt
r_flag db 08h ; XIOS receive flag
t_flag db 09h ; XIOS transmit flag
; if using port 1, these values are copied over port 0's
port1_params rb 0
p1_base equ 02F8h ; port base address
dw p1_base ; rw data
dw p1_base + 1 ; wo int enable
dw p1_base + 2 ; ro int ident
dw p1_base + 4 ; wo bit 3 = en int
dw p1_base + 5 ; ro tbe and da
dw p1_base + 6 ; ro dsr cts
dw fake_int1 ; soft int
dw 11 * 4 ; int vector location
dw i_serial_1 ; int entry point
dw data_seg_1 ; port 1's data seg
db 0F7h ; pic interrupt mask
db 0Ah ; rec flag
db 0Bh ; xmit flag
port_param_size equ offset $ - offset port1_params
; queue descriptors, buffers, and parameter blocks
q_in_buf rb q_mes_size * in_q_num
qd_in dw 0,0,02h
db 'SerIn'
qd_in_p db '0 '
dw q_mes_size,in_q_num,0
dw 0,0,0
dw q_in_buf
q_out_buf rb q_mes_size * out_q_num
qd_out dw 0,0,02h
db 'SerOut'
qd_out_p db '0 '
dw q_mes_size,out_q_num,0
dw 0,0,0
dw q_out_buf
qd_mx dw 0,0,03h
db 'MXSer'
qd_mx_p db '0 '
dw 0,1,0
dw 0,0,0,0
qpb_in dw 0,0,0,msg_in
db 'SerIn'
qpb_in_p db '0 '
qpb_out dw 0,0,0,t_block_o
db 'SerOut'
qpb_out_p db '0 '
qpb_mx dw 0,0,0,0
db 'MXSer'
qpb_mx_p db '0 '
; queue name adjustment list for port numbers
qn_list dw qd_in_p
dw qd_out_p
dw qd_mx_p
dw qpb_in_p
dw qpb_out_p
dw qpb_mx_p
qn_list_size equ (offset $ - offset qn_list) / 2
t_block_o dw t_block
t_block_s rw 1 ; filled by write t_block
msg_in rb q_mes_size
r_prot_state db 0FFh ; 0FFh => rec protocol on
all_on equ i_en+dtr_bit+rts_bit
r_on_prot db xon, all_on, all_on, all_on
r_off_prot db xoff, rts_bit+i_en, dtr_bit+i_en, i_en
serin_data_top equ offset $
eject
; ***************************************
; *** Interrupt routine begins here ***
; ***************************************
cseg
org serin_code_top
; this collection of data must be in the code segment
; and it must not change, or reentrancy is blown
data_seg0 rw 1 ; port 0's data segment
data_seg1 rw 1 ; port 1's data segment
i_sup_o rw 1 ; supervisor offset
i_sup_s rw 1 ; supervisor segment
supervisor equ dword ptr i_sup_o
; serial char interrupt entry for port 0
i_serial_0:
push ds ; on user's stack
mov ds,data_seg0 ; get port zero's data seg
jmps i_serial_shared ; and share the rest
; serial char interrupt entry for port 1
i_serial_1:
push ds ; on user's stack
mov ds,data_seg1 ; get port 1's data seg
; jmps i_serial_shared ; and share the rest
; shared serial char interrupt code
i_serial_shared:
mov save_ax,ax ; first switch stacks
mov save_ss,ss
mov save_sp,sp
mov ax,ds ; set up local stack
mov ss,ax
mov sp,local_stack
push bx ! push cx ! push dx ! push bp
push si ! push di ! push es
mov es,ax ; for receive stosb
mov dx,port_int_id ; int identification reg
in al,dx ; this says which int it is
and al,07h ; 3 lsb's only
mov int_id,al ; save for pic reset
cmp al,rec_int ; if not from char received
jnz i_transmit ; then skip
i_receive:
mov dx,port_status ; just to double check
in al,dx ; we look at line status
test al,dr ; if not data ready
jz i_rec_wait ; then skip buffer write
i_rec_char:
mov dx,port_data
in al,dx ; fetch the data char
test tran_prot_code,xon_xoff ; check to see if this is
jz i_rec_to_buf ; an xon/xoff character
cmp al,xon ; for the transmit side
jz i_rec_x ; if it is, get out of here
cmp al,xoff
jz i_rec_x
i_rec_to_buf: ; a valid data char rec'd
mov di,buf_in_ptr
stosb ; store to circular buffer
cmp di,c_buf_end ; check for wrap around
jb i_rec_no_wrap
mov di,offset c_buf ; wrap to beginning
i_rec_no_wrap:
mov ax,di
sub ax,buf_out_ptr ; check buffer fullness
jz i_rec_too_full ; right to the brim
mov buf_in_ptr,di ; update if any room at all
jnc i_rec_valid ; subtraction worked
add ax,c_buf_size ; ax = chars in buffer
i_rec_valid:
cmp ax,c_buf_size - 10h ; if not too full
jb i_rec_wait ; then carry on
i_rec_too_full:
call rec_prot_off ; else, try to stop the stream
i_rec_wait:
mov al,0
xchg al,r_wait ; test and set wait flag
test al,al ; if Serial In isn't waiting
jz i_transmit ; then check for transmit
mov dl,r_flag ; if it is waiting
call i_set_flag ; then set the flag
jmps i_transmit ; check for xmit message
i_rec_x:
mov rec_x,al ; save xon/xoff for transmit
; jmps i_transmit ; and carry on
; in case a character was received just before transmit is to
; be checked, we always check for a pending transmit message.
i_transmit:
cmp send_x,0 ; if there is an xon/xoff
jz i_tran_from_buf ; to send, it has top priority
call trans_ready ; check tbe
jz i_exit ; if not ready, come back again
mov dx,port_data
mov al,send_x
out dx,al ; send an xon/xoff char
mov send_x,0 ; don't do it again
jmps i_exit ; and depart
i_tran_from_buf:
cmp t_count,0 ; if transmit message is empty
jz i_tran_wait ; see if serout is waiting
call trans_prot ; if protocol blocks transmission
jz i_tran_wait ; then skip the output
call trans_ready ; check tbe
jz i_tran_wait ; for a flag wait on serout
les si,t_pointer ; point to transmit message
mov al,es:[si] ; grab the character
mov dx,port_data
out dx,al ; ship the character
inc si ; bump and
mov t_ptr_off,si ; update the pointer
dec t_count ; and if characters remain
jnz i_exit ; then don't set the flag
i_tran_wait:
mov al,0
xchg al,t_wait ; test and set wait flag
test al,al ; if Serial Out not waiting
jz i_exit ; then we're done
mov dl,t_flag ; if it is waiting
call i_set_flag ; then set flag
i_exit:
cmp int_id,no_int ; if interrupt was soft
jz i_no_pic ; then don't reset pic
mov al,ns_eoi ; reset the pic
out pic_ack,al
i_no_pic:
pop es ! pop di ! pop si
pop bp ! pop dx ! pop cx ! pop bx
mov ax,save_ax
mov ss,save_ss ; restore the stack
mov sp,save_sp
pop ds ; get back user's ds
iret ; do not dispatch!
; check for transmit buffer empty. zf set if not
trans_ready:
mov dx,port_status ; if message pending, see if
in al,dx ; the port is ready
test al,tbe ; if not ready, then check
ret
; check for transmit protocol ready. zf set if not
trans_prot:
mov ah,tran_prot_code ; which protocol to use
test ah,ah ; if zero
jz trans_prot_ok ; then skip the works
mov dx,port_modem_stat
in al,dx ; get the status bits
test ah,dtr_dsr
jz trans_prot1 ; if dsr/dtr protocol
test al,dsr_bit ; see if dsr is on
jz trans_prot_done ; if not, it's no good
trans_prot1:
test ah,rts_cts
jz trans_prot2 ; if rts/cts protocol
test al,cts_bit ; see if cts is on
jz trans_prot_done ; if not, forget it
trans_prot2:
test ah,xon_xoff
jz trans_prot_ok ; if xon/xoff protocol
cmp rec_x,xoff ; see if we've got an xoff
jz trans_prot_done ; if so, go back
trans_prot_ok:
or al,0FFh ; clear the zero flag
trans_prot_done:
ret ; with zf
; set the flag passed in register dl
i_set_flag:
mov dh,0 ; ensure dx = flag num
mov cx,dev_flag_set
push ds
mov ds,sys_data_s
callf supervisor ; right to the sup
pop ds
ret
; ************************************
; *** Interrupt data begins here ***
; ************************************
dseg
org serin_data_top
save_ax rw 1 ; for stack switch
save_ss rw 1
save_sp rw 1
int_id rb 1 ; saves the int_id_reg
r_wait db 00h ; is Serial In waiting?
; this is the data block which is shared with SerOut
t_block equ offset $
t_wait db 00h ; is Serial Out waiting?
t_count db 00h ; message byte count
t_ptr_off rw 1 ; offset of trans message
t_ptr_seg rw 1 ; segment of trans message
t_pointer equ dword ptr t_ptr_off
rec_prot_code db 00h ; receive protocol code
tran_prot_code db dtr_dsr + xon_xoff ; transmit protocol code
buf_in_ptr dw c_buf ; used by int to fill
buf_out_ptr dw c_buf ; used by Serial In to empty
rec_x db xon ; our received xon/xoff
send_x db 00h ; an xon/xoff to send
sys_data_s rw 1 ; system data seg
i_disp_o rw 1 ; dispatcher offset
i_disp_s rw 1 ; dispatcher segment
dispatcher equ dword ptr i_disp_o
c_buf rb c_buf_size ; receive circular buffer
c_buf_end equ offset $
rb stack_size ; local interrupt stack
local_stack equ offset $
db 00h ; for gencmd
end