Device Configuration Concepts
For each node in the kernel device tree, the system selects a
driver for the node based on the node name and the compatible property (see
Binding a Driver to a Device). The same driver might bind to multiple device nodes. The driver can
differentiate different nodes by instance numbers assigned by the system.
After a driver is selected for a device node, the driver's probe(9E)
entry point is called to determine the presence of the device on the
system. If probe() is successful, the driver's attach(9E) entry point is invoked to set
up and manage the device. The device can be opened if and only
if attach() returns success (see attach() Entry Point).
A device might be unconfigured to conserve system memory resources or to enable
the device to be removed while the system is still running. To
enable the device to be unconfigured, the system first checks whether the device instance
is referenced. This check involves calling the driver's getinfo(9E) entry point to
obtain information known only to the driver (see getinfo() Entry Point). If the device instance
is not referenced, the driver's detach(9E) routine is invoked to unconfigure the device
(see detach() Entry Point).
To recap, each driver must define the following entry points that are used
by the kernel for device configuration:
Note that attach(), detach(), and getinfo() are required. probe() is only required for devices
that cannot identify themselves. For self-identifying devices, an explicit probe() routine can be
provided, or nulldev(9F) can be specified in the dev_ops structure for the
probe() entry point.
Device Instances and Instance Numbers
The system assigns an instance number to each device. The driver might not
reliably predict the value of the instance number assigned to a particular device.
The driver should retrieve the particular instance number that has been assigned by
calling ddi_get_instance(9F).
Instance numbers represent the system's notion of devices. Each dev_info, that is,
each node in the device tree, for a particular driver is assigned an
instance number by the kernel. Furthermore, instance numbers provide a convenient mechanism for
indexing data specific to a particular physical device. The most common use of
instance numbers is ddi_get_soft_state(9F), which uses instance numbers to retrieve soft state data
for specific physical devices.
Caution - For pseudo devices, that is, the children of pseudo nexuses, the instance numbers
are defined in the driver.conf(4) file using the instance property. If the
driver.conf file does not contain the instance property, the behavior is undefined.
For hardware device nodes, the system assigns instance numbers when the device is
first seen by the OS. The instance numbers persist across system reboots and
OS upgrades.
Minor Nodes and Minor Numbers
Drivers are responsible for managing their minor number namespace. For example, the sd
driver needs to export eight character minor nodes and eight block minor nodes
to the file system for each disk. Each minor node represents either a
block interface or a character interface to a portion of the disk. The
getinfo(9E) entry point informs the system about the mapping from minor number to
device instance (see getinfo() Entry Point).
probe() Entry Point
For non-self-identifying devices, the probe(9E) entry point should determine whether the hardware device
is present on the system.
For probe() to determine whether the instance of the device is present, probe()
needs to perform many tasks that are also commonly done by attach(9E). In particular,
probe() might need to map the device registers.
Probing the device registers is device-specific. The driver often has to perform a
series of tests of the hardware to assure that the hardware is really
present. The test criteria must be rigorous enough to avoid misidentifying devices. For
example, a device might appear to be present when in fact that device
is not available, because a different device seems to behave like the expected
device.
The test returns the following flags:
DDI_PROBE_SUCCESS if the probe was successful
DDI_PROBE_FAILURE if the probe failed
DDI_PROBE_DONTCARE if the probe was unsuccessful yet attach(9E) still needs to be called
DDI_PROBE_PARTIAL if the instance is not present now, but might be present in the future
For a given device instance, attach(9E) will not be called until probe(9E)
has succeeded at least once on that device.
probe(9E) must free all the resources that probe() has allocated, because probe() might
be called multiple times. However, attach(9E) is not necessarily called even if
probe(9E) has succeeded
ddi_dev_is_sid(9F) can be used in a driver's probe(9E) routine to determine whether the
device is self-identifying. ddi_dev_is_sid() is useful in drivers written for self-identifying and non-self-identifying
versions of the same device.
The following example is a sample probe() routine.
Example 6-3 probe(9E) Routine
static int
xxprobe(dev_info_t *dip)
{
ddi_acc_handle_t dev_hdl;
ddi_device_acc_attr_t dev_attr;
Pio_csr *csrp;
uint8_t csrval;
/*
* if the device is self identifying, no need to probe
*/
if (ddi_dev_is_sid(dip) == DDI_SUCCESS)
return (DDI_PROBE_DONTCARE);
/*
* Initalize the device access attributes and map in
* the devices CSR register (register 0)
*/
dev_attr.devacc_attr_version = DDI_DEVICE_ATTR_V0;
dev_attr.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC;
dev_attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC;
if (ddi_regs_map_setup(dip, 0, (caddr_t *)&csrp, 0, sizeof (Pio_csr),
&dev_attr, &dev_hdl) != DDI_SUCCESS)
return (DDI_PROBE_FAILURE);
/*
* Reset the device
* Once the reset completes the CSR should read back
* (PIO_DEV_READY | PIO_IDLE_INTR)
*/
ddi_put8(dev_hdl, csrp, PIO_RESET);
csrval = ddi_get8(dev_hdl, csrp);
/*
* tear down the mappings and return probe success/failure
*/
ddi_regs_map_free(&dev_hdl);
if ((csrval & 0xff) == (PIO_DEV_READY | PIO_IDLE_INTR))
return (DDI_PROBE_SUCCESS);
else
return (DDI_PROBE_FAILURE);
}
When the driver's probe(9E) routine is called, the driver does not know
whether the device being probed exists on the bus. Therefore, the driver might
attempt to access device registers for a nonexistent device. A bus fault might
be generated on some buses as a result.
The following example shows a probe(9E) routine that uses ddi_poke8(9F) to
check for the existence of the device. ddi_poke8() cautiously attempts to write a value
to a specified virtual address, using the parent nexus driver to assist in
the process where necessary. If the address is not valid or the value
cannot be written without an error occurring, an error code is returned. See
also ddi_peek(9F).
In this example, ddi_regs_map_setup(9F) is used to map the device registers.
Example 6-4 probe(9E) Routine Using ddi_poke8(9F)
static int
xxprobe(dev_info_t *dip)
{
ddi_acc_handle_t dev_hdl;
ddi_device_acc_attr_t dev_attr;
Pio_csr *csrp;
uint8_t csrval;
/*
* if the device is self-identifying, no need to probe
*/
if (ddi_dev_is_sid(dip) == DDI_SUCCESS)
return (DDI_PROBE_DONTCARE);
/*
* Initialize the device access attrributes and map in
* the device's CSR register (register 0)
*/
dev_attr.devacc_attr_version - DDI_DEVICE_ATTR_V0;
dev_attr.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC;
dev_attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC;
if (ddi_regs_map_setup(dip, 0, (caddr_t *)&csrp, 0, sizeof (Pio_csr),
&dev_attr, &dev_hdl) != DDI_SUCCESS)
return (DDI_PROBE_FAILURE);
/*
* The bus can generate a fault when probing for devices that
* do not exist. Use ddi_poke8(9f) to handle any faults that
* might occur.
*
* Reset the device. Once the reset completes the CSR should read
* back (PIO_DEV_READY | PIO_IDLE_INTR)
*/
if (ddi_poke8(dip, csrp, PIO_RESET) != DDI_SUCCESS) {
ddi_regs_map_free(&dev_hdl);
return (DDI_FAILURE);
csrval = ddi_get8(dev_hdl, csrp);
/*
* tear down the mappings and return probe success/failure
*/
ddi_regs_map_free(&dev_hdl);
if ((csrval & 0xff) == (PIO_DEV_READY | PIO_IDLE_INTR))
return (DDI_PROBE_SUCCESS);
else
return (DDI_PROBE_FAILURE);
}
attach() Entry Point
The kernel calls a driver's attach(9E) entry point to attach an instance of a
device or to resume operation for an instance of a device that has
been suspended or has been shut down by the power management framework.
This section discusses only the operation of attaching device instances. Power
management is discussed in Chapter 12, Power Management.
A driver's attach(9E) entry point is called to attach each instance of a
device that is bound to the driver. The entry point is called
with the instance of the device node to attach, with DDI_ATTACH specified as
the cmd argument to attach(9E). The attach entry point typically includes the following
types of processing:
Allocating a soft-state structure for the device instance
Initializing per-instance mutexes
Initializing condition variables
Registering the device's interrupts
Mapping the registers and memory of the device instance
Creating minor device nodes for the device instance
Reporting that the device instance has attached
Driver Soft-State Management
To assist device driver writers in allocating state structures, the Solaris DDI/DKI provides a
set of memory management routines called software state management routines, which are also known as
the soft-state routines. These routines dynamically allocate, retrieve, and destroy memory items of a specified
size, and hide the details of list management. An instance number identifies the
desired memory item. This number is typically the instance number assigned by the
system.
Drivers typically allocate a soft-state structure for each device instance that attaches to
the driver by calling ddi_soft_state_zalloc(9F), passing the instance number of the device.
Because no two device nodes can have the same instance number, ddi_soft_state_zalloc(9F) fails
if an allocation already exists for a given instance number.
A driver's character or block entry point (cb_ops(9S)) references a particular soft state
structure by first decoding the device's instance number from the dev_t argument
that is passed to the entry point function. The driver then calls ddi_get_soft_state(9F),
passing the per-driver soft-state list and the instance number that was derived. A
NULL return value indicates that effectively the device does not exist and the
appropriate code should be returned by the driver.
See Creating Minor Device Nodes for additional information on how instance numbers and device numbers, or
dev_t's, are related.
Lock Variable and Conditional Variable Initialization
Drivers should initialize any per-instance locks and condition variables during attach. The
initialization of any locks that are acquired by the driver's interrupt handler must
be initialized prior to adding any interrupt handlers. See Chapter 3, Multithreading for a
description of lock initialization and usage. See Chapter 8, Interrupt Handlers for a discussion of
interrupt handler and lock issues.
Creating Minor Device Nodes
An important part of the attach process is the creation of minor nodes
for the device instance. A minor node contains the information exported by
the device and the DDI framework. The system uses this information to create
a special file for the minor node under /devices.
Minor nodes are created when the driver calls ddi_create_minor_node(9F). The driver supplies a
minor number, a minor name, a minor node type, and whether the minor node represents a block or
character device.
Drivers can create any number of minor nodes for a device. The
Solaris DDI/DKI expects certain classes of devices to have minor nodes created in a
particular format. For example, disk drivers are expected to create 16 minor
nodes for each physical disk instance attached. Eight minor nodes are created, representing
the a - h block device interfaces, with an additional eight minor nodes for the
a,raw - h,raw character device interfaces.
The minor number passed to ddi_create_minor_node(9F) is defined wholly by the driver. The minor
number is usually an encoding of the instance number of the device with
a minor node identifier. In the preceding example, the driver creates minor numbers
for each of the minor nodes by shifting the instance number of the
device left by three bits and using the OR of that result with
the minor node index. The values of the minor node index range from
0 to 7. Note that minor nodes a and a,raw share the same
minor number. These minor nodes are distinguished by the spec_type argument passed
to ddi_create_minor_node().
The minor node type passed to ddi_create_minor_node(9F) classifies the type of device, such as
disks, tapes, network interfaces, frame buffers, and so forth.
The following table lists the types of possible nodes that might be created.
Table 6-1 Possible Node Types
Constant |
Description |
DDI_NT_SERIAL |
Serial
port |
DDI_NT_SERIAL_DO |
Dialout ports |
DDI_NT_BLOCK |
Hard disks |
DDI_NT_BLOCK_CHAN |
Hard disks with channel or target numbers |
DDI_NT_CD |
ROM drives (CD-ROM) |
DDI_NT_CD_CHAN |
ROM drives
with channel or target numbers |
DDI_NT_FD |
Floppy disks |
DDI_NT_TAPE |
Tape drives |
DDI_NT_NET |
Network devices |
DDI_NT_DISPLAY |
Display devices |
DDI_NT_MOUSE |
Mouse |
DDI_NT_KEYBOARD |
Keyboard |
DDI_NT_AUDIO |
Audio Device |
DDI_PSEUDO |
General pseudo devices |
The node types DDI_NT_BLOCK, DDI_NT_BLOCK_CHAN, DDI_NT_CD, and DDI_NT_CD_CHAN cause devfsadm(1M) to identify the
device instance as a disk and to create names in the /dev/dsk
or /dev/rdsk directory.
The node type DDI_NT_TAPE causes devfsadm(1M) to identify the device instance as a
tape and to create names in the /dev/rmt directory.
The node types DDI_NT_SERIAL and DDI_NT_SERIAL_DO cause devfsadm(1M) to perform these actions:
Identify the device instance as a serial port
Create names in the /dev/term directory
Add entries to the /etc/inittab file
Vendor-supplied strings should include an identifying value such as a name or stock
symbol to make the strings unique. The string can be used in
conjunction with devfsadm(1M) and the devlinks.tab file (see the devlinks(1M) man page) to
create logical names in /dev.
Deferred Attach
open(9E) might be called on a minor device before attach(9E) has succeeded on
the corresponding instance. open() must then return ENXIO, which causes the system to attempt
to attach the device. If the attach() succeeds, the open() is retried automatically.
Example 6-5 Typical attach() Entry Point
/*
* Attach an instance of the driver. We take all the knowledge we
* have about our board and check it against what has been filled in
* for us from our FCode or from our driver.conf(4) file.
*/
static int
xxattach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
int instance;
Pio *pio_p;
ddi_device_acc_attr_t da_attr;
static int pio_validate_device(dev_info_t *);
switch (cmd) {
case DDI_ATTACH:
/*
* first validate the device conforms to a configuration this driver
* supports
*/
if (pio_validate_device(dip) == 0)
return (DDI_FAILURE);
/*
* Allocate a soft state structure for this device instance
* Store a pointer to the device node in our soft state structure
* and a reference to the soft state structure in the device
* node.
*/
instance = ddi_get_instance(dip);
if (ddi_soft_state_zalloc(pio_softstate, instance) != 0)
return (DDI_FAILURE);
pio_p = ddi_get_soft_state(pio_softstate, instance);
ddi_set_driver_private(dip, (caddr_t)pio_p);
pio_p->dip = dip;
/*
* Before adding the interrupt, get the interrupt block
* cookie associated with the interrupt specification to
* initialize the mutex used by the interrupt handler.
*/
if (ddi_get_iblock_cookie(dip, 0, &pio_p->iblock_cookie) !=
DDI_SUCCESS) {
ddi_soft_state_free(pio_softstate, instance);
return (DDI_FAILURE);
}
mutex_init(&pio_p->mutex, NULL, MUTEX_DRIVER, pio_p->iblock_cookie);
/*
* Now that the mutex is initialized, add the interrupt itself.
*/
if (ddi_add_intr(dip, 0, NULL, NULL, pio_intr, (caddr_t)instance) !=
DDI_SUCCESS) {
mutex_destroy(&pio_p>mutex);
ddi_soft_state_free(pio_softstate, instance);
return (DDI_FAILURE);
}
/*
* Initialize the device access attributes for the register mapping
*/
dev_acc_attr.devacc_attr_version = DDI_DEVICE_ATTR_V0;
dev_acc_attr.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC;
dev_acc_attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC;
/*
* Map in the csr register (register 0)
*/
if (ddi_regs_map_setup(dip, 0, (caddr_t *)&(pio_p->csr), 0,
sizeof (Pio_csr), &dev_acc_attr, &pio_p->csr_handle) !=
DDI_SUCCESS) {
ddi_remove_intr(pio_p->dip, 0, pio_p->iblock_cookie);
mutex_destroy(&pio_p->mutex);
ddi_soft_state_free(pio_softstate, instance);
return (DDI_FAILURE);
}
/*
* Map in the data register (register 1)
*/
if (ddi_regs_map_setup(dip, 1, (caddr_t *)&(pio_p->data), 0,
sizeof (uchar_t), &dev_acc_attr, &pio_p->data_handle) !=
DDI_SUCCESS) {
ddi_remove_intr(pio_p->dip, 0, pio_p->iblock_cookie);
ddi_regs_map_free(&pio_p->csr_handle);
mutex_destroy(&pio_p->mutex);
ddi_soft_state_free(pio_softstate, instance);
return (DDI_FAILURE);
}
/*
* Create an entry in /devices for user processes to open(2)
* This driver will create a minor node entry in /devices
* of the form: /devices/..../pio@X,Y:pio
*/
if (ddi_create_minor_node(dip, ddi_get_name(dip), S_IFCHR,
instance, DDI_PSEUDO, 0) == DDI_FAILURE) {
ddi_remove_intr(pio_p->dip, 0, pio_p->iblock_cookie);
ddi_regs_map_free(&pio_p->csr_handle);
ddi_regs_map_free(&pio_p->data_handle);
mutex_destroy(&pio_p->mutex);
ddi_soft_state_free(pio_softstate, instance);
return (DDI_FAILURE);
}
/*
* reset device (including disabling interrupts)
*/
ddi_put8(pio_p->csr_handle, pio_p->csr, PIO_RESET);
/*
* report the name of the device instance which has attached
*/
ddi_report_dev(dip);
return (DDI_SUCCESS);
case DDI_RESUME:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
Note - The attach() routine must not make any assumptions about the order of invocations
on different device instances. The system might invoke attach() concurrently on different device
instances. The system might also invoke attach() and detach() concurrently on different device instances.
detach() Entry Point
The kernel calls a driver's detach(9E) entry point to detach an instance of a
device or to suspend operation for an instance of a device by power
management. This section discusses the operation of detaching device instances. Refer
to Chapter 12, Power Management for a discussion of power management issues.
A driver's detach() entry point is called to detach an instance of a
device that is bound to the driver. The entry point is
called with the instance of the device node to be detached and with
DDI_DETACH, which is specified as the cmd argument to the entry point.
A driver is required to cancel or wait for any time outs
or callbacks to complete, then release any resources that are allocated to the
device instance before returning. If for some reason a driver cannot cancel
outstanding callbacks for free resources, the driver is required to return the device
to its original state and return DDI_FAILURE from the entry point, leaving the device
instance in the attached state.
There are two types of callback routines: those callbacks that can be canceled
and those that cannot be canceled. timeout(9F) and bufcall(9F) callbacks can
be atomically cancelled by the driver during detach(9E). Other types of callbacks such
as scsi_init_pkt(9F) and ddi_dma_buf_bind_handle(9F) cannot be canceled. The driver must either block in
detach() until the callback completes or else fail the request to detach.
Example 6-6 Typical detach() Entry Point
/*
* detach(9e)
* free the resources that were allocated in attach(9e)
*/
static int
xxdetach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
Pio *pio_p;
int instance;
switch (cmd) {
case DDI_DETACH:
instance = ddi_get_instance(dip);
pio_p = ddi_get_soft_state(pio_softstate, instance);
/*
* turn off the device
* free any resources allocated in attach
*/
ddi_put8(pio_p->csr_handle, pio_p->csr, PIO_RESET);
ddi_remove_minor_node(dip, NULL);
ddi_regs_map_free(&pio_p->csr_handle);
ddi_regs_map_free(&pio_p->data_handle);
ddi_remove_intr(pio_p->dip, 0, pio_p->iblock_cookie);
mutex_destroy(&pio_p->mutex);
ddi_soft_state_free(pio_softstate, instance);
return (DDI_SUCCESS);
case DDI_SUSPEND:
default:
return (DDI_FAILURE);
}
}
getinfo() Entry Point
The system calls getinfo(9E) to obtain configuration information that only the driver knows. The
mapping of minor numbers to device instances is entirely under the control of
the driver. The system sometimes needs to ask the driver which device a
particular dev_t represents.
The getinfo() function can take either DDI_INFO_DEVT2INSTANCE or DDI_INFO_DEVT2DEVINFO as its infocmd argument.
The DDI_INFO_DEVT2INSTANCE command requests the instance number of a device. The DDI_INFO_DEVT2DEVINFO
command requests a pointer to the dev_info structure of a device.
In the DDI_INFO_DEVT2INSTANCE case, arg is a dev_t, and getinfo() must translate the minor
number in dev_t to an instance number. In the following example, the minor
number is the instance number, so getinfo() simply passes back the minor number.
In this case, the driver must not assume that a state structure is
available, since getinfo() might be called before attach(). The mapping defined by the
driver between the minor device number and the instance number does not necessarily
follow the mapping shown in the example. In all cases, however, the mapping
must be static.
In the DDI_INFO_DEVT2DEVINFO case, arg is again a dev_t, so getinfo() first decodes the
instance number for the device. getinfo() then passes back the dev_info pointer saved
in the driver's soft state structure for the appropriate device, as shown in
the following example.
Example 6-7 Typical getinfo() Entry Point
/*
* getinfo(9e)
* Return the instance number or device node given a dev_t
*/
static int
xxgetinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
{
int error;
Pio *pio_p;
int instance = getminor((dev_t)arg);
switch (infocmd) {
/*
* return the device node if the driver has attached the
* device instance identified by the dev_t value which was passed
*/
case DDI_INFO_DEVT2DEVINFO:
pio_p = ddi_get_soft_state(pio_softstate, instance);
if (pio_p == NULL) {
*result = NULL;
error = DDI_FAILURE;
} else {
mutex_enter(&pio_p->mutex);
*result = pio_p->dip;
mutex_exit(&pio_p->mutex);
error = DDI_SUCCESS;
}
break;
/*
* the driver can always return the instance number given a dev_t
* value, even if the instance is not attached.
*/
case DDI_INFO_DEVT2INSTANCE:
*result = (void *)instance;
error = DDI_SUCCESS;
break;
default:
*result = NULL;
error = DDI_FAILURE;
}
return (error);
}
Note - The getinfo() routine must be kept in sync with the minor nodes that
the driver creates. If the minor nodes get out of sync, any hotplug
operations might fail and cause a system panic.