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.
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.
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.
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.
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.
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.
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.
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.
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)
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:
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.
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.
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); }
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; }
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); }
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.
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); }
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]
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; }
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.
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.
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]
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; }
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; }
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; }
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; }
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; }
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; }
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; }
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:
The device type union contains the device name, type, the number of soft and hard errors, and other information common to all device types. This information is defined as a union, where each member of the union represents a different version. Thus, the definition can change without impacting the interfaces that reference it.
The bus structure contains the adapter number, nexus, bus number, controller number, remote controller number, and other information common to all bus types.
The disk structure contains the device status, number of blocks, block size, number of partitions, and so on. This information is specific to disk devices. This structure is a member of a union, so that both disk and tape devices can use the same overall structure.
Figure 8-3 shows the relationship between the structures and unions that make up the 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; }
See Writing Device Drivers: Tutorial for more information on the system topology. [Return to example]
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; }
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); }
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.