In This Issue...
Sorry we haven't written sooner...
This really is the March issue you're receiving sometime in the middle of April, and there's no blaming the Post Office. We've had a surplus of other work lately and a shortage of AAL inspiration. (That's a hint: you can send in articles and programs and help us and all AAL readers. We are especially interested in good short items about exploring the IIgs.) We were almost back on track by last December, but slipped again. We expect to catch up across the next several months....
About those Disk Subscriptions
When you subscribe to the Apple Assembly Line Monthly Disks you not only receive all the source code from an issue, you now also get the text of all that month's articles (in DOS 3.3 Text files.) We know of six blind subscribers who really appreciate this format, and there may be more of you who are interested. Our thanks to Larry Skutchan, of the American Printing House for the Blind, for prompting us to add this service.
Apple recently released version 1.3 of ProDOS, and sent copies to all licensees. We started sending it out with the ProDOS version of the S-C Macro Assembler. But last week (around March 16th) we received a letter from Apple "recalling" version 1.3. It has two serious problems.
First, there is a "BRA" (BRanch Always) opcode in it. This means version 1.3 will not operate in an older Apple with only a 6502 microprocessor. If you have a 65C02 or 65802 or 65816, no problem here. You are safe in a IIgs, //c, or enhanced //e. You are also safe if you have upgraded the cpu chip yourself in an older machine. That offending instruction could just as well be changed to a BEQ opcode, because that would always branch in this case. With that change the 6502 machines work fine.
Second, when the Apple experts tried to implement the patch developed in Australia and reported first by Tom Weishaar in "Open Apple" to fix an elusive disk-trashing problem, they didn't do it right. This is the same fix I reported in the November AAL, page 13. (Turns out I didn't report it right either, because I overlooked one part of the patch. More on this below.) The way Apple did it causes severe problems with two-drive systems. When you are accessing drive two, version 1.3 keeps switching back to drive 1. If you try to use FILER to copy a volume from drive 1 to a blank disk in drive 2, and if you remember to write-protect the disk in drive 1, FILER will hang up with an I/O ERROR after initializing the drive 2 disk. FILER evidently applies the drive 1 write-protect status to drive 2, and gives up. I don't even want to experiment without having drive 1 write-protected! On the other hand, if you copy from drive 2 to drive 1 it works, but it takes a lot longer than it should to read each segment from the source disk in drive 2.
Both bugs can be fixed by rather simple patches. Boot up PRODOS, into either BAISC.SYSTEM or the S-C Macro Assembler. Then load the PRODOS file, with "BLOAD PRODOS,TSYS,A$2000". Then get into the monitor with "MNTR" from S-C Macro Assembler or "CALL-151" from BASIC.SYSTEM. Type the following patches:
4CCD:F0 (was 80, changing BRA to BEQ) 5204:BD 8E C0 (was EA EA EA) 58C3:BD 80 C0 BD 82 C0 BD 84 C0 BD 86 C0
Then get back into the system by typing "3D0G", and save the new version with "BSAVE PRODOS,TSYS,A$2000".
The third patching line above replaces Apple's flawed loop which walked on too many soft-switches. Apple's loop does a LDA from C080, C082, C084, and C086; this is correct. It also does it from C088, C08A, C08C, and C08E. This is not correct. It turns off the motor and selects drive 1. The only correct one among this group of four was C08E, intended to be sure the selected drive is in read mode.
Old Code Apple's Loop My Patch ----------- --------------- ----------- STA $C080,X LDY #8 LDA $C080,X STA $C082,X .1 LDA $C080,X LDA $C082,X STA $C084,X INX LDA $C084,X STA $C086,X INX LDA $C086,X DEY BNE .1 NOP NOP
My patch puts the C08E load where Tom's Australian-connection originally put it, over some NOPs which immediately followed the JSR to the code shown above.
Now about my incomplete patch to version 1.1.1 from last November. I omitted the "LDA $C08E,X", which gets patched at location $5004 in this version. I also mis-typed the address for the other patches as going at $56D3 when they actually belong at $56C3. So, in version 1.1.1, following the same load-patch-save sequence above, the patches are:
5004:BD 8E C0 (was EA EA EA) 56C3:BD 80 C0 BD 82 C0 BD 84 C0 BD (changing 9D's to BD's)
Version 1.4 is due out soon, and we trust Apple will have it all right this time. Still, I am getting skittish.
Meanwhile, unless you are using a IIgs, you may wish to stick with version 1.1.1. The only real advantage the newer versions have is automatic recognition of the IIgs clock-calendar chip. You don't need this feature if you are not running in a IIgs.
Bob's article "New Features in Apple //gs Monitor" in the January 1987 issue of AAL was interesting and useful, but I have several corrections. I also have two nice examples for the U-command, which allows you to call tools from the monitor.
First, I noticed Bob said that the Change Cursor Command is Control-6 followed by the new cursor character. While that works on his keyboard, the actual character is Control-Shift-6. Apparently Bob's IIgs, which is a //e box and keyboard with a IIgs motherboard, accepts Control-6 and Control-Shift-6 to generate the same code ($9E). In "true" ASCII, this key is really known as Control-Shift-N. If you remember the old Apple II or II+ keyboards, which were modeled after teletypes, the carat character (ASCII $DE) was generated by a Shift-N; likewise, a Control-Shift-N generated the ASCII code $9E. On the //e, //c, and IIgs the carat has been moved to the Shift-6 position. Several other characters were moved at the same time, allowing the all of the alphabet keys could use the Shift Key to control lower- and upper-case input.
Second, Bob apparently misunderstood the meaning of the Flip ASCII capability. When you are entering data into RAM with the "addr:" command, or specifying patterns for the "\pattern\<addr.addrP" search command, you can enter ASCII strings or flip ASCII strings.
Normal strings are entered by typing any number of characters between quotation marks. The status of the ASCII FILTER MASK determines whether the string is low-ASCII or high-ASCII. You control the filter mask using the monitor "=" command: type "7F=F" before entering strings you want in low-ASCII, or "FF=F" before strings in high-ASCII.
Flipped strings can be from one to four characters. The characters are typed between apostrophes, and will be stored in RAM or used as a pattern in REVERSE ORDER. Again, the characters will be in either low- or high-ASCII depending on the state of the filter mask. For example:
*FF=F *300:"ABCDEFGH" *300.307 00/0300:C1 C2 C3 C4 C5 C6 C7 C8-ABCDEFGH *7F=F *300:"ABCDEFGH" *300.307 00/0300:41 42 43 44 45 46 47 48-ABCDEFGH *300:'ABCD' 'EFGH' (note filter still 7F) *300.307 00/0300:44 43 42 41 48 47 46 45-DCBAHGFE *FF=F 300:'ABC' 'MNO' 'ST' *300.307 00/0300:C3 C2 C1 CF CE CD D4 D3:CBAONMTS
The monitor "U" command gives you the ability to call tools in the various tool sets. In particulary, I have worked out the method for calling the two time routines in the Miscellaneous Tool Set.
Call ReadTimeHex (tool set $03, function $0D)
*\08 08 00 00 00 00 00 00 00 00 0D 03\U Tool Error-> 0000 06 1F 17 57 1B 02 00 07
Interpretation of Input:
08 = 8 bytes on stack 08 = 8 bytes off stack 00...00 = 8 zero bytes constitute parms 0D = function number in toolset 03 = toolset number
Interpretation of Output:
Error 0000 means NO ERROR 06 = 6 seconds 1F = 31 minutes 16 = 22 hours 57 = year 1987 17 = 24th day (0=first day) 02 = 3rd month (0=first month) 00 = null 03 = 3rd day of week (1=Sunday, first day)
Call ReadASCIITime (tool set $03, function $0F):
*\04 00 00 00 03 00 0F 03\U 300.317 Tool Error-> 0000 00/0300:A0 B4 AF A0 B1 AF B8 B7: 4/ 1/87 00/0308:A0 A0 B9 BA B1 B3 BA B0: 9:13:0 00/0310:B3 A0 C1 CD FF 00 00 FF:3 AM....
Interpretation of Input:
04 = 4 bytes on stack 00 = 0 bytes off stack 00 00 03 00 = buffer address at 00/0300 0F = function number in toolset 03 = toolset number
Interpretation of Output:
Error 0000 means NO ERROR 300.313 has string " 4/ 1/87 9:13:03 AM"
Ever since ProDOS first appeared in 1983 people have been asking us about an Applesoft compiler that would run under that system, but we've had to tell them that there was no such thing. Well the good folks at Beagle Bros have remedied that situation by bringing us The Beagle Compiler, by Alan Bird.
This isn't a true native code compiler, it's more of a very high-speed interpreter. As a result, the compiled program is usually smaller than the original source Applesoft program. On the other hand you lose some memory because of the extra space taken by the compiler's runtime system. The compiler and runtime system together take about 11K, but you can cut that figure in half if you only need the runtime portion. If your program will not fit the first time you try, you can probably take advantage of the compiler's ability to use //e or //c auxiliary memory for storage of arrays and strings.
Beagle claims a 2 to 15 times speedup of a program, depending on what operations are most common. They say that string and variable manipulations show the most improvement, while heavy floating point calculations aren't affected at all. The compiled code automatically uses integer arithmetic whenever possible, thereby avoiding a lot of floating point arithmetic in most programs.
There are several programs in the package: COMPILER.SYSTEM is the normal runtime interpreter, replacing and extending BASIC.SYSTEM. COMPILER processes an Applesoft program file into its high-speed version. AUX.SLOT.SYSTEM & APPLEMEM.SYSTEM (included with version 2.0 and beyond) are versions of COMPILER.SYSTEM that take advantage of memory expansion cards for array and string storage. You have to have one of the SYSTEM files to execute a compiled program, but you can distribute these files with your product "after paying a very reasonable licensing fee."
The Beagle Compiler is made to be very simple to use. After booting into the SYSTEM your STARTUP program can execute COMPILER. Then you just need to type RUN <filename> to load your file, compile it, and start execution. Once you have a finished program you can type COMPILE <source file>,<object file> to produce a disk file of the compiled code. To execute that file you no longer need COMPILER, just one of the SYSTEM files.
As supplied, the compiler uses the standard Applesoft INPUT, but they also include an "INPUT anything" routine that allows commas and semicolons. There is also a FAST.HPLOT routine available that noticeably speeds up HPLOT, but at the cost of an extra 1K of memory for the runtime code. A special SLOW.PDL patch can slow down game paddle reading so you always get the correct reading.
This compiler, unlike other Applesoft compilers we've seen, allows full use of &-routines, including parameter passing. An &-call without parameters will work just fine as it's written; one with parameters will need to be rewritten to match the compiler's syntax and evaluation needs, but the new procedures are easy to use and very well documented. It's not immediately clear how well an "&-interpreter" will work with this approach.
Beagle does an excellent job of documenting how a machine language program can get along with a compiled program. They list all the keyword entry points as well as the entries to many useful utility routines, including string and numeric output, error handling, string allocation and handling, variable assigment and evaluation, and many others.
They also provide complete details of the unique method the compiler uses to store and access its variables. All variables are referred to by a one byte index, so there can be up to 256 different variables or arrays. By the way, this really is an upper limit on the number of variables. For some reason there is no clear statement in the manual saying so, but if you have more than 256 different variable names it cannot be compiled. The single-byte variable index is used through six different pointers to access the values. The six pointers are:
$78 Variable Type (array, string, numeric, FN, integer) $7A Val1 -- array: LSB of header address string: LSB of string address FP: non-zero flags FP, 1st byte of value integer: a zero here flags integer $7C Val2 -- array: MSB of header address string: MSB of string address FP: 2nd byte of value integer: LSB of value $7E Val3 -- array: LSB of array address FP: 3rd byte of value integer: MSB of value $80 val4 -- array: MSB of array address FP: 4th byte of value $82 Val5 -- array: number of dimensions, 0 if undimensioned FP: 5th byte of value
This approach to dealing with variables is one of the main keys to the Beagle Compiler's speed. Another great factor in the speed is that GOTO and GOSUB can point to absolute addresses in RAM, rather than to a line number that must be searched for during execution.
Here's an example of a short Applesoft program along with a hex dump of its compiled result. (When COMPILER.SYSTEM finishes executing a program it trashes its pointers, so that's why I ended my little program with a CALL-151 to get us into the Monitor with everything intact.)
10 D$ = CHR$(4):BUFFER = 4096:REM $1000 20 ARGUMENT = 20 30 RESULT = BUFFER / 2 + ARGUMENT * 3.3 40 MYSTRING$ = "AARDVARK" 50 CALL -151 0078-43 08 48 08 4D 08 52 08 57 08 5C 08 0800-00 00 00 11 56 8D 05 40 00 42 00 00 00 00 00 00 0810-00 34 00 B6 94 04 14 01 96 00 10 14 02 94 20 14 0820-03 A8 AE 9C 01 94 02 AC 9C 02 98 82 53 33 33 33 0830-34 04 9A 08 41 41 52 44 56 41 52 4B 6E 96 69 FF 0840-12 FF 12 40 00 00 00 40 FE 00 00 8C 33 65 00 20 0850-06 08 00 10 00 99 00 00 00 00 99 00 00 00 00 9A 0860-00
The compiled code for the program apparently begins at $0810, and continues to $0842. In comparison, the original Applesoft program (not counting 35 bytes used to store the variables) ran from $0800 through $0875. There are five variables, so the six tables which begin at $0843 are each five bytes long. Using the table which comes in the Compiler manual I was able to "decompile" the compiled code as follows:
00 Initialize and run program D$=CHR$(4) 34 Assign value to simple string variable 00 1st Variable (D$) B6 CHR$ 94 04 One-byte integer (04) BUFFER=4096 14 Assign value to simple numeric variable 01 2nd Variable (BUFFER) 96 00 10 Two-byte integer ($1000=4096) ARGUMENT=32 14 Assign value to simple numeric variable 02 3rd variable (ARGUMENT) 94 20 One-byte integer ($20=32) RESULT = BUFFER/2 + ARGUMENT*3.3 14 Assign value to simple numeric variable 03 4th variable (RESULT) A8 addition [Notice the Polish form here] AE division 9C 01 get value of 2nd variable (BUFFER) 94 02 One-byte integer (2) AC Multiplication 9C 02 get value of 3rd variable (ARGUMENT) 98 Five-byte floating point constant follows 82 53 33 33 33 Floating point constant (3.3) MYSTRING$="AARDVARK" 34 04 Assign value to 4th variable (MYSTRING$) 9A A string constant follows... 08 41 41 52 44 56 41 52 4B String, 8 bytes, "AARDVARK" CALL -151 6E Call 96 69 FF Two-byte integer ($FF69 = -151) 12 End of program
Notice how all variables are referenced by the one-byte index, and how the constants are compiled in-line in their final form (integer, floating-point, or string). Looking at the six variable slices, and lining them up for easier viewing, I see:
00 -- 40 FE 65 00 00 00 D$ = CHR$(4) 01 -- 00 00 00 10 00 00 BUFFER = 4096 02 -- 00 00 20 00 00 00 ARGUMENT = 32 03 -- 00 8C 06 99 99 9A RESULT = 2153.6 04 -- 40 33 08 00 00 00 MYSTRING$ = "AARDVARK"
Maybe all this detail is overkill, but I wanted to show that the Beagle Compiler is real, well-constructed, valuable, and interesting. It is an excellent value at their price of $74.95. If you remember, the various DOS-based compilers cost more and (many would agree) delivered less. We like this one enough to help you get a copy: you can buy them through us for $65.
As I promised last December, here is another piece of ProDOS. This time I am unveiling the code which processes IRQ interrupts, and the handlers for the related MLI calls. All of the following applies to version 1.1.1 of ProDOS. Later versions may differ in this area.
The two MLI calls related to interrupts are $40 (Allocate Interrupt) and $41 (De-Allocate Interrupt). There is room inside ProDOS for connecting up to four user-coded routines for processing IRQ interrupts. The Allocate Interrupt call stores the address of your routine at the next available entry in the IRQ Path Table. This table exists in the MLI Global Page ($BF00-BFFF), and is shown in lines 1140-1170 in the listing below. When you boot ProDOS these four entries all contain $0000, indicating no interrupts are allocated. An MLI call of the form:
JSR $BF00 .DA #$40,IRQ.IOB
with an IOB like this:
IRQ.IOB .DA #2 IRQ.NUM .BS 1 .DA MY.IRQ.PROCESSOR
will cause the address of MY.IRQ.PROCESSOR to be stored in the IRQ Path table. The index into the table pointing to the entry used will be converted to an integer from 1 to 4, and stored at IRQ.NUM in the IOB. The purpose of this number is to allow you to later de-allocate the interrupt if you wish. A call and an IOB like this will de-allocate an interrupt:
JSR $BF00 .DA $41,IRQ.IOB --- --- IRQ.IOB .DA #1 IRQ.NUM .BS 1 (filled in by program)
Note that the first byte if the IOB is different this time, because there is only one parameter rather than two. It is important to de-allocate, because otherwise a sneaky interrupt could occur which would cause ProDOS to branch after your program is gone.
Another way to de-allocate is to store zeroes directly into the IRQ Path table, but Apple warns against this practice. It is quicker and easier, though.
There may be more than one source of IRQ interrupts in a given system. For example, you may be using both a clock card and a modem, both with interrupts. ProDOS allows you to have separate interrupt handlers for each of them installed. When an IRQ occurs the first handler installed will be called first. If that handler determines the IRQ is its own, it should process the IRQ and return with carry status clear. If not, the handler should return with carry status set. ProDOS will try giving the interrupt to each handler in turn, until one of them returns with carry clear. If none of them claim the IRQ, or if there are no processors allocated, "System Death" will occur: you will get error code $01, and a message to insert system disk and restart. It seems a nicer approach to unclaimed interrupts would be to count it, and continue processing. When the count exceeds some magic number, say 255, that would be the time to go through the agony of "System Death". I would also like to know the cause of "Death", if possible.
Lines 1400-1640 in the listing below show the code for allocating an interrupt. The code searches the IRQ Path table for an available entry (equal to 0000), and inserts the user's processor address in the first one found. If none are found you get error $25, INTERRUPT TABLE FULL. Actually only the high byte of each entry is checked, which means you cannot put an interrupt processing routine anywhere in page zero. The MLI call will allow you to do so, and it will even work, but if you later try to allocate another interrupt it will use the same table entry and clobber the first one. I suppose this is a bug in ProDOS, but not too likely to cause any problem because you are not likely to stick your code down in that page. Still, it COULD happen.
Lines 1660-1770 de-allocate an interrupt routine. If the interrupt index number is not in the range form 1 to 4, you will get error $53, BAD PARAMETER. Otherwise, the indicated entry will be zeroed.
When an IRQ interrupt occurs, if the status is such that interrupts are enabled, the processor status and the current PC-address will be pushed onto the stack; then processing will branch to the address currently at $FFFE and FFFF. What address is there will depend on which kind of Apple you are in, and whether ROMs or RAM are currently switched into the $D000-FFFF area. The original Apple II monitor and also the Apple II+ monitor vector IRQs into the $F8 monitor ROM area. A short routine there saves some registers and separates BRKs from IRQs (because they both share the same vector at $FFFE). IRQs then branch through another vector at $3FE and 3FF. The various Apple //e monitors vector IRQs and BRKs to an address at $C3FA, while the //c monitors send them to $C803.
When you boot ProDOS the installer/relocator code checks which kind of monitor you have. If your IRQ vector points anywhere below $D000, it assumes you have a "new style" monitor; if it points to anywhere between $D000 and $FFFF it assumes you have an "old style" monitor. The Apple II and II+ are old style, all others are new style. The installer/relocator stores a flag at $DFD8 so that the IRQ handler can tell what kind of machine it is in when an IRQ occurs later. This flag is shown at lines 2650-2710. In new style machines the vector found in ROM at $FFFE is copied into both Main and Auxiliary RAM banks at the same address, in case an interrupt occurs when RAM is switched on. In old style machines the address $FF9B is left in the RAM vector, pointing to a special IRQ handler shown in lines 3060-3400 below.
The vector at $3FE,3FF is set up to point to IRQ.ENTRY, a short routine inside the MLI Global Page. This is shown in lines 3000-3040. Since no matter what kind of monitor is resident the IRQ eventually vectors here, I will start the explanation here. Lines 3020-3030 turn on RAM at $D000-FFFF, so that ProDOS is accessible. It also write enables the RAM, because the IRQ processing will be storing a value at $DFCE, which identifies the current owner of the $C800 space (lines 1970-1980).
Lines 1800-1870 save the registers in the MLI Global Page. If you are in an old style machine lines 1880-1950 will save the processor status and return address in the Global Page as well.
ProDOS wants to make it easier to write IRQ processors, so it also makes sure you can use the stack and some page zero. If there are not at least 128 bytes left on the stack it will pop off 16 bytes and save them in a special buffer; if there are at least 128 bytes left this step is skipped. Then lines 2070-2120 save zero page locations $FA through $FF in a special buffer. Your IRQ processor can use these six bytes without worrying about saving and restoring them. (If you need more, you will have to save-restore them yourself.) This is all nice, but it does add to the general overhead for processing interrupts, which is already burdensome.
Lines 2130-2320 sequence through the installed interrupt processors until one of them claims the IRQ. Lines 2330-2350 signal DEATH if none of the processors claim the IRQ.
If the IRQ is properly claimed, lines 2360-2410 restore the six zero page bytes; lines 2420-2500 restore the 16 bytes of stack space if they were previously saved. Lines 2520-2630 restore some registers and the $C800 space if you are in an old style machine, and in any case branch to the IRQ.EXIT routine in the MLI Global Page.
IRQ.EXIT, shown in lines 2800-2960, restores the correct kind of memory (RAM or ROM) and then executes an RTI instruction. In an old style machine if the IRQ happened during a time when the RAM was switched on, this will send control to IRQ.EXIT.OLD, shown in lines 3690-3740. In a new style machine, or in any machine if the ROMs were on when the IRQ happened, the RTI will go back to the control of the monitor; exactly where that is depends on which monitor. Normally BANK.ID.BYTE contains the value $01. If an IRQ occurs in an old style machine when RAM is switched on, it will be changed to $00 or $FF depending on which $D000 bank is selected. Lines 2930-2940 change it back to $01 after one either $00 or $FF is processed.
One advantage to having both IRQ.ENTRY and IRQ.EXIT in the MLI Global Page is that you could substitute your own code if you wish. If you want to reduce overhead, and you know that you will always be running in a specific monitor configuration, you can patch in here. You could also patch in through the vector at $3FE, and avoid even more overhead. However, you would no longer be "standard".
I published a listing of lines 3070-3640 way back in December 1983, but I decided to include it here for completeness. This code is only used in an old style machine, and only when the IRQ occurs while RAM is switched on. The vector at $FFFE starts up the code at line 3140. The nonsense in lines 3140-3180 regarding location $45 makes sure we do not clobber the saved A-register. The old style monitor ROMs save the A-register at $45 rather than on the stack. This conflicts with use of the same location within both DOS 3.3 and ProDOS. QUESTION: Wouldn't it have been both easier and better to avoid using locaton $45 inside ProDOS? Kludge on top of kludge, if you ask me.
Lines 3290-3340 set up fake data on the stack for later use by an RTI instruction. Lines 3350-3380 do the same for an RTS instruction. Note that RTS requires an address with is one less than the actual address, while RTI requires the address un-modified. RTS pops the address, adds one, and branches; RTI pops the address and status, and branches without adding one. Line 3400 switches back to ROM. This means the next instruction will be executed from $FFCB in ROM, which is ALWAYS an RTS. Anyway it had BETTER be! If you ever make your own monitor ROM, be sure to leave an RTS here. (You will also need an RTS at $FF58, because a lot of I/O firmware expects that one is there.)
Lines 3420-3470 are executed in the old style machines if RAM is switched on when you hit RESET. That is, if you have the particular type of RAM card which leaves the F8 area switched on when you hit RESET. Many of them switch back to ROM when RESET occurs. Just in case, the code is here.
That about wraps it up. But it still leaves a lot of mystery in that part of IRQ processing which occurs inside the monitor ROMs. Each monitor version has its own unique code. The Apple II was simple, the II+ about the same. The three versions of //e and three versions of //c monitors of which I am aware are all mutually different. The IIgs is even more so. Perhaps in a future article we can rationalize them all.
1000 *SAVE MLI.IRQ 1010 *-------------------------------- 1020 PARM.PNTR .EQ $40,41 1030 COMMAND .EQ $42 1040 SAVE.A .EQ $45 1050 *-------------------------------- 1060 CURRENT.ROM.SLOT .EQ $07F8 $C0 + Slot which owns $C800. 1070 *-------------------------------- 1080 CALL.SYSERR .EQ $BF09 1090 CALL.DEATH .EQ $BF0C 1100 *-------------------------------- 1110 SAVE.LOC45 .EQ $BF56 Used if in Apple II or II+ 1120 SAVE.D000 .EQ $BF57 ditto 1130 *-------------------------------- 1140 IRQ.PATH.1 .EQ $BF80 These are 0000 if not allocated, 1150 IRQ.PATH.2 .EQ $BF82 address of user IRQ handler 1160 IRQ.PATH.3 .EQ $BF84 if allocated. 1170 IRQ.PATH.4 .EQ $BF86 1180 *-------------------------------- 1190 IRQ.A .EQ $BF88 1200 IRQ.X .EQ $BF89 1210 IRQ.Y .EQ $BF8A 1220 IRQ.S .EQ $BF8B 1230 IRQ.P .EQ $BF8C 1240 BANK.ID.BYTE .EQ $BF8D 1250 IRQ.RETURN .EQ $BF8E,BF8F 1260 *-------------------------------- 1270 IO.RESET.ROMS .EQ $CFFF De-select $C800 space. 1280 *-------------------------------- 1290 IRQ.SV .EQ $FEDF thru $FEEE (16 bytes saved from STACK) 1300 *-------------------------------- 1310 .PH $DEF3 1320 *-------------------------------- 1330 * Handle $40 and $41 MLI calls 1340 *-------------------------------- 1350 * (DEF3) DE57 1360 INTERRUPT.HANDLER 1370 STA COMMAND Save in case anyone cares later. 1380 LSR $40 or $41, lsb into CARRY 1390 BCS .5 ...$41 is DEALLOCATE 1400 *---$40 is ALLOCATE-------------- 1410 LDX #3 FOR X = 3 TO 9 STEP 2 1420 .1 LDA IRQ.PATH.1-2,X 1430 BNE .2 ...ALREADY ALLOCATED 1440 LDY #3 FOUND HOLE, INSTALL IRQ 1450 LDA (PARM.PNTR),Y 1460 BEQ .3 BAD PARAMETER 1470 STA IRQ.PATH.1-2,X 1480 DEY 1490 LDA (PARM.PNTR),Y 1500 STA IRQ.PATH.1-3,X 1510 TXA GIVE IRQ# TO CALLER 1520 LSR MAKE 3,5,7,9 INTO 1,2,3,4 1530 DEY 1540 STA (PARM.PNTR),Y 1550 CLC Signal NO ERROR 1560 RTS 1570 .2 INX Next X 1580 INX 1590 CPX #11 1600 BNE .1 1610 LDA #$25 "INTERRUPT TABLE FULL" 1620 BNE .4 ...ALWAYS 1630 .3 LDA #$53 "BAD PARAMETER" 1640 .4 JSR CALL.SYSERR (NEVER RETURNS) 1650 *---$41 is DEALLOCATE------------ 1660 .5 LDY #1 1670 LDA (PARM.PNTR),Y 1680 BEQ .3 ...0 is illegal value 1690 CMP #5 Must be 1,2,3,4 1700 BCS .3 ...too large 1710 ASL DOUBLE FOR INDEX 1720 TAX 1730 LDA #0 CLEAR THE ENTRY 1740 STA IRQ.PATH.1-2,X 1750 STA IRQ.PATH.1-1,X 1760 CLC Signal NO ERROR 1770 RTS 1780 *-------------------------------- 1790 * If an IRQ occurs, we eventually get HERE. 1800 *-------------------------------- 1810 IRQ.HANDLER 1820 LDA SAVE.A 1830 STA IRQ.A 1840 STX IRQ.X 1850 STY IRQ.Y 1860 TSX 1870 STX IRQ.S 1880 LDA ENHANCE.FLAG 1890 BNE .1 ...In a "new style" monitor 1900 PLA ...In an Apple II or II+ monitor 1910 STA IRQ.P Save P-reg and RETURN address 1920 PLA 1930 STA IRQ.RETURN 1940 PLA 1950 STA IRQ.RETURN+1 1960 .1 TXS Keep P-reg and RETURN on stack 1970 LDA CURRENT.ROM.SLOT Save $C800 Slot 1980 STA ROM.PAGE.BYTE 1990 *---Save some stack, maybe------- 2000 TSX If in bottom half of stack, 2010 BMI .3 then save 16 bytes of it. 2020 LDY #15 SAVE 16 BYTES FROM STACK 2030 .2 PLA 2040 STA IRQ.SV,Y 2050 DEY 2060 BPL .2 2070 *---Save some page zero---------- 2080 .3 LDX #$FA SAVE 6 BYTES FROM PAGE ZERO 2090 .4 LDA 0,X $FA...FF 2100 STA IRQ.SV-$FA+16,X 2110 INX 2120 BNE .4 2130 *---Call to first IRQ vector----- 2140 LDA IRQ.PATH.1+1 2150 BEQ .5 IRQ#1 EMPTY 2160 JSR IRQ.1 Try this IRQ level 2170 BCC .9 ...IRQ Claimed, Now Exit 2180 * 2190 .5 LDA IRQ.PATH.2+1 2200 BEQ .6 IRQ#2 EMPTY 2210 JSR IRQ.2 Try this IRQ level 2220 BCC .9 ...IRQ Claimed, Now Exit 2230 * 2240 .6 LDA IRQ.PATH.3+1 2250 BEQ .7 IRQ#3 EMPTY 2260 JSR IRQ.3 Try this IRQ level 2270 BCC .9 ...IRQ Claimed, Now Exit 2280 * 2290 .7 LDA IRQ.PATH.4+1 2300 BEQ .8 IRQ#4 EMPTY 2310 JSR IRQ.4 Try this IRQ level 2320 BCC .9 ...IRQ Claimed, Now Exit 2330 *---No IRQ vectors alive!-------- 2340 .8 LDA #$01 Un-claimed Interrupt Error 2350 JSR CALL.DEATH (NEVER RETURNS) 2360 *---IRQ PROCESSING COMPLETE------ 2370 .9 LDX #$FA RESTORE $FA...FF 2380 .10 LDA IRQ.SV-$FA+16,X 2390 STA 0,X 2400 INX 2410 BNE .10 2420 *---If saved, restore stack------ 2430 LDX IRQ.S 2440 BMI .12 16 BYTES FROM STACK NOT SAVED 2450 LDY #0 RESTORE 16 BYTES TO STACK 2460 .11 LDA IRQ.SV,Y 2470 PHA 2480 INY 2490 CPY #16 2500 BNE .11 2510 *---Choose EXIT routine---------- 2520 .12 LDA ENHANCE.FLAG 2530 BNE IRQXIT ..."New style" monitor ROMs 2540 LDY IRQ.Y ...Apple II or II+ monitor. 2550 LDX IRQ.X 2560 LDA IO.RESET.ROMS Turn off $C800 bank 2570 LDA $CF00 Select Interrupted $C800 bank 2580 * (DFCE) DF5E DFCF (Hi-byte filled in) 2590 ROM.PAGE.BYTE .EQ *-1 (Self-modifying code!) 2600 LDA ROM.PAGE.BYTE 2610 STA CURRENT.ROM.SLOT 2620 * (DFD5) DFC1 2630 IRQXIT JMP IRQ.EXIT 2640 *-------------------------------- 2650 * (DFD8) DF49 DFBE 2660 ENHANCE.FLAG .HS 00 (Set to 01 by relocator if new 2670 * type ROM is found) 2680 * ((If IRQ vector in ROM at $FFFE,F points below 2690 * $D000, it is "new type". The "old type", 2700 * which is the original $F8 ROM in the Apple II 2710 * or that of the Apple II+, points to $Fxxx.)) 2720 *-------------------------------- 2730 * (DFD9-E2) DF7C DF86 DF90 DF9A 2740 IRQ.1 JMP (IRQ.PATH.1) 2750 IRQ.2 JMP (IRQ.PATH.2) 2760 IRQ.3 JMP (IRQ.PATH.3) 2770 IRQ.4 JMP (IRQ.PATH.4) 2780 *-------------------------------- 2790 .PH $BFD0 2800 *-------------------------------- 2810 * IRQ ENTRY/EXIT CODE IN GLOBAL PAGE 2820 *-------------------------------- 2830 IRQ.EXIT 2840 LDA BANK.ID.BYTE 2850 IRQ.EXIT.1 2860 BEQ .2 2870 BMI .1 2880 LSR 2890 BCC .3 2900 LDA $C081 Switch on ROMs at D000-FFFF 2910 BCS .3 ...ALWAYS 2920 .1 LDA $C083 Switch on RAMs at D000-FFFF 2930 .2 LDA #1 2940 STA BANK.ID.BYTE 2950 .3 LDA IRQ.A 2960 RTI 2970 *-------------------------------- 2980 * An IRQ interrupt comes here when it occurs 2990 * because of the vector at $3FE,3FF. 3000 *-------------------------------- 3010 IRQ.ENTRY 3020 BIT $C08B Switch on and write-enable 3030 BIT $C08B RAM at D000-FFFF 3040 JMP IRQ.HANDLER 3050 *-------------------------------- 3060 .PH $FF9B 3070 *-------------------------------- 3080 * IRQ CODE FOR APPLE II AND II+ MONTOR ROMS 3090 * This code is used when IRQ happens while 3100 * the RAM at D000-FFFF is switched on (inside 3110 * an MLI call, for example) if we have the 3120 * "new style" monitor ROMs. 3130 *-------------------------------- 3140 IRQ PHA SAVE A-REG 3150 LDA SAVE.A ALSO SAVE SAVE.A 3160 STA SAVE.LOC45 3170 PLA NOW PUT A-REG INTO SAVE.A 3180 STA SAVE.A 3190 PLA PEEK AT STATUS 3200 PHA 3210 AND #$10 WAS IT "BRK"? 3220 BNE .2 ...YES, LET MONITOR HANDLE IT 3230 LDA $D000 CHECK WHETHER D000 BANK 1 OR 2 3240 EOR #$D8 "CLD" OPCODE 3250 BEQ .1 ...IN D000 BANK 1 3260 LDA #$FF ...IN D000 BANK 2 3270 .1 STA BANK.ID.BYTE 3280 STA SAVE.D000 3290 LDA /IRQ.EXIT.OLD PUSH FAKE "RTI" VECTOR 3300 PHA 3310 LDA #IRQ.EXIT.OLD 3320 PHA 3330 LDA #$04 3340 PHA 3350 .2 LDA /$FA41 PUSH FAKE "RTS" VECTOR INTO 3360 PHA MONITOR ROM 3370 LDA #$FA41 3380 PHA 3390 CALL.MONITOR 3400 STA $C082 SWITCH TO MOTHERBOARD ROMS, 3410 * WHERE THERE IS AN "RTS" OPCODE 3420 *-------------------------------- 3430 RESET LDA RESET.VECTOR+1 3440 PHA PUSH FAKE "RTS" INTO MONITOR 3450 LDA RESET.VECTOR 3460 PHA 3470 JMP CALL.MONITOR 3480 *-------------------------------- 3490 RESET.VECTOR .DA $FA61 MON.RESET-1 3500 *-------------------------------- 3510 IRQ.SPLICE 3520 STA IRQ.A 3530 LDA SAVE.LOC45 3540 STA SAVE.A 3550 LDA $C08B FINISH WRITE-ENABLING RAM 3560 LDA SAVE.D000 3570 JMP IRQ.EXIT.1 3580 *-------------------------------- 3590 .BS $FFFA-* <<<EMPTY SPACE>>> 3600 *-------------------------------- 3610 V.NMI .DA $03FB 3620 V.RESET .DA RESET 3630 V.IRQ .DA IRQ (Replaced by relocator with 3640 * the value from ROM vector if 3650 * the machine has "new style" monitor. 3660 *-------------------------------- 3670 .PH $BF50 3680 *-------------------------------- 3690 * LITTLE PIECE OF IRQ EXIT CODE USED WITH 3700 * OLD TYPE MONITOR ROMS 3710 *-------------------------------- 3720 IRQ.EXIT.OLD 3730 LDA $C08B SWITCH RAM ON, D000 BANK 1 3740 JMP IRQ.SPLICE 3750 *-------------------------------- 3760 .LIST OFF |
This is still a problem. When you use the monitor "G" command, it loads up the registers from $45 through $49 and then goes to the address you specified. Location $48 supplies the status, the P-register. Unfortunately, both DOS and ProDOS walk on location $48. Therefore it might have a meaningless value.
Worse than meaningless, it might be dangerous. It was for me a few days ago. I was debugging a program under ProDOS, and found that if I started it up with "800G" immediately after a SAVE command, it went completely crazy. The screen output was totally trashed. Scrolling turned into shuffling, the cursor jumped around the screen when typing in characters, and so on.
Why? Because $48 had a garbage value with bit 3 turned on, which turned on the decimal mode. Apparently SAVE stores something into $48, and the actual value depends on mysterious factors. Anyway, I woke up the next morning with the explanation of what was going on. I even remembered that I wrote an article about the problem in the June 1984 issue of AAL (pages 13 and 14).
One solution is to put a "CLD" instruction at the beginning of my program, just in case. Other, less secure fixes are to type "48:4 N 800G" instead of just "800G", or avoid using the G-command altogether by putting the code on a BRUNnable file.
Tom Weishaar sent me copies of some correspondence regarding the RamFactor boards and and Apple's FID program. If the name FID means nothing to you, it is the DOS 3.3 equivalent to the ProDOS FILER program, and comes on the Apple DOS 3.3 System Master Disk. It seems that if you use FID to transfer files to a 400K DOS 3.3 partition on the card that FID quits with the DISK FULL error long before the volume is really full.
I tried to reproduce the problem on my system. When I used FID, it failed just as stated above. The exact point at which FID gave up seemed to vary with the size of the files being copied. When I used plain DOS, using BSAVE or SAVE, I had no problem really filling the disk. I also tried it with my word processor, with no problems. Apparently the problem is in FID.
When I got the DISK FULL message from FID, I went to the monitor and took a look at the VTOC sector of the RamFactor drive. The VTOC buffer is located at $B3BB thru $B4BA after leaving FID. Sure enough, there were plenty of free sectors left, but they were almost all in sectors 0 thru 15 (the third and fourth bytes of each track's bit map) of each track. I went back into FID and experimented with trying to copy various size files. FID had no trouble copying files of fewer than 17 sectors, but on larger files it said DISK FULL again. It seems FID has a bug in the subroutine which looks for free sectors to COPY into.
I investigated a little more, with the help of a partial disassembly of FID which Bill Morgan did back in 1982. Sure enough, the subroutine to find the next free sector ignores the third and fourth bytes of each track's bitmap in the VTOC. Since FID allows the DOS File Manager to do part of the allocation, some of those sectors do get used. However, when the GET.FREE.SECTOR subroutine is called it will not use sectors 0-15 of a 32 sector per track disk.
Lines 1000-1210 in the listing which follows are the offending code from FID. As you can see, only two bytes of the VTOC are moved (lines 1090-1120). Lines 1130-1150 check if those two bytes are both zero, and if so move on to the next track.
My first attempt to fix the bug is shown in lines 1220-1430. I re-arranged the code somewhat, to free up the X-register so that I could write a loop which would move four bytes from the VTOC to the bitmap work area. The loop is in lines 1330-1390. This did not leave room for code to check whether all four bytes were zero or not, so I just left out that feature; it was not necessary anyway, but it did shave a few microseconds off the worst cases.
In my second attempt I found room for testing whether or not there are any free sectors in a track and avoiding the extra time it takes to look at the bitmap one bit a time. Lines 1440-1680 show the result.
To install the patch, first assemble it as shown. This will leave a copy of the original code starting at $21E0, of PATCH1 at $31E0, and PATCH2 at $41E0. Then BLOAD your copy of FID, and check the starting address and length by looking at AA60.AA61 for the length and AA72.AA73 for the starting address. In my copy, the starting address was $803 and the length was $124F. Using the monitor, verify that the original code at $11E0 is the same. If you have a different version of FID, this is the time to find out!
: (type in the source code as shown) :ASM :BLOAD FID :$AA60.AA61 AA72.AA73 AA60:4F 12 AA72:03 08 (this is what Apple displays) :$11E0<21E0.2207V
If no discrepancies are reported, you can copy either PATCH1 or PATCH2 into place and BSAVE the result.
:$11E0<31E0.3207M (for PATCH1) or :$11E0<41E0.4207M (for PATCH2) :BSAVE FID.PATCHED,A$803,L$124F
A patched version of FID is included on the monthly and quarterly disks for this issue.
1000 *SAVE S.PATCH.FID 1010 *-------------------------------- 1020 .OR $11E0 1030 .TA $21E0 1040 *-------------------------------- 1050 TXA 1060 ASL 1070 ASL 1080 TAY 1090 LDA $198A,Y 1100 STA BITMAP.OF.CURRENT.TRACK 1110 LDA $198B,Y 1120 STA $1932 1130 BNE $11F7 1140 ORA BITMAP.OF.CURRENT.TRACK 1150 BEQ $11DA 1160 STX $1393 1170 STX FREE.TRK 1180 LDX $1987 # OF SECTORS PER TRACK 1190 STX FREE.SEC 1200 STY $1392 1210 BNE $11A4 1220 *-------------------------------- 1230 .OR $11E0 1240 .TA $31E0 1250 *-------------------------------- 1260 PATCH1 STX $1393 1270 STX FREE.TRK 1280 TXA 1290 ASL MULTIPLY BY FOUR 1300 ASL 1310 TAY 1320 STY $1392 1330 LDX #0 COPY 4 BYTES TO WORK AREA 1340 .1 LDA $198A,Y 1350 STA BITMAP.OF.CURRENT.TRACK,X 1360 INY 1370 INX 1380 CPX #4 1390 BCC .1 1400 LDX $1987 # OF SECTORS PER TRACK 1410 STX FREE.SEC 1420 BNE $11A4 1430 .BS 5,$EA FILLER IS "NOP" 1440 *-------------------------------- 1450 .OR $11E0 1460 .TA $41E0 1470 *-------------------------------- 1480 PATCH2 STX $1393 1490 STX FREE.TRK 1500 TXA 1510 ASL MULTIPLY BY FOUR 1520 ASL ALSO CLEARS CARRY 1530 TAY 1540 STY $1392 1550 LDX #$FC 256-4 1560 .1 LDA $198A,Y 1570 BEQ .2 THIS ONE HAS NO FREE SECTORS 1580 SEC SIGNAL THERE WERE FREE SECTORS 1590 .2 STA BITMAP.OF.CURRENT.TRACK-$FC,X 1600 INY 1610 INX 1620 BNE .1 ...UNTIL 4 BYTES MOVED 1630 BCC $11D7 1640 LDX $1987 # OF SECTORS PER TRACK 1650 STX FREE.SEC 1660 BNE $11A4 1670 NOP FILLER IS "NOP" 1680 NOP 1690 *-------------------------------- 1700 FREE.SEC .EQ $192F 1710 FREE.TRK .EQ $1930 1720 BITMAP.OF.CURRENT.TRACK .EQ $1931 ... 1934 1730 *-------------------------------- |
Apple Assembly Line (ISSN 0889-4302) is published monthly by S-C SOFTWARE CORPORATION, P.O. Box 280300, Dallas, Texas 75228. Phone (214) 324-2050. Subscription rate is $18 per year in the USA, sent Bulk Mail; add $3 for First Class postage in USA, Canada, and Mexico; add $14 postage for other countries. Back issues are available for $1.80 each (other countries add $1 per back issue for postage).
All material herein is copyrighted by S-C SOFTWARE CORPORATION,
all rights reserved. (Apple is a registered trademark of Apple Computer, Inc.)