[Return to Library] [Contents] [Previous Chapter] [Next Section] [Next Chapter] [Index] [Help]


6    Implementing a Configure Interface to Support Static and Dynamic Configuration

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.


[Return to Library] [Contents] [Previous Chapter] [Next Section] [Next Chapter] [Index] [Help]


6.1    Using the cfg_subsys_attr_t Structure

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.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


6.2    Using the cfg_attr_t Structure

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.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


6.3    Setting Up Configure-Related Declarations and the cfg_subsys_attr_t Data Structure

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]
};

  1. Declares and initializes to the constant NO_DEV an integer variable called majnum. This variable is used to initialize the data value associated with the majnum attribute in the none_attributes table. (The data value is the addr member of a cfg_subsys_attr_t structure. Each element in the table is a cfg_subsys_attr_t structure.) Because the majnum attribute's operation is CFG_OP_QUERY, the cfgmgr framework queries the value during a query operation. This means the driver writer passed CFG_OP_QUERY to the optype argument of the driver's configure interface. [Return to example]

  2. Declares and initializes to the value zero (0) an integer variable called noneversion. This variable is used to initialize the data value associated with the version attribute in the none_attributes table. Because the version attribute's operation is CFG_OP_QUERY, the cfgmgr framework queries the value during a query operation. This means the driver writer passed CFG_OP_QUERY to the optype argument of the driver's configure interface. [Return to example]

  3. Declares and initializes to the value zero (0) an integer variable called none_developer_debug. This variable is used to initialize the data value associated with the None_Developer_Debug attribute in the none_attributes table. For this attribute, the possible operations are CFG_OP_QUERY, CFG_OP_RECONFIGURE, or CFG_OP_CONFIGURE. The CFG_OP_QUERY operation indicates that the cfgmgr framework queries the value during a query operation. This means the driver writer passed CFG_OP_QUERY to the optype argument of the driver's configure interface.

    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]

  4. Declares and initializes to the null string a character variable called mcfgname. The mcfgname variable is the address where the cfgmgr framework stores the driver name for the Module_Config_Name attribute. [Return to example]

  5. Declares and initializes to the null string a character variable called unused. The unused variable is the address where the cfgmgr framework stores the value for the attributes that the /dev/none driver does not use. [Return to example]

  6. Declares and initializes to the null string a character variable called cma_dd. The cma_dd variable is the address where the cfgmgr framework stores the value for the CMA_Option attribute. The cfgmgr framework obtains this value from the /etc/sysconfigtab database. [Return to example]

  7. Declares and initializes to the null string a character variable called tc_optiondata. The tc_optiondata variable is the address where the cfgmgr framework stores the value for the TC_Option attribute. The cfgmgr framework obtains this value from the /etc/sysconfigtab database. [Return to example]

  8. Declares an array of cfg_subsys_attr_t structures and calls it none_attributes. Section 6.1 describes the members of the cfg_subsys_attr_t structure. [Return to example]

  9. Sets the array to the attribute fields associated with the /dev/none driver. The following list describes how to interpret the elements in the array:

    [Return to example]

  10. Sets the array to device driver-related attribute fields that the /dev/none driver does not use. The reason for setting unused attribute fields in the driver's cfg_subsys_attr_t array is to prevent the cfgmgr framework from printing error messages on the console terminal when the driver is configured into the kernel. Note that the address of the data value associated with each of these attributes is unused. See Table 14-1 for a description of the following attribute fields:

    [Return to example]

  11. Sets the array to an attribute field used to produce runtime messages. The None_Developer_Debug attribute is used with the printf interface to display runtime messages on the console terminal. A device driver writer might want to define such an attribute to collect detailed information relating to a driver problem. When the attribute is set to 1 by the cfgmgr framework (through the CFG_OP_RECONFIGURE operation) or initialized in the boot path with any values defined in the /etc/sysconfigtab database, these messages appear on the console terminal. This would occur if the driver writer set to the value 1 the None_Developer_Debug attribute in the /etc/sysconfigtab database. These messages can provide the driver writer with configuration information that relates to detailed bus configuration information.

    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]

  12. Ends the array by specifying the null string. [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:

  1. Gets the entries for the /dev/none driver from the /etc/sysconfigtab database by locating the none: entry.

    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.

  2. Fills in the mcfgname and devmajor data values with the associated values in the /etc/sysconfigtab database.

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.

Figure 6-1: The cfgmgr Framework Filling in Attributes for the /dev/none Driver


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


6.4    Defining Bus-Specific Name Constants

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]

  1. Defines a constant called DN_BUSNAME1 that represents the name of the bus on which the driver operates. You pass this constant to the configure_driver and unconfigure_driver interfaces. Your goal should be to write one device driver that can operate on multiple buses. One way to handle multiple buses is to define the constant as a wildcard. One way to specify a wildcard is to use the DRIVER_WILDNAME constant, which signifies a wildcard character to the configure_driver and unconfigure_driver interfaces. In other words, the wildcard character tells the configure_driver and unconfigure_driver interfaces that the driver can operate on any of the Digital-supported buses. Section 6.6.3 shows how to call configure_driver and Section 6.7 shows how to call the unconfigure_driver interfaces.

    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]


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


6.5    Setting Up the Configure Interface

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 */

  1. Declares an argument called op to contain a constant that describes the configuration operation to be performed on the driver. This argument is used in a switch statement and evaluates to one of the following valid constants: CFG_OP_CONFIGURE, CFG_OP_UNCONFIGURE, CFG_OP_QUERY, or CFG_OP_RECONFIGURE. [Return to example]

  2. Declares a pointer to a cfg_attr_t data structure called indata that consists of inputs to the none_configure interface. The cfgmgr framework fills in this data structure. The cfg_attr_t data structure is used to represent a variety of information, including the /dev/none driver's major number requirements. Section 6.2 describes the cfg_attr_t structure. [Return to example]

  3. Declares an argument called indatalen to store the size of this input data structure. This argument represents the number of cfg_attr_t structures included in indata. [Return to example]

  4. Used with user-defined operations. The configuration entry point for user-defined operations is when the optype argument of the driver's configure interface is CFG_OP_USERDEFINED. [Return to example]

  5. Used with user-defined operations. The configuration entry point for user-defined operations is when the optype argument of the driver's configure interface is CFG_OP_USERDEFINED. [Return to example]

  6. Declares a variable called retval to store the return value from the register_configuration interface. Section 6.6.4.3 discusses the register_configuration interface. [Return to example]

  7. Declares a variable called i used in the for loop when none_configure unconfigures the dynamically configured /dev/none driver. Section 6.6.4.3 shows the use of this for loop in the CFG_OP_UNCONFIGURE operation. [Return to example]

  8. Declares a variable called driver_cfg_state that stores the configuration state (either static or dynamic) of the driver. Section 6.6.3 shows the use of this variable in the CFG_OP_CONFIGURE operation. [Return to example]

  9. Defines a constant that represents the number of configuration lines in the sysconfigtab file fragment. [Return to example]

  10. Declares an array of cfg_attr_t data structures and calls it cfg_buf. The number of cfg_attr_t structures in the array matches the number of configuration lines specified in the sysconfigtab file fragment for this device driver. The cfgmgr framework passes the cfg_attr_t array to the indata argument of the driver's configure interface. This array contains the strings that are stored in the sysconfigtab database for this device driver.

    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]


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


6.6    Implementing the CFG_OP_CONFIGURE Operation

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.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


6.6.1    Parsing Attributes in the cfg_attr_t Structure Array

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]

  1. Specifies the CFG_OP_CONFIGURE constant to indicate that this section of code implements the configure (static and dynamic) driver operation. The file /usr/sys/include/sys/sysconfig.h contains the definition of this constant. [Return to example]

  2. Calls the bcopy interface to copy a series of bytes with a specified limit.

    The bcopy interface takes three arguments:

    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]

  3. Sets up a for loop to traverse the cfg_attr_t structure. The for loop uses the indatalen argument (the size of the cfg_attr_t structure) to determine how many times the loop is executed. [Return to example]

  4. Evaluates (for this element in the array) the data type associated with the attribute. If the case statement that follows evaluates to the constant CFG_ATTR_STRTYPE, then the switch statement stops execution. Otherwise, the default case is executed. [Return to example]

  5. Evaluates (for this element in the array) the return code associated with the attribute. Attributes passed through the cfg_attr_t structure are not known to be valid until the /dev/none driver can check their status in the status member of the cfg_attr_t structure that the cfgmgr framework passes to the indata argument. When the case statement that follows evaluates to CFG_FRAME_SUCCESS, then the attribute needs no further checking and the switch statement stops execution. [Return to example]

  6. If the case statement does not evaluate to CFG_FRAME_SUCCESS, calls the printf interface to print on the console terminal the name of the attribute currently being checked. [Return to example]

  7. Evaluates (for this element in the array) the return code associated with the attribute. [Return to example]

  8. This case statement and those that follow check the return code constants. For example, if the case statement evaluates to the constant CFG_ATTR_EEXISTS, the printf interface prints the associated message on the console terminal. The case statement then stops execution.

    The purpose of these case statements is to:

    [Return to example]

  9. Returns an error if the /dev/none driver has already been either statically or dynamically configured into the kernel. [Return to example]


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


6.6.2    Initializing the Device Driver Name

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.
                                 */

  1. Performs a check on the device driver name that the cfgmgr framework uses to configure (as a result of a static or dynamic configuration request) the driver into the kernel. If the device driver name is NULL, subsequent code uses the controller name stored in the driver structure's ctlr_name member. The driver structure for the /dev/none driver is called nonedriver. Section 5.2 shows the declaration and initialization of the nonedriver structure.

    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:

    [Return to example]

  2. If the string comparison succeeds (that is, the mcfgname variable equals the null string), calls the strcpy interface to copy the string none into the mcfgname variable.

    The strcpy interface takes two arguments:

    [Return to example]

  3. Shows that the name can be obtained from the /etc/sysconfigtab database. Section 6.6.3 shows the completion of the code associated with this else statement. [Return to example]


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


6.6.3    Determining the Configuration State

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;

  1. Calls the cfgmgr_get_state interface to determine the configuration state of the driver. A device driver is either in the static or dynamic configuration state.

    The cfgmgr_get_state interface takes two arguments:

    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]

  2. If the /dev/none driver is in the static configuration state:

    The register_callback interface takes four arguments:

    [Return to example]

  3. Executes the lines of code that follow if driver_cfg_state evaluates to SUBSYSTEM_DYNAMICALLY_CONFIGURED, which indicates the /dev/none driver is in the dynamic configuration state. [Return to example]

  4. Sets the none_is_dynamic variable to SUBSYSTEM_DYNAMICALLY_CONFIGURED, which indicates the /dev/none driver is in the dynamic configuration state. [Return to example]

  5. Calls the register_configuration interface, which is implemented by the /dev/none driver to register controller and device information. Section 6.6.4.3 describes the implementation of the register_configuration interface. [Return to example]

  6. If the register_configuration interface does not return ESUCCESS, the none_configure interface returns a status value from register_configuration. [Return to example]

  7. Calls the configure_driver interface to configure the /dev/none driver into the system (hardware) configuration tree. If configure_driver does not return ESUCCESS, the none_configure interface returns an error to the cfgmgr framework. This error is from the configure_driver interface.

    The configure_driver driver interface takes four arguments:

    [Return to example]

  8. Calls the register_major_number interface, which is implemented by the /dev/none driver to register the /dev/none driver's I/O services interfaces and to reserve a major number. Section 6.6.6.2 describes the implementation of the register_major_number interface. [Return to example]

  9. If register_major_number does not return ESUCCESS, the none_configure interface returns an appropriate error value. This error value is from the register_major_number interface. [Return to example]


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


6.6.4    Registering Controller and Device Information

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.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


6.6.4.1    The controller_config Data Structure

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.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


6.6.4.2    The device_config Data Structure

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.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


6.6.4.3    Using create_controller_struct and create_device_struct to Register Hardware Information

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]
}

  1. Shows that the register_configuration interface does not have any arguments. [Return to example]

  2. Declares a controller_config structure and calls it ctlr_register. The line of code that follows declares a pointer to a controller_config structure called ctlr_register_ptr and initializes it to the address of the previously declared controller_config structure. The controller_config structure stores information about controllers. [Return to example]

  3. Declares a device_config structure and calls it device_register. The line of code that follows declares a pointer to a device_config structure called device_register_ptr and initializes it to the address of the previously declared device_config structure. The device_config structure stores information about devices. [Return to example]

  4. Declares a variable called status to contain the return value from the create_controller_struct and create_device_struct interfaces. [Return to example]

  5. Sets the revision member of the controller_config structure pointer to CTLR_CONFIG_REVISION. Digital defines this constant to an appropriate value in the /usr/sys/include/io/common/devdriver.h file. [Return to example]

  6. Sets the subsystem_name member of the controller_config structure pointer to the value stored in the mcfgname variable. This value is the driver name, none. Section 6.6.2 shows how the /dev/none driver uses this variable to check the driver name. The strcpy interface is the mechanism used to set this member to the driver name. [Return to example]

  7. Sets the bus_name member of the controller_config structure pointer to the value stored in the DN_BUSNAME1 constant. The value associated with DN_BUSNAME1 is the asterisk (*), which signifies a wildcard for the bus name. Section 6.4 discusses how to define bus-specific name constants. The strcpy interface is the mechanism used to set this member to the wild card value. [Return to example]

  8. Sets the devdriver member of the controller_config structure pointer to the address of the /dev/none driver's driver structure, nonedriver. Section 5.2.1 describes the nonedriver structure. [Return to example]

  9. Calls the create_controller_struct interface.

    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]

  10. If create_controller_struct fails, it returns an appropriate error to the status variable. The register_configuration interface, in turn, returns this status to the calling interface. [Return to example]

  11. Sets the revision member of the device_config structure pointer to DEVICE_CONFIG_REVISION. Digital defines this constant to an appropriate value in the /usr/sys/include/io/common/devdriver.h file. [Return to example]

  12. Sets the device_type member of the device_config structure pointer to the string disk. The strcpy interface is the mechanism used to set this member to the disk string. [Return to example]

  13. Sets the device_name member of the device_config structure pointer to the string nonedev. The strcpy interface is the mechanism used to set this member to the nonedev string. [Return to example]

  14. Sets the controller_name member of the device_config structure pointer to the string none. The strcpy interface is the mechanism used to set this member to the none string. [Return to example]

  15. Sets the controller_num member of the device_config structure pointer to the value zero (0). [Return to example]

  16. Calls the create_device_struct interface.

    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]

  17. If create_device_struct fails, it returns an appropriate error to the status variable. The register_configuration interface, in turn, returns this status to the calling interface. [Return to example]

  18. Returns ESUCCESS to the calling interface. [Return to example]


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


6.6.5    Registering I/O Services Interfaces and Reserving a Major Number

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.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


6.6.5.1    The dsent Data Structure

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.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


6.6.5.2    Using the devsw_add Interface to Register I/O Services Interfaces and Reserve a Major Number

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.

Figure 6-2: Adding Entries to the dsent Table for Loadable Drivers

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 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]
}

  1. Shows that the register_major_number interface takes no arguments. [Return to example]

  2. If there are no devices present in the system after driver configuration has completed, sets the cfgmgr status to unconfigured and exits from the callback. [Return to example]

  3. Calls the devsw_add interface to register the /dev/none driver's I/O services interfaces (and other information) and to reserve a major number.

    The devsw_add interface takes four arguments:

    [Return to example]

  4. If the call to devsw_add fails, returns the error ENODEV. This error is defined in the /usr/sys/include/sys/errno.h file. The call could fail if the driver requests a specific major number and that major number is currently in use. The call could also fail if the device switch table is currently full. [Return to example]

  5. Stores the major number returned by the devsw_add interface so that it can be used later to unconfigure the device. [Return to example]

  6. Sets begunit to the first minor device number in the range. In this case, the first minor number is zero (0). [Return to example]

  7. Sets the state flag to the constant TRUE to indicate that the /dev/none device driver is now dynamically configured into the kernel. [Return to example]

  8. Returns ESUCCESS. [Return to example]


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


6.6.6    Implementing Callback Interfaces

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.


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


6.6.6.1    Implementing the callback_register_configuration Interface

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;
   }
}

  1. Specifies the dispatch point at which the kernel calls this callback request (the driver's xxcallback interface). The kernel passes the value associated with this dispatch point to the driver's xxcallback interface when it calls it.

    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]

  2. Specifies the order in which you want callback requests (the driver's xxcallback interfaces) registered for the same dispatch point to be executed. The kernel passes the value associated with this order to the driver's xxcallback interface when it calls it. You use this argument to control the order of execution of multiple xxcallback interfaces within each dispatch point. The register_callback interface executes a device driver's xxcallback interfaces for the same dispatch point in increasing numerical order of the value specified in the order argument.

    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]

  3. Specifies an argument that you want the kernel to pass to the driver's xxcallback interface when the kernel calls it. You pass the integer constant 0L to indicate that you do not want to pass an argument.

    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]

  4. Specifies an argument passed by the kernel dispatcher and is specific to the execution point. If there is no argument associated with this execution point, the kernel dispatcher passes the integer constant 0L. [Return to example]

  5. Declares a variable to store the return value from the call to the register_configuration interface. [Return to example]

  6. Determines if a previous failure has occurred in statically configuring the /dev/none driver. If so, exits from the callback without doing any configuration work. [Return to example]

  7. If no previous failure has occurred, calls the register_configuration interface. This is the interface that performs the task of creating the controller and device structures associated with the /dev/none driver. Section 6.6.4.3 describes the implementation of the register_configuration interface. [Return to example]

  8. If configuration registration is successful, returns from this callback. [Return to example]

  9. If configuration registration is not successful, calls the cfgmgr_set_status interface to report a failure to the cfgmgr framework.

    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]

  10. Sets the callback_return_status to the return value from the register_configuration interface. Section 5.2.1 shows the declaration and initialization of the callback_return_status variable. Section 6.6.3 shows the setting of this variable in the CFG_OP_CONFIGURE entry point. [Return to example]


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


6.6.6.2    Implementing the callback_register_major_number Interface

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;
   }
}

  1. Specifies the dispatch point at which the kernel calls this callback request (the driver's xxcallback interface). The kernel passes the value associated with this dispatch point to the driver's xxcallback interface when it calls it.

    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]

  2. Specifies the order in which you want callback requests (the driver's xxcallback interfaces) registered for the same dispatch point to be executed. The kernel passes the value associated with this order to the driver's xxcallback interface when it calls it. You use this argument to control the order of execution of multiple xxcallback interfaces within each dispatch point. The register_callback interface executes a device driver's xxcallback interfaces for the same dispatch point in increasing numerical order of the value specified in the order argument.

    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]

  3. Specifies an argument that you want the kernel to pass to the driver's xxcallback interface when the kernel calls it. You pass the integer constant 0L to indicate that you do not want to pass an argument.

    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]

  4. Specifies an argument passed by the kernel dispatcher and is specific to the execution point. If there is no argument associated with this execution point, the kernel dispatcher passes the integer constant 0L. [Return to example]

  5. Declares a variable to store the return value from the call to the register_major_number interface. [Return to example]

  6. Determines if a previous failure has occurred in statically configuring the /dev/none driver. If so, exits from the callback without doing any configuration work. [Return to example]

  7. If no previous failure has occurred, calls the register_major_number interface. This is the interface that performs the task of registering the /dev/none driver's I/O services interfaces and reserving a major number in the dsent table. Section 6.6.5 describes the implementation of the register_major_number interface. [Return to example]

  8. If the registration of the I/O services interfaces and the reservation of the major number is successful, returns from this callback. [Return to example]

  9. If the registration of the I/O services interfaces and the reservation of the major number is not successful, calls the cfgmgr_set_status interface to report a failure to the cfgmgr framework.

    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]

  10. Sets the callback_return_status to the return value from the register_major_number interface. Section 5.2.1 shows the declaration and initialization of the callback_return_status variable. Section 6.6.3 shows the setting of this variable in the CFG_OP_CONFIGURE entry point. [Return to example]


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


6.7    Implementing the CFG_OP_UNCONFIGURE Operation

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;

  1. Specifies the CFG_OP_UNCONFIGURE constant to indicate that this section of code implements the unconfigure operation of the loadable driver. The file /usr/sys/include/sys/sysconfig.h contains the definition of this constant. [Return to example]

  2. Returns ESUCCESS if the none_is_dynamic variable is SUBSYSTEM_STATICALLY_CONFIGURED. This constant signifies that the /dev/none device driver is in the static configuration state. A statically configured driver cannot be unconfigured or unloaded; therefore, the appropriate task here is to return. [Return to example]

  3. Prevents the system manager from unloading the device driver if it is currently active. To determine if the driver is active, the code checks the sc_openf member of this NONE device's none_softc structure to determine if the device is open. If so, the code returns the EBUSY constant. This error code is defined in /usr/sys/include/sys/errno.h. [Return to example]

  4. Calls the devsw_del interface to delete the /dev/none driver's entry points from the dsent table.

    The devsw_del interface takes two arguments:

    If devsw_del fails, it returns ESRCH to indicate that the driver is not currently present in the dsent table. [Return to example]

  5. Calls the unconfigure_driver driver interface to unconfigure the /dev/none driver. A call to this interface results in a call to the driver's none_ctlr_unattach interface for each instance of the controller. Section 7.3.2 shows you how to set up the none_ctlr_unattach interface and Section 7.3.3 shows some typical tasks that this interface can perform.

    The unconfigure_driver driver interface takes five arguments:

    [Return to example]

  6. Sets the none_is_dynamic variable to the value zero (0). This indicates that the dynamically configured /dev/none driver was unloaded. [Return to example]

  7. Sets the none_config variable to the value FALSE to indicate that the /dev/none device driver is now unconfigured. [Return to example]


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


6.8    Implementing the CFG_OP_RECONFIGURE Operation

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;

  1. Specifies the CFG_OP_RECONFIGURE operation to indicate that this section of code implements the reconfigure operation of the loadable driver. The file /usr/sys/include/sys/sysconfig.h contains the definition of this constant. [Return to example]


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Section] [Next Chapter] [Index] [Help]


6.9    Implementing the CFG_OP_QUERY Operation

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;

  1. Specifies the CFG_OP_QUERY operation to indicate that this section of code implements the query operation of the loadable driver. The file /usr/sys/include/sys/sysconfig.h contains the definition of this constant. Note that the CFG_OP_QUERY case statement does not perform any explicit operations. The reason for this is the configuration management framework returns the values associated with a query request. [Return to example]


[Return to Library] [Contents] [Previous Chapter] [Previous Section] [Next Chapter] [Index] [Help]


6.10    Implementing the Default Operation

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] }

  1. Defines an unknown operation type and returns the error constant ENOTSUP to indicate this condition. This section of code is called if the op argument is set to anything other than CFG_OP_CONFIGURE, CFG_OP_UNCONFIGURE, CFG_OP_RECONFIGURE, or CFG_OP_QUERY. [Return to example]

  2. To indicate that the /dev/none driver's none_configure interface has completed the requested operation of CFG_OP_CONFIGURE, CFG_OP_RECONFIGURE, CFG_OP_UNCONFIGURE, or CFG_OP_QUERY successfully, returns the value zero (0). [Return to example]