Chapter 48. Hardware Driver Interface
Table of Contents
While the DISK I/O package provides the top level, hardware independent, part of each disk driver, the actual hardware interface is handled by a hardware dependent interface module. To add support for a new disk device, the user should be able to use the existing hardware independent portion and just add their own interface driver which handles the details of the actual device. The user should have no need to change the hardware independent portion.
The interfaces used by the disk driver and disk implementation
modules are contained in the file <cyg/io/disk.h>
.
Note | |
---|---|
In the sections below we use the notation <<xx>> to mean a module specific value, referred to as “xx” below. |
48.1. DevTab Entry
The interface module contains the devtab entry (or entries if a single module supports more than one interface). This entry should have the form:
BLOCK_DEVTAB_ENTRY(<<module_name>>, <<device_name>>, 0, &cyg_io_disk_devio, <<module_init>>, <<module_lookup>>, &<<disk_channel>> );
Arguments
-
module_name
- The "C" label for this devtab entry
-
device_name
-
The "C" string for the
device. E.g.
/dev/serial0
. -
cyg_io_disk_devio
- The table of I/O functions. This set is defined in the hardware independent disk driver and should be used exactly as shown here.
-
module_init
- The module initialization function.
-
module_lookup
- The device lookup function. This function typically sets up the device for actual use, turning on interrupts, configuring the controller, etc.
-
disk_channel
- This table (defined below) contains the interface between the interface module and the disk driver proper.
48.2. Disk Controller Structure
The arrangement of disk hardware usually has a number of physical disks connected to a common controller. For example, each IDE interface connects to just two disk devices, a SCSI controller may be connected to several disks. The important feature to consider here is that any current data transfer for any one disk on a controller prevents transfers being started on any other disks on that controller until it is finished. Disk controllers are therefore the level at which concurrency and interrupt controls must be implemented.
Each disk controller is created by the macro:
DISK_CONTROLLER(l, dev_priv)
Arguments
-
l
- The "C" label for this structure.
-
dev_priv
- A placeholder for any device specific data for this controller.
48.3. Disk Channel Structure
Each physical disk connected to a controller is represented by a disk channel. Each channel is defined with the following macro:
DISK_CHANNEL(l, funs, dev_priv, controller, mbr_supp, max_part_num )
Arguments
-
l
- The "C" label for this structure.
-
funs
- The set of interface functions (see below).
-
dev_priv
- A placeholder for any device specific data for this channel.
-
controller
- Pointer to controller to which this disk channel is attached.
-
mbr_supp
- Does this disk support partitioning.
-
max_part_num
- The maximum number of partitions to be supported.
The interface from the hardware independent driver into the hardware
interface module is contained in the funs
table. This is defined by the DISK_FUNS
macro.
If the space for the channel has been allocated elsewhere, the following macro may be used to initialise it:
DISK_CHANNEL_INIT(dc, funs, dev_priv, controller, disk_info, part_dev_tab, part_chan_tab, part_tab, mbr_supp, max_part_num )
The arguments are as for DISK_CHANNEL()
except for the following:
Arguments
-
dc
- The name of an object of type disk_channel. This object will be initialised by the macro.
-
disk_info
- The name of an object of type disk_info.
-
part_dev_tab
- The name of an array of objects of type struct cyg_devtab_entry. The number of array members must equal max_part_num, plus one.
-
part_chan_tab
- The name of an array of objects of type disk_channel. The number of array members must equal max_part_num.
-
part_tab
- The name of an array of objects of type cyg_disk_partition_t. The number of array members must equal max_part_num.
48.4. Disk Functions Structure
DISK_FUNS(l, read, write, get_config, set_config)
Arguments
-
l
- The "C" label for this structure.
-
read
Cyg_ErrNo (*read)(disk_channel *priv, void *buf, cyg_uint32 len, cyg_uint32 block_num)
This function reads
len
sectors of data from the disk at the sector number given byblock_num
. The actual quantity of data transferred depends on the disk's sector size, which can be obtained using theCYG_IO_GET_CONFIG_DISK_INFO
key.If the read completes immediately, or the low level driver is configured to do all IO synchronously, this function will return
ENOERR
, and if it fails will return a negative error code, for example-EIO
. If the function returns-EWOULDBLOCK
then it has only started the transfer and will indicate its completion by calling thetransfer_done
callback.-
write
Cyg_ErrNo (*write)(disk_channel *priv, void *buf, cyg_uint32 len, cyg_uint32 block_num)
This function writes
len
sectors of data to the disk at the block given byblock_num
. The actual quantity of data transferred depends on the disk's sector size, which can be obtained using theCYG_IO_GET_CONFIG_DISK_INFO
key.If the write completes immediately, or the low level driver is configured to do all IO synchronously, this function will return
ENOERR
, and if it fails will return a negative error code, for example-EIO
. If the function returns-EWOULDBLOCK
then it has only started the transfer and will indicate its completion by calling thetransfer_done
callback.-
get_config
bool (*get_config)(serial_channel *priv, cyg_uint32 key, const void *xbuf, cyg_uint32 *len); )
This function is used to get configuration data from the device. The
key
argument defines the configuration data to be fetched. Thexbuf
and*len
arguments describe a buffer into which the data will be put. The function should returntrue
if the key type is supported and the buffer of sufficient length to contain the data. The value of*len
should be updated to actual length of the data returned. The function should returnfalse
if the driver cannot support the key value or the buffer is of insufficient length.The following keys may be used to get information from a disk device.
-
CYG_IO_GET_CONFIG_DISK_INFO
-
This key causes a cyg_disk_info_t
structure, as defined in
diskio.h
to be returned. -
CYG_IO_GET_CONFIG_DISK_EVENT
-
This key returns a copy of the
cyg_disk_event_t previously set
by
CYG_IO_SET_CONFIG_DISK_EVENT
.
-
-
set_config
bool (*set_config)(serial_channel *priv, cyg_uint32 key, const void *xbuf, cyg_uint32 *len); )
This function is used to change the configuration of the device. The
key
argument defines the kind of configuration data to be set. Thexbuf
and*len
arguments describe a buffer in which the data is supplied. The function should returntrue
if the key type is supported and the buffer of the correct length and the data appears valid. The function should returnfalse
if the driver cannot support the key value or the buffer is the wrong length, or the data is invalid in some other way.The following keys can be sent to a driver:
-
CYG_IO_SET_CONFIG_DISK_MOUNT
-
This is invoked from the filesystem after locating the
device driver to record that the device has been
mounted. The generic device layer records the mount
against both the partition and physical disk and passes
the call on down to the driver. The
xbuf
and*len
arguments are unused. -
CYG_IO_SET_CONFIG_DISK_UMOUNT
-
This is invoked from the filesystem to record that the
device has been unmounted. The generic device layer
records the unmount against both the partition and
physical disk and passes the call on down to the
driver. If the
chan->info->mounts
counter is zero, the driver should call thedisk_disconnected()
callback to prepare the generic layer for a potential media change. Thexbuf
and*len
arguments are unused. -
CYG_IO_SET_CONFIG_DISK_EVENT
-
This may be invoked by the application to set a disk event
callback function. The generic disk layer is mostly
responsible for handling this by recording the event
function in the disk channel structure. The call is
additionally passed down to the hardware driver so that it
may prepare the hardware, if necessary. The
xbuf
should point to a cyg_disk_event_t structure.
-
48.5. Callbacks
The interface from the hardware specific driver to the hardware
independent driver is contained in a disk_callbacks_t
structure. A pointer to this is automatically included into the disk
channel structure callbacks
field by the
DISK_CHANNEL()
macro. The
disk_callbacks_t structure contains the following
function pointers:
-
disk_init
cyg_bool (*disk_init)(struct cyg_devtab_entry *tab);
Initialize the disk. This must be called from the disk driver's init routine to initialize the device independent driver's data structures for this disk.
-
disk_connected
Cyg_ErrNo (*disk_connected)(struct cyg_devtab_entry *tab, cyg_disk_identify_t *ident);
This is called when a valid disk device has been recognised on the given disk channel. At this point, if the disk supports partitioning the disk's partition table will be read and the partitions determined. This may be called either from the driver's init routine, for fixed disks, or alternatively from the driver's lookup routine. It may also be called from other places when, for example, disk insertion is detected. All the fields of the
ident
structure must be filled in by the driver before this call is made.-
disk_disconnected
Cyg_ErrNo (*disk_disconnected)(struct disk_channel *chan);
This is called when, for example, disk removal is detected. It invalidates all the existing partition and driver information and renders the channel ready for a new disk device to be inserted.
-
disk_lookup
Cyg_ErrNo (*disk_lookup)(struct cyg_devtab_entry **tab, struct cyg_devtab_entry *sub_tab, const char *name);
This must be called from the driver's lookup function to complete the lookup process. It is here that the interpretation of the partition number element of the device name is done and a new devtab entry created for the partition if necessary.
-
disk_transfer_done
void (*disk_transfer_done)(struct disk_channel *chan, Cyg_ErrNo res);
When the call to the
read()
orwrite()
disk function returns-EWOULDBLOCK
then the driver must indicate completion of the actual transfer by calling this function. This function should not be called from an ISR, but it may be called from the DSR.
In addition to these functions in disk_callbacks_t, the hardware driver is also responsible for calling the disk event callback. The calls should be made as follows:
disk_channel *chan = <get pointer to disk channel>; … chan->event( CYG_DISK_EVENT_CONNECT, devno, chan->event_data );
The first argument should be the event being notified:
CYG_DISK_EVENT_CONNECT
as shown here, or
CYG_DISK_EVENT_DISCONNECT
. The second argument is a
device number; this is needed for devices that dynamically instantiate
disk devices, such as USB. If the driver does not do this, then this
argument should be -1. The third argument is the user data value
passed in when the callback was registered.
The driver may call this function at any time and from any context other than an ISR. Normally it will be called either from a DSR or from a thread context. By default, the generic disk layer will install a dummy function in the disk channel structure, so the driver can always make the call without needing to test for a NULL pointer. A CONNECT event call should be made when the driver detects that a new device has been inserted into the drive, and an DISCONNECT event call should be made when the device is removed.
A CONNECT event call should also be made if a disk device is already
connected when the driver observes the application registering for
notification of disk events by use of the
CYG_IO_SET_CONFIG_DISK_EVENT
cyg_io_set_config()
operation. However, this only
applies to connected disks - the driver does not indicate DISCONNECT events
for unconnected disks.
48.6. Putting It All Together
The above descriptions, while strictly useful as documentation, do not really show how it all gets put together to make a device driver. The following example of how to create the data structures for a device driver, for a standard PC target, are derived from the eCosPro IDE disk driver.
The first thing to do is to define the disk controllers:
static ide_controller_info_t ide_controller_info_0 = { ctlr: 0, vector: HAL_IDE_INTERRUPT_PRI }; DISK_CONTROLLER( ide_disk_controller_0, ide_controller_info_0 ); static ide_controller_info_t ide_controller_info_1 = { ctlr: 1, vector: HAL_IDE_INTERRUPT_SEC }; DISK_CONTROLLER( ide_disk_controller_1, ide_controller_info_1 );
A typical PC target has two IDE controllers, so we define two
controllers. The ide_controller_info_t structure is
defined by the driver and contains information needed to access the
controller. In this case this is the controller number, zero or one,
and the interrupt vector it uses. The
DISK_CONTROLLER()
macro generates a system defined
controller structure and populates it with a pointer to the matching
controller info structure.
The next step is to define the disk functions that will be called to perform data transfers on this driver. These functions the main part of the driver, together with the init and lookup functions and any ISR and DSR functions.
DISK_FUNS(ide_disk_funs, ide_disk_read, ide_disk_write, ide_disk_get_config, ide_disk_set_config );
We can now start generating per-disk-channel data structures. To make
this easier we define a macro, IDE_DISK_INSTANCE()
to make this easier.
#define IDE_DISK_INSTANCE(_number_,_ctlr_,_dev_,_mbr_supp_) \ static ide_disk_info_t ide_disk_info##_number_ = { \ num: _number_, \ ctlr: &ide_controller_info_##_ctlr_, \ dev: _dev_, \ }; \ DISK_CHANNEL(ide_disk_channel##_number_, \ ide_disk_funs, \ ide_disk_info##_number_, \ ide_disk_controller_##_ctlr_, \ _mbr_supp_, \ 4 \ ); \ BLOCK_DEVTAB_ENTRY(ide_disk_io##_number_, \ CYGDAT_IO_DISK_IDE_DISK##_number_##_NAME, \ 0, \ &cyg_io_disk_devio, \ ide_disk_init, \ ide_disk_lookup, \ &ide_disk_channel##_number_ \ );
The first thing this macro does is generate an instance of the
ide_disk_info_t. This is a driver-defined structure to
contain any info that does not fit in the system defined
structures. In this case the important things are the number of the
device on the controller, zero or one mapping to master or slave, and
a pointer to the driver-defined controller structure. The
DISK_CHANNEL()
macro creates a disk channel object
and populates it with the function list defined earlier, a pointer to the
matching local info structure just defined, and a pointer to the
controller it is attached to. Finally, a device table entry is
created. This uses linker features to install an entry into the device
table that allows the IO subsystem to locate this device.
Finally we need to instantiate all the channels that this driver will support.
IDE_DISK_INSTANCE(0, 0, 0, true); IDE_DISK_INSTANCE(1, 0, 1, true); IDE_DISK_INSTANCE(2, 1, 0, true); IDE_DISK_INSTANCE(3, 1, 1, true);
Each invocation of IDE_DISK_INSTANCE()
generates
all the data structures needed to access each possible physical disk
that may be present.
2024-03-18 | eCosPro Non-Commercial Public License |