Bill Wilkinson Cupertino, CA
I have recently seen a copy of the complete De Re Atari (by Atari's own Chris Crawford, author of SCRAM and EASTERN FRONT, et al). Since two out of three people I talk to say "Huh?" when I mention the name, I have personally subtitled it Everything You Ever Wanted to Know About the Atari Computers But Didn't Know Enough to Ask. The book concerns itself with foibles, tricks, innards, hardware, software, and everything in between: there are even tricks using Atari BASIC (that are "obvious" upon discovery) which we never thought about when we designed the thing! I must heartily recommend that every serious Atari programmer trade in his or her left thumb, if necessary, for a copy of this book.
"De Re" (the insiders' appellation) is currently being serialized in BYTE magazine (I guess Atari's trying to impress the non-Atari world), but seeing the book in one piece is somehow more instructive. "De Re" is generally a fantastic resource, but it does often assume that the reader has intimate knowledge and understanding of the Atari Hardware Reference Manuals, etc. This is not a fault (the authors forewarn the reader); and, besides, it does leave room for columns like this. I don't intend to duplicate material in either Atari's manuals or "De Re", but there is bound to be some overlap. I intend to present the "hows" and "whys" to supplement Atari's "whats."
I try to write this column for the programmer: the person who knows software, but is unfamiliar with Atari hardware and/or Atari's system level software. If this column stretches your understanding of the Atari and/or its software, that's probably good. And I am constantly amazed at the questions which beginners on the Atari come up with; they often show "insights" to solution methods that I wouldn't dream of. The first questions are arriving in my mailbox. Send more!
This month's column is part three of the series on the Atari Operating System. Next month we will cover screen output, including graphics, to formally end the series. I have a few ideas on what should come next for you non-BASIC Atari users, but I would welcome some input. Also, this month, we begin a series which will explore the inner workings of Atari BASIC.
Atari I/O, Part 3: Device Handlers
As we noted before, Atari's OS is actually a very small program (approximately 700 bytes). Even so, it is able to handle the wide variety of I/O requests detailed in the first two parts of this series with a surprisingly simple and consistent assembly language interface. Perhaps even more amazing is the purity and simplicity of the OS interface to its device handlers.
Admittedly, because of this very simplicity, Atari's OS is sometimes slower than one would wish (probably only noticeably so with PUT BINARY RECORD and GET BINARY RECORD) and the handlers must be relatively sophisticated. But not overly so, as we will show.
The Device Handler Table
Atari OS has, in ROM, a list of the standard devices (P:,C:,E:,S:, and K:) and the addresses thereof. So far, so good. But notice that, for example, the disk handler (D:) is not listed there; how does OS know about other devices? Simple. On SYSTEM RESET, the list is moved from ROM to RAM, and OS then utilizes only the RAM version. To add a device, simply tack it on to the end of the list: you need only specify the devices name (one character) and the address of its handler table (more on that in a moment). To reassure you that it is this simple, let me point out that this is exactly how the "D:" (Disk) handler is attached when the disk is booted.
|HTABS||; the Printer device|
|.WORD||PDEVICE||; and the address of its driver|
|.BYTE||'C'||; the Cassette device|
|.BYTE||'E'||; the screen Editor device|
|.BYTE||'S'||; the graphics Screen device|
|.BYTE||'K'||; the Keyboard device|
|.BYTE||0||; zero marks the end of the table|
|.WORD||0||; …but there's room for up to|
|.BYTE||0||; …9 more devices et cetera|
In theory, all named device handlers under Atari OS may handle more than one physical device. Just as the disk handler understands "Dl:" and "D2:", so could a printer handler understand "PI:" and "P2:". In practice, of all the standard Atari handlers only the Disk and Serial Port handlers can utilize the sub-device numbers. Incidentally, Atari OS supplies a default sub-device number of "1" if no number is given (thus "D:" becomes "Dl:"). A project for those of you with two printers (there must be one or two of you): presumably one of them is connected via the MacroTronics interface; if so, try modifying the MacroTronics handler so that "PI:" refers to the Atari 850 interface while "P2:" refers to the MacroTronics. It's really a fairly easy project, presuming you have the listings of Atari's OS (which are available from Atari).
Rules For Writing Device Handlers
Each device which has its handler address placed into the handler address table (above) is expected to conform to certain rules. In particular, the driver is expected to provide six action subroutines and an initialization routine. (In practice, I believe the current Atari OS only calls the initialization routines for its own pre-defined devices. Since this may change in future OS's and since one can force the call to one's own initialization routine, I must recommend that each driver include one, even if it does nothing.) The address placed in the handler address table must point to, again, another table, the form of which is shown in Figure 2.
Notice the six addresses which must be specified; and note that, in the table, one must subtract one from each address (the "-1" simply makes ClO'sjob easier…honest). A brief word about each routine is in order.
The OPEN routine must perform any initialization needed by the device. For many devices, such as a printer, this may consist of simply checking the device status to insure that it is actually present. Since the X-register, on entry to each of these routines, contains the IOCB number being used for this call, the driver may examine ICAX1 (via LDA ICAX1,X) and/or ICAX2 to determine the kind of OPEN being requested. (Caution: Atari OS preempts bits 2 and 3, $04 and $08, of ICAX1 for read/write access control. These bits may be examined, but should normally not be changed.)
The CLOSE routine is often even simpler. It should "turn off" the device if necessary and if possible.
The PUTBYTE and GETBYTE routines are just what are implied by their names: the device handler must supply a routine to output one byte to the device and a routine to input one byte from the device. However, for many devices, one or the other of these routines doesn't make sense (ever tried to input from a printer?). In this case the routine may simply RTS and Atari OS will supply an error code.
The STATUS routine is intended to implement a dynamic status check. Generally, if dynamic checking is not desirable or feasible, the routine may simply return the status value it finds in the user's IOCB. However, it is not an error under Atari OS to call the status routine for an unopened device, so be careful.
The XIO routine does just what its name implies: it allows the user to call any and all special and wonderful routines that a given device handler may choose to implement. OS does nothing to process an XIO call except pass it to the appropriate driver.
Note: In general, the AUXilliary bytes of each IOCB are available to each driver. In practice, it is best to avoid ICAX1 and ICAX2, as several BASIC and OS commands will alter them at their will. Note that ICAX3 through ICAX5 may be used to pass and receive information to and from BASIC via the NOTE and POINT commands (which are actually special XIO commands). Finally, drivers should not touch any other bytes in the IOCBs, especially the first two bytes.
Notice that handlers need not be concerned with PUT BINARY RECORD, GET TEXT RECORD, etc.: OS performs all the needed housekeeping for these user-level commands.
|.WORD||< address of OPEN routine > -1|
|.WORD||< address of CLOSE routine >-l|
|.WORD||< address of GETBYTE routine > -1|
|.WORD||< address of PUTBYTE routine > -1|
|JMP||< address of initialization routine >|
Rules For Adding Things To OS
We touched on this subject last month, in the section titled "The Easiest Way of Making Room?", but a review and an addition are in order. Both Atari FMS (File Manager System, otherwise known as DOS and/or the Disk Device Driver) and the serial port handlers follow the same scheme when they add themselves to OS, so it is safe to assume that this method may be considered the de facto Atari standard. We enumerate:
- Inspect the system MEMLO pointer (at $2E7, I called it LOMEM last month, which is BASIC'S name for it).
- Load your routine (including needed buffers) at the current value of MEMLO.
- Add the size of your routine to MEMLO.
- Store the resultant value back in MEMLO.
- Connect your driver to OS by adding its name and address into the handler address table.
- Fool OS so that if SYSTEM RESET is hit steps 3 through 5 will be re-executed (because SYSTEM RESET indeed resets the handler address table and the value of MEMLO).
In point of fact, step 2 is the hardest of these to accomplish. In order to load your routine at wherever MEMLO may be pointing, you need a relocatable (or self-relocatable) routine. Since there is currently no assembler for the Atari which produces relocatable code, this is not an easy task. (However, I just happen to have a method which works. But it will have to wait for a later article.)
Step 6 is accomplished by making Atari OS think that your driver is the Disk driver for initialization purposes (by "stealing" the DOSINI vector) and then calling the Disk's initializer yourself when steps 3 through 5 are performed. This is a fairly simple process, but again, details must await a future article.
Yet Another Real Live Example
I promised last month that we would present a driver for a "peripheral" device found in every Atari, yet not supported by any Atari device handlers. I could have been cagey and presented a driver for a "Null" device. (A handy thing to have, actually: One can throw away one's output very fast when trying to debug a program. See De Re Atari for a simple implementation of one. Better yet, try to write one from the information presented herein.) Being a glutton for punishment, I undertook to write a truly useful handler for Atari's overlooked device: RAM memory!!
After the snickers and sarcastic comments die down, let me point out how truly useful such a device is to BASIC programs: program one can "write" data to RAM and then chain to program two, which then "reads" the same data back. Voila! Chaining with COMMON in Atari BASIC. So herewith the "M:" (Memory) driver, presented in its entirety in Figure 3.
Does It Work?
Some words of caution are in order. This driver does not perform step 6 as noted in the last section (but it may be reinitialized via a BASIC USR call). It does not perform self-relocation: instead it simply locates itself above all normal low memory usage (except the serial port drivers, which would have to be loaded after this driver). If you assemble it yourself, you could do so at the MEMLO you find in your normal system configuration (or you could improve it to be self-modifying, of course).
Other caveats pertain to the handler's usage: it uses RAM from the contents of MEMTOP ($2E5) downward. It does not check to see if it has bumped into BASIC'S MEMTOP ($90) and hence could conceivably wipe out programs and/or data. To be safe, don't write more data to the RAM than a FRE(O) shows (and preferrably even less).
In operation, the M: driver reinitializes upon an OPEN for write access (mode 8). A CLOSE followed by a subsequent READ access will allow the data to be read in the order it was written. More cautions: don't change graphics modes between writing and reading if the change would use more memory (to be safe, simply don't change at all). The M: will perform almost exactly as if it were a cassette file, so the user program should be data sensitive if necessary: the M: driver will not itself give an error based on data contents. Note that the data may be re-READ if desired (via CLOSE and re-OPEN).
Installing The M: Driver
The most obvious way to install this driver (Program 1) is to type in the source and assemble it directly to the disk. Then simply loading the object file from DOS 2 (or OS/A +) will activate the driver and move LOMEM as needed. You could even name the resulting file "AUTORUN.SYS" so that it would be automatically booted on power up.
If you don't have an assembler and/or disk, the problem is a little more difficult. If you are comfortable writing BASIC programs that load assembly language data to memory, you migth use the techniques described in last month's "Make Room?" to reserve the required memory. Then a simple POKEr program which uses DATA statements would suffice.
But the assembly listing given here is designed for a disk system and would waste 5K bytes or so in a cassette system. So, if you can't reassemble it and/ or write that POKEr program, you will just have to be patient: I will try to give you a simplified BASIC POKEr program next month.
A suggested set of BASIC programs is presented:
Ending of Program 1:
9900 OPEN #2,8,0,"M:" 9910 PRINT #2; LEN(A$) 9920 PRINT #2; A$ 9930 CLOSE #2 9940 RUN "D:PROGRAM2"
Beginning of Program 2:
100 JUNK = USR( 7984 ) [ to insure the M: driver is linked, in case of RESET ] 110 OPEN #4,4,0,"M:" 120 INPUT #4, SIZE 130 DIM STRING$(SIZE) 140 INPUT #4, STRING$ 150 CLOSE #4
BASIC A + users might find RPUT/RGET and BPUT/BGET to be useful tools here instead of PRINT and INPUT. And, of course, users of any other language(s) might find this a handy inter-program communications device.
BASIC, Part 1: Why?
The first "Why?" I usually hear is "Why not Microsoft BASIC?" After a little probing, I find that the question really boils down to "Why not string arrays?" There is no simple answer to that question, so I hope to save myself time in the future by pointing toward these articles. Because I intend to give the true and not-so-simple answer, along with some (hopefully) very interesting information.
Believe it or not, Atari BASIC pretty much works the way it was designed and specified. And yours truly must take a large part of the brickbats or roses you might throw because of those specifications. We (that is, at the time, Shepardson Microsystems) were just finishing the highly successful and very powerful Cromemco 32K Structured BASIC. And, while a few Cromemco users had carped about the lack of string arrays, on the whole the real power of the language is extraordinarily impressive. All this "power" probably went to our head(s), so of course we had to duplicate the feat for Atari.
Oops. A small problem: Cromemco gave us 32K bytes for Structured BASIC; Atari gave us 10K bytes. What comes out? Wrong question! What can stay in?! Of course, Atari had some ideas, too, and the important features that we ended up with include (in my opinion):
Long Variable Names
Long Strings (more than 255 bytes)
Reasonable Assembly Language Interface
Syntax Check at entrh time
That last item won't be appreciated by those of you who haven't used a BASIC that doesn't do it, so I will try to describe the horrors to you: You type in a long program which includes a line such as:
3034 IF SYSTEMERROR THEN PINT "Bad Disk Drive": GOTO 4090
Did you catch it? It says 'PINT' where it should say 'PRINT'. Most microcomputer BASICs will happily gulp that line in with nary a burp. Now, 13 months later, when that dreaded 'systemerror' actually occurs, your user (who lives in Hong Kong, naturally) sees the helpful message
***SYNTAX ERROR at LINE 3037
When you have fathomed the implications of that, calm your nerves so we can continue.
Needless to say, we were more than happy to include the Syntax Check feature. However, this inclusion had implications that rippled throughout the rest of the design of BASIC. First, you don't get something for nothing: such syntax checking uses memory, perhaps one to two kilobytes. Second, pre-syntaxing implies that the user program will be "tokenized": that is, the user's source will be converted into internal tokens for ease of execution and efficiency. Even Microsoft BASICs tokenize the keywords of the language; Atari BASIC tokenizes everything: keywords, variables, constants, operators, etc. Thirdly, the decision to have strings longer than 255 characters (coupled with the tight memory requirements) simply precluded any implementation of string arrays. (In fact, I do not know of any small-machine BASIC that supports string arrays with elements longer than 255 characters.)
Before perusing some quickie programs to show the effects of tokenizing, I should like to give some credit where it is due. Though I participated in the specifications for Atari BASIC, I had little to do with the actual implementation. More history: Atari asked us (in September, 1978) to bid on producing a custom "consumer-oriented" BASIC for them. Sometime in October, the specifications were finalized and Paul Laughton and Kathleen O'Brien (with a very little help from three more of us) began to work in earnest. The contract called for delivery by April 6, 1979, and included delivery of a File Manager System (DOS 1). Atari planned to take an early, 8K Microsoft BASIC to the Consumer Electronics Show (in Las Vegas) in January, 1979, and then switch later. The actual purchase order took a while to get through Atari's red tape, and the final version thereof is dated 12/28/78 – about one week after both BASIC and DOS were delivered to Atari! Atari took Atari BASK to CES.
Investigating BASIC'S Tokens
There are three fundamental types of tokens in Atari BASIC, each of which occupies exactly one byte of RAM memory, with only two special cases. The token types are statement name tokens, operator name tokens (which include function names and some other miscellany), and variable name tokens. The special cases are numeric and string constants, which begin with an operator name token, but are followed by the actual value of the constant.
Statement name tokens can only occur as the first item of a statement and, thus, have their own keyword and tokenizing table. In theory, Atari BASIC'S structure could support up to 256 types of statements. Variable name tokens and operator name tokens are intermixed throughout the rest of a statement and are distinguished by the state of their upper bit: variable name tokens have their upper bit on, operators don't.
A few of the statement types are also special cased in that they are not followed by operator and variable tokens. These special cases include the obvious REM and DATA and the not-so-obvious ERROR (the statement name given to lines containing a syntax error).
Since each variable is reduced to a single byte (with its upper bit set), there are a maximum of 128 different variable names per program. There is the further implication that BASIC must remember the association of name to token in order to LIST your program back to you. The actual ATASCII names are stored in the "Variable Name Table," and we investigated its structure in COMPUTE # 17 under the heading of "VARIABLE, VARIBLE, VARABLE." (Briefly, the names are simply stored one after the other, with the upper bit of the last character of each name turned on.)
The statement and operator names are obviously predefined in the BASIC ROM cartridge, and we offer herewith a program (Program 2) which prints out the token numbers and corresponding keywords. When you run the program, you will notice that some operators (especially the left parenthesis) appear to be repeated. They are. We will find out why next month.