.TITLE IOSYS I/O subsystem
.IDENT /01.25/
;
; This is a simple packet-oriented I/O subsystem for GRUMPF.
; All I/O is done with packets and all packets are associated with some
; I/O channel, I/O channel 0 is a control channel. An I/O channel is just
; an index into an array of structures containing subroutine pointers and
; arguments for these subroutines, this array is private to a job.
;
; I/O Request Packet (IORQP) format:
; IO$CH Byte containing I/O channel number
; IO$FUN Byte containing the function code
; IO$OFS Word containing an offset in the I/O channel
; IO$SIZ Word containing the size of IO$DAT in bytes
; IO$DAT IO$SIZ bytes of data, at least one word
;
; Device structure format:
; D$TYPE RAD50 word containing the device type
; D$NUM RAD50 word containing the device number
; D$NAME RAD50 word containing the device name
; D$IORQ pointer to I/O request packet handler
; D$INFO pointer to a device information string
; D$OPEN pointer to a "open" routine, sets carry if device busy
;
; system calls provided:
; $ASSOC associates an interrupt handler with an interrupt vector
; $DISSOC dissociates an interrupt handler from an interrupt vector
; $IOREQ issues an I/O request
; $IODONE clears an I/O channel (used by driver CLOSE routines)
;
; well-known I/O functions:
; $READ Read a block of data. Arguments are IORQP buffer, data buffer
; size and data buffer address. If used on I/O channel 0,
; IO$SIZ/SIZE$D device table entries will be copied into the
; data buffer, beginning with entry IO$OFS, leaving IO$OFS
; "pointing" to the following entry.
; $WRIT Write a block of data. Arguments are IORQP buffer, data buffer
; size and data buffer address. If used on I/O channel 0, the
; contents of the data buffer are used as a new device table
; entry. The data buffer size has to be SIZE$D, or else an error
; will occur. IO$OFS is not affected.
; $SEEK Change IO$OFS. Argument is a count of blocks or bytes or
; something else. If used on channel 0, the count is interpreted
; as block count (one block = one device table entry)
; $OPEN Opens an I/O channel. There are three arguments NAM, NUM and
; TYPE, which serve as addresses of the name, number and type
; strings respectivly of the device to be opened when used on
; channel 0. The new I/O channel number is returned in R0.
; $CLOS Closes an I/O channel. There are no arguments. If used on
; channel 0, the jobs old I/O channel list is freed if it exists
; and a new one is created.
; $BSIZ Return the block size of the device associated to the I/O
; channel. If used on channel 0, SIZE$D is returned.
; $MODE set some mode bits of the device
;
; All these I/O functions are called through macros which fill in the arguments
; into the IORQP and then call the $IOREQ system call.
;
; well-known I/O error status codes:
; IO$OK equals 0, no error. The I/O request was successfully processed.
; IO$INV invalid request or data in I/O request packet
; IO$BSY device, driver or system busy
; IO$INT internal error occured while processing the request
; IO$EOF end-of-file (or -device) reached
;
; additional macros:
; .DEVCTL device structure format
; .IOSYS I/O request packet format
; .IOFUNC well-known I/O functions
; .IOERR well-known I/O errors
; .IOCLR clears the IORQP given as argument
; .IOHND creates a primitve IOREQ handler
; .IOTAB creates a jump table for the IOREQ handler
; .DEV creates a device structure
;
; tunable constants:
; NDEV maximum number of devices
; NCHAN maximum number of I/O channels per job
;
; 29-AUG-2005 H. Rosenfeld fixed bug in GETDEV
; 27-MAY-2005 H. Rosenfeld return error codes in R0 instead of writing
; them in IO$DAT
; 22-MAY-2005 H. Rosenfeld changed $OPEN semantics to return new channel
; number instead of changing the packet, wake up
; for device after REGDEV
; 17-MAY-2005 H. Rosenfeld fix ASSOC, setting of PSW got lost somewhen in
; january
; 06-MAY-2005 H. Rosenfeld fix bug in IODONE
; 05-MAY-2005 H. Rosenfeld add $MODE I/O function & $IODONE syscall
; 03-MAY-2005 H. Rosenfeld use system call priorites
; 28-APR-2005 H. Rosenfeld added .DEV macro, fixed bug in IOOPEN (channel
; number calculation), fixed bug in .IOHND
; 22-APR-2005 H. Rosenfeld added .IOHND and .IOTAB, removed private POOL
; (the device structures are not copied)
; 17-APR-2005 H. Rosenfeld the I/O channel array is now partiton, not a
; pool buffer, the driver open routine will
; be called with the device structure as argument
; 11-APR-2005 H. Rosenfeld changed to use private POOL partition
; 08-APR-2005 H. Rosenfeld adapt to change of .IMAGE macro
; 06-MAR-2005 H. Rosenfeld make use of new .IMAGE macro
; 29-JAN-2005 H. Rosenfeld modified the system calls and I/O routines to
; make use of the new calling standard
; 07-DEC-2004 H. Rosenfeld fixed another bug in IOOPEN (stack corruption
; on unsuccessful return of driver open routine)
; 06-DEC-2004 H. Rosenfeld changed syscalls to reflect changes to $SYS
; fixed bug in IOOPEN, wrong adressing of driver
; open routine, wrong assignment of I/O channel
; to IORQP
; 03-DEC-2004 H. Rosenfeld fixed bug in IOCTL (R1 corruption)
; added support for driver OPEN routine
; 28-NOV-2004 H. Rosenfeld
;
.ENABL REG
.LIST ME
.NLIST CND
.LIBRARY /JOBCTL.SML/
.LIBRARY /SYSCLL.SML/
.LIBRARY /CALL.SML/
.LIBRARY /MEMORY.SML/
.MCALL $M$ALLO,$M$FREE
.MCALL .CALL,.ENTRY,.RETRN,.IMAGE
.MCALL $SYS,$REGSYS
.MCALL $CURJOB,$DONE,$TERM
.MCALL .JOBCTL
.MACRO .IOSYS
IO$CH=0
IO$FUN=1
IO$OFS=2
IO$SIZ=4
IO$DAT=6
.ENDM
.MACRO .IOFUNC
IO$RDB=0
IO$WRB=1
IO$SKB=2
IO$OPN=3
IO$CLS=4
IO$BSZ=5
IO$MOD=6
.ENDM
.MACRO .DEVCTL
D$TYPE=0
D$NUM=2
D$NAME=4
D$IORQ=6
D$INFO=10
D$OPEN=12
SIZE$D=14
.ENDM
.MACRO .DEV TYP,NUM,NAM,IORQ,INFO,OPEN
.WORD TYP
.WORD NUM
.WORD NAM
.WORD IORQ
.WORD INFO
.WORD OPEN
.ENDM
.MACRO .IOERR
IO$OK=0
IO$INV=-1
IO$BSY=-2
IO$INT=-3
IO$EOF=-4
.ENDM
.MACRO .IOCLR IORQP
MOV R0,-(SP)
MOV IORQP,R0
CLR (R0)
CLR IO$OFS(R0)
CLR IO$SIZ(R0)
MOV (SP)+,R0
.ENDM
.MACRO .IOHND ?RANGE
MOV 2(R5),R0
MOVB IO$FUN(R0),R0
ASL R0
CMP R0,#IO$END-IO$TAB
BHI RANGE
JMP @IO$TAB(R0)
RANGE: SEC
RTS R5
.ENDM
.MACRO .IOTAB H1,H2,H3,H4,H5,H6,H7,H8,H9
.NARG I$ARGC
.IF EQ I$ARGC
.ERROR ; no arguments to .IOTAB
.MEXIT
.ENDC
IO$TAB:
.IIF NB
.WORD H1
.IIF NB .WORD H2
.IIF NB .WORD H3
.IIF NB .WORD H4
.IIF NB .WORD H5
.IIF NB .WORD H6
.IIF NB .WORD H7
.IIF NB .WORD H8
.IIF NB .WORD H9
IO$END:
.ENDM
.MACRO .IOCHN
CH$HND=0
CH$ARG=2
SIZE$C=4
.ENDM
.DEVCTL
.IOSYS
.IOERR
.IOFUNC
.IOCHN
.JOBCTL
NDEV=20
NCHAN=20
.IMAGE IOSYS
.CSECT
; I/O system initialization
; clear device table, register system calls
IOSYS: MOV #DEVTAB,R0
MOV #NDEV,R1 ; clear DEVTAB
1$: CLR (R0)+
SOB R1,1$
$REGSYS #^RASS,#^ROC,#ASSOC,#0
$REGSYS #^RIOR,#^REQ,#IOREQ$,#0
$REGSYS #^RDIS,#^RSOC,#DISSOC,#0
$REGSYS #^RIOD,#^RONE,#IODONE,#0
$TERM ; terminate
; ASSOC, associate interrupt handler with vector
; input: vector, handler address, PSW for handler
; output: handler & PSW is registered for vector
; return: none
.MACRO $ASSOC VEC HND PSW
$SYS #^RASS,#^ROC,VEC,HND,PSW
.ENDM
ASSOC: .ENTRY
MOV 6(R5),R0 ; first argument: vector
INC R0
BIC #1,R0 ; make it word-aligned
CMP #400,R0 ; is it inside the vector area?
BLOS 1$ ; nay, error
MOV 10(R5),(R0)+ ; second argument: handler address
MOV 12(R5),(R0)+ ; third argument: PSW for handler
1$: .RETRN
; DISSOC, dissociate handler from vector
; input: vector
; output: generic handler is registered for vector
; return: none
.MACRO $DISSOC VEC
$SYS #^RDIS,#^RSOC,VEC
.ENDM
DISSOC: .ENTRY
MOV 6(R5),R0 ; argument: vector
INC R0
BIC #1,R0 ; make it word-aligned
CMP #400,R0 ; is it inside the vector area?
BLOS 1$ ; nay, error
MOV #GENHND,(R0)+ ; use a generic handler
CLR (R0)+ ; at priority level 0
1$: .RETRN
; IODONE, clear I/O channel
; input: I/O request packet
; output: I/O channel of packet is cleared
; return: none
.MACRO $IODONE IORQP
$SYS #^RIOD,#^RONE,IORQP
.ENDM
IODONE: .ENTRY
MOV 6(R5),R4 ; argument: I/O request packet
MOVB IO$CH(R4),R3 ; get I/O channel number
DEC R3 ; channel 0 has no entry, so decrement
CMP R3,#NCHAN ; inside valid range?
BLO 1$ ; yea, go on
.RETRN
1$: MOV #SIZE$C,R2 ; get size of a I/O channel
MUL R2,R3 ; to create offset in I/O channel array
$CURJOB ; get current job
TST R0 ; no current job?
BNE 2$ ; nay, go on
.RETRN
2$: MOV J$IOCH(R0),R2 ; get I/O channel array
ADD R3,R2 ; add offset
CLR CH$HND(R2) ; clear handler and argument
CLR CH$ARG(R2)
.RETRN
; IOREQ, issue I/O request
; input: I/O request packet
; output: depends on function called
; return: carry set on unknown function or illegal I/O channel,R0 set to IO$INV
; depends on function called otherwise
.MACRO $IOREQ IORQP
$SYS #^RIOR,#^REQ,IORQP,#0
.ENDM
.MACRO $READ IORQP BSIZ BADR
MOV R5,-(SP)
MOV IORQP,R5
MOVB #IO$RDB,IO$FUN(R5)
MOV #4,IO$SIZ(R5)
MOV BSIZ,IO$DAT(R5)
MOV BADR,IO$DAT+2(R5)
MOV (SP)+,R5
$IOREQ IORQP
.ENDM
.MACRO $WRIT IORQP BSIZ BADR
MOV R5,-(SP)
MOV IORQP,R5
MOVB #IO$WRB,IO$FUN(R5)
MOV #4,IO$SIZ(R5)
MOV BSIZ,IO$DAT(R5)
MOV BADR,IO$DAT+2(R5)
MOV (SP)+,R5
$IOREQ IORQP
.ENDM
.MACRO $SEEK IORQP BCNT
MOV R5,-(SP)
MOV IORQP,R5
MOVB #IO$SKB,IO$FUN(R5)
MOV #2,IO$SIZ(R5)
MOV BCNT,IO$DAT(R5)
MOV (SP)+,R5
$IOREQ IORQP
.ENDM
.MACRO $OPEN IORQP NAM NUM TYPE
MOV R5,-(SP)
MOV IORQP,R5
MOVB #IO$OPN,IO$FUN(R5)
MOV #6,IO$SIZ(R5)
MOV NAM,IO$DAT(R5)
MOV NUM,IO$DAT+2(R5)
MOV TYPE,IO$DAT+4(R5)
MOV (SP)+,R5
$IOREQ IORQP
.ENDM
.MACRO $CLOS IORQP
MOV R5,-(SP)
MOV IORQP,R5
MOVB #IO$CLS,IO$FUN(R5)
MOV #2,IO$SIZ(R5)
MOV (SP)+,R5
$IOREQ IORQP
.ENDM
.MACRO $BSIZ IORQP
MOV R5,-(SP)
MOV IORQP,R5
MOVB #IO$BSZ,IO$FUN(R5)
MOV #2,IO$SIZ(R5)
MOV (SP)+,R5
$IOREQ IORQP
.ENDM
.MACRO $MODE IORQP,MODE
MOV R5,-(SP)
MOV IORQP,R5
MOVB #IO$MOD,IO$FUN(R5)
MOV #2,IO$SIZ(R5)
MOV MODE,IO$DAT(R5)
MOV (SP)+,R5
$IOREQ IORQP
.ENDM
; The I/O request dispatcher will not use .ENTRY, this is supposed to be done
; by the I/O request handler for the selected channel. This means that no
; registers other than R0 and R1 may be changed.
IOREQ$: TST 4(R5) ; test second argument
BNE 1$ ; nonzero? -> error
MOV 2(R5),R0 ; first argument: I/O request packet
INC R0
BIC #1,R0 ; make it word-aligned
MOV R0,R1
TSTB (R1) ; check I/O channel number
BEQ IOCTL ; shortcut to IOCTL if zero
MOVB (R1),R0 ; get I/O channel number
DEC R0 ; channel 0 has no entry, so decrement
CMP #NCHAN,R0 ; inside valid range?
BLOS 1$ ; nay, error
ASH #2,R0 ; shift to create offset
MOV R0,-(SP) ; store for later use
$CURJOB ; get current job
MOV R0,R1
MOV (SP)+,R0 ; restore I/O channel number
TST R1 ; test current job
BEQ 2$ ; no job? -> error
MOV J$IOCH(R1),R1 ; get I/O channel array
BEQ 2$ ; no I/O channels? -> error
ADD R1,R0 ; add offset
MOV CH$ARG(R0),4(R5); save handler argument as second argument
MOV CH$HND(R0),R0 ; get handler address
BEQ 2$ ; no handler? -> error
JMP (R0) ; dispatch to handler
2$: MOV 2(R5),R1 ; restore I/O request packet
1$: MOV #IO$INV,R0 ; this was an invalid request
SEC ; indicate error
RTS R5
; IOCTL, I/O request handler for I/O channel 0
IOCTL: MOVB IO$FUN(R1),R0 ; get I/O function number
ASL R0 ; shift to create offset
CMP R0,#IO$END-IO$TAB ; inside valid range?
BHI 1$ ; nay, error
JMP @IO$TAB(R0) ; dispatch to I/O function
1$: MOV #IO$INV,R0 ; this was an invalid request
SEC ; indicate error
RTS R5
; GETDEV, READ function for channel 0
; input: output buffer and buffer size
; output: device structures in output buffer
; return: carry set on error, cleared otherwise,
; R0 number of device structures read
GETDEV: .ENTRY
MOV 6(R5),R0 ; first argument: I/O request packet
CMP IO$SIZ(R0),#4 ; READ should have a size of 4
BNE 1$ ; unequal? -> error
SUB #SIZE$D,IO$DAT(R0) ; is the buffer large enough?
BMI 1$ ; nay, error
CLR -(SP) ; read count
4$: MOV IO$OFS(R0),R3 ; resume any previous read
CMP #NDEV,R3 ; no more devices left?
BLOS 2$ ; yea, return EOF
ASL R3 ; shift to create offset
MOV DEVTAB(R3),R3 ; get device structure
BEQ 2$ ; zero entry? return EOF
MOV IO$DAT+2(R0),R2 ; get output buffer
MOV #SIZE$D,R1
3$: MOVB (R3)+,(R2)+ ; copy device structure
SOB R1,3$
INC IO$OFS(R0) ; increment offset for subsequent reads
INC (SP) ; increment read count
SUB #SIZE$D,IO$DAT(R0) ; more to read?
BPL 4$ ; yea, read another
MOV (SP)+,R0 ; return read count
CLC ; no error
.RETRN
2$: MOV #IO$EOF,R0 ; EOF was reached
SEC ; indicate error
.RETRN
1$: MOV #IO$INV,R0 ; this was an invalid request
SEC ; indicate error
.RETRN
; REGDEV, WRITE function for I/O channel 0
; input: pointer to device structure, size of device structure
; output: pointer to device structure is saved in device table, device name
; event will be $DONE
; return: carry set on error, cleared otherwise
REGDEV: .ENTRY
MOV 6(R5),R4 ; argument: I/O request packet
CMP IO$SIZ(R4),#4 ; WRIT should have a size of 4
BNE 1$ ; unequal -> error
CMP #SIZE$D,IO$DAT(R4) ; size of a device structure?
BNE 1$ ; nay, error
CLR R1
3$: TST DEVTAB(R1) ; free device table entry?
BEQ 2$ ; yea, insert here
ADD #2,R1 ; get next entry
CMP R1,#NDEV*2 ; end of table?
BNE 3$ ; nay, continue
MOV #IO$BSY,R0 ; no free entry found
SEC ; indicate error
.RETRN
2$: MOV IO$DAT+2(R4),DEVTAB(R1) ; save pointer to device structure
; no copy is made
MOV DEVTAB(R1),R4 ; get device
$DONE D$NAME(R4),D$NUM(R4)
CLR R0 ; no error
CLC
.RETRN
1$: MOV #IO$INV,R0 ; invalid request
SEC ; indicate error
.RETRN
; IOINIT, CLOSE routine for I/O channel 0
; input: none
; output: new I/O channel array allocated for job, old one returned to system
; if it existed
; return: carry set on error, cleared otherwise
IOINIT: .ENTRY
MOV 6(R5),R4 ; argument: I/O request packet
CMP #2,IO$SIZ(R4) ; CLOS should have a size of 2
BNE 1$ ; unequal -> error
$CURJOB ; get current job
TST R0
BEQ 1$ ; no job? -> error
MOV R0,R3
TST J$IOCH(R3) ; test I/O channel array
BEQ 2$ ; don't free channel list if unexistant
$M$FREE J$IOCH(R3) ; free I/O channel array
2$: $M$ALLO #0,#NCHAN*SIZE$C,#^RUSR,#^RIOC,#^RHAN ; allocate new array
BCC 3$ ; continue if no error
MOV #IO$INT,R0 ; internal error
BR 5$ ; get out
3$: MOV R0,J$IOCH(R3) ; save new array in job structure
MOV #NCHAN,R1
6$: CLR (R0)+ ; clear new I/O channel array
CLR (R0)+
SOB R1,6$
CLR R0 ; no error
CLC
.RETRN
1$: MOV #IO$INV,R0 ; invalid request
5$: SEC ; indicate error
.RETRN
; IOOPEN, OPEN function for I/O channel 0
; input: device name, number and type
; output: device is opened, I/O channel created
; return: carry set on error, cleared otherwise
; R0 new I/O channel number
IOOPEN: .ENTRY
MOV 6(R5),R4 ; argument: I/O request packet
CMP #6,IO$SIZ(R4) ; OPEN should have a size of 6
BNE 1$ ; unequal -> error
$CURJOB ; get current job
TST R0
BEQ 1$ ; no job? -> error
MOV R0,R3
TST J$IOCH(R3)
BEQ 1$ ; no I/O channel array? -> error
MOV J$IOCH(R3),R3
MOV #NCHAN,R0
2$: TST (R3)+ ; search first free channel
BEQ 3$ ; found, use it
TST (R3)+
SOB R0,2$
BR 1$
3$: TST -(R3) ; rewind to free channel
MOV R0,-(SP) ; save for later reuse
MOV #DEVTAB,R1
MOV #NDEV,R0
4$: MOV (R1)+,R2 ; look for matching device
CMP IO$DAT(R4),D$NAME(R2)
BNE 5$
CMP IO$DAT+2(R4),D$NUM(R2)
BNE 5$
CMP IO$DAT+4(R4),D$TYPE(R2)
BEQ 6$
5$: SOB R0,4$
BR 1$ ; no matching device found? error
6$: .CALL @D$OPEN(R2),R2 ; call driver open routine
BCS 7$ ; abort on error
MOV D$IORQ(R2),CH$HND(R3) ; use driver I/O request handler
MOV R0,CH$ARG(R3) ; use driver supplied argument
MOV #NCHAN+1,R0
SUB (SP)+,R0 ; calculate I/O channel number
CLC ; no error
.RETRN
7$: MOV #IO$BSY,R0 ; request aborted, system busy
SEC ; indicate error
.RETRN
1$: MOV #IO$INV,R0 ; invalid request
SEC ; indicate error
.RETRN
; IOBSIZ, BSIZE function for I/O channel 0
; input: none
; output: none
; return: carry set on error, cleared otherwise
; R0 size of device structure
IOBSIZ: .ENTRY
MOV 6(R5),R0 ; argument: I/O request packet
CMP #2,IO$SIZ(R0) ; BSIZ should have a size of 2
BNE 1$ ; unequal -> error
MOV #SIZE$D,R0 ; return size of device structure
CLC ; no error
.RETRN
1$: MOV #IO$INV,R0 ; invalid request
SEC ; indicate error
.RETRN
; IOSEEK, SEEK function for I/O channel 0
; input: number of blocks to seek
; output: offset in I/O request packet adjusted by seek amount
; return: carry set on error, cleared otherwise
IOSEEK: .ENTRY
MOV 6(R5),R0 ; argument: I/O request packet
CMP #2,IO$SIZ(R0) ; SEEK should have a size of 2
BNE 1$ ; unequal -> error
ADD IO$DAT(R0),IO$OFS(R0) ; adjust offset
CLC ; no error
.RETRN
1$: MOV #IO$INV,R0 ; invalid request
SEC ; indicate error
.RETRN
GENHND: RTI
.PSECT DATA
.IOTAB GETDEV,REGDEV,IOSEEK,IOOPEN,IOINIT,IOBSIZ
DEVTAB::.BLKW NDEV
.END IOSYS