Chapter 48. Hardware Driver Interface

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]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 by block_num. The actual quantity of data transferred depends on the disk's sector size, which can be obtained using the CYG_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 the transfer_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 by block_num. The actual quantity of data transferred depends on the disk's sector size, which can be obtained using the CYG_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 the transfer_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. The xbuf and *len arguments describe a buffer into which the data will be put. The function should return true 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 return false 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. The xbuf and *len arguments describe a buffer in which the data is supplied. The function should return true if the key type is supported and the buffer of the correct length and the data appears valid. The function should return false 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 the disk_disconnected() callback to prepare the generic layer for a potential media change. The xbuf 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() or write() 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.