NitrOS-9's Unified I/O System

From NitrOS-9
Jump to: navigation, search

NitrOS-9’s Unified Input/Output System

Chapter 1 mentioned that NitrOS-9 has a unified I/O system, consisting of all modules except those at the kernel level. This chapter discusses the I/O modules in detail.

The VDG interface performs both interface and low-level routines for VDG Color Computer 2 compatible modes and has limited support for high resolution screen allocation.

The CoGrf interface provides the standard code interpretations and interface functions.

The CoWin interface, available in the Multi-View package, contains all the functionality of CoGrf along with additional support features. If you use CoWin, do not include Grflnt.

Both CoWIn and Grflnt use the low-level driver GrfDrv to perform drawing on the bitmap screens.

Term_VDG uses VTIO CoVDG while Term_win and all window descriptors use VTIO/(CoWin/Grflnt)/GrfDrv modules.

The I/O system provides system-wide, hardware-independent I/O services for user programs and OS-9 itself. All I/O system calls are received by the kernel and passed to the I/O manager for processing.

The I/O manager performs some processing, such as the allocation of data structures for the I/O path. Then, it calls the file managers and device drivers to do most of the work. Additional file manager, device driver, and device descriptor modules can be loaded into memory from files and used while the system is running.

The I/O Manager

The I/O manager provides the first level of service of I/O system calls. It routes data on I/O process paths to and from the appropriate file managers and device drivers.

The I/O Manager also maintains two important internal OS-9 data structures: the device table and the path table. Never modify the I/O manager.

When a path is opened, the I/O manager tries to link to a memory module that has the device name given or implied in the pathlist. This module is the device descriptor. It contains the names of the device driver and file manager for the device. The I/O manager saves the names so later system calls can be routed to these modules.

File Managers

NitrOS-9 can have any number of file manager modules. Each of these modules processes the raw data stream to or from a class of device drivers that have similar operational characteristics. It removes as many unique characteristics as possible from I/O operations. Thus, it assures that similar devices conform to the NitrOS-9 standard I/O and file structure.

The file manager also is responsible for mass storage allocation and directory processing, if these are applicable to the class of devices it serves. File managers usually buffer the data stream and issue requests to the kernel for dynamic allocation of buffer memory. They can also monitor and process the data stream, for example, adding linefeed characters after carriage-return characters.

The file managers are re-entrant. The three standard NitrOS-9 file managers are:

  • Random block file manager: The RBF manager supports random-access, block-structured devices such as disk systems and bubble memories. (Chapter 5 discusses the RBF manager in detail.)
  • Sequential Character File Manager: The SCF manager supports single-character-oriented devices, such as CRTs or hardcopy terminals, printers, and modems. (Chapter 6 discusses SCF in detail.)
  • Pipe File Manager (PIPEMAN): The pipe manager supports interprocess communication via pipes.

File Manager Structure

Every file manager must have a branch table in exactly the following format. Routines that are not used by the file manager must branch to an error routine that sets the carry and loads B with an appropriate error code before returning. Routines returning without error must ensure that the carry bit is clear.

* All routines are entered with:
* (Y) = Path Descriptor pointer
* (U) = Caller’s register stack pointer
*
EntryPt equ *
        lbra Create
        lbra Open
        lbra MakDir
        lbra ChgDir
        lbra Delete
        lbra Seek
        lbra Read
        lbra Write
        lbra ReadLn
        lbra WriteLn
        lbra GetStat
        lbra PutStat
        lbra Close

Create, Open

Create and Open handle file creating and opening for devices. Typically, the process involves allocating any required buffers, initializing path descriptor variables, and establishing the path name. If the file manager controls multi-file devices (RBF), directory searching is performed to find or create the specified file.

MakDir

MakDir creates a directory file on multi-file devices. MakDir is neither preceded by a Create nor followed by a Close. File managers that are incapable of supporting directories need to return carry set with an appropriate error code in Register B.

ChgDir

On multi-file devices, ChgDir searches for a directory file. If ChgDir finds the directory, it saves the address of the directory (up to four bytes) in the caller’s process descriptor. The descriptor is located at P$DIO + 2 (for a data directory) or P$DIO + 8 (for an execution directory).

In the case of the RBF manager, the address of the directory’s file descriptor is saved. Open/Create begins searching in the current directory when the caller’s pathlist does not begin with a slash. File managers that do not support directories should return the carry set and an appropriate error code in Register B.

Delete

Multi-file device managers handle file delete requests by initiating a directory search that is similar to Open. Once a device manager finds the file, it removes the file from the directory.

Any media in use by the file are returned to unused status. In the case of the RBF manager, space is returned for system use and is marked as available in the free cluster bitmap on the disk. File managers that do not support multi-file devices return an error.

Seek

File managers that support random access devices use Seek to position file pointers of an already open path to the byte specified. Typically, the positioning is a logical movement. No error is produced at the time of the seek if the position is beyond the current “end of file.”

Normally, file managers that do not support random access ignore Seek, However, an SCF-type manager can use Seek to perform cursor positioning.

Read

Read returns the number of bytes requested to the user’s data buffer. Make sure Read returns an EOF error if there is no data available. Read must be capable of copying pure binary data, and generally performs no editing on the data. Generally, the file manager calls the device driver to actually read the data into the buffer. Then, the file manager copies the data from the buffer into the user’s data area to keep file managers device independent.

Write

The Write request, like Read, must be capable of recording pure binary data without alteration. The routines for Read and Write are almost identical with the exception that Write uses the device driver’s output routine instead of the input routine. The RBF manager and similar random access devices that use fixed length records (sectors) must often pre-read a sector before writing it, unless they are writing the entire sector. In OS-9, writing past the end of file on a device expands the file with new data.

ReadLn

ReadLn differs from Read in two respects. First, ReadLn terminates when the first end-of-line (carriage return) is encountered. ReadLn performs any input editing that is appropriate for the device. In the case of SCF, editing involves handling functions such as backspace, line deletion, and the removal of the high order bit from characters.

WriteLn

WriteLn is the counterpart of ReadLn. It calls the device driver to transfer data up to and including the first (if any) carriage return encountered. Appropriate output editing can also be performed. For example, SCF outputs a line feed, a carriage return character, and nulls (if appropriate for the device). It also pauses at the end of a screen page.

GetStat, PutStat

The GetStat (get status) and PutStat (put status) system calls are wildcard calls designed to provide a method of accessing features of a device (or file manager) that are not generally device independent. The file manager can perform specific functions such as setting the size of a file to a given value. Pass unknown status calls to the driver to provide further means of device independence. For example, a PutStat call to format a disk track might behave differently on different types of disk controllers.

Close

Close is responsible for ensuring that any output to a device is completed. (If necessary, Close writes out the last buffer.) It releases any buffer space allocated in an Open or Create. Close does not execute the device driver’s terminate routine, but can do specific end-of-file processing if you want it to, such as writing end-of-file records on disks, or form feeds on printers.

Interfacing with Device Drivers

Strictly speaking, device drivers must conform to the general format presented in this manual. The I/O Manager is slightly different because it only uses the Init and Terminate entry points.

Other entry points need only be compatible with the file manager for which the driver is written. For example, the Read entry point of an SCF driver is expected to return one byte from the device. The Read entry point of an RBF driver, on the other hand, expects Read to return an entire sector.

The following code is part of an SCF file manager. The code shows how a file manager might call a driver.

********************
* IOEXEC
* Execute Device's Read/Write Routine
*
* Passed: (A) = Output character (write)
* (X) = Device Table entry ptr
* (Y) = Path Descriptor pointer
* (U) = Offset of routine (D$Read,D$Write)
*
* Returns: (A) = Input char (read)
* (B) = Error code, CC set if error
*
* Destroys B,CC
IOEXEC pshs a,x,y,u  save registers
       ldu  V$STAT,x get static storage for driver
       ldx  V$DRIV,x get driver module address
       ldd  M$EXEC,x and offset of execution entries
       addd 5,s      offset by read/write
       leax d,x      absolute entry address
       lda  ,s+      restore char (for write)
       jsr  ,x       execute driver read/write
       puls x,y,u,pc return (A)=char, (B)=error
       emod          Module CRC
Size   equ  *        size of sequential file manager

Device Driver Modules

The device driver modules are subroutine packages that perform basic, low-level I/O transfers to or from a specific type of I/O device hardware controller. These modules are re-entrant. So, one copy of the module can concurrently run several devices that use identical I/O controllers.

Device driver modules use a standard module header, in which the module type is specified as code $Ex (device driver). The execution offset address in the module header points to a branch table that has a minimum of six 3-byte entries.

Each entry is typically an LBRA to the corresponding subroutine. The file managers call specific routines in the device driver through this table, passing a pointer to a path descriptor and passing the hardware control register address in the 6809 registers. The branch table looks like this:

Code Meaning
$00 Device initialization routine
$03 Read form device
$06 Write to device
$09 Get device status
$0C Set device status
$0F Device termination routine

(For a complete description of the parameters passed to these subroutines, see the “Device Driver Subroutines” sections in Chapters 5 and 6.)

NitrOS-9 Interaction with Devices

Device drivers often must wait for hardware to complete a task or for a user to enter data. Such a wait situation occurs if an SCF device driver receives a Read but there is no data is available, or if it receives a Write and no buffer space is available. NitrOS-9 drivers that encounter this situation should suspend the current process (via F$Sleep). In this way the driver allows other processes to continue using CPU time.

The most efficient way for a driver to awaken itself and resume processing data is by using interrupt requests (IRQs). It is possible for the driver to sleep for a number of system clock ticks and then check the device or buffer for a ready signal. The drawbacks to this technique are:

  • It requires the system clock to always remain active.
  • It might require a large number of ticks (perhaps 20) for the device to become ready. Such a case leaves you with a dilemma. If you make the program sleep for two ticks, the system wastes CPU time while checking for device ready. If the driver sleeps 20 ticks, it does not have a good response time.

An interrupt system allows the hardware to report to the CPU and the device drivers when the device is finished with an operation. Using interrupts to its advantage, a device driver can setup interrupt handling to occur when a character is sent or received or when a disk operation is complete. There is a built-in polling facility for pausing and awakening processes. Here is a technique for handling interrupts in a device driver:

  1. Use the Init routine to place the driver interrupt service call (IRQSVC) routine in the IRQ polling sequence via an F$IRQ system call:
        ldd   V.Port,u       get address to poll
        leax  IRQPOLL,pcr    point to IRQ packet
        leay  IRQSERVC,pcr   point to IRQ routine
        os9   F$IRQ add dev  to poll Sequence
        bcs   Error          abnormal exit if error
  1. Ensure that driver programs waiting for their hardware call the sleep routine. The sleep routine copies V.Busy to V.Wake. Then, it goes to sleep for some period of time.
  2. When the driver program wakes up, have it check to see whether it was awakened by an interrupt or by a signal sent from some other process.

Usually, the driver performs this check by reading the V.Wake storage byte. The V.Busy byte is maintained by the file manager to be used as the process ID of the process using the driver. When V.Busy is copied into V.Wake, then V.Wake becomes a flag byte and an information byte. A non-zero Wake byte indicates that there is a process awaiting an interrupt. The value in the Wake byte indicates the process to be awakened by sending a wakeup signal as shown in the following code:

        lda   V.Busy,u    get proc ID
        sta   V.Wake,u    arrange for wakeup
        andcc #^IntMasks  prep for interrupts
Sleep50 ldx   #0          or any other tick time (if signal test )
        OS9   F$Sleep     await an IRQ
        ldx   D.Proc      get proc desc ptr if signal test
        ldb   P$Signal,x  i5 signal present? (if signal test)
        bne   SigTest     bra if 50 if Signal test
        tst   V.Wake,u    IRQ occur?
        bne   Sleep50     bra if not

Note that the code labeled “if signal test” is only necessary if the driver wishes to return to the caller if a signal is sent without waiting for the device to finish. Also note that IRQs and FIRQs must be masked between the time a command is given to the device and the moving of V.Busy and V.Wake. If they are not masked, it is possible for the device IRQ to occur and the IRQSERVC routine to become confused as to whether it is sending a wakeup signal or not.

  1. When the device issues an interrupt, NitrOS-9 calls the routine at the address given in F$IRQ with the interrupts masked. Make the routine as short as possible, and have it return with an RTS instruction. IRQSERVC can verify that an interrupt has occurred for the device. It needs to clear the interrupt to retrieve any data in the device. Then the V.Wake byte communicates with the main driver module. If V.Wake is non-zero, clear it to indicate a true device interrupt and use its contents as the process ID for an F$Send system call. The F$Send call sends a wakeup signal to the process. Here is an example:
         ldx   V.Port,u   get device address
         tst   ??         is it real interrupt from device?
         bne   IRQSVC90   bra to error if not
         lda   Data,x     get data from device
         sta   0,y
         lda   V.Wake,u
         beq   IRQSVC80   bra if none
         clr   V.Wake,u   clear it as flag to main routine
         ldb   #S$Wake,u  get wakeup signal
         os9   F$Send     Send Signal to driver
IRQSVC80 clrb             clear carry bit (all is well)
         rts

IROSVC90 comb             Set carry bit (is an IRQ call)
         rts

Suspend State (NitrOS-9 Level 2 only)

The Suspend State allows the elimination of the F$Send system call during interrupt handling. Because the process is already in the active queue, it need not be moved from one queue to another. The device driver IRQSERVC routine can now wake up the suspended main driver by clearing the process status byte suspend bit in the process state. Following are sample routines for the Sleep and IRQSERVC calls:

        lda   D.Proc     get process ptr
        sta   V.Wake,u   prep for re-awakening
enable device to IRQ,    give command, etc.
        bra   Cmd5O      enter suspend loop

Cmd30   ldx   D.Proc     get ptr to process desc
        lda   P$State,x  get state flag
        ora   Suspend    put proc in suspend state
        sta   P$State,x  save it in proc desc
        andcc #^IntMasks unmask interrupts
        ldx   #1         give up time slice
        OS9   F$Sleep    suspend (in active queue)
Cmd50   orcc  #IntMasks  mask interrupts while changing state
        ldx   D.Proc     get proc desc addr (if signal test)
        lda   P$Signal,x get signal (if signal test)
        beq   SigProc    bra if signal to be handled
        lda   V.Wake,u   true interrupt?
        bne   Cmd30      bra if not
        andcc #^IntMasks assure interrupts unmasked

Note that D.Proc is a pointer to the process descriptor of the current process. Process descriptors are always allocated on 256 byte page boundaries. Thus, having the high order byte of the address is adequate to locate the descriptor. D.Proc is put in V.Wake as a dual value. In one instance, it is a flag byte indicating that a process is indeed suspended. In the other instance, it is a pointer to the process descriptor which enables the IRQSERVC routine to clear the suspend bit. It is necessary to have the interrupts masked from the time the device is enabled until the suspend bit has been set. Making the interrupts ensure that the IRQSERVC routine does not think it has cleared the suspend bit before it is even set. If this happens, when the bit is set the process might go into permanent suspension. The IRQSERVC routine sample follows:

         ldy  V.Port,u  get dev addr
         tst  V.Wake,u  is process awaiting IRQ?
         beq  IRQSVCER  no exit
 clear device interrupt
 exit if IRQ not from this device
         lda  V.Wake,u  get process ptr
         clrb
         stb  V.Wake,u  clear proc waiting flag
         tfr  d,x       get process descriptor ptr
         lda  P$State,x get state flag
         anda #Suspend  clear suspend state
         sta  P$State,x save it
         clrb           clear carry bit
         rts

IRQSVCER comb           Set carry bit
         rts

Device Descriptor Modules

Device descriptor modules are small, non-executable modules. Each one provides information that associates a specific I/O device with its logical name, hardware controller address(es), device driver, file manager name, and initialization parameters.

Unlike the device drivers and file managers, which operate on classes of devices, each device descriptor tailors its functions to a specific device. Each device must have a device descriptor.

Device descriptor modules use a standard module header, in which the module type is specified as code $Fx (device descriptor). The name of the module is the name by which the system and user know the device (the device name given in path lists).

The rest of the device descriptor header consists of the information in the following chart:

Relative Address(es) Use
$09,$0A The relative address of the file manager name string address
$0B,$0C The relative address of the device driver name string
$0D Mode/Capabilities: D S PE PW PR E W R (directory, single user, public execute, public write, public read, execute, write, read)
$0E,$0F,$10 The absolute physical (24-bit) address of the device controller
$11 The number of bytes (n bytes) in the initialization table
$12,$12 + n Initialization table

When OS-9 opens a path to the device, the system copies the initialization table into the option section (PD.OPT) of the path descriptor. (See “Path Descriptors” in this chapter.)

The values in this table can be used to define the operating parameters that are alterable by the Get Status and Set Status system calls (I$GetStt and I$SetStt). For example, parameters that are used when initializing terminals define which control characters are to be used for functions such as backspace and delete.

The initialization table can be a maximum of 32 bytes long. If the table is fewer than 32 bytes long, OS-9 sets the remaining values in the path descriptor to 0.

You might wish to add devices to your system. If a similar device driver already exists, all you need to do is add the new hardware and load another device descriptor. Device descriptors can be in the boot module or they can be loaded into RAM from mass-storage files while the system is running.

The following diagram illustrates the device descriptor format:

Device Descriptor Format

Name Relative Address Bytes Use
M$ID $00-$01 2 Sync Bytes ($87CD)
M$Size $02-$03 2 Module Size (bytes)
M$Name $04-$05 2 Offset to Module Name
M$Type $06 1 Type / Language
M$Revs $07 1 Attributes / Revision Level
M$Parity $08 1 Header Parity Check
M$FMgr $09-$0A 2 File Manager Name Offset
M$PDev $0B-$0C 2 Device Driver Name Offset
M$Mode $0D 1 Mode
M$Port $0E-$10 3 Port Address
M$Opt $11 1 Initialization Table Size
  $12,$12 n n Initialization table
      Name Strings, and so on
      CRC Check Value

Path Descriptors

Every open path is represented by a data structure called a path descriptor (PD). The PD contains the information the file managers and device drivers require to perform I/O functions.

PDs are 64 bytes long and are dynamically allocated and deallocated by the I/O manager as paths are opened and closed.

They are internal data structures that are not normally referenced from user or applications programs. The description of PDs is presented here mainly for those programmers who need to write custom file managers, device drivers, or other extensions to OS-9.

PDs have three sections. The first section, which is ten bytes long, is the same for all file managers and device drivers. The information in the first section is shown in the following chart.

Path Descriptor: Standard Information

Name Address Bytes Use
PD.PD $00 1 Path number
PD.MOD $01 1 Access mode: 1 = read, 2 = write, 3 = update
PD.CNT $02 1 Number of open paths using this PD
PD.DEV $03 2 Address of the associated device table entry
PD.CPR $05 1 Current process ID
PD.RGS $06 2 Address of the caller’s register stack
PD.BUF $08 2 Address of the 256-byte data buffer (if used)
PD.FST $0A 22 Defined by the file manager
PD.OPT $20 32 Reserved for the GetStat/SetStat options

PD.FST is 22-byte storage reserved for and defined by each type of file manager for file pointers, permanent variables, and so on.

PD.OPT is a 32-byte option area used for file or device operating parameters that are dynamically alterable. When the path is opened, the I/O manager initializes these variables by copying the initialization table that is in the device descriptor module. User programs can change the values later, using the Get Status and Set Status system calls.

PD.FST and PD.OPT are defined for the file manager in the assembly-language equate file (SCFDefs for the SCF manager or RBFDefs for the RBF manager).