view docs/articles/os9software.article @ 3051:d0ec54ac8b94

Added help files for sdir and sdrive
author tlindner
date Sun, 25 Jan 2015 12:05:20 -0800
parents 1e04ad1dfdce
children
line wrap: on
line source

<!--
    The author has not been contacted about adding this article to the
    documentation set.
    Original URL: http://www.onastick.clara.net/os9sw.htm
-->
<article>
<articleinfo>
<author><firstname>Jon</firstname><surname>Bird</surname></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 Programming 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>