changeset 469:6b15b51ee3ba

An article by Jon Bird about writing OS9 device drivers.
author roug
date Sat, 05 Oct 2002 15:36:00 +0000
parents 60b821f18853
children 6e1cff14e8c4
files docs/articles/os9software.article
diffstat 1 files changed, 1714 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/articles/os9software.article	Sat Oct 05 15:36:00 2002 +0000
@@ -0,0 +1,1714 @@
+<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook V4.1//EN">
+<!-- The author has not been contacted about adding this article to the
+     documentation set.
+-->
+<article>
+<articleinfo>
+<author><firstname>Jon</><surname>Bird</></author>
+<title>Software for OS9</title>
+</articleinfo>
+<para>
+There has been a fair deal of material written on using OS9, applications and 
+filters but very little that I have seen (in the Dragon world anyway) about 
+actually writing software for OS9. I think most people only tend to use it for 
+Stylograph and such like, because it is a completly different environent to 
+the Dragon in it's native mode and can to be honest pretty horrendous to get 
+to grips with on a normal 64K Dragon. Originally I intended to cover this as 
+part of the stuff I've been writing on connecting PC's/Dragons but it's such a 
+big subject I thought breaking it out separatly would be better. Therefore, 
+I'll also cover device drivers and stuff like that in support of the PC thing, 
+because OS9 gets a lot better and easier to use by the addition of say an 80 
+column screen and hard disk. 
+</para>
+<section>
+<title>DragonDOS vs OS9</title>
+<para>
+Trying to explain the difference between a Dragon running normal BASIC/DOS and 
+one running OS9 is difficult if you have not used one or the other. However, 
+for the programmer the Dragon is ideal, because you get the best of both 
+worlds, in 'native' mode where you can program and do exactly what you want 
+with the machine and under OS9 where things are a lot more tightly managed by 
+the operating system which is a lot more how it is becoming with Windows and 
+such like. 
+</para>
+<para>
+Essentially, OS9 is composed of a set of 'building blocks' and can easily be 
+expanded by adding or changing another building block or module. A completly 
+minmimal setup would therefore consist of the OS9 kernel (called OS9p1 &amp; OS9p2 
+on a Dragon) which is just a block of core procedures required to manage the 
+system. It has no idea of keyboards, disks or screens or any input/output come 
+to that and does not even come with any sort of command interpretor (the shell 
+utility performs this which is a seperate entity in its own right). In fact on 
+its own it is pretty much useless on an ordinary home computer. However, OS9 
+has found its home (as many of the books on it will tell you) in lots of real 
+time applications, where there is no need for direct human interaction. 
+However, for most of use some sort of interaction is reqired so you will find 
+a dedicated module for handling all I/O (called the IO Manager (or IOMAN for 
+short)) and further modules for dealing with specific types of IO. As an 
+example, standard OS9 is shipped with two file managers, the Sequential File 
+Manager (SCFMan) which manages all sequential types of devices, such as the 
+keyboard, screen and knows all about sequential type peripherals, such as 
+delete keys, carriage return and other line editing functions. The other file 
+manager is the Random Block File Manager (RBFMan) which looks after random 
+block type devices, such as disks. This knows all about what an OS9 disks 
+looks like, what makes up a file and how to find it. Finally, there are the 
+drivers, which actually talk to the hardware, and on the Dragon are 
+specifically designed for the Dragon's hardware. For example, DDISK the Dragon 
+device driver, knows how to talk to the disk controller chip to read a bit of 
+the disk. On the other hand, it does not know what a file is. Right at the 
+bottom of the change are the device descriptors, which are basicly blocks of 
+memory to tell the system, this is disk drive 0, it uses DDISK to talk to the 
+disk controller, and RBFMan is it's file manager. A common data flow would 
+therefore look something like:
+<screen>
+ Your OS9 Program says:
+   'read 100 bytes from path number n'
+
+ Which is passed to IOMAN as:
+   'read 100 bytes from path number n'
+ which determines that RBFMan is responsible for path n:
+
+ Which is passed to RBFMan as:
+  'read 100 bytes from path number n'
+ who knows all about what path n is, and where to get those
+ 100 bytes from:
+
+ Which is then passed to DDISk as:
+  'read sector m'
+ which actually gets the disk controller to read the data
+</screen>
+</para>
+<para>
+This collection of modules forms, in equivalent Dragon terms the BASIC &amp; DOS 
+ROM starting at 32768 ($8000) onwards. On its own, they provide little more 
+than procedures and subroutines for using your machine.
+</para>
+<para>
+In order to utilise these routines, and actually get your machine doing some 
+useful work, some sort of user friendly interface is needed to talk to the 
+user, under OS9 a program called Shell is provided to do this, and the BASIC 
+interpretor provides this facility under normal DragonDOS mode. They both work 
+in a similar manner: each has a set of internal commands which it understands 
+and can execute, and each has the ability to run external programs. For 
+example, under Dragon BASIC the line:
+<screen>
+PRINT "HELLO WORLD"
+</screen>
+</para>
+<para>
+is effectivly running at internal program within the interpretor called PRINT. 
+Likewise with the OS9 Shell:
+<screen>
+chd /d0
+</screen>
+</para>
+<para>
+is understood by the Shell to mean 'change directory to drive D0' and runs the 
+internal code required to perform this operation.
+</para>
+<para>
+In order to run a program external to the BASIC interpretor, you need to tell 
+it explicitly, as in:
+<screen>
+RUN "MYPROG"
+</screen>
+</para>
+<para>
+Tells it that MYPROG.BAS is not part of the on board ROM, and that it has to 
+go away and fetch it from somewhere else (in this case the disk). The Shell 
+works slightly differently, in that any command you type which is not 
+recognized by the Shell itself means that is will go away and attempt to find 
+it externally. For example:
+<screen>
+dir /d1
+</screen>
+</para>
+<para>
+The DIR command is not recognized by the Shell, therefore it automatically 
+attempts to load and run it from elsewhere. The Shell itself contains 
+relativly few in-built commands (unlike the BASIC interpretor) and so for the 
+majority of things will be forcing the execution of an external program.
+</para>
+<para>
+One of the utility programs shipped with Dragon OS9 is MDIR which simply 
+provides a list of things in memory, or a very simplified OS9 memory map. When 
+I run it on my system I get something like the following:
+<screen>
+ Module Directory at 10:47:24
+ADDR SIZE TY RV AT UC   NAME
+---- ---- -- -- -- -- ---------
+F05E  7E7 C1  1 r     OS9
+F853  4FC C1  1 r   1 OS9p2
+FD4F   2E C0  1 r   1 Init
+FD7D  182 C1  1 r   1 BooT
+BF00  122 C1  4 r   1 SYSGO
+C022  101 C1  1 r   1 Clock
+C123  6E7 C1  1 r   1 IOMan
+C80A  CF5 D1  1 r   1 Rbf
+D4FF  40E D1  1 r   2 Scf
+D90D  1FF D1  1 r     PipeMan
+DB0C  5E2 E1  7 r     Ddisk
+E0EE  3AF E1  6 r   2 Kbvdio
+E49D   8D E1  1 r     Printer
+E52A   28 E1  1 r     Piper
+E552   2E F1  1 r     D0
+E580   2E F1  2 r     D1
+E5AE   3C F1  1 r   2 Term
+E5EA   3A F1  1 r     P
+E624   26 F1  1 r     Pipe
+E64A  4FA 11  1 r   2 Shell
+EB44  121 E1  3 r   1 Rdisk
+EC65   2E F1  1 r   1 R0
+EC93  160 E1  1 r     PC_Disk
+EDF3   30 F1  1 r     H0
+5A00  1A6 11  1 r   1 Mdir
+</screen>
+</para>
+<para>
+The majority of these modules are the drivers, managers and general routines 
+that make up the 'core' of the OS9 system. In fact, the only programs as such 
+are the Shell, and the MDIR program itself. One of the things to note, is that 
+if you run MDIR on your OS9 system, it would almost certainly look different: 
+not only would you probably have different drivers, they would almost 
+certainly appear in different areas of memory. This is one of the key 
+differences between OS9 and normal Dragon BASIC mode, that any program written 
+for OS9 does not know where it will be in memory and therefore has to be 
+written to be run from any location in memory. One of the side effects of this 
+is that code modules in OS9 will probably tend to execute slower that a 
+DragonDOS counterpart. Part of this is also due to the fact that within the 
+Dragon BASIC/DOS ROM routines always lie at fixed addresses, and can be called 
+quickly. OS9 programs have to perform more complex indexing operations to find 
+routines. Therefore, OS9 always seems considerably more slower and therefore 
+more painful to work with than DragonDOS.
+</para>
+<para>
+To see this, and some of the pros and cons of writing programs for each 
+system, here is a couple of 'hello world' routines for each system. Firstly, 
+the Dragon assembler version - this assumes DASM to be the assembler:
+<screen>
+10 EXEC &amp;H6C00
+20 ALL
+30 @MSG FCC 0,"HELLO WORLD",0
+40 @START LDX #@MSG
+50 JSR $90E5
+60 RTS
+70 END @START
+80 EXEC
+</screen>
+</para>
+<para>
+It is a fairly simple piece of code, and also fairly easy to write, assemble 
+and get running. Firstly, DASM is loaded from disk, you write the code much 
+like a BASIC program using all the normal BASIC editor functions then just run 
+it. The program itself makes use of a standard ROM routine, which prints a 
+string pointed to by the X register. The 'END' statement at line 70 puts the 
+starting address into the default EXEC location, which is then called by line 
+80.
+</para>
+<para>
+Onto the OS9 version then:
+<screen>
+ nam WORLD
+ ttl Hello World program
+
+ ifp1
+  use /d0/defs/os9defs
+ endc
+
+*Data area
+ org 0
+stack rmb 200
+datsiz equ .
+
+*Module header
+type equ PRGRM+OBJCT
+revs equ REENT+1
+ mod length,name,type,revs,start,datsiz
+name fcs /WORLD/
+msg  fcc /Hello World/
+ fcb 13
+
+*main code
+start leax msg,pcr
+ ldy #12
+ lda #1
+ os9 i$writln
+ bcs error
+ clrb
+error os9 f$exit
+
+ emod
+
+length equ *
+</screen>
+</para>
+<para>
+One of the first problems is actually getting an environment to write this in. 
+Dragon OS9 comes with an edit command, which in all honestly is complete 
+rubbish and I'd suggest you use Stylo or another editor. I use a program 
+called 'ed' which is based on the Unix 'vi' editor. Once you've written it, 
+saved it to disk, you can assemble it using the OS9 assembler:
+<screen>
+asm world #20k
+</screen>
+</para>
+<para>
+Assumes you've saved the file as world. Assuming all goes well, you will end 
+up with a file called 'world' in your default execution directory (typically 
+/d0/cmds). However, it is the norm for OS9 (I have found anyway) that things 
+are not often that straightforward. Firstly, you may find you don't actually 
+have a copy of the 'asm' program, and even if you do the program you have just 
+written requires an additional file (called up by the 'use /d0/defs/os9defs' 
+statement) which may or may not be on your disk. Either way, you will probably 
+end up searching through a variety of disks in order to get the files required 
+to the right place. Once you have, and a great deal of disk chugging later 
+with any luck, simply typing 'world' at the OS9 shell prompt, should once 
+again after a bit of disk chugging display the message 'hello world' on your 
+screen.
+</para>
+<para>
+So how does it all work, and is it all worth it. Firstly, the NAM and TTL 
+directives just tell the assembler the name and title of your program and are 
+not strictly required. Next, there is an assembler directive to include the 
+file /d0/defs/os9defs but only on pass one (the IFP1 condition) of the 
+assembly. This file contains a list of definitions about OS9 and is almost 
+always required for any OS9 module. Whilst you can get away without it, for 
+general neatness and code readability it's a good idea to use it. For example, 
+in our simple program, the words PRGRM, OBJCT, i$write and f$exit are all 
+definitions from os9defs.
+</para>
+<para>
+Next the program's data area is defined, which as a minimum should define at 
+least 200 bytes for the stack (I'll cover data areas later). Following this, 
+information for the programs module header is defined. In OS9, each program 
+unit or module must conform to a specific format in order for OS9 to recognize 
+it and successfully execute it. In particular, it contains information about 
+the execution address, how much data size is required and also a checksum of 
+the whole module. All this minimizes the risk of trying to run corrupted or 
+invalid code. The 'mod' assembler directive tells the assembler to form this 
+header using the information provided. This requires the overall length of the 
+module then a pointer to the name of the module. Next are two equates defined 
+as TYPE and REVS. The TYPE equate is a single byte defining the module type 
+and language and is preset to a normal program module (PRGRM) and 6809-object 
+code (OBJCT). The REVS equate is a single byte defining the module attributes 
+and revision level. It has an attribute of sharable (REENT) and a revision 
+level of 1. The final two parameters are the start address and overall module 
+data size. 
+</para>
+<para>
+Following the module header is the module name. The FCS directive is much like 
+the FCC directive except the top bit of the last character is the string is 
+set. This is often used by OS9 to indicate the end of a string. Finally, the 
+main code for the program occurs.
+</para>
+<para>
+The code make use of the OS9 system call i$writln. In order to make a system 
+call, the assembler directive 'os9' is used followed by the call name. All the 
+valid OS9 call names are defined in the os9defs file. OS9 system calls are 
+made through one of the software interrupts, in this case SWI2 followed by a 
+unique code identifying the call number. Therefore, the assembler directive 
+'os9 i$writln' translates into 'SWI2 $8C' where $8C is the i$writln code.
+</para>
+<para>
+This call is much like the ROM call at $90E5, where the X register points to 
+the string to display. In addition, however the Y register should also contain 
+the maximum number of characters to display and the A register should contain 
+the path number. A path number of 1 refers to the standard output - the 
+display. The call will then display the required number of characters, or up 
+to the first carriage return - whichever occurs sooner. On completion of the 
+call, the carry bit is set if an error has occured with the error number in 
+the B register. Finally, the routine is terminated with a call to the F$EXIT 
+routine not an RTS instruction. 
+</para>
+<para>
+Once the code is complete, the EMOD statement indicates that this is the end 
+of the module, and generates the module's checksum and then the length label 
+is calculated.
+</para>
+<para>
+This program also gives an example writing position independent code ie. code 
+that can be executed anywhere in memory. Note how the X register in the os9 
+example is set to point to the string compared to the DragonDOS one:
+<screen>
+DDOS: LDX #@MSG       - absolute address of MSG
+OS9: leax msg,pcr     - relative address of MSG
+</screen>
+</para>
+<para>
+The OS9 variant sets X to point to the MSG label relative to the current 
+program counter ie. whereever you are in memory. The DragonDOS one specifies 
+the absolute address in memory. This means that with the DragonDOS code you 
+are forced to have your program at this location in order for it to work. Of 
+course, you could replace your DragonDOS one with the same instruction as the 
+OS9 one and make it position independent, but with OS9 you must use this 
+format or it will not work.
+</para>
+</section>
+<section>
+<title>OS9 Advantages</title>
+<para>
+From the last couple of articles, we have created two comparable programs 
+under Dragon BASIC and OS9 - the now famous 'Hello World' program. However, 
+the steps to create the OS9 one are significantly more complex than the 
+DragonDOS counterpart. We have already seen one of the OS9 program's 
+advantages - that of position independent code, though with a bit of 
+forethought the Dragon BASIC one can also be made position independent. Here 
+are a few other advantages of the OS9 variant.
+</para>
+<para>
+Suppose you wanted to print the words 'Hello World' on the printer. This can 
+be accomplished fairly simply under both OS9 and Dragon BASIC:
+<screen>
+Dragon BASIC:  POKE 111,254:EXEC
+	 OS9:  world >/p
+</screen>
+</para>
+<para>
+The Dragon BASIC variant simply sets the default IO location to 254 (-2) the 
+printer. The OS9 one utilises the shell re-direction operator (>) to send the 
+output to the printer. So far so good. What if you wanted to send this message 
+to a file for example. With standard DragonDOS, there is no easy way - later 
+DOS variants can use the OPEN# format:
+<screen>
+OPEN #1,"O","TEST.DAT"
+POKE 111,1:EXEC
+CLOSE #1
+</screen>
+</para>
+<para>
+Once again the OS9 variant is pretty much straightforward:
+<screen>
+world >test.dat
+</screen>
+</para>
+<para>
+Suppose then you have added a custom device to your system, an extra parallel 
+port for example for a second printer. Under OS9 all you need to do is create 
+a new device driver/descriptor and call it say P1. The world program can 
+automatically be used with your new device:
+<screen>
+world >/p1
+</screen>
+</para>
+<para>
+The OS9 world program can then theorhetically be run on any OS9 system and 
+make use of any device which is implemented on that machine.
+</para>
+<para>
+The other 'selling point' of OS9 is it's multi-tasking ability - ie. run more 
+than one program at once. This is all handled by the OS9 kernel routines, and 
+you do not need to worry about it when writing code, except to ensure your 
+programs are position independent, and re-entrant. A re-entrant program is all 
+to do with how any data your program utilises is managed and means that one 
+copy of a program in memory can be running a number of times with a different 
+set of data. 
+</para>
+</section>
+<section>
+<title>OS9 Data Storage</title>
+<para>
+When you write your OS9 program, you should allocate all your data areas using 
+rmb directives starting at 0 eg.
+<screen>
+*Data area
+ org 0                   specify starting at 0
+length rmb 2             data items for
+name   rmb 20            the program
+next   rmb 2
+stack  rmb 200
+datsiz equ .
+</screen>
+</para>
+<para>
+Your program's stack is also part of this data area, so you should allocate a 
+sufficient area for this - 200 bytes is a recommended minimum. The datsiz 
+label gives the total data size required for the program, and is used as part 
+of the module header created by the MOD assembler directive:
+<screen>
+ mod length,name,type,revs,start,datsiz
+</screen>
+</para>
+<para>
+When OS9 starts your program, it sets up some of the CPU registers to provide 
+information about your data area-
+<screen>
+|                 | 
+| Parameter Area  |
+-------------------  -- X, S
+|                 |
+|                 |
+|   Data Area     |
+|                 |
+-------------------
+|  Direct Page    |
+-------------------  -- U, DP  (LS address)
+</screen>
+</para>
+<para>
+Firstly, the U and DP registers point to the start of your data area, with the 
+stack pointer set to the top of your data area. Essentially then, you just 
+need to reference the U register when accessing your data variables within 
+your program:
+<screen>
+ sty length,u             stores Y into 'length' 
+ ldx next,u               loads X from 'next' 
+</screen>
+</para>
+<para>
+However, any data items stored within the first 256 bytes of your data area 
+can be accessed more quickly and efficiently by making use of the DP register. 
+This is an 8 bit register which contains the upper 8 bits of an address. In 
+order to specify DP addressing with the OS9 assembler simply supply the data 
+label:
+<screen>
+ sty length               use DP addressing to
+ ldx next                 access data items
+</screen>
+</para>
+<para>
+Using the data declarations used earlier:
+<screen>
+ length lies at offset 0 ($00).
+ next lies at offset 22 ($16).
+</screen>
+</para>
+<para>
+The assembler translates this into:
+<screen>
+ STY $00
+ LDX $16
+</screen>
+</para>
+<para>
+instructing the processor to use the DP register to provide the upper part of 
+the address. Therefore, if DP=$20 this would become:
+<screen>
+ STY $2000
+ LDX $2016
+</screen>
+</para>
+<para>
+Not only does this form of addressing take up less storage space, it is also 
+quicker to execute. 
+</para>
+<para>
+You will still need to use the U register to set up pointers to data buffers 
+since you cannot use the DP register for this:
+<screen>
+ leax name,u       X=start of name item
+</screen>
+</para>
+<para>
+There is an important difference between the way the OS9 assembler and 
+DragonDOS assemblers (DASM &amp; DREAM) use the DP register. By default, the OS9 
+assembler will use the DP register if you just enter something like:
+<screen>
+ ldx next  -> LDX $16
+</screen>
+</para>
+<para>
+However, DragonDOS assemblers will automatically use extended addressing so 
+assuming @NEXT lies at address $5020.
+<screen>
+ LDX #@NEXT -> LDX $5020
+</screen>
+</para>
+<para>
+The assembler directive '>' is used to set extended addressing under OS9 ie.
+<screen>
+ ldx >$16   -> LDX $0016
+</screen>
+</para>
+<para>
+and co-incidently is used to force DP addressing under DragonDOS ie.
+<screen>
+ ldx >$F0  -> LDX $F0
+</screen>
+</para>
+<para>
+The DP register is nearly always set to 0 under DragonDOS so you can use it to 
+quickly access memory locations 0-255. 
+</para>
+<para>
+So, just by the kernel setting the U and DP registers to different positions 
+in memory the same piece of code need only be resident in memory once but can 
+be running in different tasks with different data areas. The REENT bit set in 
+the module header is used to tell OS9 that our program supports this - if you 
+recall:
+<screen>
+*Module header
+type equ PRGRM+OBJCT
+revs equ REENT+1
+ mod length,name,type,revs,start,datsiz
+name fcs /WORLD/
+msg  fcc /Hello World/
+ fcb 13
+</screen>
+</para>
+<para>
+The parameter area (pointed to by the X register on program entry) is utilised 
+to pass data to a program. If it has been started from the shell (command 
+line) any text following the program name will be stored here terminated by a 
+carriage return ie.
+<screen>
+OS9:world this is a parameter
+</screen>
+</para>
+<para>
+then X would point to a block of memory containing the ASCII string:
+<screen>
+this is a parameter[CR]
+</screen>
+</para>
+</section>
+<section>
+<title>Other Progamming Tips</title>
+<para>
+Generally, thats about all there is to writing OS9 programs. Remember, that an 
+OS9 process should exit by calling os9 f$exit (with the B register containing 
+any error code you wish to return, or 0 for none) instead of rts. The OS9 
+System Programmers Manual which you should have with your copy of OS9 contains 
+all the system calls available. However, its worth mentioning a bit about 
+standard IO paths.
+</para>
+<para>
+We have already seen standard output used with the Hello World program:
+<screen>
+ ldy #12
+ lda #1          standard output
+ os9 i$writln
+ bcs error
+</screen>
+</para>
+<para>
+On entry to any OS9 program 3 file paths are open:
+<screen>
+0 = Standard Input (keyboard)
+1 = Standard Output (screen)
+2 = Standard Error (screen)
+</screen>
+</para>
+<para>
+These may or may not correspond to screen or keyboard depending on whether the 
+shell or other program has re-directed them ie.
+<screen>
+world >/p
+</screen>
+</para>
+<para>
+forces standard output to be the printer. This means that for the duration of 
+your program, path number 1 is still valid but points to the printer instead. 
+This is the premise of a lot of OS9 utilities called filters which typically 
+perform some kind of modification to incoming data and write the modified data 
+out. An example might be a program to add line feeds to a file in preparation 
+to porting it to another environment which requires CR/LF terminated strings 
+as opposed to CR which OS9 uses. The basis of the filter might be:
+<screen>
+loop
+ leax buffer,u      point to buffer
+ ldy #1             1 char to read
+ clra               from stdin
+ os9 i$read         read byte
+ bcs error          handle error
+ inca               set to stdout
+ os9 i$write        write byte
+ bcs error
+ ldb ,x             was it a CR
+ cmpb #13
+ bne loop           no, loop for next
+ ldb #10            write an LF
+ stb ,x             to stdout
+ os9 i$write
+ bcs error
+ bra loop
+
+error cmpb #E$EOF   check for EOF
+ bne term           if not, exit with error
+ clrb               else quit ok
+term os9 f$exit
+</screen>
+</para>
+<para>
+Here we make use of the i$read, i$write calls which read and write 'raw' data. 
+A byte is read from stdin and then written out again to stdout. If it was a CR 
+then an LF is also written to stdout. The program will terminate when an error 
+condition occurs. If all the data is exhausted, this error will be EOF on the 
+read which is therefore not treated as an error and causes the program to exit 
+without error. This is the case for all filters. Assuming the program is 
+assembled to the file 'addlf' an example usage could be with the stylograph 
+mail merge utility (mm):
+<screen>
+mm my_doc ! addlf >/d1/my_doc.out
+</screen>
+</para>
+<para>
+Here the Stylograph file 'my_doc' is output by mailmerge and piped into our 
+addlf utility, so stdin is redirected here from the pipe. The resulting output 
+(stdout) is then sent to the file 'my_doc.out'.
+</para>
+<para>
+This filter isn't very efficient, because it carries the overhead of calling 
+i$read/i$write for every byte. If you recall, an OS9 system call translates 
+into an SWI2 (software interrupt) call. This results in a lot of processing, 
+and also the stacking/de-stacking of all the registers required for an SWI 
+call. It would be far better to read the data in larger chunks and process it 
+in a separate loop. 
+</para>
+<para>
+Note that any registers not updated explicity for output of a system call will 
+remain unchanged from when the call was made eg.
+<screen>
+i$read:  Entry:  X = Addr of data
+		 Y = Bytes to read
+		 A = Path number
+	 Exit:   Y = Bytes actually read
+		 ALL OTHER REGISTERS UNCHANGED
+</screen>
+</para>
+</section>
+<section>
+<title>Device Drivers</title>
+<para>
+One of the things which became apparant after the infamous 'world' routine was 
+that this simple program could make use of any OS9 system peripherals provided 
+the drivers were there for it to use. This is also true of most OS9 software - 
+if you have the drivers any software can use it. For example, if you added a 
+hard drive to your system and called it H0, you could quite easily store 
+Stylograph on it, save your data on it, run the C compiler not to mention our 
+'Hello World' program. Unfortunatly, this isn't the case with DragonDOS which 
+is more or less tied to the pre-set Dragon hardware. So if you are planning to 
+add ram discs, hard discs etc. then OS9 is by far the easiest platform to 
+write software for. You will also probably find that writing software becomes 
+that much easier for OS9 once you have access to faster disk drives and maybe 
+an 80 column screen. It's by no means impossible to write stuff for DragonDOS 
+- just a lot harder thats all and I'll touch on this a bit later.
+</para>
+<para>
+The key to this flexibility really comes down to device drivers and 
+descriptors on an OS9 system. Generally, any device you add will fall under 
+the category of disk - in which case an RBF device driver is required or 
+sequential (serial port, printer etc.) in which case an SCF device driver is 
+required.
+</para>
+<para>
+Device drivers are just like any other OS9 program, they have a module header, 
+data area and code to perform to necessary operations. Here is a rough outline 
+of a device driver which is called DEVx:
+<screen>
+ nam DEVx
+ ttl DEVx device driver
+
+ ifp1
+ use /h0/defs/os9defs
+ use /h0/defs/iodefs
+ use /h0/defs/scfdefs
+ endc
+
+*data
+ org V.SCF
+PST equ .
+
+type equ DRIVR+OBJCT
+revs equ REENT+1
+ mod PEND,PNAM,type,revs,PENT,PST
+
+PNAM fcs /DEVx/
+
+PENT lbra INIT
+ lbra READ
+ lbra WRITE
+ lbra GETSTA
+ lbra PUTSTA
+ lbra TERM
+
+INIT   .
+READ   .
+WRITE  .
+GETSTA .
+PUTSTA .
+TERM   .
+
+ emod
+PEND equ * 
+</screen>
+</para>
+<para>
+It follows the standard module structure, first the name and title of the 
+module NAM and TTL. Then the 'use' files are given. As well as os9defs we 
+include iodefs which contains i/o specific definitions, and because this just 
+happens to be an SCF device, scfdefs are included also. Next comes the data 
+area, however because the file managers (RBF or SCF) also require some data, 
+you should not start your data area off at 0 but the V.SCF equate (or DRVBEG 
+for RBF devices). No stack space is required here as the stack used will be 
+the one from the calling program. The PST label marks the datasize of the 
+module, and then the module header is given. Notice that the PRGRM equate is 
+replaced by the DRIVR equate indicating this is a device driver.
+The PENT label marks the start of the driver. All OS9 drivers have 5 entry 
+points at the module start and LBRA instructions should always be used to call 
+these routines which are as follows:
+</para>
+<para>
+INIT - called once on module start. Include any initialisation code required 
+to set your device up.
+</para>
+<para>
+READ - called to read an 'item' of data from the device. Depending on the file 
+manager an 'item' can be anything from 1 character (SCF devices) to a disk 
+sector (RBF devices).
+</para>
+<para>
+WRITE - called to write an 'item' of data to the device.
+</para>
+<para>
+GETSTA - called to perform miscellaneous IO status routines. Since all IO 
+devices have different capabilities this provides a method to perform 
+operations which generally return some sort of status back to the caller - for 
+example the End Of File function (EOF) is implemented via a GetStat call.
+</para>
+<para>
+SETSTA - similar to GetSta, but generally used to set some status information 
+on the device.
+</para>
+<para>
+TERM - called once when the module is about to be removed from memory. Include 
+any de-allocation of memory/clean up routines etc. here.
+</para>
+<para>
+Prior to showing a couple of example drivers, there are a few data structures 
+you need to be aware of when dealing with device drivers. Firstly, there is 
+your static storage, which can be thought of in similar terms to the data area 
+of a normal module. The static storage area for your device driver starts at 
+the V.SCF (or DRVBEG) position given in the previous header file, and 
+similarly to a normal OS9 program, the U register always points to this area. 
+Note, that unlike a module's data area, you should not use DP addressing on 
+data items. So, if you declare a variable as such:
+<screen>
+ org V.SCF
+flag rmb 1
+</screen>
+</para>
+<para>
+it can be accessed in any of the 5 procedures as:
+<screen>
+ lda flag,u
+</screen>
+</para>
+<para>
+In addition to your own data held here, the system also stores some of it's 
+own static data just prior to the V.SCF (or DRVBEG) label which you can also 
+access. This consists of a 'common' area followed by a file manager specific 
+area. The exact format of these are listed in the System Programmers Guide 
+(and also in the iodefs,scf/rbfdefs files) but one of these items held in the 
+common area which the example drivers will make use of is named V.Port and 
+holds the address of the physical device in memory. How this gets set up, I 
+will cover later but suffice to say it enables the driver to access a physical 
+device in memory without having to know where it is exactly. As an example, if 
+you had an SCF device driver to talk to MC6821 PIAs and you had 4 of these 
+PIAs in your system you would only need 1 copy of the driver loaded - the 
+system would ensure V.Port is setup correctly depending on which PIA is being 
+accessed:
+<screen>
+ lda #$FF      A=255
+ ldx V.Port,u  port address
+ sta 1,x       port address+1=255
+</screen>
+</para>
+<para>
+The other data block you may need to be aware of is called the path 
+descriptor. This is a unique set of data for each open file or device in the 
+system. You cannot allocate your own data items here, but you can read &amp; write 
+to data items here. Generally, the path descriptor holds information relevent 
+to the current open file or device (such as path numbers, file lengths etc.) 
+rather than constant device characteristics (port address etc.). Again, there 
+is a common part of the path descriptor, and then a file manager specific 
+area. The format of path descriptors is also listed in the System Programmers 
+Guide and can also be found in the iodefs,scf/rbfdefs files. Initially, you 
+will not probably need to utilise anything in the path descriptor, but if you 
+start writing more complex drivers you almost certainly will - generally the 
+file manager will provide most of the path descriptor management. 
+</para>
+<para>
+The path descriptor is pointed to by the Y register given in the READ, WRITE, 
+GETSTA &amp; SETSTA procedures (remember, it only exists for an open file so will 
+not be accessable by the INIT &amp; TERM routines) and as such can be accessed as 
+follows:
+<screen>
+ lda PD.PD,y    A=path number from path descriptor
+ </screen>
+</para>
+<para>
+Last time I covered general device drivers. Next, are 2 example drivers, one 
+an SCF device, the other an RBF device.
+</para>
+</section>
+<section>
+<title>SCF Device Driver</title>
+<para>
+This device driver utilises an MC6821 PIA to perform character driven IO.
+</para>
+<para>
+The first section is the common module header format:
+ <screen>
+*******************************************
+* PIA21
+*  Device Driver for MC6821 PIAs
+*
+* By J.Bird (c) 1992
+*
+* Uses Side B of MC6821 PIA
+* CA2 &amp; CB2 Control Strobe lines
+* Non-interrupt driven
+*
+
+ nam PIA21
+ ttl MC6821 PIA Device Driver
+
+ ifp1
+ use /h0/defs/os9defs
+ use /h0/defs/iodefs
+ use /h0/defs/scfdefs
+ endc
+
+***********************
+* Edition History
+*
+* #   date     Comments
+* - -------- -------------------------------
+* 1 05.05.92  Driver first written. JRB
+* 2 25.03.95  Update for Parallel Dragon link. JRB.
+
+Revision equ 2
+
+PBCREG equ %00101101
+PBOUTP equ %11111111
+PBINP  equ %00000000
+
+ org V.SCF
+PST equ .
+
+ mod PEND,PNAM,Drivr+Objct,Reent+Revision,PENT,PST
+ fcb READ.+WRITE.
+PNAM fcs /PIA21/
+
+PENT lbra INIT
+ lbra READ
+ lbra WRITE
+ lbra GETSTA
+ lbra PUTSTA
+ lbra TERM
+</screen>
+</para>
+<para>
+This defines an SCF man device called PIA21. On a sideline note, you may have 
+noticed the revision number as 2, and this is built into the module header. 
+This will ensure that if this module is loaded into memory and revision 1 is 
+also in memory (as part of the OS9 boot load for example) that revision 2 will 
+be used. Subsequently, a revision 3 would take precedence over 2 etc. etc.
+</para>
+<para>
+The following two routines will be used throughout the code to configure the 
+port for either input or output. Note the use of the V.Port address from the 
+static storage to find the IO port's address.
+<screen>
+**********************
+* SETOUT
+*   Set port dir for o/p
+*
+SETOUT ldx V.PORT,u       load port address
+ clr 1,x                  clear control reg
+ lda #PBOUTP              set port for o/p
+ sta ,x
+ lda #PBCREG              reload ctrl reg
+ sta 1,x
+ clrb
+ rts
+
+**********************
+* SETIN ldx V.PORT,u
+*   Set port dir for i/p
+*
+SETIN ldx V.PORT,u
+ clr 1,x
+ lda #PBINP
+ sta ,x
+ lda #PBCREG
+ sta 1,x
+ clrb
+ rts
+</screen>
+</para>
+<para>
+The first of the device driver procedures INIT, just configures the port as 
+input and returns. All routines should return with B=0 and the carry bit clear 
+unless you wish to report an error, in which case the B register should 
+contain the OS9 error code. Note, that rts is used to return from a device 
+driver routine, not os9 f$exit as per normal OS9 programs.
+<screen>
+**********************
+* INIT
+* Entry: U = Static storage
+*   Setup the PIA
+*
+INIT bsr setin
+ clrb
+ rts
+</screen>
+</para>
+<para>
+The READ routine is required to fetch a data item from the IO port. For SCF 
+type devices, this is 1 character and should be returned in the A register.
+<screen>
+**********************
+* READ
+* Entry: U = Static Storage
+*        Y = Path Descriptor
+* Exit:  A = Character read
+*
+*   Read a byte from Port B
+*
+READ ldx V.PORT,u      load port address
+readlp tst 1,x         test for data ready
+ bmi readbyte
+ pshs x                sleep for a bit if not
+ ldx #10
+ os9 f$sleep
+ puls x
+ bra readlp
+readbyte lda ,x        read byte
+ sta ,x                issue strobe
+ clrb 
+ rts
+</screen>
+</para>
+<para>
+The READ routine makes use of another OS9 system call - sleep. On entry, X 
+should contain the number of clock cycles to sleep for (clock cycle = 1/50 
+second on the Dragon). This is not strictly required, however when an IO 
+request is performed OS9 prevents any other process from running until it 
+completes or executes a sleep request. For example, if you had a process 
+running as a background task using this device and the device stalled (ie. it 
+never became ready) your machine would lockup. By sleeping for a short while 
+between checks you ensure this cannot happen.
+</para>
+<para>
+The WRITE routine acts in a similar way to READ, except the character passed 
+in the A register should be written to the port:
+<screen>
+**********************
+* WRITE
+* Entry: U = Static storage
+*        Y = Path Descriptor
+*        A = Char to write
+* 
+*   Write a byte to Port B
+*
+WRITE ldx V.PORT,U   load port address
+ sta ,x              write byte
+writlp tst 1,x       wait for ack
+ bmi wrt
+ pshs x
+ ldx #1
+ os9 f$sleep
+ puls x
+ bra writlp
+wrt lda ,x           clear control reg
+ok clrb
+ rts
+</screen>
+</para>
+<para>
+The next two routines demonstrate device driver usage of the GetStat and 
+SetStat calls and also the path descriptor. 
+</para>
+<para>
+There are a few predefined GetStat calls under OS9 which nearly all SCF device 
+drivers will have to deal with. These are Test for Data Ready and Test for End 
+Of File. The predefined GetStat codes are defined in the OS9Defs file but be 
+warned: a lot are device dependent and will not be implemented. Generally, 
+only the ones listed above are available on all SCF devices.
+</para>
+<para>
+GetStat calls are made by the caller by issuing an OS9 i$getstt call with the 
+A register containing the path number and the B register containing the 
+GetStat code - the other register contents depend on the GetStat call being 
+made. This example program loops until data is available from the keyboard:
+<screen>
+chklp clra             pathnum of stdin
+      ldb #SS.Ready    getstat code
+      os9 i$getstt     issue call
+      bcc okay         if c bit clear data is rdy
+      cmpb #E$NRDY     otherwise check the code
+      bne error        returned was not ready
+      bra chklp
+</screen>
+</para>
+<para>
+In order to write a routine to process a GetStat call, you need to access the 
+contents of the registers when the call was made - in this case the B register 
+to obtain the getstat code. Within the common part of the path descriptor is a 
+pointer to a data area which contains all the register contents when the call 
+was made. Labels set up in the os9defs file make accessing this information 
+easy.
+</para>
+<para>
+The following GetStat routine processes 3 codes. It first retrieves the 
+register pointer from the path descriptor then loads what was in the B 
+register into the A register using the R$B label to find the location. The 3 
+getstat codes are SS.Ready - test for data ready which is performed by using 
+the PIA to determine if a data byte is waiting to be processed or not, SS.EOF 
+- end of file which for this device has no meaning and therefore always 
+returns with no error ie. not end of file. The final code is a user defined 
+one I created specific for this device named SS.VSupv. When called with this 
+code it forces the port to change direction to input using the 'setin' 
+procedure defined earlier. All other codes return an error.
+<screen>
+**********************
+* GETSTA
+*   U = Static Storage
+*   Y = Path Descriptor
+*
+GETSTA ldx PD.Rgs,y     X=pointer to registers
+ lda R$B,x              A=contents of B reg.
+ cmpa #SS.Ready         check for ready code
+ bne gsta1              if not, check next
+ ldx V.Port,u           use the PIA to determine
+ tst 1,x                if data is available
+ bmi ok
+ ldb #E$NotRdy
+ coma                   sets the carry flag
+ rts
+gsta1 cmpa #SS.EOF      check for eof code
+ beq ok                 which always returns ok
+ cmpa #SS.VSupv         check for a special code
+ beq setin              which changes port dir
+ comb                   otherwise return
+ ldb #E$UnkSVC          unknown code error
+ rts
+</screen>
+</para>
+<para>
+The SetSta call works in an identical way to GetStat, and generally SCF 
+devices do not use SetStat calls so you can just return the E$UnkSVC error 
+immediatly. For our example driver, SetStat supports the new SS.VSupv code 
+which when received will force the port direction to be output using the 
+'setout' procedure:
+<screen>
+**********************
+* PUTSTA
+*   Supv request sets port to o/p
+*
+PUTSTA ldx PD.Rgs,y     X=register stack
+ ldb R$B,x              B=contents of reg B
+ cmpb #SS.VSupv         check for recognized code
+ beq setout             and process it
+ comb                   otherwise return error.
+ ldb #E$UnkSVC
+ rts
+</screen>
+</para>
+<para>
+Finally, the TERM routine. Since the driver has not allocated any extra memory 
+or anything it can just return okay:
+<screen>
+**********************
+* TERM
+*   Terminate Driver
+*
+TERM clrb
+ rts
+
+ emod
+PEND equ * 
+</screen>
+</para>
+<para>
+Thats just about it.
+</para>
+<para>
+Before proceeding to an RBF device driver there is one more thing required to 
+complete the new SCF device - a device descriptor.
+</para>
+<para>
+The device descriptor is just composed of a set of data items which define the 
+device you are creating. In particular, it is the name you refer to when you 
+want to access the device - so our example is called P1 so in order to access 
+it you just refer to this device ie.
+<screen>
+list startup >/p1
+</screen>
+</para>
+<para>
+copies the startup file to device /p1. The descriptor also contains information 
+to identify it's device driver, file manager and other unique information. It 
+also contains the port address (which is copied to V.Port so your driver can 
+refer to it) and in the case of SCF devices contains a whole list of attributes 
+referring to the capabilities of the device.
+</para>
+<para>
+Here is the PIA device descriptor to go with the PIA21 device driver:
+<screen>
+****************************************
+* PIA Descriptor module
+*
+* Source by J.Bird (C) 1992
+*
+ ifp1
+ use /h0/defs/os9defs
+ endc
+
+ nam P1
+ ttl PIA Device Descriptor
+
+ mod PEND,PNAM,DEVIC+OBJCT,REENT+1,PMGR,PDRV
+
+ fcb READ.+WRITE.+SHARE.
+ fcb $FF IOBlock (unused)
+ fdb $FF32 hardware address
+ fcb PNAM-*-1 option byte count
+ fcb $0 SCF device
+ fcb $0 Case (upper &amp; lower)
+ fcb $1 Erase on backspace
+ fcb $0 delete (BSE over line)
+ fcb $0 echo off
+ fcb $1 lf on
+ fcb $0 eol null count
+ fcb $0 no pause
+ fcb 24 lines per page
+ fcb $8 backspace
+ fcb $18 delete line char
+ fcb $0D end of record
+ fcb $1b eof
+ fcb $04 reprint line char
+ fcb $01 duplicate last line char
+ fcb $17 pause char
+ fcb $03 interrupt char
+ fcb $05 quit char
+ fcb $08 backspace echo char
+ fcb $07 bell
+ fcb $00 n/a
+ fcb $00 n/a
+ fdb pnam offset to name
+ fdb $0000 offset to status routine
+pnam fcs "P1"
+pmgr fcs "SCF"
+pdrv fcs "PIA21"
+ emod
+pend equ *
+ end
+</screen>
+</para>
+<para>
+Once again it's format is similiar to any OS9 module, with a program type of 
+DEVIC indicating device descriptor. The module just consists of fcb data 
+stataments. Generally, this may well suffice for any SCF module you care to 
+create. Some key features to note however are:
+<screen>
+ fcb READ.+WRITE.+SHARE.
+</screen>
+</para>
+<para>
+indicates the device can be read, written and also multiple processes can 
+access it at any one time. Failing to specify some of these will cause the 
+system to return 'device not capable of operation' type errors.
+<screen>
+ fdb $FF32  hardware address
+</screen>
+</para>
+<para>
+The physical hardware port address. This indicates our PIA sits at $FF32 in 
+memory - copied to V.Port in the device driver.
+<screen>
+ fcb $0     echo off
+</screen>
+</para>
+<para>
+This is an important one. If set to non-zero when the SCF file manager reads a 
+byte it will automatically send it back out using the write routine. In the 
+case of our 6821 driver which only operates in one direction at a time this is 
+not a good idea.
+<screen>
+pnam fcs "P1"
+pmgr fcs "SCF"
+pdrv fcs "PIA21"
+</screen>
+</para>
+<para>
+Finally, these give the device descriptor name (pnam), its associated file 
+manager (pmgr) and device driver (pdrv) and is the only way the system has of 
+working this out.
+</para>
+<para>
+As an additional note, the device driver INIT also sets the Y register to point 
+to the device descriptor data should you need to access it. Most of the 
+parameters are also copied into the path descriptor option section where you 
+can access them if required. Remember, any modifications you make to the path 
+descriptor locations will only be valid for the duration the device or file is 
+'open'.
+</para>
+<para>
+Once you have your new driver and descriptor you just load them into memory and 
+then any OS9 program can use them:
+<screen>
+load pia21
+load p1
+list startup >/p
+</screen>
+</para>
+</section>
+<section>
+<title>RBF Device Driver</title>
+<para>
+RBF drivers tend to be a little more complex than SCF ones but not drastically 
+so. In addition, practically everything covered in the SCF device also applies 
+to RBF device covered here. They also tend to open up your system a lot more - 
+essentially you are getting another disk drive out of it. The example I am 
+using here is based around the
+<ulink url="http://www.onastick.clara.net/rdisk.htm">RAM disc design</ulink>
+I wrote a couple of years ago in Up2Date. 
+</para>
+<para>
+The RAM disc design used once again an MC6821 PIA, which utilised one side to 
+select a RAM function to be performed and the other side as the data bus. The 
+design was specifically built to make it easy to interface into an OS9 system. 
+</para>
+<para>
+For RBF devices, the read and write functions are required to transfer a 256 
+byte sector from a given 24 bit logical sector number (LSN). LSNs give a means 
+of addressing disks without having to worry about tracks/sectors per track. 
+They are numbered from 0 upwards starting (on a 'real' disk anyway) as LSN 
+0=track 0, sector 1 etc. On a standard Dragon disk (40T/SS) LSN 0=Track 0, 
+sector 1, LSN 1=Track 0, sector 2, LSN 18=Track 1, sector 1 etc.
+</para>
+<para>
+The PIA design allowed for the following operations:
+</para>
+<para>
+1. Write MSB of LSN. Write the top 16 bits of the LSN to the RAM drive (the top 
+8 bits are discarded as you would need a phenominal amount of RAM to need these 
+bits).
+</para>
+<para>
+2. Write LSN of LSN. Write the botton 8 bits of the LSN to the RAM drive.
+</para>
+<para>
+3. Read a sector. Transfer the sector byte by byte from the RAM drive hardware 
+to a buffer.
+</para>
+<para>
+4. Write a sector. Transfer a sector byte by byte from a buffer to the RAM 
+drive hardware.
+</para>
+<para>
+So, there is really minimal processing involved to utilise the RAM drives. Most 
+floppy, or hard disk drivers would normally require some LSN to track/sector 
+conversion.
+</para>
+<para>
+All RBF device drivers differ subtly from SCF drivers in that there are 
+certain mandatory things you must perform within them in order for them to 
+function correctly. I will attempt to explain these throughout the driver 
+example.
+</para>
+<para>
+An RBF driver starts off like all OS9 modules, with the standard header 
+format. Here you see I am up to revision 6 of the PIA RAM disk driver:
+<screen>
+*********************************************
+* Rdisk
+*  A driver for a Ram disk!
+* By G.Twist (c) 1986
+* modified by Bernd H. Neuner 1987
+* Version for a totally different Ram disk!
+*
+* By J.B. &amp; O.B. (C) 1991,1992
+*
+
+ nam Rdisk
+ ttl A Device Driver for a RAM Disk
+
+ ifp1
+ use /d0/defs/os9defs
+ use /d0/defs/iodefs
+ use /d0/defs/rbfdefs
+ endc
+
+***********************
+* Edition History
+
+*  #   date    Comments
+* -- -------- ----------------------------------------------
+*  1 86/12/17  Driver first developed GDT
+*  2 86/12/18  work to fix minor access bugs GDT
+*  3 30.11.87  bug in COPY routine fixed. BHN (no more error 214 now.)
+*  4 31.12.91  Test version for main RAM JB/OB
+*  5 08.01.92  Driver for 128K PIA RAM JB/OB
+*  6 18.09.92  Up-issue to support up to 512K JB
+
+Revision equ 6 
+NumDrvs  set 1 Number of drives
+
+* pia control comands
+pia.ddr equ %00111000
+pia.off equ %00111100
+pia.act equ %00101100
+ext.msr equ %00000001
+ext.lsr equ %00001000
+ext.read equ %00000010
+ext.writ equ %00000110
+output equ %11111111
+outb equ %00001111
+pia.iora equ 0
+pia.cnra equ 1
+pia.iorb equ 2
+pia.cnrb equ 3
+
+
+ org Drvbeg
+ rmb NumDrvs*DrvMem
+LSNZERO  rmb 1
+RAMSTA equ .
+
+ mod RAMEND,RAMNAM,Drivr+Objct,Reent+Revision,RAMENT,RAMSTA
+RAMNAM fcs /Rdisk/
+
+RAMENT lbra INIT
+ lbra READ
+ lbra WRITE
+ lbra GETSTA
+ lbra PUTSTA
+ lbra TERM
+</screen>
+</para>
+<para>
+Along with the equates needed for the driver, the RBF driver static storage is 
+being utilised (note the V.SCF equate replaced with DRVBEG). The first 
+mandatory thing about an RBF driver is that you must allocate a drive table 
+for EACH drive you plan the driver to deal with. The drive table will hold 
+information particular to the drive (such as number of sectors/track etc.) and 
+must be the first block of data in your static storage. You should reserve 
+DRVMEM bytes (DRVMEM is defined in the rbfdefs file) multiplied by the number 
+of drives your driver can handle (in this case NumDrvs is defined as 1). So 
+the DDISK OS9 driver reserves 4 times this amount to handle the 4 possible 
+floppy disks on a Dragon system. The only data item declared for our use is 
+the LSNZERO byte. 
+</para>
+<para>
+The INIT routine of the driver also requires some mandatory code required by 
+all RBF drivers:
+</para>
+<para>
+1. Initialise the V.NDRV variable of the RBF static storage to the number of 
+drives the driver will deal with.
+</para>
+<para>
+2. Initialise the DD.TOT and V.Trak variables held within the drive tables of 
+the static storage of EACH drive to a non-zero value. 
+</para>
+<para>
+In this driver, although it is only targetted for 1 drive, there is a loop 
+construct present for dealing with multiple drives should this ever be a 
+requirement. In addition, our INIT routine also sets up the PIA and 
+initialises our own data item.
+<screen>
+*****************************
+* INIT
+*  Set up the ramdisk
+
+INIT lda #NumDrvs Set no drives to 1
+ sta V.NDRV,U
+ clr LSNZERO,U    Initialise our data
+ lda #$FF         non-zero value
+ leax DrvBeg,U    X=Start of drive table
+initdrv
+ sta DD.Tot,X     write in non-zero values
+ sta V.Trak,X
+ leax DrvMem,x
+ deca             loop through drives
+ bne initdrv
+* set up pia
+ ldx V.PORT,U
+ lda #pia.off
+ sta pia.cnra,x   select a side off
+ lda #pia.ddr
+ sta pia.cnrb,x   select b side ddr
+ lda #outb
+ sta pia.iorb,x   set ls 4 bits to outputs
+ lda #pia.off
+ sta pia.cnrb,x   select b side io reg
+ clr pia.iorb,x
+ clrb
+INITXIT rts
+</screen>
+</para>
+<para>
+The READ routine is required to transfer an entire disk sector to a buffer. 
+The 24 bit LSN number to read is held in the B and X registers as follows:
+<screen>
+  bits#
+  23 ... 15 ... 7 ... 0
+  |   B  |      X     |
+</screen>
+</para>
+<para>
+The VALID procedure actually performs the sector validation and loading of 
+this data into the PIAs. The sector is then transfered into the sector buffer 
+pointed to by the PD.BUF location in the path descriptor.
+</para>
+<para>
+The READ routine (surprisingly enough) also has to perform some mandatory 
+operations - in particular when LSN 0 is read. This contains disk ID 
+information and whenever a request is made to read this sector, the driver is 
+required to copy DD.SIZ bytes into the drive table for the required drive:
+<screen>
+*****************************
+* READ
+*  read a sector from disk
+*  Entry: U = Static Storage
+*         Y = Path Descriptor
+*         B = MSB of LSN
+*         X = LSB's of LSN
+*  Exit: 256 byte sector in PD.BUF buffer
+*
+READ clr LSNZERO,U      init LSNZERO flag
+ bsr VALID              validate the LSN supplied
+ bcs READ99             raise error if invalid
+ pshs Y                 preserve pointer to PD
+ ldy PD.BUF,Y           Y=start of sector buffer
+ ldx V.PORT,U           X=PIA port address
+ lda #pia.ddr           program PIA for transfer
+ sta pia.cnra,x
+ clr pia.iora,x
+ lda #pia.act
+ sta pia.cnra,x
+ lda #ext.read
+ sta pia.iorb,x
+ leax pia.iora,x
+ clrb
+READS10 lda ,x          transfer sector from PIA
+ sta ,y+
+ decb
+ bne READS10
+ leax V.Port,u          reset PIA
+ clr pia.iorb,x
+ lda #pia.off
+ sta pia.cnra,x
+ puls Y
+*mandatory RBF code - copy LSN 0
+ tst LSNZERO,U          VALID routine will set this
+ beq READ90             if LSN 0 specified
+ lda PD.DRV,Y           extract drive num
+ ldb #DRVMEM            size of drive table
+ mul
+ leax DRVBEG,U          start of drive tables
+ leax D,X               add on drive offset
+ ldy PD.BUF,Y           copy DD.Siz bytes
+ ldb #DD.SIZ-1          into drive table
+COPLSN0 lda B,Y
+ sta B,X
+ decb
+ bpl COPLSN0
+READ90 clrb
+READ99 rts
+</screen>
+</para>
+<para>
+The sector write routine works in a similar manner: the LSN is supplied in the 
+same format, and this time the sector held in PD.BUF must be transferred to 
+the RAM disk PIAs. No special processing is required for LSN 0.
+<screen>
+*****************************
+* WRITE
+*  Write a sector to disk
+* Entry: U = Static Storage
+*        Y = Path Descriptor
+*        B = MSB of LSN
+*        X = LSB's of LSN
+*        PD.Buf = Sector to write
+*
+WRITE bsr VALID          validate sector
+ bcs WRIT99
+WRITS pshs Y             save PD
+ ldy PD.BUF,Y            Y=sector buffer
+ ldx V.PORT,U            X=port address
+ lda #pia.ddr            program PIA
+ sta pia.cnra,x          for write txfr
+ lda #output
+ sta pia.iora,x
+ lda #pia.act
+ sta pia.cnra,x
+ lda #ext.writ
+ sta pia.iorb,x
+ leax pia.iora,x
+ clrb
+WRITS10 lda ,y+          transfer sector
+ sta ,x                  to PIA
+ lda ,x
+ decb
+ bne WRITS10
+ ldx V.PORT,U            restore PIA
+ clr pia.iorb,x
+ lda #pia.off
+ sta pia.cnra,x
+ puls Y
+ clrb
+WRIT99 rts
+</screen>
+</para>
+<para>
+The VALID subroutine is shown below. This serves to program the PIA with the 
+LSN supplied - note the top part of the LSN held in the B register is 
+discarded for this driver, only the X part is used.
+<screen>
+*****************************
+* VALID
+*  validate a sector
+*  and set up external registers
+*  to reqired page in ram
+* 
+VALID 
+ cmpx #$0000     check for LSN 0
+ bne NOTLSN0     set flag appropriatly
+ inc LSNZERO,U
+NOTLSN0 pshs y
+ ldy V.PORT,U
+ lda #pia.ddr    select direction reg
+ sta pia.cnra,y
+ lda #output     set bits to output
+ sta pia.iora,y
+ lda #pia.act    enable ca2 strobe
+ sta pia.cnra,y
+ lda #ext.msr    select ms sector reg
+ sta pia.iorb,y 
+ tfr x,d
+ sta pia.iora,y  write ms value
+ lda pia.iora,y  do the read (strobe)
+ lda #ext.lsr    select ls sector reg
+ sta pia.iorb,y
+ stb pia.iora,y  write ls value
+ ldb pia.iora,y  do the read (strobe)
+ clr pia.iorb,y  select nothing
+ puls y          note a side still set for output
+ clrb
+ rts
+</screen>
+</para>
+<para>
+On the GETSTA and SETSTA side of things, there are no 'mandatory' getsta codes 
+you need to deal with and two SETSTA codes: SS.RST (restore head to track 0) 
+and SS.WRT (write track). Restore head to track 0 serves no useful purpose on 
+a RAM disc and can just return okay. The SS.WRT call is used during format 
+operations to actually format the track - again for a RAM drive this will not 
+be required and can just return okay. All others return with an error:
+<screen>
+**************************
+* GETSTA
+*  get device status
+*
+GETSTA 
+Unknown comb
+ ldb #E$UnkSVC
+ rts
+
+**************************
+* PUTSTA
+*  Set device Status
+*
+PUTSTA cmpb #SS.Reset
+ beq PUTSTA90
+ cmpb #SS.WTrk
+ bne Unknown
+
+PUTSTA90 clrb
+ rts
+</screen>
+</para>
+<para>
+Finally, the TERM routine simply exits cleanly:
+<screen>
+*****************************
+* TERM
+*  terminate Driver
+*
+TERM clrb
+ rts
+
+ emod
+RAMEND equ *
+</screen>
+</para>
+<para>
+All that remains to cover is the RBF device descriptor, which is actually a 
+lot simpler than the SCF one:
+<screen>
+******************************************
+* RamDisk Discriptor module
+*
+*
+ ifp1
+ use /d0/defs/os9defs
+ endc
+
+ nam R0
+ ttl Drive Discriptor module
+
+ mod DEnd,DNam,DEVIC+OBJCT,REENT+1,DMgr,DDrv
+
+ fcb DIR.+SHARE.+PREAD.+PWRIT.+UPDAT.+EXEC.+PEXEC.
+ fcb $FF IOBlock (unused)
+ fdb $FF34 hardware address
+ fcb DNam-*-1 option byte count
+ fcb $1 Rbf device
+ fcb 0 Drive number
+ fcb 03 6ms Step rate
+ fcb $80 Standard OS9 Winchester drive
+ fcb 0 Single density
+ fdb 4 number of tracks
+ fcb 1 number of sides
+ fcb 1 dont verify any writes
+ fdb 256 sectors per track
+ fdb 256 sectors on track 0, side 0
+ fcb 1 sector interleave factor
+ fcb 1 sector allocation size
+DNam fcs "R0"
+DMgr fcs "RBF"
+DDrv fcs "RDisk"
+ emod
+DEnd equ *
+ end
+</screen>
+</para>
+<para>
+This is the device descriptor named R0 for our RAM drive. It shares similar 
+properties to the SCF one, firstly the attributes byte indicating full access 
+(READ.+WRITE. etc. ) including directory access. There is also the familiar 
+hardware address and at the end the device descriptor name, file manager name 
+and driver name. Most of the descriptor is composed of the drive attributes: 
+drive number, total tracks, sectors per track etc. Since this is a RAM drive 
+all I have done is ensured the total sectors on the media is equal to the 
+amount of RAM I have available - in this case 256K. 
+</para>
+<para>
+Once assembled, you can simply load and access the RAM drive like any other 
+disk on your system:
+<screen>
+load rdisk
+load r0
+chd /r0
+copy /d0/startup /r0/startup #32k
+
+etc.
+</screen>
+</para>
+<para>
+That just about wraps it up. A couple of other points worth noting: it's 
+normally worth either checking out or basing your driver on someone else's as 
+a good starting point, there is also a fair deal of information around 
+particularly from the CoCo side of things. And something I'd always recommend: 
+as you have seen is nearly always easier to debug stuff under normal DragonDOS 
+so I'd suggest at least prototyping your code under this environment to see if 
+at least the logic works how you think it should. That way, when you port it 
+to OS9 all you have to worry about is the quirks of OS9 rather than your own 
+driver logic.
+</para>
+</section>
+</article>