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
The source code uses the following convention:
extern int hz; [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]
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:
-
An EPROM address space
-
A 32-bit ADDRESS register with bits scrambled for direct use as a TURBOchannel
DMA address
-
A 32-bit DATA register used for programmed I/O and as the holding
register for DMA
-
A 16-bit register used to control four light-emitting diodes (LEDs) on
the TURBOchannel option card, a 1-bit TEST register, and a 16-bit
control status register (CSR)
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:
-
Reads from the data register on the test board to words in system
memory
-
Writes to the data register on the test board from words in system
memory
-
Tests the interrupt logic on the test board
-
Reads one 32-bit word from the test board address (ROM/register) space
into system memory
-
Updates, reads, and returns the 32-bit CSR value
-
Starts and stops clock-driven incrementing of the four spare LEDs on the
board
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]
-
Defines a constant called
CB_REL_LOC.
[Return to example]
-
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]
-
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]
-
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]
-
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]
-
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]
-
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]
-
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]
-
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]
-
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]
-
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]
-
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]
-
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]
-
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]
-
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]
-
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]
-
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]
-
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]
-
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]
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]
-
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]
-
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]
-
Defines a constant used by the
/dev/cb
driver's
cb_register_major_number
interface when it reserves a major number.
[Return to example]
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
};
-
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]
-
Declares the driver interfaces for the
/dev/cb
device driver.
[Return to example]
-
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]
-
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.
-
cbprobe,
the driver's
probe
interface
Section 6.7.1
shows how to implement
cbprobe.
-
cbattach,
the driver's
cattach
interface
Section 6.7.2
shows how to implement
cbattach.
-
cb,
the device name
-
cbinfo,
which references the array of pointers to the previously declared
controller
structures
You index this array with the controller number as specified in the
ctlr_num
member of the
controller
structure.
-
cb_ctlr_unattach,
the driver's controller
unattach
interface
The
cb_ctlr_unattach
interface removes the
controller
structure associated with the TURBOchannel test board from the list of
controller
structures that it handles.
Section 6.7.3
shows how to implement
cb_ctlr_unattach.
Device drivers that are dynamically configured into the kernel use the
unattach
interface.
[Return to example]
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]
-
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]
-
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]
-
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]
-
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]
-
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]
-
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]
-
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]
-
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]
-
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]
-
Defines the constant
string
"*"
to indicate that this driver can operate on any bus.
[Return to example]
-
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]
-
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]
-
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]
-
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]
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
-
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]
-
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]
-
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:
-
attached
Stores a value to indicate that the specified
CB
device is attached.
Section 6.7.2
shows that the
cbattach
interface sets this member to a value that indicates the device is
attached.
-
opened
Stores a value to indicate that the specified
CB
device is opened.
Section 6.9.1
shows that the
cbopen
interface sets this member to a value that indicates the device is open;
and
Section 6.9.2
shows that the
cbclose
interface clears the value to indicate the device is closed.
-
iomode
Stores the read/write mode to one of the bits represented by
these constants defined in
cbreg.h:
CBPIO
(programmed I/O)
and
CBDMA
(DMA I/O read code).
Section 6.13.3
shows that the
cbioctl
interface sets this member to these constants.
-
intrflag
Stores a flag value used to test for an interrupt.
Section 6.15
shows that
cbintr
sets the interrupt flag; and
Section 6.13.4
shows that
cbioctl
clears the interrupt flag.
-
ledflag
Stores a flag value for the LED increment function.
Section 6.8.7
shows that
cb_configure
turns off the LED increment function; and
Section 6.13.2
shows that
cbioctl
sets the LED increment flag.
Section 6.13.5
shows that
cbioctl
sets the flag so that it turns off the LED increment
function.
-
adapter
Stores the TC slot number.
Section 6.7.2
shows that
cbattach
sets this member to the slot number for this
CB
controller.
-
cbad
Stores the ROM base address.
Section 6.7.2
shows that
cbattach
sets this member to the address of the data to be checked for read
accessibility.
-
cbr
Stores the I/O handle for the device registers associated with this
CB
device.
An I/O handle is a data entity that is of type
io_handle_t.
You pass this I/O handle to the
read_io_port
and
write_io_port
interfaces.
Section 6.7.2
shows that
cbattach
sets this member to point to this
CB
device's registers.
Section 6.2
shows the declaration of the device register offsets used with the I/O
handle.
-
cbbuf
Stores a pointer to a
buf
structure.
Section 6.7.2
shows that
cbattach
sets this member to point to this
CB
device's buffer header.
[Return to example]
-
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]
-
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]
-
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]
-
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:
-
cbopen,
the driver's
open
interface
Section 6.9.1
shows how to implement
cbopen.
-
cbclose,
the driver's
close
interface
Section 6.9.2
shows how to implement
cbclose.
-
cbread,
the driver's
read
interface
Section 6.10.1
shows how to implement
cbread.
-
cbwrite,
the driver's
write
interface
Section 6.10.2
shows how to implement
cbwrite.
-
cbioctl,
the driver's
ioctl
interface
Section 6.13.1
shows how to implement
cbioctl.
[Return to example]
-
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]
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
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]
}
-
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]
-
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]
-
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]
-
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]
-
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]
-
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]
-
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]
-
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]
-
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]
-
Sets the
intr
member of the
info
data structure
to
cbintr,
the
/dev/cb
device driver's interrupt handler.
[Return to example]
-
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]
-
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]
-
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]
-
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]
-
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]
-
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]
-
Increments the number of instances of this controller found on
the system.
[Return to example]
-
Returns the value 1 to indicate success status
because the TURBOchannel initialization code already verified that the
device was present.
[Return to example]
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]
}
-
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]
-
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]
-
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]
-
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]
-
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]
-
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]
-
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]
-
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]
-
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]
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]
}
-
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]
-
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]
-
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]
-
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]
-
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]
-
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]
-
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]
-
Returns
ESUCCESS
to the bus code upon successful completion of the
tasks performed by the
cb_ctlr_unattach
interface.
[Return to example]
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
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
-
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]
-
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]
-
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]
-
The
outdata
and
outdatalen
formal parameters are not currently used.
[Return to example]
-
Declares a variable called
cdevno
to temporarily store the major device number for the
CB
device.
[Return to example]
-
Declares a variable called
retval
to store the return value from the
cb_register_configuration
interface.
[Return to example]
-
Declares a variable called
i
to be used in the
for
loop when
cb_configure
unconfigures the driver.
[Return to example]
-
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]
-
Declares a forward reference to the
cbincled
interface.
Section 6.14
shows the implementation of
cbincled.
[Return to example]
-
Defines a constant that represents the number of configuration lines in
the
sysconfigtab
file fragment for the
/dev/cb
device driver.
[Return to example]
-
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]
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;
-
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]
-
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:
-
Debugs
cfgmgr
loading problems with the
cb_attributes
structure
-
Displays the contents and status of the attributes in the
/etc/sysconfigtab
database for the
/dev/cb
driver on the console terminal
-
Reports the
cfgmgr
framework's status that indicates a failure to load any of the attribute
fields into the
cb_attributes
structure
[Return to example]
-
If the
Module_Config_Name
attribute is the null string, initializes the attribute to the string
"cb".
[Return to example]
-
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]
-
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:
-
The
callback_cb_register_configuration
interface is invoked at the
CFG_PT_PRECONFIG
point. It is called third among the callback routines registered at this
point. This interface checks the configuration state of the
driver, then calls
cb_register_configuration
to configure the driver into the system.
Section 6.8.3
and
Section 6.8.4
describe the
callback_cb_register_configuration
and
cb_register_configuration
interface.
-
The
callback_0
routine is invoked at the
CFG_PT_PRECONFIG
point. It is called fifth among the callback routines registered at
this point.
This callback routine simply prints a message at the console terminal.
It is included here only to demonstrate the ordering of callback
routines within a given configuration point.
-
The
callback_1
routine is invoked at the
CFG_PT_PRECONFIG
point. It is called fourth among the callback routines registered at
this point.
This callback routine simply prints a message at the console terminal.
It is included here only to demonstrate the ordering of callback
routines within a given configuration point.
-
The
callback_2
routine is invoked at the
CFG_PT_PRECONFIG
point. It is called first among the callback routines registered at
this point.
This callback routine simply prints a message at the console terminal.
It is included here only to demonstrate the ordering of callback
routines within a given configuration point.
-
The
callback_3
routine is invoked at the
CFG_PT_PRECONFIG
point. It is called second among the callback routines registered at
this point.
This callback routine simply prints a message at the console terminal.
It is included here only to demonstrate the ordering of callback
routines within a given configuration point.
-
The
callback_cb_register_major_number
interface is invoked at the
CFG_PT_POSTCONFIG
point. This interface checks the configuration state of the driver, then
calls the
cb_register_major_number
interface to register the driver's major number with the system.
Section 6.8.5
and
Section 6.8.6
describe the
callback_cb_register_major_number
and
cb_register_major_number
interfaces.
[Return to example]
-
If the driver is dynamically configured, performs the following steps:
-
Sets the
cb_is_dynamic
global variable to
SUBSYSTEM_DYNAMICALLY_CONFIGURED.
-
Calls the
cb_register_configuration
interface to configure the driver into the system.
-
If the call to
cb_register_configuration
fails, exits with the error status returned from
cb_register_configuration.
-
If the call to
cb_register_configuration
succeeds, calls
configure_driver
interface to perform dynamic configuration of the driver.
-
Calls
cb_register_major_number
to add the device to the device switch table.
-
If the call to
cb_register_major_number
fails, exits with the error status value returned by
cb_register_major_number.
[Return to example]
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]
}
-
Creates a
controller_config
structure called
ctlr_register
and a pointer to that structure called
ctlr_register_ptr.
[Return to example]
-
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]
-
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]
-
Returns
ESUCCESS
if the driver is successfully configured.
[Return to example]
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;
}
}
-
Defines the
callback_cb_register_configuration
interface to be called with the following arguments:
-
point
is the configuration point at which the interface is called.
-
order
is the relative order in which the interface is called within its
configuration point.
-
args
is an optional argument to be passed to the interface.
-
event_arg
is an optional argument that the configuration manager passes to the interface.
[Return to example]
-
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]
-
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]
-
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]
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);
}
-
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]
-
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]
-
If the call to
devsw_add
fails, returns the
ENODEV
error status.
[Return to example]
-
Initializes the
cb_devno
global variable to the device major number.
[Return to example]
-
Initializes the
cb_config
global variable to
TRUE
to indicate that the device has been successfully configured.
[Return to example]
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;
}
}
-
Defines the
callback_cb_register_major_number
interface to be called with the following arguments:
-
point
is the configuration point at which the interface is called.
-
order
is the relative order in which the interface is called within its
configuration point.
-
args
is an optional argument to be passed to the interface.
-
event_arg
is an optional argument that the configuration manager passes to the interface.
[Return to example]
-
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]
-
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]
-
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]
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;
-
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]
-
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]
-
As long as the variable
i
is less than the number of controllers found by
cbprobe,
executes the following:
[Return to example]
-
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]
-
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]
-
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]
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]
}
-
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]
-
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]
-
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]
-
To indicate that
the
/dev/cb
driver's
cb_configure
interface completed successfully, returns the
ESUCCESS
status value.
[Return to example]
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
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]
}
-
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]
-
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]
-
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]
-
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]
-
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]
-
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]
-
Returns success to the
open
system call, indicating a successful open of this
CB
device.
[Return to example]
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]
}
-
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]
-
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]
-
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]
-
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]
-
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]
-
Returns success to the
close
system call, indicating a successful close of this
CB
device.
[Return to example]
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
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));
}
-
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]
-
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]
-
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]
-
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]
-
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]
-
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]
-
Initializes the
err
variable to the value zero (0) to indicate no error has occurred yet.
[Return to example]
-
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]
-
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]
-
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]
-
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]
-
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:
-
The first argument specifies
an I/O handle that you can use to reference a device register or memory
located in bus address space (either I/O space or memory space).
This I/O handle references a device register in the bus address space
where the read operation originates.
You can perform standard C mathematical operations (addition and
subtraction only) on the I/O handle.
In this call, the
/dev/cb
driver ORs the I/O handle with the 32-bit read/write data register
represented by
CB_DATA.
-
The second argument specifies
the width (in bytes) of the data to be read.
Valid values are 1, 2, 3, 4, and 8.
Not all CPU platforms or bus adapters support all of these values.
In this call, the
/dev/cb
driver passes the value 4.
-
The third argument specifies
flags to indicate special processing requests.
In this call, the
/dev/cb
driver passes the value zero (0).
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]
-
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:
-
A pointer to the kernel buffer in system virtual space
In this call, this pointer is the 32-bit read/write data contained in the
tmp
variable.
-
The number of bytes to be moved
In this call, the number of bytes to move is contained in the
cnt
variable, which is always 4 bytes.
-
A pointer to a
uio
structure
This structure describes the current position within a logical user
buffer in user virtual space.
[Return to example]
-
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]
-
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:
-
A pointer to the driver's
strategy
interface
In this call, the driver's
strategy
interface is
cbstrategy.
Section 6.11.2
shows how to set up the
cbstrategy
interface.
-
A pointer to a
buf
structure
In this call, the
buf
structure is the one associated with this
CB
device.
This structure contains information such as the binary status flags, the
major/minor device numbers, and the address of the associated buffer.
This buffer is always a special buffer header owned
exclusively by the device for handling I/O requests.
-
The device number, which in this call is contained in the
dev
argument
-
The read/write flag
In this call the read/write flag is the constant
B_READ.
-
A pointer to the
minphys
interface
In this call, the driver's
minphys
interface is
cbminphys.
Section 6.11.1
shows how to set up the
cbminphys
interface.
-
A pointer to a
uio
structure
[Return to example]
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));
}
-
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]
-
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]
-
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]
-
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]
-
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]
-
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]
-
Initializes the
err
variable to the value zero (0) to indicate no error has occurred yet.
[Return to example]
-
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]
-
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]
-
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]
-
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]
-
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]
-
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:
-
The first argument specifies
an I/O handle that you can use to reference a device register or memory
located in bus address space (either I/O space or memory space).
This I/O handle references a device register in the bus address space
where the write operation occurs.
You can perform standard C mathematical operations (addition and
subtraction only) on the I/O handle.
In this call, the
/dev/cb
driver ORs the I/O handle with the 32-bit read/write data register
represented by
CB_DATA.
-
The second argument specifies
the width (in bytes) of the data to be written.
Valid values are 1, 2, 3, 4, and 8.
Not all CPU platforms or bus adapters support all of these values.
In this call, the
/dev/cb
driver passes the value 4.
-
The third argument specifies
flags to indicate special processing requests.
In this call, the
/dev/cb
driver passes the value zero (0).
-
The fourth argument specifies
the data to be written to the specified device register in bus address
space.
In this call,
the
/dev/cb
driver passes the value stored in the
tmp
variable.
Section 6.10.1
shows that the
read_io_port
interface stored this value in the
tmp
variable.
[Return to example]
-
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]
-
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]
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
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;
}
-
Declares a pointer to a
buf
structure and calls it
bp.
[Return to example]
-
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]
6.11.2 Setting Up the cbstrategy Interface
The
cbstrategy
interface performs the following tasks:
-
Initializes the
buf
structure for data transfer
-
Tests the low-order 2 bits and uses the internal buffer
-
Converts the buffer virtual address
-
Converts the 32-bit physical address
-
Starts I/O and checks for timeouts
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]
-
Declares a pointer to a
buf
structure and calls it
bp.
[Return to example]
-
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]
-
Declares a pointer to a
controller
structure and calls it
ctlr.
[Return to example]
-
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]
-
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]
-
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]
-
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]
-
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]
-
Declares a variable called
err
that stores the error status returned by
cbstart.
[Return to example]
-
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]
-
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]
-
Declares a temporary holding variable called
tmp.
Section 6.11.2.4
shows that the
CB_SCRAMBLE
macro uses this variable.
[Return to example]
-
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]
-
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]
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]
-
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]
-
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]
-
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]
-
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]
-
Copies the buffer's virtual address into the
buff_addr
variable for use by the driver.
[Return to example]
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:
-
Exit with an error
-
Take some action to ensure the words are aligned on the transfer
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]
}
}
}
-
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]
-
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]
-
If the transfer type is a write, clears the one-word temporary buffer
tmpbuffer.
[Return to example]
-
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:
-
The address in user space of the data to be copied
In this call, the address of the data is stored in the
buff_addr
variable.
Section 6.11.2.1
discusses how to set this address.
-
The address in kernel space to copy the data to
In this call, the address in kernel space is stored in the
virt_addr
variable.
Section 6.11.2.2
discusses how to set this address.
-
The number of bytes to copy
In this call, the number of bytes to copy is stored in the
b_resid
member of the pointer to the
buf
structure.
Section 6.11.2.1
shows the initialization of this member.
[Return to example]
-
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]
-
Sets
b_flags
to the bitwise inclusive OR of the read and error bits.
[Return to example]
-
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]
-
Returns with an error to
physio,
which was called by
cbwrite.
Section 6.10.2
shows the call to
physio.
[Return to example]
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]
-
Converts the buffer virtual address to a physical address for
DMA.
This interface takes two arguments:
-
The first argument specifies
a pointer to a
proc
structure.
The
vtop
interface uses the
proc
structure pointer to obtain the pmap.
In this call, the
/dev/cb
driver passes the
b_proc
member of the
buf
structure pointer associated with this
CB
device.
The
b_proc
member specifies
a pointer to the
proc
structure that represents the process performing the I/O.
-
The second argument specifies
the virtual address that
vtop
converts to a physical address.
In this call, the
/dev/cb
driver passes the
value stored in
virt_addr.
Upon successful completion,
vtop
returns the physical address associated with the specified virtual address.
[Return to example]
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]
-
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]
-
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:
-
The first argument specifies
an I/O handle that you can use to reference a device register or memory
located in bus address space (either I/O space or memory space).
This I/O handle references a device register in the bus address space
where the write operation occurs.
You can perform standard C mathematical operations (addition and
subtraction only) on the I/O handle.
In this call, the
/dev/cb
driver ORs the I/O handle with the 32-bit read/write DMA address register
represented by
CB_ADDER.
-
The second argument specifies
the width (in bytes) of the data to be written.
Valid values are 1, 2, 3, 4, and 8.
Not all CPU platforms or bus adapters support all of these values.
In this call, the
/dev/cb
driver passes the value 4.
-
The third argument specifies
flags to indicate special processing requests.
In this call, the
/dev/cb
driver passes the value zero (0).
-
The fourth argument specifies
the data to be written to the specified device register in bus address
space.
In this call,
the
/dev/cb
driver passes the value returned by
CB_SCRAMBLE
in the
tmp
variable.
[Return to example]
-
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]
-
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]
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;
}
-
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]
-
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]
-
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]
-
If the return value from
cbstart
is not zero (0),
executes the following lines because the DMA completed successfully.
[Return to example]
-
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]
-
If the
copyout
kernel interface was unable to copy data from kernel address space to
user address space,
copyout
does the following:
-
Sets the
b_error
member in the
buf
structure pointer to the value returned by
copyout.
This value indicates that the kernel address specified in the first
argument could not be accessed or that the number of bytes to copy
specified in the third argument is invalid.
-
Sets the
b_flags
member in the
buf
structure pointer to indicate an error occurred on this data transfer.
[Return to example]
-
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]
-
Calls
iodone
to indicate that the I/O transfer has completed and to initiate the return
status.
[Return to example]
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]
}
-
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]
-
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]
-
Declares a variable to contain the timeout loop count.
[Return to example]
-
Declares a variable to contain the CSR contents for status
checking.
[Return to example]
-
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]
-
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:
-
The first argument specifies
an I/O handle that you can use to reference a device register or memory
located in bus address space (either I/O space or memory space).
This I/O handle references a device register in the bus address space
where the read operation originates.
You can perform standard C mathematical operations (addition and
subtraction only) on the I/O handle.
In this call, the
/dev/cb
driver ORs the I/O handle with the go bit represented by
CB_TEST.
-
The second argument specifies
the width (in bytes) of the data to be read.
Valid values are 1, 2, 3, 4, and 8.
Not all CPU platforms or bus adapters support all of these values.
In this call, the
/dev/cb
driver passes the value 4.
-
The third argument specifies
flags to indicate special processing requests.
In this call, the
/dev/cb
driver passes the value zero (0).
Upon successful completion,
read_io_port
returns the data read from the go bit register to the
status
variable.
[Return to example]
-
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:
-
The first argument specifies
an I/O handle that you can use to reference a device register or memory
located in bus address space (either I/O space or memory space).
This I/O handle references a device register in the bus address space
where the write operation occurs.
You can perform standard C mathematical operations (addition and
subtraction only) on the I/O handle.
In this call, the
/dev/cb
driver ORs the I/O handle with the 16-bit read/write CSR/LED register
represented by
CB_CSR.
-
The second argument specifies
the width (in bytes) of the data to be written.
Valid values are 1, 2, 3, 4, and 8.
Not all CPU platforms or bus adapters support all of these values.
In this call, the
/dev/cb
driver passes the value 4.
-
The third argument specifies
flags to indicate special processing requests.
In this call, the
/dev/cb
driver passes the value zero (0).
-
The fourth argument specifies
the data to be written to the specified device register in bus address
space.
In this call,
the
/dev/cb
driver passes the value stored in the
cmd
variable.
[Return to example]
-
Calls the
mb
kernel interface to ensure that a write to I/O space has completed.
[Return to example]
-
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]
-
Calls
mb
again to ensure that a write to I/O space has completed.
[Return to example]
-
Initializes the timeout loop counter variable,
timecnt,
to the value 10.
[Return to example]
-
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]
-
Spins until the DMA has completed or the timeout loop counter expires.
Then:
-
Writes a value to the specified location by calling the
write_io_port
interface.
This call is the same as previous calls.
For the first argument, the location is the result of ORing the I/O handle
with the 16-bit read/write CSR/LED device register offset.
This register value is defined by the
CB_CSR
device register offset associated with this
CB
device.
For the fourth argument, the data to be written is stored in the
cmd
variable.
-
Calls
mb
a third time to ensure that a write to I/O space
has completed.
-
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.
-
Decrements the counter.
[Return to example]
-
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]
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
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]
-
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]
-
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]
-
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]
-
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]
-
Declares an argument that specifies the access mode of the device.
The
/dev/cb
driver does not use this argument.
[Return to example]
-
Declares a temporary holding variable called
tmp.
[Return to example]
-
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]
-
Declares a variable called
timecnt.
Section 6.13.4
shows that this variable is used in the timeout loop count.
[Return to example]
-
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]
-
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]
-
Declares a forward reference to the
cbincled
interface.
Section 6.14
shows the implementation of
cbincled.
[Return to example]
-
Sets the pointer to the
cb_unit
structure to the address of the unit data structure associated with this
CB
device.
[Return to example]
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;
-
Uses the
cmd
argument to perform the appropriate
ioctl
operation.
[Return to example]
-
When
cmd
evaluates to
CBINC&0xFF,
the
ioctl
operation starts incrementing the lights on the TURBOchannel test
board.
[Return to example]
-
Determines the start of the increment function by
checking the
ledflag
member of the
CB
structure associated with this
CB
device.
[Return to example]
-
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 first argument is a
pointer to the interface to call, which in this case is
cbincled.
-
The second argument is a single argument to be passed to the interface
specified by the first argument when it is called.
In this example, the single argument is the
pointer
to the
cb_unit
data structure associated with this
CB
device.
Because the second argument to
timeout
is of type
caddr_t,
the code performs the appropriate type-casting operation.
-
The third argument is the amount of time to delay before calling the
cbincled
interface.
The constant
CBIncSec
represents some amount of time in seconds.
The
timeout
interface initializes a callout queue element.
[Return to example]
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;
-
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]
-
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]
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]
-
When
cmd
evaluates to
CBINT&0xFF,
the
ioctl
operation performs an interrupt test.
[Return to example]
-
Initializes the timeout loop count variable,
timecnt,
to the value 10.
[Return to example]
-
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]
-
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:
-
The first argument specifies
an I/O handle that you can use to reference a device register or memory
located in bus address space (either I/O space or memory space).
This I/O handle references a device register in the bus address space
where the read operation originates.
You can perform standard C mathematical operations (addition and
subtraction only) on the I/O handle.
In this call, the
/dev/cb
driver ORs the I/O handle with the go bit device register
represented by
CB_TEST.
-
The second argument specifies
the width (in bytes) of the data to be read.
Valid values are 1, 2, 3, 4, and 8.
Not all CPU platforms or bus adapters support all of these values.
In this call, the
/dev/cb
driver passes the value 4.
-
The third argument specifies
flags to indicate special processing requests.
In this call, the
/dev/cb
driver passes the value zero (0).
Upon successful completion,
read_io_port
returns the data read from the go bit device register to the
tmp
variable.
[Return to example]
-
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]
-
Calls the
write_io_port
interface to load enables and LEDs.
The
write_io_port
interface takes four arguments:
-
The first argument specifies
an I/O handle that you can use to reference a device register or memory
located in bus address space (either I/O space or memory space).
This I/O handle references a device register in the bus address space
where the write operation occurs.
You can perform standard C mathematical operations (addition and
subtraction only) on the I/O handle.
In this call, the
/dev/cb
driver ORs the I/O handle with the 16-bit read/write CSR/LED register
represented by
CB_CSR.
-
The second argument specifies
the width (in bytes) of the data to be written.
Valid values are 1, 2, 3, 4, and 8.
Not all CPU platforms or bus adapters support all of these values.
In this call, the
/dev/cb
driver passes the value 4.
-
The third argument specifies
flags to indicate special processing requests.
In this call, the
/dev/cb
driver passes the value zero (0).
-
The fourth argument specifies
the data to be written to the specified device register in bus address
space.
In this call,
the
/dev/cb
driver passes the value stored in the
tmp
variable.
This value is the bit calculated by the
CB_INTERUPT
macro and the current LED state.
[Return to example]
-
Calls the
mb
kernel interface to ensure that a write to I/O space has completed.
[Return to example]
-
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]
-
Calls
mb
again to ensure that a write to I/O space has completed.
[Return to example]
-
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:
-
Updates the status of the 16-bit read/write CSR/LED register by calling
the
write_io_port
interface.
-
Calls
mb
again to ensure that a write to I/O space has completed.
-
Decrements the timeout loop count variable.
This section of code executes until the interrupt flag is set and the
timeout loop counter expires.
[Return to example]
-
Ensures that the go bit is cleared by calling the
read_io_port
interface.
[Return to example]
-
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]
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]
}
-
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]
-
Gets the specified byte offset from the argument that is a kernel
address.
[Return to example]
-
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]
-
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]
-
Returns the word from the TURBOchannel test board.
[Return to example]
-
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]
-
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]
-
Calls the
mb
kernel interface to ensure that a write to I/O space has completed.
[Return to example]
-
Returns the CSR from the TURBOchannel test board by calling the
read_io_port
interface.
[Return to example]
-
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]
-
Returns an error to indicate that the default is the error case.
[Return to example]
-
Upon successful completion,
cbioctl
returns the value zero (0) to the
ioctl
system call.
[Return to example]
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;
}
-
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]
-
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]
-
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 first argument is a
pointer to the interface to call, which in this case is
cbincled.
-
The second argument is a single argument to be passed to the interface
specified by the first argument when it is called.
In this example, the single argument is the
pointer
to the
cb_unit
data structure associated with this
CB
device.
Because the second argument to
timeout
is of type
caddr_t,
the code performs the appropriate type-casting operation.
-
The third argument is the amount of time to delay before calling the
cbincled
interface.
The constant
CBIncSec
represents some amount of time in seconds.
The
timeout
interface initializes a callout queue element.
[Return to example]
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]
}
-
Declares a variable to contain the controller number, which is passed in
by the operating system interrupt code.
[Return to example]
-
Declares a temporary variable to hold the go bit.
[Return to example]
-
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]
-
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]
-
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:
-
The first argument specifies
an I/O handle that you can use to reference a device register or memory
located in bus address space (either I/O space or memory space).
This I/O handle references a device register in the bus address space
where the read operation originates.
You can perform standard C mathematical operations (addition and
subtraction only) on the I/O handle.
In this call, the
/dev/cb
driver ORs the I/O handle with the go bit device register
represented by
CB_TEST.
-
The second argument specifies
the width (in bytes) of the data to be read.
Valid values are 1, 2, 3, 4, and 8.
Not all CPU platforms or bus adapters support all of these values.
In this call, the
/dev/cb
driver passes the value 4.
-
The third argument specifies
flags to indicate special processing requests.
In this call, the
/dev/cb
driver passes the value zero (0).
Upon successful completion,
read_io_port
returns the data read from the go bit device register to the
tmp
variable.
[Return to example]
-
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]
-
Returns to the operating system interrupt code.
[Return to example]