; *** 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