When Digital UNIX boots up, the kernel determines what devices are connected to the computer. After finding a device, the kernel initializes it so that the device can be used at a later time. A device driver has a probe, attach, and possibly a slave interface. The probe interface determines if a particular device is present and the attach interface initializes the device. The slave interface, if one is implemented, checks that a device is valid for a specific controller.
The autoconfiguration support section of a device driver contains the code that implements these interfaces for both character and block device drivers. For drivers that are dynamically configured into the kernel, the autoconfiguration support section also contains a controller unattach or a device unattach interface, which is called when the driver is unloaded. You define the entry point for each of these interfaces in the driver structure.
Section 5.3 describes the driver structure. The following sections show you how to set up and implement each of these interfaces. If you prefer to study the /dev/none driver with inline comments, see the source code listing in Appendix B.
A device driver's probe interface performs the tasks necessary to determine if the device exists and is functional on a given system. The bus configuration code calls the driver's probe interface. You specify the entry point for the driver's probe interface in the driver structure.
To implement a probe interface you must understand the multiple bus issues associated with a probe interface and the I/O handle. Section 7.1.1 and Section 7.1.2 discuss these topics.
The following list describes some typical tasks that a probe interface performs:
Your probe interface will probably perform most of these tasks and, possibly, some additional ones. The following sections describe each of these tasks.
The first argument associated with a driver's probe interface is bus specific. To write portable device drivers across multiple bus architectures, you need to know the first argument associated with the probe interface for that bus. See the bus-specific driver book for a description of the first argument associated with the probe interface for that bus.
The second argument for a driver's probe interface is always a pointer to a controller structure. You can use the controller structure pointer to determine the bus to which the device controller is connected. Typically, you reference the bus_name member of the bus structure through the controller structure's bus_hd member. Chapter 17 discusses the members of the bus and controller structures.
As described in Section 2.3.1, the I/O handle is a data entity that is of type io_handle_t. Device drivers use the I/O handle to reference bus address space (either I/O space or memory space).
The bus configuration code passes the I/O handle to the device driver's probe interface during device autoconfiguration. (The bus configuration code also passes this I/O handle to the device driver's slave interface, if one is implemented, during device autoconfiguration.) The number of I/O handles that the bus configuration code passes to the device driver depends on the bus the device operates on. The I/O handle passed to the driver's probe interface describes how to access (address) that device on a particular Alpha CPU.
The properties associated with an I/O handle depend on what slot, bus, or child bus the device resides in.
An I/O handle has properties similar to memory-mapped registers on other UNIX-based systems (for example, TURBOchannel base address regions on ULTRIX MIPS). See the bus-specific device driver books for information on the I/O handle or handles a specific bus passes to a device driver's probe interface.
You can perform standard C mathematical operations (addition and subtraction only) on the I/O handle. For example, you can add an offset to or subtract an offset from the I/O handle. The following list points out restrictions on the use of the I/O handle:
You call the iohandle_to_phys interface to convert an I/O handle into a valid system physical address that resides in sparse space, dense space, or bus physical address space.
One purpose of the I/O handle is to hide CPU-specific architecture idiosyncracies that describe how to access a device's control status registers (CSRs) and how to perform I/O copy operations. For example, rather than perform an I/O copy operation according to a specific CPU architecture, you call an I/O copy interface that uses the I/O handle. The use of the I/O handle guarantees device driver portability across those CPUs on which the I/O copy interface is implemented.
Section 7.1.6 shows you how the /dev/none driver deals with the I/O handle.
The way you set up a probe interface depends on the bus on which the driver operates. The following code example shows you how to set up a probe interface for a driver that operates on a TURBOchannel bus, using the /dev/none driver as an example:
noneprobe(bus_io_handle, ctlr) io_handle_t bus_io_handle; [1] struct controller *ctlr; [2]{
.
.
.
If you want to write one device driver that operates on multiple buses, the driver's probe interface must handle any differences related to the bus. The following example shows you how to set up a probe interface to handle the EISA, ISA, PCI, and TURBOchannel buses, using the /dev/xx driver as an example. (Assume that the /dev/xx driver operates on these four buses.)
xxprobe(bus_io_handle, ctlr) io_handle_t bus_io_handle; [1] struct controller *ctlr; [2] {
.
.
.
struct pci_config_hdr *pci_cfg_hdr = (void *)bus_io_handle; [3] struct xx_softc *sc = &xx_softc[ctlr->ctlr_num]; [4]
.
.
.
int unit = ctlr->ctlr_num; [5]
.
.
.
switch (ctlr->bus_hd->bus_type) { [6] case BUS_TC: [7]
.
.
.
break; case BUS_ISA: case BUS_EISA: [8]
.
.
.
break;
.
.
.
case BUS_PCI: [9] sc->basereg = pci_cfg_hdr->bar0;
.
.
.
break; default: [10] printf("xx%d: xxprobe: unknown device\n",unit); return 0; [11]
.
.
.
}
The bus_hd member specifies a pointer to the bus structure to which this controller is connected. [Return to example]
One task that a device driver's probe interface must perform is to register interrupt handlers. An interrupt handler is a device driver routine that services hardware interrupts. Digital requires that all drivers call the handler interfaces to dynamically register interrupt handlers. The dynamic registration of interrupt handlers involves the use of the following data structures:
The following sections describe each of these data structures.
The ihandler_t structure contains information associated with device driver interrupt handling. This model of interrupt dispatching uses the bus as the means of interrupt dispatching for all drivers. For this reason, all of the information needed to register an interrupt is considered to be bus specific. As a result, no attempt is made to represent all the possible permutations within the ihandler_t data structure.
Table 7-1 lists the members of the ihandler_t structure along with their associated data types.
Member Name | Data Type |
ih_id | ihandler_id_t |
ih_bus | struct bus * |
ih_bus_info | char * |
The ih_id member specifies a unique ID. Device drivers do not use this member.
The ih_bus member specifies a pointer to the bus structure associated with this device driver. This member is needed because the interrupt dispatching methodology requires that the bus be responsible for dispatching interrupts in a bus-specific manner. Section 17.1 describes the bus structure.
The ih_bus_info member specifies bus registration information. Each bus type could have different mechanisms for registering interrupt handlers on that bus. Thus, the ih_bus_info member contains the bus-specific information needed to register the interrupt handlers.
For example, on a TURBOchannel bus, the bus-specific information might consist of:
Device driver writers set the ih_bus_info member to the filled-in handler_intr_info structure, which contains the bus-specific information. Section 7.1.5.2 describes the handler_intr_info structure.
Device driver writers pass the ihandler_t structure to the handler_add interface in the driver's probe interface to specify how interrupt handlers are to be registered with the bus-specific interrupt dispatcher.
The handler_intr_info structure contains interrupt handler information for device controllers connected to a bus. This generic structure makes device drivers more portable across different buses because it contains all of the necessary information to add an interrupt handler for any bus. Device driver writers set the ih_bus_info member of the ihandler_t structure to the filled-in handler_intr_info structure, usually in the driver's probe interface.
The bus and controller structures contain the bus- and controller-specific information that is not provided in handler_intr_info.
For VMEbus device drivers, you use the vme_handler_info structure instead of the handler_intr_info structure. One of the members of the vme_handler_info structure is a handler_intr_info structure. See Writing VMEbus Device Drivers for more information on the vme_handler_info structure.
Table 7-2 lists the members of the handler_intr_info structure along with their associated data types. You typically set the members of this structure to appropriate values in the driver's probe interface.
Member Name | Data Type |
configuration_st | caddr_t |
intr | int (*intr) () |
param | caddr_t |
config_type | unsigned int |
The configuration_st member specifies a pointer to the bus or controller structure for which an associated interrupt handler is written.
The intr member specifies the name of the interrupt handler for the specified bus or controller.
The param member specifies a parameter that you want passed to the specified interrupt handler.
The config_type member specifies the driver type and whether the driver's interrupt handler can handle shared interrupts. If your device driver's interrupt handler cannot handle shared interrupts, you set this member to one of the following interrupt handler type bit values defined in handler.h: CONTROLLER_CONFIG_TYPE (the interrupt handler is for a controller) or ADAPTER_CONFIG_TYPE (the interrupt handler is for a bus adapter). If your device driver's interrupt handler can handle shared interrupts, you set this member to the bitwise inclusive OR of the interrupt handler type bit value and the shared interrupt bit value SHARED_INTR_CAPABLE. Section 7.1.6 shows you how to register an interrupt handler, using the /dev/none driver as an example. Section 7.1.7 shows you how to register a shared interrupt handler, using the /dev/xx driver as an example.
To register a device driver's interrupt handlers, you perform the following tasks:
Your probe interface will probably perform most of these tasks and, possibly, some additional ones. The following code shows how to perform these tasks, using the /dev/none driver as an example:
.
.
.
ihandler_t handler; [1] struct handler_intr_info info; [2] int unit = ctlr->ctlr_num; [3] register io_handle_t reg = bus_io_handle; [4]
if( num_none >= NNONE) [5] return (0);
handler.ih_bus = ctlr->bus_hd; [6]
info.configuration_st = (caddr_t)ctlr; [7]
info.config_type = CONTROLLER_CONFIG_TYPE; [8]
info.intr = noneintr; [9]
info.param = (caddr_t)unit; [10]
handler.ih_bus_info = (char *)&info; [11]
none_id_t[unit] = handler_add(&handler); [12] if (none_id_t[unit] == NULL) { [13]
return(0); }
if (handler_enable(none_id_t[unit]) != 0) { [14] handler_del(none_id_t[unit]); return(0); } num_none++; [15]
.
.
.
The /dev/none device driver uses this interrupt handler structure to dynamically register its interrupt handler. Section 7.1.5.2 describes the handler_intr_info structure. [Return to example]
The controller number is contained in the ctlr_num member of the controller structure associated with this NONE device. Section 17.2.4 describes the ctlr_num member. [Return to example]
Section 5.2.1 shows the declaration and initialization of the NNONE constant. Section 6.3 shows the use of the NNONE constant with the numunit field in the none_attributes table. [Return to example]
This line also performs a type-casting operation that converts ctlr (which is of type pointer to a controller structure) to be of type caddr_t, the type of the configuration_st member. [Return to example]
This line also performs a type-casting operation that converts unit (which is of type int) to be of type caddr_t, the type of the param member. [Return to example]
This line also performs a type-casting operation that converts info (which is of type ihandler_t) to be of type char *, the type of the ih_bus_info member. [Return to example]
The handler_add interface takes one argument: a pointer to an ihandler_t data structure, which in the example is the initialized structure called handler.
This interface returns an opaque ihandler_id_t key, which is a unique number used to identify the interrupt handler to be acted on by subsequent calls to handler_del, handler_disable, and handler_enable. Note that this key is stored in the none_id_t array, which is discussed in Section 5.2. Section 7.3.3 shows how to call handler_del and handler_disable. [Return to example]
The handler_enable interface takes one argument: a pointer to the interrupt handler's entry in the interrupt table. In this example, this ID is contained in the none_id_t array.
If the call to handler_enable failed, removes the previously registered interrupt handler by calling handler_del prior to returning an error status. The handler_del interface deregisters a device driver's interrupt handler from the bus-specific interrupt-dispatching algorithm.
The handler_del interface takes one argument: a pointer to the interrupt handler's entry in the interrupt table. In this example, this ID is contained in the none_id_t array. [Return to example]
Some Alpha CPU platforms require multiple hardware devices to share an interrupt line. A shared interrupt line is a hardware interrupt level (connection) shared by more than one hardware device. To work correctly on these types of systems, a driver must determine if an interrupt was generated by the device it controls. Digital UNIX provides a framework for supporting shared interrupt lines and their associated shared interrupt handlers. With this shared interrupt framework, all devices registered to use a specific interrupt line are called, one at a time, until an interrupt handler indicates that it has handled the interrupt. Although device drivers are not required to support shared interrupts, failure to do so might limit the hardware configuration on which the device is supported.
A shared interrupt handler is a driver routine registered to take advantage of the shared interrupt framework that Digital UNIX provides for hardware devices that share an interrupt line. Currently, the AlphaStation 200, AlphaStation 400, and AlphaStation 600 series of computer systems support shared interrupts on the PCI bus. Binary compatibility is maintained with device drivers that do not implement the functionality to support shared interrupts. To register a device driver's shared interrupt handlers, you perform the same tasks you perform to register a nonshared interrupt handler, and one more task specific to shared interrupts:
Your probe interface will probably perform most of these tasks and, possibly, some additional ones. The following code shows how to perform these tasks, using the /dev/xx device driver as an example. Assume that in this example the /dev/xx driver operates on one of the Alpha systems and the PCI bus that require use of the shared interrupt framework.
The code also provides a typical implementation of a device driver's interrupt handler that takes advantage of the shared interrupt functionality supplied by Digital UNIX. The numbered items that follow the example describe only the differences for implementing shared interrupts. Section 7.1.6 describes the other items in the example. Note that the /dev/xx driver's probe interface is set up to handle multiple buses. Assume that the /dev/xx driver has two other interfaces: xx_readio and xx_writeio.
.
.
.
ihandler_id_t *xx_id_t[NXX];
.
.
.
int xxprobe(bus_io_handle, ctlr) io_handle_t bus_io_handle; struct controller *ctlr; {struct pci_config_hdr *pci_cfg_hdr = (void*)bus_io_handle; [1] struct xx_softc *sc = &xx_softc[ctlr->ctlr_num]; [2] ihandler_t handler; struct handler_intr_info xx_intr_info; int unit = ctlr->ctlr_num; switch (ctlr->bus_hd->bus_type) { case BUS_PCI: sc->basereg = pci_cfg_hdr->bar0; [3]
.
.
.
case BUS_EISA: case BUS_ISA: [4]
.
.
.
case BUS_TC: [5]handler.ih_bus = ctlr->bus_hd;
xx_intr_info.configuration_st = (caddr_t)ctlr;
xx_intr_info.config_type = (CONTROLLER_CONFIG_TYPE | SHARED_INTR_CAPABLE); [6]
xx_intr_info.intr = xxintr;
xx_intr_info.param = (caddr_t)unit;
handler.ih_bus_info = (char *)&xx_intr_info;
xx_id_t[unit] = handler_add(&handler); if (xx_id_t[unit] == NULL) {
return(0); }
if (handler_enable(xx_id_t[unit]) != 0) { handler_del(xx_id_t[unit]); return(0); } xx_none++; return(0);
.
.
.
}/* End of xxprobe */
.
.
.
int xx_intr(parameter) int parameter; [7] {
.
.
.
register struct xx_softc *sc = xx_softc[unit]; [8] int intr_status; [9]
.
.
.
intr_status = xx_readio(PNV_INTR_CTRL, sc); [10] if (!(intr_status & PNV_INTR_OCCURRED)) return(INTR_NOT_SERVICED); [11] if ((intr_status & PNV_DMA_INTR) { [12] xx_writeio(PNV_INTR_CTRL, (intr_status & ~(PNV_CRD_INTR|PNV_ABRT_INTR)), sc);
.
.
.
} return(INTR_SERVICED); [13] } /* End of xx_intr */
In this example, the device driver's interrupt handler does not make use of this argument. [Return to example]
To check the read accessibility of addressed data, call the BADADDR interface. The following code shows a call to this interface, using the /dev/none driver as an example:
.
.
.
if (none_is_dynamic == SUBSYSTEM_STATICALLY_CONFIGURED) { [1] if (BADADDR( (caddr_t) reg + NONE_CSR, sizeof(long), NULL) !=0) [2] { return (0); [3] } }
.
.
.
The BADADDR interface takes three arguments. However, only two are needed in this call.
Because the first argument to BADADDR is of type caddr_t, this line also performs a type-casting operation that converts the type of the reg variable (which is of type io_handle_t) to type caddr_t. Section 7.1.6 shows how the /dev/none driver declares the reg variable and initializes it to the I/O handle.
In the example, the length is the value returned by the sizeof operator the number of bytes needed to contain a value of type long. The reason for this is the address being checked is of type long.
The BADADDR interface generates a call to a machine-dependent interface that does a read access check of the data at the supplied address and dismisses any machine check exception that may result from the attempted access. You call this interface to probe for memory or I/O devices at a specified address during device autoconfiguration.
The BADADDR interface returns the value zero (0) if the data is accessible and nonzero if the data is not accessible.
For the PCI bus and the VMEbus, you must do the following before calling BADADDR:
To read data from a device register located in bus address space, call the read_io_port interface. The read_io_port interface is a generic interface that maps to a bus- and machine-specific interface that actually performs the read operation. Using this interface to read data from a device register makes the device driver more portable across different bus architectures, different CPU architectures, and different CPU types within the same CPU architecture.
To write data to a device register located in bus address space, call the write_io_port interface. The write_io_port interface is a generic interface that maps to a bus- and machine-specific interface that actually performs the write operation. Using this interface to write data to a device register makes the device driver more portable across different bus architectures, different CPU architectures, and different CPU types within the same CPU architecture.
The following code shows how to call the read_io_port and write_io_port interfaces, using the /dev/none driver as an example. This code follows the call to the BADADDR interface, which is described in Section 7.1.8. The code also shows calls to the mb interface, which performs a memory barrier.
.
.
.
write_io_port(reg + NONE_CSR, 4, 0, DN_RESET); [1] mb(); [2]if(read_io_port(reg + NONE_CSR, 4, 0) & DN_ERROR) { return (0); } [3]
write_io_port(reg + NONE_CSR, 4, 0, 0); [4] mb(); [5]
return (1); [6] }
The write_io_port interface takes four arguments:
The read_io_port interface takes three arguments:
The example discussed in Section 7.1.9 shows how a device driver reads data from and writes data to a device register by directly calling read_io_port and write_io_port. Another way to read data from and write data to a device register is to call the following Digital-supplied macros:
Perform byte, word, longword, and quadword bus I/O read operations
Perform byte, word, longword, and quadword bus I/O write operations
Writing Device Drivers: Reference provides reference pages that give additional information on the arguments and tasks associated with these Digital-supplied macros.
Many device drivers also construct their own macros based on the Digital-supplied macros, as in the following code that uses the /dev/xx driver as an example:
.
.
.
#define XX_ID 0xc80 [1] #define XX_CSR 0xc00 #define XX_BAT 0xc04 #define XX_HIBASE 0xc08 #define XX_CONFIG 0xc0c #define XX_ID 0xc80 #define XX_CTRL 0xc84
.
.
.
struct xx_softc {
.
.
.
io_handle_t regbase [2]
.
.
.
};
.
.
.
#define XXREADIO_D8(a) READ_BUS_D8((io_handle_t)(sc->regbase | a)) [3] #define XXREADIO_D16(a) READ_BUS_D16((io_handle_t)(sc->regbase | a)) #define XXREADIO_D32(a) READ_BUS_D32((io_handle_t)(sc->regbase | a)) #define XXREADIO_D64(a) READ_BUS_D64((io_handle_t)(sc->regbase | a))
.
.
.
#define XX_WRITEIO_D8(a,d) WRITE_BUS_D8((io_handle_t)(sc->regbase | a),(long)d) [4] #define XX_WRITEIO_D16(a,d) WRITE_BUS_D16((io_handle_t)(sc->regbase | a),(long)d) #define XX_WRITEIO_D32(a,d) WRITE_BUS_D32((io_handle_t)(sc->regbase | a),(long)d) #define XX_WRITEIO_D64(a,d) WRITE_BUS_D64((io_handle_t)(sc->regbase | a),(long)d)
.
.
.
xxprobe(bus_io_handle, ctlr) io_handle_t bus_io_handle; struct controller *ctlr; { register struct xx_softc *sc; unsigned int hw_id;
.
.
.
sc->regbase = (u_long)addr; [5]
.
.
.
hw_id = XXREADIO_D32(XX_ID); [6]
.
.
.
XX_WRITEIO_D32(XX_ID, 0); [7]
.
.
.
}
To obtain the correct offset with large offsets like 0xc84, you may need to perform an addition operation instead of an OR operation. [Return to example]
To obtain the correct offset with large offsets like 0xc84, you may need to perform an addition operation instead of an OR operation. [Return to example]
A device driver's attach interface establishes communication with the device. There are two attach interfaces: a controller attach interface for controller-specific initialization (called once for each controller) and a device attach interface for device-specific initialization (called once for each slave device connected to a controller). The following sections show how you set up each of these interfaces.
The following code shows you how to set up a controller attach interface, using the /dev/none driver as an example:
nonecattach(ctlr) struct controller *ctlr; [1] {
.
.
.
return; }
The following code fragment shows you how to set up a device attach interface:
nonedattach(device) struct device *device; [1] { /* Attach interface goes here. */
.
.
.
return; }
A device driver's unattach interface removes the specified controller or device structure from the list of controllers or devices it handles. There are two unattach interfaces: a controller unattach interface for removing the specified controller structures and a device unattach interface for removing the specified device structure. To implement an unattach interface, you perform the following tasks:
The following sections show you how to perform these tasks. Writing Device Drivers: Reference provides a reference page that gives additional information on the arguments and tasks associated with a controller unattach and a device unattach interface.
The following code fragment shows you how to set up a controller unattach interface:
int none_ctlr_unattach(bus, ctlr) struct bus *bus; [1] struct controller *ctlr; [2] {
.
.
.
The following code fragment shows you how to set up a device unattach interface:
none_dev_unattach(ctlr, device) struct controller *ctlr; [1] struct device *device; [2] {
.
.
.
}
In addition to removing the specified controller or device structure from the list of controllers or devices the driver handles, the driver's controller or device unattach interface also deregisters the interrupt handlers. It does so by calling the handler_disable and handler_del interfaces. The following example shows how to remove the controller structure and disable the interrupt handler, using the /dev/none driver as an example.
.
.
.
register int unit = ctlr->ctlr_num; [1]
if ((unit > num_none) || (unit < 0)) { [2] return(ENODEV); }
if (none_is_dynamic == SUBSYSTEM_STATICALLY_CONFIGURED) { [3] return(ENODEV); }
if (handler_disable(none_id_t[unit]) != 0) { [4] return(ENODEV); }
if (handler_del(none_id_t[unit]) != 0) { [5] return(ENODEV); } return(ESUCCESS); [6] }
This sequence of code validates the controller number. The num_none variable contains the number of instances of the NONE controller found by the noneprobe interface. Section 7.1.6 describes the registration of the interrupt handlers and shows how noneprobe increments num_none. [Return to example]
The none_is_dynamic variable contains a constant to control any differences in tasks performed by the statically or dynamically configured /dev/none device driver. The none_is_dynamic variable is declared and initialized in the declarations section of the /dev/none driver. Section 5.2 describes the declarations section for the /dev/none driver. [Return to example]
This sequence of code is executed if none_is_dynamic is not equal to the constant SUBSYSTEM_STATICALLY_CONFIGURED, indicating that the /dev/none device driver is in the dynamic configuration state.
The handler_disable interface takes one argument: a pointer to the interrupt handler's entry in the interrupt table. In this call, the ID is accessed through the none_id_t array. Section 7.1.6 shows that handler_add fills in this array. Note that the unit variable is used as an index to identify the interrupt handler associated with a specific controller structure. Section 7.1.6 shows that noneprobe initializes this variable to the controller number. [Return to example]
This sequence of code is executed if none_is_dynamic is not equal to the value zero (0), indicating that the /dev/none device driver is dynamically configured into the kernel.
The handler_del interface takes the same argument as the handler_disable interface: a pointer to the interrupt handler's entry in the interrupt table. In this call, the ID is accessed through the none_id_t array. [Return to example]
The bus configuration code calls a device driver's slave interface only for a controller that has slave devices connected to it. This interface is called once for each slave attached to the controller. The code associated with a slave interface resides in the autoconfiguration support section of the device driver. You specify the entry point for the driver's slave interface in the driver structure. Writing Device Drivers: Reference provides a reference page that gives additional information on the arguments and tasks associated with a slave interface.
To implement a slave interface, you must understand the multiple bus issues associated with a slave interface and the I/O handle. Section 7.1.2 discusses the I/O handle and Section 7.4.1 discusses the multiple bus issues. Section 7.4.2 shows how to set up a slave interface.
The first argument for a driver's slave interface is always a pointer to a device structure. You can use the device structure pointer to reference such information as the logical unit number of the device, whether the device is functional, and the bus number the device resides on. The second argument associated with a driver's slave interface is bus-specific. To write portable device drivers across multiple bus architectures, you need to know the second argument associated with the slave interface for that bus. See the bus-specific driver book for a description of the second argument associated with the slave interface for that bus.
The way you set up a slave interface depends on the bus on which the driver operates. The following code shows you how to set up a slave interface for a driver that operates on a TURBOchannel bus, using the /dev/xx driver as an example:
xxslave(device, bus_io_handle) struct device *device; [1] io_handle_t bus_io_handle; [2] {
.
.
.