PDDREF:OS/2 Physical Device Driver Design Issues

For the best use of the OS/2 operating system, design the physical device driver to conform to these standard system performance guidelines:
 * Physical device drivers written for DOS are synchronous and poll their devices. Because DOS is a single-tasking operating system, a device driver can hold the CPU until I/O is complete. In the OS/2 multitasking environment, however, physical device drivers must surrender the CPU while they wait for I/O completion. Consequently, OS/2 physical device drivers must be interrupt driven.
 * When access to the structures or buffers must take place at both task time and interrupt time, locate critical data structures or data transfer areas in the physical device driver's data segment. For further details, see Building a Physical Device Driver.
 * Design the physical device driver strategy routine to provide checks to yield the CPU about every 3 milliseconds (ms). If the physical device driver strategy routine does not check to yield the CPU and it executes for periods longer than 3 milliseconds, it could delay dispatching of a process that is ready to run.
 * As a guideline, design both the physical device driver strategy routine and the hardware interrupt handler to run with processor interrupts enabled as much as possible. Plan to have the interrupt handler issue the End-Of-Interrupt (EOI) as soon as critical processing is performed. If the physical device driver does processing after sending an EOI, it can receive nested interrupts. To consume as little stack space as possible, the number of nested interrupts must be limited.
 * The time the physical device driver runs with interrupts disabled affects system performance.

Resource Management
The various types of resource management for physical device drivers are as follows:
 * Request packet queue management
 * Memory management
 * Semaphore management
 * Character queue management

Request Packet Queue Management
The strategy routine can either queue a request packet or process it immediately. Typically, only READ and WRITE requests need to be queued because the device is busy. Other types of requests can usually be handled immediately by the strategy routine.

A block device driver, such as the physical disk device driver, can process queued requests in any order. For instance, the block device driver can choose to sort the requests to optimize device access time. A character device driver must always handle queued requests in the order it received them; otherwise, mixed output could result.

The request packet queue is a linked list that contains a linkage field which allows the packets to be chained together. The device driver can manage its work queue of request packets with the DevHlp services for request queue management.

To use the request queue management DevHlp services, the physical device driver must allocate a DWORD variable as a queue header, with one header for each queue. The DWORD variable must be initialized to zero to indicate an empty linked list or the end of the linked list.

The DevHlp services use the queue header to identify a specific linked list of request packets and set the header to the first request packet in the list. The linkage field in the request packet then chains the request packet to another request packet.

Memory Management
The physical device driver must manage addressability to data across task-time and interrupt-time operations. DevHlp services are provided to allow the physical device driver to be independent of the CPU mode, whether at task time or interrupt time. Addressability is particularly critical at interrupt time because the context of the current process might not cover the address space containing the data buffer that the hardware interrupt handler needs to access in order to move data.

To prevent an application from passing an unauthorized address, the physical device driver can use the DevHlp_VerifyAccess to validate the application's authority to access the memory. Because physical device drivers execute at the operating system privilege level (Ring 0), they have access rights to segments at all privilege levels. However, a well-balanced device driver must not allow an application to force it into accessing segments the application does not own. This check applies to addresses an application passes within a generic IOCtl request; the OS/2 kernel validates addresses for READ and WRITE requests. If an application passes a bad address to the physical device driver, the physical device driver could cause a system halt if it does not verify the caller's access authority. Once an address has been verified, the physical device driver can proceed with the I/O request.

The DevHlp services Lock and Unlock are used to fix a segment in place, which prevents the segment from being removed or swapped while the physical device driver needs access to it. The physical device driver does not need to lock segments for the READ or WRITE request packets. However, segments referenced in the generic IOCtl request packet need to be locked by the physical device driver, if it intends to access those segments at interrupt time.

Once a segment has been locked, the physical device driver can convert the virtual address into a physical address with the DevHlp_VirtToPhys, for later use at interrupt time.

The DevHlps AllocPhys and FreePhys allow the physical device driver to allocate and free a fixed amount of memory. The physical device driver must use the DevHlp_PhysToVirt, DevHlp_PhysToGDTSelector, or DevHlp_PageListToLin to obtain a virtual address to access the memory.

Semaphore Management
There are two kinds of semaphores, RAM and system. These semaphores are characterized in the following table: RAM semaphores are defined when the semaphore user allocates a DWORD variable and uses the address in place of the handle in DevHlp semaphore services. The OS/2 operating system provides no resource management (such as releasing the semaphore when the owner terminates) on RAM semaphores.

System semaphores are created by an application through a dynamic link function call. The OS/2 operating system provides full resource management on system semaphores, including releasing the semaphore and notification when the owner of the semaphore terminates.

System semaphores are typically used by a physical device driver to communicate with an application process. A physical device driver cannot create a system semaphore, although it can use the system semaphore that the application process has created. The application process must pass the application's handle to the physical device driver in a generic IOCtl. The physical device driver then uses the DevHlp_SemHandle to obtain a semaphore handle that it can use. The physical device driver must indicate in the SemHandle call that the system semaphore is IN-USE by the physical device driver. When the physical device driver no longer needs to use the system semaphore to communicate with the application, it must call the DevHlp_SemHandle and specify that the system semaphore is NOT-IN-USE.

Character Queue Management
Character queues are used by character device drivers to buffer data. The two most frequently used structures for character buffers are the FIFO and the circular buffer.

A character device driver can use the DevHlp services to manage a simple circular buffer for characters. The DevHlp services operate on the following character queue header:  CharQueue STRUC Qsize  DW      ? ; Size of buffer in bytes Qchrout DW     ? ; Index to next char out Qcount DW      ? ; Count of characters in buffer Qbase  DB      ? ; Start of buffer CharQueue ENDS  Prior to using the character queue DevHlp services, a physical device driver must allocate the queue header and initialize the Qsize field. The DevHlp_QueueInit must be called before any of the other character queue DevHlps can be called. The other fields in the queue header are managed by the character queue DevHlps and do not need to be examined or altered by the physical device driver.

A character device driver is not required to use the character queue DevHlp services. A character device driver can define its own character buffer management tailored to the requirements of its buffer structure.

Requesting OS/2 Services
Because many of the functions of an OS/2 device driver are related to system operations, OS/2 services are available through the DevHlp functions. Access to OS/2 services is obtained at initialization.

Some OS/2 services are limited by the context in which the physical device driver is running. See Device Helper (DevHlp) Services for a list of the DevHlp services, descriptions, and limitations.


 * Note: In general, DevHlp services might briefly disable processor interrupts to prevent interruptions, but preserve the entry state of the interrupt flag upon exit from the service.

Limiting the Number of Nested Interrupts
Physical device driver interrupt handlers must be written to prevent uncontrolled stack growth of the interrupt stack. The interrupt handler must consume as little stack space as possible and limit the depth of nested interrupts.

The system's interrupt fixed-sized stack is configurable; that is, device drivers can indicate their interrupt stack usage by calling the RegisterStackUsage device helper routine during initialization. This allows the physical device drivers that require large stack space to be installed without decreasing available low memory for all device drivers. When calling RegisterStackUsage, a physical device driver provides data describing the interrupt handler for a specified hardware interrupt level (IRQ). Therefore, RegisterStackUsage is called by a physical device driver for each IRQ that it services. In addition to the interrupt stack size, this data includes the maximum number of interrupt levels that the physical device driver expects to nest. The OS/2 operating system uses this data to detect when unbounded nesting occurs and prevents nested interrupts from causing a system halt.

Physical device drivers that do processing after sending an End-Of-Interrupt (EOI) to the interrupt controller (8259) and enabling interrupts (STI) can receive nested interrupts. To prevent using excessive interrupt stack space, the physical device driver should keep internal flags to limit the amount of interrupt nesting. The amount of interrupt nesting is limited by performing all post-EOI processing at the first-level interrupt. Nested interrupts must avoid and, if possible, eliminate post-EOI processing. In all cases, the physical device driver must bound the number of levels of nesting to as few as possible (preferably two) and must never permit the levels of nesting to be unbounded. Limiting the amount of interrupt nesting to two levels can be implemented as follows:
 * When a nested interrupt is encountered, a flag (or count) is set so that the post-EOI processing can be done again by the first level interrupt, if necessary.
 * On a first level interrupt, interrupts are enabled before doing the EOI and remain enabled during the post-EOI processing. Interrupts must be disabled and remain disabled before the interrupt handler clears the interrupt-in-progress flag and returns to the Interrupt Manager.
 * On a nested interrupt, interrupts must be disabled and remain disabled before the interrupt handler calls the DevHlp_EOI and returns to the Interrupt Manager.
 * Note: If a physical device driver needs to support more than one level of nested interrupts, it still must limit the number of nested interrupts that it handles. A physical device driver must avoid this if at all possible.

As device drivers allow more levels of nesting for their interrupt handlers, the potential exists for the entire interrupt stack to be consumed. This applies to all device drivers, regardless of the interrupt rate of the device being supported. It might seem that a device driver for a slow device need not follow this convention. However, if the system contains another device that has a high interrupt rate or many devices with more moderate interrupt rates, the time between occurrence of the hardware interrupt and dispatch to the interrupt handler can become greater than the interrupt rate of the slow device, and excessive nesting can occur.

Device Monitor Support in Character Device Drivers
The OS/2 operating system provides a mechanism for applications to directly access and intercept data flowing through data streams belonging to some character device drivers. This mechanism allows applications to filter (remove, insert, or modify) data passing through a character device by registering one or more character device monitors with the physical device driver. The mechanism requires support by the character device driver as well as by the application.

A character device monitor is an application or part of an application that uses OS/2 dynamic link function calls to interact with: The following figure shows the Input Device Monitor (for example, the Mouse Monitor).  ┌───────┐      ┌───────┐                  │Device │       │Device │ │Monitor│      │Monitor│ │      │       │       │ ┌──────┐         └─▲───┬─┘       └─▲───┬─┘      ┌───────────┐ │      │           │   │           │   │        │           │ │      │           │   └───────────┘   │        │Application│ │Device│        ┌─┼───────────────────▼─┐      │  Reading  │ │     │Interrupt│ │     Character     │ │ Read │   from    │ │     ├────────►├─┘      Device       └─┼─────►│  Device   │ │     │         │        Driver         │      │           │ └──────┘        └───────────────────────┘      └───────────┘  The following figure shows the Input Device Monitor (for example, the Parallel Port Monitor).  ┌───────┐      ┌───────┐                     │Device │       │Device │ │Monitor│      │Monitor│ │      │       │       │ ┌───────────┐        └─▲───┬─┘       └─▲───┬─┘      ┌──────┐ │           │          │   │           │   │        │      │ │Application│          │   └───────────┘   │        │      │ │ Writing  │        ┌─┼───────────────────▼─┐      │Device│ │   to     │ Write  │ │     Character     │ │      │      │ │ Device   ├───────►├─┘      Device       └─┼─────►│      │ │          │        │        Driver         │      │      │ └───────────┘       └───────────────────────┘      └──────┘  Character device monitors intercepting data from the same data stream are linked together in a monitor chain. The first character device monitor in a monitor chain receives data directly from the physical device driver. This monitor filters the data and passes it on to the next character device monitor in the chain. This monitor then filters the filtered data and passes it on to the next character device monitor in the chain, and so forth. The last character device monitor in the chain passes the filtered data back to the physical device driver.
 * A physical device driver to gain access to its data streams by calling DosMonOpen, DosMonReg, and DosMonClose.
 * A data stream to intercept data passing through the device by calling DosMonRead and DosMonWrite.

The following figure shows the Device Monitor Chains.  ┌───────┐   ┌───────┐    ┌───────┐            │Device │    │Device │    │Device │ │Monitor│   │Monitor│    │Monitor│ │      │    │       │    │       │            └─▲───┬─┘    └─▲───┬─┘    └─▲───┬─┘              │   │        │   │        │   │              │   └────────┘   └────────┘   │            ┌─┼─────────────────────────────┼─┐    Int     │ │          Character          │ │  Int ─────────►├─┘          Device            └─┼─────────► Write  │             Driver              │  Read └─────────────────────────────────┘  Character device drivers provide support for character device monitors by using the Monitor Dispatcher Device Helper services to:
 * Create monitor chains for their data streams by calling the MonitorCreate device helper routine
 * Register monitor buffers with their monitor chains on behalf of monitor applications by calling the Register device helper routine
 * Send data to their monitors by calling the MonWrite device helper routine
 * Flush all data from their monitors by calling the MonFlush device helper routine
 * Remove monitor buffers from their monitor chains on behalf of monitor applications by calling the DeRegister device helper routine

Device Support
If device drivers are to be used on various system units, keep in mind that different system units can require different device drivers to run the same device. Notice that any device driver can be written to access hardware directly, even on a machine that has some form of BIOS interface. Writing directly to the hardware, however, makes the device driver device-dependent. Consequently, the physical device driver must be rewritten each time the hardware changes. Writing to a BIOS interface that hides device-dependent features allows a physical device driver to be supported across many devices.


 * Note: Refer to Using Advanced Bios (ABIOS) for information on ABIOS.

Consider how users of the device will make requests to the device. If users are expected to issue DosOpen, DosRead, DosWrite, and DosClose system calls, the physical device driver must support at least the READ and WRITE commands.

If users issue dynamic link calls to a subsystem, and the subsystem in turn issues DosOpen, DosDevIOCtl, and DosClose calls, the device driver must support the generic IOCtl commands.

To let users erase the contents of the physical device driver's I/O buffers, the driver must support the FLUSH INPUT and FLUSH OUTPUT commands. For information on strategy commands, their parameters, and service requirements, see Physical Device Driver Strategy Commands.