Mastering Sideways ROM & RAM - Module 13 - Trapping errors ---------------------------------------------------------- In module 8 you were shown how to deal with user-generated errors created within your own sideways RAM programs. Errors can also be generated in other sideways ROMs and it is possible to trap these errors and process them within your ROM image. This is not an easy task but it can produce some useful and impressive software. Errors generated in SWR service code are dealt with by copying an error message, preceded and followed by a BRK instruction, into the error buffer and then jumping to the BRK instruction preceding the error message. When the BRK instruction is executed, the MOS issues service call 6 to all the sideways ROMs before handing over the error to the current language. Errors generated in other, lower priority, ROMs can be trapped in your SWR program by intercepting service call 6. Ideally your program should be in socket &0F. The MOS issues service call 6 with the error number pointer (in locations &FD and &FE) containing the address of the byte after the BRK instruction and location &F0 containing the value of the stack pointer after the BRK was executed. Osbyte &BA can be used to find the number of the ROM which was active when the BRK was executed. There are 2 ways of processing errors after trapping them in your sideways RAM program: 1. Your routine can completely replace the error handler which would normally deal with the error. 2. Your routine can perform a function or print a message before handing the error back to the current language's error handling routine. In this module I will show you how to design a sideways RAM program to completely replace the normal error handling of the DFS "Cat full" and "Disc full" errors. The program will be used to copy all the files from a disc in drive 0 onto as many drive 1 discs as are necessary to hold them all. This might seem a trivial task but consider what would happen if drive 0 has a full 80 track disc and drive 1 is a 40 track drive. Using *COPY 0 1 *.* will eventually create the disc full error and stop transfering the files before they have all been copied. Using the same command will create a cat full error if the disc in drive 1 has only one or two catalogue entries unused. The program FILES instructs the command line interpreter to *COPY all the files from drive 0 to drive 1. If an error is generated the program intercepts service call 6 and tests the address stored in the error number pointer to see if either the cat full or disc full error has been generated. If it finds either it instructs the user to change the disc in drive 1 and then continues with the save operation that generated the error. When all the files have been transfered from drive 0 to drive 1 the program re-enters BASIC to clear any error condition that might exist. After explaining how to design this type of program I will show how it can be modified to pass control back to the normal error handling routine. If you intend to write a program which traps errors by intercepting service call 6 you need to know the error number of the error you intend to trap. You can either look the error number up in a manual or deliberately create the error and then look at a memory dump of the error buffer and find the error number from there. Figure 13.1 is a memory dump of the error buffer after creating a cat full and a disc full error. The error number of each error is stored in the byte after the first BRK instruction, ie. in location &101. The cat full error number is &BE and the disc full error number is &C6. 0100 00 BE 43 61 74 20 66 75 ..Cat fu 0108 6C 6C 00 00 00 00 00 00 ll...... 0110 00 00 00 00 00 00 00 00 ........ 0100 00 C6 44 69 73 6B 20 66 ..Disk f 0108 75 6C 6C 00 00 00 00 00 ull..... 0110 00 00 00 00 00 00 00 00 ........ Figure 13.1 The error buffer after Cat full and Disc full errors. ----------- ----------------------------------------------------- To trap either of these two errors it is necessary for the SWR interpreter to intercept service call 6 and test the content of the memory location stored in the error number pointer to see if it contains either &BE or &C6. If either of these numbers is found the new error handling routine can process the error, if neither is found the registers must be restored and control passed back to the MOS. The purpose of the program is to copy all the files from a disc in drive 0 onto a disc in drive 1. To do this the program will store data concerned with the task in its private workspace. The data stored in private workspace includes a flag which indicates whether or not the utility is active. If the utility is active the interpreter can trap and process the cat full and disc full errors. If it is not active the interpreter must not trap them. The routine in figure 13.2 is taken from FILES and tests a flag set up in the last byte of the first page of private workspace. This byte stores &FF if the utility is active and &00 if it is not. Control will only pass to the new error handler if the utility is active and if either the cat full or disc full error has been generated. The flag is set up when the private workspace is claimed and switched on and off when the utility starts and finishes. PHA CMP #6 \ is it service call 6? BNE out \ branch if not TXA \ store all registers PHA TYA PHA LDX &F4 \ load this ROM number LDA &DF0,X \ find start of workspace STA &AB \ store high byte of start of workspace LDA #0 \ workspace always starts at page boundary STA &AA \ low byte of start of workspace LDY #&FF \ flag address offset LDA (&AA),Y \ load flag BEQ noton \ branch if utility not active INY \ Y=0 LDA (&FD),Y \ load errror number CMP #&BE \ Cat full error number BEQ change \ branch if Cat full error CMP #&C6 \ Disc full error number BEQ change \ branch if Disc full error .noton PLA \ restore all registers TAY PLA TAX .out PLA RTS .change Figure 13.2 Checking the utility active flag. ----------- --------------------------------- As well as a utility active flag there is quite a lot of other data stored in private workspace. The entire catalogue of drive 0 is stored and workspace is needed for a command line interpreter buffer and an Osword parameter block. The program needs to claim two pages of private workspace to store all these data. Figure 13.3 is a memory dump of three parts of the two pages of private workspace claimed by the program FILES. The memory dump was made while the program FILES was transfering the first file from drive 0 to drive 1. It shows how the private workspace is used by the program. In the computer in which the memory dump was made, the private workspace claimed by FILES was in pages &17 and &18. It could be in any two consecutive pages depending on the workspace claimed by other ROMs and the priority of the program FILES. 1700 43 4F 50 59 20 30 20 31 COPY 0 1 1708 20 24 2E 54 52 41 43 45 $.TRACE 1710 20 20 0D 00 00 00 00 00 ...... 1780 00 00 18 FF FF 03 53 00 ......S. 1788 00 21 00 00 00 00 00 00 .!...... 17F8 00 00 00 00 00 00 00 FF ........ 1800 53 57 52 20 53 4F 46 54 SWR SOFT 1808 54 52 41 43 45 20 20 24 TRACE $ 1810 4F 53 57 4F 52 44 20 24 OSWORD $ 1818 4F 53 42 59 54 45 20 24 OSBYTE $ 1820 4F 53 57 44 45 4D 4F 24 OSWDEMO$ Figure 13.3 Parts of a memory dump of private workspace. ----------- -------------------------------------------- The drive 0 catalogue is read using Osword &7F and stored in the second page of the private workspace (page &18 in this case). The Osword parameter block starts at &1780. The contents of this buffer (figure 13.3) indicate a successful read data multi sector from drive 0, track 0, sector 0 with data stored at &FFFF1800. The command line interpreter buffer starts at &1700. The program transfers all the files by copying each filename from the drive 0 catalogue (in page &18) into this buffer and then calling the command line interpreter. The utility active flag is at &17FF. The contents of this flag in figure 13.2 indicate that the utility was active when the memory dump was made. There are a number of pointers set up in zero page to store the addresses of the various locations indirectly addressed in the private workspace. The absolute addresses will depend on the other ROMs in the computer but in the computer used to generate figure 13.3 they had the values shown in figure 13.4. Name high byte low byte Function -------------------------------------------------------------------- directory &17 &09 The directory of the current file private &17 &00 The start of private workspace catbuff &18 &08 The address of the first filename clibuff &17 &0B The first byte of the current filename block &17 &80 The Osword parameter block Figure 13.4 The zero page pointers. ----------- ----------------------- The SWR interpreter intercepts service call 2 to claim two pages of private workspace and set up the utility active flag to indicate that the utility is not yet active (lines 420-570). The one-command interpreter (lines 1050-1270) is used to recognise the command *FILES and control is passed to the label ".found" (line 1280). The program is designed to trap errors generated in another ROM and for this reason the stack is balanced before doing anything else (lines 1290-1310). Errors generated outside your control can play havoc with the stack and balancing it at this stage minimises the problems that can be created. The program checks that the DFS is active (lines 1320-1370) and if all is well prints a "Copy all files - Go?" message (lines 1500-1520). If the user types a "Y" contol is passed to the label ".yes" (line 1630). The zero page pointers (figure 13.4) are set up (lines 1640-1810) and the utility active flag is set to indicate that errors &BE and &C6 should be trapped (lines 1820-1840). The Osword parameter block contents are copied into the Osword buffer (lines 1850-1910) and the most significant byte of the address of the catalogue buffer is also stored in the Osword buffer (lines 1920-1940). The first part of the *COPY command is transfered into the command line interpreter buffer (lines 1950-2010) and the least significant byte of the address of the first file in the drive 0 catalogue is stored in the zero page pointer to the command line interpreter buffer (line 2020). The drive 0 catalogue is read and stored in the second page of the private workspace (lines 2030-2060). The files are transfered, one at a time, from drive 0 to drive 1. (lines 2070-2320). Up to 31 files will be copied but if either the cat full or disc full errors is generated by the DFS the interpreter will trap the error (lines 610-800) and pass control to the label ".change" (line 890). The stack is pulled six times to balance it. The stack has the registers from the SWR interpreter as well as the status register and return address pushed on when the DFS BRK was executed. These can all be discarded. The program pROMpts the user to change the disc in drive 1 (lines 960-1030) and then returns control to the loop transfering the files (line 1040 and line 2270). When all the files have been transfered from drive 0 to drive 1, the program clears the utility active flag (lines 2340-2360), flushes all the buffers (lines 2380-2400), finds BASIC (lines 2410-2440) and enters BASIC (lines 2450-2460) which clears all error conditions. To modify FILES to pass control back to the normal error handling routine delete lines 930-950 and 2410-2460. Type the new line 2410. 2410 LDA #0:RTS The program will perform the same function but report the Disc full or Cat full error message at the end of the file transfers. You could also modify the program to use pages &09 and &0A instead of private workspace. This would make the program shorter and not raise Oshwm and PAGE but, if you use these pages instead of private workspace, you can not be sure that your workspace will remain uncorrupted (see Module 11). 10 REM: FILES 20 MODE7 30 HIMEM=&3C00 40 DIM save 50 50 diff=&8000-HIMEM 60 directory=&A8 70 private=directory+2 80 catbuff=directory+4 90 clibuff=directory+6 100 block=directory+8 110 address=directory+10 120 comvec=&F2 130 ROMnumber=&F4 140 errornum=&FD 150 errstack=&100 160 workspace=&DF0 170 osargs=&FFDA 180 osrdch=&FFE0 190 osasci=&FFE3 200 osnewl=&FFE7 210 osword=&FFF1 220 osbyte=&FFF4 230 oscli=&FFF7 240 FOR pass = 0 TO 2 STEP 2 250 P%=HIMEM 260 [ OPT pass 270 BRK 280 BRK 290 BRK 300 JMP service+diff 310 OPT FNequb(&82) 320 OPT FNequb((copyright+diff) MOD 256) 330 BRK 340 .title 350 OPT FNequs("FILES") 360 .copyright 370 BRK 380 OPT FNequs("(C)1987 Gordon Horsington") 390 BRK 400 .service 410 PHA 420 CMP #2 430 BNE tryfour 440 TYA 450 PHA 460 STA workspace,X 470 LDA #0 480 STA private 490 STY private+1 500 LDY #&FF 510 STA (private),Y \ 0 = utility off 520 PLA 530 TAY 540 INY \ 2 pages of 550 INY \ private workspace 560 PLA 570 RTS 580 .tryfour 590 CMP #4 600 BEQ unrecognised 610 CMP #6 620 BNE out 630 TXA 640 PHA 650 TYA 660 PHA 670 LDX ROMnumber 680 LDA workspace,X 690 STA private+1 700 LDA #0 710 STA private 720 LDY #&FF 730 LDA (private),Y 740 BEQ noton 750 INY 760 LDA (errornum),Y 770 CMP #&BE \ Cat full 780 BEQ change 790 CMP #&C6 \ Disc full 800 BEQ change 810 .noton 820 PLA 830 TAY 840 PLA 850 TAX 860 .out 870 PLA 880 RTS 890 .change 900 PLA \ Y 910 PLA \ X 920 PLA \ A 930 PLA \ Status register 940 PLA \ Return 950 PLA \ address 960 LDX #(swapdisc+diff) MOD 256 970 LDY #(swapdisc+diff) DIV 256 980 JSR print+diff 990 .spacebar 1000 LDA #&7A 1010 JSR osbyte \ Scan keyboard 1020 CPX #&62 1030 BNE spacebar \ Wait for Space 1040 JMP another+diff 1050 .unrecognised 1060 TXA 1070 PHA 1080 TYA 1090 PHA 1100 LDX #&FF 1110 .comloop 1120 INX 1130 LDA title+diff,X 1140 BEQ found 1150 LDA (comvec),Y 1160 INY 1170 CMP #ASC(".") 1180 BEQ found 1190 AND #&DF 1200 CMP title+diff,X 1210 BEQ comloop 1220 PLA 1230 TAY 1240 PLA 1250 TAX 1260 PLA 1270 RTS 1280 .found 1290 PLA 1300 PLA 1310 PLA 1320 LDA #0 1330 TAX 1340 TAY 1350 JSR osargs 1360 CMP #4 \ Is DFS selected? 1370 BEQ dfs 1380 LDA #(wrong+diff) MOD 256 1390 STA address 1400 LDA #(wrong+diff) DIV 256 1410 STA address+1 1420 LDY #&FF 1430 .errorloop 1440 INY 1450 LDA (address),Y 1460 STA errstack,Y 1470 BPL errorloop 1480 JMP errstack 1490 .dfs 1500 LDX #(go+diff) MOD 256 1510 LDY #(go+diff) DIV 256 1520 JSR print+diff 1530 JSR osrdch 1540 AND #&DF 1550 PHA 1560 JSR osasci 1570 JSR osnewl 1580 PLA 1590 CMP #ASC("Y") 1600 BEQ yes 1610 LDA #0 1620 RTS 1630 .yes 1640 JSR osnewl 1650 LDX ROMnumber 1660 LDA workspace,X 1670 STA block+1 1680 STA clibuff+1 1690 STA private+1 1700 STA directory+1 1710 CLC 1720 ADC #1 1730 STA catbuff+1 1740 LDA #&80 1750 STA block 1760 LDY #8 1770 STY catbuff 1780 INY 1790 STY directory 1800 LDA #0 1810 STA private 1820 LDA #&FF 1830 TAY 1840 STA (private),Y \ &FF = utility on 1850 INY \ Y=0 1860 .dataloop 1870 LDA data+diff,Y 1880 STA (block),Y 1890 INY 1900 CPY #&A 1910 BCC dataloop 1920 LDY #2 1930 LDA catbuff+1 1940 STA (block),Y 1950 LDY #0 1960 .copyloop 1970 LDA copy+diff,Y 1980 STA (private),Y 1990 INY 2000 CPY #&B 2010 BCC copyloop 2020 STY clibuff 2030 LDA #&7F 2040 LDX block 2050 LDY block+1 2060 JSR osword \ Read catalogue 2070 .catalogue 2080 LDY #0 2090 .transfer 2100 LDA (catbuff),Y 2110 STA (clibuff),Y 2120 INY 2130 CPY #7 2140 BCC transfer 2150 LDA #&D 2160 STA (clibuff),Y 2170 LDA (catbuff),Y 2180 AND #&7F 2190 CMP #ASC(" ") 2200 BCC finish 2210 LDY #0 2220 STA (directory),Y 2230 LDA catbuff 2240 CLC 2250 ADC #8 2260 STA catbuff 2270 .another 2280 LDX #0 2290 LDY clibuff+1 2300 JSR oscli 2310 LDA catbuff 2320 BNE catalogue 2330 .finish 2340 LDA #0 2350 LDY #&FF 2360 STA (private),Y \ 0 = utility off 2370 JSR osnewl 2380 LDA #&F 2390 LDX #0 2400 JSR osbyte \ Flush buffers 2410 LDA #&BB 2420 LDX #0 2430 LDY #&FF 2440 JSR osbyte \ Find BASIC 2450 LDA #&8E 2460 JMP osbyte \ Enter BASIC 2470 .print 2480 STX address 2490 STY address+1 2500 LDA #&F 2510 LDX #0 2520 JSR osbyte \ Flush buffers 2530 LDY #0 2540 .printloop 2550 LDA (address),Y 2560 BEQ endprint 2570 JSR osasci 2580 INY 2590 JMP printloop+diff 2600 .endprint 2610 RTS 2620 .data 2630 OPT FNequd(&FF000000) 2640 OPT FNequd(&005303FF) 2650 OPT FNequw(&2100) 2660 .copy 2670 OPT FNequs("COPY 0 1 *.") 2680 .swapdisc 2690 OPT FNequw(&070D) 2700 OPT FNequs("Insert a new disc in ") 2710 OPT FNequb(&3A) 2720 OPT FNequs("1") 2730 OPT FNequb(&0D) 2740 OPT FNequs("and press the Space Bar") 2750 OPT FNequw(&0D0D) 2760 BRK 2770 .go 2780 OPT FNequs("Copy all files from ") 2790 OPT FNequb(&3A) 2800 OPT FNequs("0 to ") 2810 OPT FNequb(&3A) 2820 OPT FNequs("1") 2830 OPT FNequb(&0D) 2840 OPT FNequs("Go (Y/N) ? ") 2850 BRK 2860 .wrong 2870 BRK 2880 OPT FNequb(&FE) 2890 OPT FNequs("DFS not active") 2900 BRK 2910 OPT FNequb(&FF) 2920 .lastbyte 2930 ] 2940 NEXT 2950 INPUT'"Save filename = "filename$ 2960 IF filename$="" END 2970 $save="SAVE "+filename$+" "+STR$~(HIMEM)+" "+STR$~(las tbyte)+" FFFF8000 FFFF8000" 2980 X%=save 2990 Y%=X% DIV 256 3000 *OPT1,2 3010 CALL oscli 3020 *OPT1,0 3030 END 3040 DEFFNequb(byte) 3050 ?P%=byte 3060 P%=P%+1 3070 =pass 3080 DEFFNequw(word) 3090 ?P%=word 3100 P%?1=word DIV 256 3110 P%=P%+2 3120 =pass 3130 DEFFNequd(double) 3140 !P%=double 3150 P%=P%+4 3160 =pass 3170 DEFFNequs(string$) 3180 $P%=string$ 3190 P%=P%+LEN(string$) 3200 =pass