;***************************************************************************
;* ************************************************************
;* SPOOL.M68 ************************************************************
;* ************************************************************
;***************************************************************************
;
; Programmed by: John Ryan
; Ryan & Vint Associates
; Santa Ana, CA
; (714)835-3073
;
; Replacement for AM's SPOOL.SBR 1.0(100)-1, which either causes parity errors
; or keeps hanging the system when called with more than one parameter.
;
;
SEARCH SYS
SEARCH SYSSYM
;
ARGBLK=10. ; BASIC argument block size
;
OBJNAM 0,0,[SBR]
EXTERN $GTARG
;
; Program variables - impure area allocated from free region @A1:
.OFINI
.OFDEF SWITCH,2 ; Switches - see BASIC manual for list
.OFDEF PRINTR,4 ; Printer name packed RAD50
.OFDEF COPIES,2 ; Number of copies
.OFDEF BLOCKS,2 ; Number of blocks required
.OFDEF FORM,4 ; Form name packed RAD50
.OFDEF WIDTH,1 ; Header line width
.OFDEF LPP,1 ; Lines per page
.OFDEF DDBBUF,512. ; Buffer for DDB
.OFDEF DDB,D.DDB ; DDB for file to spool
EVEN
.OFSIZ IMPSIZ ; Size of impure area
;
; Line printer queue block offsets for elements within queue for a particular
; printer. LPTQUE => first queue element, which is a list of printers. Each
; printer begins another queue of requests pending for that printer.
; These offsets are from start of the data area (after 1st longword pointer to
; next queue block or 0 if none) as deduced from disassembly of SPOOL).
; Names are my own of course.
LP.FIL=0 ; file name
LP.EXT=LP.FIL+4 ; extension
LP.DEV=LP.EXT+2 ; device name
LP.PPN=LP.DEV+2 ; PPN
LP.UNI=LP.PPN+2 ; device unit number
LP.COP=LP.UNI+2 ; copies
LP.SWI=LP.COP+2 ; switches
LP.FRM=LP.SWI+2 ; form name
LP.LPP=LP.FRM+4 ; lines per page
LP.WID=LP.LPP+2 ; width (also 1 word)
;
; Offsets for first printer queue, listing available printers
LP.PTR=4 ; addr of 1st element in queue this printer
LP.NAM=10 ; printer name word (RAD50)
LP.BLK=20 ; number of blocks pending for this printer
LP.FLG=22 ; flag word
LP.JOB=24 ; addr of spooler job using this printer
;
LP.DEF=20 ; bit pattern in flag word - if set,
; indicates this is the default printer
;
VMAJOR=1.
VMINOR=0.
VSUB=1.
VEDIT=1.
PHDR -1,0,PH$REE!PH$REU
;
; Setup code - use A1 to point to base of my impure area
SPOOL:: EVNA A4 ; ensure A4 is even
MOV A4,A1 ; point to base
ADD #IMPSIZ,A4 ; add size - so later $GTARG calls ok
CMP A4,A5 ; make sure there's room
JHIS ERR1
;
CALL CLRARG ; clear impure area
CALL GETARG ; get arguments and store
JNE BACK
;
MOV LPTQUE,A2 ; line printer queue head
MOV A2,D7 ; copy so sets condition codes
JEQ NOPRT ; no printers set up for spooling
TST PRINTR(A1) ; was the printer name passed?
JEQ FNDPRT ; no - find the default printer
10$: MOV PRINTR(A1),D7 ; yes - find specified printer
CMP D7,LP.NAM(A2) ; right name?
BEQ HAVPRT ; match
MOV @A2,A2 ; point to next element (& set Z bit)
MOV A2,D7 ; copy to set condition codes
JEQ NOPRT ; no more to test?
BR 10$ ; test the next
; A1 is my impure pointer
; A2 points to printer queue element to use
HAVPRT: LOOKUP DDB(A1) ; setup D.WRK area
JNE BACK ; file not found
JOBIDX A6
MOVW JOBUSR(A6),D1 ; get current PPN
CMPW D1,DDB+D.PPN(A1) ; compare to file's
BEQ CKQUE ; same - ok
ANDW #-5,SWITCH(A1) ; reset deletion switch if PPNs
ORW #10,SWITCH(A1) ; differ
CKQUE: CMP QFREE ,#15. ; at least 15 free queue blks remain?
BLOS WTQUE
JLOCK ; no context switch while updating queue
MOVW COPIES(A1),D0 ; number copies requested
MOV DDB+D.WRK(A1),D2 ; number of blocks in file
MUL D0,D2 ; total number blocks required in D0
CMP D0,#^H0FFFF ; range check
BLOS 20$ ; use unsigned arithmetic
10$: CALL ERR5 ; inform caller
BR DONE ; and return
20$: MOVW D0,BLOCKS(A1) ; save number blocks required
ADDW LP.BLK(A2),D2 ; bump number pending
BVS 10$ ; too many?
LEA A3,LP.PTR(A2) ; addr of start of queue for this printer
QADD A3 ; add block to end of list
BNE DONE ; no block available
MOVW D2,LP.BLK(A2) ; save total pending for printer
; fill in queue block
MOV DDB+D.FIL(A1),(A3)+ ; filename from DDB
MOVW DDB+D.EXT(A1),(A3)+ ; extension
MOVW DDB+D.DEV(A1),(A3)+ ; device
MOVW DDB+D.PPN(A1),(A3)+ ; PPN
MOVW DDB+D.DRV(A1),(A3)+ ; unit number
MOVW COPIES(A1),(A3)+ ; copies
MOVW SWITCH(A1),@A3 ; switches
ANDW #-401,(A3)+ ; clear extra bits
MOV FORM(A1),(A3)+ ; copy form
MOVW BLOCKS(A1),(A3)+ ; number of blocks
MOVB LPP(A1),(A3)+ ; lines per page
MOVB WIDTH(A1),(A3)+ ; page width
DONE: JUNLOK ; permit context switching
MOV LP.JOB(A2),A0 ; point to spooler for this printer
JRUN 40 ; start up the spooler
RTN ; to BASIC - normally
;
; **************************************************************************
; Extended mainline routines
; See if should wait for queue blocks or just return
WTQUE: MOVW SWITCH(A1),D7
ANDW #400,D7
BEQ 10$
MOV #10000.,D6 ; 1 second wait
SLEEP
JMP CKQUE
10$: TYPECR ?Not enough queue blocks for spooler request
RTN ; to user - not enough queue space
NOPRT: TYPECR ?Unable to find specified printer
BACK: RTN ; to user
; find the default printer to use - A2 points to head of line printer queue
FNDPRT: MOVW LP.BLK(A2),D2 ; number of blocks
; use SP as save area for pointer to queue element chosen
PUSH A2 ; first printer in queue
MOVW LP.FLG(A2),D7 ; test - is this the default printer?
ANDW #LP.DEF,D7 ; bit test
BNE GOTPRT ; yes
10$: MOV @A2,A2 ; point to next element and repeat test
MOV A2,D7 ; set condition codes
BEQ GOTPRT ; any more?
MOVW LP.FLG(A2),D7 ; get status
ANDW #LP.DEF,D7 ; bit test
BNE 20$ ; have printer => br
CMPW D2,LP.BLK(A2) ; if none flagged, use printer with
BLO 10$ ; fewest pending blocks
MOVW LP.BLK(A2),D2 ; D2 saves least number blocks
MOV A2,@SP ; (SP) saves ptr to queue w/ least blks
BR 10$ ; search next queue element
20$: MOV A2,@SP ; set up for next instruction
GOTPRT: POP A2 ; set A2 to queue element to use
JMP HAVPRT ; return
;
; ***** Subroutines ********************************************************
;
; **************************************************************************
; These were extracted from WLSRCH subroutine for speedy use, with
; minor modifications - JRR.
; **************************************************************************
;
; Clear the impure area from (A1)
CLRARG: MOV A1,A6
MOV #<IMPSIZ/2>-1,D7
1$: CLRW (A6)+
DBF D7,1$
RTN
;
; Get the parameters passed:
; D0 = index to argument blocks
; D1, D2 = parameter passing registers
; D3, D4 = work registers
; A0 = BASIC impure area pointer
; A3 = BASIC argument pointer
; A4 = BASIC's free pointer
; A5 = BASIC's arithmetic stack
; A1 = my impure area pointer
; A2 = string pointer work register
; A6 = work register (not preserved across monitor calls)
GETARG: TSTW (A3) ; see if at least 1 argument passed
JEQ ERR2 ; nope
CMPW (A3),#7 ; more than 7 arguments?
JGT ERR2 ; yep - jump
LEA A4,DDB(A1) ; Use A4 as DDB base register
MOV #2,D0 ; initialize argument pointer
;
MOVW #1,COPIES(A1) ; set copy default
MOVW #[NOR],FORM(A1) ; set printer form default
MOVW #[MAL],FORM+2(A1)
;
CALL GTADDR ; get addr of filename parm
JNE 110$ ; error return
LEA A6,DDBBUF(A1) ; point to buffer
MOV A6,DDB+D.BUF(A1) ; save absolute buffer addr in DDB
MOVB #D$INI!D$ERC,DDB+D.FLG(A1) ; flag buffer inited, return on error
MOV D1,A2 ; copy pointer to addr
MOV A2,A6 ; another copy
ADD D2,A6 ; add size
MOV A6,D4 ; save end pointer
MOVB (A6),D3 ; save last byte
CLRB (A6) ; clear the byte - ensure legal string
FSPEC DDB(A1),LST ; set up DDB
MOV D4,A6 ; restore end pointer
MOVB D3,(A6) ; restore former byte
TSTB DDB+D.ERR(A1) ; check for errors
JNE 110$ ; return if so
;
; LOOKUP use 0 defaults - ok for INPUT, etc. but not printer spooler
; replace defaults with job's defaults
JOBIDX A6
TSTW DDB+D.PPN(A1) ; PPN specified?
BNE 10$
MOVW JOBUSR(A6),DDB+D.PPN(A1)
10$: TSTW DDB+D.DEV(A1) ; device name
BNE 20$
MOVW JOBDEV(A6),DDB+D.DEV(A1)
MOVW JOBDRV(A6),DDB+D.DRV(A1)
BR 30$
20$: CMPW DDB+D.DRV(A1),#-1
BNE 30$
MOVW JOBDRV(A6),DDB+D.DRV(A1)
30$: CMPW (A3),#1 ; 1 argument?
JEQ 100$ ; yes - we are done
CALL GTADDR ; get addr of printer name string
JNE 110$ ; error
MOV A1,D3 ; save my impure pointer
LEA A1,PRINTR(A1) ; point A1 to buffer
MOV D1,A2 ; A2 points to string to pack
PACK
PACK
MOV D3,A1 ; restore impure pointer
;
CMPW (A3),#2 ; 2 arguments?
JEQ 100$ ; yes => done
CALL GTNMBR ; get a number
JNE 110$ ; error?
MOVW D1,SWITCH(A1) ; save switches
;
CMPW (A3),#3 ; 3 arguments?
JEQ 100$ ; done?
CALL GTNMBR ; get number
JNE 110$ ; error?
CMP D1,#^H0FFFF ; out of range?
BLOS 40$ ; use unsigned arithmetic
CALL ERR4 ; inform caller
JMP 110$ ; and take error return
40$: MOVW D1,COPIES(A1) ; save copies requested
BNE 42$ ; no - leave default
MOVW #1,COPIES(A1) ; restore copy default
;
42$: CMPW (A3),#4 ; 4 arguments?
JEQ 100$ ; done?
CALL GTADDR ; get addr of form string
JNE 110$ ; error?
MOV A1,D3 ; save my impure pointer
LEA A1,FORM(A1) ; point A1 to buffer
MOV D1,A2 ; A2 points to string to pack
PACK
PACK
MOV D3,A1 ; restore impure pointer
TST FORM(A1)
BNE 44$
MOVW #[NOR],FORM(A1) ; restore printer form default
MOVW #[MAL],FORM+2(A1)
;
44$: CMPW (A3),#5 ; 5 arguments?
JEQ 100$ ; done?
CALL GTNMBR ; get number
JNE 110$ ; error?
CMP D1,#^H0FF ; out of range?
BLOS 50$ ; use unsigned arithmetic
CALL ERR4 ; inform caller
JMP 110$ ; and take error return
50$: MOVB D1,WIDTH(A1) ; save line width for header
;
CMPW (A3),#6 ; 6 arguments?
JEQ 100$ ; done?
CALL GTNMBR ; get number
JNE 110$ ; error?
CMP D1,#^H0FF ; out of range?
BLOS 60$ ; use unsigned arithmetic
CALL ERR4 ; inform caller
JMP 110$ ; and take error return
60$: MOVB D1,LPP(A1) ; save lines per page
;
;
100$: LCC #4 ; normal return
RTN
110$: LCC #0 ; error return
RTN
;
; Return a numeric argument value into D1 (binary, fp or string)
; Address of argument returned in D2
; A3, A5 are as entered subroutine from BASIC - D0 has offset to argument blk
GTNMBR: MOV D0,D1 ; index to D1
MOV 2(A3)[D1],D2 ; save address
ANDW #7,0(A3)[D1] ; clear meaningless & subscript bit
CMPW 0(A3)[D1],#0 ; unformatted variable?
JEQ ERR3 ; yes - a no no
CMPW 0(A3)[D1],#6 ; binary variable?
BNE 1$ ; nope
CMP 6(A3)[D1],#4 ; 4 bytes or less?
JGT ERR3 ; no => error
MOV 2(A3)[D1],A6 ; addr to A6
MOV @A6,D1 ; binary var to D1
BR NXTARG
1$: CALL $GTARG ; decode fp or string variable
JNE ERR3 ; conversion error check
NXTARG: ADD #ARGBLK,D0 ; bump argument index
LCC #4 ; set Z bit
RTN
;
; Return an address in D1, and size in D2 - strings or unformatted variables
; A3 = Basic argument pointer
; D0 = argument index
GTADDR: MOV D0,D1 ; put index into D1
ANDW #7,0(A3)[D1] ; clear meaningless & subscript bit
CMPW 0(A3)[D1],#4 ; fp?
JEQ ERR3
CMPW 0(A3)[D1],#6 ; binary?
JEQ ERR3
; Don't check variable type
GTADR2: MOV D0,D1 ; set up index again
MOV 6(A3)[D1],D2 ; size to D2
MOV 2(A3)[D1],D1 ; addr to D1
BR NXTARG
;
; ***************************************************************************
; Error routines
ERR1: TYPE <?Insufficient impure memory space>
JMP ERRMSG
ERR2: TYPE <?Missing or excess arguments>
JMP ERRMSG
ERR3: TYPE <?Bad argument #>
ERR31: SUB #2,D0 ; subtract 1st word
MOV #1,D1 ; counter for # arguments
1$: TST D0 ; when 0, at the argument
BLE 2$
SUB #ARGBLK,D0
INC D1
BR 1$
2$: DCVT 2,OT$TRM!OT$LSP!OT$TSP ; decimal #, bracket w/ blanks
BR ERRMSG
ERR4: TYPE <?Result too large for argument #>
SUB #ARGBLK,D0
BR ERR31
ERR5: TYPE <?Too many blocks total pending to print>
ERRMSG: TYPECR < in XCALL SPOOL>
LCC #0 ; clear Z bit
RTN
;
END