Copyright (c) Hyperion Entertainment and contributors.
Difference between revisions of "SCSI Device"
Steven Solie (talk | contribs) |
Steven Solie (talk | contribs) |
||
(5 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
− | {{NeedUpdate}} |
+ | [[Category:Devices|SCSI]]{{NeedUpdate}} |
== SCSI Device == |
== SCSI Device == |
||
Line 54: | Line 54: | ||
== Device Interface == |
== Device Interface == |
||
− | The SCSI device operates like other Amiga devices. To use it, you must first open the SCSI device, then send I/O requests to it, and then close it when finished. See |
+ | The SCSI device operates like other Amiga devices. To use it, you must first open the SCSI device, then send I/O requests to it, and then close it when finished. See [[Exec_Device_I/O|Exec Device I/O]] for general information on device usage. |
The power of the SCSI device comes from its special facility for passing SCSI and SCSI-2 command blocks to any SCSI unit on the bus. This facility is commonly called ''SCSI-direct'' and it allows the Amiga to perform SCSI functions that are “non-standard” in terms of the normal Amiga I/O model. |
The power of the SCSI device comes from its special facility for passing SCSI and SCSI-2 command blocks to any SCSI unit on the bus. This facility is commonly called ''SCSI-direct'' and it allows the Amiga to perform SCSI functions that are “non-standard” in terms of the normal Amiga I/O model. |
||
Line 275: | Line 275: | ||
== RigidDiskBlock – Fields and Implementation == |
== RigidDiskBlock – Fields and Implementation == |
||
− | |||
− | |||
The RigidDiskBlock (RDB) standard was borne out of the same development effort as HD_SCSICMD and as a result has a heavy bias towards SCSI. However, there is nothing in the RDB specification that makes it unusable for devices using other bus protocols. The XT style disks used in the ''A590'' also support the RDB standard. |
The RigidDiskBlock (RDB) standard was borne out of the same development effort as HD_SCSICMD and as a result has a heavy bias towards SCSI. However, there is nothing in the RDB specification that makes it unusable for devices using other bus protocols. The XT style disks used in the ''A590'' also support the RDB standard. |
||
Line 282: | Line 280: | ||
The RDB scheme was designed to allow the automatic mounting of all partitions on a hard drive and subsequent booting from the highest priority partition even if it has a soft loaded filing system. Disks can be removed from one controller and plugged into another (supporting the RDB scheme) and will carry with it all the necessary information for mounting and booting with them. |
The RDB scheme was designed to allow the automatic mounting of all partitions on a hard drive and subsequent booting from the highest priority partition even if it has a soft loaded filing system. Disks can be removed from one controller and plugged into another (supporting the RDB scheme) and will carry with it all the necessary information for mounting and booting with them. |
||
− | The preferred method of creating RigidDiskBlocks is with the ''HDToolBox'' program |
+ | The preferred method of creating RigidDiskBlocks is with the ''HDToolBox'' program. Most controllers include an RDB editor or utility. |
− | When a driver is initialized, it uses the information contained in the RDB to mount the required partitions and mark them as bootable if needed. The driver is also responsible for loading any filing systems that are required if they are not already available on the |
+ | When a driver is initialized, it uses the information contained in the RDB to mount the required partitions and mark them as bootable if needed. The driver is also responsible for loading any filing systems that are required if they are not already available on the FileSystem.resource list. File systems are added to the resource according to DosType and version number. |
The following is a listing of devices/hardblocks.h that describes all the fields in the RDB specification. |
The following is a listing of devices/hardblocks.h that describes all the fields in the RDB specification. |
||
+ | <pre> |
||
− | <pre>/*-------------------------------------------------------------------- |
||
+ | /*-------------------------------------------------------------------- |
||
* |
* |
||
* This file describes blocks of data that exist on a hard disk |
* This file describes blocks of data that exist on a hard disk |
||
Line 380: | Line 379: | ||
#define RDBFB_CTRLRID 5 /* rdb_Controller... identification valid */ |
#define RDBFB_CTRLRID 5 /* rdb_Controller... identification valid */ |
||
#define RDBFF_CTRLRID 0x20L |
#define RDBFF_CTRLRID 0x20L |
||
− | /* added 7/20/89 |
+ | /* added 7/20/89: */ |
#define RDBFB_SYNCH 6 /* drive supports scsi synchronous mode */ |
#define RDBFB_SYNCH 6 /* drive supports scsi synchronous mode */ |
||
#define RDBFF_SYNCH 0x40L /* CAN BE DANGEROUS TO USE IF IT DOESN'T! */ |
#define RDBFF_SYNCH 0x40L /* CAN BE DANGEROUS TO USE IF IT DOESN'T! */ |
||
Line 469: | Line 468: | ||
#define IDNAME_LOADSEG 0x4C534547 /* 'LSEG' */ |
#define IDNAME_LOADSEG 0x4C534547 /* 'LSEG' */ |
||
− | #endif /* DEVICES_HARDBLOCKS_H */ |
+ | #endif /* DEVICES_HARDBLOCKS_H */ |
+ | </pre> |
||
+ | |||
=== How a Driver Uses RDB === |
=== How a Driver Uses RDB === |
||
Line 480: | Line 481: | ||
# Checks the PBFB_NOMOUNT flag. If set then this partition is just reserving space. Skip to the next partition without mounting the current one. |
# Checks the PBFB_NOMOUNT flag. If set then this partition is just reserving space. Skip to the next partition without mounting the current one. |
||
# If PBFB_NOMOUNT is false, then the partition is to be mounted. The driver fetches the given drive name from pb_DriveName. This name will be of the form dh0, work, wb_2.x etc. A check is made to see if this name already exists on eb_MountList or DOS’s device list. If it does, then the name is algorithmically altered to remove duplicates. The ''A590'', ''A2091'' and ''A3000'' append .n (where ''n'' is a number) unless another name ending with .n is found. In that case the name is changed to .n+1 and the search for duplicates is retried. |
# If PBFB_NOMOUNT is false, then the partition is to be mounted. The driver fetches the given drive name from pb_DriveName. This name will be of the form dh0, work, wb_2.x etc. A check is made to see if this name already exists on eb_MountList or DOS’s device list. If it does, then the name is algorithmically altered to remove duplicates. The ''A590'', ''A2091'' and ''A3000'' append .n (where ''n'' is a number) unless another name ending with .n is found. In that case the name is changed to .n+1 and the search for duplicates is retried. |
||
− | # Next the driver constructs a parameter packet for MakeDosNode() using the (possibly altered) drive name and information about the Exec device name and unit number. MakeDosNode() is called to create a DOS device node. MakeDosNode() constructs a |
+ | # Next the driver constructs a parameter packet for MakeDosNode() using the (possibly altered) drive name and information about the Exec device name and unit number. MakeDosNode() is called to create a DOS device node. MakeDosNode() constructs a file system startup message from the given information and fills in defaults for the ROM filing system. |
# If MakeDosNode() succeeds then the driver checks to see if the entry is using a standard (“DOS0”) filing system. If not then the routine for patching in non-standard filing systems is called (see “Alien File Systems” below). |
# If MakeDosNode() succeeds then the driver checks to see if the entry is using a standard (“DOS0”) filing system. If not then the routine for patching in non-standard filing systems is called (see “Alien File Systems” below). |
||
# Now that the DOS node has been set up and the correct filing system segment has been associated with it, the driver checks PBFB_BOOTABLE to see if this partition is marked as bootable. If the partition is not bootable, or this is not autoboot time (DiagArea == 0) then the driver simply calls AddDosNode() to enqueue the DOS device node. If the partition is bootable, then the driver constructs a boot node and enqueues it on eb_MountList using the boot priority from the environment vector. If this boot priority is -128 then the partition is not considered bootable. |
# Now that the DOS node has been set up and the correct filing system segment has been associated with it, the driver checks PBFB_BOOTABLE to see if this partition is marked as bootable. If the partition is not bootable, or this is not autoboot time (DiagArea == 0) then the driver simply calls AddDosNode() to enqueue the DOS device node. If the partition is bootable, then the driver constructs a boot node and enqueues it on eb_MountList using the boot priority from the environment vector. If this boot priority is -128 then the partition is not considered bootable. |
||
Line 488: | Line 489: | ||
When a filing system other than the ROM filing system is to be used, the following steps take place. |
When a filing system other than the ROM filing system is to be used, the following steps take place. |
||
− | # First, open |
+ | # First, open FileSystem.resource in preparation for finding the file system segment we want. If FileSystem.resource doesn’t exist then create it and add it via AddResource(). Under 2.0 the resource is created by the system early on in the initialization sequence. Under pre-V36 Kickstart, it is the responsibility of the first RDB driver to create it. |
− | # Scan |
+ | # Scan FileSystem.resource looking for a file system that matches the DosType and version that we want. If it exists go to step 4. |
− | # Since the driver couldn’t find the |
+ | # Since the driver couldn’t find the file system it needed, it will have to load it from the RDB area. The list of FileSysHeaderBlocks (pointed to by the “RDSK” block) is scanned for a file system of the required DosType and version. If none is found then the driver will give up and abort the mounting of the partition. If the required file system is found, then it is LoadSeg()’ed from the “LSEG” blocks and added as a new entry to the FileSystem.resource. |
− | # The SegList pointer of the found or loaded |
+ | # The SegList pointer of the found or loaded file system is held in the FileSysEntry structure (which is basically an environment vector for this filing system). Using the patch flags, the driver now patches the newly created environment vector (pointed to by the new DosNode) using the values in the FileSysEntry being used. This ensures that the partition will have the correct filing system set up with the correct mount variables using a shared SegList. |
The eb_Mountlist will now be set up with prioritized bootnodes and maybe some non-bootable, but mounted partitions. The system bootstrap will now take over. |
The eb_Mountlist will now be set up with prioritized bootnodes and maybe some non-bootable, but mounted partitions. The system bootstrap will now take over. |
||
Line 514: | Line 515: | ||
<table> |
<table> |
||
− | <thead> |
||
<tr class="header"> |
<tr class="header"> |
||
<th align="right">Priority</th> |
<th align="right">Priority</th> |
||
<th align="left">Drive</th> |
<th align="left">Drive</th> |
||
</tr> |
</tr> |
||
− | </thead> |
||
− | <tbody> |
||
<tr class="odd"> |
<tr class="odd"> |
||
<td align="right">5</td> |
<td align="right">5</td> |
||
Line 537: | Line 535: | ||
<td align="left">df3:</td> |
<td align="left">df3:</td> |
||
</tr> |
</tr> |
||
− | </tbody> |
||
</table> |
</table> |
||
Latest revision as of 19:39, 2 October 2015
This page is not yet fully updated to AmigaOS 4.x some of the information contained here may not be applicable in part or totally. |
Contents
SCSI Device
The Small Computer System Interface (SCSI) hardware of the A3000 and A2091/A590 is controlled by the SCSI device. The SCSI device allows an application to send Exec I/O commands and SCSI commands to a SCSI peripheral. Common SCSI peripherals include hard drives, streaming tape units and CD-ROM drives.
SCSI Device Commands and Functions
Command | Command Operation |
---|---|
HD_SCSICMD | Issue a SCSI-direct command to a SCSI unit. |
Trackdisk Device Commands Supported by the SCSI Device
Command | Command Operation |
---|---|
TD_CHANGESTATE | Return the disk present/not-present status of a drive. |
TD_FORMAT | Initialize one or more tracks with a data buffer. |
TD_SEEK | Move the head to a specific track. |
TD_PROTSTATUS | Return the write-protect status of a disk. |
Exec Commands Supported by SCSI Device
Command | Command Operation |
---|---|
CMD_READ | Read one or more sectors from a disk. |
CMD_START | Restart a SCSI unit that was previously stopped with CMD_STOP. |
CMD_STOP | Stop a SCSI unit. |
CMD_WRITE | Write one or more sectors to a disk. |
Device Interface
The SCSI device operates like other Amiga devices. To use it, you must first open the SCSI device, then send I/O requests to it, and then close it when finished. See Exec Device I/O for general information on device usage.
The power of the SCSI device comes from its special facility for passing SCSI and SCSI-2 command blocks to any SCSI unit on the bus. This facility is commonly called SCSI-direct and it allows the Amiga to perform SCSI functions that are “non-standard” in terms of the normal Amiga I/O model.
To send SCSI-direct or other commands to the SCSI device, an extended I/O request structure named IOStdReq is used.
struct IOStdReq { struct Message io_Message; struct Device *io_Device; /* device node pointer */ struct Unit *io_Unit; /* unit (driver private)*/ UWORD io_Command; /* device command */ UBYTE io_Flags; BYTE io_Error; /* error or warning num */ ULONG io_Actual; /* actual number of bytes transferred */ ULONG io_Length; /* requested number bytes transferred*/ APTR io_Data; /* points to data area */ ULONG io_Offset; /* offset for block structured devices */ };
See the include file exec/io.h for the complete structure definition.
Opening the SCSI Device
Three primary steps are required to open the SCSI device:
- Create a message port using CreatePort(). Reply messages from the device must be directed to a message port.
- Create an I/O request structure of type IOStdReq. The IOStdReq structure is created by the CreateExtIO() function. CreateExtIO will initialize your IOStdReq to point to your reply port.
- Open the SCSI device. Call OpenDevice() passing it the I/O request and the SCSI unit encoded in the unit field.
SCSI unit encoding consists of three decimal digits which refer to the SCSI Target ID (bus address) in the 1s digit, the SCSI logical unit (LUN) in the 10s digit, and the controller board in the 100s digit. For example:
SCSI unit | Meaning |
6 | drive at address 6 |
12 | LUN 1 on multiple drive controller at address 2 |
104 | second controller board, address 4 |
88 | not valid: both logical units and addresses range from 0–7 |
The Commodore 2090/2090A unit numbers are encoded differently. The SCSI logical unit (LUN) is in the 100s digit, and the SCSI Target ID is a permuted 1s digit: Target ID 0–6 maps to unit 3–9 (7 is reserved for the controller).
2090/A unit | Meaning |
3 | drive at address 0 |
109 | drive at address 6, logical unit 1 |
1 | not valid: this is not a SCSI unit. |
Perhaps it’s an ST506 unit. |
Some controller boards generate a unique name for the second controller board, instead of implementing the 100s digit (e.g., the 2090A’s iddisk.device).
struct MsgPort *SCSIMP; /* Message port pointer */ struct IOStdReq *SCSIIO; /* IORequest pointer */ /* Create message port */ if (!(SCSIMP = CreatePort(NULL,NULL))) cleanexit("Can't create message port\n",RETURN_FAIL); /* Create IORequest */ if (!(SCSIIO = CreateExtIO(SCSIMP,sizeof(struct IOStdReq)))) cleanexit("Can't create IORequest\n",RETURN_FAIL); /* Open the SCSI device */ if (error = OpenDevice("scsi.device",6L,SCSIIO,0L)) cleanexit("Can't open scsi.device\n",RETURN_FAIL);
In the code above, the SCSI unit at address 6 of logical unit 0 of board 0 is opened.
Closing the SCSI Device
Each OpenDevice() must eventually be matched by a call to CloseDevice(). All I/O requests must be complete before calling CloseDevice(). If any requests are still pending, abort them with AbortIO().
if (!(CheckIO(SCSIIO))) { AbortIO(SCSIIO); /* Ask device to abort any pending requests */ WaitIO(SCSIIO); /* Wait for abort, then clean up */ } CloseDevice(SCSIIO); /* Close SCSI device */
SCSI-Direct
SCSI-direct is the facility of the Amiga’s SCSI device interface that allows low-level SCSI commands to be passed directly to a SCSI unit on the bus. This makes it possible to support the special features of tape drives, hard disks and other SCSI equipment that do not fit into the Amiga’s normal I/O model. For example, with SCSI-direct, special commands can be sent to hard drives to modify various drive parameters that are normally inaccessible or which differ from drive to drive.
In order to use SCSI-direct, you must first open the SCSI device for the unit you want to use in the manner described above. You then send an HD_SCSICMD I/O request with a pointer to a SCSI command data structure.
The SCSI device uses a special data structure for SCSI-direct commands named SCSICmd.
struct SCSICmd { UWORD *scsi_Data; /* word aligned data for SCSI Data Phase */ /* (optional) data need not be byte aligned */ /* (optional) data need not be bus accessible */ ULONG scsi_Length; /* even length of Data area */ /* (optional) data can have odd length */ /* (optional) data length can be > 2**24 */ ULONG scsi_Actual; /* actual Data used */ UBYTE *scsi_Command; /* SCSI Command (same options as scsi_Data) */ UWORD scsi_CmdLength; /* length of Command */ UWORD scsi_CmdActual; /* actual Command used */ UBYTE scsi_Flags; /* includes intended data direction */ UBYTE scsi_Status; /* SCSI status of command */ UBYTE *scsi_SenseData; /* sense data: filled if SCSIF_[OLD]AUTOSENSE */ /* is set and scsi_Status has CHECK CONDITION */ /* (bit 1) set */ UWORD scsi_SenseLength; /* size of scsi_SenseData, also bytes to */ /* request w/ SCSIF_AUTOSENSE, must be 4..255 */ UWORD scsi_SenseActual; /* amount actually fetched (0 means no sense) */ };
See the include file devices/scsidisk.h for the complete structure definition.
SCSICmd will contain the SCSI command and any associated data that you wish to pass to the SCSI unit. You set its fields to the values required by the unit and the command. When you have opened the SCSI device and set the SCSICmd to the proper values, you are ready for SCSI-direct.
You send a SCSI-direct command by passing an IOStdReq to the SCSI device with a pointer to the SCSICmd structure set in io_Data, the size of the SCSICmd structure set in io_Length and HD_SCSICMD set in io_Command.
struct IOStdReq *SCSIreq = NULL; struct SCSICmd Cmd; /* where the actual SCSI command goes */ SCSIreq->io_Length = sizeof(struct SCSICmd); SCSIreq->io_Data = (APTR)&Cmd; SCSIreq->io_Command = HD_SCSICMD; DoIO(SCSIreq);
The SCSICmd structure must be filled in prior to passing it to the SCSI unit. How it is filled in depends on the SCSI-direct being passed to the unit. Below is an example of setting up a SCSICmd structure for the MODE_SENSE SCSI-direct command.
UBYTE *buffer; /* a data buffer used for mode sense data */ UBYTE Sense[20]; /* buffer for request sense data */ struct SCSICmd Cmd; /* where the actual SCSI command goes */ static UBYTE ModeSense[]={ 0x1a,0,0xff,0,254,0 }; /* the command being sent */ Cmd.scsi_Data = (UWORD *)buffer; /* where we put mode sense data */ Cmd.scsi_Length = 254; /* how much we will accept */ Cmd.scsi_CmdLength = 6; /* length of the command */ Cmd.scsi_Flags = SCSIF_AUTOSENSE | /* do automatic REQUEST_SENSE */ SCSIF_READ; /* set expected data direction */ Cmd.scsi_SenseData = (UBYTE *)Sense; /* where sense data will go */ Cmd.scsi_SenseLength = 18; /* how much we will accept */ Cmd.scsi_SenseActual = 0; /* how much has been received */ Cmd.scsi_Command=(UBYTE *)ModeSense;/* issuing a MODE_SENSE command */
The fields of the SCSICmd are:
- scsi_Data
- This field points to the data buffer for the SCSI data phase (if any is expected). It is generally the job of the driver software to ensure that the given buffer is DMA-accessible and to drop to programmed I/O if it isn’t. The filing system provides a stop-gap fix for non-conforming drivers with the AddressMask parameter in DEVS:MountList. For absolute safety, restrict all direct reads and writes to Chip RAM.
- scsi_Length
This is the expected length of data to be transferred. If an unknown amount of data is to be transferred from target to host, set the scsi_Length to be larger than the maximum amount of data expected. Some controllers explicitly use scsi_Length as the amount of data to transfer. The A2091, A590 and A3000 drivers always do programmed I/O for data transfers under 256 bytes or when the DMA chip doesn’t support the required alignment.
- scsi_Actual
- How much data was actually received from or sent to the SCSI unit in response to the SCSI-direct command.
- scsi_Command
- The SCSI-direct command.
- scsi_CmdLength
- The length of the SCSI-direct command in bytes.
- scsi_CmdActual
- The actual number of bytes of the SCSI-direct command that were transferred to the SCSI unit.
- scsi_Flags
- These flags contain the intended data direction for the SCSI command. It is not strictly necessary to set the data direction flag since the SCSI protocol will inform the driver which direction data transfers will be going. However, some controllers use this information to set up DMA before issuing the command . It can also be used as a sanity check in case the data phase goes the wrong way.
- One flag in particular, is worth noting. SCSIF_AUTOSENSE is used to make the driver perform an automatic REQUEST SENSE if the target returns CHECK CONDITION for a SCSI command. The reason for having the driver do this is the multitasking nature of the Amiga. If two tasks were accessing the same drive and the first received a CHECK CONDITION, the second task would destroy the sense information when it sent a command. SCSIF_AUTOSENSE prevents the caller from having to make two I/O requests and removes this window of vulnerability.
- scsi_Status
- The status of the SCSI-direct command. The values returned in this field can be found in the SCSI specification. For example, 2 is CHECK_CONDITION.
- scsi_SenseActual
- If the SCSIF_AUTOSENSE flag is set, it is important to initialize this field to zero before issuing a SCSI command because some drivers don’t support AUTOSENSE and won’t initialize the field.
- scsi_SenseData
- This field is used only for SCSIF_AUTOSENSE. If a REQUEST SENSE command is directly sent to the driver, the data will be deposited in the buffer pointed to by scsi_Data.
Keep in mind that SCSI-direct is geared toward an initiator role so it can’t be expected to perform target-like operations. You can only send commands to a device, not receive them from an initiator. There is no provision for SCSI messaging, either. This is due mainly to the interactive nature of the extended messages (such as synchronous transfer requests) which have to be handled by the driver because it knows the limitations of the controller card and has to be made aware of such protocol changes.
RigidDiskBlock – Fields and Implementation
The RigidDiskBlock (RDB) standard was borne out of the same development effort as HD_SCSICMD and as a result has a heavy bias towards SCSI. However, there is nothing in the RDB specification that makes it unusable for devices using other bus protocols. The XT style disks used in the A590 also support the RDB standard.
The RDB scheme was designed to allow the automatic mounting of all partitions on a hard drive and subsequent booting from the highest priority partition even if it has a soft loaded filing system. Disks can be removed from one controller and plugged into another (supporting the RDB scheme) and will carry with it all the necessary information for mounting and booting with them.
The preferred method of creating RigidDiskBlocks is with the HDToolBox program. Most controllers include an RDB editor or utility.
When a driver is initialized, it uses the information contained in the RDB to mount the required partitions and mark them as bootable if needed. The driver is also responsible for loading any filing systems that are required if they are not already available on the FileSystem.resource list. File systems are added to the resource according to DosType and version number.
The following is a listing of devices/hardblocks.h that describes all the fields in the RDB specification.
/*-------------------------------------------------------------------- * * This file describes blocks of data that exist on a hard disk * to describe that disk. They are not generically accessable to * the user as they do not appear on any DOS drive. The blocks * are tagged with a unique identifier, checksummed, and linked * together. The root of these blocks is the RigidDiskBlock. * * The RigidDiskBlock must exist on the disk within the first * RDB_LOCATION_LIMIT blocks. This inhibits the use of the zero * cylinder in an AmigaDOS partition: although it is strictly * possible to store the RigidDiskBlock data in the reserved * area of a partition, this practice is discouraged since the * reserved blocks of a partition are overwritten by "Format", * "Install", "DiskCopy", etc. The recommended disk layout, * then, is to use the first cylinder(s) to store all the drive * data specified by these blocks: i.e. partition descriptions, * file system load images, drive bad block maps, spare blocks, * etc. * * Though only 512 byte blocks are currently supported by the * file system, this proposal tries to be forward-looking by * making the block size explicit, and by using only the first * 256 bytes for all blocks but the LoadSeg data. * *------------------------------------------------------------------*/ /* * NOTE * optional block addresses below contain $ffffffff to indicate * a NULL address, as zero is a valid address */ struct RigidDiskBlock { ULONG rdb_ID; /* 4 character identifier */ ULONG rdb_SummedLongs; /* size of this checksummed structure */ LONG rdb_ChkSum; /* block checksum (longword sum to zero) */ ULONG rdb_HostID; /* SCSI Target ID of host */ ULONG rdb_BlockBytes; /* size of disk blocks */ ULONG rdb_Flags; /* see below for defines */ /* block list heads */ ULONG rdb_BadBlockList; /* optional bad block list */ ULONG rdb_PartitionList; /* optional first partition block */ ULONG rdb_FileSysHeaderList; /* optional file system header block */ ULONG rdb_DriveInit; /* optional drive-specific init code */ /* DriveInit(lun,rdb,ior): "C" stk & d0/a0/a1 */ ULONG rdb_Reserved1[6]; /* set to $ffffffff */ /* physical drive characteristics */ ULONG rdb_Cylinders; /* number of drive cylinders */ ULONG rdb_Sectors; /* sectors per track */ ULONG rdb_Heads; /* number of drive heads */ ULONG rdb_Interleave; /* interleave */ ULONG rdb_Park; /* landing zone cylinder */ ULONG rdb_Reserved2[3]; ULONG rdb_WritePreComp; /* starting cylinder: write precompensation */ ULONG rdb_ReducedWrite; /* starting cylinder: reduced write current */ ULONG rdb_StepRate; /* drive step rate */ ULONG rdb_Reserved3[5]; /* logical drive characteristics */ ULONG rdb_RDBBlocksLo; /* low block of range reserved for hardblocks */ ULONG rdb_RDBBlocksHi; /* high block of range for these hardblocks */ ULONG rdb_LoCylinder; /* low cylinder of partitionable disk area */ ULONG rdb_HiCylinder; /* high cylinder of partitionable data area */ ULONG rdb_CylBlocks; /* number of blocks available per cylinder */ ULONG rdb_AutoParkSeconds; /* zero for no auto park */ ULONG rdb_HighRDSKBlock; /* highest block used by RDSK */ /* (not including replacement bad blocks) */ ULONG rdb_Reserved4; /* drive identification */ char rdb_DiskVendor[8]; char rdb_DiskProduct[16]; char rdb_DiskRevision[4]; char rdb_ControllerVendor[8]; char rdb_ControllerProduct[16]; char rdb_ControllerRevision[4]; ULONG rdb_Reserved5[10]; }; #define IDNAME_RIGIDDISK 0x5244534B /* 'RDSK' */ #define RDB_LOCATION_LIMIT 16 #define RDBFB_LAST 0 /* no disks exist to be configured after */ #define RDBFF_LAST 0x01L /* this one on this controller */ #define RDBFB_LASTLUN 1 /* no LUNs exist to be configured greater */ #define RDBFF_LASTLUN 0x02L /* than this one at this SCSI Target ID */ #define RDBFB_LASTTID 2 /* no Target IDs exist to be configured */ #define RDBFF_LASTTID 0x04L /* greater than this one on this SCSI bus */ #define RDBFB_NORESELECT 3 /* don't bother trying to perform reselection */ #define RDBFF_NORESELECT 0x08L /* when talking to this drive */ #define RDBFB_DISKID 4 /* rdb_Disk... identification valid */ #define RDBFF_DISKID 0x10L #define RDBFB_CTRLRID 5 /* rdb_Controller... identification valid */ #define RDBFF_CTRLRID 0x20L /* added 7/20/89: */ #define RDBFB_SYNCH 6 /* drive supports scsi synchronous mode */ #define RDBFF_SYNCH 0x40L /* CAN BE DANGEROUS TO USE IF IT DOESN'T! */ /*------------------------------------------------------------------*/ struct BadBlockEntry { ULONG bbe_BadBlock; /* block number of bad block */ ULONG bbe_GoodBlock; /* block number of replacement block */ }; struct BadBlockBlock { ULONG bbb_ID; /* 4 character identifier */ ULONG bbb_SummedLongs; /* size of this checksummed structure */ LONG bbb_ChkSum; /* block checksum (longword sum to zero) */ ULONG bbb_HostID; /* SCSI Target ID of host */ ULONG bbb_Next; /* block number of the next BadBlockBlock */ ULONG bbb_Reserved; struct BadBlockEntry bbb_BlockPairs[61]; /* bad block entry pairs */ /* note [61] assumes 512 byte blocks */ }; #define IDNAME_BADBLOCK 0x42414442 /* 'BADB' */ /*------------------------------------------------------------------*/ struct PartitionBlock { ULONG pb_ID; /* 4 character identifier */ ULONG pb_SummedLongs; /* size of this checksummed structure */ LONG pb_ChkSum; /* block checksum (longword sum to zero) */ ULONG pb_HostID; /* SCSI Target ID of host */ ULONG pb_Next; /* block number of the next PartitionBlock */ ULONG pb_Flags; /* see below for defines */ ULONG pb_Reserved1[2]; ULONG pb_DevFlags; /* preferred flags for OpenDevice */ UBYTE pb_DriveName[32]; /* preferred DOS device name: BSTR form */ /* (not used if this name is in use) */ ULONG pb_Reserved2[15]; /* filler to 32 longwords */ ULONG pb_Environment[17]; /* environment vector for this partition */ ULONG pb_EReserved[15]; /* reserved for future environment vector */ }; #define IDNAME_PARTITION 0x50415254 /* 'PART' */ #define PBFB_BOOTABLE 0 /* this partition is intended to be bootable */ #define PBFF_BOOTABLE 1L /* (expected directories and files exist) */ #define PBFB_NOMOUNT 1 /* do not mount this partition (e.g. manually */ #define PBFF_NOMOUNT 2L /* mounted, but space reserved here) */ /*------------------------------------------------------------------*/ struct FileSysHeaderBlock { ULONG fhb_ID; /* 4 character identifier */ ULONG fhb_SummedLongs; /* size of this checksummed structure */ LONG fhb_ChkSum; /* block checksum (longword sum to zero) */ ULONG fhb_HostID; /* SCSI Target ID of host */ ULONG fhb_Next; /* block number of next FileSysHeaderBlock */ ULONG fhb_Flags; /* see below for defines */ ULONG fhb_Reserved1[2]; ULONG fhb_DosType; /* file system description: match this with */ /* partition environment's DE_DOSTYPE entry */ ULONG fhb_Version; /* release version of this code */ ULONG fhb_PatchFlags; /* bits set for those of the following that */ /* need to be substituted into a standard */ /* device node for this file system: e.g. */ /* 0x180 to substitute SegList & GlobalVec */ ULONG fhb_Type; /* device node type: zero */ ULONG fhb_Task; /* standard dos "task" field: zero */ ULONG fhb_Lock; /* not used for devices: zero */ ULONG fhb_Handler; /* filename to loadseg: zero placeholder */ ULONG fhb_StackSize; /* stacksize to use when starting task */ LONG fhb_Priority; /* task priority when starting task */ LONG fhb_Startup; /* startup msg: zero placeholder */ LONG fhb_SegListBlocks; /* first of linked list of LoadSegBlocks: */ /* note that this entry requires some */ /* processing before substitution */ LONG fhb_GlobalVec; /* BCPL global vector when starting task */ ULONG fhb_Reserved2[23]; /* (those reserved by PatchFlags) */ ULONG fhb_Reserved3[21]; }; #define IDNAME_FILESYSHEADER 0x46534844 /* 'FSHD' */ /*------------------------------------------------------------------*/ struct LoadSegBlock { ULONG lsb_ID; /* 4 character identifier */ ULONG lsb_SummedLongs; /* size of this checksummed structure */ LONG lsb_ChkSum; /* block checksum (longword sum to zero) */ ULONG lsb_HostID; /* SCSI Target ID of host */ ULONG lsb_Next; /* block number of the next LoadSegBlock */ ULONG lsb_LoadData[123]; /* data for "loadseg" */ /* note [123] assumes 512 byte blocks */ }; #define IDNAME_LOADSEG 0x4C534547 /* 'LSEG' */ #endif /* DEVICES_HARDBLOCKS_H */
How a Driver Uses RDB
The information contained in the RigidDiskBlock and subsequent PartitionBlocks, et al., is used by a driver in the following manner.
After determining that the target device is a hard disk (using the SCSI-direct command INQUIRY), the driver will scan the first RDB_LOCATION_LIMIT (16) blocks looking for a block with the “RDSK” identifier and a correct sum-to-zero checksum. If no RDB is found then the driver will give up and not attempt to mount any partitions for this unit. If the RDB is found then the driver looks to see if there’s a partition list for this unit (rdb_PartitionList). If none, then just the rdb_Flags will be used to determine if there are any LUNs or units after this one. This is used for early termination of the search for units on bootup.
If a partition list is present, and the partition blocks have the correct ID and checksum, then for each partition block the driver does the following.
- Checks the PBFB_NOMOUNT flag. If set then this partition is just reserving space. Skip to the next partition without mounting the current one.
- If PBFB_NOMOUNT is false, then the partition is to be mounted. The driver fetches the given drive name from pb_DriveName. This name will be of the form dh0, work, wb_2.x etc. A check is made to see if this name already exists on eb_MountList or DOS’s device list. If it does, then the name is algorithmically altered to remove duplicates. The A590, A2091 and A3000 append .n (where n is a number) unless another name ending with .n is found. In that case the name is changed to .n+1 and the search for duplicates is retried.
- Next the driver constructs a parameter packet for MakeDosNode() using the (possibly altered) drive name and information about the Exec device name and unit number. MakeDosNode() is called to create a DOS device node. MakeDosNode() constructs a file system startup message from the given information and fills in defaults for the ROM filing system.
- If MakeDosNode() succeeds then the driver checks to see if the entry is using a standard (“DOS0”) filing system. If not then the routine for patching in non-standard filing systems is called (see “Alien File Systems” below).
- Now that the DOS node has been set up and the correct filing system segment has been associated with it, the driver checks PBFB_BOOTABLE to see if this partition is marked as bootable. If the partition is not bootable, or this is not autoboot time (DiagArea == 0) then the driver simply calls AddDosNode() to enqueue the DOS device node. If the partition is bootable, then the driver constructs a boot node and enqueues it on eb_MountList using the boot priority from the environment vector. If this boot priority is -128 then the partition is not considered bootable.
Alien Filing Systems
When a filing system other than the ROM filing system is to be used, the following steps take place.
- First, open FileSystem.resource in preparation for finding the file system segment we want. If FileSystem.resource doesn’t exist then create it and add it via AddResource(). Under 2.0 the resource is created by the system early on in the initialization sequence. Under pre-V36 Kickstart, it is the responsibility of the first RDB driver to create it.
- Scan FileSystem.resource looking for a file system that matches the DosType and version that we want. If it exists go to step 4.
- Since the driver couldn’t find the file system it needed, it will have to load it from the RDB area. The list of FileSysHeaderBlocks (pointed to by the “RDSK” block) is scanned for a file system of the required DosType and version. If none is found then the driver will give up and abort the mounting of the partition. If the required file system is found, then it is LoadSeg()’ed from the “LSEG” blocks and added as a new entry to the FileSystem.resource.
- The SegList pointer of the found or loaded file system is held in the FileSysEntry structure (which is basically an environment vector for this filing system). Using the patch flags, the driver now patches the newly created environment vector (pointed to by the new DosNode) using the values in the FileSysEntry being used. This ensures that the partition will have the correct filing system set up with the correct mount variables using a shared SegList.
The eb_Mountlist will now be set up with prioritized bootnodes and maybe some non-bootable, but mounted partitions. The system bootstrap will now take over.
Amiga BootStrap
At priority -40 in the system module initialization sequence, after most other modules are initialized, appropriate expansion boards are configured. Appropriate boards will match a FindConfigDev(, -1, -1)—these are all boards on the expansion library board list. Furthermore, they will meet all of the following conditions:
CDB_CONFIGME
set in cd_Flags,
ERTB_DIAGVALID
set in cd_Rom->er_Type,
diagnostic area pointer (in cd_Rom->er_Reserved0c) is non-zero,
DAC_CONFIGTIME
set in da_Config, and
at least one valid resident tag within the diagnostic area, the first of which is used by InitResident() below. This resident structure was patched to be valid during the ROM diagnostic routine run when the expansion library first initialized the board.
Boards meeting all these conditions are initialized with the standard InitResident() mechanism, with a NULL SegList. The board initialization code can find its ConfigDev structure with the expansion.library’s GetCurrentBinding() function. This is an appropriate time for drivers to Enqueue() a boot node on the expansion library’s eb_MountList for use by the strap module below, and clear CDB_CONFIGME so a C:BindDrivers command will not try to initialize the board a second time.
This module will also enqueue nodes for 3.5’’ trackdisk device units. These nodes will be at the following priorities:
Priority | Drive |
---|---|
5 | df0: |
-10 | df1: |
-20 | df2: |
-30 | df3: |
Next, at priority -60 in the system module initialization sequence, the strap module is invoked. Nodes from the prioritized eb_MountList list is used in priority order in attempts to boot. An item on the list is given a chance to boot via one of two different mechanisms, depending on whether it it uses boot code read in off the disk (BootBlock booting), or uses boot code provided in the device ConfigDev diagnostic area (BootPoint booting). Floppies always use the BootBlock method. Other entries put on the eb_MountList (e.g. hard disk partitions) used the BootPoint mechanism for pre-V36 Kickstart, but can use either for V36/V37.
The eb_MountList is modified before each boot attempt, and then restored and re-modified for the next attempt if the boot fails:
- The node associated with the current boot attempt is placed at the head of the eb_MountList.
- Nodes marked as unusable under AmigaDOS are removed from the list. Nodes that are unusable are marked by the longword bn_DeviceNode->dn_Handler having the most significant bit set. This is used, for example, to keep Unix partitions off the AmigaDOS device list when booting AmigaDOS instead of Unix.
The selection of which of the two different boot mechanisms to use proceeds as follows:
The node must be valid boot node, i.e. meet both of the following conditions:
ln_Type
is NT_BOOTNODE,
bn_DeviceNode
is non-zero,
The type of boot is determined by looking at the DosEnvec pointed to by fssm_Environ pointed to by the dn_Startup in the bn_DeviceNode:
if the de_TableSize is less than DE_BOOTBLOCKS, or the de_BootBlocks entry is zero, BootPoint booting is specified, otherwise
de_BootBlocks
contains the number of blocks to read in from the beginning of the partition, checksum, and try to boot from.
BootBlock Booting
In BootBlock booting the sequence of events is as follows:
- The disk device must contain valid boot blocks:
- the device and unit from dn_Startup opens successfully,
- memory is available for the de_BootBlocks * de_SizeBlock * 4 bytes of boot block code,
- the device commands CMD_CLEAR, TD_CHANGENUM, and CMD_READ of the boot blocks execute without error,
- the boot blocks start with the three characters “DOS” and pass the longword checksum (with carry wraparound), and
- memory is available to construct a boot node on the eb_MountList to describe the floppy. If a device error is reported in 1.c., or if memory is not available for 1.b. or 1.e., a recoverable alert is presented before continuing.
- The boot code in the boot blocks is invoked as follows:
- The address of the entry point for the boot code is offset BB_ENTRY into the boot blocks in memory.
- The boot code is invoked with the I/O request used to issue the device commands in 1.c. above in register A1, with the io_Offset pointing to the beginning of the partition (the origin of the boot blocks) and SysBase in A6.
- The boot code returns with results in both D0 and A0.
- Non-zero D0 indicates boot failure. The recoverable alert AN_BootError is presented before continuing.
- Zero D0 indicates A0 contains a pointer to the function to complete the boot. This completion function is chained to with SysBase in A6 after the strap module frees all its resources. It is usually the dos.library initialization function, from the dos.library resident tag. Return from this function is identical to return from the strap module itself.
BootPoint Booting
BootPoint booting follows this sequence:
The eb_MountList node must contain a valid BootPoint:
ConfigDev
pointer (in ln_Name) is non-zero,
diagnostic area pointer (in cd_Rom->er_Reserved0c) is non-zero,
DAC_CONFIGTIME
set in da_Config.
The boot routine of a valid boot node is invoked as follows:
The address of the boot routine is calculated from da_BootPoint.
The resulting boot routine is invoked with the ConfigDev pointer on the stack in C fashion (i.e., (*boot)(configDev);). Moreover, register A2 will contain the address of the associated eb_MountList node.
Return from the boot routine indicates failure to boot.
If all entries fail to boot, the user is prompted to put a bootable disk into a floppy drive with the “strap screen”. The system floppy drives are polled for new disks. When one appears, the “strap screen” is removed and the appropriate boot mechanism is applied as described above. The process of prompting and trying continues till a successful boot occurs.
SCSI-Direct Example
/* * SCSI_Direct.c * * The following program demonstrates the use of the HD_SCSICmd to send a * MODE SENSE to a unit on the requested device (default scsi.device). This * code can be easily modified to send other commands to the drive. * * Compile with SAS C 5.10 lc -b1 -cfistq -v -y -L * * Run from CLI only */ #include <exec/types.h> #include <exec/memory.h> #include <exec/io.h> #include <devices/scsidisk.h> #include <dos/dosextens.h> #include <clib/exec_protos.h> #include <clib/alib_protos.h> #include <stdlib.h> #include <stdio.h> #ifdef LATTICE int CXBRK(void) { return(0); } /* Disable SAS CTRL/C handling */ int chkabort(void) { return(0); } /* really */ #endif #define BUFSIZE 256 UBYTE *buffer; /* a data buffer used for mode sense data */ struct IOStdReq SCSIReq; /* a standard IORequest structure */ struct SCSICmd Cmd; /* where the actual SCSI command goes */ UBYTE Sense[20]; /* buffer for request sense data */ struct MsgPort Port; /* our ReplyPort */ void ShowSenseData(void); static UBYTE TestReady[] = { 0,0,0,0,0,0 }; /* not used but here for */ static UBYTE StartUnit[] = { 0x1b,0,0,0,1,0 }; /* illustration of other */ static UBYTE StopUnit[] = { 0x1b,0,0,0,0,0 }; /* commands. */ static UBYTE ModeSense[]={ 0x1a,0,0xff,0,254,0 }; /* the command being sent */ void main(int argc, char **argv) { int unit,tval,i; char *dname = "scsi.device"; UBYTE *tbuf; if ((argc < 2) || (argc > 3)) { printf("Usage: %s unit [xxxx.device]\n",argv[0]); exit(100); } unit = atoi( argv[1] ); if (argc == 3) dname = argv[2]; buffer = (UBYTE *) AllocMem(BUFSIZE, MEMF_PUBLIC|MEMF_CLEAR); if (!buffer) { printf("Couldn't get memory\n"); exit(100); } Port.mp_Node.ln_Pri = 0; /* setup the ReplyPort */ Port.mp_SigBit = AllocSignal(-1); Port.mp_SigTask = (struct Task *)FindTask(0); NewList( &(Port.mp_MsgList) ); SCSIReq.io_Message.mn_ReplyPort = &Port; if (OpenDevice( dname, unit, &SCSIReq, 0)) { printf("Couldn't open unit %ld on %s\n",unit,dname); FreeMem( buffer,BUFSIZE ); exit(100); } SCSIReq.io_Length = sizeof(struct SCSICmd); SCSIReq.io_Data = (APTR)&Cmd; SCSIReq.io_Command = HD_SCSICMD; /* the command we are sending */ Cmd.scsi_Data = (UWORD *)buffer; /* where we put mode sense data */ Cmd.scsi_Length = 254; /* how much we will accept */ Cmd.scsi_CmdLength = 6; /* length of the command */ Cmd.scsi_Flags = SCSIF_AUTOSENSE|SCSIF_READ; /* do automatic REQUEST_SENSE */ /* set expected data direction */ Cmd.scsi_SenseData =(UBYTE *)Sense; /* where sense data will go */ Cmd.scsi_SenseLength = 18; /* how much we will accept */ Cmd.scsi_SenseActual = 0; /* how much has been received */ Cmd.scsi_Command=(UBYTE *)ModeSense;/* issuing a MODE_SENSE command */ DoIO( &SCSIReq ); /* send it to the device driver */ if (Cmd.scsi_Status) ShowSenseData(); /* if bad status then show it */ else { printf("\nBlock descriptor header\n"); printf("=======================\n"); printf("Mode Sense data length = %d\n",(short)buffer[0]); printf("Block descriptor length = %d\n",(short)buffer[3]); tbuf = &buffer[4]; printf("Density code = %d\n",(short)tbuf[0]); tval = (tbuf[1]<<16) + (tbuf[2]<<8) + tbuf[3]; printf("Number of blocks = %ld\n",tval); tval = (tbuf[5]<<16) + (tbuf[6]<<8) + tbuf[7]; printf("Block size = %ld\n",tval); tbuf += buffer[3]; /* move to page descriptors */ while ((tbuf - buffer) < buffer[0]) { switch (tbuf[0] & 0x7f) { case 1: printf("\nError Recovery Parameters\n"); printf("=========================\n"); printf("Page length = %d\n",(short)tbuf[1]); printf("DISABLE CORRECTION = %d\n",(short)tbuf[2]&1); printf("DISABLE XFER ON ERROR = %d\n",(short)(tbuf[2]>>1)&1); printf("POST ERROR = %d\n",(short)(tbuf[2]>>2)&1); printf("ENABLE EARLY CORRECTION = %d\n",(short)(tbuf[2]>>3)&1); printf("READ CONTINUOUS = %d\n",(short)(tbuf[2]>>4)&1); printf("TRANSFER BLOCK = %d\n",(short)(tbuf[2]>>5)&1); printf("AUTO READ REALLOCATION = %d\n",(short)(tbuf[2]>>6)&1); printf("AUTO WRITE REALLOCATION = %d\n",(short)(tbuf[2]>>7)&1); printf("Retry count = %d\n",(short)tbuf[3]); printf("Correction span = %d\n",(short)tbuf[4]); printf("Head offset count = %d\n",(short)tbuf[5]); printf("Data strobe offset count= %d\n",(short)tbuf[6]); printf("Recovery time limit = %d\n",(short)tbuf[7]); tbuf += tbuf[1]+2; break; case 2: printf("\nDisconnect/Reconnect Control\n"); printf("============================\n"); printf("Page length = %d\n",(short)tbuf[1]); printf("Buffer full ratio = %d\n",(short)tbuf[2]); printf("Buffer empty ratio = %d\n",(short)tbuf[3]); tval = (tbuf[4]<<8)+tbuf[5]; printf("Bus inactivity limit = %d\n",tval); tval = (tbuf[6]<<8)+tbuf[7]; printf("Disconnect time limit = %d\n",tval); tval = (tbuf[8]<<8)+tbuf[9]; printf("Connect time limit = %d\n",tval); tval = (tbuf[10]<<8)+tbuf[11]; printf("Maximum burst size = %d\n",tval); printf("Disable disconnection = %d\n",(short)tbuf[12]&1); tbuf += tbuf[1]+2; break; case 3: printf("\nDevice Format Parameters\n"); printf("========================\n"); printf("Page length = %d\n",(short)tbuf[1]); tval = (tbuf[2]<<8)+tbuf[3]; printf("Tracks per zone = %d\n",tval); tval = (tbuf[4]<<8)+tbuf[5]; printf("Alternate sectors/zone = %d\n",tval); tval = (tbuf[6]<<8)+tbuf[7]; printf("Alternate tracks/zone = %d\n",tval); tval = (tbuf[8]<<8)+tbuf[9]; printf("Alternate tracks/volume = %d\n",tval); tval = (tbuf[10]<<8)+tbuf[11]; printf("Sectors per track = %d\n",tval); tval = (tbuf[12]<<8)+tbuf[13]; printf("Bytes per sector = %d\n",tval); tval = (tbuf[14]<<8)+tbuf[15]; printf("Interleave = %d\n",tval); tval = (tbuf[16]<<8)+tbuf[17]; printf("Track skew factor = %d\n",tval); tval = (tbuf[18]<<8)+tbuf[19]; printf("Cylinder skew factor = %d\n",tval); tbuf += tbuf[1]+2; break; case 4: printf("\nDrive Geometry Parameters\n"); printf("=========================\n"); printf("Page length = %d\n",(short)tbuf[1]); tval = (tbuf[2]<<16)+(tbuf[3]<<8)+tbuf[4]; printf("Number of cylinders = %ld\n",tval); printf("Number of heads = %d\n",(short)tbuf[5]); tval = (tbuf[6]<<16)+(tbuf[6]<<8)+tbuf[8]; printf("Start write precomp = %ld\n",tval); tval = (tbuf[9]<<16)+(tbuf[10]<<8)+tbuf[11]; printf("Start reduced write = %ld\n",tval); tval = (tbuf[12]<<8)+tbuf[13]; printf("Drive step rate = %d\n",tval); tval = (tbuf[14]<<16)+(tbuf[15]<<8)+tbuf[16]; printf("Landing zone cylinder = %ld\n",tval); tbuf += tbuf[1]+2; break; default: printf("\nVendor Unique Page Code %2x\n",(short)tbuf[0]); printf("==========================\n"); for (i=0; i<=tbuf[1]+1; i++ ) printf("%x ",(short)tbuf[i]); printf("\n"); tbuf += tbuf[1]+2; } } } CloseDevice( &SCSIReq ); FreeMem( buffer, BUFSIZE ); FreeSignal(Port.mp_SigBit); } void ShowSenseData(void) { int i; for (i=0; i<18; i++) printf("%x ",(int)Sense[i]); printf("\n"); }
Additional Information on the SCSI Device
Additional programming information on the SCSI device can be found in the include files for the SCSI device and RigidDiskBlock. Both are contained in the SDK.
For information on the SCSI commands, see either the ANSI-X3T9 (draft SCSI-2) or ANSI X3.131 (SCSI-1) specification.
Includes |
---|
devices/scsidisk.h |
devices/hardblocks.h |