DDI: 8 sample driver

samp_config entry point routine

The config(D2) entry point routine is one of the major structural changes introduced in DDI 8. This is used to support the instance independence feature, where each hardware device instance (or ``unit'') is identified separately. Each instance has its own resource manager database key and the instances are dynamic as required to support hotplug functionality. Each configured device has its own resource manager key, each key has multiple parameter/value pairs, and each parameter can have multiple values.

The config( ) entry point is coded as a switch statement with a case for each supported subfunction (CFG_ADD, CFG_SUSPEND, and so forth) The samp driver uses all the subfunctions that are required to support hotplug functionality. Most drivers for true ISA devices would also support a CFG_VERIFY subfunction to verify that the configuration information in the resource manager database is correct. Because this is a RAM driver rather than a true driver for a peripheral device, the CFG_VERIFY coding is not included.

The first code in the samp_config( ) entry point routine associates the samp_instance_t structure that was populated above with the samp_idata object and defining that to be the idata value.

The ASSERT(D3) macro is called many times in the rest of this code. ASSERT( ) statements are called only when the code is compiled with the -DDEBUG flag and are used to verify that the value of a variable or structure member is correct. If the statement evaluates to be true, ASSERT( ) has no effect. If the statement is not true, ASSERT( ) causes the system to halt so you can study the system state with crash or a kernel debugger to determine how the value became skewed. See ``Using crash and the kernel debuggers'' for information about using crash and the kernel debuggers.

Because this code was developed specifically to illustrate the DDI 8 driver interface, it does not have as much debugging code embedded as most production drivers have. See ``Coding the driver for debugging'' for information about other coding practices that are used. Specifically, note that DDI 8 and later drivers can call the call_demon(D3) function to halt the system and call the configured kernel debugger.

This first ASSERT( ) statement calls the getpl(D3) function to verify that the priority level is set to plbase, meaning that no interrupts are blocked. pl(D5) discusses priority levels, which are used with the locking functions that protect critical code. Ensuring that the priority levels are correct before any locks are allocated or set is an important step in driver debugging. The ``Spin locks (DDI)'' article includes guidelines for selecting an appropriate pl level.

At the end of the samp_config( ) code, the default case returns an EOPNOTSUPP errno if the entry point is called for a subfunction that is not coded. Again, it is highly unlikely that this could happen, but the driver should be coded to handle the condition if it does occur.

CFG_ADD subfunction

The CFG_ADD subfunction is called to inform the driver of a new device instance and to initialize resources for that device instance. It calls the kmem_zalloc(D3) function to allocate and zero a memory region and assigning the samp_idata structure to that memory region. The config( ) entry point routine executes in blockable context so kmem_zalloc( ) uses the KM_SLEEP flag so that, if resources are not available for this allocation, the driver will block until they can be allocated. See ``Context of a driver'' for information about context and its ramifications.

The samp_get_config( ) subordinate driver routine is called to get configuration information from the resource manager database. If that routine has a non-zero return, the resource manager entry is invalid, so the driver calls the kmem_free(D3) function to release the memory allocated for the driver's idata structure and fails with the EINVAL error message.

The CFG_ADD subfunction then calls the LOCK_ALLOC(D3) and SV_ALLOC(D3) functions to allocate a spin lock and a synchronization variabale for this idata structure. Subsequent accesses of idata by other entry point routines and subfunctions must be atomic, meaning that one access must be complete before another access starts. These synchronization primitives will ensure that. The spin lock provides non-blocking synchronization; other spin locks could be used instead, such as read/write locks that would differentiate access for reading and writing or trylocks that only set the lock if noone else has it. See ``Spin locks (DDI)'' for information about the varieties of spin locks that are available.

Synchronization variables provide blocking synchronization, similar to that provided by sleep( ) in older UNIX systems. See ``Synchronization variables'' for more information about synchronization variables and a comparison with sleep( ) functionality.

This LOCK_ALLOC( ) call initializes the samp_lkinfo lock that was declared at the beginning of the code. The first argument is the hierarchy(D5), which defines the order in which locks will be required. 1 is the minimum value, meaning that other locks will take a higher priority. The second argument is the pl(D5) priority level, which defines the minimum priority level that will be passed with any attempt to acquire this lock. The ``Spin locks (DDI)'' article contains a discussion of hierarchy values. The KM_SLEEP flag means that the process will block, if necessary, until memory can be allocated for this lock structure.

Once the synchronization primitives are allocated, the idata pointer is passed back so it is available for other subfunctions and entry point routines.

If this driver controlled a device that generated interrupts, it would call the cm_intr_attach(D3) function to attach interrupts in the CFG_ADD subfunction. See ``Interrupt handlers, attaching and registering''.

CFG_SUSPEND subfunction

The CFG_SUSPEND subfunction suspends the device instance. After this executes, all subsequent I/O should be queued and be ready to be removed from the system.

The code begins with an ASSERT( ) statement to verify that the instance is active. It then calls the LOCK(D3) function to set the lock that provides exclusive access to the idata structure and sets the state to SUSPENDED. Before suspending the device instance, the driver must wait for any pending I/O operations to complete, so it calls the SV_WAIT(D3) to do this. The synchronization variable is released by sampl_end_io( ). Even though the same variable is released by the CFG_REMOVE and CFG_RESUME subfunctions, logically it is not waiting for these subfunctions to release the variable. It is, instead, waiting for the I/O operation to complete, and samp_end_io( ) knows about this. After this, the UNLOCK(D3) function is called to release the spin lock on the device instance idata structure. Note that only one UNLOCK(D3) call is required even though there are two LOCK(D3) calls. This is because SV_WAIT( ) contains an implicit UNLOCK( ) call.

CFG_MODIFY subfunction

The CFG_MODIFY subfunction modifies the driver parameters when the device is replaced. It begins with an ASSERT(D3) call to ensure that the device instance is suspended when the subfunction is entered, then calls the samp_get_config( ) subordinate driver routine to query the resource manager database for new configuration parameters for the new device, and returns.

Note that the CFG_MODIFY subfunction is not required for a DDI 8 driver, but if there is no CFG_MODIFY subfunction, the CFG_RESUME subfunction must do everything that is described in the preceeding paragraph: check whether the same resource manager key that was being used when the driver was suspended is in effect or if a new resource manager key is being used and handle either condition appropriately.

CFG_RESUME subfunction

The CFG_RESUME subfunction resumes (reactivates) the device instance after the device has been replaced or restored. It begins with an ASSERT( ) call to ensure that the device instance is suspended when the subfunction is entered, then sets a lock on the idata structure, updates the state to ACTIVE, unlocks the idata structure, and calls the SV_BROADCAST(D3) function to unblock all processes that are blocked on I/O to or from this device.

Only one process will be able to run at a time, so when several processes are unblocked, one will get access to the device and the others will block until that I/O operation is completed.

CFG_REMOVE subfunction

The CFG_REMOVE subfunction removes a device instance from the system. Ideally, the device is suspended before it is removed, but the driver must allow for cases where the device is removed without first being suspended.

The code locks the idata structure and sets the state to REMOVED. It then issues the SV_BROADCAST(D3) function to unblock any processes that might be blocked on this device. If the device is not available, those processes will fail or may have an alternate path for the I/O operation, depending on how they are coded. After this subfunction executes, all current and future I/O requests to this device fail.

After releasing the spin lock and synchronization variable for the instance, the code calls the samp_free_instance( ) subordinate driver function to free all resources that were allocated for the device instance.

© 2005 The SCO Group, Inc. All rights reserved.
OpenServer 6 and UnixWare (SVR5) HDK - June 2005