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


8    Writing a Disk Device Driver

Writing Device Drivers: Tutorial describes the data structures that device drivers need and the common operations that they perform. The interfaces are presented in a device-neutral format. Disk device drivers have the same set of interfaces as described in the tutorial, but they perform disk-specific operations within that common framework. In some cases, disk-specific operations are implemented within a single entry point. More often, several entry points must cooperate, using global data structures and common interfaces to coordinate their behavior.

Table 8-1 lists the entry points for which a disk device driver needs to provide disk-specific operations. All the other entry points described in the tutorial, such as configure, do not have disk-specific behaviors. You can implement them as described in the tutorial.

Table 8-1: Disk Device Driver Entry Points

Routine Description Section
open Opens the device. Section 8.4.1
close Closes the device. Section 8.4.3
read Reads characters from the device. Section 8.5.1
write Writes characters to the device. Section 8.5.1
strategy Reads and writes blocks of data. Section 8.5.2
ioctl Performs general-purpose I/O control. Section 8.6
size Returns the size of a partition. Section 8.7
dump Copies system memory to the dump device. Section 8.8

This chapter presents a model for disk device drivers, including the characteristics of disk devices that make them unique, the disk-specific behaviors that each entry point must support, and the interfaces that disk device drivers can use to implement them. When your driver follows this model, it can work in conjunction with Digital UNIX utilities and applications.


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


8.1    Disk Device Names

Note

The device naming rules described in this section may change in a future release.

Device special files for disks have the form shown in Figure 8-1.

Figure 8-1: Form for Device Special Files

The driver name for your disk device driver must be consistent with other devices on the system because it is referenced in several places. For example, the DEVGETINFO ioctl command returns the driver name.

The /dev/MAKEDEV script determines how device special files are made for the devices on the system. When you add a new device driver for disk devices, you can add a section to the system's MAKEDEV script or provide your own script to describe how to create your driver's device special files.


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


8.2    Disk Layout

The disk driver needs to understand the layout of the disk (the disk geometry and partitions) to access the data on the disk and to map read and write operations to specific locations on the disk. This section defines disk geometry and partitions, including how to locate the target partition for a request. Other sections in this chapter describe how the driver represents the disk layout internally and how driver interfaces access this information.


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


8.2.1    Disk Geometry

At the hardware level, disks are described in terms of cylinders, tracks, and sectors. A sector (or block) is the lowest addressable unit and consists of a number of bytes, usually 512. A track is made up of some number of sectors, and cylinders are made up of tracks. You can calculate the capacity in blocks of a typical disk as follows:

capacity = cylinders * tracks-per-cylinder * sectors-per-track

Newer disks use Zone Bit Recording (ZBR), which takes advantage of the fact that the outer edge of the disk platter has more media space and can therefore have more sectors per track. Manufacturers of these disks commonly divide them into zones, with the number of sectors per track changing from zone to zone. The above formula does not apply to ZBR disks. Instead, each zone must be calculated separately, then totaled. Unfortunately, outside of the manufacturer's disk specification manual, the number of zones and their values are rarely known. As a result, disk geometry has become less representative of the actual disk media.

In the past, file systems depended on the geometry to optimize disk access patterns. Today, most file systems are concerned only with the disk's total capacity and block size. The remaining geometry information is used as hints for optimization, if used at all.

When reporting disk geometry values, a driver should return either the values obtained from the device or a set of values that best describes the device. See Writing Device Drivers: Reference for a description of the disk geometry data structure, DEVGEOMST.


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


8.2.2    Disk Partitions

Because a disk can be quite large, a device driver does not usually present it as a single sequence of blocks spanning the entire disk. At the software level, a disk is divided into partitions -- logical divisions the driver uses to access particular regions of the disk. Digital UNIX allows up to 8 partitions on a physical disk. The MAXPARTITION constant defines this limit. By convention, partitions are labeled from a to h.

Each partition is treated as a logical disk, and data in the partition is accessed by logical block numbers, beginning with 0 at the beginning of the partition. Knowing the partition number and the partition offset, a disk device driver can convert a logical block number into a physical block number, as follows:

physical_block = partition_offset[partition_number] + logical_block

For disks to be partitioned individually and for the disk device driver to retrieve the partition information prior to the file system being mounted, the driver writes a label to a reserved area of the disk, called the boot block. The disk label contains a description of the partitions on the disk, as well as any customized geometry information. However, the offset of the label within the boot block can vary. Under Digital UNIX, the boot block is in physical block 0. The driver uses the LABELSECTOR constant to reference this location. This constant is defined in the /usr/sys/include/sys/disklabel.h include file.

A partition map within the disk label stores the following information about each partition on the disk: its physical block offset from the beginning of the disk, length in blocks, file system type, and other file system parameters. A partition map is an array of structures, one per partition. The device driver can quickly access a partition by its number, where 0 refers to partition a, 1 to partition b, and so on.

Writing Device Drivers: Reference defines the disklabel structure.


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


8.2.3    Locating the Partition for a Request

All driver entry points accessed through the device switch tables are passed a dev_t that contains a major number and a minor number. The major number gives the index of the device driver in the device switch tables. The kernel uses the major number to find the driver's entry points. The minor number represents both the location of the device and a partition number. You determine how the driver encodes this information into the minor number.

For example, the sample disk driver in this chapter assumes that the lower 3 bits of the minor number contain the partition number, bits 3 through 5 contain device-specific information as determined by the driver, and bits 6 through 19 contain the device number, as shown in Figure 8-2.

Figure 8-2: Disk Device Minor Number

To access the partition number, the driver masks out all but the lower 3 bits. To access the device number, it shifts the minor number to the right by 6 bits. The sample driver defines the following program constants and macros to use whenever it needs to extract this information:

#define PART_MASK            0x7
#define PART_SHIFT           3
#define GET_PARTITION(dev)   (getminor(dev) & PART_MASK)
#define GET_DEVICENUM(dev)   (getminor(dev) >> PART_SHIFT)


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


8.3    Device-Specific Structure

Disk device drivers represent disk devices in device-specific data stuctures. The device and the driver determine the contents of these structures. That is, the driver can store any information that it needs for accessing the device.

The following structure definition shows some of the information that a device driver might keep in a disk-specific structure. This data structure is referenced in code examples throughout this chapter to demonstrate how the driver interfaces make use of this structure.

typedef struct xxx_device {
    struct device       *device;
    DEVGEOMST           geometry;
    struct disklabel    label;
    ulong               flags;
    U32                 raw_part_opens;
    U32                 blk_part_opens;
    U32                 label_writeable;
    U32                 media_changes;
    U32                 soft_err_cnt;
    U32                 hard_err_cnt;
} xxx_device_t;

The members of this structure store the following information about the disk device:

device
Contains a pointer to the topology device structure that the DEVGETINFO ioctl command uses to return information about the device and controller. Writing Device Drivers: Reference describes the members of the device structure.

geometry
Contains a disk geometry structure. This member is initialized when the device is first opened. Information in this structure ensures that read and write operations are the correct size for the device. The DEVGETGEOM reference page in Writing Device Drivers: Reference describes the members of this structure.

label
Contains a copy of the on-disk disk label structure. This member is initialized when the device is first opened. Information in this structure is used, for example, to ensure that read and write operations do not span partition boundaries. Writing Device Drivers: Reference describes the members of the disklabel structure.

flags
Contains flags that indicate characteristics of the device. The strategy interface, for example, checks to see if the READ-ONLY flag is set when it receives a write request.

raw_part_opensblk_part_opens
Contain bit masks that keep track of the opened partitions on the disk.

label_writeable
Contains a flag to indicate whether the disk is write protected.

media_changes
Contains the number of media changes that have occurred since the system started.

soft_err_cnthard_err_cnt
Contain the number of soft and hard errors that have occurred since the system started. The DEVGETINFO ioctl command reports these numbers.

The sample disk driver stores device-specific structures in an array, up to the maximum number of devices. The driver can access a structure within the array by its device number. The sample driver assumes that device numbers are assigned sequentially as the devices are probed and attached. Nothing in the example shows how this is accomplished. Your driver would most likely use a different mechanism for maintaining information about devices. The scheme shown here is for demonstration purposes only.


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


8.4    Opening and Closing a Device

Partitions are logical devices, with many partitions representing a single physical device. In addition, many partitions can be opened at the same time on the same physical device. Therefore, the driver must ensure that the disk label remains consistent from the time the physical device is first opened until it is fully closed.

The first open request opens the device and reads the disk label into memory. The driver should not close the device until the last user makes a close request to the last opened partition on the disk. The driver can call the readdisklabel kernel interface to read the disk label. Given the driver's strategy interface and a disklabel structure, readdisklabel reads the sectors that contain the label, locates the disk label in those sectors, and copies the label into memory.

Once initialized, the driver should not change this in-memory disk label while the device is open. Any ioctl commands and write requests that would change the disk label should be ignored, unless the explicit purpose of the ioctl command is to change the disk label. New partition information written to the on-disk label should not take effect until all partitions are closed. The driver will read the new disk label into memory the next time it opens the device.

If the driver cannot read the on-disk label, it should inititalize its partition information with default values. Digital recommends that the driver call the get_def_partitionmap kernel interface, which calculates a default partition map based on the disk's geometry. However, if you want the driver to create its own default partition map, it must initialize partition c to start at offset 0 and span the entire physical device. It should also initialize partition a to start at offset 0 and span the entire disk.

A disk device driver must keep track of the partitions that are currently opened, so it can handle the first open or last close request correctly. One way to do this is by maintaining a bit mask, where each bit represents one partition. When the driver receives an open request, it sets the bit corresponding to the partition being opened. When it receives a close request, it clears the bit for that partition. It does not actually close the disk until all bits in the mask are cleared.

The examples in the following sections show how a driver can use a bit mask to keep track of open partitions.


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


8.4.1    The open Interface

The open interface takes three arguments: a device major/minor number; flags to indicate whether the device is being opened for reading, writing, or both; and a constant that indicates whether the device is a character or block device. The following example shows the common steps of the open interface, with comments marking the places where you must take architecture-dependent steps:

int
xxx_open(dev_t dev, int flag, int format)
{
    xxx_device_t    *devp;
    int             partmask;
    int             device_number;
    void            read_label(xxx_device_t *devp, dev_t dev);

    device_number = GET_DEVICENUM(dev); [1]

    if ((device_number >= MAX_XXX_DEVICES) || [2]
        (xxx_devices[device_number] == (xxx_device_t *)NULL))
        return(ENODEV);

    devp = xxx_devices[device_number]; [3]

    partmask = 1 << GET_PARTITION(dev); [4]

    /*
     * Bring the device on line [5]
     */

    devp->geometry.geom_info.attributes = FILLIN; [6]
    devp->geometry.geom_info.nsectors = FILLIN;
    devp->geometry.geom_info.interleave =  FILLIN;
    devp->geometry.geom_info.trackskew = FILLIN;
    devp->geometry.geom_info.cylskew = FILLIN;
    devp->geometry.geom_info.ncylinders = FILLIN;
    devp->geometry.geom_info.ntracks = FILLIN;
    devp->geometry.geom_info.rpm = FILLIN;

    devp->geometry.geom_info.sector_size = FILLIN;
    devp->geometry.geom_info.dev_size = FILLIN;

    /* pick relevant values for your hardware */
    devp->geometry.geom_info.min_trans =
                   devp->geometry.geom_info.sector_size;
    devp->geometry.geom_info.max_trans = XXX_MAX_XFRLEN;
    devp->geometry.geom_info.prefer_trans = (16 * 1024);

    /*
     * query the device for other properties, such as write protection.
     */

    if ((devp->raw_part_opens | devp->blk_part_opens) == 0) [7]
        read_label(devp, dev);

    switch (format) { [8]
    case S_IFCHR:
        devp->raw_part_opens |= partmask;
        break;
    case S_IFBLK:
        devp->blk_part_opens |= partmask;
        break;
    }

    /*
     * If the device has removable media and an option to
     * programatically disable media removal, do so.  If this
     * fails, the driver should not generate an error message.
     */

    return(ESUCCESS);
}

  1. Extracts the device number from the minor number. The driver defines the GET_DEVICENUM macro. [Return to example]

  2. Checks that the device number is within the allowed range and that the device-specific structure is not NULL. If the device is invalid, the driver returns the ENODEV error status value. [Return to example]

  3. Locates a device-specific structure. The structure is kept in an array and indexed by the device number. Your driver will use a different mechanism to locate its device-specific structures. [Return to example]

  4. Sets the bit within the bit mask for the partition being opened. [Return to example]

  5. Brings the device on line according to the driver architecture. However, if the FNDELAY or FNONBLOCK flags are set, the driver should ignore failures that would normally cause the driver to exit with an error status. [Return to example]

  6. Initializes the device-specific structure with geometry information. The driver needs to query the device for capacity and geometry information. If it cannot obtain all the information shown in the example, the driver should at least initialize the dev_size and sector_size members. [Return to example]

  7. Checks to see if this is the first open to the physical device. If so, the driver calls the read_label subroutine to read the disk label from the disk and store it in the device-specific structure. The sample driver defines the read_label subroutine, described in Section 8.4.2. [Return to example]

  8. Uses the format argument to determine whether the open is for a character or block device. The driver sets the appropriate bit for this partition in the device-specific structure. [Return to example]


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


8.4.2    The read_label Subroutine

The driver's open interface calls the read_label subroutine to read the disk label into memory and to initialize the device-specific structure with this information. The subroutine takes two arguments: the address of the device-specific structure and the dev_t passed to open by the kernel.

The sample disk device driver implements the read_label subroutine as follows:

static void
read_label(xxx_device_t *devp, dev_t dev)
{
    struct disklabel    *lp = &devp->label;
    char                *statusmsg;
    struct pt_tbl       ptable;
    dev_t               tempdev;
    int                 i;
    void                xxx_strategy(struct buf *bp);

    lp->d_magic = 0;  [1]
    lp->d_secsize = devp->geometry.geom_info.sector_size;
    lp->d_secperunit = devp->geometry.geom_info.dev_size;
    lp->d_secpercyl = 1;
    lp->d_nsectors = lp->d_secperunit;
    lp->d_npartitions = 1;
    lp->d_partitions[0].p_offset = 0;
    lp->d_partitions[0].p_size = lp->d_secperunit;

    tempdev = makedev(getmajor(dev),
                      (getminor(dev) & ~PART_MASK)); [2]

    devp->raw_part_opens |= 1; [3]
    devp->blk_part_opens |= 1;

    statusmsg = readdisklabel(tempdev, 
                              (int (*)())xxx_strategy, lp); [4]

    if (statusmsg != NULL) { [5]
        lp->d_magic = 0;
        if (get_def_partitionmap(&devp->geometry, 
                                 &ptable) == 0) { [6]
            lp->d_npartitions = 8;
            for (i=0; i<8; i++) {
                lp->d_partitions[i].p_offset =
                            ptable.d_partitions[i].p_offset;
                lp->d_partitions[i].p_size =
                            ptable.d_partitions[i].p_size;
            }
        } else { [7]
            bzero((caddr_t)lp->d_partitions,
                  sizeof(lp->d_partitions));
            lp->d_npartitions = 8;
            lp->d_partitions[0].p_size = 
                  lp->d_partitions[2].p_size =
                  devp->geometry.geom_info.dev_size;
        }
    }
    devp->raw_part_opens &= ~1; [8]
    devp->blk_part_opens &= ~1;
    return;
}

  1. Creates a temporary partition map, where partition 0 spans the entire disk. (On the first open to the physical device, the driver has no partition information.) [Return to example]

  2. Creates a temporary dev_t with the same device number as the dev_t that was passed into the read_label subroutine, but with the partition number set to 0. [Return to example]

  3. Sets the bit corresponding to partition 0 in the partition bit masks. In this way, the driver opens partition 0 so that the partition can be read. [Return to example]

  4. Calls the readdisklabel kernel interface to read the disk label from the disk into the lp structure, using the device driver's strategy interface. [Return to example]

  5. Checks the return status from readdisklabel. The readisklabel interface returns a NULL string on success and an error message string when an error occurs. If the driver cannot read the partition map, it creates a default map by calling the get_default_partitionmap interface. The driver may also log an error in the system error log file at this time. [Return to example]

  6. Copies the partition map into the lp structure, if get_def_partitionmap succeeds, [Return to example]

  7. Creates a single partition (c) which spans the entire disk, if get_def_partitionmap fails. [Return to example]

  8. Clears the bits corresponding to partition 0, leaving it closed when the routine exits. [Return to example]


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


8.4.3    The close Interface

The close interface is called with the same arguments as open:

Using the partition number, the close interface clears the bit for the partition being closed. If this is not the last open partition for the device, close simply returns to the caller. If no partitions are open, it closes the device.

The sample disk device driver implements the close interface as follows:

int
xxx_close(dev_t dev, int flag, int format)
{
    xxx_device_t    *devp;
    int             partmask;
    int             device_number;

    device_number = GET_DEVICENUM(dev); [1]

    if ((device_number >= MAX_XXX_DEVICES) || [2]
        (xxx_devices[device_number] == (xxx_device_t *)NULL))
        return(ENODEV);

    devp = xxx_devices[device_number]; [3]

    partmask = 1 << GET_PARTITION(dev); [4]

    switch (format) { [5]
    case S_IFCHR:
        devp->raw_part_opens &= ~partmask;
        break;
    case S_IFBLK:
        devp->blk_part_opens &= ~partmask;
        break;
    }

    if ((devp->raw_part_opens | devp->blk_part_opens) != 0) [6]
        return(ESUCCESS);

    devp->label_writeable = FALSE; [7]

    /*
     * If the device has removable media and the driver has
     * disabled media removal in the xxx_open interface, re-enable
     * media removal.  If this fails, the driver may issue an
     * error message.
     */

    return(ESUCCESS);
}

  1. Extracts the device number from the minor number. The driver defines the GET_DEVICENUM macro. [Return to example]

  2. Checks that the device number is within the allowed range and that the device-specific structure is not NULL. If the device is invalid, the driver returns the ENODEV error status value. [Return to example]

  3. Locates a device-specific structure. The device-specific structure is kept in an array and indexed by the device number. Your driver will use a different mechanism to locate its device-specific structures. [Return to example]

  4. Sets the bit within a bit mask for the partition being closed, using the GET_PARTITION macro to extract the partition number from the dev_t. The driver implements the GET_PARTITION macro. [Return to example]

  5. Uses the format argument to determine whether the close is for a character or block device. The driver clears the appropriate bit for the partition in the device-specific structure. [Return to example]

  6. Checks to see if one or more partitions are still open for this device. If either the block or character partition mask is nonzero, the driver simply returns ESUCCESS. [Return to example]

  7. Resets the write-protection flag so that the next user cannot inadvertently write the disk label. [Return to example]


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


8.5    Reading and Writing Data

Devices that can perform both block and character I/O must provide read and write interfaces for character I/O and a strategy interface for block I/O.

Digital UNIX currently assumes a block size of 512 bytes for all block I/O operations. If the sector size of your disk device is not 512 bytes, the block numbers referenced by the device are going to differ from the block numbers referenced by the system. If the device block size is not a multiple of 512 bytes, the driver needs to convert device block numbers to operating system block numbers and vice versa, or it should reject the read or write request.


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


8.5.1    The read and write Interfaces

The read and write interfaces are called with the same arguments: a major/minor number and a pointer to a uio structure (a vector for passing data between the user process and the device during an I/O operation). See Writing Device Drivers: Reference for more information on the uio structure.

When the read and write interfaces call the strategy interface to perform the data transfer, they allocate a buf structure for passing data between the user and the device. The buf structure is passed to the driver's strategy interface through the physio kernel interface.

The sample disk device driver implements the read interface as follows:

int
xxx_read(dev_t dev, struct uio *uio)
{
    struct buf      *bp;
    xxx_device_t    *devp;
    int             status;
    int             device_number;
    void            xxx_strategy(struct buf *bp);
    void            xxx_minphys(struct buf *bp);

    device_number = GET_DEVICENUM(dev); [1]

    if ((device_number >= MAX_XXX_DEVICES) || [2]
        (xxx_devices[device_number] == (xxx_device_t *)NULL))
        return(ENODEV);

    devp = xxx_devices[device_number]; [3]

    if (((devp->raw_part_opens | devp->blk_part_opens) & [4]
         (1 << GET_PARTITION(dev))) != 0)
        return(EBADF);

    if ((uio->uio_offset % 
        devp->geometry.geom_info.sector_size) != 0) [5]
        return(EIO);

    bp = getnewbuf(); [6]

    status = physio((int (*)())xxx_strategy, bp, dev, B_READ, [7]
                    (uint (*)())xxx_minphys, uio);

    brelse(bp); [8]

    return(status);
}

  1. Extracts the device number from the minor number. The driver defines the GET_DEVICENUM macro. [Return to example]

  2. Checks that the device number is within the allowed range and that the device-specific structure is not NULL. If the device is invalid, the driver returns the ENODEV error status value. [Return to example]

  3. Locates a device-specific structure. The device-specific structure is kept in an array and indexed by the device number. Your driver will use a different mechanism to locate its device-specific structures. [Return to example]

  4. Tests whether the partition passed in through the minor number has been opened. The GET_PARTITION macro extracts the partition number from the dev_t, and the partition bit masks indicate which partitions have been opened. If the bit corresponding to this partition is not set, the driver returns the EBADF error status value. [Return to example]

  5. Validates that the I/O request is aligned at the start of a block. If not, it returns the EIO error status value. [Return to example]

  6. Calls getnewbuf to allocate a buf structure. The driver may use other methods for allocating this buffer. For example, it could allocate a single buf structure for each device, maintain a pool of buffers, or call generic allocation interfaces. However, the getnewbuf interface guarantees that the buf structure is properly initialized. [Return to example]

  7. Calls physio to perform the actual read or write operation. The physio interface calls the strategy interface to perform the data transfer. It calls the completion callback interface when the data transfer has completed. If your device supports a large, arbitrary transfer length, the driver may pass the address of the system minphys interface instead of supplying a driver-specific interface,

    The only difference between read and write is the call to physio. The write interface passes the B_WRITE flag instead of B_READ. [Return to example]

  8. Deallocates the buf structure after physio returns. If the structure was allocated with getnewbuf, it can be deallocated with the brelse kernel interface. [Return to example]


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


8.5.2    The strategy Interface

The strategy interface performs the data transfer for all character and block read and write operations. It is called with the address of a buf structure describing the I/O operation. Members within the buf structure provide the following information to strategy:

The strategy interface is responsible for filling in the following members of the buf structure:

Refer to the IEEE Standard: Portable Operating System Interface for Computer Environments, 1003.1, for information on handling end of media, residual counts, and the B_ERROR flag.

Before it can perform the read or write, the strategy interface must check that the partition map is valid and that the data to be transferred does not go beyond the bounds of the partition.

The sample disk device driver implements the strategy interface as follows:

void
xxx_strategy(struct buf *bp)
{
    xxx_device_t        *devp;
    struct partition    *pp;
    int                 partition;
    int                 device_number;
    U32                 nblocks;
    U32                 start_blk;

    if ((bp->b_bcount % 
        devp->geometry.geom_info.sector_size) != 0) { [1]
        bp->b_flags |= B_ERROR;
        bp->b_resid = bp->b_bcount;
        bp->b_error = EIO;
        (void)biodone(bp);
        return;
    }

    device_number = GET_DEVICENUM(bp->b_dev); [2]

    if ((device_number >= MAX_XXX_DEVICES) || [3]
        (xxx_devices[device_number] == (xxx_device_t *)NULL)) {
        bp->b_flags |= B_ERROR;
        bp->b_resid = bp->b_bcount;
        bp->b_error = ENODEV;
        (void)biodone(bp);
        return;
    }

    devp = xxx_devices[device_number]; [4]

    partition = GET_PARTITION(bp->b_dev); [5]

    if (partition >= devp->label.d_npartitions) { [6]
        bp->b_flags |= B_ERROR;
        bp->b_resid = bp->b_bcount;
        bp->b_error = ENXIO;
        (void)biodone(bp);
        return;
    }
    pp = &devp->label.d_partitions[partition];

    if (pp->p_size == 0) { [7]
        bp->b_flags |= B_ERROR;
        bp->b_resid = bp->b_bcount;
        bp->b_error = EROFS;
        (void)biodone(bp);
        return;
    }

    if (((devp->raw_part_opens | devp->blk_part_opens) & [8]
            (1 << partition)) != 0) {
        bp->b_flags |= B_ERROR;
        bp->b_resid = bp->b_bcount;
        bp->b_error = EBADF;
        (void)biodone(bp);
        return;
    }

    if ((devp->flags & READ_ONLY) && 
        ((bp->b_flags & B_READ) == 0)) { [9]
        bp->b_flags |= B_ERROR;
        bp->b_resid = bp->b_bcount;
        bp->b_error = EROFS;
        (void)biodone(bp);
        return;
    }

    nblocks = (bp->b_bcount + 
               (devp->geometry.geom_info.sector_size - 1)) [10]
               / devp->geometry.geom_info.sector_size;

    start_blk = bp->b_blkno + pp->p_offset; [11]

    if ((start_blk <= LABELSECTOR) && [12]
        ((bp->b_flags & B_READ) == 0) &&
        (devp->label.d_magic == DISKMAGIC) &&
        (devp->label_writeable == FALSE)) {

        bp->b_flags |= B_ERROR;
        bp->b_resid = bp->b_bcount;
        bp->b_error = EROFS;
        (void)biodone(bp);
        return;
    }

    if ((bp->b_blkno < 0) || (bp->b_blkno >= pp->p_size) || [13]
        (pp->p_offset >= devp->geometry.geom_info.dev_size) ) {

        if ((bp->b_flags & B_READ) == 0) { [14]
            bp->b_flags |= B_ERROR;
            bp->b_error = ENOSPC;
        }
        bp->b_resid = bp->b_bcount; [15]
        (void)biodone(bp);
        return;
    }

    if ((bp->b_blkno + nblocks) > pp->p_size )  { [16]
        bp->b_resid = bp->b_bcount;
        bp->b_bcount = (pp->p_size - bp->b_blkno) *
                            devp->geometry.geom_info.sector_size;
    } else  {
        bp->b_resid = 0;
    }

    /*
     * Transfer the data
     */

    return;
}

  1. Validates that the I/O is properly aligned on a block boundary. Alternatively, the driver could perform a read-modify-write sequence to transfer data of any length. You must decide whether the complexity of this is worth the effort. [Return to example]

  2. Extracts the device number from the minor number. The GET_DEVICENUM macro is defined by the driver. [Return to example]

  3. Checks that the device number is within the allowed range and that the device-specific structure is not NULL. If the device is invalid, the driver returns the ENODEV error status value. [Return to example]

  4. Locates the device-specific structure. The device-specific structure is kept in an array and indexed by the device number. Your driver will use a different mechanism to locate its device-specific structures. [Return to example]

  5. Extracts the partition number from the dev_t. The GET_PARTITION macro is defined by the driver. [Return to example]

  6. Sets the b_error member of the buf structure to ENXIO, if the partition number is greater than or equal to the number of partitions on the device. The driver then calls the biodone interface and exits. [Return to example]

  7. Sets the b_error member of the buf structure to EROFS, if the partition length is 0. The driver then calls the biodone interface and exits. [Return to example]

  8. Sets the b_error member of the buf structure to EBADF, if the partition is not open. The driver then calls the biodone interface and exits. [Return to example]

  9. Sets the b_error member of the buf structure to EROFS, if this is a write request and the device is read-only. The driver then calls the biodone interface and exits. [Return to example]

  10. Performs range checking on the location and length of the I/O request. The number of blocks being read or written is rounded up by adding the byte count to the sector size minus 1, then dividing by the sector size. [Return to example]

  11. Calculates the starting block number as the sum of the block number and the starting offset of the partition. [Return to example]

  12. Checks that the disk label is not going to be overwritten by a write request. If it is, the driver sets the b_error member of the buf structure to EROFS, then calls the biodone interface and exits. [Return to example]

  13. Checks that the block number is valid, falls within the partition, and that the partition offset is not greater than the total disk size. Any of these conditions indicates an invalid block or offset. [Return to example]

  14. Sets the b_error member of the buf structure to ENOSPC, if a write operation is requested at an invalid block or offset. The driver then calls the biodone interface and exits. [Return to example]

  15. Sets the residual count and exits, if a read operation is requested at an invalid block or offset. This is not an error. [Return to example]

  16. Allows a transfer up to the end of the partition, if the number of bytes to transfer is greater than the partition size. The driver places the remaining byte count in the b_resid member of the buf structure. Otherwise, it sets b_resid to 0. [Return to example]


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


8.5.3    The minphys Interface

The physio kernel interface calls the driver's minphys interface to make sure the I/O request fits within the maximum transfer length for the device. If it does not, the driver adjusts the byte count so that it is within the allowable transfer length.

The sample disk device driver implements the minphys interface as follows:

void
xxx_minphys(struct buf *bp)
{
    if (bp->b_bcount > XXX_MAX_XFRLEN)
        bp->b_bcount = XXX_MAX_XFRLEN;

    return;
}

A driver should provide its own minphys routine if it supports arbitrary transfer lengths. It can use the system's default transfer length by calling strategy with the system minphys routine.


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


8.6    The ioctl Interface

The ioctl interface handles all device-related operations other than read or write. Table 8-2 lists the ioctl commands that disk device drivers need to recognize. Refer to Writing Device Drivers: Reference for a full ioctl definition.

Table 8-2: Disk Device ioctl Commands

Command Description
DIOCGDINFO Returns a pointer to a disklabel structure.
DIOCGPART Returns information about a single partition.
DIOCSDINFO Sets a disklabel structure.
DIOCWLABEL Enables writes to the label sector area.
DIOCWDINFO Writes a disk label.
DIOCGDEFPT Creates a default partition map.
DIOCGCURPT Returns the current partition map.
DEVGETINFO Returns device information.
DEVGETGEOM Returns device geometry.

The ioctl interface is called with the following arguments:

Because there are many commands, each requiring different procedures, the ioctl interface is often implemented as a switch statement where each case statement handles one command.

The sample disk device driver implements the ioctl interface as follows:

int
xxx_ioctl(dev_t dev, U32 cmd, caddr_t data, int flag)
{
    xxx_device_t        *devp;
    struct disklabel    *lp;
    int                 retval = ESUCCESS;
    struct partition    *pp;
    int                 partition;
    int                 device_number;
    U32                 current_opens;

    device_number = GET_DEVICENUM(dev); [1]

    if ((device_number >= MAX_XXX_DEVICES) || [2]
        (xxx_devices[device_number] == (xxx_device_t *)NULL))
        return(ENODEV);

    devp = xxx_devices[device_number]; [3]

    lp = &devp->label; [4]
    current_opens = (devp->raw_part_opens | devp->blk_part_opens);

    partition = GET_PARTITION(dev); [5]
    pp = &lp->d_partitions[partition];

    if (((devp->raw_part_opens | devp->blk_part_opens) & [6]
            (1 << GET_PARTITION(dev))) != 0) {
        if ((cmd != DEVGETINFO) &&
            (cmd != DEVIOCGET))
            return(EBADF);
    }

    switch (cmd) { [7]

  1. Extracts the device number from the minor number. The driver defines the GET_DEVICENUM macro. [Return to example]

  2. Checks that the device number is within the allowed range and its device-specific structure is not NULL. If the device is invalid, the driver returns the ENODEV error status value. [Return to example]

  3. Locates a device-specific structure. The device-specific structure is kept in an array and indexed by the device number. Your driver will use a different mechanism to locate its device-specific structures. [Return to example]

  4. Initializes the lp variable to the address of the disk label structure; initializes the current_opens variable to the bit mask that indicates the partitions that are currently opened. [Return to example]

  5. Extracts the partition number from the dev_t. The partition variable is initialized to the partition number, and the pp variable is initialized to the address of the partition structure for that partition. [Return to example]

  6. Returns the EBADF error status value for all commands except DEVGETINFO and DEVIOCGET, if the partition is not opened. [Return to example]

  7. Enters the switch statement to handle the command, if the partition is opened. [Return to example]


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


8.6.1    DIOCGDINFO ioctl Command

The DIOCGDINFO command returns a disklabel structure to the user data buffer.

The sample disk device driver implements the DIOCGDINFO case statement as follows:

case DIOCGDINFO:
    {
    if (lp->d_magic != DISKMAGIC) [1]
        return(EINVAL);

    *(struct disklabel *)data = *lp; [2]

    break;
    }

  1. Returns the EINVAL error status value if the d_magic member of the disklabel structure is not set to DISKMAGIC. This indicates that the disk does not have a disk label. [Return to example]

  2. Assigns the disk label to the user data buffer, if the disk has a disk label. [Return to example]


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


8.6.2    DIOCGPART ioctl Command

The file system issues the DIOCGPART command to obtain information about a device and partition. The command returns a structure to the data buffer, which contains a pointer to a disklabel structure and a pointer to a partition structure for a single partition on the disk. Writing Device Drivers: Reference describes the disklabel and partition structures.

The sample disk device driver implements the DIOCGPART case statement as follows:

case DIOCGPART:
    {
    if ((lp->d_magic != DISKMAGIC) || [1]
        (partition >= lp->d_npartitions))
        return(EINVAL);

    ((struct partinfo *)data)->disklab = lp; [2]
    ((struct partinfo *)data)->part = pp;

    break;
    }

  1. Returns the EINVAL error status code, if the disklabel structure's magic number is not DISKMAGIC or the partition number is greater than or equal to the number of partitions on the device. (The disk label is invalid.) [Return to example]

  2. Casts the data buffer as a partinfo structure, assigns the disklabel structure to the disklab member, and assigns the partition structure for the current partition to the part member, if a disklabel does exist. [Return to example]


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


8.6.3    DIOCSDINFO ioctl Command

The DIOCSDINFO command sets the in-memory disk label as specified in the user data buffer. Only users with root privileges should be allowed to change the disk label. The flag argument to the ioctl interface must be set to FWRITE or the command fails.

The DIOCSDINFO command sets the disk label by calling the setdisklabel interface and passing a bit mask of opened partitions. (When the device is in raw mode, it passes 0, indicating that no partitions are opened.) The setdisklabel interface validates the new disk label. If a partition is opened, its definition is not changed. Otherwise, the interface updates the changed partition.

For more information on the setdisklabel interface, see Writing Device Drivers: Reference.

The sample disk device driver implements the DIOCSDINFO case statement as follows:

case DIOCSDINFO:
    {
    if ((flag & FWRITE) == 0) [1]
        return(EBADF);

    if (lp->d_magic == DISKMAGIC) { [2]
        retval = setdisklabel(lp, (struct disklabel *)data, [3]
                              current_opens);
    } else { [4]
        retval = setdisklabel(lp, (struct disklabel *)data, 0);
    }

    break;
    }

  1. Returns the EBADF error status code, if the flag argument to the ioctl interface does not contain the value FWRITE. (The device was not opened with write permissions.) [Return to example]

  2. Checks the disklabel structure's magic number to see if it is initialized to DISKMAGIC. If so, the disk label is valid and the device is in block mode. [Return to example]

  3. Passes a partition bit mask to the setdisklabel interface when the device is in block mode. The setdisklabel interface checks each open partition to be sure the members of the in-memory and user-specified partition structures contain the same information. [Return to example]

  4. Calls setdisklabel without a partition bit mask argument when the device is in raw mode. The setdisklabel interface simply copies the contents of the data buffer into the disklabel structure in memory. [Return to example]


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


8.6.4    DIOCWLABEL ioctl Command

The DIOCWLABEL command turns the write-protect flag on and off for the block where the disk label is kept. This block should be write-protected most of the time, and should be unprotected only by someone with root privileges.

The driver stores the write-protect flag in its device-specific structure.

The sample disk device driver implements the DIOCWLABEL case statement as follows:

case DIOCWLABEL:
    {
    if ((flag & FWRITE) == 0) [1]
        return(EBADF);

    devp->label_writeable = *(int *)data; [2]

    break;
    }

  1. Checks that the flag argument passed into the ioctl interface contains the FWRITE flag, indicating that this request has write permissions. If not, the driver returns the EBADF error status value. [Return to example]

  2. Sets the write-protect label to the value specified in the user data buffer. [Return to example]


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


8.6.5    DIOCWDINFO ioctl Command

The disklabel utility usually writes the disk label (see disklabel(8)). This utility opens the partition at physical block 0 and writes the sector referenced by LABELSECTOR by using read and write system calls and the DIOCWDINFO ioctl command.

For general support, the driver should support the DIOCWDINFO ioctl command to update and write the sector referenced by LABELSECTOR with the writedisklabel kernel interface. Given the driver's strategy interface and a disklabel structure as arguments, writedisklabel updates the label within the block and rewrites the sector referenced by LABELSECTOR.

To ensure that the user does not inadvertently overwrite the disk label by making a write request to physical block 0, the driver should maintain a write-protect flag for physical block 0. The driver must also support the DIOCWLABEL ioctl command to toggle the write-protect flag on and off (see Section 8.6.4).

Note

A partition overlap error can occur when writing the disk label if a partition is already in use. The writedisklabel interface detects a partition overlap when the p_fstype member of the d_partition structure contains a non-NULL value. See Writing Device Drivers: Reference for more information on the disklabel structure.

The sample disk device driver implements the DIOCWDINFO case statement as follows:

case DIOCWDINFO:
    {
    struct disklabel    *new_lp = (struct disklabel *)data;
    U32                 current_label_wrtbl;

    if ((flag & FWRITE) == 0) [1]
        return(EBADF);

    if ((partition >= lp->d_npartitions) || [2]
        (new_lp->d_partitions[partition].p_offset != 0))
        return(EINVAL);

    if (lp->d_magic == DISKMAGIC) { [3]
        retval = setdisklabel(lp, new_lp, current_opens);
    } else {
        retval = setdisklabel(lp, new_lp, 0);
    }

    if (retval != ESUCCESS) [4]
        break;

    current_label_wrtbl = devp->label_writeable; [5]
    devp->label_writeable = TRUE; [6]

    retval = writedisklabel(dev, (int (*)())xxx_strategy, lp); [7]

    devp->label_writeable = current_label_wrtbl; [8]

    break;
    }

  1. Checks that the flag argument passed into the ioctl interface contains the FWRITE flag, indicating that this request has write permissions. If not, the driver returns the EBADF status value. [Return to example]

  2. Makes sure the partition in the new label starts a block 0. Otherwise, it cannot write the label to the disk and returns the EINVAL error status value. [Return to example]

  3. Protects currently opened partitions by passing the partition mask to setdisklabel, if the disk label's d_magic member is set to DISKMAGIC. Otherwise, the driver can assume that it is using a default disk label, and it does not pass the partition mask to setdisklabel. A driver should always call setdisklabel before writedisklabel. [Return to example]

  4. Breaks out of the switch statement if setdisklabel fails. The driver cannot write the disk label. [Return to example]

  5. Saves the current write-protect label so it can be restored when the disk label has been written. [Return to example]

  6. Sets the write-protect label to TRUE, turning write-protection off. [Return to example]

  7. Writes the new disk label to the disk, using writedisklabel and the device driver's strategy interface. [Return to example]

  8. Restores the old write-protect label. [Return to example]


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


8.6.6    DIOCGDEFPT ioctl Command

The DIOCGDEFPT command returns the default partition map to the user data buffer. The pt_tbl structure contains the partition map -- an array of partition structures, up to the maximum number of partitions allowed on the device. See Writing Device Drivers: Reference for a description of this structure.

This ioctl command can return the default partition map regardless of whether a valid disklabel exists on the disk because it calculates the partition map from the number of blocks and sector size. If no geometry information is available, the DIOCGDEFPT command sets the a and c partitions to the size of the entire disk.

The sample disk device driver implements the DIOCGDEFPT case statement as follows:

case DIOCGDEFPT:
    {
    struct pt_tbl   *ptable = (struct pt_tbl *)data; [1]

    if (get_def_partitionmap(&devp->geometry, ptable) != 0) { [2]

        bzero((caddr_t)ptable, sizeof(struct pt_tbl)); [3]
        ptable->d_partitions[0].p_size =
            ptable->d_partitions[2].p_size =
                devp->geometry.geom_info.dev_size;
    }

    break;
    }

  1. Assigns the address of the data buffer to a partition map structure, casting the data buffer to a pt_tbl structure. In this way, the partition map is returned to the user through the data buffer. [Return to example]

  2. Calculates the default partition map and returns it in the ptable structure. [Return to example]

  3. Sets the a and c partitions to the size of the entire disk, if the call to get_def_partitionmap fails. [Return to example]


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


8.6.7    DIOCGCURPT ioctl Command

The DIOCGCURPT command places the current partition map in the user data buffer. The pt_tbl structure contains the partition map -- an array of partition structures, up to the maximum number of partitions allowed on the device. See Writing Device Drivers: Reference for a description of this structure.

The sample disk device driver implements the DIOCGCURPT case statement as follows:

case DIOCGCURPT:
    {
    *(struct pt_tbl *)data = *(struct pt_tbl *)lp->d_partitions; [1]
    break;
    }

  1. Copies the array of partition structures from the disk label to the data buffer. [Return to example]


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


8.6.8    DEVGETINFO ioctl Command

Note

The DEVGETINFO command replaces the DEVIOCGET command. Your driver may still support DEVIOCGET for backwards compatibility, but DEVGETINFO is the preferred interface. The DEVIOCGET ioctl command may not be supported in future releases.

The DEVGETINFO command gathers information about a device from the device and controller structures associated with the device. It queries the device for status, such as whether the device is write-locked or write-enabled, and looks at device-specific structures for information specific to the driver architecture. Because the kernel may call it at system startup, this command must be able to operate even if the device has not been opened.

The command places the information in a device information structure. Both disk and tape devices can use this structure definition. It is made up of the following members:

Figure 8-3 shows the relationship between the structures and unions that make up the device information structure.

Figure 8-3: Device Information Structure

Writing Device Drivers: Reference describes these structures.

The sample disk device driver implements the DEVGETINFO case statement as follows:

case DEVGETINFO:
    {
    struct device           *device;
    v1_device_info_t        *devi_p;
    v1_bustype_info_t       *busp;
    v1_disk_dev_info_t      *diskp;

    device = devp->device;

    devi_p = (v1_device_info_t *)data; [1]
    bzero((caddr_t)devi_p,sizeof(*devi_p));

    /****************************************************
     * fill in generic information
     ****************************************************/

    devi_p->version    = VERSION_1; [2]
    devi_p->category   = DEV_DISK;
    devi_p->bus        = FILLIN;
    bcopy("XXX", devi_p->interface, 3);
    bcopy("xxxdev", devi_p->device, 6);
    bcopy("xx", devi_p->dev_name, 2);
    devi_p->soft_count = devp->soft_err_cnt;
    devi_p->hard_count = devp->hard_err_cnt;

    /****************************************************
     * fill in (topology) bus-generic information
     ****************************************************/

    busp = &devi_p->businfo; [3]
    if (device != (struct device *)NULL) {
        busp->nexus_num = device->ctlr_hd->slot;
        busp->adpt_num = 0;
        busp->bus_num = device->ctlr_hd->bus_hd->bus_num;
        busp->ctlr_num = device->ctlr_num;
        busp->rctlr_num = device->ctlr_hd->rctlr;
        busp->slave_num = 0;
        busp->unit_num = device_number;
    } else { [4]
        busp->nexus_num =  -1;
        busp->adpt_num =  -1;
        busp->bus_num =  -1;
        busp->ctlr_num = -1;
        busp->rctlr_num = 0;
        busp->slave_num = 0;
        busp->unit_num = device_number;
    }

    /****************************************************
     * fill in bus-specific information
     ****************************************************/


    /****************************************************
     * fill in category-specific information
     ****************************************************/

    diskp = (v1_disk_dev_info_t *)&devi_p->devinfo; [5]
    diskp->class        = DKDEV_CLS_HARDDISK;
    diskp->part_num     = partition;
    diskp->blocksz      = devp->geometry.geom_info.sector_size;
    diskp->capacity     = devp->geometry.geom_info.dev_size;


    /****************************************************
     * fill in category-specific architecture information
     ****************************************************/


    break;
    }

  1. Assigns the address of the data buffer to the devi_p variable, casting the data buffer to a device information structure. In this way, information is returned to the caller. [Return to example]

  2. Provides general information about the device. This information can come from hard-coded values, such as the version number and device category, or from device-specific data structures, such as the soft and hard error counts. [Return to example]

  3. Provides the following information about the bus (the information comes from the device structure):

    See Writing Device Drivers: Tutorial for more information on the system topology. [Return to example]

  4. Intializes these structure members to -1 or 0, as appropriate, if no device structure is found, [Return to example]

  5. Gets other information by querying the device or from its own knowledge of the device architecture. The driver should fill in fields appropriate for the device, including attributes for online/offline, write protection, removable media, media changes, and so on. Subclasses for devices such as CD-ROMs or diskettes should be set, if applicable. See /usr/sys/include/sys/devgetinfo.h for additional information this command can return. [Return to example]


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


8.6.9    DEVGETGEOM ioctl Command

The DEVGETGEOM command returns the disk geometry to the user data buffer. It gets much of this information by querying the device itself or from internal knowledge of the device. Therefore, how this information is obtained depends on the device driver architecture and the disk device.

The sample disk device driver implements the DEVGETGEOM case statement as follows:

case DEVGETGEOM:
    {
    if ((devp->geometry.geom_info.ntracks == 0) || [1]
        (devp->geometry.geom_info.nsectors == 0) ||
        (devp->geometry.geom_info.ncylinders == 0)) {
        return(EIO);
    }

    /* Query the device for information, if necessary /* [2]

    *(DEVGEOMST *)data = *(&devp->geometry); [3]

    break;
    }

  1. Returns the EIO error status code, if no geometry information is available. [Return to example]

  2. Queries the device for the latest status, if the device can dynamically change geometry. [Return to example]

  3. Returns the geometry information to the user by initializing the data buffer to the address of the geometry structure. The driver's open routine initializes the device-specific structure with geometry information when the device is first opened, as shown in Section 8.4.1. [Return to example]


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


8.7    The size Interface

The size interface returns the number of blocks in a partition. It can get the partition size from either the disklabel structure or the partition map. The kernel calls the size interface through the device switch tables. It returns the size on success or -1 if the specified partition number is greater than the number of partitions on the disk.

The sample disk device driver implements the size interface as follows:

daddr_t
xxx_size(dev_t dev)
{
    xxx_device_t    *devp;
    int             partition;
    int             device_number;

    device_number = GET_DEVICENUM(dev); [1]

    if ((device_number >= MAX_XXX_DEVICES) || [2]
        (xxx_devices[device_number] == (xxx_device_t *)NULL))
        return(-1);

    devp = xxx_devices[device_number]; [3]

    partition = GET_PARTITION(dev); [4]

    if (partition >= devp->label.d_npartitions) [5]
        return(-1);
    else
        return(devp->label.d_partitions[partition].p_size);
}

  1. Extracts the device number from the minor number. The driver defines the GET_DEVICENUM macro. [Return to example]

  2. Checks that the device number is within the allowed range and that the device-specific structure is not NULL. If the device is invalid, the driver returns -1. [Return to example]

  3. Locates a device-specific structure. The device-specific structure is kept in an array of structures and indexed by the device number. Your driver will use a different mechanism to locate its device-specific structures. [Return to example]

  4. Extracts the partition number from the dev_t. The driver defines the GET_PARTITION macro. [Return to example]

  5. Returns -1, if the partition number is greater than or equal to the number of partitions on the device. Otherwise, the driver returns the p_size member of the disklabel structure. [Return to example]


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


8.8    The dump Interface

Note

Digital UNIX does not provide boot and dump support in this release. If you need this feature, please contact your Digital Support Center for assistance.