Mercurial > hg > Members > kono > nitros9-code
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 & 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 & 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 &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 & 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 & 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 & SETSTA procedures (remember, it only exists for an open file so will +not be accessable by the INIT & 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 & 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 & 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. & 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>