This chapter shows you how to develop a very simple, working driver. This chapter explains how to write the driver and configuration file, compile the driver, load the driver, and test the driver.
The driver that is shown in this chapter is a pseudo device driver that merely writes a message to a system log every time an entry point is entered. This driver demonstrates the minimum functionality that any character driver must implement. You can use this driver as a template for building a complex driver.
This chapter discusses the following driver development steps:
This example guides you through the following steps:
Create a directory where you can develop your driver and open a new text file named dummy.c.
Write the entry points for loadable module configuration: _init(9E), _info(9E), and _fini(9E).
Write the entry points for autoconfiguration: attach(9E), detach(9E), getinfo(9E), and prop_op(9E).
Write the entry points for user context: open(9E), close(9E), read(9E), and write(9E).
Define the data structures: the character and block operations structure cb_ops(9S), the device operations structure dev_ops(9S), and the module linkage structures modldrv(9S) and modlinkage(9S).
Create the driver configuration file dummy.conf.
Build and install the driver.
Test the driver by loading the driver, reading from and writing to the device node, and unloading the driver.
The entry points that are to be created in this example are shown in the following diagram.
This section describes the entry points and data structures that are included in this driver and shows you how to define them. All of these data structures and almost all of these entry points are required for any character device driver.
This section describes the following entry points and data structures:
Loadable module configuration entry points
Autoconfiguration entry points
User context entry points
Character and block operations structure
Device operations structure
Module linkage structures
First, create a directory where you can develop your driver. This driver is named dummy because this driver does not do any real work. Next, open a new text file named dummy.c.
Every kernel module of any type must define at least the following three loadable module configuration entry points:
The _init(9E) routine initializes a loadable module. The _init(9E) routine must at least call the mod_install(9F) function and return the success or failure value that is returned by mod_install(9F).
The _info(9E) routine returns information about a loadable module. The _info(9E) routine must at least call the mod_info(9F) function and return the value that is returned by mod_info(9F).
The _fini(9E) routine prepares a loadable module for unloading. The _fini(9E) routine must at least call the mod_remove(9F) function and return the success or failure value that is returned by mod_remove(9F). When mod_remove(9F) is successful, the _fini(9E) routine must undo everything that the _init(9E) routine did.
The mod_install(9F), mod_info(9F), and mod_remove(9F) functions are used in exactly the same way in every driver, regardless of the functionality of the driver. You do not need to investigate what the values of the arguments of these functions should be. You can copy these function calls from this example and paste them into every driver you write.
In this section, the following code is added to the dummy.c source file:
/* Loadable module configuration entry points */ int _init(void) { cmn_err(CE_NOTE, "Inside _init"); return(mod_install(&ml)); } int _info(struct modinfo *modinfop) { cmn_err(CE_NOTE, "Inside _info"); return(mod_info(&ml, modinfop)); } int _fini(void) { cmn_err(CE_NOTE, "Inside _fini"); return(mod_remove(&ml)); }
The _init(9E), _info(9E), and _fini(9E) routine names are not unique to any particular kernel module. You customize the behavior of these routines when you define them in your module, but the names of these routines are not unique. These three routines are declared in the modctl.h header file. You need to include the modctl.h header file in your dummy.c file. Do not declare these three routines in dummy.c.
The _init(9E) routine returns type int and takes no arguments. The _init(9E) routine must call the mod_install(9F) function and return the success or failure value that is returned by mod_install(9F).
The mod_install(9F) function takes an argument that is a modlinkage(9S) structure. See Defining the Module Linkage Structures for information about the modlinkage(9S) structure.
This driver is supposed to write a message each time an entry point is entered. Use the cmn_err(9F) function to write a message to a system log. The cmn_err(9F) function usually is used to report an error condition. The cmn_err(9F) function also is useful for debugging in the same way that you might use print statements in a user program. Be sure to remove cmn_err() calls that are used for development or debugging before you compile your production version driver. You might want to use cmn_err() calls in a production driver to write error messages that would be useful to a system administrator.
The cmn_err(9F) function requires you to include the cmn_err.h header file, the ddi.h header file, and the sunddi.h header file. The cmn_err(9F) function takes two arguments. The first argument is a constant that indicates the severity of the error message. The message written by this driver is not an error message but is simply a test message. Use CE_NOTE for the value of this severity constant. The second argument the cmn_err(9F) function takes is a string message.
The following code is the _init(9E) routine that you should enter into your dummy.c file. The ml structure is the modlinkage(9S) structure that is discussed in Defining the Module Linkage Structures.
int _init(void) { cmn_err(CE_NOTE, "Inside _init"); return(mod_install(&ml)); }
The _info(9E) routine returns type int and takes an argument that is a pointer to an opaque modinfo structure. The _info(9E) routine must return the value that is returned by the mod_info(9F) function.
The mod_info(9F) function takes two arguments. The first argument to mod_info(9F) is a modlinkage(9S) structure. See Defining the Module Linkage Structures for information about the modlinkage(9S) structure. The second argument to mod_info(9F) is the same modinfo structure pointer that is the argument to the _info(9E) routine. The mod_info(9F) function returns the module information or returns zero if an error occurs.
Use the cmn_err(9F) function to write a message to the system log in the same way that you used the cmn_err(9F) function in your _init(9E) entry point.
The following code is the _info(9E) routine that you should enter into your dummy.c file. The ml structure is discussed in Defining the Module Linkage Structures. The modinfop argument is a pointer to an opaque structure that the system uses to pass module information.
int _info(struct modinfo *modinfop) { cmn_err(CE_NOTE, "Inside _info"); return(mod_info(&ml, modinfop)); }
The _fini(9E) routine returns type int and takes no arguments. The _fini(9E) routine must call the mod_remove(9F) function and return the success or failure value that is returned by mod_remove(9F).
When mod_remove(9F) is successful, the _fini(9E) routine must undo everything that the _init(9E) routine did. The _fini(9E) routine must call mod_remove(9F) because the _init(9E) routine called mod_install(9F). The _fini(9E) routine must deallocate anything that was allocated, close anything that was opened, and destroy anything that was created in the _init(9E) routine.
The _fini(9E) routine can be called at any time when a module is loaded. In normal operation, the _fini(9E) routine often fails. This behavior is normal because the kernel allows the module to determine whether the module can be unloaded. If mod_remove(9F) is successful, the module determines that devices were detached, and the module can be unloaded. If mod_remove(9F) fails, the module determines that devices were not detached, and the module cannot be unloaded.
The following actions take place when mod_remove(9F) is called:
The kernel checks whether this driver is busy. This driver is busy if one of the following conditions is true:
A device node that is managed by this driver is open.
Another module that depends on this driver is open. A module depends on this driver if the module was linked using the -N option with this driver named as the argument to that -N option. See the ld(1) man page for more information.
If the driver is busy, then mod_remove(9F) fails and _fini(9E) fails.
If the driver is not busy, then the kernel calls the detach(9E) entry point of the driver.
If detach(9E) fails, then mod_remove(9F) fails and _fini(9E) fails.
If detach(9E) succeeds, then mod_remove(9F) succeeds, and _fini(9E) continues its cleanup work.
The mod_remove(9F) function takes an argument that is a modlinkage(9S) structure. See Defining the Module Linkage Structures for information about the modlinkage(9S) structure.
Use the cmn_err(9F) function to write a message to the system log in the same way that you used the cmn_err(9F) function in your _init(9E) entry point.
The following code is the _fini(9E) routine that you should enter into your dummy.c file. The ml structure is discussed in Defining the Module Linkage Structures.
int _fini(void) { cmn_err(CE_NOTE, "Inside _fini"); return(mod_remove(&ml)); }
The _init(9E), _info(9E), _fini(9E), and mod_install(9F) functions require you to include the modctl.h header file. The cmn_err(9F) function requires you to include the cmn_err.h header file, the ddi.h header file, and the sunddi.h header file.
The following header files are required by the three loadable module configuration routines that you have written in this section. Include this code near the top of your dummy.c file.
#include <sys/modctl.h> /* used by _init, _info, _fini */ #include <sys/cmn_err.h> /* used by all entry points for this driver */ #include <sys/ddi.h> /* used by all entry points for this driver */ #include <sys/sunddi.h> /* used by all entry points for this driver */
Every character driver must define at least the following autoconfiguration entry points. The kernel calls these routines when the device driver is loaded.
The attach(9E) routine must call ddi_create_minor_node(9F). The ddi_create_minor_node(9F) function provides the information the system needs to create the device files.
The detach(9E) routine must call ddi_remove_minor_node(9F) to deallocate everything that was allocated by ddi_create_minor_node(9F). The detach(9E) routine must undo everything that the attach(9E) routine did.
The getinfo(9E) routine returns requested device driver information through one of its arguments.
The prop_op(9E) routine returns requested device driver property information through a pointer. You can call the ddi_prop_op(9F) function instead of writing your own prop_op(9E) entry point. Use the prop_op(9E) entry point to customize the behavior of the ddi_prop_op(9F) function.
In this section, the following code is added:
/* Device autoconfiguration entry points */ static int dummy_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { cmn_err(CE_NOTE, "Inside dummy_attach"); switch(cmd) { case DDI_ATTACH: dummy_dip = dip; if (ddi_create_minor_node(dip, "0", S_IFCHR, ddi_get_instance(dip), DDI_PSEUDO,0) != DDI_SUCCESS) { cmn_err(CE_NOTE, "%s%d: attach: could not add character node.", "dummy", 0); return(DDI_FAILURE); } else return DDI_SUCCESS; default: return DDI_FAILURE; } } static int dummy_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { cmn_err(CE_NOTE, "Inside dummy_detach"); switch(cmd) { case DDI_DETACH: dummy_dip = 0; ddi_remove_minor_node(dip, NULL); return DDI_SUCCESS; default: return DDI_FAILURE; } } static int dummy_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp) { cmn_err(CE_NOTE, "Inside dummy_getinfo"); switch(cmd) { case DDI_INFO_DEVT2DEVINFO: *resultp = dummy_dip; return DDI_SUCCESS; case DDI_INFO_DEVT2INSTANCE: *resultp = 0; return DDI_SUCCESS; default: return DDI_FAILURE; } } static int dummy_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op, int flags, char *name, caddr_t valuep, int *lengthp) { cmn_err(CE_NOTE, "Inside dummy_prop_op"); return(ddi_prop_op(dev,dip,prop_op,flags,name,valuep,lengthp)); }
The attach(9E), detach(9E), getinfo(9E), and prop_op(9E) entry point routines need to be uniquely named for this driver. Choose a prefix to use with each entry point routine.
By convention, the prefix used for function and data names that are unique to this driver is either the name of this driver or an abbreviation of the name of this driver. Use the same prefix throughout the driver. This practice makes debugging much easier.
In the example shown in this chapter, dummy_ is used for the prefix to each function and data name that is unique to this example.
The following declarations are the autoconfiguration entry point declarations you should have in your dummy.c file. Note that each of these functions is declared static.
static int dummy_attach(dev_info_t *dip, ddi_attach_cmd_t cmd); static int dummy_detach(dev_info_t *dip, ddi_detach_cmd_t cmd); static int dummy_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp); static int dummy_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op, int flags, char *name, caddr_t valuep, int *lengthp);
The attach(9E) routine returns type int. The attach(9E) routine must return either DDI_SUCCESS or DDI_FAILURE. These two constants are defined in sunddi.h. All of the autoconfiguration entry point routines except for prop_op(9E) return either DDI_SUCCESS or DDI_FAILURE.
The attach(9E) routine takes two arguments. The first argument is a pointer to the dev_info structure for this driver. All of the autoconfiguration entry point routines take a dev_info argument. The second argument is a constant that specifies the attach type. The value that is passed through this second argument is either DDI_ATTACH or DDI_RESUME. Every attach(9E) routine must define behavior for at least DDI_ATTACH.
The DDI_ATTACH code must initialize a device instance. In a realistic driver, you define and manage multiple instances of the driver by using a state structure and the ddi_soft_state(9F) functions. Each instance of the driver has its own copy of the state structure that holds data specific to that instance. One of the pieces of data that is specific to each instance is the device instance pointer. Each instance of the device driver is represented by a separate device file in /devices. Each device instance file is pointed to by a separate device instance pointer. See Managing Device State for information about state structures and ddi_soft_state(9F) functions. See Devices as Files for information about device files and instances.
This dummy driver allows only one instance. Because this driver allows only one instance, this driver does not use a state structure. This driver still must declare a device instance pointer and initialize the pointer value in the attach(9E) routine. Enter the following code near the beginning of dummy.c to declare a device instance pointer for this driver:
dev_info_t *dummy_dip; /* keep track of one instance */
The following code is the dummy_attach() routine that you should enter into your dummy.c file. You can copy the name portion of this function definition directly from the declaration you entered in Declaring the Autoconfiguration Entry Points.
static int dummy_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { cmn_err(CE_NOTE, "Inside dummy_attach"); switch(cmd) { case DDI_ATTACH: dummy_dip = dip; if (ddi_create_minor_node(dip, "0", S_IFCHR, ddi_get_instance(dip), DDI_PSEUDO,0) != DDI_SUCCESS) { cmn_err(CE_NOTE, "%s%d: attach: could not add character node.", "dummy", 0); return(DDI_FAILURE); } else return DDI_SUCCESS; default: return DDI_FAILURE; } }
First, use cmn_err(9F) to write a message to the system log, as you did in your _init(9E) entry point. Then provide DDI_ATTACH behavior. Within the DDI_ATTACH code, first assign the device instance pointer from the dummy_attach() argument to the dummy_dip variable that you declared above. You need to save this pointer value in the global variable so that you can use this pointer to get information about this instance from dummy_getinfo() and detach this instance in dummy_detach(). In this dummy_attach() routine, the device instance pointer is used by the ddi_get_instance(9F) function to return the instance number. The device instance pointer and the instance number both are used by ddi_create_minor_node(9F) to create a new device node.
A realistic driver probably would use the ddi_soft_state(9F) functions to create and manage a device node. This dummy driver uses the ddi_create_minor_node(9F) function to create a device node. The ddi_create_minor_node(9F) function takes six arguments. The first argument to the ddi_create_minor_node(9F) function is the device instance pointer that points to the dev_info structure of this device. The second argument is the name of this minor node. The third argument is S_IFCHR if this device is a character minor device or is S_IFBLK if this device is a block minor device. This dummy driver is a character driver.
The fourth argument to the ddi_create_minor_node(9F) function is the minor number of this minor device. This number is also called the instance number. The ddi_get_instance(9F) function returns this instance number. The fifth argument to the ddi_create_minor_node(9F) function is the node type. The ddi_create_minor_node(9F) man page lists the possible node types. The DDI_PSEUDO node type is for pseudo devices. The sixth argument to the ddi_create_minor_node(9F) function specifies whether this is a clone device. This is not a clone device, so set this argument value to 0.
If the ddi_create_minor_node(9F) call is not successful, write a message to the system log and return DDI_FAILURE. If the ddi_create_minor_node(9F) call is successful, return DDI_SUCCESS. If this dummy_attach() routine receives any cmd other than DDI_ATTACH, return DDI_FAILURE.
The detach(9E) routine takes two arguments. The first argument is a pointer to the dev_info structure for this driver. The second argument is a constant that specifies the detach type. The value that is passed through this second argument is either DDI_DETACH or DDI_SUSPEND. Every detach(9E) routine must define behavior for at least DDI_DETACH.
The DDI_DETACH code must undo everything that the DDI_ATTACH code did. In the DDI_ATTACH code in your attach(9E) routine, you saved the address of a new dev_info structure and you called the ddi_create_minor_node(9F) function to create a new node. In the DDI_DETACH code in this detach(9E) routine, you need to reset the variable that pointed to the dev_info structure for this node. You also need to call the ddi_remove_minor_node(9F) function to remove this node. The detach(9E) routine must deallocate anything that was allocated, close anything that was opened, and destroy anything that was created in the attach(9E) routine.
The following code is the dummy_detach() routine that you should enter into your dummy.c file. You can copy the name portion of this function definition directly from the declaration you entered in Declaring the Autoconfiguration Entry Points.
static int dummy_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { cmn_err(CE_NOTE, "Inside dummy_detach"); switch(cmd) { case DDI_DETACH: dummy_dip = 0; ddi_remove_minor_node(dip, NULL); return DDI_SUCCESS; default: return DDI_FAILURE; } }
First, use cmn_err(9F) to write a message to the system log, as you did in your _init(9E) entry point. Then provide DDI_DETACH behavior. Within the DDI_DETACH code, first reset the dummy_dip variable that you set in dummy_attach() above. You cannot reset this device instance pointer unless you remove all instances of the device. This dummy driver supports only one instance.
Next, call the ddi_remove_minor_node(9F) function to remove this device node. The ddi_remove_minor_node(9F) function takes two arguments. The first argument is the device instance pointer that points to the dev_info structure of this device. The second argument is the name of the minor node you want to remove. If the value of the minor node argument is NULL, then ddi_remove_minor_node(9F) removes all instances of this device. Because the DDI_DETACH code of this driver always removes all instances, this dummy driver supports only one instance.
If the value of the cmd argument to this dummy_detach() routine is DDI_DETACH, remove all instances of this device and return DDI_SUCCESS. If this dummy_detach() routine receives any cmd other than DDI_DETACH, return DDI_FAILURE.
The getinfo(9E) routine takes a pointer to a device number and returns a pointer to a device information structure or returns a device instance number. The return value of the getinfo(9E) routine is DDI_SUCCESS or DDI_FAILURE. The pointer or instance number requested from the getinfo(9E) routine is returned through a pointer argument.
The getinfo(9E) routine takes four arguments. The first argument is a pointer to the dev_info structure for this driver. This dev_info structure argument is obsolete and is no longer used by the getinfo(9E) routine.
The second argument to the getinfo(9E) routine is a constant that specifies what information the getinfo(9E) routine must return. The value of this second argument is either DDI_INFO_DEVT2DEVINFO or DDI_INFO_DEVT2INSTANCE. The third argument to the getinfo(9E) routine is a pointer to a device number. The fourth argument is a pointer to the place where the getinfo(9E) routine must store the requested information. The information stored at this location depends on the value you passed in the second argument to the getinfo(9E) routine.
The following table describes the relationship between the second and fourth arguments to the getinfo(9E) routine.
Table 2–1 Get Driver Information Entry Point Arguments
cmd |
arg |
resultp |
---|---|---|
DDI_INFO_DEVT2DEVINFO |
Device number |
Device information structure pointer |
DDI_INFO_DEVT2INSTANCE |
Device number |
Device instance number |
The following code is the dummy_getinfo() routine that you should enter into your dummy.c file. You can copy the name portion of this function definition directly from the declaration you entered in Declaring the Autoconfiguration Entry Points.
static int dummy_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp) { cmn_err(CE_NOTE, "Inside dummy_getinfo"); switch(cmd) { case DDI_INFO_DEVT2DEVINFO: *resultp = dummy_dip; return DDI_SUCCESS; case DDI_INFO_DEVT2INSTANCE: *resultp = 0; return DDI_SUCCESS; default: return DDI_FAILURE; } }
First, use cmn_err(9F) to write a message to the system log, as you did in your _init(9E) entry point. Then provide DDI_INFO_DEVT2DEVINFO behavior. A realistic driver would use arg to get the instance number of this device node. A realistic driver would then call the ddi_get_soft_state(9F) function and return the device information structure pointer from that state structure. This dummy driver supports only one instance and does not use a state structure. In the DDI_INFO_DEVT2DEVINFO code of this dummy_getinfo() routine, simply return the one device information structure pointer that the dummy_attach() routine saved.
Next, provide DDI_INFO_DEVT2INSTANCE behavior. Within the DDI_INFO_DEVT2INSTANCE code, simply return 0. This dummy driver supports only one instance. The instance number of that one instance is 0.
The prop_op(9E) entry point is required for every driver. If your driver does not need to customize the behavior of the prop_op(9E) entry point, then your driver can use the ddi_prop_op(9F) function for the prop_op(9E) entry point. Drivers that create and manage their own properties need a custom prop_op(9E) routine. This dummy driver uses a prop_op(9E) routine to call cmn_err(9F) before calling the ddi_prop_op(9F) function.
The prop_op(9E) entry point and the ddi_prop_op(9F) function both require that you include the types.h header file. The prop_op(9E) entry point and the ddi_prop_op(9F) function both take the same seven arguments. These arguments are not discussed here because this dummy driver does not create and manage its own properties. See the prop_op(9E) man page to learn about the prop_op(9E) arguments.
The following code is the dummy_prop_op() routine that you should enter into your dummy.c file. You can copy the name portion of this function definition directly from the declaration you entered in Declaring the Autoconfiguration Entry Points.
static int dummy_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op, int flags, char *name, caddr_t valuep, int *lengthp) { cmn_err(CE_NOTE, "Inside dummy_prop_op"); return(ddi_prop_op(dev,dip,prop_op,flags,name,valuep,lengthp)); }
First, use cmn_err(9F) to write a message to the system log, as you did in your _init(9E) entry point. Then call the ddi_prop_op(9F) function with exactly the same arguments as the dummy_prop_op() function.
All of the autoconfiguration entry point routines and all of the user context entry point routines require that you include the ddi.h and sunddi.h header files. You already included these two header files for the cmn_err(9F) function.
The ddi_create_minor_node(9F) function requires the stat.h header file. The dummy_attach() routine calls the ddi_create_minor_node(9F) function. The prop_op(9E) and the ddi_prop_op(9F) functions require the types.h header file.
The following code is the list of header files that you now should have included in your dummy.c file for the four autoconfiguration routines you have written in this section and the three loadable module configuration routines you wrote in the previous section.
#include <sys/modctl.h> /* used by _init, _info, _fini */ #include <sys/types.h> /* used by prop_op, ddi_prop_op */ #include <sys/stat.h> /* defines S_IFCHR used by ddi_create_minor_node */ #include <sys/cmn_err.h> /* used by all entry points for this driver */ #include <sys/ddi.h> /* used by all entry points for this driver */ /* also used by ddi_get_instance, ddi_prop_op */ #include <sys/sunddi.h> /* used by all entry points for this driver */ /* also used by ddi_create_minor_node, */ /* ddi_get_instance, and ddi_prop_op */
User context entry points correspond closely to system calls. When a system call opens a device file, then the open(9E) routine in the driver for that device is called.
All character and block drivers must define the open(9E) user context entry point. However, the open(9E) routine can be nulldev(9F). The close(9E), read(9E), and write(9E) user context routines are optional.
The open(9E) routine gains access to the device.
The close(9E) routine relinquishes access to the device. The close(9E) routine must undo everything that the open(9E) routine did.
The read(9E) routine reads data from the device node.
The write(9E) routine writes data to the device node.
In this section, the following code is added:
/* Use context entry points */ static int dummy_open(dev_t *devp, int flag, int otyp, cred_t *cred) { cmn_err(CE_NOTE, "Inside dummy_open"); return DDI_SUCCESS; } static int dummy_close(dev_t dev, int flag, int otyp, cred_t *cred) { cmn_err(CE_NOTE, "Inside dummy_close"); return DDI_SUCCESS; } static int dummy_read(dev_t dev, struct uio *uiop, cred_t *credp) { cmn_err(CE_NOTE, "Inside dummy_read"); return DDI_SUCCESS; } static int dummy_write(dev_t dev, struct uio *uiop, cred_t *credp) { cmn_err(CE_NOTE, "Inside dummy_write"); return DDI_SUCCESS; }
The user context entry point routines need to be uniquely named for this driver. Use the same prefix for each of the user context entry points that you used for each of the autoconfiguration entry point routines. The following declarations are the entry point declarations you should have in your dummy.c file:
static int dummy_attach(dev_info_t *dip, ddi_attach_cmd_t cmd); static int dummy_detach(dev_info_t *dip, ddi_detach_cmd_t cmd); static int dummy_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp); static int dummy_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op, int flags, char *name, caddr_t valuep, int *lengthp); static int dummy_open(dev_t *devp, int flag, int otyp, cred_t *cred); static int dummy_close(dev_t dev, int flag, int otyp, cred_t *cred); static int dummy_read(dev_t dev, struct uio *uiop, cred_t *credp); static int dummy_write(dev_t dev, struct uio *uiop, cred_t *credp);
The open(9E) routine returns type int. The open(9E) routine should return either DDI_SUCCESS or the appropriate error number.
The open(9E) routine takes four arguments. This dummy driver is so simple that this dummy_open() routine does not use any of the open(9E) arguments. The examples in Chapter 3, Reading and Writing Data in Kernel Memory show the open(9E) routine in more detail.
The following code is the dummy_open() routine that you should enter into your dummy.c file. You can copy the name portion of this function definition directly from the declaration you entered in Declaring the User Context Entry Points. Write a message to the system log and return success.
static int dummy_open(dev_t *devp, int flag, int otyp, cred_t *cred) { cmn_err(CE_NOTE, "Inside dummy_open"); return DDI_SUCCESS; }
The close(9E) routine returns type int. The close(9E) routine should return either DDI_SUCCESS or the appropriate error number.
The close(9E) routine takes four arguments. This dummy driver is so simple that this dummy_close() routine does not use any of the close(9E) arguments. The examples in Chapter 3, Reading and Writing Data in Kernel Memory show the close(9E) routine in more detail.
The close(9E) routine must undo everything that the open(9E) routine did. The close(9E) routine must deallocate anything that was allocated, close anything that was opened, and destroy anything that was created in the open(9E) routine. In this dummy driver, the open(9E) routine is so simple that nothing needs to be reclaimed or undone in the close(9E) routine.
The following code is the dummy_close() routine that you should enter into your dummy.c file. You can copy the name portion of this function definition directly from the declaration you entered in Declaring the User Context Entry Points. Write a message to the system log and return success.
static int dummy_close(dev_t dev, int flag, int otyp, cred_t *cred) { cmn_err(CE_NOTE, "Inside dummy_close"); return DDI_SUCCESS; }
The read(9E) routine returns type int. The read(9E) routine should return either DDI_SUCCESS or the appropriate error number.
The read(9E) routine takes three arguments. This dummy driver is so simple that this dummy_read() routine does not use any of the read(9E) arguments. The examples in Chapter 3, Reading and Writing Data in Kernel Memory show the read(9E) routine in more detail.
The following code is the dummy_read() routine that you should enter into your dummy.c file. You can copy the name portion of this function definition directly from the declaration you entered in Declaring the User Context Entry Points. Write a message to the system log and return success.
static int dummy_read(dev_t dev, struct uio *uiop, cred_t *credp) { cmn_err(CE_NOTE, "Inside dummy_read"); return DDI_SUCCESS; }
The write(9E) routine returns type int. The write(9E) routine should return either DDI_SUCCESS or the appropriate error number.
The write(9E) routine takes three arguments. This dummy driver is so simple that this dummy_write() routine does not use any of the write(9E) arguments. The examples in Chapter 3, Reading and Writing Data in Kernel Memory show the write(9E) routine in more detail.
The following code is the dummy_write() routine that you should enter into your dummy.c file. You can copy the name portion of this function definition directly from the declaration you entered in Declaring the User Context Entry Points. Write a message to the system log and return success.
static int dummy_write(dev_t dev, struct uio *uiop, cred_t *credp) { cmn_err(CE_NOTE, "Inside dummy_write"); return DDI_SUCCESS; }
The four user context entry point routines require your module to include several header files. You already have included the types.h header file, the ddi.h header file, and the sunddi.h header file. You need to include the file.h, errno.h, open.h, cred.h, and uio.h header files.
The following code is the list of header files that you now should have included in your dummy.c file for all the entry points you have written in this section and the previous two sections:
#include <sys/modctl.h> /* used by modlinkage, modldrv, _init, _info, */ /* and _fini */ #include <sys/types.h> /* used by open, close, read, write, prop_op, */ /* and ddi_prop_op */ #include <sys/file.h> /* used by open, close */ #include <sys/errno.h> /* used by open, close, read, write */ #include <sys/open.h> /* used by open, close, read, write */ #include <sys/cred.h> /* used by open, close, read */ #include <sys/uio.h> /* used by read */ #include <sys/stat.h> /* defines S_IFCHR used by ddi_create_minor_node */ #include <sys/cmn_err.h> /* used by all entry points for this driver */ #include <sys/ddi.h> /* used by all entry points for this driver */ /* also used by ddi_get_instance and */ /* ddi_prop_op */ #include <sys/sunddi.h> /* used by all entry points for this driver */ /* also used by ddi_create_minor_node, */ /* ddi_get_instance, and ddi_prop_op */
All of the data structures described in this section are required for every device driver. All drivers must define a dev_ops(9S) device operations structure. Because the dev_ops(9S) structure includes a pointer to the cb_ops(9S) character and block operations structure, you must define the cb_ops(9S) structure first. The modldrv(9S) linkage structure for loadable drivers includes a pointer to the dev_ops(9S) structure. The modlinkage(9S) module linkage structure includes a pointer to the modldrv(9S) structure.
Except for the loadable module configuration entry points, all of the required entry points for a driver are initialized in the character and block operations structure or in the device operations structure. Some optional entry points and other related data also are initialized in these data structures. Initializing the entry points in these data structures enables the driver to be dynamically loaded.
The loadable module configuration entry points are not initialized in driver data structures. The _init(9E), _info(9E), and _fini(9E) entry points are required for all kernel modules and are not specific to device driver modules.
In this section, the following code is added:
/* cb_ops structure */ static struct cb_ops dummy_cb_ops = { dummy_open, dummy_close, nodev, /* no strategy - nodev returns ENXIO */ nodev, /* no print */ nodev, /* no dump */ dummy_read, dummy_write, nodev, /* no ioctl */ nodev, /* no devmap */ nodev, /* no mmap */ nodev, /* no segmap */ nochpoll, /* returns ENXIO for non-pollable devices */ dummy_prop_op, NULL, /* streamtab struct; if not NULL, all above */ /* fields are ignored */ D_NEW | D_MP, /* compatibility flags: see conf.h */ CB_REV, /* cb_ops revision number */ nodev, /* no aread */ nodev /* no awrite */ }; /* dev_ops structure */ static struct dev_ops dummy_dev_ops = { DEVO_REV, 0, /* reference count */ dummy_getinfo, /* no getinfo(9E) */ nulldev, /* no identify(9E) - nulldev returns 0 */ nulldev, /* no probe(9E) */ dummy_attach, dummy_detach, nodev, /* no reset - nodev returns ENXIO */ &dummy_cb_ops, (struct bus_ops *)NULL, nodev, /* no power(9E) */ ddi_quiesce_not_needed, /* no quiesce(9E) */ }; /* modldrv structure */ static struct modldrv md = { &mod_driverops, /* Type of module. This is a driver. */ "dummy driver", /* Name of the module. */ &dummy_dev_ops }; /* modlinkage structure */ static struct modlinkage ml = { MODREV_1, &md, NULL }; /* dev_info structure */ dev_info_t *dummy_dip; /* keep track of one instance */
The cb_ops(9S) structure initializes standard character and block interfaces. See the cb_ops(9S) man page to learn what each element is and what the value of each element should be. This dummy driver does not use all of the elements in the cb_ops(9S) structure. See the description that follows the code sample.
When you name this structure, use the same dummy_ prefix that you used for the names of the autoconfiguration routines and the names of the user context routines. Prepend the static type modifier to the declaration.
The following code is the cb_ops(9S) structure that you should enter into your dummy.c file:
static struct cb_ops dummy_cb_ops = { dummy_open, dummy_close, nodev, /* no strategy - nodev returns ENXIO */ nodev, /* no print */ nodev, /* no dump */ dummy_read, dummy_write, nodev, /* no ioctl */ nodev, /* no devmap */ nodev, /* no mmap */ nodev, /* no segmap */ nochpoll, /* returns ENXIO for non-pollable devices */ dummy_prop_op, NULL, /* streamtab struct; if not NULL, all above */ /* fields are ignored */ D_NEW | D_MP, /* compatibility flags: see conf.h */ CB_REV, /* cb_ops revision number */ nodev, /* no aread */ nodev /* no awrite */ };
Enter the names of the open(9E) and close(9E) entry points for this driver as the values of the first two elements of this structure. Enter the names of the read(9E) and write(9E) entry points for this driver as the values of the sixth and seventh elements of this structure. Enter the name of the prop_op(9E) entry point for this driver as the value of the thirteenth element in this structure.
The strategy(9E), print(9E), and dump(9E) routines are for block drivers only. This dummy driver does not define these three routines because this driver is a character driver. This driver does not define an ioctl(9E) entry point because this driver does not use I/O control commands. This driver does not define devmap(9E), mmap(9E), or segmap(9E) entry points because this driver does not support memory mapping. This driver does not does not define aread(9E) or awrite(9E) entry points because this driver does not perform any asynchronous reads or writes. Initialize all of these unused function elements to nodev(9F). The nodev(9F) function returns the ENXIO error code.
Specify the nochpoll(9F) function for the chpoll(9E) element of the cb_ops(9S) structure because this driver is not for a pollable device. Specify NULL for the streamtab(9S) STREAMS entity declaration structure because this driver is not a STREAMS driver.
The compatibility flags are defined in the conf.h header file. The D_NEW flag means this driver is a new-style driver. The D_MP flag means this driver safely allows multiple threads of execution. All drivers must be multithreaded-safe, and must specify this D_MP flag. The D_64BIT flag means this driver supports 64-bit offsets and block numbers. See the conf.h header file for more compatibility flags.
The CB_REV element of the cb_ops(9S) structure is the cb_ops(9S) revision number. CB_REV is defined in the devops.h header file.
The dev_ops(9S) structure initializes interfaces that are used for operations such as attaching and detaching the driver. See the dev_ops(9S) man page to learn what each element is and what the value of each element should be. This dummy driver does not use all of the elements in the dev_ops(9S) structure. See the description that follows the code sample.
When you name this structure, use the same dummy_ prefix that you used for the names of the autoconfiguration routines and the names of the user context routines. Prepend the static type modifier to the declaration.
The following code is the dev_ops(9S) structure that you should enter into your dummy.c file:
static struct dev_ops dummy_dev_ops = { DEVO_REV, 0, /* reference count */ dummy_getinfo, /* no getinfo(9E) */ nulldev, /* no identify(9E) - nulldev returns 0 */ nulldev, /* no probe(9E) */ dummy_attach, dummy_detach, nodev, /* no reset - nodev returns ENXIO */ &dummy_cb_ops, (struct bus_ops *)NULL, nodev, /* no power(9E) */ ddi_quiesce_not_needed, /* no quiesce(9E) */ };
The DEVO_REV element of the dev_ops(9S) structure is the driver build version. DEVO_REV is defined in the devops.h header file. The second element in this structure is the driver reference count. Initialize this value to zero. The driver reference count is the number of instances of this driver that are currently open. The driver cannot be unloaded if any instances of the driver are still open.
The next six elements of the dev_ops(9S) structure are the names of the getinfo(9E), identify(9E), probe(9E), attach(9E), detach(9E), and reset() functions for this particular driver. The identify(9E) function is obsolete. Initialize this structure element to nulldev(9F). The probe(9E) function determines whether the corresponding device exists and is valid. This dummy driver does not define a probe(9E) function. Initialize this structure element to nulldev. The nulldev(9F) function returns success. The reset() function is obsolete. Initialize the reset() function to nodev(9F).
The next element of the dev_ops(9S) structure is a pointer to the cb_ops(9S) structure for this driver. You initialized the cb_ops(9S) structure for this driver in Defining the Character and Block Operations Structure. Enter &dummy_cb_ops for the value of the pointer to the cb_ops(9S) structure.
The next element of the dev_ops(9S) structure is a pointer to the bus operations structure. Only nexus drivers have bus operations structures. This dummy driver is not a nexus driver. Set this value to NULL because this driver is a leaf driver.
The next element of the dev_ops(9S) structure is the name of the power(9E) routine for this driver. The power(9E) routine operates on a hardware device. This driver does not drive a hardware device. Set the value of this structure element to nodev.
The last element of the dev_ops(9S) structure is the name of the quiesce(9E) routine for this driver. The quiesce(9E) routine operates on a hardware device. This driver does not drive a hardware device. Set the value of this structure element to ddi_quiesce_not_needed()(9F).
Two other module loading structures are required for every driver. The modlinkage(9S) module linkage structure is used by the _init(9E), _info(9E), and _fini(9E) routines to install, remove, and retrieve information from a module. The modldrv(9S) linkage structure for loadable drivers exports driver-specific information to the kernel. See the man pages for each structure to learn what each element is and what the value of each element should be.
The following code defines the modldrv(9S) and modlinkage(9S) structures for the driver shown in this chapter:
static struct modldrv md = { &mod_driverops, /* Type of module. This is a driver. */ "dummy driver", /* Name of the module. */ &dummy_dev_ops }; static struct modlinkage ml = { MODREV_1, &md, NULL };
The first element in the modldrv(9S) structure is a pointer to a structure that tells the kernel what kind of module this is. Set this value to the address of the mod_driverops structure. The mod_driverops structure tells the kernel that the dummy.c module is a loadable driver module. The mod_driverops structure is declared in the modctl.h header file. You already included the modctl.h header file in your dummy.c file, so do not declare the mod_driverops structure in dummy.c. The mod_driverops structure is defined in the modctl.c source file.
The second element in the modldrv(9S) structure is a string that describes this module. Usually this string contains the name of this module and the version number of this module. The last element of the modldrv(9S) structure is a pointer to the dev_ops(9S) structure for this driver. You initialized the dev_ops(9S) structure for this driver in Defining the Device Operations Structure.
The first element in the modlinkage(9S) structure is the revision number of the loadable modules system. Set this value to MODREV_1. The next element of the modlinkage(9S) structure is the address of a null-terminated array of pointers to linkage structures. Driver modules have only one linkage structure. Enter the address of the md structure for the value of this element of the modlinkage(9S) structure. Enter the value NULL to terminate this list of linkage structures.
The cb_ops(9S) and dev_ops(9S) structures require you to include the conf.h and devops.h header files. The modlinkage(9S) and modldrv(9S) structures require you to include the modctl.h header file. You already included the modctl.h header file for the loadable module configuration entry points.
The following code is the complete list of header files that you now should have included in your dummy.c file:
#include <sys/devops.h> /* used by dev_ops */ #include <sys/conf.h> /* used by dev_ops and cb_ops */ #include <sys/modctl.h> /* used by modlinkage, modldrv, _init, _info, */ /* and _fini */ #include <sys/types.h> /* used by open, close, read, write, prop_op, */ /* and ddi_prop_op */ #include <sys/file.h> /* used by open, close */ #include <sys/errno.h> /* used by open, close, read, write */ #include <sys/open.h> /* used by open, close, read, write */ #include <sys/cred.h> /* used by open, close, read */ #include <sys/uio.h> /* used by read */ #include <sys/stat.h> /* defines S_IFCHR used by ddi_create_minor_node */ #include <sys/cmn_err.h> /* used by all entry points for this driver */ #include <sys/ddi.h> /* used by all entry points for this driver */ /* also used by cb_ops, ddi_get_instance, and */ /* ddi_prop_op */ #include <sys/sunddi.h> /* used by all entry points for this driver */ /* also used by cb_ops, ddi_create_minor_node, */ /* ddi_get_instance, and ddi_prop_op */
This driver requires a configuration file. The minimum information that a configuration file must contain is the name of the device node and the name or type of the device's parent. In this simple example, the node name of the device is the same as the file name of the driver. Create a file named dummy.conf in your working directory. Put the following single line of information into dummy.conf:
name="dummy" parent="pseudo";
This section shows you how to build and install the driver for a 32-bit platform. See Building a Driver and Installing a Driver for build and install instructions for SPARC architectures and for 64-bit x86 architectures.
Compile and link the driver. Use the -D_KERNEL option to indicate that this code defines a kernel module. The following example shows compiling and linking for a 32-bit architecture using the Oracle Solaris Studio C compiler:
% cc -D_KERNEL -c dummy.c % ld -r -o dummy dummy.o |
Make sure you are user root when you install the driver.
Install drivers in the /tmp directory until you are finished modifying and testing the _info(), _init(), and attach() routines. Copy the driver binary to the /tmp directory. Link to the driver from the kernel driver directory. See Device Driver Testing Tips for more information.
# cp dummy /tmp |
Link to the following directory for a 32-bit architecture:
# ln -s /tmp/dummy /usr/kernel/drv/dummy |
Copy the configuration file to the kernel driver area of the system.
# cp dummy.conf /usr/kernel/drv |
This dummy driver merely writes a message to a system log each time an entry point routine is entered. To test this driver, watch for these messages to confirm that each entry point routine is successfully entered.
The cmn_err(9F) function writes low priority messages such as the messages defined in this dummy driver to /dev/log. The syslogd(1M) daemon reads messages from /dev/log and writes low priority messages to /var/adm/messages.
In a separate window, enter the following command and monitor the output as you perform the tests described in the remainder of this section:
% tail -f /var/adm/messages |
Make sure you are user root when you add the driver. Use the add_drv(1M) command to add the driver:
# add_drv dummy |
You should see the following messages in the window where you are viewing /var/adm/messages:
date time machine dummy: [ID 513080 kern.notice] NOTICE: Inside _info date time machine dummy: [ID 874762 kern.notice] NOTICE: Inside _init date time machine dummy: [ID 678704 kern.notice] NOTICE: Inside dummy_attach |
The _info(9E), _init(9E), and attach(9E) entry points are called in that order when you add a driver.
The dummy driver has been added to the /devices directory:
% ls -l /devices/pseudo | grep dummy drwxr-xr-x 2 root sys 512 date time dummy@0 crw------- 1 root sys 92, 0 date time dummy@0:0 |
The dummy driver also is the most recent module listed by modinfo(1M):
% modinfo Id Loadaddr Size Info Rev Module Name 180 ed192b70 544 92 1 dummy (dummy driver) |
The module name, dummy driver, is the value you entered for the second member of the modldrv(9S) structure. The value 92 is the major number of this module.
% grep dummy /etc/name_to_major dummy 92 |
The Loadaddr address of ed192b70 is the address of the first instruction in the dummy driver. This address might be useful, for example, in debugging.
% mdb -k > dummy`_init $m BASE LIMIT SIZE NAME ed192b70 ed192ff0 480 dummy > $q |
The dummy driver also is the most recent module listed by prtconf(1M) in the pseudo device section:
% prtconf -P pseudo, instance #0 dummy, instance #0 (driver not attached) |
A driver is automatically loaded when a device that the driver manages is accessed. A driver might be automatically unloaded when the driver is not in use.
If your driver is in the /devices directory but modinfo(1M) does not list your driver, you can use either of the following methods to load your driver:
Use the modload(1M) command.
Access the device. The driver is loaded automatically when a device that the driver manages is accessed. The following section describes how to access the dummy device.
Make sure you are user root when you perform the tests described in this section. If you are not user root, you will receive “Permission denied” error messages when you try to access the /devices/pseudo/dummy@0:0 special file. Notice the permissions that are shown for /devices/pseudo/dummy@0:0 in Adding the Template Driver.
Test reading from the device. Your dummy device probably is named /devices/pseudo/dummy@0:0. The following command reads from your dummy device even if it has a slightly different name:
# cat /devices/pseudo/dummy* |
You should see the following messages in the window where you are viewing /var/adm/messages:
date time machine dummy: [ID 136952 kern.notice] NOTICE: Inside dummy_open date time machine dummy: [ID 623947 kern.notice] NOTICE: Inside dummy_getinfo date time machine dummy: [ID 891851 kern.notice] NOTICE: Inside dummy_prop_op date time machine dummy: [ID 623947 kern.notice] NOTICE: Inside dummy_getinfo date time machine dummy: [ID 891851 kern.notice] NOTICE: Inside dummy_prop_op date time machine dummy: [ID 623947 kern.notice] NOTICE: Inside dummy_getinfo date time machine dummy: [ID 709590 kern.notice] NOTICE: Inside dummy_read date time machine dummy: [ID 550206 kern.notice] NOTICE: Inside dummy_close |
# echo hello > `ls /devices/pseudo/dummy*` |
You should see the following messages in the window where you are viewing /var/adm/messages:
date time machine dummy: [ID 136952 kern.notice] NOTICE: Inside dummy_open date time machine dummy: [ID 623947 kern.notice] NOTICE: Inside dummy_getinfo date time machine dummy: [ID 891851 kern.notice] NOTICE: Inside dummy_prop_op date time machine dummy: [ID 623947 kern.notice] NOTICE: Inside dummy_getinfo date time machine dummy: [ID 891851 kern.notice] NOTICE: Inside dummy_prop_op date time machine dummy: [ID 623947 kern.notice] NOTICE: Inside dummy_getinfo date time machine dummy: [ID 672780 kern.notice] NOTICE: Inside dummy_write date time machine dummy: [ID 550206 kern.notice] NOTICE: Inside dummy_close |
As you can see, this output from the write test is almost identical to the output you saw from the read test. The only difference is in the seventh line of the output. Using the cat(1) command causes the kernel to access the read(9E) entry point of the driver. Using the echo(1) command causes the kernel to access the write(9E) entry point of the driver. The text argument that you give to echo(1) is ignored because this driver does not do anything with that data.
Make sure you are user root when you unload the driver. Use the rem_drv(1M) command to unload the driver and remove the device from the /devices directory:
# rem_drv dummy |
You should see the following messages in the window where you are viewing /var/adm/messages:
date time machine dummy: [ID 513080 kern.notice] NOTICE: Inside _info date time machine dummy: [ID 617648 kern.notice] NOTICE: Inside dummy_detach date time machine dummy: [ID 812373 kern.notice] NOTICE: Inside _fini |
The dummy device is no longer in the /devices directory:
# ls /devices/pseudo/dummy* /devices/pseudo/dummy*: No such file or directory |
The next time you want to read from or write to the dummy device, you must load the driver again using add_drv(1M).
You can use the modunload(1M) command to unload the driver but not remove the device from /devices. Then the next time you read from or write to the dummy device, the driver is automatically loaded.
Press Control-C to stop tailing the /var/adm/messages messages.
The following code is the complete source for the dummy driver described in this chapter:
/* * Minimalist pseudo-device. * Writes a message whenever a routine is entered. * * Build the driver: * cc -D_KERNEL -c dummy.c * ld -r -o dummy dummy.o * Copy the driver and the configuration file to /usr/kernel/drv: * cp dummy.conf /usr/kernel/drv * cp dummy /tmp * ln -s /tmp/dummy /usr/kernel/drv/dummy * Add the driver: * add_drv dummy * Test (1) read from driver (2) write to driver: * cat /devices/pseudo/dummy@* * echo hello > `ls /devices/pseudo/dummy@*` * Verify the tests in another window: * tail -f /var/adm/messages * Remove the driver: * rem_drv dummy */ #include <sys/devops.h> /* used by dev_ops */ #include <sys/conf.h> /* used by dev_ops and cb_ops */ #include <sys/modctl.h> /* used by modlinkage, modldrv, _init, _info, */ /* and _fini */ #include <sys/types.h> /* used by open, close, read, write, prop_op, */ /* and ddi_prop_op */ #include <sys/file.h> /* used by open, close */ #include <sys/errno.h> /* used by open, close, read, write */ #include <sys/open.h> /* used by open, close, read, write */ #include <sys/cred.h> /* used by open, close, read */ #include <sys/uio.h> /* used by read */ #include <sys/stat.h> /* defines S_IFCHR used by ddi_create_minor_node */ #include <sys/cmn_err.h> /* used by all entry points for this driver */ #include <sys/ddi.h> /* used by all entry points for this driver */ /* also used by cb_ops, ddi_get_instance, and */ /* ddi_prop_op */ #include <sys/sunddi.h> /* used by all entry points for this driver */ /* also used by cb_ops, ddi_create_minor_node, */ /* ddi_get_instance, and ddi_prop_op */ static int dummy_attach(dev_info_t *dip, ddi_attach_cmd_t cmd); static int dummy_detach(dev_info_t *dip, ddi_detach_cmd_t cmd); static int dummy_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp); static int dummy_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op, int flags, char *name, caddr_t valuep, int *lengthp); static int dummy_open(dev_t *devp, int flag, int otyp, cred_t *cred); static int dummy_close(dev_t dev, int flag, int otyp, cred_t *cred); static int dummy_read(dev_t dev, struct uio *uiop, cred_t *credp); static int dummy_write(dev_t dev, struct uio *uiop, cred_t *credp); /* cb_ops structure */ static struct cb_ops dummy_cb_ops = { dummy_open, dummy_close, nodev, /* no strategy - nodev returns ENXIO */ nodev, /* no print */ nodev, /* no dump */ dummy_read, dummy_write, nodev, /* no ioctl */ nodev, /* no devmap */ nodev, /* no mmap */ nodev, /* no segmap */ nochpoll, /* returns ENXIO for non-pollable devices */ dummy_prop_op, NULL, /* streamtab struct; if not NULL, all above */ /* fields are ignored */ D_NEW | D_MP, /* compatibility flags: see conf.h */ CB_REV, /* cb_ops revision number */ nodev, /* no aread */ nodev /* no awrite */ }; /* dev_ops structure */ static struct dev_ops dummy_dev_ops = { DEVO_REV, 0, /* reference count */ dummy_getinfo, /* no getinfo(9E) */ nulldev, /* no identify(9E) - nulldev returns 0 */ nulldev, /* no probe(9E) */ dummy_attach, dummy_detach, nodev, /* no reset - nodev returns ENXIO */ &dummy_cb_ops, (struct bus_ops *)NULL, nodev, /* no power(9E) */ ddi_quiesce_not_needed, /* no quiesce(9E) */ }; /* modldrv structure */ static struct modldrv md = { &mod_driverops, /* Type of module. This is a driver. */ "dummy driver", /* Name of the module. */ &dummy_dev_ops }; /* modlinkage structure */ static struct modlinkage ml = { MODREV_1, &md, NULL }; /* dev_info structure */ dev_info_t *dummy_dip; /* keep track of one instance */ /* Loadable module configuration entry points */ int _init(void) { cmn_err(CE_NOTE, "Inside _init"); return(mod_install(&ml)); } int _info(struct modinfo *modinfop) { cmn_err(CE_NOTE, "Inside _info"); return(mod_info(&ml, modinfop)); } int _fini(void) { cmn_err(CE_NOTE, "Inside _fini"); return(mod_remove(&ml)); } /* Device configuration entry points */ static int dummy_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { cmn_err(CE_NOTE, "Inside dummy_attach"); switch(cmd) { case DDI_ATTACH: dummy_dip = dip; if (ddi_create_minor_node(dip, "0", S_IFCHR, ddi_get_instance(dip), DDI_PSEUDO,0) != DDI_SUCCESS) { cmn_err(CE_NOTE, "%s%d: attach: could not add character node.", "dummy", 0); return(DDI_FAILURE); } else return DDI_SUCCESS; default: return DDI_FAILURE; } } static int dummy_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { cmn_err(CE_NOTE, "Inside dummy_detach"); switch(cmd) { case DDI_DETACH: dummy_dip = 0; ddi_remove_minor_node(dip, NULL); return DDI_SUCCESS; default: return DDI_FAILURE; } } static int dummy_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp) { cmn_err(CE_NOTE, "Inside dummy_getinfo"); switch(cmd) { case DDI_INFO_DEVT2DEVINFO: *resultp = dummy_dip; return DDI_SUCCESS; case DDI_INFO_DEVT2INSTANCE: *resultp = 0; return DDI_SUCCESS; default: return DDI_FAILURE; } } /* Main entry points */ static int dummy_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op, int flags, char *name, caddr_t valuep, int *lengthp) { cmn_err(CE_NOTE, "Inside dummy_prop_op"); return(ddi_prop_op(dev,dip,prop_op,flags,name,valuep,lengthp)); } static int dummy_open(dev_t *devp, int flag, int otyp, cred_t *cred) { cmn_err(CE_NOTE, "Inside dummy_open"); return DDI_SUCCESS; } static int dummy_close(dev_t dev, int flag, int otyp, cred_t *cred) { cmn_err(CE_NOTE, "Inside dummy_close"); return DDI_SUCCESS; } static int dummy_read(dev_t dev, struct uio *uiop, cred_t *credp) { cmn_err(CE_NOTE, "Inside dummy_read"); return DDI_SUCCESS; } static int dummy_write(dev_t dev, struct uio *uiop, cred_t *credp) { cmn_err(CE_NOTE, "Inside dummy_write"); return DDI_SUCCESS; }