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