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


6    TURBOchannel Device Driver Example

This chapter provides an opportunity to study a device driver called /dev/cb, a simple interface to the TURBOchannel test board. The /dev/cb device driver operates on a TURBOchannel bus and implements many of the character device driver interfaces summarized in Chapter 3, plus other interfaces that the TURBOchannel test board needs.

The chapter begins with an overview of the tasks that the /dev/cb device driver performs. Table 6-1 lists the parts of the /dev/cb device driver and the sections of the chapter where each is described.

Table 6-1: Parts of the /dev/cb Device Driver

Part Section
The cbreg.h Header File Section 6.2
Include Files Section Section 6.3
Autoconfiguration Support Declarations and Definitions Section Section 6.4
Configuration Support Declarations and Definitions Section Section 6.5
Local Structure and Variable Definitions Section Section 6.6
Autoconfiguration Support Section Section 6.7
Configuration Section Section 6.8
Open and Close Device Section Section 6.9
Read and Write Device Section Section 6.10
Strategy Section Section 6.11
Start Section Section 6.12
The ioctl Section Section 6.13
Increment LED Section Section 6.14
Interrupt Section Section 6.15

The source code uses the following convention:

extern int hz; [1]

  1. Numbers appear after each line or lines of code in the /dev/cb device driver example. Following the example, a corresponding number appears that contains an explanation for the associated line or lines. The source code does not contain any inline comments. Appendix B contains the /dev/cb driver source code in its entirety with the inline comments. [Return to example]


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


6.1    Overview of the /dev/cb Device Driver

The /dev/cb device driver is a character driver that implements a test board on the TURBOchannel bus. The TURBOchannel test board is a minimal implementation of all the TURBOchannel hardware functions: programmed I/O, direct memory access (DMA) read, DMA write, and I/O read/write conflict testing. The software view of the board consists of:

All registers must be accessed as 32-bit longwords, even when they are not implemented as 32 bits. The CSR contains bits to enable option DMA read testing, conflict signal testing, I/O interrupt testing, and option DMA write testing. The CSR also contains a bit that indicates that one or more of the tests are enabled, 4-byte mask flag bits, and a DMA done bit.


The /dev/cb device driver:


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


6.2    The cbreg.h Header File

The cbreg.h file is the device register header file for the /dev/cb device driver. It contains public declarations and the device register structure for the TURBOchannel test board.

#define CB_REL_LOC 0x00040000 [1] #define CB_ADR(n) ((io_handle_t)(n + CB_REL_LOC)) [2] #define CB_SCRAMBLE(x) (((unsigned)x<<3)&~(0x1f))|(((unsigned)x>>29)&0x1f) [3]

#define CB_INTERUPT 0x0e00 [4] #define CB_CONFLICT 0x0d00 [5] #define CB_DMA_RD 0x0b00 [6] #define CB_DMA_WR 0x0700 [7] #define CB_DMA_DONE 0x0010 [8]

#define CBPIO _IO('v',0) [9] #define CBDMA _IO('v',1) [10] #define CBINT _IO('v',2) [11]

#define CBROM _IOWR('v',3,int) [12] #define CBCSR _IOR('v',4,int) [13]

#define CBINC _IO('v',5) [14] #define CBSTP _IO('v',6) [15]

#define CB_ADDER 0x0 [16] #define CB_DATA 0x4 [17] #define CB_CSR 0x8 [18] #define CB_TEST 0xC [19]

  1. Defines a constant called CB_REL_LOC. [Return to example]

  2. Defines a macro called CB_ADR that the cbattach interface calls to convert the register offset to the kernel virtual address. The data type specified in the type-casting operation is of type io_handle_t. Section 6.7.2 shows how cbattach calls this macro. [Return to example]

  3. Defines a macro called CB_SCRAMBLE that the cbstrategy interface calls to discard the low-order 2 bits of the physical address while scrambling the rest of the address. Section 6.11.2.4 shows how cbstrategy calls this macro. [Return to example]

  4. Defines a constant called CB_INTERUPT that is used to set bits 9, 10, and 11 of the CSR. Section 6.13.4 shows how the cbioctl interface uses this constant when it performs interrupt testing. [Return to example]

  5. Defines a constant called CB_CONFLICT that is used to set bits 9, 8, 10, and 11 of the CSR. This constant is not currently used. [Return to example]

  6. Defines a constant called CB_DMA_RD that is used to set bits 10, 8, 9, and 11 of the CSR. Section 6.11.2.4 shows how the cbstrategy interface uses this constant to set up the DMA enable bits. [Return to example]

  7. Defines a constant called CB_DMA_WR that is used to set bits 11, 8, 9, and 10 of the CSR. Section 6.11.2.4 shows how the cbstrategy interface uses this constant when converting the buffer virtual address. [Return to example]

  8. Defines a constant called CB_DMA_DONE for use by the cbstart interface's timeout loop. Section 6.12 shows how cbstart uses this constant in the timeout loop. [Return to example]

  9. Uses the _IO macro to construct an ioctl macro called CBPIO. The _IO macro defines ioctl types for situations where no data is actually transferred between the application program and the kernel. For example, this could occur in a device control operation. Writing Device Drivers: Reference provides information on the _IO, _IOR, _IOW, and _IOWR ioctl macros.

    The cbread, cbwrite, and cbioctl interfaces use CBPIO to set the iomode member of the cb_unit structure for this CB device to the programmed I/O (PIO) read or write code. Section 6.10.1 shows how the cbread interface uses this ioctl. Section 6.10.2 shows how the cbwrite interface uses this ioctl. Section 6.13.3 shows how the cbioctl interface uses this ioctl. [Return to example]

  10. Uses the _IO macro to construct an ioctl macro called CBDMA. The cbread, cbwrite, and cbioctl interfaces use CBDMA to set the iomode member of the cb_unit structure for this CB device to the DMA I/O read or write code. Section 6.10.1 shows how the cbread interface uses this ioctl. Section 6.10.2 shows how the cbwrite interface uses this ioctl. Section 6.13.3 shows how the cbioctl interface uses this ioctl. [Return to example]

  11. Uses the _IO macro to construct an ioctl macro called CBINT. Section 6.13.4 shows how the cbioctl interface uses CBINT to perform interrupt tests. [Return to example]

  12. Uses the _IOWR macro to construct an ioctl macro called CBROM. The _IOWR macro defines ioctl types for situations where data is transferred from the user's buffer into the kernel. The driver then performs the appropriate ioctl operation and returns data of the same size back up to the user-level application. Typically, this data consists of device control or status information passed to the driver from the application program. Section 6.13.5 shows how the cbioctl interface uses CBROM to perform a variety of tasks. [Return to example]

  13. Uses the _IOR macro to construct an ioctl macro called CBCSR. The _IOR macro defines ioctl types for situations where data is transferred from the kernel into the user's buffer. Typically, this data consists of device control or status information returned to the application program. Section 6.13.5 shows how the cbioctl interface uses _IOR to perform a variety of tasks. [Return to example]

  14. Uses the _IO macro to construct an ioctl macro called CBINC. Section 6.13.2 shows how the cbioctl interface uses CBINC when it starts to increment the lights. [Return to example]

  15. Uses the _IO macro to construct an ioctl macro called CBSTP. Section 6.13.5 shows how cbioctl uses CBSTP when it stops incrementing the lights. [Return to example]

  16. Defines the device register offset definitions for the CB device. The device registers are aligned on longword (32-bit) boundaries, even when they are implemented with less than 32 bits. The CB_ADDER device register offset represents the 32-bit read/write DMA address register. The CB_ADDER and the following device register offset definitions show the new technique for defining the registers of a device. Previous versions of the /dev/cb device driver defined a device register structure called CB_REGISTERS. The /dev/cb driver used the members of this data structure to directly access the device registers of the CB device.

    By defining device register offset definitions, the /dev/cb driver can use the read_io_port and write_io_port interfaces to access the device registers of the CB device. This makes the /dev/cb driver more portable across different bus architectures, different CPU architectures, and different CPU types within the same CPU architecture.

    Section 6.11.2.4 shows the use of CB_ADDER in a call to write_io_port. [Return to example]

  17. Represents the 32-bit read/write data register. Section 6.10.1 shows the use of CB_DATA in a call to read_io_port. Section 6.10.2 shows the use of CB_DATA in a call to write_io_port. [Return to example]

  18. Represents the 16-bit read/write CSR/LED register. Section 6.12, Section 6.14, Section 6.13.4, and Section 6.13.5 show the use of CB_CSR in calls to read_io_port and write_io_port. [Return to example]

  19. Represents the go bit set by the cbwrite interface and cleared by the cbread interface. Section 6.12 and Section 6.13.4 show the use of CB_TEST in calls to read_io_port and write_io_port. Section 6.15 shows the use of CB_TEST in a call to read_io_port. [Return to example]


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


6.3    Include Files Section

The include files section identifies the following header files that the /dev/cb device driver needs:

#include <sys/param.h> #include <kern/lock.h> #include <sys/ioctl.h> #include <sys/user.h> #include <sys/proc.h> #include <hal/cpuconf.h> #include <io/common/handler.h> #include <sys/vm.h> #include <sys/buf.h> #include <sys/errno.h> #include <sys/conf.h> #include <sys/file.h> #include <sys/uio.h> #include <sys/types.h>

#include <io/common/devdriver.h> #include <sys/sysconfig.h> #include <io/dec/tc/tc.h>

#include <io/CB100/cbreg.h> [1]

#define NCB TC_OPTION_SLOTS [2] #define NO_DEV -1 [3]

  1. Includes the device register header file, which contains the definitions of the device register offsets for the CB device. Section 6.2 shows the definitions of the device register offsets. The directory specification adheres to the device driver (kernel) kits delivery process discussed in Writing Device Drivers: Tutorial.

    The directory specification you make here depends on where you put the device register header file. Writing Device Drivers: Reference provides reference page descriptions of the header files that Digital UNIX device drivers use most frequently. [Return to example]

  2. Defines a constant called NCB that is used to allocate data structures that the /dev/cb driver needs. The define uses the constant TC_OPTION_SLOTS, which is defined in /usr/sys/include/io/dec/tc/tc.h. There can be at most three instances of the CB controller on the system. This is a small number of instances of the driver on the system and the data structures themselves are not large, so it is acceptable to allocate for the maximum configuration. This example uses the static allocation model technique described in Writing Device Drivers: Tutorial. [Return to example]

  3. Defines a constant used by the /dev/cb driver's cb_register_major_number interface when it reserves a major number. [Return to example]


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


6.4    Autoconfiguration Support Declarations and Definitions Section

The autoconfiguration support declarations and definitions section contains the following declarations that the /dev/cb device driver needs:

extern int hz; [1]

int cbprobe(), cbattach(), cbintr(), cbopen(), cbclose(); int cbread(), cbwrite(), cbioctl(), cbstart(), cbminphys(); int cbincled(), cb_ctlr_unattach(), cbstrategy(); [2]

struct controller *cbinfo[NCB]; [3]

struct driver cbdriver = { [4] cbprobe, 0, cbattach, 0, 0, 0, 0, 0, "cb", cbinfo, 0, 0, 0, 0, 0, cb_ctlr_unattach, 0 };

  1. Declares the global variable hz to store the number of clock ticks per second. You typically use the hz global variable with the timeout kernel interface to schedule interfaces to be run at the time stored in the variable. Section 6.13.2 shows how cbioctl uses this global variable. Section 6.14 shows how cbincled uses this variable. [Return to example]

  2. Declares the driver interfaces for the /dev/cb device driver. [Return to example]

  3. Declares an array of pointers to controller structures and calls it cbinfo. The controller structure represents an instance of a controller entity, one that connects logically to a bus. A controller can control devices that are directly connected or can perform some other controlling operation, such as a network interface or terminal controller operation.

    The NCB constant represents the maximum number of CB controllers. This number sizes the array of pointers to controller structures. If you are writing a new device driver (as opposed to porting an existing driver, which is the case for the /dev/cb driver), dynamically allocate the data structures as needed by calling the MALLOC interface. See Writing Device Drivers: Tutorial for a discussion of this technique. The structures for the /dev/cb driver are not dynamically allocated. [Return to example]

  4. Declares and initializes the driver structure and calls it cbdriver. The value zero (0) indicates that the /dev/cb driver does not make use of a specific member of the driver structure.

    The following list describes those members that the /dev/cb device driver initializes to a nonzero value.

    [Return to example]


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


6.5    Configuration Support Declarations and Definitions Section

The configuration support declarations and definitions section contains the following declarations that the /dev/cb device driver uses when it is configured:

static int majnum = NO_DEV; [1]
static int cbversion = 0; [2]
static int cb_developer_debug = 0; [3]

 
static unsigned char mcfgname[CFG_ATTR_NAME_SZ] = ; [4] static unsigned char unused[350] = ; [5] static unsigned char cma_dd[120] = ; [6]
 
cfg_subsys_attr_t cb_attributes[] = { [7] {"Module_Config_Name", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)mcfgname,2,CFG_ATTR_NAME_SZ,0}, {"CMA_Option", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)cma_dd,0,350,0}, {"TC_Option", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)&tc_optiondata,0,300,0},
 

 
{"Module_Path", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)unused,0,300,0}, {"Device_Subdir", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)unused,0,300,0}, {"Device_Block_Subdir", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)unused,0,300,0}, {"Device_Char_Subdir", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)unused,0,300,0}, {"Device_Major_Req", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)unused,0,300,0}, {"Device_Block_Major", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)unused,0,300,0}, {"Device_Block_Minor", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)unused,0,300,0}, {"Device_Block_Files", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)unused,0,300,0}, {"Device_Char_Major", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)unused,0,300,0}, {"Device_Char_Minor", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)unused,0,300,0}, {"Device_Char_Files", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)unused,0,300,0}, {"Device_User", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)unused,0,300,0}, {"Device_Group", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)unused,0,300,0}, {"Device_Mode", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)unused,0,300,0}, {"EISA_Option", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)unused,0,300,0}, {"ISA_Option", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)unused,0,300,0}, {"PCI_Option", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)unused,0,350,0}, {"VBA_Option", CFG_ATTR_STRTYPE, CFG_OP_CONFIGURE, (caddr_t)unused,0,350,0}, {"majnum", CFG_ATTR_INTTYPE, CFG_OP_QUERY, (caddr_t)&majnum,0,512,0}, {"version", CFG_ATTR_INTTYPE, CFG_OP_QUERY, (caddr_t)&cbversion,0,9999999,0},
 
{"CB_Developer_Debug", CFG_ATTR_INTTYPE, CFG_OP_QUERY | CFG_OP_RECONFIGURE | CFG_OP_CONFIGURE, (caddr_t)&cb_developer_debug,0,1,0}, {,0,0,0,0,0,0} };
 
extern int nodev(), nulldev(); [8] ihandler_id_t *cb_id_t[NCB]; [9]

#define DN_BUSNAME1 "*" [10]

int cb_is_dynamic = 0; [11] int driver_cfg_state; [12]
 
int cbcallback_return_status = ESUCCESS; [13] int num_cb = 0; [14]

  1. Declares and initializes to the value NO_DEV an integer variable called majnum. This variable initializes the data value associated with the majnum attribute in the cb_attributes table. [Return to example]

  2. Declares and initializes to zero (0) an integer variable called cbversion. This variable initializes the data value associated with the version attribute in the cb_attributes table. [Return to example]

  3. Declares and initializes to zero (0) an integer variable called cb_developer_debug. This variable initializes the data value associated with the CB_Developer_Debug attribute in the cb_attributes table. [Return to example]

  4. Declares and initializes to the null string a character variable called mcfgname. This variable initializes the data value associated with the Module_Config_Name attribute in the cb_attributes table. [Return to example]

  5. Declares and initializes to the null string a character variable called unused. This variable initializes the data values associated with those attributes in the cb_attributes table that are not used by the driver, such as Module_Path and Device_Subdir. [Return to example]

  6. Declares and initializes to the null string a character variable called cma_dd. This variable initializes the data value associated with the CMA_Option attribute in the cb_attributes table. [Return to example]

  7. Declares and initializes an array of cfg_subsys_attr_t data structures and calls it cb_attributes. The cfg_subsys_attr_t structure contains attribute information. [Return to example]

  8. Declares external references for the nodev and nulldev interfaces, which are used to initialize members of the dsent structure under specific circumstances.

    The devsw_add kernel interface, called by the driver's cb_configure interface, initializes the dsent table. [Return to example]

  9. Declares a pointer to an array of IDs used to deregister the interrupt handlers. The NCB constant represents the maximum number of CB controllers. This number sizes the array of IDs. Thus, there is one ID per CB device. Section 6.7.1 shows how cbprobe uses cb_id_t.

    If you are writing a new device driver (as opposed to porting an existing driver, which is the case for the /dev/cb driver), dynamically allocate the data structures as needed by calling the MALLOC interface. [Return to example]

  10. Defines the constant string "*" to indicate that this driver can operate on any bus. [Return to example]

  11. Declares a variable called cb_is_dynamic and initializes it to the value zero (0). This variable is used to control any differences in the tasks performed by the /dev/cb device driver when it is configured statically or dynamically. Thus, the /dev/cb driver can be compiled once. The decision as to whether it is statically or dynamically configured is made at kernel configuration time by the system manager and not at compile time by the driver writer.

    Section 6.7.1 shows how cbprobe uses cb_is_dynamic. Section 6.7.3 shows how cb_ctlr_unattach uses cb_is_dynamic. Section 6.8.2 shows how cb_configure uses cb_is_dynamic. [Return to example]

  12. Declares a global variable called driver_cfg_state to store the configuration state of the driver. This variable is initialized to either SUBSYSTEM_STATICALLY_CONFIGURED or SUBSYSTEM_DYNAMICALLY_CONFIGURED. Section 6.8.2 shows how the driver checks the value of this constant to determine which configuration callback routines to invoke. [Return to example]

  13. Declares the variable called cbcallback_return_status, which indicates whether a configuration callback routine is successful when called during static configuration. Section 6.8.2 shows how the driver initializes this variable when it registers callback routines for static configuration. [Return to example]

  14. Declares a variable called num_cb to store the number of controllers probed during autoconfiguration. This variable is initialized to the value zero (0) to indicate that no instances of the controller have been initialized yet. Section 6.7.1 shows how cbprobe uses this variable. Section 6.7.3 shows how cb_ctlr_unattach uses this variable. Section 6.8.2 shows how cb_configure uses this variable. [Return to example]


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


6.6    Local Structure and Variable Definitions Section

The local structure and variable definitions section contains the following declarations:

struct buf cbbuf[NCB];  [1]

 
unsigned tmpbuffer; [2]
 
struct cb_unit { [3] int attached; int opened; int iomode; int intrflag; int ledflag; int adapter; caddr_t cbad; io_handle_t cbr; struct buf *cbbuf; } cb_unit[NCB];
 
#define MAX_XFR 4 [4]
 
int cb_config = FALSE; [5] dev_t cb_devno = NODEV; [6]
 
struct dsent cb_devsw_entry = { [7] cbopen, cbclose, nodev, cbread, cbwrite, cbioctl, nodev, nodev, nodev, nodev, nodev, nodev, 0, 0, DEV_FUNNEL_NULL, 0 0 };
 
#define CB_DEBUG [8] #undef CB_DEBUGx

  1. Declares an array of buf structures and calls it cbbuf.

    The NCB constant represents the maximum number of CB devices. This number sizes the array of buf structures. Thus, there is one buf structure per CB device. Section 6.7.2 shows how cbattach references the buf structure. Section 6.10.1 and Section 6.10.2 show the buf structure passed as an argument to the physio kernel interface. [Return to example]

  2. Declares a one-word buffer called tmpbuffer. Section 6.11.2.2 shows how the cbstrategy interface uses this variable to store the internal buffer virtual address. [Return to example]

  3. Declares an array of cb_unit data structures. Again, the NCB constant represents the maximum number of CB devices. This number sizes the array of cb_unit structures. Thus, there is one cb_unit structure per CB device.

    The cbattach interface initializes some of the members of the cb_unit data structure, and all of the other /dev/cb driver interfaces reference cb_unit.

    The following list describes the members contained in this structure:

    [Return to example]

  4. Defines a constant called MAX_XFR that specifies the maximum chunk of data (in bytes) that can be transferred in read and write operations. Section 6.10.1 shows how cbread uses MAX_XFR. Section 6.10.2 shows how cbwrite uses this constant. Section 6.11.1 shows that the cbminphys interface uses this constant to set the b_bcount member of the buf structure associated with this CB device. [Return to example]

  5. Declares a variable called cb_config to store state flags that indicate whether the /dev/cb driver is configured. Section 6.8.5 shows how the cb_register_major_number interface sets cb_config to TRUE to indicate that the /dev/cb driver has been successfully configured. Section 6.8.7 shows how the cb_configure interface sets cb_config to FALSE when it unconfigures the driver. [Return to example]

  6. Declares a variable called cb_devno to store the dsent table entry slot to use. The cb_devno variable is initialized to the value NODEV to indicate that no major number for the device has been assigned. Section 6.8.2 shows that cb_configure sets cb_devno to the table entry slot. [Return to example]

  7. Declares and initializes the dsent structure called cb_devsw_entry. This structure registers the device driver's switch table entries. Section 6.8.2 shows that devsw_add uses cb_devsw_entry to add the /dev/cb driver interfaces to the in-memory dsent table.

    The following list describes those members that the /dev/cb device driver initializes to a nonzero value:

    [Return to example]

  8. Uses two of the C preprocessor statements to define and undefine debug constants. The /dev/cb device driver contains numerous conditional compilation debug statements. This section shows only one of these statements. However, the source code listing in Appendix B contains all the debug code. [Return to example]


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


6.7    Autoconfiguration Support Section

Table 6-2 lists the three interfaces implemented as part of the autoconfiguration support section, along with the sections in the book where each is described.

Table 6-2: Interfaces Implemented as Part of the Autoconfiguration Support Section

Interface Section
Implementing the cbprobe Interface Section 6.7.1
Implementing the cbattach Interface Section 6.7.2
Implementing the cb_ctlr_unattach Interface Section 6.7.3


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


6.7.1    Implementing the cbprobe Interface

The cbprobe interface is applicable to both the statically and dynamically configured versions of the /dev/cb device driver. The cbprobe interface's main task is to determine whether any CB devices exist on the system and to register the interrupt handlers for the driver.

The following code implements the cbprobe interface:

cbprobe(addr, ctlr)
io_handle_t addr;          [1]
struct controller *ctlr;   [2]
{

ihandler_t handler; [3] struct handler_intr_info info; [4] int unit = ctlr->ctlr_num; [5]

/*************************************************** * DEBUG STATEMENT * ***************************************************/ #ifdef CB_DEBUG [6] printf("CBprobe @ %8x, addr = %8x, ctlr = %8x\n",cbprobe,addr,ctlr); #endif /* CB_DEBUG */

handler.ih_bus = ctlr->bus_hd; [7]

info.configuration_st = (caddr_t)ctlr; [8]

info.config_type = CONTROLLER_CONFIG_TYPE; [9]

info.intr = cbintr; [10]

info.param = (caddr_t)unit; [11]

handler.ih_bus_info = (char *)&info; [12]

cb_id_t[unit] = handler_add(&handler); [13] if (cb_id_t[unit] == NULL) { [14]

return(0); }

if (handler_enable(cb_id_t[unit]) != 0) { [15] handler_del(cb_id_t[unit]); [16]

return(0); }

num_cb++; [17]

return(1); [18] }

  1. Declares 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).

    In previous versions of the operating system, this first argument was of type caddr_t. The argument specifies the system virtual address (SVA) that corresponds to the base slot address. The interpretation of this argument depends on the bus that your driver operates on. [Return to example]

  2. Declares a pointer to the controller structure associated with this CB device. The controller structure represents an instance of a controller entity, one that connects logically to a bus. A controller can control devices that are directly connected or can perform some other controlling operation, such as a network interface or terminal controller operation. [Return to example]

  3. Declares an ihandler_t data structure called handler to contain information associated with the /dev/cb device driver interrupt handling. The cbprobe interface initializes two members of this data structure. [Return to example]

  4. 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. In previous versions of the operating system, the /dev/cb driver declares a tc_intr_info structure, which is specific to the TURBOchannel bus. The handler_intr_info structure is a generic structure that contains interrupt handler information for buses connected to a device controller. Using the handler_intr_info structure makes the driver more portable across different bus architectures.

    The /dev/cb device driver uses this interrupt handler structure to dynamically register the device interrupt handlers. [Return to example]

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

    The controller number is contained in the ctlr_num member of the controller structure associated with this CB device. This member is used as an index into a variety of tables to retrieve information about this instance of the CB device. [Return to example]

  6. Calls the printf interface to print information that is useful for debugging purposes. This line is executed only during debugging of the /dev/cb driver.

    Only a limited number of characters (currently 128) can be sent to the console display during each call to any section of a driver. The reason is that the characters are buffered until the driver returns to the kernel, at which time they are actually sent to the console display. If more than 128 characters are sent to the console display, the storage pointer may wrap around, discarding all previous characters; or it may discard all characters following the first 128.

    If you need to see the results on the console terminal, limit the message size to the maximum of 128 characters whenever you send a message from within the driver. However, printf also stores the messages in an error log file. You can use the uerf command to view the text of this error log file. (See the uerf reference page.) The messages are easier to read if you use uerf with the -o terse option. [Return to example]

  7. 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 initialization, the ih_bus member of the ihandler_t structure contains the pointer to the bus structure associated with the /dev/cb device driver.

    This line, and the lines that follow, describe the setup of the driver's interrupt handler. In previous versions of the operating system, dynamic registration of interrupt handlers was accomplished only for dynamically configured drivers. For the Digital UNIX operating system, Digital recommends that you register interrupt handlers for all drivers by using the handler interfaces. [Return to example]

  8. Sets the configuration_st member of the info data structure to the pointer to the controller structure associated with this CB device. This controller structure is the one for which an associated interrupt 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]

  9. Sets the config_type member of the info data structure to the constant CONTROLLER_CONFIG_TYPE, which identifies the /dev/cb driver type as a controller. [Return to example]

  10. Sets the intr member of the info data structure to cbintr, the /dev/cb device driver's interrupt handler. [Return to example]

  11. Sets the param member of the info data structure to the controller number for the controller structure associated with this CB device. Once the driver is operational and interrupts are generated, the cbintr interface is called with the controller number, which specifies which instance of the controller the interrupt is associated with.

    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]

  12. Sets the ih_bus_info member of the handler data structure to the address of the bus-specific information structure, info. This setting is necessary because registration of the interrupt handlers will indirectly call bus-specific interrupt registration interfaces.

    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]

  13. Calls the handler_add interface and saves its return value for use later by the handler_del interface.

    The handler_add interface takes one argument: a pointer to an ihandler_t data structure, which in the example is the initialized handler structure.

    This interface returns an opaque ihandler_id_t key, which is a unique number used to identify the interrupt service interfaces to be acted on by subsequent calls to handler_del, handler_disable, and handler_enable. This key is stored in the cb_id_t array (indexed by the unit number), which was declared in Section 6.5. Section 6.7.3 shows how to call handler_del and handler_disable. [Return to example]

  14. If the return value from handler_add equals NULL, returns a failure status to indicate that registration of the interrupt handler failed. [Return to example]

  15. 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 service interface.

    The handler_enable interface takes one argument: a pointer to the interrupt service interface's entry in the interrupt table. In this example, the ID associated with the interrupt entry is contained in the cb_id_t array. [Return to example]

  16. If the call to handler_enable failed, removes the previously registered interrupt handler by calling handler_del prior to returning an error status. [Return to example]

  17. Increments the number of instances of this controller found on the system. [Return to example]

  18. Returns the value 1 to indicate success status because the TURBOchannel initialization code already verified that the device was present. [Return to example]


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


6.7.2    Implementing the cbattach Interface

The cbattach interface performs controller-specific initialization. The following code implements the cbattach interface:

cbattach(ctlr) struct controller *ctlr; [1] { struct cb_unit *cb; [2]

cb = &cb_unit[ctlr->ctlr_num]; [3]

cb->attached = 1; [4]

cb->adapter = ctlr->slot; [5]

cb->cbad = ctlr->addr; [6]

cb->cbr = (io_handle_t)CB_ADR(ctlr->addr); [7] cb->cbbuf = &cbbuf[ctlr->ctlr_num]; [8] cb->iomode = CBPIO; [9] }

  1. Declares a pointer to the controller structure associated with this CB device. The controller structure represents an instance of a controller entity, one that connects logically to a bus. A controller can control devices that are directly connected or can perform some other controlling operation, such as a network interface or terminal controller operation. [Return to example]

  2. Declares a pointer to the cb_unit data structure associated with this CB device and calls it cb. Section 6.6 shows the declaration of cb_unit. [Return to example]

  3. Sets the pointer to the cb_unit structure to the address of the unit data structure associated with this CB device. The ctlr_num member of the controller structure pointed to by ctlr holds the controller number for the controller associated with this CB device. Thus, this member is used as an index into the array of cb_unit structures to set the instance that represents this CB device. [Return to example]

  4. Indicates that this CB device is attached to its associated controller by setting the attached member of the device's associated cb_unit structure to the value 1. [Return to example]

  5. Sets this CB device's TURBOchannel slot number by setting the adapter member of this CB device's cb_unit structure to the slot number contained in the slot member of the device's controller structure. [Return to example]

  6. Sets the base address of the device ROM. This location is the address of the data to be checked for read accessibility. The cbattach interface accomplishes this task by setting the cbad member of this CB device's cb_unit structure to the address contained in the addr member of its controller structure. [Return to example]

  7. Sets the pointer to the CB device's registers. The cbattach interface accomplishes this task by setting the cbr member of this device's cb_unit structure to the register address calculated by the CB_ADR macro. The cbr member is an I/O handle. An I/O handle is a data entity that is of type io_handle_t.

    Note that CB_ADR takes as an argument the address of the data to be checked for read accessibility, which in this case is stored in the addr member of the controller structure. Section 6.2 shows the definition of the CB_ADR macro.

    When CB_ADR completes execution, the cb->cbr pointer is set to the base address plus 20000 hexadecimal because the CB device registers are located at that offset from the base address. [Return to example]

  8. Sets the buffer structure address (the cbbuf member of this CB device's cb_unit structure) to the address of this CB device's buf structure. Again, the ctlr_num member is used as an index into the array of buf structures associated with this CB device. [Return to example]

  9. Sets the read/write mode to the ioctl represented by CBPIO. This ioctl indicates that the driver starts in programmed I/O (PIO) read or write mode. [Return to example]


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


6.7.3    Implementing the cb_ctlr_unattach Interface

The cb_ctlr_unattach interface is specific to drivers that are dynamically configured. It is called indirectly from the bus code when a system manager uses the sysconfig utility to unload the driver. This interface would never be called if the /dev/cb device driver were configured statically because static drivers cannot be unconfigured. The cb_ctlr_unattach interface's main tasks are to deregister the interrupt handlers associated with the /dev/cb device driver and to remove the specified controller structure from the list of controllers the /dev/cb driver handles.

The following code implements the cb_ctlr_unattach interface:

int cb_ctlr_unattach(bus, ctlr) struct bus *bus; [1] struct controller *ctlr; [2] {

register int unit = ctlr->ctlr_num; [3]

if ((unit > num_cb) || (unit < 0)) { [4] return(ENODEV); }

if (cb_is_dynamic == 0) { [5] return(ENODEV); }

if (handler_disable(cb_id_t[unit]) != 0) { [6] return(ENODEV); }

if (handler_del(cb_id_t[unit]) != 0) { [7] return(ENODEV); } return(ESUCCESS); [8] }

  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). [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/cb device driver. [Return to example]

  3. Declares a unit variable and initializes it to the controller number for this controller. This controller number identifies the specific CB controller whose associated controller structure is to be removed from the list of controllers handled by the /dev/cb driver.

    The controller number is contained in the ctlr_num member of the controller structure associated with this CB device. [Return to example]

  4. If the controller number is greater than the number of controllers found by the cbprobe interface or the controller number is less than zero, returns ENODEV to the bus code to indicate an error. This sequence of code validates the controller number. The num_cb variable contains the number of instances of the CB controller found by the cbprobe interface. Section 6.7.1 describes the implementation of cbprobe. [Return to example]

  5. If cb_is_dynamic is equal to the value zero (0), returns ENODEV to the bus code to indicate an error. This sequence of code validates whether the /dev/cb driver was dynamically loaded. The cb_is_dynamic variable contains a value to control any differences in tasks performed by a statically or dynamically configured /dev/cb device driver. This approach means that any differences are made at run time and not at compile time. The cb_is_dynamic variable was previously set by the cb_configure interface, discussed in Section 6.8.2. [Return to example]

  6. 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 code to indicate an error. Otherwise, the handler_disable interface makes the /dev/cb device driver's previously registered interrupt service interfaces unavailable to the system. [Return to example]

  7. 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 code to indicate an error. Otherwise, the handler_del interface deregisters the /dev/cb device driver's interrupt service interface from the bus-specific interrupt dispatching algorithm.

    The handler_del interface takes the same argument as the handler_disable interface: a pointer to the interrupt service's entry in the interrupt table. [Return to example]

  8. Returns ESUCCESS to the bus code upon successful completion of the tasks performed by the cb_ctlr_unattach interface. [Return to example]


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


6.8    Configuration Section

The configuration section implements the cb_configure interface. Table 6-3 lists the tasks associated with implementing the configuration section, along with the sections in the book where each task is described.

Table 6-3: Tasks Associated with Implementing the Configuration Section

Tasks Section
Setting Up the cb_configure Interface Section 6.8.1
Configuring the /dev/cb Device Driver Section 6.8.2
Configuring the Driver into the System Section 6.8.3
Registering the Configuration Callback Routine Section 6.8.4
Registering the Driver's Major Number Section 6.8.5
Registering the Driver's Major Number Callback Routine Section 6.8.6
Unconfiguring the /dev/cb Device Driver Section 6.8.7
Performing Other Configuration Operations Section 6.8.8


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


6.8.1    Setting Up the cb_configure Interface

The following code shows how to set up the cb_configure interface:

cb_configure(op, indata, indatalen, outdata, outdatalen) cfg_op_t op; [1] cfg_attr_t *indata; [2] size_t indatalen; [3] cfg_attr_t *outdata; [4] size_t outdatalen; { dev_t cdevno; [5] int retval; [6] int i; [7] struct cb_unit *cb; [8] int cbincled(); [9]
 
#ifdef CB_DEBUG #define MAX_DEVICE_CFG_ENTRIES 18 [10]
 
cfg_attr_t cfg_buf[MAX_DEVICE_CFG_ENTRIES]; [11] #endif

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

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

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

  4. The outdata and outdatalen formal parameters are not currently used. [Return to example]

  5. Declares a variable called cdevno to temporarily store the major device number for the CB device. [Return to example]

  6. Declares a variable called retval to store the return value from the cb_register_configuration interface. [Return to example]

  7. Declares a variable called i to be used in the for loop when cb_configure unconfigures the driver. [Return to example]

  8. Declares a pointer to the cb_unit data structure associated with this CB device and calls it cb. Section 6.6 shows the declaration of cb_unit. [Return to example]

  9. Declares a forward reference to the cbincled interface. Section 6.14 shows the implementation of cbincled. [Return to example]

  10. Defines a constant that represents the number of configuration lines in the sysconfigtab file fragment for the /dev/cb device driver. [Return to example]

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

    Thus, for the /dev/cb device driver, the cfgmgr framework passes the cfg_attr_t array of attributes to the indata argument and the number of cfg_attr_t structures in the array to the indatalen argument of the cb_configure interface. [Return to example]


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


6.8.2    Configuring the /dev/cb Device Driver

The following code shows how to implement the configuration portion of the cb_configure interface:

        switch (op) {

case CFG_OP_CONFIGURE: [1] #ifdef CB_DEBUG [2] bcopy(indata, cfg_buf[0].name, indatalen*(sizeof(cfg_attr_t))); printf(" The cb_configure routine was called. op = %x\n",op); for( i=0; i < indatalen; i++){ printf("%s: ",cfg_buf[i].name); switch(cfg_buf[i].type){ case CFG_ATTR_STRTYPE: printf("%s\n",cfg_buf[i].attr.str.val); break; default: switch(cfg_buf[i].status){ case CFG_ATTR_EEXISTS: printf("**Attribute does not exist\n"); break; case CFG_ATTR_EOP: printf("**Attribute does not support operation\n"); break; case CFG_ATTR_ESUBSYS: printf("**Subsystem Failure\n"); break; case CFG_ATTR_ESMALL: printf("**Attribute size/value too small\n"); break; case CFG_ATTR_ELARGE: printf("**Attribute size/value too large\n"); break; case CFG_ATTR_ETYPE: printf("**Attribute invalid type\n"); break; case CFG_ATTR_EINDEX: printf("**Attribute invalid index\n"); break; case CFG_ATTR_EMEM: printf("**Attribute memory allocation error\n"); break; default: printf("**Unknown attribute: "); printf("%x\n", cfg_buf[i].status); break; } break; } } #endif
 
if(strcmp(mcfgname,)==0) { [3] strcpy(mcfgname,"cb"); } else { }
 
if(cfgmgr_get_state(mcfgname, &driver_cfg_state) != ESUCCESS){ [4] return(EINVAL); }
 
if(driver_cfg_state == SUBSYSTEM_STATICALLY_CONFIGURED) { [5] cbcallback_return_status = ESUCCESS; cb_is_dynamic = SUBSYSTEM_STATICALLY_CONFIGURED;
 
register_callback(callback_cb_register_configuration, CFG_PT_PRECONFIG, CFG_ORD_NOMINAL, (long) 0L);
 
register_callback(callback_0, CFG_PT_PRECONFIG, CFG_ORD_NOMINAL+100, (long) 0L);
 
register_callback(callback_1, CFG_PT_PRECONFIG, CFG_ORD_NOMINAL, (long) 0L);
 
register_callback(callback_2, CFG_PT_PRECONFIG, CFG_ORD_MAXIMUM, (long) 0L);
 
register_callback(callback_3, CFG_PT_PRECONFIG, CFG_ORD_MAXIMUM+100, (long) 0L);
 
register_callback(callback_cb_register_major_number, CFG_PT_POSTCONFIG, CFG_ORD_NOMINAL, (long) 0L); } else { [6] cb_is_dynamic = SUBSYSTEM_DYNAMICALLY_CONFIGURED; retval = cb_register_configuration(); if(retval != ESUCCESS) return(retval);
 
if((retval = configure_driver(mcfgname, DRIVER_WILDNUM, DN_BUSNAME1, &cbdriver)) != ESUCCESS) { return(retval); }
 
retval = cb_register_major_number(); if(retval != ESUCCESS) return(retval); } break;

  1. Specifies the CFG_OP_CONFIGURE constant to indicate that this section of code implements the configure driver operation. The file /usr/sys/include/sys/sysconfig.h defines this constant. [Return to example]

  2. The /dev/cb device driver requests that the cfgmgr framework initialize the cb_attributes structure with all of the attributes specified for the cb entry in the /etc/sysconfigtab database.

    Attributes passed through the cb_attributes structure are not known to be valid until the device driver can check their status in the indata argument. A status other than CFG_FRAME_SUCCESS is an error condition. The code does the following:

    [Return to example]

  3. If the Module_Config_Name attribute is the null string, initializes the attribute to the string "cb". [Return to example]

  4. If the Module_Config_Name attribute is not null, calls the cfgmgr_get_state interface to determine whether the driver is statically or dynamically configured. If this call fails, the driver exits with the EINVAL error status value. [Return to example]

  5. If the driver is statically configured, initializes the cbcallback_return_status global variable to ESUCCESS and the cb_is_dynamic global variable to SUBSYSTEM_STATICALLY_CONFIGURED. The driver then registers a series of callback routines to be executed at various times during startup, as follows:

    [Return to example]

  6. If the driver is dynamically configured, performs the following steps:

    [Return to example]


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


6.8.3    Configuring the Driver into the System

The cb_register_configuration interface creates and initializes data structures for the driver within the system (hardware) tree. It is implemented as follows:

int
cb_register_configuration()
{
struct controller_config ctlr_register; [1]
struct controller_config * ctlr_register_ptr = &ctlr_register;
int    i, status;

 
ctlr_register_ptr->revision = CTLR_CONFIG_REVISION; [2] strcpy(ctlr_register_ptr->subsystem_name, mcfgname); strcpy(ctlr_register_ptr->bus_name,DN_BUSNAME1); ctlr_register_ptr->devdriver = &cbdriver;
 
for(i = 0; i < NCB; i++) { [3] status = create_controller_struct(ctlr_register_ptr); if(status != ESUCCESS) return (status); }
 
return(ESUCCESS); [4] }

  1. Creates a controller_config structure called ctlr_register and a pointer to that structure called ctlr_register_ptr. [Return to example]

  2. Initializes members of the ctlr_register structure with the revision number of the structure definition, the value of the Module_Config_Name attribute, the bus name, and the address of the driver structure for the device driver. The revision number is specified by the CTLR_CONFIG_REVISION constant defined in /usr/sys/include/io/common/devdriver.h. [Return to example]

  3. For each available slot (indicated by the NCB variable), the driver calls create_controller_struct to create a controller structure in the system (hardware) tree. The interface uses information passed to it in the controller_config structure to intialize the controller structure. If create_controller_struct fails, the driver returns the error status. [Return to example]

  4. Returns ESUCCESS if the driver is successfully configured. [Return to example]


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


6.8.4    Registering the Configuration Callback Routine

The callback_cb_register_configuration interface is called at startup for statically configured drivers. This interface calls the cb_register_configuration interface and sets the global status variable to indicate that the driver was successfully configured, as follows:

void
callback_cb_register_configuration(point, order, args, event_arg) [1]
    int point;
    int order;
    ulong args;
    ulong event_arg;
{
int  status;

 
if(cbcallback_return_status != ESUCCESS) { [2] return; }
 
status = cb_register_configuration(); [3]
 
if(status != ESUCCESS) { [4] cfgmgr_set_status(mcfgname); cbcallback_return_status = status;
 
return; } }

  1. Defines the callback_cb_register_configuration interface to be called with the following arguments:

    [Return to example]

  2. Checks the cbcallback_return_status global variable to make sure no previous error has occurred while configuring the driver. If so, the driver exits without doing any configuration. [Return to example]

  3. Calls the cb_register_configuration interface to configure the driver in the system (hardware) tree. Section 6.8.3 describes this routine. [Return to example]

  4. If the status returned by cb_register_configuration is not ESUCCESS, calls the cfgmgr_set_status interface and sets the cbcallback_return_status global variable to the value returned by cb_register_configuration. The cfgmgr_set_status interface adjusts the state of the driver and unconfigures the driver from the configuration framework. [Return to example]


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


6.8.5    Registering the Driver's Major Number

The cb_register_major_number interface adds the driver's entry points in the device switch table, as follows:


 
int cb_register_major_number() {
 
if (num_cb == 0) { [1] return (ENODEV); }
 
majnum = devsw_add(mcfgname, MAJOR_INSTANCE, [2] majnum, &cb_devsw_entry);
 
if (majnum == NO_DEV) { [3] return (ENODEV); }
 
cb_devno = majnum; [4] cb_config = TRUE; [5] return(ESUCCESS); }

  1. Checks the value of the num_cb global variable to see if there are any devices present in the system after the driver has been configured. If not, the driver exits with the ENODEV error status. [Return to example]

  2. Calls the devsw_add interface to add the device driver information to the device switch table. The majnum value is normally initialized to -1. The kmknod and device.mth programs are triggered to match device special files based on the major number, which they query from the device driver's attribute table. [Return to example]

  3. If the call to devsw_add fails, returns the ENODEV error status. [Return to example]

  4. Initializes the cb_devno global variable to the device major number. [Return to example]

  5. Initializes the cb_config global variable to TRUE to indicate that the device has been successfully configured. [Return to example]


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


6.8.6    Registering the Major Number Callback Routine

The callback_cb_register_major_number interface is called at startup for statically configured drivers. This interface calls the cb_register_major_number interface and adds the driver's entry points in the device switch table, as follows:

void
callback_cb_register_major_number(point, order, args, event_arg) [1]
 int point;
 int order;
 ulong args;
 ulong event_arg;
{
int  status;

 
if(cbcallback_return_status != ESUCCESS) [2] return;
 
status = cb_register_major_number(); [3]
 
if(status != ESUCCESS) { [4] cfgmgr_set_status(mcfgname); cbcallback_return_status = status; return; } }

  1. Defines the callback_cb_register_major_number interface to be called with the following arguments:

    [Return to example]

  2. Checks the cbcallback_return_status global variable to make sure no previous error has occurred while configuring the driver. If so, the driver exits without doing any configuration. [Return to example]

  3. Calls the cb_register_major_number interface to add the driver information to the device switch table. Section 6.8.6 describes the cb_register_major_number interface. [Return to example]

  4. If the status returned by cb_register_major_number is not ESUCCESS, calls the cfgmgr_set_status interface and sets the cbcallback_return_status global variable to the value returned by cb_register_major_number. The cfgmgr_set_status interface adjusts the state of the driver and unconfigures the driver from the configuration framework. [Return to example]


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


6.8.7    Unconfiguring the /dev/cb Device Driver

The following code shows how to implement the unconfiguration of a dynamically configured /dev/cb device driver:

              case CFG_OP_UNCONFIGURE: [1]

 
if(cb_is_dynamic == SUBSYSTEM_STATICALLY_CONFIGURED) [2] { return(ESUCCESS); }
 
for (i = 0; i < num_cb; i++) { [3] cb = &cb_unit[i]; cb->ledflag = 0; untimeout(cbincled, (caddr_t)cb); }
 
retval = devsw_del(mcfgname, MAJOR_INSTANCE); [4] if (retval == NO_DEV) { return(ESRCH); }
 
if (unconfigure_driver(DN_BUSNAME1, [5] DRIVER_WILDNUM, &cbdriver, DRIVER_WILDNAME, DRIVER_WILDNUM) != 0) {
 
return(ESRCH); } cb_is_dynamic = 0; [6] cb_config = FALSE; break;

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

  2. Checks to see if the driver is statically configured. If so, the driver exits with the ESCUCCESS status but does not unconfigure the driver. [Return to example]

  3. As long as the variable i is less than the number of controllers found by cbprobe, executes the following:

    [Return to example]

  4. Calls the devsw_del interface to remove the instance of the driver from the device switch table, as indicated by the Module_Name attribute and the major number. If devsw_del returns NO_DEV, the driver exits with the ESRCH error status. [Return to example]

  5. Calls the unconfigure_driver interface to remove the device from the system (hardware) configuration tree. If unconfigure_driver fails, the driver exits with the ESRCH error status. [Return to example]

  6. Sets the cb_is_dynamic global variable to zero (0), and sets the cb_config global variable to FALSE. This indicates that the driver is not configured. [Return to example]


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


6.8.8    Performing Other Configuration Operations

The following code shows how to implement the reconfiguration and query sections of a device driver. The query section of code executes when the system manager queries information associated with the driver. This section of code is also a program request to the loadable subsystem support interfaces. This is how the cfgmgr framework makes the device special files after the cb_configure interface executes.

	      case CFG_OP_RECONFIGURE: [1]
                break;

 
case CFG_OP_QUERY: [2] break;
 
default: return(ENOTSUP); [3] break; }

return(ESUCCESS); [4] }

  1. Specifies the CFG_OP_RECONFIGURE operation to indicate that this section of code implements the reconfiguration operation. The file /usr/sys/include/sys/sysconfig.h defines the CFG_OP_RECONFIGURE constant. [Return to example]

  2. Specifies the CFG_OP_QUERY operation to indicate that this section of code implements the query operation. The file /usr/sys/include/sys/sysconfig.h defines the CFG_OP_QUERY constant. [Return to example]

  3. Returns the ENOTSUP error constant if the cb_configure interface receives an unknown operation type. This section of code is called if the op argument is set to anything other than CFG_OP_CONFIGURE, CFG_OP_UNCONFIGURE, CFG_OP_RECONFIGURE, or CFG_OP_QUERY. [Return to example]

  4. To indicate that the /dev/cb driver's cb_configure interface completed successfully, returns the ESUCCESS status value. [Return to example]


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


6.9    Open and Close Device Section

The open and close device section is responsible for opening and closing the device. Table 6-4 lists the two interfaces implemented as part of the open and close device section, along with the sections in the book where each is described.

Table 6-4: Interfaces Implemented as Part of the Open and Close Device Section

Interfaces Section
Implementing the cbopen Interface Section 6.9.1
Implementing the cbclose Interface Section 6.9.2


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


6.9.1    Implementing the cbopen Interface

The following code implements the cbopen interface:

cbopen(dev, flag, format)
dev_t dev;   [1]
int flag;    [2]
int format;  [3]
{

int unit = minor(dev); [4]

if ((unit > NCB) || !cb_unit[unit].attached) return(ENXIO); [5] cb_unit[unit].opened = 1; [6] return(0); [7] }

  1. Declares an argument that specifies the major and minor device numbers for a specific CB device. The minor device number is used to determine the logical unit number for the CB device that is to be opened. [Return to example]

  2. Declares an argument to contain flag bits from the file /usr/sys/include/sys/file.h. These flags indicate whether the device is being opened for reading, writing, or both. [Return to example]

  3. Declares an argument that specifies the format of the special device to be opened. The format argument is used by a driver that has both block and character interfaces and that uses the same open interface in the dsent table. The driver uses this argument to distinguish the type of device being opened. The cbopen interface does not use this argument. [Return to example]

  4. Declares a unit variable and initializes it to the device minor number. Note the use of the minor interface to obtain the device minor number.

    The minor interface takes one argument: the number of the device for which an associated device minor number will be obtained. The minor number is encoded in the dev argument, which is of type dev_t. [Return to example]

  5. If the device minor number is greater than the number of CB devices configured in this system, or if this CB device is not attached, returns the error code ENXIO, which indicates no such device or address. This error code is defined in /usr/sys/include/sys/errno.h.

    The NCB constant is used in the comparison of the unit variable. This constant defines the maximum number of CB devices configured on this system.

    The line also checks the attached member of this CB device's cb_unit structure. Section 6.7.2 shows that the cbattach interface set this member to the value 1. [Return to example]

  6. If the previous line evaluates to FALSE, sets the opened member of this CB device's cb_unit structure to the value 1 to indicate that this CB device is open and ready for operation. Section 6.6 shows the declaration of the cb_unit data structure. [Return to example]

  7. Returns success to the open system call, indicating a successful open of this CB device. [Return to example]


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


6.9.2    Implementing the cbclose Interface

The following code implements the cbclose interface:

cbclose(dev, flag, format) dev_t dev; [1] int flag; [2] int format; [3] {

int unit = minor(dev); [4] cb_unit[unit].opened = 0; [5] return(0); [6] }

  1. Declares an argument that specifies the major and minor device numbers for a specific CB device. The minor device number is used to determine the logical unit number for the CB device that is to be closed. [Return to example]

  2. Declares an argument to contain flag bits from the file /usr/sys/include/sys/file.h. The cbclose interface does not use this argument. [Return to example]

  3. Declares an argument that specifies the format of the special device to be closed. The format argument is used by a driver that has both block and character interfaces and that uses the same close interface in the dsent table. The driver uses this argument to distinguish the type of device being closed.

    The cbclose interface does not use this argument. [Return to example]

  4. Declares a unit variable and initializes it to the device minor number. Note the use of the minor interface to obtain the device minor number.

    The minor interface takes one argument: the number of the device for which an associated device minor number will be obtained. The minor number is encoded in the dev argument, which is of type dev_t. [Return to example]

  5. Sets the opened member of this CB device's cb_unit structure to the value zero (0), indicating that this CB device is now closed. Section 6.6 shows the declaration of the cb_unit data structure. [Return to example]

  6. Returns success to the close system call, indicating a successful close of this CB device. [Return to example]


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


6.10    Read and Write Device Section

The read and write device section is applicable to the /dev/cb device driver. Table 6-5 lists the interfaces implemented as part of the read and write device section, along with the sections in the book where each is described.

Table 6-5: Interfaces Implemented as Part of the Read and Write Device Section

Interfaces Section
Implementing the cbread Interface Section 6.10.1
Implementing the cbwrite Interface Section 6.10.2


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


6.10.1    Implementing the cbread Interface

The following code implements the cbread interface:

cbread(dev, uio, flag)
dev_t dev;       [1]
struct uio *uio; [2]
int flag;
{
    unsigned tmp; [3]
    int cnt, err; [4]
    int unit = minor(dev); [5]
    struct cb_unit *cb;    [6]

err = 0; [7] cb = &cb_unit[unit]; [8] if(cb->iomode == CBPIO) { [9]

while((cnt = uio->uio_resid) && (err == 0)) { [10]

if(cnt > MAX_XFR)cnt = MAX_XFR; [11] tmp = read_io_port(cb->cbr | CB_DATA, 4, 0);[12]

err = uiomove(&tmp,cnt,uio); [13] } return(err); [14] } else if(cb->iomode == CBDMA) [15]
 
return(physio(cbstrategy,cb->cbbuf,dev, B_READ,cbminphys,uio)); }

  1. Declares an argument that specifies the major and minor device numbers for a specific CB device. The minor device number is used to determine the logical unit number for the CB device on which the read operation is performed. [Return to example]

  2. Declares a pointer to a uio structure. This structure contains the information for transferring data to and from the address space of the user's process. You typically pass this pointer unmodified to the uiomove or physio kernel interface. This driver passes the pointer to both interfaces. [Return to example]

  3. Declares a variable called tmp to store the 32-bit read/write data register. This variable is passed as an argument to the uiomove kernel interface. [Return to example]

  4. Declares a variable called cnt to store the number of bytes of data that still need to be transferred. This variable is passed as an argument to the uiomove interface.

    Declares a variable called err to store the return value from uiomove. [Return to example]

  5. Declares a unit variable and initializes it to the device minor number. Note the use of the minor interface to obtain the device minor number.

    The minor interface takes one argument: the number of the device for which an associated device minor number will be obtained. The minor number is encoded in the dev argument, which is of type dev_t.

    The unit variable is used to select the CB board to be accessed for the read operation. [Return to example]

  6. Declares a pointer to the cb_unit data structure associated with this CB device and calls it cb. Section 6.6 shows the declaration of cb_unit. [Return to example]

  7. Initializes the err variable to the value zero (0) to indicate no error has occurred yet. [Return to example]

  8. Sets the pointer to the cb_unit structure to the address of the unit data structure associated with this CB device. The unit variable contains this CB device's minor number. Thus, this argument is used as an index into the array of cb_unit structures associated with this CB device. [Return to example]

  9. If the I/O mode bit is CBPIO, then this is a programmed I/O read operation. This bit is set in the iomode member of the pointer to the cb_unit data structure associated with this CB device.

    For a programmed I/O read operation, the contents of the data register on the TURBOchannel test board are read into a 32-bit local variable. Then the uiomove interface moves the contents of that variable into the buffer in the user's virtual address space. [Return to example]

  10. Sets up a while loop that allows the uiomove interface to transfer bytes from the TURBOchannel test board data register to the user's buffer until all of the requested bytes are moved or until an error occurs.

    The uio_resid member of the uio structure specifies the number of bytes that still need to be transferred.

    The while loop must accomplish this task because the TURBOchannel test board data register can supply only a maximum of MAX_XFR bytes at a time. The MAX_XFR constant was previously defined as 4 bytes. This loop may not be required by other devices. [Return to example]

  11. If the number of bytes that still need to be transferred is greater than 4, forces cnt to contain 4 bytes of data. Thus, a read of more than 4 bytes is divided into a number of 4-byte maximum transfers with a final transfer of 4 bytes or less. [Return to example]

  12. Reads the 32-bit read/write data register by calling the read_io_port interface. This register value is defined by the CB_DATA device register offset associated with this CB device. 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.

    The read_io_port interface takes three arguments:

    Upon successful completion, read_io_port returns the data read from the 32-bit read/write data register to the tmp variable. [Return to example]

  13. Calls the uiomove interface to move the bytes read from the TURBOchannel data register in system virtual space to the user's buffer in user space. The maximum number of bytes moved is 4 and uio_resid is updated as each move is completed.

    The uiomove interface takes three arguments:

    [Return to example]

  14. Returns a zero (0) value whenever the user virtual space described by the uio structure is accessible and the data is successfully moved. Otherwise, it returns an EFAULT error value. [Return to example]

  15. If the I/O mode bit is CBDMA, then this is a DMA I/O read operation. This bit is set in the iomode member of the pointer to the cb_unit data structure associated with this CB device.

    For a DMA I/O read operation, the physio kernel interface and the /dev/cb driver's cbstrategy and cbminphys interfaces are called to transfer the contents of the data register on the TURBOchannel test board into the buffer in the user's virtual address space. Because only a single word of 4 bytes can be transferred at a time, both modes of reading include code to limit the read to chunks with a maximum of 4 bytes each. Reading more than 4 bytes will propagate the contents of the data register throughout the words of the user's buffer.

    The physio interface takes six arguments:

    [Return to example]


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


6.10.2    Implementing the cbwrite Interface

The following code implements the cbwrite interface:

cbwrite(dev, uio, flag) dev_t dev; [1] struct uio *uio; [2] int flag; { unsigned tmp; [3] int cnt, err; [4] int unit = minor(dev); [5] struct cb_unit *cb; [6]

err = 0; [7] cb = &cb_unit[unit]; [8] if(cb->iomode == CBPIO) { [9]

while((cnt = uio->uio_resid) && (err == 0)) { [10] if(cnt > MAX_XFR)cnt = MAX_XFR; [11]

err = uiomove(&tmp,cnt,uio); [12] write_io_port(cb->cbr | CB_DATA, 4, 0, tmp); [13] } return(err); [14] } else if(cb->iomode == CBDMA) [15]

return(physio(cbstrategy,cb->cbbuf,dev, B_WRITE,cbminphys,uio)); }

  1. Declares an argument that specifies the major and minor device numbers for a specific CB device. The minor device number is used to determine the logical unit number for the CB device on which the write operation is performed. [Return to example]

  2. Declares a pointer to a uio structure. This structure contains the information for transferring data to and from the address space of the user's process. You typically pass this pointer unmodified to the uiomove or physio kernel interface. This driver passes the pointer to both interfaces. [Return to example]

  3. Declares a variable called tmp to store the 32-bit read/write data register. This variable is passed as an argument to the uiomove kernel interface. [Return to example]

  4. Declares a variable called cnt to store the number of bytes of data that still need to be transferred. This variable is passed as an argument to the uiomove interface.

    Declares a variable called err to store the return value from uiomove. [Return to example]

  5. Declares a unit variable and initializes it to the device minor number. Note the use of the minor interface to obtain the device minor number.

    The minor interface takes one argument: the number of the device for which an associated device minor number will be obtained. The minor number is encoded in the dev argument, which is of type dev_t.

    The unit variable is used to select the TURBOchannel test board to be accessed for the write operation. [Return to example]

  6. Declares a pointer to the cb_unit data structure associated with this CB device and calls it cb. Section 6.6 shows the declaration of cb_unit. [Return to example]

  7. Initializes the err variable to the value zero (0) to indicate no error has occurred yet. [Return to example]

  8. Sets the pointer to the cb_unit structure to the address of the unit data structure associated with this CB device. The unit variable contains this CB device's minor number. Thus, this argument is used as an index into the array of cb_unit structures associated with this CB device. [Return to example]

  9. If the I/O mode bit is CBPIO, then this is a programmed I/O write operation. This bit is set in the iomode member of the pointer to the cb_unit data structure associated with this CB device.

    For a programmed I/O write operation, the uiomove interface moves the contents of one word from the buffer in the user's virtual address space to a 32-bit local variable. Then the contents of that variable are moved to the data register on the CB test board. [Return to example]

  10. Sets up a while loop that allows the uiomove interface to transfer bytes from the user's buffer to the TURBOchannel test board data register until all of the requested bytes are moved or until an error occurs.

    The uio_resid member of the pointer to the uio structure specifies the number of bytes that still need to be transferred.

    The while loop must do this task because the TURBOchannel test board data register can accept only a maximum of MAX_XFR bytes at a time. The MAX_XFR constant was previously defined as 4 bytes. This loop may not be required by other devices. [Return to example]

  11. If the number of bytes that still need to be transferred is greater than 4, then forces cnt to contain 4 bytes of data. This code causes a write of more than 4 bytes to be divided into a number of 4-byte maximum transfers with a final transfer of 4 bytes or less. [Return to example]

  12. Calls the uiomove interface to move the bytes from the user's buffer to the local variable, tmp. The maximum number of bytes moved is 4 and uio_resid is updated as each move is completed.

    The uiomove interface takes the same three arguments as described for cbread. [Return to example]

  13. Writes the data to the 32-bit read/write data register by calling the write_io_port interface. This register value is defined by the CB_DATA device register offset associated with this CB device. 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 write_io_port interface takes four arguments:

    [Return to example]

  14. Returns a zero (0) value whenever the user virtual space described by the uio structure is accessible and the data is successfully moved. Otherwise, it returns an EFAULT error value. [Return to example]

  15. If the I/O mode bit is CBDMA, then this is a DMA I/O write operation. This bit is set in the iomode member of the pointer to the cb_unit data structure associated with this CB device.

    For a DMA I/O write operation, the physio kernel interface and the /dev/cb driver's cbstrategy and cbminphys interfaces are called to transfer the contents of the buffer in the user's virtual address space to the data register on the TURBOchannel test board. Because only a single word of 4 bytes can be transferred at a time, both modes of reading include code to limit the write to chunks with a maximum of 4 bytes. Writing more than 4 bytes has limited usefulness because all the words in the user's buffer will be written into the single data register on the test board.

    This call to the physio interface takes the same arguments as those passed to cbread except the read/write flag is B_WRITE instead of B_READ. [Return to example]


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


6.11    Strategy Section

Table 6-6 lists the tasks associated with implementing the strategy section, along with the sections in the book where each task is described.

Table 6-6: Tasks Associated with Implementing the Strategy Section

Tasks Section
Setting Up the cbminphys Interface Section 6.11.1
Setting Up the cbstrategy Interface Section 6.11.2
Initializing the buf Structure for Transfer Section 6.11.2.1
Testing the Low-Order Two Bits and Using the Internal Buffer Section 6.11.2.2
Converting the Buffer Virtual Address Section 6.11.2.3
Converting the 32-Bit Physical Address Section 6.11.2.4
Starting I/O and Checking for Timeouts Section 6.11.2.5


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


6.11.1    Setting Up the cbminphys Interface

The following code sets up the cbminphys interface, whose major task is to bound the data transfer size to 4 bytes:

cbminphys(bp) register struct buf *bp; [1] { if (bp->b_bcount > MAX_XFR) [2] bp->b_bcount = MAX_XFR; return; }

  1. Declares a pointer to a buf structure and calls it bp. [Return to example]

  2. If the size of the requested transfer is greater than 4 bytes, sets the b_bcount member of bp to 4 bytes and returns.

    The b_bcount member stores the size of the requested transfer (in bytes).

    In the call to physio, the driver writer passes cbminphys as the interface to call to check for size limitations. Section 6.10.1 and Section 6.10.2 discuss the call to physio. [Return to example]


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


6.11.2    Setting Up the cbstrategy Interface

The cbstrategy interface performs the following tasks:

Section 6.10.1 and Section 6.10.2 show that cbread and cbwrite call the cbstrategy interface.

cbstrategy(bp) register struct buf *bp; [1] { register int unit = minor(bp->b_dev); [2]

register struct controller *ctlr; [3] struct cb_unit *cb; [4] caddr_t buff_addr; [5] caddr_t virt_addr; [6] unsigned phys_addr; [7] int cmd; [8] int err; [9] int status; [10] unsigned lowbits; [11] unsigned tmp; [12] int s; [13]

ctlr = cbinfo[unit]; [14]

  1. Declares a pointer to a buf structure and calls it bp. [Return to example]

  2. Gets the minor device number and stores it in the unit variable.

    The minor kernel interface is used to obtain the minor device number associated with this CB device.

    The minor interface takes one argument: the number of the device for which the minor device needs to be obtained. The device number is stored in the b_dev member of the buf structure associated with this CB device. [Return to example]

  3. Declares a pointer to a controller structure and calls it ctlr. [Return to example]

  4. Declares a pointer to the cb_unit data structure associated with this CB device and calls it cb. Section 6.6 shows the declaration of cb_unit. [Return to example]

  5. Declares a variable called buff_addr that stores the user buffer's virtual address. Section 6.11.2.2 shows that buff_addr is passed to the copyin kernel interface. Section 6.11.2.5 shows that buff_addr is passed to the copyout kernel interface. [Return to example]

  6. Declares a variable called virt_addr that stores the user buffer's virtual address. Section 6.11.2.2 shows that virt_addr is passed to the copyin kernel interface. Section 6.11.2.3 shows that virt_addr is passed to the vtop kernel interface. Section 6.11.2.5 shows that virt_addr is passed to the copyout kernel interface. [Return to example]

  7. Declares a variable called phys_addr that stores the user buffer's physical address. Section 6.11.2.3 shows that this variable stores the value returned by the vtop kernel interface. [Return to example]

  8. Declares a variable called cmd that stores the current command for the TURBOchannel test board. Section 6.11.2.4 shows that the commands are represented by the constants CB_DMA_RD and CB_DMA_WR. [Return to example]

  9. Declares a variable called err that stores the error status returned by cbstart. [Return to example]

  10. Declares a variable called status to store the value associated with the 16-bit read/write CSR/LED register. This value is obtained by calling the read_io_port interface. Appendix B shows that cbstrategy uses this variable in a CB_DEBUG statement. [Return to example]

  11. Declares a variable called lowbits to store the low 2 virtual address bits. Section 6.11.2.2 and Section 6.11.2.5 show how this variable is used. [Return to example]

  12. Declares a temporary holding variable called tmp. Section 6.11.2.4 shows that the CB_SCRAMBLE macro uses this variable. [Return to example]

  13. Declares a temporary holding variable called s. Section 6.11.2.4 shows that the splbio kernel interface uses this variable to store its return value. [Return to example]

  14. Sets the pointer to the controller structure to its associated CB device. Note that unit, which now contains this CB device's minor device number, is used as an index into the array of controller structures to obtain the controller structure associated with this device. [Return to example]


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


6.11.2.1    Initializing the buf Structure for Transfer

The following code initializes the buf structure for transfer:

bp->b_resid = bp->b_bcount; [1] bp->av_forw = 0; [2]

cb = &cb_unit[unit]; [3]

virt_addr = bp->b_un.b_addr; [4] buff_addr = virt_addr; [5]

  1. Initializes the bytes not transferred. This is done in case the transfer fails at a later time. The b_resid member of the pointer to the buf structure stores the data (in bytes) not transferred because of some error. The b_bcount member stores the size of the requested transfer (in bytes). [Return to example]

  2. Clears the buffer queue forward link. The av_forw member stores the position on the free list if the b_flags member is not set to B_BUSY. [Return to example]

  3. Sets the pointer to the cb_unit structure to the address of the unit data structure associated with this CB device. The unit variable contains this CB device's minor number. Thus, this argument is used as an index into the array of cb_unit structures associated with this CB device. Section 6.11.2 shows how the minor interface initializes unit to the device's minor number. [Return to example]

  4. Sets the virt_addr variable to the buffer's virtual address. The operating system software sets this address in the b_addr member of the union member b_un in the pointer to the buf structure. [Return to example]

  5. Copies the buffer's virtual address into the buff_addr variable for use by the driver. [Return to example]


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


6.11.2.2    Testing the Low-Order Two Bits and Using the Internal Buffer

Direct memory access (DMA) on the TURBOchannel test board can be done only with full words and must be aligned on word boundaries. Because the user's buffer can be aligned on any byte boundary, the /dev/cb driver code must check for and handle the cases where the buffer is not word aligned. In this context, word aligned means that the address is evenly divisible by 4, where a word is a 4-byte entity. Any address that is word aligned has its lowest order 2 bits set to zeros. (If the TURBOchannel interface hardware included special hardware to handle nonword-aligned transfers, this checking would not have to be performed.) If the user's buffer is not word aligned, the driver can:

Because virtual-to-physical mapping is done on a page basis, the low-order 2 bits of the virtual address of the user's buffer are also the low 2 bits of the physical address of the user's buffer. You can determine the buffer alignment by examining the low 2 bits of the virtual buffer address. If these 2 bits are nonzero, the buffer is not word aligned and the driver must take the correct action.

The following code tests the low-order 2 bits:

if ((lowbits = (unsigned)virt_addr & 3) != 0) { [1] virt_addr = (caddr_t)(&tmpbuffer); [2]

if ( !(bp->b_flags&B_READ) ) { [3] tmpbuffer = 0 ;

if (err = copyin(buff_addr,virt_addr, bp->b_resid)) { [4] bp->b_error = err; [5] bp->b_flags |= B_ERROR; [6] iodone(bp); [7] return; [8] } } }

  1. This bitwise AND operation uses the low-order 2 bits of the buffer virtual address as the word-aligned indicator for this transfer. If the result of the bitwise AND operation is nonzero, the user's buffer is not word aligned and the next line gets executed.

    Section 6.11.2.1 shows that the virt_addr variable gets set to the buffer's virtual address. Because virt_addr is of type caddr_t and lowbits is of type unsigned, the code performs the appropriate type-casting operation. [Return to example]

  2. Because the user's buffer is not word aligned, uses the internal buffer, tmpbuffer. This line replaces the current user buffer virtual address with the internal buffer virtual address. The physio kernel interface updates the current user buffer virtual address as each word is transferred. Because DMA to the TURBOchannel test board can be done only a word at a time, the internal buffer needs to be only a single word. [Return to example]

  3. If the transfer type is a write, clears the one-word temporary buffer tmpbuffer. [Return to example]

  4. Calls the copyin kernel interface to copy data from the user address space to the kernel address space.

    The copyin kernel interface takes three arguments:

    [Return to example]

  5. Upon success, copyin returns the value zero (0). Otherwise, it returns EFAULT to indicate that the address specified in buff_addr could not be accessed. This line sets the b_error member of the pointer to the buf structure to the value returned in err. Section 6.10.2 shows that err was initialized to the value zero (0) to indicate that no error has yet occurred. [Return to example]

  6. Sets b_flags to the bitwise inclusive OR of the read and error bits. [Return to example]

  7. Calls the iodone kernel interface to indicate that the I/O operation is complete.

    This interface takes one argument: a pointer to a buf structure. [Return to example]

  8. Returns with an error to physio, which was called by cbwrite. Section 6.10.2 shows the call to physio. [Return to example]


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


6.11.2.3    Converting the Buffer Virtual Address

The following code for the cbstrategy interface converts the buffer virtual address to a physical address for DMA by calling the vtop interface. In previous versions of the operating system, the /dev/cb driver made calls to the IS_KSEG_VA, KSEG_TO_PHYS, IS_SEG0_VA, pmap_kernel, and pmap_extract interfaces to accomplish this task. On the Digital UNIX operating system the /dev/cb driver now makes one call to vtop.

phys_addr = vtop(bp->b_proc, virt_addr); [1]

  1. Converts the buffer virtual address to a physical address for DMA.

    This interface takes two arguments:

    Upon successful completion, vtop returns the physical address associated with the specified virtual address. [Return to example]


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


6.11.2.4    Converting the 32-Bit Physical Address

The following code uses the CB_SCRAMBLE macro to convert the 32-bit physical address to a form suitable for use in the DMA operation:

tmp = CB_SCRAMBLE(phys_addr); [1] write_io_port(cb->cbr | CB_ADDER, 4, 0, tmp); [2]

if(bp->b_flags&B_READ) [3] cmd = CB_DMA_WR; else cmd = CB_DMA_RD;

s = splbio(); [4]

  1. Converts the 32-bit physical address (actually the low 32 bits of the 34-bit physical address) from the linear form to the condensed form that the DMA operation uses to pack 34 address bits onto 32 board lines. TURBOchannel DMA operations can be done only with full words and must be aligned on word boundaries. The CB_SCRAMBLE macro discards the low-order 2 bits of the physical address while scrambling the rest of the address. Therefore, anything that is going to be done to resolve this address must be done before calling CB_SCRAMBLE. [Return to example]

  2. Writes the data to the 32-bit read/write DMA address register by calling the write_io_port interface. This register value is defined by the CB_ADDER device register offset associated with this CB device. 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 write_io_port interface takes four arguments:

    [Return to example]

  3. If the read bit is set, initializes the cmd argument to the DMA write bit, which is defined in the cbreg.h file. This bit indicates a write to memory.

    Otherwise, if the read bit is not set, initializes the cmd argument to the DMA read bit, which also is defined in the cbreg.h file. This bit indicates a read from memory. [Return to example]

  4. Calls the splbio interface to mask (disable) all controller interrupts. The value returned by splbio is an integer value that represents the CPU priority level that existed prior to the call. The return value stored in s becomes the argument passed to splx, which is discussed in Section 6.11.2.5. [Return to example]


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


6.11.2.5    Starting I/O and Checking for Timeouts

The following code starts the I/O and checks for timeouts:

err = cbstart(cmd,cb); [1] splx(s); [2]

if(err <= 0) { [3]

bp->b_error = EIO; bp->b_flags |= B_ERROR; iodone(bp); return; } else { [4]

if ( (lowbits)!=0 && bp->b_flags&B_READ) { [5]

if (err = copyout(virt_addr,buff_addr, bp->b_resid)) { [6] bp->b_error = err; bp->b_flags |= B_ERROR; } } bp->b_resid = 0; [7] }

iodone(bp); [8]

return; }

  1. Starts the I/O by calling the driver's cbstart interface, passing to it the current command for the test board and the address of the cb_unit data structure associated with this CB device. Section 6.12 describes the cbstart driver interface.

    The cmd argument is set either to CB_DMA_WR (the write to memory bit) or to CB_DMA_RD (the read from memory bit). [Return to example]

  2. Restores the CPU priority by calling the splx kernel interface, passing to it the value returned in a previous call to splbio. This value is an integer that represents the CPU priority level that existed before the call to splbio. [Return to example]

  3. If the return value from cbstart is zero (0), the DMA operation did not complete within the timeout period. In this case, cbstart does the following:

    [Return to example]

  4. If the return value from cbstart is not zero (0), executes the following lines because the DMA completed successfully. [Return to example]

  5. If a read was attempted to an unaligned user buffer, calls copyout to copy the bytes that were read into the user buffer. [Return to example]

  6. If the copyout kernel interface was unable to copy data from kernel address space to user address space, copyout does the following:

    [Return to example]

  7. Sets the b_resid member in the buf structure pointer to the value zero (0) to indicate that the read or write operation has completed. [Return to example]

  8. Calls iodone to indicate that the I/O transfer has completed and to initiate the return status. [Return to example]


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


6.12    Start Section

The cbstart interface loads the CSR register of the TURBOchannel test board. Because the cbincled interface increments the LEDs in the high 4 bits of the 16-bit CSR register, cbstart always loads the 4 bits into whatever value it will be storing into the CSR before doing the actual storage operation. The cbstart interface is called with system interrupts disabled; thus, cbincled is not called while cbstart is incrementing.

The following code shows the implementation of the cbstart interface:

int cbstart(cmd,cb)
int cmd;              [1]
struct cb_unit *cb;   [2]
{
        int timecnt; [3]
        int status;  [4]

cmd = (read_io_port(cb->cbr | CB_CSR, 4, 0)&0xf000)|(cmd&0xfff); [5]
 
status = read_io_port(cb->cbr | CB_TEST, 4, 0); [6]
 

 
write_io_port(cb->cbr | CB_CSR, 4, 0, cmd); [7] mb(); [8]

write_io_port(cb->cbr | CB_TEST, 4, 0, 0); [9]
 
mb(); [10]

timecnt = 10; [11] status = read_io_port(cb->cbr | CB_CSR, 4, 0); [12]

while((!(status & CB_DMA_DONE)) && timecnt > 0) { [13] write_io_port(cb->cbr | CB_CSR, 4, 0, cmd);
 
mb(); status = read_io_port(cb->cbr | CB_CSR, 4, 0); timecnt --; }

return(timecnt); [14] }

  1. Declares a variable to contain the current command for the test board. Section 6.11.2.4 shows that cmd was set to CB_DMA_WR or CB_DMA_RD. [Return to example]

  2. Declares a pointer to the cb_unit data structure associated with this CB device and calls it cb. Section 6.6 shows the declaration of cb_unit. [Return to example]

  3. Declares a variable to contain the timeout loop count. [Return to example]

  4. Declares a variable to contain the CSR contents for status checking. [Return to example]

  5. Sets the cmd variable to the logical or high 4 LED bits and the command to be performed by calling 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. [Return to example]

  6. Reads the CSR/LED register by calling the read_io_port interface. On the CB device, reading the test register clears the go bit. The test register is defined by the CB_TEST device register offset associated with this CB device.

    The read_io_port interface takes three arguments:

    Upon successful completion, read_io_port returns the data read from the go bit register to the status variable. [Return to example]

  7. Writes the data to the specified location by calling the write_io_port interface. This location is the result of the ORing of the I/O handle with the 16-bit read/write CSR/LED register value. This register value is defined by the CB_CSR device register offset associated with this CB device. 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 write_io_port interface takes four arguments:

    [Return to example]

  8. Calls the mb kernel interface to ensure that a write to I/O space has completed. [Return to example]

  9. Writes the value zero (0) to the specified location by calling the write_io_port interface. This call is the same as the previous call except for the values passed to the first and fourth arguments. For the first argument, the location is the result of ORing the I/O handle with the go bit device register offset. This register value is defined by the CB_TEST device register offset associated with this CB device. For the fourth argument, the data to be written is the value zero (0). Writing zero (0) to this device register has the effect of setting the go bit. [Return to example]

  10. Calls mb again to ensure that a write to I/O space has completed. [Return to example]

  11. Initializes the timeout loop counter variable, timecnt, to the value 10. [Return to example]

  12. Reads the status for this CB device from the specified location by calling the read_io_port interface. This call passes the same values as a previous call. For the first argument, the location of the read operation is the result of ORing the I/O handle with the 16-bit read/write CSR/LED register represented by CB_CSR. [Return to example]

  13. Spins until the DMA has completed or the timeout loop counter expires. Then:

    [Return to example]

  14. Returns the timeout count. If the command is successful, cbstart returns a nonzero value. If the loop exits because of a timeout, cbstart returns a zero (0) value. [Return to example]


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


6.13    The ioctl Section

Table 6-7 lists the tasks associated with implementing the ioctl section, along with the sections in the book where each task is described.

Table 6-7: Tasks Associated with Implementing the ioctl Section

Tasks Section
Setting Up the cbioctl Interface Section 6.13.1
Incrementing the Lights Section 6.13.2
Setting the I/O Mode Section 6.13.3
Performing an Interrupt Test Section 6.13.4
Returning a ROM Word, Updating the CSR, and Stopping Increment of the Lights Section 6.13.5


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


6.13.1    Setting Up the cbioctl Interface

The following code sets up the cbioctl interface:

#define CBIncSec 1 [1]

cbioctl(dev, cmd, data, flag) dev_t dev; [2] unsigned int cmd; [3] int *data; [4] int flag; [5] {

int tmp; [6] int *addr; [7] int timecnt; [8] int unit = minor(dev); [9] struct cb_unit *cb; [10] int cbincled(); [11]

cb = &cb_unit[unit]; [12]

  1. Defines a constant called CBIncSec that indicates the number of seconds between increments of the TURBOchannel test board lights. Section 6.13.2 shows that this constant is passed to the timeout kernel interface. [Return to example]

  2. Declares an argument that specifies the major and minor device numbers for a specific CB device. The minor device number is used to determine the logical unit number for the CB device on which the ioctl operation is to be performed. [Return to example]

  3. Declares an argument that specifies the ioctl command in the file /usr/sys/include/sys/ioctl.h or in another include file that the device driver writer defines. There are two types of ioctl commands. One type is supported by all drivers of a given class. Another type is specific to a given device. The values of the cmd argument are defined by using the _IO, _IOR, _IOW, and _IOWR macros. Section 6.2 shows that the following ioctl commands are defined in the cbreg.h file: CBPIO, CBDMA, CBINT, CBROM, CBCSR, CBINC, and CBSTP. [Return to example]

  4. Declares a pointer to ioctl command-specific data that is to be passed to the device driver or filled in by the device driver. This argument is a kernel address. The size of the data cannot exceed the size of a page (currently 8 KB on Alpha systems). At least 128 bytes are guaranteed. Any size between 128 bytes and the page size may fail if memory cannot be allocated. The particular ioctl command implicitly determines the action to be taken. The ioctl system call performs all the necessary copy operations to move data to and from user space. Section 6.13.5 shows how cbioctl initializes the data argument. [Return to example]

  5. Declares an argument that specifies the access mode of the device. The /dev/cb driver does not use this argument. [Return to example]

  6. Declares a temporary holding variable called tmp. [Return to example]

  7. Declares a pointer to a variable called addr that is used for word access to the TURBOchannel test board. Section 6.13.5 shows that this variable is used with the data argument. [Return to example]

  8. Declares a variable called timecnt. Section 6.13.4 shows that this variable is used in the timeout loop count. [Return to example]

  9. Declares a unit variable and initializes it to the device minor number. Note the use of the minor interface to obtain the device minor number.

    The minor interface takes one argument: the number of the device for which an associated device minor number will be obtained. The minor number is encoded in the dev argument, which is of type dev_t.

    The unit variable is used to select the TURBOchannel test board to be accessed for the ioctl operation. [Return to example]

  10. Declares a pointer to the cb_unit data structure associated with this CB device and calls it cb. Section 6.6 shows the declaration of cb_unit. [Return to example]

  11. Declares a forward reference to the cbincled interface. Section 6.14 shows the implementation of cbincled. [Return to example]

  12. Sets the pointer to the cb_unit structure to the address of the unit data structure associated with this CB device. [Return to example]


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


6.13.2    Incrementing the Lights

The following code starts incrementing the lights on the TURBOchannel test board:

        switch(cmd&0xFF) {      [1]
               case CBINC&0xFF:         [2]
                        if(cb->ledflag == 0) {  [3]
                             cb->ledflag++;  [4]
                             timeout(cbincled, (caddr_t)cb, CBIncSec*hz);
                        }
                        break;

  1. Uses the cmd argument to perform the appropriate ioctl operation. [Return to example]

  2. When cmd evaluates to CBINC&0xFF, the ioctl operation starts incrementing the lights on the TURBOchannel test board. [Return to example]

  3. Determines the start of the increment function by checking the ledflag member of the CB structure associated with this CB device. [Return to example]

  4. If the increment function has not started, sets the flag for the LED increment function, then starts the timer by calling the timeout kernel interface.

    The timeout kernel interface takes three arguments:

    The timeout interface initializes a callout queue element. [Return to example]


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


6.13.3    Setting the I/O Mode

The following code sets the I/O mode for the ioctl operations to either programmed I/O or DMA I/O:

case CBPIO&0xFF: [1] cb->iomode = CBPIO; break;

case CBDMA&0xFF: [2] cb->iomode = CBDMA; break;

  1. When cmd evaluates to CBPIO&0xFF, the ioctl operation sets the I/O mode to programmed I/O for this CB device. [Return to example]

  2. When cmd evaluates to CBDMA&0xFF, the ioctl operation sets the I/O mode to DMA I/O for this CB device. [Return to example]


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


6.13.4    Performing an Interrupt Test

The following code tests the interrupt operation:

             case CBINT&0xFF: [1]
                     timecnt = 10; [2]
                     cb->intrflag = 0; [3]

tmp = read_io_port(cb->cbr | CB_TEST, 4, 0); [4]

tmp = CB_INTERUPT|(read_io_port(cb->cbr | CB_CSR, 4, 0)&0xf000); [5] write_io_port(cb->cbr | CB_CSR, 4, 0, tmp); [6] mb(); [7]

write_io_port(cb->cbr | CB_TEST, 4, 0, 1); [8] mb(); [9]

while ((cb->intrflag == 0) && (timecnt > 0)) { [10] write_io_port(cb->cbr | CB_CSR, 4, 0, tmp); mb(); timecnt --; }

tmp = read_io_port(cb->cbr | CB_TEST, 4, 0); [11]

return(timecnt == 0); [12]

  1. When cmd evaluates to CBINT&0xFF, the ioctl operation performs an interrupt test. [Return to example]

  2. Initializes the timeout loop count variable, timecnt, to the value 10. [Return to example]

  3. Clears the interrupt flag by setting the intrflag member of the cb_unit structure associated with this CB device to the value zero (0). Section 6.13.1 shows the declaration of the pointer to the cb_unit data structure called cb. [Return to example]

  4. Clears the go bit by calling 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.

    The read_io_port interface takes three arguments:

    Upon successful completion, read_io_port returns the data read from the go bit device register to the tmp variable. [Return to example]

  5. Performs a bitwise inclusive OR operation that assigns the 16-bit read/write CSR/LED register to the temporary holding variable. This operation uses two values. The first value is represented by the constant CB_INTERUPT. Section 6.2 shows that this constant is currently defined as 0x0e00. The second value is the result of the bitwise AND operation of the value returned by read_io_port and the value 0xf000. These 4 bits contain the current LED state.

    The result of these operations produces the value, which is assigned to the tmp variable. [Return to example]

  6. Calls the write_io_port interface to load enables and LEDs.

    The write_io_port interface takes four arguments:

    [Return to example]

  7. Calls the mb kernel interface to ensure that a write to I/O space has completed. [Return to example]

  8. Writes the value 1 to the specified location by calling the write_io_port interface. For the first argument, the location is the result of ORing the I/O handle with the go bit device register offset. This register value is defined by the CB_TEST device register offset associated with this CB device. For the fourth argument, the data to be written is the value 1. [Return to example]

  9. Calls mb again to ensure that a write to I/O space has completed. [Return to example]

  10. The interrupt flag, cb->intrflag, is set to a nonzero value by cbintr if an interrupt is received. Section 6.15 discusses cbintr.

    While the interrupt flag is equal to the value zero (0) and the timeout loop count variable is greater than zero (0), cbioctl executes the following statements:

    This section of code executes until the interrupt flag is set and the timeout loop counter expires. [Return to example]

  11. Ensures that the go bit is cleared by calling the read_io_port interface. [Return to example]

  12. Returns to the ioctl system call. If the interrupt is started before the timeout loop count expires, cbioctl returns a zero (0) value to indicate success. If the timeout count expires, cbioctl returns a nonzero (1) value to indicate failure. [Return to example]


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


6.13.5    Returning a ROM Word, Updating the CSR, and Stopping Increment of the Lights

The following code returns a ROM word, updates the CSR, and then stops incrementing the lights on the TURBOchannel test board:

             case CBROM&0xFF:           [1]
                    tmp = *data;        [2]
                    if(tmp < 0 || tmp >= 32768*4+4*4) [3]
                            return(-tmp);
                    tmp <<= 1;
                    addr = (int *)&(cb->cbad[tmp]); [4]
                    *data = *addr;        [5]
                    break;

case CBCSR&0xFF: [6] write_io_port(cb->cbr | CB_CSR, 4, 0, read_io_port(cb->cbr | CB_CSR, 4, 0)); [7] mb(); [8] *data = read_io_port(cb->cbr | CB_CSR, 4, 0); [9] break;

case CBSTP&0xFF: [10] cb->ledflag = 0; break;

default: [11] return(EINVAL); } return(0); [12] }

  1. When cmd evaluates to CBROM&0xFF, the ioctl operation returns a ROM word for this CB device by executing the statements from 2 to 5. [Return to example]

  2. Gets the specified byte offset from the argument that is a kernel address. [Return to example]

  3. If the byte offset is not in the valid range of 32k words + 4 registers, returns the byte offset to the ioctl system call to indicate that it is out of range. [Return to example]

  4. Gets the ROM base address from the cbad member of the CB structure associated with this CB device.

    The cbad member provides the base address of the ROM. Because cbad is type cast as an int *, the tmp variable is used as an index to determine how many bytes to go into the ROM, and the resulting address is used to fetch the contents.
    [Return to example]

  5. Returns the word from the TURBOchannel test board. [Return to example]

  6. When cmd evaluates to CBCSR&0xFF, the ioctl operation updates and returns the CSR for this CB device by executing the statements from 7 to 9. [Return to example]

  7. Reads from and writes to this CB device's 16-bit read/write CSR/LED register by calling the read_io_port and write_io_port interfaces. [Return to example]

  8. Calls the mb kernel interface to ensure that a write to I/O space has completed. [Return to example]

  9. Returns the CSR from the TURBOchannel test board by calling the read_io_port interface. [Return to example]

  10. When cmd evaluates to CBSTP&0xFF, the ioctl operation stops incrementing the lights on the next timeout by clearing the LED increment function flag. [Return to example]

  11. Returns an error to indicate that the default is the error case. [Return to example]

  12. Upon successful completion, cbioctl returns the value zero (0) to the ioctl system call. [Return to example]


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


6.14    Increment LED Section

The cbincled interface called by the softclock kernel interface CBIncSec seconds after the last timeout call. If the increment flag is still set, cbincled increments the pattern in the high four LEDs of the LED/CSR register and restarts the timeout to recall later.

The following code shows the implementation of the cbincled interface:

cbincled(cb) struct cb_unit *cb; [1]

{ int tmp;
 
tmp = read_io_port(cb->cbr | CB_CSR, [2] 4, 0); tmp -= 0x1000; write_io_port(cb->cbr | CB_CSR, 4, 0, tmp); if(cb->ledflag != 0) { [3] timeout(cbincled, (caddr_t)cb, CBIncSec*hz); } return; }

  1. Declares a pointer to the cb_unit data structure associated with this CB device and calls it cb. Section 6.6 shows the declaration of cb_unit.

    This argument is specified in the callout to the timeout interface. [Return to example]

  2. Calls the read_io_port and write_io_port interfaces to increment the lights. Because the LEDs are on when a bit is zero (0), a subtraction is done to accomplish the increment. [Return to example]

  3. If the increment function flag is still set, restarts the timer by calling the timeout kernel interface and returns to softclock.

    The increment function flag is stored in the ledflag member of the cb_unit data structure associated with this CB device.

    The timeout kernel interface takes three arguments:

    The timeout interface initializes a callout queue element. [Return to example]


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


6.15    Interrupt Section

The cbintr interface clears the go bit and sets a flag to indicate that an interrupt occurred.

The following code shows the implementation of the cbintr interface:

cbintr(ctlr) int ctlr; [1] { int tmp; [2] struct cb_unit *cb; [3] cb = &cb_unit[ctlr]; [4] tmp = read_io_port(cb->cbr | CB_TEST, [5] 4, 0); cb->intrflag++; [6]

return; [7] }

  1. Declares a variable to contain the controller number, which is passed in by the operating system interrupt code. [Return to example]

  2. Declares a temporary variable to hold the go bit. [Return to example]

  3. Declares a pointer to the cb_unit data structure associated with this CB device and calls it cb. Section 6.6 shows the declaration of cb_unit. [Return to example]

  4. Sets the pointer to the cb_unit structure to the address of the unit data structure associated with this CB device. The ctlr argument is used as an index into the array of cb_unit structures associated with this CB device. [Return to example]

  5. Calls the read_io_port interface to read the test register to clear the go bit. 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.

    The read_io_port interface takes three arguments:

    Upon successful completion, read_io_port returns the data read from the go bit device register to the tmp variable. [Return to example]

  6. Sets the interrupt flag to indicate that an interrupt occurred.

    The flag value is contained in the intrflag member of the cb_unit data structure associated with this CB device. [Return to example]

  7. Returns to the operating system interrupt code. [Return to example]