| United States-English |
|
|
|
![]() |
Managing and Developing Dynamically Loadable Kernel Modules > Chapter 1 Managing and Developing Dynamically Loadable Kernel ModulesDeveloping Dynamically Loadable Kernel Modules |
|
This section explains the process of writing modules in the DLKM format and provides background information specific to device driver development. It focuses on the writing and installation of loadable device drivers because they constitute the majority of supported DLKM modules for HP-UX 11.0 and later releases. This section is intended primarily for programmers who want to write DLKM modules and/or convert existing static (non-loadable) modules to the DLKM format. It is assumed that the reader has a good understanding of the HP-UX operating system, the C programming language, and the writing and testing of static device drivers. Since kernel modules written to the DLKM specification can either be configured as dynamically loadable or statically linked, device driver developers must write their DLKM modules to accommodate either configuration. Writing modules in the DLKM format requires writing additional module initialization code called wrapper code, which enables the DLKM infrastructure to logically connect and disconnect a loadable module to and from the running kernel. Existing traditional device drivers that are going to be configured into the system as loadable modules must be converted to the DLKM format, and must be re-packaged according to the module packaging architecture introduced in HP-UX 11.0.
This section covers the following topics:
Dynamically Loadable Kernel Modules were introduced in HP-UX 11.0. This feature allowed kernel modules to be loaded or unloaded dynamically without rebooting. However, loading and unloading of modules was allowed only during system run-time. This prevented very low-level kernel functions that are needed at early system boot from being implemented as DLKM modules. With this release the Boot Time Loading feature eliminates this limitation for the Itanium-based platform. The HP-UX boot process can be divided into two parts, the architecture-dependent part and the architecture-independent part. The architecture-dependent part is referred to as the boot phase 1, and the architecture-independent part is referred to as boot phase 2. More precisely, boot phase 1 is the time when the kernel does not use pfdat (virtual memory page management table) based memory allocation system, and boot phase 2 is the time when the kernel begins to use the regular virtual memory allocation system that is used during system run-time When you configure a module to be loaded during boot time, it is necessary to specify the phase in which you want to load the module. A module can specify two kinds of loading phases: “configured loading phase” and “supported loading phase.” The supported loading phase specifies the capability of a module to be loaded in a certain phase of the booting process. The supported loading phase for a module is specified in the module's master file. A module can have more than two supported loading phases defined concurrently. If no phases are specified in the master file, the module is loaded during run-time. The configured loading phase specifies the phase in which the module needs to be loaded. The configured loading phase for a module is specified in its system file and the valid values are as follows:
The module can specify BOOT1 or BOOT2 for its configured loading phase only when it also has BOOT1 or BOOT2 (or both) specified in its supported loading phase. Otherwise the module configuration process will complain about this module.
Modules configured as INIT will attempt to be loaded by the kminit rc script. This is identical to the procedure for DLKMs in the HP-UX 11.0 release, which attempts to load the modules that are registered in the /etc/loadmods file. Modules configured as AUTO are not loaded automatically in any of the phases unless they are referenced as a dependent of another module(s). If they are not referenced during the boot or init phases as a dependent, then they will be registered during boot phase 2. However they will not be explicitly loaded when the system is re-booted. They will remain ready to load on demand (kmadmin -L), or on access, for module types supporting the auto-load mechanism. If a module requires another module in order to work properly, you can specify a dependency between them. The module loader attempts to load all the dependent modules when it finds dependencies during loading of a module. This module dependency mechanism is also applicable to Boot Time Loading. For the system to work properly, the module developer or system administrator needs to exercise care in determining the configured loading phase and the supported loading phase of modules. A module without BOOT1 or BOOT2 specified in its supported loading phase would not be registered during boot phase 1 or boot phase 2. Such modules cannot be loaded in those phases even if they are defined as dependents of other boot-time-loaded modules. A module loading failure in boot time may be critical for your system if the failed module provides essential functionality for your system (i.e., SCSI disk driver). If there is a possibility that the module will be referred to as a dependent during boot time, then the module must include BOOT1 or BOOT2 in the supported loading phase specification. All modules that have BOOT1 in their supported loading phase specification are registered in system boot phase 1. All other modules are registered during boot phase 2, regardless of the values specified in their supported loading phase.
A module having a configured loading phase of BOOT1 will be loaded just after DLKM has initialized in boot phase 1. This is later than initialization steps for spinlocks and the primitive memory allocation method used by boot phase 1, but prior to beginning I/O initialization. A module having BOOT2 as its configured loading phase will be loaded just after the statically linked device drivers have been initialized. Any modules either referenced by an auto-loading stub linked into the static kernel, or depended-upon by other modules loaded during the boot process, are loaded when they are referenced at boot time. They must, however, be configured with the applicable loading phase in their supported loading phase specification. A DLKM module must be modularly packaged (see “Managing Dynamically Loadable Kernel Modules”). A DLKM module is installed on disk as a set of files, which includes the module's object file and other configuration files needed to install the module into the system. The files are located in a single directory having an arbitrary name. The kminstall(1M) command must then be used to copy the module's installation component files into the locations at which the kernel configuration tools expect to find them. The component files for a DLKM module are:
The C program source code for a DLKM module, which is compiled and possibly linked to become mod.o, is similar to the source code for a static module except that it contains the following additional information:
The source code for a DLKM module must contain a wrapper, defined by the modwrapper structure, specifying the _load() and _unload() function entry points and other data structures used by the module. The name of the modwrapper structure for a DLKM module must be module_name_wrapper. The wrapper data structures are partially initialized by the DLKM infrastructure using values taken from the module's configuration files. These structures provide information needed during loading and unloading, such as the values needed to populate a loadable driver's device switch table entries for the major device number it supports. The code definition of the modwrapper structure is defined in header file moddefs.h and shown below. The data structures and #define statement preceding the modwrapper structure are also defined in moddefs.h. The modwrapper structure contains a pointer to the modlink structure, and the modlink structure contains pointers to the mod_operations and mod_type data structures. The #define statement indicates the revision (version) number of the modwrapper structure, which is 1.0 for HP-UX 11.0.
Example 1-1 An Extract from moddefs.h File Showing modwrapper Structure
The elements of the modwrapper structure are:
The elements of the modlink structure are:
The elements of the mod_type_data structure are:
A sample wrapper for a WSIO driver is as follows: Example 1-2 WSIO Driver Wrapper—Example
When a DLKM module is configured as statically linked, its wrapper is not used by the system. int module_name_load (void *arg); A module's _load() function is called by the DLKM infrastructure whenever the module is loaded from disk into active memory. The function may be given any name (typically module_name_load); a pointer to the _load() function is obtained from the module's wrapper. The _load() function must perform any initialization tasks required by the module before the module is logically connected to the kernel. Typical initialization tasks include acquiring private memory for the module and initializing devices and data structures. If the module is unable to initialize itself, the _load() function must free any memory that it allocated and undo any other action that it took prior to the failure including canceling all outstanding calls to timeout. The _load() function should return 0 on success and an errno value on failure. The argument passed to the function is type-specific. The use of this argument is described in “Initializing and Terminating DLKM Modules”. int module_name_unload (void *arg); The _unload() function is called by the DLKM infrastructure whenever the module is about to be removed from active memory. The function may be given any name (typically module_name_unload); a pointer to the _unload() function is obtained from the module's wrapper. The _unload() function must clean up any resources that were allocated to the module, and it must remove all references to the module. Typical cleanup tasks include releasing private memory acquired by the module, removing device interrupts, disabling interrupts from the device, and canceling any outstanding timeout requests made by the module. The module's _unload() function should return 0 on success and an errno value on failure. In the event of failure, the function must leave the module in a sane state, since the module will remain loaded after the return. The system will never attempt to unload a module that it thinks is busy. However, the system cannot determine under all cases when the module is in use. Currently, a module is considered to be busy when another module that depends on it is also loaded. In addition, WSIO class drivers and STREAMS drivers track the open() and close() calls; these types of modules are busy whenever there is at least one open on the device using the driver. Under most other circumstances, the module must determine for itself whether it is appropriate for it to be unloaded. When a module is still in use, its _unload() function can return a nonzero value to cancel the unload. The argument passed to the _unload() function by the DLKM infrastructure is the same type-specific value that was passed to the module's _load() function. The use of this argument is described in “Initializing and Terminating DLKM Modules”. Each DLKM module has its own master file. The format of the master file includes the following section keywords:
For complete details on the use of these keywords, see the "Modular Master File" section of the master(4) manpage. Every DLKM module requires a system file. The system file includes the following mandatory section keyword and four optional section keywords:
For complete details on the use of these keywords, see the "Modular System File" section of the system(4) manpage. An optional component, the space.h file contains storage allocations and initializations of data structures associated with a DLKM module when the size or initial value of the data structures depend on configurable values such as tunable parameters. In order to communicate these values to the rest of the DLKM module, the values are stored in global variables and accessed by the module via extern declarations in the module's mod.o file.
An optional component, the Modstub.o file is statically configured into the kernel as a "place holder" for functions implemented in a loadable module that will be loaded at a later time. Its purpose is to enable the kernel to resolve references to the absent module's functions. Modstub.o contains “stubs” for entry points defined in the associated loadable module that can be referenced by other statically linked kernel modules currently configured in the system. Access to a stub causes the kernel to auto load the associated loadable module. Configuring a module that uses stubs requires a full kernel build so that the stubs can be statically linked to the kernel. In a traditional system with statically linked modules, the modules are initialized during system boot. Because dynamically loadable modules can be loaded either during boot or after the system is booted, they must be initialized differently than modules that are statically linked into the kernel during system boot. For HP-UX 11i Version 1.5 systems, loadable kernel modules can be configured to be loaded during the early stages of system boot. On PA systems, dynamically loadable modules must be loaded at run time. Boot time loading on PA platforms will be available in a future HP-UX release. The rest of this section describes the steps that each type of dynamically loadable module must take to initialize itself and to terminate itself. In addition to WSIO class drivers, WSIO interface drivers, STREAMS drivers, STREAMS modules, and miscellaneous modules, this section includes the initialization and termination tasks for WSIO monolithic drivers. A monolithic driver is both a class driver (one that has a device special file) and an interface driver (one that touches real hardware and registers). To make a WSIO class driver loadable, the driver must provide a _load() and _unload() function and make minor changes to the way that the driver initializes itself. When a driver is statically linked, it typically links its _init() function into the dev_init chain, and the _init() function calls the next driver's _init() function in the chain. This mechanism is inappropriate for loadable drivers since the dev_init chain is only used during system boot, and there is no mechanism to remove the entry from the chain when the module is unloaded. Therefore, the module's _load() function should perform both the installation tasks (those normally done during install for static modules) and the driver initialization tasks, and the dev_init chain should be ignored. Additionally, the _load() function is passed a pointer to an updated version of the drv_info_t structure; the driver must use this version of the drv_info structure when it registers itself with WSIO. A sample _load() function for a WSIO class driver is as follows: Example 1-3 WSIO Class Driver _load Function
Initialization for a statically linked WSIO class driver is unchanged from historical practice. That is, the initial driver entry point is module_name_install. This function typically installs an _init() function in a list of functions that will be invoked later in the system boot process, and then the driver registers itself with WSIO. For example: Example 1-4 WSIO Class Driver _install Function
The _init() function in the static case must call the next driver's _init() function: Example 1-5 WSIO Class Driver _init Function
The _unload() function frees resources allocated by the driver and unregisters the driver from WSIO. If it is safe to unload the driver when the number of open devices for the driver goes to zero, then the module need not perform any special checks to determine if it is busy. If any step in the _unload() process fails, the driver must undo any action prior to the failure. A sample _unload() function for a WSIO class driver is as follows: Example 1-6 WSIO Class Driver _unload Function
A WSIO interface driver requires an _attach() function for any interface card it intends to claim. It will also require a _probe() function if it intends to install device probes. When installing a WSIO interface driver, the driver must register itself with WSIO and set up the _attach() function and any device probes. Because the timing of initialization differs between loadable and statically linked modules, some of the steps that are handled automatically during boot for static drivers must be explicitly performed by the _load() function. The primary difference in making a WSIO interface driver loadable concerns the handling of the attach list. Historically, drivers have added their _attach() functions to a global list, and the _attach() function was responsible for calling the next driver's _attach() function in the list. However, this method does not allow the driver to remove its _attach() function from the list; thus, this approach cannot support unloading. New support functions have been added to WSIO to allow interface drivers to add and remove their _attach() functions from the attach list. These functions are:
list_type specifies the attach list to use; valid entries are MOD_WSIO_CORE, MOD_WSIO_PCI, and MOD_WSIO_EISA. Both functions return 0 on success and 1 on failure. These functions should only be called when the module is dynamically loaded. Statically linked modules should continue to use the existing attach chain. Device probes are normally associated with interface drivers during the initialization of the WSIO Context Dependent Input/Output (CDIO) at boot time. Since this is only done once, loadable interface drivers must explicitly connect the device probe to the driver's drv_info structure: void wsio_activate_probe(char *probe_name, struct drv_info *drv_infop); probe_name is the name of the probe as registered by wsio_register_dev_probe() or wsio_register_probe_func(). drv_infop is a pointer to the drv_info structure for this driver. In general, use a probe provided by the system. For example, parallel_scsi_probe or side_probe. See the HP-UX Driver Development Guide for complete information. Finally, similar to class drivers, the _load() function for interface drivers is passed a pointer to an updated version of the drv_info_t structure; the interface driver must use this version of the drv_info structure when it registers itself with WSIO. The sample code below puts all these concepts together to demonstrate the loading and initialization steps for an interface driver. This sample can be configured as either a loadable module or a statically linked module. Example 1-7 WSIO Interface Driver Loading and Initialization Coding
The _unload() function determines if the driver is still busy, and if not, it cleans up all resources that were obtained by the driver. It should free memory that it allocated and remove the attach() function from the attach list. It should also unregister any _probe() function pointers that the driver has registered with other kernel services; for example, the _unload() function should unlink the interrupt service function. If an operation fails while unloading, the _unload() function must be able to restore the driver to a working state and return a nonzero value. Unlike class drivers, there is no automatic method for the system to determine if an interface driver is still busy. This problem can sometimes be avoided by making class drivers that use the interface driver dependent upon the interface driver. With this dependency relationship in place, the system will not allow the interface driver to be unloaded as long as any class driver that depends upon it is still loaded. If it is not appropriate for the interface driver to rely on dependencies, then it must determine via other means if it is possible to unload the driver. The _unload() function is always free to return a nonzero value, and the module will remain loaded. A sample _unload() function for a WSIO interface driver is as follows: Example 1-8 WSIO Interface Driver _unload Function
The _load() function for a monolithic driver must effectively be the union of the _load() functions for the class and interface drivers described previously. Similarly, the _unload() function must effectively be the union of the _unload() functions for the class and interface drivers. Initialization of STREAMS drivers is very similar for both the loadable and statically linked module cases. The only difference is that loadable drivers must use the drv_info_t structure that is passed as an argument to the _load() function. The major numbers from this structure must also be used in the streams_info_t structure passed to str_install(). Sample _load() and _install() functions for a STREAMS driver are as follows: Example 1-9 STREAMS Driver _load and _install Functions
STREAMS drivers, like WSIO class drivers, automatically track open() and close() system calls for the STREAMS device. The system will prevent a STREAMS driver from unloading whenever the device has one or more open file handles. Of course, the driver can still disallow an unload if this check is insufficient for its needs. A typical _unload() function for a STREAMS driver is as follows: Loadable STREAMS modules have no special requirements during initialization. The argument passed to the _load() function should be ignored. Sample _load() and _install() functions are as follows: Example 1-11 STREAMS Module _load and _install Functions
The system automatically tracks pushes and pops of a STREAMS module on active streams; a module cannot be unloaded while it is pushed onto one or more streams. A typical unload() function for a STREAMS module is as follows: Miscellaneous modules can implement any feature within the kernel. As such, a miscellaneous module's _load() function must address all of the module's specific needs. Similarly, the module's _unload() function must determine for itself if it is safe to unload. The system will not allow a module to be unloaded if other loaded modules are dependent upon the module. Other than this check, the system performs no other checks when the administrator attempts to remove a miscellaneous module from the kernel. The argument to the _load() function is not meaningful and should be ignored. Developing a DLKM module such as a device driver requires more up front planning than most application programming projects. At the very least, testing and debugging are more involved, and more knowledge about hardware is required. This section describes the procedures for developing a DLKM module. Since you can include a DLKM module in the system in one of two ways, either as a Dynamically Configured Loadable Module or as a Statically Configured Loadable Module, this section considers both methods of inclusion. Complete command line invocations are included in the detailed descriptions where appropriate. Developing a DLKM module is an iterative process. You write, test, and debug a module in a piecemeal fashion, building up to the implementation of the complete module. During the first phases of development, possible errors in the module code may panic or damage the system, even parts of the system that may seem unrelated to your module. Testing should be done when no other users are on the system and all production data files are backed up. Ideally, testing would be performed on a restricted use system set up specifically for the purpose of developing kernel modules.
Create the mod.o, master, system, space.h (optional), and Modstub.o (optional) files for the DLKM module in a single directory. It is suggested that you use a subdirectory of /usr/conf such as /usr/conf/module_name. When choosing a name for your module, choose a unique name to avoid conflict with kernel functions and global variables. Consider using your company's name and something that indicates the module's purpose. To compile your module source code, use the ANSI C compiler /opt/ansic/bin/cc and the compiler options shown below. You can also use the
compiler options found in the makefile /stand/build/config.mk or To compile your module source code, follow these steps:
Once the module's mod.o, master, system, and optional files have been created, call the kminstall command to copy those files to certain subdirectories of /usr/conf and /stand. The kminstall command creates the required subdirectories if they do not exist. kminstall expects to find the module's component files in the current working directory. If module_name already exists on the system, kminstall prints a message and fails. To install a DLKM module's components, follow these steps: kminstall copies the module's component files to the appropriate locations. For example, kminstall copies the master file to /usr/conf/master.d/module_name/0.1.0, and the system file to /stand/system.d/module_name/0.1.0. File /usr/conf/master.d/module_name/0.1.0 is known as the module's master configuration file. File /stand/system.d/module_name/0.1.0 is known as the module's system description file.
After a module's component files have been installed using the kminstall command, the module is ready to be configured and registered with the running kernel. Follow the procedures in “DLKM Procedures for Dynamically Configured Loadable Modules” for instructions on how to prepare, configure, register, load, and manage your DLKM as dynamically loadable, on the target system. To configure and test your DLKM as statically linked refer to “DLKM Procedures for Statically Configured Loadable Modules”. Before devices supported by a driver type DLKM module can be accessed by the system, you need to create special files in the /dev directory using the mknod(1M) command. You do not have to create these files every time you build and boot a new kernel--only when you first add the new module. There must be a special file for each device on your system. When you set up the special files for a newly added device driver, you must specify the device type (character or block) and the major and minor device numbers. The kernel recognizes any single device by the major-minor number combination encoded in the device special files. For a dynamically loadable driver, the system assigns a major number to the driver when it first registers with the system. For a statically linked driver, the system assigns a major number to the driver during system boot. The system remembers assigned major numbers from system boot to system boot. To have the system dynamically assign a major number to your driver, follow these steps:
To create device special files for your driver, follow these steps:
Testing a DLKM module consists of testing all of its functions under a variety of operating conditions and for both configurations: dynamically loadable and statically linked. Debugging a module is largely a process of analyzing the code to determine what could have caused a given problem. The most common debugging technique is monitoring the kernel code using the printf() function to print statements included in the module source code so that you know what your module is doing during run-time operation. There are also debuggers such as Q4 to examine the kernel code. See the Q4 description file in the /usr/contrib/doc directory for more detail. You can use a combination of C preprocessor macros, conditional compilation, and control variables to turn printf() messages on or off. The sample template character driver, “dlclass.c”, includes a control variable named dlclass_debug to determine whether or not to generate the messages. All of the messages can be disabled at once by using kmtune -s to change the value of dlclass_debug to 0 before configuring dlclass. Correcting errors in the DLKM module is largely a process of analyzing the code to determine what could have caused a given problem. Some common errors are as follows:
Use the -u option of the kminstall command to update a DLKM module's components in the subdirectories of /usr/conf and /stand. Use this command when you modify one or more of the module's component files. If you modify the module source code, you must first recompile the source code in accordance to “Creating the Module's Component Files”. kminstall expects to find the module's component files in the current directory. If module_name already exists on the system, kminstall updates the module. If module_name does not exist on the system, kminstall prints a warning and adds the module's components to the system. When updating an existing module, kminstall takes the values of the tunable parameters and the $CONFIGURATION and $LOADABLE flags from the module's current system description file and saves them to the module's new system description file. To update a DLKM module's components, follow these steps: kminstall copies the module's component files to the appropriate locations, thereby overwriting the same named files at those locations. Use the -d option of the kminstall command to remove a DLKM module's components from the system. If the specified module is listed in the /etc/loadmods file, kminstall prints a warning message and removes the module entry from /etc/loadmods. If the specified module is currently loaded, kminstall tries to unload the module. If the unload fails, it prints a message and exits with an error; otherwise, kminstall tries to unregister the module. If the unregistration fails, kminstall prints a message and exits with an error. To remove a DLKM module's components, execute the following kminstall command:
kminstall deletes the module's components from the /usr/conf and
If the removed DLKM module is statically linked into the running kernel, you will have to execute config -u to reconfigure the kernel once the module has been removed. Then you will have to shutdown and restart the system for the new configuration to take effect. This section presents a complete skeleton of a DLKM WSIO class driver in HP-UX. The files shown are:
This driver skeleton is a complete working character driver that does not use any hardware. It can be added to any HP-UX 11.0 kernel and executed as an example. The character driver supports a pseudo device. Characters written to the device are passed to the kernel message buffer, so you have to execute the dmesg(1M) command to actually see the output. A read sequence results in the printing of the following hardcoded string compiled into the driver: "Reading from loadable WSIO driver 'dlclass'." The character driver supports three tunable parameters:
To install, configure, and load the character driver, log in as user root and perform the following steps:
You can read a message from dlclass by executing the following command: cat /dev/dlclass. The following message prints on your computer screen: "Reading from loadable WSIO driver 'dlclass'." You can write a message to dlclass by executing a command such as echo Hello Hello > /dev/dlclass. To see the output, execute the following command: dmesg | tail. The system responds as follows: dlclass> OPEN -- write buffer size = 40 But if you write a message more than 40 characters, (cp master
#
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||