A device driver's configure interface cooperates with the cfgmgr framework to handle user-level requests to dynamically configure, unconfigure, query, and reconfigure a device driver. In addition, the driver's configure interface cooperates with the cfgmgr framework to handle static configuration requests. The driver's configure interface also cooperates with the cfgmgr framework to perform one-time initialization tasks such as allocating memory, initializing data structures and variables, adding the driver's I/O services interfaces, and reserving a major number in the dsent (device switch) table. You should implement a driver's configure interface to handle static and dynamic configuration.
The code associated with this interface resides in the configure section of the device driver. To implement a configure interface, you must perform the following tasks:
The following sections describe each of these tasks.
The cfg_subsys_attr_t data structure contains information that device drivers use to describe a variety of attributes. Device driver writers declare and initialize an array of cfg_subsys_attr_t structures in their device drivers. The cfg_subsys_attr_t structure is a simplified version of the cfg_attr_t structure and is designed to save space in the kernel. Driver writers need to be intimately familiar with the members of the cfg_subsys_attr_t structure. The following code shows the C definition:
typedef struct { char name[CFG_ATTR_NAME_SZ]; uchar type; uchar operation; caddr_t addr; ulong min_val; ulong max_val; ulong val_size; } cfg_subsys_attr_t;
The name member specifies the ASCII name of the attribute. The name must be between two and CFG_ATTR_NAME_SZ characters in length, including the terminating null character. Do not begin the ASCII name of the attribute with the Method_ or Device_ characters. The cfgmgr framework reserves certain names that begin with the Method_ and Device_ characters.
The type member specifies the data type associated with the name attribute. You must set the type member to one of the following constants:
Value | Meaning |
CFG_ATTR_STRTYPE | Data type is a null-terminated array of characters. |
CFG_ATTR_INTTYPE | Data type is a 32-bit signed integer. |
CFG_ATTR_UINTTYPE | Data type is a 32-bit unsigned integer. |
CFG_ATTR_LONGTYPE | Data type is a 64-bit signed integer. |
CFG_ATTR_ULONGTYPE | Data type is a 64-bit unsigned integer. |
CFG_ATTR_BINTYPE | Data type is an array of bytes. |
CFG_ATTR_UCHARTYPE | Data type is an 8-bit unsigned character. |
CFG_ATTR_USHORTTYPE | Data type is a 16-bit unsigned short integer. |
The operation member specifies the operations that the cfgmgr framework can perform on the attribute.
You can set this member to one of the following constants: CFG_OP_CONFIGURE, CFG_OP_QUERY, and CFG_OP_RECONFIGURE.
The following table describes the meaning of these constants.
Value | Meaning |
CFG_OP_CONFIGURE |
The
cfgmgr
framework configures the attribute.
This means the
cfgmgr
framework obtains a data value for the attribute from the
/etc/sysconfigtab
database.
The configure operation occurs when the cfgmgr framework calls the driver's configure interface at its CFG_OP_CONFIGURE entry point. (That is, the optype argument of the driver's configure interface evaluates to the CFG_OP_CONFIGURE constant.) |
CFG_OP_QUERY |
The
cfgmgr
framework queries (reads) the attribute.
This means the driver cooperates with the
cfgmgr
framework to provide the value associated with the attribute as a result
of user-initiated query requests.
These requests are typically the result of the
sysconfig -q
command.
The query operation occurs when the cfgmgr framework calls the driver's configure interface at its CFG_OP_QUERY entry point. (That is, the optype argument of the driver's configure interface evaluates to the CFG_OP_QUERY constant.) |
CFG_OP_RECONFIGURE |
The
cfgmgr
framework reconfigures the attribute.
This means the
cfgmgr
framework reconfigures the data value for the attribute.
This functionality allows a user to modify the attribute.
A reconfigure request is typically the result of the
sysconfig -r
command.
The reconfigure operation occurs when the cfgmgr framework calls the driver's configure interface at its CFG_OP_RECONFIGURE entry point. (That is, the optype argument of the driver's configure interface evaluates to the CFG_OP_RECONFIGURE constant.) |
The addr member specifies the address of the data value associated with the attribute. The cfgmgr framework obtains the data value for this attribute from the /etc/sysconfigtab database and stores it at this address. The cfgmgr framework performs this storage operation if the following occurs:
Although the device driver's configure interface can initialize attributes that appear in the array with an operation code of CFG_OP_CONFIGURE, the cfgmgr framework overrides this initialization with the value specified in the /etc/sysconfigtab database.
The min_val member specifies the minimum length of a string data value. If the data type for the attribute is numeric, specifies the minimum range.
The max_val member specifies the maximum length of a string data value. If the data type for the attribute is numeric, specifies the maximum range.
The val_size member specifies the binary data size.
The cfg_attr_t data structure contains information for managing the configuring and unconfiguring of device drivers. The cfgmgr framework passes a pointer to this data structure to the device driver's configure interface. The device driver can parse this structure pointer to check the validity of the values associated with the driver's associated sysconfigtab file fragment and the /etc/sysconfigtab database. Section 13.4 discusses the sysconfigtab file fragment. Driver writers need to be intimately familiar with the members of the cfg_attr_t structure. The following code shows the C definition:
typedef struct cfg_attr { char name[CFG_ATTR_NAME_SZ]; uchar type; uchar operation; uint status; long index; union { struct { caddr_t val; ulong min_size; ulong max_size; void (*disposal)(); ulong val_size; } bin; struct { caddr_t val; ulong min_len; ulong max_len; void (*disposal)(); } str; struct { ulong val; ulong min_val; ulong max_val; } num; } attr; } cfg_attr_t;
The name member specifies the ASCII name of the attribute. The name must be between two and CFG_ATTR_NAME_SZ characters in length, including the terminating null character. Do not begin the ASCII name of the attribute with the Method_ or Device_ characters. The cfgmgr framework reserves certain names that begin with the Method_ and Device_ characters.
The type member specifies the data type associated with the name attribute. You must set the type member to one of the following constants:
Value | Meaning |
CFG_ATTR_STRTYPE | Data type is a null-terminated array of characters. |
CFG_ATTR_INTTYPE | Data type is a 32-bit signed integer. |
CFG_ATTR_UINTTYPE | Data type is a 32-bit unsigned integer. |
CFG_ATTR_LONGTYPE | Data type is a 64-bit signed integer. |
CFG_ATTR_ULONGTYPE | Data type is a 64-bit unsigned integer. |
CFG_ATTR_BINTYPE | Data type is an array of bytes. |
CFG_ATTR_UCHARTYPE | Data type is an 8-bit unsigned character. |
CFG_ATTR_USHORTTYPE | Data type is a 16-bit unsigned short integer. |
The
operation
member specifies
the operations that the
cfgmgr
framework can perform on the attribute.
You can set this member to one of the following constants:
CFG_OP_CONFIGURE,
CFG_OP_QUERY,
and
CFG_OP_RECONFIGURE.
The following table describes the meaning of these constants.
Value | Meaning |
CFG_OP_CONFIGURE |
The
cfgmgr
framework configures the attribute.
This means the
cfgmgr
framework obtains a data value for the attribute from the
/etc/sysconfigtab
database.
The configure operation occurs when the cfgmgr framework calls the driver's configure interface at its CFG_OP_CONFIGURE entry point. (That is, the optype argument of the driver's configure interface evaluates to the CFG_OP_CONFIGURE constant.) |
CFG_OP_QUERY |
The
cfgmgr
framework queries (reads) the attribute.
This means the driver cooperates with the
cfgmgr
framework to provide the value associated with the attribute as a result
of user-initiated query requests.
These requests are typically the result of the
sysconfig -q
command.
The query operation occurs when the cfgmgr framework calls the driver's configure interface at its CFG_OP_QUERY entry point. (That is, the optype argument of the driver's configure interface evaluates to the CFG_OP_QUERY constant.) |
CFG_OP_RECONFIGURE |
The
cfgmgr
framework reconfigures the attribute.
This means the
cfgmgr
framework reconfigures the data value for the attribute.
This functionality allows a user to modify the attribute.
A reconfigure request is typically the result of the
sysconfig -r
command.
The reconfigure operation occurs when the cfgmgr framework calls the driver's configure interface at its CFG_OP_RECONFIGURE entry point. (That is, the optype argument of the driver's configure interface evaluates to the CFG_OP_RECONFIGURE constant.) |
The status member stores the return code from operations (configure, unconfigure, query) that the cfgmgr framework performs. The cfgmgr framework can return one of the following operation codes:
Value | Meaning |
CFG_ATTR_SUCCESS | Successful operation. |
CFG_ATTR_EEXISTS | The attribute you specified in the name member does not exist. |
CFG_ATTR_EOP | The attribute you specified in the name member does not support the operation. |
CFG_ATTR_ESUBSYS | The subsystem failed. |
CFG_ATTR_ESMALL | The value or size of the attribute you specified in the name member is too small. |
CFG_ATTR_ELARGE | The value or size of the attribute you specified in the name member is too large. |
CFG_ATTR_ETYPE | The data type that you specified for the attribute you specified in the name member is invalid or is a mismatch. |
CFG_ATTR_EINDEX | The index associated with the attribute that you specified in the name member is invalid. |
CFG_ATTR_EMEM | The cfgmgr framework could not allocate memory for the specified attribute. |
CFG_ATTR_ENOTNUMBER | The attribute that you specified in the member cannot be converted to a number. |
The index member stores a value that scopes the target for indexed attributes.
The attr member specifies a union of the possible attribute types used for storing values, kernel locations, validation criteria, and disposal interfaces.
The cfgmgr framework uses the appropriate union element according to the attribute type. For example, attributes of type CFG_ATTR_ULONGTYPE use the union element num.
As part of implementing a device driver's configure interface you declare a number of variables and initialize the cfg_subsys_attr_t data structure. The following code shows the declaration of some typical variables and the initialization of the cfg_subsys_attr_t structure for a configure interface, using the /dev/none device driver as an example. Section 6.1 describes the members of the cfg_subsys_attr_t structure.
static int majnum = NO_DEV; [1] static int noneversion = 0; [2] static int none_developer_debug = 0; [3] static unsigned char mcfgname[CFG_ATTR_NAME_SZ] = ; [4] static unsigned char unused[300] = ; [5] static unsigned char cma_dd[120] = ; [6] static unsigned char tc_optiondata[300] = ; [7] cfg_subsys_attr_t none_attributes[] = { [8] {"Module_Config_Name", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, [9] (caddr_t)mcfgname,2,CFG_ATTR_NAME_SZ,0}, {"CMA_Option", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)cma_dd,0,300,0}, {"numunit", CFG_ATTR_INTTYPE, CFG_OP_QUERY | CFG_OP_CONFIGURE, (caddr_t)&NNONE,0,MAX_NNONE,0}, {"TC_Option", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)&tc_optiondata,0,300,0}, {"Module_Path", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)unused,0,300,0}, {"Device_Subdir", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)unused,0,300,0}, {"Device_Block_Subdir", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)unused,0,300,0}, {"Device_Char_Subdir", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)unused,0,300,0}, {"Device_Major_Req", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)unused,0,300,0}, {"Device_Block_Major", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)unused,0,300,0}, {"Device_Block_Minor", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)unused,0,300,0}, {"Device_Block_Files", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)unused,0,300,0}, {"Device_Char_Major", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)unused,0,300,0}, {"Device_Char_Minor", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)unused,0,300,0}, {"Device_Char_Files", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)unused,0,300,0}, {"Device_User", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)unused,0,300,0}, {"Device_Group", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)unused,0,300,0}, {"Device_Mode", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)unused,0,300,0}, {"EISA_Option", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)unused,0,300,0}, {"ISA_Option", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)unused,0,300,0}, {"VBA_Option", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)unused,0,300,0}, {"PCI_Option", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)unused,0,300,0}, [10] {"majnum", CFG_ATTR_INTTYPE, CFG_OP_QUERY, (caddr_t)&majnum,0,512,0}, {"version", CFG_ATTR_INTTYPE, CFG_OP_QUERY, (caddr_t)&noneversion,0,9999999,0}, {"None_Developer_Debug", CFG_ATTR_INTTYPE, CFG_OP_QUERY | CFG_OP_RECONFIGURE | CFG_OP_CONFIGURE, (caddr_t)&none_developer_debug,0,1,0}, [11] {,0,0,0,0,0,0} [12] };
The CFG_OP_RECONFIGURE operation indicates that the cfgmgr framework reconfigures the value during a reconfigure operation. This functionality allows a user to modify the attribute. This means the driver writer passed CFG_OP_RECONFIGURE to the optype argument of the driver's configure interface.
The CFG_OP_CONFIGURE operation indicates that the cfgmgr framework obtains a data value for the attribute from the /etc/sysconfigtab database during a configure operation. This means the driver writer passed CFG_OP_CONFIGURE to the optype argument of the driver's configure interface. [Return to example]
This element is the ASCII name of the attribute, for example, Module_Config_Name, Module_Type, majnum, dsflags, and so forth.
This element is the data type associated with the name attribute, for example, CFG_ATTR_STRTYPE and CFG_ATTR_INTTYPE.
This element is the operation or operations the cfgmgr framework can perform on the attribute, for example, CFG_OP_CONFIGURE (configure) and CFG_OP_QUERY (query), and so forth.
This element is the address of the data value associated with the name attribute. For example, mcfgname is the data value associated with the Module_Config_Name attribute.
If the data type for the attribute is a string, this element is the minimum length of the string value. For example, the minimum length of the mcfgname data value is 2. If the data type for the attribute is numeric, this element is the minimum range of the numeric value. For example, the minimum range of the none_developer_debug data value is zero (0).
If the data type for the attribute is a string, this element is the maximum length of the string value. For example, the maximum length of the mcfgname data value is CFG_ATTR_NAME_SZ. If the data type for the attribute is numeric, this element is the maximum range of the numeric value. For example, the maximum range of the none_developer_debug data value is the value 1.
This element is the binary data size. For example, the binary data size for the mcfgname data value is zero (0).
See Table 14-1 for a description of the Module_Config_Name, Module_Type, Device_Char_Major, CMA_Option. and TC_Option attribute fields.
The None_Developer_Debug attribute (or another attribute similar to it) is used for detailed internal debugging of code by Digital personnel or someone who has a source license.
Note that the None_Developer_Debug attribute's operations are query, reconfigure, and configure. [Return to example]
As shown in Figure 6-1, the cfgmgr framework fills in the attributes designated as CFG_OP_CONFIGURE specified in the none_attributes structure array. As the figure shows, the cfgmgr framework:
The driver writers at EasyDriver Incorporated create a sysconfigtab file fragment for the /dev/none driver. The sysconfigdb utility appends this sysconfigtab file fragment to the /etc/sysconfigtab database. Section 13.4 describes the sysconfigtab file fragment.
To determine if cfgmgr initialized attributes correctly, the /dev/none driver verifies each from the cfg_attr_t structure that cfgmgr passes into the none_configure interface's indata argument. Section 6.6.1 shows how to parse the cfg_attr_t structure to check attributes.
To write portable device drivers across multiple bus architectures, you need to know that the configure_driver and unconfigure_driver interfaces take as an argument the bus name. You define the bus names for the buses you want the driver to operate on as constants that you pass to the previously listed interfaces. The following code shows how the /dev/none driver defines the bus name:
#define DN_BUSNAME1 "DRIVER_WILDNAME [1]
For VMEbus device drivers, you must specify vba as the definition for the bus name constant. For example:
#define DN_BUSNAME1 vba
This is the only way the VMEbus autoconfiguration code determines that the controller structure is associated with the VMEbus. [Return to example]
A device driver's configure interface is called indirectly by the cfgmgr framework. The cfgmgr framework is responsible for calling all single binary modules for registration and integration into the kernel. The cfgmgr framework requires that a single binary module has both an attributes table and a configure interface before it (the single binary module) can be registered as part of the cfgmgr framework and the Digital UNIX kernel. Section 6.3 shows how to set up the attributes table.
The following code shows you how to set up a configure interface, using the /dev/none device driver as an example:
none_configure(op, indata, indatalen, outdata, outdatalen) cfg_op_t op; [1] cfg_attr_t *indata; [2] size_t indatalen; [3] cfg_attr_t *outdata; [4] size_t outdatalen; [5] { int retval; [6] int i; [7] int driver_cfg_state; [8]
#define MAX_DEVICE_CFG_ENTRIES 18 [9]
#define NONE_DEBUG #ifdef NONE_DEBUG cfg_attr_t cfg_buf[MAX_DEVICE_CFG_ENTRIES]; [10] #endif /* NONE_DEBUG */
Thus, for the /dev/none device driver, the cfgmgr framework passes the cfg_attr_t array to the indata argument and the number of cfg_attr_t structures in the array to the indatalen argument of the none_configure interface.
This code is used to verify the driver during development. To save space in the final driver product, it is compiled out. [Return to example]
The configure interface's CFG_OP_CONFIGURE operation performs the tasks associated with cooperating with the cfgmgr framework to complete configure (load) requests of a statically or dynamically configured driver. A configure (load) request for a dynamically configured driver is made as a result of a system manager's use of the sysconfig utility. The configure interface's CFG_OP_CONFIGURE operation performs the following tasks related to configuring (loading) the device driver. Your configure interface will probably perform most of these tasks and, possibly, some additional ones.
The following sections describe each of these tasks.
One task associated with the CFG_OP_CONFIGURE operation is to parse the cfg_attr_t structure array to determine if any of the attributes failed to load. The following code shows you how to parse the cfg_attr_t structure array, using the /dev/none device driver as an example:
switch (op) {
case CFG_OP_CONFIGURE: [1]
bcopy(indata, cfg_buf[0].name, [2] indatalen*(sizeof(cfg_attr_t))); for(i=0; i < indatalen; i++) [3] switch(cfg_buf[i].type){ [4] case CFG_ATTR_STRTYPE: break; default: switch(cfg_buf[i].status){ [5] case CFG_FRAME_SUCCESS: break; default: printf("%s:",cfg_buf[i].name); [6] switch(cfg_buf[i].status){ [7] case CFG_ATTR_EEXISTS: [8] printf("Attribute does not exist\n"); break; case CFG_ATTR_EOP: printf("Attribute does not support operation\n"); break; case CFG_ATTR_ESUBSYS: printf("Subsystem Failure\n"); break; case CFG_ATTR_ESMALL: printf("Attribute size/value too small\n"); break; case CFG_ATTR_ELARGE: printf("Attribute size/value too large\n"); break; case CFG_ATTR_ETYPE: printf("Attribute invalid type\n"); break; case CFG_ATTR_EINDEX: printf("Attribute invalid index\n"); break; case CFG_ATTR_EMEM: printf("Attribute memory allocation error\n"); break; default: printf("**Unknown attribute: "); printf("%x\n", cfg_buf[i].status); } } break; } if(none_config == TRUE) return(EINVAL); [9]
The bcopy interface takes three arguments:
In this call, the array of characters is the cfg_attr_t structure that the cfgmgr framework passes to the indata argument.
In this call, the buffer is the array of cfg_attr_t structures called cfg_buf. Section 6.5 shows the declaration of this array.
In this call, the number of bytes to copy is the result of the sizeof operator (in this case the number of bytes associated with the cfg_attr_t structure).
The bcopy interface copies the cfg_attr_t structure stored in the indata argument to the first element of the cfg_buf buffer array. The /dev/none device driver requests that the cfgmgr framework initialize the none_attributes structure with all of the attributes specified for the none entry in the /etc/sysconfigtab database. [Return to example]
The purpose of these case statements is to:
Another task associated with the CFG_OP_CONFIGURE operation is to check the name of the device driver. The following code shows you how to accomplish this task by calling the strcmp and strcpy interfaces:
if(strcmp(mcfgname,)==0) { [1] strcpy(mcfgname,"none"); [2] } else { [3] /* mcfgname from sysconfigtab is used to configure the device driver in the following calls to the configure_driver interface. */
The name specified in the ctlr_name member will be replaced if the mcfgname variable is not NULL. The value stored in mcfgname supersedes the controller name stored in ctlr_name during configuration of the driver. The mcfgname variable is used as the data value address for the Module_Config_Name attribute. The driver writer at EasyDriver Incorporated sets the Module_Config_Name attribute to the driver name in the sysconfigtab file fragment. Section 13.4 describes the sysconfigtab file fragment. Section 14.1.5 shows the sysconfigtab file fragment that the driver writer at EasyDriver Incorporated creates. Section 6.3 shows the declaration and use of this variable in the none_attributes structure array.
The configure interface performs this check by calling the strcmp interface.
The strcmp takes two arguments:
In this call, the string is the driver name stored in the mcfgname variable.
In this call, the string is the driver name, none.
The strcmp interface lexicographically compares the string stored in mcfgname to the null string. If the strings match, strcmp returns the value (0) to indicate success and the following line is executed.
The strcpy interface takes two arguments:
In this call, the mcfgname variable contains the null string.
In this call, the string is the driver name stored in the mcfgname variable. This sequence of code shows that the driver can force a default value on an attribute.
Another task associated with the CFG_OP_CONFIGURE operation is to determine the configuration state of the driver. A device driver is either in the static or dynamic configuration state. A device driver performs different tasks for the static and dynamic configuration states. One task associated with statically configured drivers is to register driver-implemented interfaces that the cfgmgr framework can call at specific points in time.
The example shows calls to the following interfaces:
The example also shows calls to the following driver-implemented interfaces:
Finally, the example shows the registration of driver-implemented interfaces associated with the statically configured /dev/none driver:
The following code shows you how to determine a driver's configuration state, using the /dev/none driver as an example:
if(cfgmgr_get_state(mcfgname, &driver_cfg_state) != ESUCCESS){ return(EINVAL); [1] } if(driver_cfg_state == SUBSYSTEM_STATICALLY_CONFIGURED) { [2] callback_return_status = ESUCCESS; none_is_dynamic = SUBSYSTEM_STATICALLY_CONFIGURED; register_callback(callback_register_configuration, CFG_PT_PRECONFIG, CFG_ORD_NOMINAL, (long) 0L ); register_callback(callback_register_major_number, CFG_PT_POSTCONFIG, CFG_ORD_NOMINAL, (long) 0L ); } else { [3] none_is_dynamic = SUBSYSTEM_DYNAMICALLY_CONFIGURED; [4] retval = register_configuration(); [5] if(retval != ESUCCESS) return(retval); [6] if((retval = configure_driver(mcfgname, DRIVER_WILDNUM, DN_BUSNAME1, &nonedriver)) != ESUCCESS) { [7] return(retval); } retval = register_major_number(); [8] if(retval != ESUCCESS) [9] return(retval); } break;
The cfgmgr_get_state interface takes two arguments:
In this call, the driver name is stored in the mcfgname variable.
Value | Meaning |
SUBSYSTEM_DYNAMICALLY_CONFIGURED | The specified device driver is in the dynamic configuration state. This means the driver was dynamically configured into the kernel. |
SUBSYSTEM_STATICALLY_CONFIGURED | The specified device driver is in the static configuration state. This means the driver was statically configured into the kernel. |
If cfgmgr_get_state successfully returns a configuration state value to the second argument, the next line of code is executed. Otherwise, none_configure returns EINVAL to the cfgmgr framework that indicates a fatal error in determining the configuration state of the driver. [Return to example]
The register_callback interface takes four arguments:
In the first call, the interface to be called for the statically configured /dev/none driver is callback_register_configuration. Section 6.6.6.2 describes the implementation of callback_register_configuration.
In the second call, the interface to be called for the statically configured /dev/none driver is callback_register_major_number. Section 6.6.6.1 describes the implementation of callback_register_major_number.
Value | Meaning |
CFG_PT_PRECONFIG | The dispatch point is hardware preconfiguration. Driver tasks that do not require completion of hardware configuration can be performed at this dispatch point. |
CFG_PT_POSTCONFIG | The dispatch point is hardware post configuration. Driver tasks that require completion of hardware configuration can be performed at this dispatch point. |
CFG_PT_ROOTFS_AVAIL | The dispatch point is root file system available. Driver tasks that require completion of the root file system mount operation can be performed at this dispatch point. |
In the first call, the dispatch point is represented by the constant CFG_PT_PRECONFIG. In the second call, the dispatch point is represented by the constant CFG_PT_POSTCONFIG.
Value | Meaning |
CFG_ORD_NOMINAL | This callback request (interface) is registered at the lowest priority. Typically, you pass this constant with an appropriate offset. The kernel executes callback requests (interfaces) registered at this priority last. |
CFG_ORD_MAXIMUM | This callback request (interface) is registered at the highest priority. Typically, you pass this constant with an appropriate offset. The kernel executes callback requests (interfaces) registered at this priority first. |
In both calls, the order is represented by the constant CFG_ORD_NOMINAL.
In both calls, the value 0L is passed.
The configure_driver driver interface takes four arguments:
In this call, the driver name is stored in the mcfgname variable.
In this call, the constant DRIVER_WILDNUM is passed.
In this call, the constant DN_BUSNAME1 is passed. Section 6.4 shows that the DN_BUSNAME1 constant is defined as DRIVER_WILDNUM.
In this call, the address of the nonedriver structure is passed.
Another task associated with the CFG_OP_CONFIGURE operation is to register the controller and device information associated with the device driver. The /dev/none accomplishes this task by implementing a driver-specific interface called register_configuration. Implementing an interface similar to register_configuration involves the following:
The following sections discuss these topics.
The controller_config data structure contains the information needed to create a controller structure and to integrate it into the system (hardware) configuration tree. To create a controller structure, you set the members of the controller_config structure to appropriate values and call the create_controller_struct interface. This interface takes as an argument a pointer to the filled-in controller_config structure. The following code lists the C definition:
struct controller_config { long revision; char subsystem_name[NAME_SIZE_REG]; char bus_name[NAME_SIZE_REG]; struct driver * devdriver; };
The revision member specifies the version number of the controller_config structure. You set this member to the constant CTLR_CONFIG_REVISION. Digital defines this constant to the appropriate version number in the /usr/sys/include/io/common/devdriver.h file.
The subsystem_name member specifies the name of the device driver. This name is a string that matches the string you specified for the entry_name item in the /etc/sysconfigtab database. Typically, third-party driver writers specify the driver name (followed by a colon) in the sysconfigtab file fragment, which gets appended to the /etc/sysconfigtab database during the driver product installation.
The bus_name member specifies the name of the bus to which the controller is connected. You can pass any string (maximum thirty characters). You can pass the constant DRIVER_WILDNAME to indicate a wildcard (this bus can be any Digital-supported bus). For Digital-supported buses, the bus name is one of the valid device definition keywords associated with the bus. For example, the keyword tc represents a TURBOchannel bus. See the System Administration guide (specifically, the section that discusses how to build the kernel to add support for a new device) for information on how to obtain the device definition keywords asssociated with Digital-supported devices.
Third-party driver writers who write drivers that operate on non-Digital buses can select a string that might include the vendor and product names. The string could also include version and release numbers. This type of naming scheme reduces the chance of name conflicts with other vendors. Note that VMEbus device drivers must specify the string vba for the bus_name member.
The devdriver member specifies a pointer to the driver's driver structure. You set this member to the name of the filled-in driver structure for this device driver.
The device_config data structure contains the information needed to create a device structure and to integrate it into the system (hardware) configuration tree. To create a device structure, you set the members of the device_config structure to appropriate values and call the create_device_struct interface. This interface takes as an argument a pointer to the filled-in device_config structure. The following code lists the C definition:
struct device_config { long revision; char device_type[NAME_SIZE_REG]; char device_name[NAME_SIZE_REG]; char controller_name[NAME_SIZE_REG]; int phys_unit_num; int logical_unit_number; int controller_num; };
The revision member specifies the version number of the device_config structure. You set this member to the constant DEVICE_CONFIG_REVISION. Digital defines this constant to the appropriate version number in the /usr/sys/include/io/common/devdriver.h file.
The device_type member specifies the device type (for example, disk or tape). You can set this member to the strings disk and tape to represent disk and tape devices. However, you can set this member to any string to represent other device types. The device type can be a maximum of thirty characters.
The device_name member specifies the name associated with the device type. You can set this member to any string (maximum eight characters). For Digital-supported devices, the device name is one of the valid device definition keywords associated with the device. For example, the keywords ra and tz represent SCSI disk and tape devices, respectively. See the System Administration guide (specifically, the section that discusses how to build the kernel to add support for a new device) for information on how to obtain the device definition keywords asssociated with Digital-supported devices.
Third-party driver writers who write drivers that operate on non-Digital devices can select a string that might include the vendor and product names. The string could also include version and release numbers. This type of naming scheme reduces the chance of name conflicts with other vendors. For example, the driver writers at EasyDriver Incorporated might specify edgd for an internally developed disk device.
The controller_name member specifies the name of the controller to which this device (the device associated with this device_config structure) is connected. You can pass any string (maximum thirty characters). For Digital-supported controllers, the controller name is one of the valid controller definition keywords associated with the controller. For example, the keyword hsc represents an HSC controller. See the System Administration guide (specifically, the section that discusses how to build the kernel to add support for a new device) for information on how to obtain the device definition keywords asssociated with Digital-supported devices.
Third-party driver writers who write drivers for non-Digital controllers can select a string that might include the vendor and product names. The string could also include version and release numbers. This type of naming scheme reduces the chance of name conflicts with other vendors. For example, the driver writers at EasyDriver Incorporated might specify edgc for an internally developed controller.
The phys_unit_num member specifies the device physical unit number.
The logical_unit_number member specifies the device logical unit number. The logical unit number for a disk drive is any positive integer, for example, 0 or 1.
The controller_num member specifies the number of the controller to which this device (the device associated with this device_config structure) is connected. Typically, you set this member to a number, for example, 0, 1, 2, and so forth.
To register a device driver's associated controller and device information, you implement a driver-specific interface. The driver-specific interface for the /dev/none driver is called register_configuration. The following code shows the implementation of the register_configuration interface, using the create_controller_struct and create_device_struct interfaces:
int register_configuration() [1] { struct controller_config ctlr_register; [2] struct controller_config * ctlr_register_ptr = &ctlr_register; struct device_config device_register; [3] struct device_config * device_register_ptr = &device_register; int status; [4] ctlr_register_ptr->revision = CTLR_CONFIG_REVISION; [5] strcpy(ctlr_register_ptr->subsystem_name, mcfgname); [6] strcpy(ctlr_register_ptr->bus_name,DN_BUSNAME1); [7] ctlr_register_ptr->devdriver = &nonedriver; [8] status = create_controller_struct(ctlr_register_ptr); [9] if(status != ESUCCESS) return (status); [10] device_register_ptr->revision = DEVICE_CONFIG_REVISION; [11] strcpy(device_register_ptr->device_type,"disk"); [12] strcpy(device_register_ptr->device_name,"nonedev"); [13] strcpy(device_register_ptr->controller_name,"none"); [14] device_register_ptr->controller_num = 0; [15] status = create_device_struct(device_register_ptr); [16] if(status != ESUCCESS) return(status); [17] return(ESUCCESS); [18] }
The create_controller_struct interface takes one argument: a pointer to the controller_config structure associated with a specific controller. You set the members of the controller_config structure to appropriate values and then pass the address of the filled-in structure to the create_controller_struct interface. The create_controller_struct interface creates a controller structure for each instance of a controller that the device driver supports. The interface integrates the controller structure for a specific controller into the list of controller structures that reside in the system configuration tree. [Return to example]
The create_device_struct interface takes one argument: a pointer to the device_config structure associated with a specific device. You set the members of the device_config structure to appropriate values and then pass the address of the filled-in structure to create_device_struct. The create_device_struct interface creates a device structure for each instance of a device that the device driver supports. The interface integrates the device structure for a specific device into the list of device structures that reside in the system configuration tree. [Return to example]
Another task associated with the CFG_OP_CONFIGURE operation is to register the I/O services interfaces and to reserve a major number. The /dev/none driver accomplishes this task by implementing a driver-specific interface called register_major_number. Implementing an interface similar to register_major_number involves the following:
The following sections discuss these topics.
The dsent data structure contains pointers to entry points for a specific driver's I/O services interfaces and other information. The system maintains a table (array) of these dsent structures (referred to as the device switch table) that contains pointers to entry points for the associated driver's I/O services interfaces for each block and character mode device that the system supports. In addition, the table can contain stubs for device driver entry points for block and character mode devices that do not exist or entry points not used by a device driver.
Both block and character device special files can access the same device driver entry. If the device driver does not support access by both block and character device special files, then the driver must perform the appropriate check in its open interface and return an error for the devices it does not support.
The following code lists the C definition:
struct dsent { int (*d_open)(); int (*d_close)(); int (*d_strategy)(); int (*d_read)(); int (*d_write)(); int (*d_ioctl)(); int (*d_dump)(); int (*d_psize)(); int (*d_stop)(); int (*d_reset)(); int (*d_select)(); int (*d_mmap)(); int (*d_segmap)(); struct tty *d_ttys; int d_funnel; int d_bflags; int d_cflags; };
The d_open member specifies a pointer to an entry point for the block and character driver's open interface, which opens a device. The d_close member specifies a pointer to an entry point for the block and character driver's close interface, which closes a device.
The d_strategy member specifies a pointer to an entry point for the block driver's strategy interface, which reads and writes block data.
The d_read member specifies a pointer to an entry point for the character driver's read interface, which reads characters or raw data. The d_write member specifies a pointer to an entry point for the character driver's write interface, which writes characters or raw data.
The d_ioctl member specifies a pointer to an entry point for the block and character driver's ioctl interface, which performs special functions or I/O control.
The d_dump member specifies a pointer to an entry point for a block driver's dump interface, which is used for panic dumps of the system image. The d_psize member specifies a pointer to an entry point for the block driver's psize interface, which returns the size in physical blocks of a device (disk partition).
The d_stop member specifies a pointer to an entry point for the driver's stop interface, which suspends other processing on behalf of the current process. You typically use the d_stop member only for terminal (character) drivers. The d_reset member specifies a pointer to an entry point for the character driver's reset interface, which stops all current work and places the device connected to the controller in a known, quiescent state.
The d_select member specifies a pointer to an entry point for the character driver's select interface, which determines if a call to a read or write interface will block.
The d_mmap member specifies a pointer to an entry point for the character driver's mmap interface, which maps kernel memory to user address space.
The d_segmap member specifies the character driver's segmap entry point.
The d_ttys member specifies a pointer to the character driver's private data.
The d_funnel member specifies block and character device drivers onto a CPU in a multiprocessor configuration. You set this member to one of the following constants:
Value | Meaning |
DEV_FUNNEL |
Specifies that you want to funnel the device driver
because you have not made it SMP safe.
This means that the driver is forced to execute on a single (the
master) CPU.
Even if you funnel your device driver, you must follow the SMP locking conventions when accessing kernel data structures external to the driver. Typically, you use kernel interfaces that Digital supplies to indirectly access kernel data structures outside the driver. |
DEV_FUNNEL_NULL | Specifies that you do not want to funnel the device driver because you have made it SMP safe. This means that the driver can execute on multiple CPUs. You make a device driver SMP safe by using the simple or complex lock mechanism. |
The d_bflags member specifies a block driver's device-related and other flags. You set this member to the bitwise inclusive OR of the device-related and other flags. One example of a device-related flag is B_TAPE. This flag is set in the b_flags member of the buf structure. The B_TAPE flag determines whether to use delayed writes, which are not allowed for tape devices. For all other drivers, this member is set to the value zero (0).
Another flag specifies whether this character driver is an SVR4 DDI/DKI-compliant device driver. You set this member to the B_DDIDKI flag to indicate that this is an SVR4 DDI/DKI-compliant device driver.
The d_cflags member specifies whether this character driver is an SVR4 DDI/DKI-compliant device driver. Set this member to the C_DDIDKI constant to indicate that this is an SVR4 DDI/DKI-compliant device driver.
The /dev/none driver accomplishes the task of registering its I/O services interfaces and reserving a major number by calling a driver-specific interface called register_major_number. The register_major_number interface calls the devsw_add interface, which actually performs these tasks. The devsw_add interface takes the address of a filled in dsent structure as an argument. Section 6.6.5.1 describes the members of the dsent structure. Figure 6-2 shows how the I/O services interfaces associated with the /dev/none driver are added to the dsent table. As the figure shows, the driver writer declares and initializes a structure called none_devsw_entry that is of type dsent. Section 5.2 shows the declaration of such a structure, using the /dev/none driver as an example.
The figure also shows that the driver writer uses devsw_add as the mechanism for adding the driver interfaces to the dsent table. The first argument to devsw_add specifies a null-terminated string that represents the name of the device driver whose entry points you want to register and for which you want to reserve an associated major number. The figure shows that the variable mcfgname is passed to the first argument. The driver name is stored in the mcfgname variable.
The second argument specifies an integer that represents a specific reservation instance for this driver. This argument allows a driver to uniquely identify multiple major numbers for the driver. The figure shows that the constant MAJOR_INSTANCE is passed. This constant is defined as the value 1.
The third argument specifies the major number you want to reserve for the driver in the dsent table. The figure shows that the value -1 is passed. This value indicates that the devsw_add interface chooses the major number.
The fourth argument specifies a pointer to the device switch structure that contains pointers to the device driver's entry points for the system service's I/O requests and other information. The figure shows that the address of the previously initialized none_devsw_entry structure is passed.
Furthermore, the figure shows that devsw_add locates the position in the table corresponding to the major number, which in this example is 26, and adds the entries from none_devsw_entry. Note that the devsw_add interface adds the driver's I/O services entry points into the on-disk table.
The figure also shows that devsw_add returns the reserved major number, which in this example is 26. As shown in Figure 6-2:
The nodev entry calls the nodev interface, which returns an ENODEV (error, no such device). You should specify nodev when it is not appropriate to call that interface for a particular driver. For example, a device driver written for a write-only printer has no need for a read interface. Therefore, the read entry point would contain a nodev entry. In this example, it is not appropriate to call a strategy interface for the /dev/none driver; therefore, the nodev entry is specified.
The nodev entry calls the nodev interface, which returns an ENODEV (error, no such device). You should specify nodev when it is not appropriate to call that interface for a particular driver. For example, a device driver written for a write-only printer has no need for a read interface. Therefore, the read entry point would contain a nodev entry. In this example, it is not appropriate to call the dump, psize, stop, reset, and select interfaces for the /dev/none driver; therefore, the nodev entry is specified.
You could also specify nulldev. The nulldev entry calls the nulldev interface, which returns the value zero (0). You should specify nulldev when it is appropriate for the interface to be called, but the driver does not need to perform any actions to support the interface.
If, for example, the stop interface has no functionality for the NONE device, the nulldev entry should be specified instead of nodev.
The following code shows you how to reserve a major number, using the /dev/none device driver as an example:
int register_major_number() [1] { if (num_none == 0) { [2] return (ENODEV); } majnum = devsw_add(mcfgname, MAJOR_INSTANCE, majnum, &none_devsw_entry); [3] if (majnum == NO_DEV) { [4] return (ENODEV); } none_devno = majnum; [5] begunit = 0; [6] none_config = TRUE; [7] return(ESUCCESS); [8] }
The devsw_add interface takes four arguments:
This name is a string that matches the string you specified for the entry_name item in the /etc/sysconfigtab database. Typically, third-party driver writers specify the driver name (followed by a colon) in the sysconfigtab file fragment, which gets appended to the /etc/sysconfigtab database during the driver product installation.
In this call, the driver name is stored in the mcfgname variable.
In this call, the constant MAJOR_INSTANCE is passed.
In this call, the majnum variable is passed. Section 6.3 shows that this is an attribute in the none_attributes table. This means that the cfgmgr framework fills in this variable with the major number specified by the driver writers at EasyDriver Incorporated in their sysconfigtab file fragment. Section 13.4 describes the sysconfigtab file fragment.
In this call, the address of the none_devsw_entry (the /dev/none driver's driver structure) is passed.
Statically configured drivers need to register driver-specific interfaces (referred to as callbacks) that the cfgmgr framework can execute at a later time. Section 6.6.3 shows that the /dev/none driver's CFG_OP_CONFIGURE entry point called the register_callback interface to register two callbacks: callback_register_configuration and callback_register_major_number. The following sections show how to implement these callbacks. Your device driver could implement callbacks similar to these and, possibly, additional callbacks.
The callback_register_configuration interface is called by the cfgmgr framework at the execution point specified in the call to the register_callback interface. This interface creates the controller and device structures associated with the NONE device. This interface is specific to the /dev/none driver when it is in the static configuration state.
The following code shows the implementation of the callback_register_configuration interface:
void callback_register_configuration(point, order, argument, event_arg) int point; [1] int order; [2] ulong argument; [3] ulong event_arg; [4] { int status; [5] if(callback_return_status != ESUCCESS) return; [6] status = register_configuration(); [7] if(status != ESUCCESS) { [8] cfgmgr_set_status(mcfgname); [9] callback_return_status = status; [10] return; } }
Section 6.6.3 shows that in the call to register_callback the value CFG_PT_PRECONFIG was passed to the point argument. [Return to example]
Section 6.6.3 shows that in the call to register_callback the value CFG_ORD_NOMINAL was passed to the order argument. The kernel executes callback requests (interfaces) registered at this priority last. [Return to example]
Section 6.6.3 shows that in the call to register_callback the value zero (0) was passed to the argument argument. [Return to example]
The cfgmgr_set_status interface takes one argument: the name of the device driver for which you want to report an associated failure. This name is a string that matches the string you specified for the entry_name item in the /etc/sysconfigtab database. Typically, third-party driver writers specify the driver name (followed by a colon) in the sysconfigtab file fragment, which gets appended to the /etc/sysconfigtab database during the driver product installation.
In this call, the driver name is stored in the mcfgname variable. [Return to example]
The callback_register_major_number interface is called by the cfgmgr framework at the execution point specified in the call to the register_callback interface. This interface adds the I/O services interfaces (and other information) to the dsent table. It also reserves a major number. This interface is specific to the /dev/none driver when it is in the static configuration state.
The following code shows the implementation of the callback_register_major_number interface:
void callback_register_major_number(point, order, argument, event_arg) int point; [1] int order; [2] ulong argument; [3] ulong event_arg; [4] { int status; [5] if(callback_return_status != ESUCCESS) return; [6] status = register_major_number(); [7] if(status != ESUCCESS) { [8] cfgmgr_set_status(mcfgname); [9] callback_return_status = status; [10] return; } }
Section 6.6.3 shows that in the call to register_callback the value CFG_PT_POSTCONFIG was passed to the point argument. [Return to example]
Section 6.6.3 shows that in the call to register_callback the value CFG_ORD_NOMINAL was passed to the order argument. The kernel executes callback requests (interfaces) registered at this priority last. [Return to example]
Section 6.6.3 shows that in the call to register_callback the value zero (0) was passed to the argument argument. [Return to example]
The cfgmgr_set_status interface takes one argument: the name of the device driver for which you want to report an associated failure. This name is a string that matches the string you specified for the entry_name item in the /etc/sysconfigtab database. Typically, third-party driver writers specify the driver name (followed by a colon) in the sysconfigtab file fragment, which gets appended to the /etc/sysconfigtab database during the driver product installation.
In this call, the driver name is stored in the mcfgname variable. [Return to example]
The configure interface's CFG_OP_UNCONFIGURE operation performs the tasks associated with a system manager request to dynamically unconfigure (unload) the currently loaded device driver. The system manager makes this request through the sysconfig utility. The CFG_OP_UNCONFIGURE operation performs the following tasks related to unconfiguring (unloading) the device driver:
The following code shows you how to perform these tasks, using the /dev/none device driver as an example:
case CFG_OP_UNCONFIGURE: [1]
if(none_is_dynamic == SUBSYSTEM_STATICALLY_CONFIGURED) { return(ESUCCESS); } [2]
for (i = 0; i < num_none; i++) { if (none_softc[i].sc_openf != 0) { return(EBUSY); } [3] }
retval = devsw_del(mcfgname, MAJOR_INSTANCE); if (retval == NO_DEV) { return(ESRCH); } [4]
if (unconfigure_driver(DN_BUSNAME1, DRIVER_WILDNUM, &nonedriver, DN_BUSNAME1, DRIVER_WILDNUM) != 0) { [5]
return(ESRCH); } }
none_is_dynamic = 0; [6] none_config = FALSE; [7] break;
The devsw_del interface takes two arguments:
In this call, the driver name is stored in the mcfgname variable.
In this call, the driver instance is represented by the constant MAJOR_INSTANCE.
If devsw_del fails, it returns ESRCH to indicate that the driver is not currently present in the dsent table. [Return to example]
The unconfigure_driver driver interface takes five arguments:
In this call, the bus name is represented by the constant DN_BUSNAME1 (which maps to the DRIVER_WILDNAME constant).
In this call, the bus number is represented by the wild card constant DRIVER_WILDNUM. The wildcard indicates that unconfigure_driver deregisters all instances of the controllers connected to the bus.
In this call, the target name is represented by the wildcard constant DN_BUSNAME1.
In this call, the target number is represented by the wildcard constant DRIVER_WILDNUM.
The configure interface's CFG_OP_RECONFIGURE operation performs the tasks associated with a system manager request to reconfigure the currently configured device driver. The system manager makes this request through the sysconfig utility. The following code shows you how to set up the case statement for the CFG_OP_RECONFIGURE operation. You need this case statement in your driver if you want the reconfigure operation to work.
case CFG_OP_RECONFIGURE: [1] break;
The configure interface's CFG_OP_QUERY operation performs the tasks associated with a system manager request to query the currently loaded device driver. The system manager makes this request through the sysconfig utility. This query request is for information about the device driver that resides in the /etc/sysconfigtab database. The following code shows you how to set up the case statement for the CFG_OP_QUERY operation. You need this case statement in your driver if you want the query operation to work.
case CFG_OP_QUERY: [1] break;
The configure interface's default operation performs the tasks for any operation code other than CFG_OP_CONFIGURE, CFG_OP_UNCONFIGURE, CFG_OP_QUERY, and CFG_OP_RECONFIGURE. The following code shows you how to set up the case statement for the default operation. The code also shows the return from the sequence of case statements.
default: return(ENOTSUP); [1] }
return(ESUCCESS); [2] }