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


7    Implementing Device Driver Interfaces That Support Device Autoconfiguration

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.


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


7.1    Implementing the probe Interface

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.


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


7.1.1    Resolving Multiple Bus Issues Related to Implementing a probe Interface

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.


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


7.1.2    Using the I/O Handle

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.


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


7.1.3    Setting Up the probe Interface

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]

{


.
.
.

  1. Declares an argument that specifies an I/O handle that you can use to reference a device register or memory located in bus address space (either I/O space or memory space). This I/O handle references the device's I/O address space for the bus where the read operation originates (in calls to the read_io_port interface) and where the write operation occurs (in calls to the write_io_port interface). The bus configuration code passes this I/O handle to the driver's probe interface during device autoconfiguration. [Return to example]

  2. Declares a pointer to a controller structure for this controller. This structure contains such information as the controller type, the controller name, and the current status of the controller. The bus configuration code passes this filled-in controller structure to the driver's probe interface. A device driver typically uses the ctlr_num member of the controller structure as an index to identify which instance of the controller a request is for. Section 17.2 describes the controller structure. [Return to example]


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


7.1.4    Setting Up the probe Interface to Handle Multiple Buses

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

  1. Declares an argument that specifies an I/O handle that you can use to reference a device register or memory located in bus address space (either I/O space or memory space). This I/O handle references the device's I/O address space for the bus where the read operation originates (in calls to the read_io_port interface) and where the write operation occurs (in calls to the write_io_port interface). The bus configuration code passes this I/O handle to the driver's probe interface during device autoconfiguration. [Return to example]

  2. Declares a pointer to a controller structure for this controller. This structure contains such information as the controller type, the controller name, and the current status of the controller. The bus configuration code passes this filled-in controller structure to the driver's probe interface. A device driver typically uses the ctlr_num member of the controller structure as an index to identify which instance of the controller a request is for. Section 17.2 describes the controller structure. [Return to example]

  3. Specifies a pointer to the pci_config_hdr data structure associated with this device. This device is connected to a controller that operates on the PCI bus. This is a PCI bus-specific structure that is described in Writing PCI Bus Device Drivers. [Return to example]

  4. Declares a pointer to the driver's softc structure and initializes it to the address of the xx_softc structure associated with this XX device. The controller number (stored in the ctlr_num member of the controller structure pointer associated with this device) is used as an index into the array of xx_softc structures to determine which xx_softc structure is associated with this XX device. Assume that the xx_softc structure contains a basereg member that stores the I/O handle to the address space specified by the base address zero (BAR0) device register. This BAR0 device register is accessed through the bar0 member of the pci_config_hdr structure. [Return to example]

  5. Stores the controller number for this controller in the unit variable. [Return to example]

  6. Evaluates the bus_type member to determine which bus this controller connects to. Note that the /dev/xx driver references the bus type through the bus_hd member of the controller structure pointer.

    The bus_hd member specifies a pointer to the bus structure to which this controller is connected. [Return to example]

  7. If the switch statement evaluates to the bus type BUS_TC, then this controller connects to a TURBOchannel bus. The xxprobe interface performs tasks specific to the TURBOchannel bus. The /usr/sys/include/io/common/devdriver.h file defines the bus type definitions. [Return to example]

  8. If the switch statement evaluates to the bus type BUS_ISA or BUS_EISA, then this controller connects to an ISA or EISA bus. The xxprobe interface performs tasks specific to the ISA or EISA bus. [Return to example]

  9. If the switch statement evaluates to the bus type BUS_PCI, then this controller connects to a PCI bus. Sets the basereg member of the xx_softc structure pointer associated with this XX device to the I/O handle. [Return to example]

  10. If the switch statement evaluates to something other than the bus type, then this controller connects to some unknown device. The xxprobe interface calls the printf interface to print an appropriate message on the console terminal. Note that the printf interface prints the controller number for this controller, which was obtained from the ctlr_num member. [Return to example]

  11. Returns the value zero (0) to the bus configuration code to indicate that the driver did not complete the probe operation. [Return to example]


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


7.1.5    Using Data Structures to Register Interrupt Handlers

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.


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


7.1.5.1    The ihandler_t Data Structure

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.

Table 7-1: Members of the ihandler_t Structure

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.


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


7.1.5.2    The handler_intr_info Data Structure

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.

Table 7-2: Members of the handler_intr_info Structure

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.


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


7.1.6    Using the handler Interfaces to Register Interrupt Handlers

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]


.
.
.

  1. Declares an ihandler_t data structure called handler to contain information associated with the /dev/none device driver interrupt handling. Section 7.1.5.1 describes the ihandler_t structure. The noneprobe interface initializes two members of this data structure. [Return to example]

  2. Declares a handler_intr_info data structure called info. The handler_intr_info structure contains interrupt handler information for device controllers connected to a bus. The handler_intr_info structure is a generic structure that contains interrupt handler information for buses connected to a device controller. Use of the handler_intr_info structure makes the driver more portable across different bus architectures.

    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]

  3. Declares a unit variable and initializes it to the controller number for this controller. This controller number identifies the specific NONE controller that is being probed.

    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]

  4. Declares a variable called reg and initializes it to the I/O handle passed to the driver's probe interface by the bus configuration code. In this case, the I/O handle describes the system virtual address (SVA) that corresponds to the base slot address (on a TURBOchannel bus) for the NONE device. [Return to example]

  5. If the number of controllers probed is greater than or equal to the number of controllers the system is configured for, returns the value zero (0) to indicate that the probe operation failed. When the noneprobe interface is called the first time, num_none is zero (0). Note that NNONE is set to one. Customers of EasyDriver Incorporated can change this value by editing the none: entry in the /etc/sysconfigtab database.

    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]

  6. Registers the interrupt handlers by setting up the ihandler_t structure. This line specifies the bus that this controller is attached to. The bus_hd member of the controller structure contains a pointer to the bus structure that this controller is connected to. After the initialization, the ih_bus member of the ihandler_t structure contains the pointer to the bus structure associated with the controller for this NONE device. [Return to example]

  7. Sets the configuration_st member of the info data structure to the pointer to the controller structure associated with this NONE device. This controller structure is the one associated with the controller for which an associated interrupt handler will be written.

    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]

  8. Sets the config_type member of the info data structure to the constant CONTROLLER_CONFIG_TYPE, which identifies the /dev/none driver type as a controller (hardware) driver. [Return to example]

  9. Sets the intr member of the info data structure to noneintr, the /dev/none device driver's interrupt handler. [Return to example]

  10. Sets the param member of the info data structure to the controller number for the controller structure associated with this NONE device.

    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]

  11. Sets the ih_bus_info member of the handler data structure to the address of the bus-specific information structure, info.

    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]

  12. Calls the handler_add interface and saves its return value for use later by the handler_del interface. The handler_add interface registers a device driver's interrupt handler and its associated ihandler_t data structure to the bus-specific interrupt-dispatching algorithm.

    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]

  13. If the return value from handler_add equals NULL, returns a failure status to indicate that there are no interrupt handlers for the /dev/none driver. [Return to example]

  14. If the handler_enable interface returns a nonzero value, returns the value zero (0) to indicate that it could not enable a previously registered interrupt handler. If the call to handler_enable succeeded, it marks that interrupts are enabled and can be dispatched to the driver's interrupt handlers, as registered in a previous call to handler_add.

    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]

  15. Increments the num_none variable to indicate that a NONE controller was probed. [Return to example]


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


7.1.7    Using the handler Interfaces to Register Shared Interrupt Handlers

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

  1. Specifies a pointer to the pci_config_hdr data structure associated with this device. This device is connected to a controller that operates on the PCI bus. This is a PCI bus-specific structure that is described in Writing PCI Bus Device Drivers. [Return to example]

  2. Declares a pointer to the driver's softc structure and initializes it to the address of the xx_softc structure associated with this XX device. The controller number (stored in the ctlr_num member of the controller structure pointer associated with this device) is used as an index into the array of xx_softc structures to determine which xx_softc structure is associated with this XX device. Assume that the xx_softc structure contains a basereg member that stores the I/O handle to the address space specified by the base address zero (BAR0) device register. This BAR0 device register is accessed through the bar0 member of the pci_config_hdr structure. [Return to example]

  3. Sets the basereg member of the xx_softc structure pointer associated with this XX device to the I/O handle. [Return to example]

  4. Performs tasks specific to the EISA or ISA bus. [Return to example]

  5. Performs tasks specific to the TURBOchannel bus. [Return to example]

  6. Sets the config_type member of the xx_intr_info structure to the bitwise inclusive OR of one of the interrupt handler type bit values and the shared interrupt bit value SHARED_INTR_CAPABLE. This example specifies the interrupt handler type bit value CONTROLLER_CONFIG_TYPE (the interrupt handler is for a controller driver). [Return to example]

  7. Specifies any parameter that the driver needs to control operation of the interrupt handler. The value that gets passed to the parameter argument is the value you stored in the param member of the handler_intr_info data structure.

    In this example, the device driver's interrupt handler does not make use of this argument. [Return to example]

  8. Specifies a pointer to a softc data structure and initializes it to the softc structure associated with this XX device. [Return to example]

  9. Declares a variable called intr_status that stores the bit associated with the interrupt controller register after the interrupt occurred. [Return to example]

  10. Calls the device driver's xx_readio interface, which reads the interrupt control register PNV_INTR_CTRL to determine if an interrupt occurred. (Assume that the /dev/xx driver implements the xx_readio interface to read the control register.) [Return to example]

  11. If there is no interrupt status bit set in the interrupt control register, returns the constant INTR_NOT_SERVICED to the kernel's interrupt dispatch code. This constant indicates that the driver's interrupt handler did not service the shared interrupt. [Return to example]

  12. If the interrupt control register is set to the PNV_DMA_INTR bit, then an interrupt occurred. Calls the device driver's xx_writeio interface to write the bit status to the interrupt control register. (Assume that the /dev/xx driver implements the xx_writeio interface to write the bit status.) [Return to example]

  13. Returns INTR_SERVICED to the kernel's interrupt dispatch code to indicate that the driver's interrupt handler serviced the shared interrupt. [Return to example]


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


7.1.8    Using BADADDR to Probe the Address

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


.
.
.

  1. Because a driver that is dynamically configured into the kernel cannot use the BADADDR interface, the noneprobe interface uses an if statement that tests the none_is_dynamic variable. 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]

  2. Calls the BADADDR interface to determine if the device is present.

    The BADADDR interface takes three arguments. However, only two are needed in this call.

    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:

    [Return to example]

  3. If the BADADDR interface determines that the data is not accessible, returns a nonzero value, which means the NONE device was not accessible. In this case it returns the value zero (0) to the bus configuration code to indicate the probe operation was not successful. [Return to example]


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


7.1.9    Using read_io_port and write_io_port to Read and Write Data

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

  1. Calls the write_io_port interface to write the bit represented by the constant DN_RESET to the NONE device's control/status register. This bit instructs the device to reset itself in preparation for data transfer operations.

    The write_io_port interface takes four arguments:

    [Return to example]

  2. Calls the mb interface to perform a memory barrier on Alpha CPUs. Section 3.1.6 discusses memory barriers. [Return to example]

  3. If the result of the bitwise AND operation produces a nonzero value (that is, the error bit is set), then noneprobe returns the value zero (0) to the bus configuration code to indicate that the device is broken. To determine if the error bit is set, the /dev/none driver reads the device register by calling the read_io_port interface.

    The read_io_port interface takes three arguments:

    [Return to example]

  4. If the result of the bitwise AND operation produces a zero value (that is, the error bit is not set), then noneprobe initializes the device's CSR/LED register to the value zero (0) by calling the write_io_port interface. The /dev/none driver passes the same arguments to write_io_port as in the previous call except for the fourth argument. In this call, write_io_port passes the value zero (0) to the fourth argument. [Return to example]

  5. The mb interface is called a second time to perform a memory barrier. [Return to example]

  6. The noneprobe interface returns to the bus configuration code a nonzero value, which indicates that the device is present and that the probe operation is successful. [Return to example]


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


7.1.10    Using Driver-Specific Macros to Read and Write Data

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:

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

  1. Defines offsets to the device registers. Typically, you define these device register offsets in a device register header file. [Return to example]

  2. Defines a softc structure that contains a regbase member. The regbase member stores the base address for the device registers. [Return to example]

  3. Constructs driver-specific macros by using the Digital-supplied macros. The first argument is constructed by ORing the I/O handle and a device register offset (stored in the regbase member) for some XX device. Specifically, the XXREADIO_D8, XXREADIO_D16, XXREADIO_D32, and XXREADIO_D64 macros read a byte (8 bits), a word (16 bits), a longword (32 bits), and a quadword (64 bits) from a device register located in the bus I/O address space.

    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]

  4. Constructs driver-specific macros by using the Digital-supplied macros. The first argument is constructed by ORing the I/O handle and a device register offset (stored in the regbase member) for some XX device. Specifically, the XX_WRITEIO_D8, XX_WRITEIO_D16, XX_WRITEIO_D32, and XX_WRITEIO_D64 macros write a byte (8 bits), a word (16 bits), a longword (32 bits), and a quadword (64 bits) to a device register located in the bus I/O address space.

    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]

  5. Stores the I/O handle passed in by the bus configuration code in the iohandle member of the sc pointer. [Return to example]

  6. Calls the XX_READ_D32 macro to read a longword from the device register associated with the XX_ID offset. [Return to example]

  7. Calls the XX_WRITE_D32 macro to write the value zero (0) to the device register associated with the XX_ID offset. [Return to example]


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


7.2    Implementing the attach Interface

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.


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


7.2.1    Setting Up the attach Interface for a Controller

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

  1. The NONE device does not need an attach interface. However, this line shows that a controller attach interface would declare a pointer to a controller structure. Your device driver could then send any information contained in this structure to the controller. Section 17.2 describes the controller structure. [Return to example]


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


7.2.2    Setting Up the attach Interface for a Device

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

  1. The NONE device does not need an attach interface. However, this line shows that a device attach interface would declare a pointer to a device structure. Your device driver could then send any information contained in this structure to the device. The code fragment also sets up the sections where you declare local variables and structures and where you write the code to implement the device attach interface. Section 17.3 describes the device structure. [Return to example]


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


7.3    Implementing the unattach Interface

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.


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


7.3.1    Setting Up the unattach Interface for a Controller

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


.
.
.

  1. Declares a pointer to a bus structure and calls it bus. The bus structure represents an instance of a bus entity. A bus is a real or imagined entity to which other buses or controllers are logically attached. All systems have at least one bus, the system bus, even though the bus may not actually exist physically. The term controller here refers both to devices that control slave devices (for example, disk or tape controllers) and to devices that stand alone (for example, terminal or network controllers). Section 17.1 describes the bus structure. [Return to example]

  2. Declares a pointer to a controller structure and calls it ctlr. This controller structure is the one you want to remove from the list of controllers handled by the /dev/none device driver. Section 17.2 describes the controller structure. [Return to example]


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


7.3.2    Setting Up the unattach Interface for a Device

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

.
.
.
}

  1. Declares a pointer to a controller structure for the controller to which the device is connected. [Return to example]

  2. Declares a pointer to the device structure you want to remove from the list of devices that this device driver handles. Section 17.3 describes the device structure. [Return to example]


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


7.3.3    Using the handler Interfaces to Deregister Interrupt Handlers

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

  1. Declares a unit variable and initializes it to the controller number for this controller. This controller number identifies the specific NONE controller whose associated controller structure is to be removed from the list of controllers handled by the /dev/none driver. 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]

  2. If the controller number is greater than the number of controllers found by the noneprobe interface or the number of controllers is less than zero (0), returns ENODEV to the bus configuration code to indicate an error.

    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]

  3. If none_is_dynamic is equal to the constant SUBSYSTEM_STATICALLY_CONFIGURED, returns ENODEV to the bus configuration code to indicate an error. If the driver is in the static configuration state, it is not necessary to deregister the interrupt handlers.

    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]

  4. If the return value from the call to the handler_disable interface is not equal to the value zero (0), returns ENODEV to the bus configuration code to indicate an error. Otherwise, the handler_disable interface makes the /dev/none device driver's previously registered interrupt handler unavailable to the system.

    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]

  5. If the return value from the call to the handler_del interface is not equal to the value zero (0), returns ENODEV to the bus configuration code to indicate an error. Otherwise, the handler_del interface deregisters the /dev/none device driver's interrupt handler from the bus-specific interrupt dispatching algorithm.

    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]

  6. Returns ESUCCESS to the bus configuration code upon successful completion of the tasks performed by the none_ctlr_unattach interface. [Return to example]


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


7.4    Implementing the slave Interface

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.


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


7.4.1    Resolving Multiple Bus Issues Related to Implementing 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.


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


7.4.2    Setting Up the slave Interface

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


.
.
.

  1. Declares a pointer to a device structure for this device. The bus configuration code passes this pointer to the driver's slave interface. The device driver can reference such information as the logical unit number of the device, whether the device is functional, and the bus number the device resides on. [Return to example]

  2. Declares an argument that specifies an I/O handle that you can use to reference a device register or memory located in bus address space (either I/O space or memory space). This I/O handle references the device's I/O address space for the bus where the read operation originates (in calls to the read_io_port interface) and where the write operation occurs (in calls to the write_io_port interface). The bus configuration code passes this I/O handle to the driver's slave interface during device autoconfiguration. 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. [Return to example]