This month, I'm going to talk about writing a driver for a simple SCSI controller under Linux. The Linux kernel SCSI layer does most of the work for SCSI device handling, so a simple SCSI driver is relatively painless to write. For more advanced devices, however, the kernel's SCSI code is actually too clever -- there are plans afoot to streamline it and solve these problems.
This month, I’m going to talk about writing a driver for a simple SCSI controller under Linux. The Linux kernel SCSI layer does most of the work for SCSI device handling, so a simple SCSI driver is relatively painless to write. For more advanced devices, however, the kernel’s SCSI code is actually too clever — there are plans afoot to streamline it and solve these problems.
The job of a SCSI driver is different than that of a block device driver. The upper layers of the SCSI code handle CD-ROMs, disks and other devices. Requests from these upper layers are turned into SCSI command blocks before they are fed to your driver. This means your SCSI driver need only worry about low-level SCSI requests and not about other aspects of the kernel device structure.
In order to illustrate SCSI drivers I’m going to invent a SCSI controller that has a simple command interface. Sadly, this is rarely the case for real devices. However, our imaginary SCSI controller will make the examples much easier to follow. This should be enough to get you started — dealing with the complexities of a particular piece of hardware would take up more space than this column allows.
Overall Driver Structure
A Linux SCSI driver contains seven main functions:
The detect function is called by the SCSI layer when the driver is initialized. It is responsible for scanning for the controller and registering whatever it finds with the SCSI layer. Oncecontrollers are registered, the SCSI layer will issue commands to the controller to probe the SCSI devices on the chain.
The queuecommand function issues a SCSI command and does not wait for it to finish. This is used by almost all
operations. When the command completes, the driver calls back up to the SCSI layer to inform it of the completion, passing back any error information it may find.
The command function issues a SCSI command synchronously and then waits for it to complete. Most drivers do this by calling their own queuecommand function.
The abort and reset functions are used to handle error situations orcases where the SCSI layer thinks a command has gone missing. The SCSI layer will first attempt to abort a command which has gone astray; if this isn’t effective, then the SCSI layer may need to reset the entire controller. Hopefully, this will never happen.
The info function returns a description of the actual controller itself. This descriptor tends to be a very short piece of code.
Finally, the bios_param function is called by the SCSI layer to ask the controller to query its BIOS for a faked disk geometry, or to have the driver compute a geometry itself. This is necessary as the mapping of SCSI blocks to the PC disk geometry has never been properly standardized; different controllers implement this in different ways.
The detect function [ see Figure 1 ] is called at boot time or when a SCSI module is loaded. For our example we are going to assume that there can be only one card and that it behaves sanely.
Figure 1: The myscsi_detect Function
int myscsi_detect(Scsi_Host_Template *tpnt)
struct Scsi_Host *shpnt;
int io = 0x320, irq = 11; /* Assume fixed for example */
if(myscsi_probe(io, irq) == 0)
/* Found - create an instance of this controller */
shpnt = scsi_register(tpnt, 0);
if(shpnt == NULL)
shpnt->unique_id = io;
shpnt->io_port = io;
shpnt->n_io_port = MY_PORT_RANGE;
shpnt->irq = irq;
shpnt->this_id = MY_SCSI_ID;
if(request_irq(irq, my_irq_handler, 0, "myscsi", shpnt))
printk("my_scsi: IRQ %d is busy.\n", irq);
In our example we will use a fixed IO and IRQ. A real controller would either read PCI space or would probe a list of common addresses. The logic to probe for a particular device is hidden here in the function myscsi_probe .
After probing for the controller, we create a device structure using scsi_register . tpnt is the template passed into the detect function, described later in the article. The returned structure, shpnt , represents the particular controller which we have registered. We pass 0 for the second argument of scsi_register , as no driver-private memory area is needed. Passing a size arranges for a private block to be allocated as shpnt->hostdata , for convenience.
Now we start to fill in the shpnt structure. unique_id is for distinguishing multiple cards. In our case, the I/O portis a convenient choice for this. this_id holds the ID of the controller itself. Each SCSI device has an identity, including the controller ID, which the kernel SCSI layer needs to know about. We assume for this example that it is fixed.
We then call the my_hardware_init routine, which initializes the actual hardware. You get to write this routine to do whatever initialization the hardware requires.
The Interrupt Handler
The next step is to register an interrupt handler in the usual way. If we can’t register an interrupt handler, we are stuck. In this case we unregister the SCSI controller and report no controllers found. We also let the user know, so as to avoid confusion.
If everything worked correctly, we report that one controller was found. (A real driver would count all detected controllers and return that value.) The SCSI layer will now go off and scan for devices hanging off of this controller. We will take care of this by using the queuecommand and command functions.
The queuecommand routine in Figure 2 takes two arguments: a pointer to the command structure, and the function pointer ( done ), which this driver should call when the command completes.
For this example, we will assume that the controller handles only one command at a time. This is typical for a cheap ISA controller, but not for decent hardware. If we supported many concurrent SCSI commands we couldn’t use a global current_command value, but would need to keep some kind of list and match replies from the card to the list entries. This is basic programming and not important to our understanding of the driver structure.
Using the outb kernel routine to perform I/O, we load the target device into the card, then we load the command. SCSI commands are blocks of up to 16 bytes, including length information. After pushing the command to the card, we kick it to begin operation and allow interrupts as we are ready to handle the result of the command.
After queuing the command, we return back to the SCSI layer. If there is no response to the command, the SCSI layer will bother us after a timeout.
When the SCSI layer does want to bother us about commands that have gone astray, it will call our abort function; if that fails, it will call our reset function. Many simpler controllers cannot abort a queued command. If so, the abort function is quite simple, as can be seen in Figure 3.
Figure 3: The myscsi_abort Function
int myscsi_abort(Scsi_Cmnd *SCpnt)
In returning SCSI_ABORT_ SNOOZE , the driver is asking the kernel to wait a bit longer and hope that the command completes. If this doesn’t happen, the kernel will get bored of waiting and call our reset function. Alternately, abort can return SCSI_ABORT_ PENDING to indicate that the command is in the process of being aborted, but hasn’t yet — for example, in case an interrupt must first confirm the abort. The other two options are to return SCSI_ ABORT_SUCCESS , if we aborted the command successfully, and SCSI_ABORT_ BUSY if the driver is busy (or if there is some other reason we would like to abort, but cannot do so right now).
After trying to abort and reissue failing commands, the SCSI layer will try to reset things. It first tries to reset the device, then the SCSI bus, and finally the SCSI controller itself.
How you handle a reset depends on the ability of the controller hardware. A simple example of this function can be seen in Figure 4.
Figure 4: The myscsi_reset Function
int myscsi_reset(Scsi_Cmnd *SCpnt, unsigned int flags)
For our example, we assume that the controller is fairly dumb and simply reset the SCSI controller itself, using the myhardware_reset function (which you should write). Returning SCSI_ RESET_PENDING indicates that the card has been reset but that commands will be returned with a failure status later. If the controller reset caused commands to be returned immediately, we could return SCSI_RESET_SUCCESS and allow commands to be reissued. If we do not think this type of reset is appropriate, we can return SCSI_ RESET_PUNT .
The flags argument is a set of four flags designed to provide hints as to what to reset and how. The important flags are SCSI_RESET_SUGGEST_ BUS_RESET , used when the SCSI layer thinks the entire bus should be reset, and SCSI_RESET_ SUGGEST_HOST_RESET , which is the last resort hint to the driver that it might be appropriate to completely restart the board itself.
The next step is to describe the controller interrupt handler [ see Figure 5 ]. The details of an interrupt handler can vary substantially between cards. For our example driver, I’m going to assume that the controller will interrupt us on two conditions: when it is ready for data to be read and/or written as the result of a command, and when a command completes.
When we requested the interrupt, we used the host pointer as dev_id — a device-specific field that is passed to the handler by the kernel. This makes it very easy for us to find which card we are handling in a driver that is supporting multiple interface cards. We then dig out our I/O port as we will probably need to utilize this often in the interrupt handler.
First the handler checks if the bus has been reset (either by the driver or by other devices). If so, we report that the command was reset. This will also inform the SCSI layer that the reset we reported as pending in our reset function has now completed.
Next we check for errors. A real driver should check for as many errors as can be identified. Here, we show handling of parity errors and “general” errors (those without any particular meaning). The SCSI layer will handle recovery from an error situation.
Then we look to see if this interrupt is due to a SCSI phase change. SCSI commands pass through a set of phases during their lifetime. A smart controller handles all of these phases automatically, whereas a dumb controller usually needs some help from the driver. In our case, we will assume that the only phase changes that will need driver assistance are ‘data in’ and ‘data out,’ in which we will copy bytes to or from the SCSI device as the result of a previously-issued command.
To send data to the board, we copy the buffer found in current_command->request_buffer to the board, using the outsw I/O routine. A real SCSI controller may use DMA for this operation instead.
To read data from the board, we first check how many bytes were received and copy them to the command request buffer, using the insw I/O routine.
If a SCSI command has completed, we read its status from the board and set it in the current_command structure, calling the scsi_done routine of the command to inform the SCSI layer that it’s done.
SCSI commands can also be issued synchronously, through the command function, although this is rarely used. Supporting synchronous commands is best done in terms of the queuecommand function, shown by the code in Figure 6.
Figure 6: The myscsi_command Function
static void it_finished(Scsi_Cmnd *SCpnt)
int myscsi_command(Scsi_Cmnd *SCpnt)
/* Wait for the command to complete */
We queue a command and tell the queue function that the completion handler ( scsi_done ) is it_finished , which simply increments the command status value. Having issued the command, we spin in a loop until the command finishes. The barrier() statement is important here. gcc might otherwise optimize:
barrier tells the compiler that it cannot cache values from variables across the barrier() function call. This ensures that the status, which is changed by an interrupt, will be seen by the looping code.
This completes the SCSI command handlers for our simple card. They are not optimized, and our card is a little simplistic. We still need to write the bios_param and info functions. The info function in Figure 7 returns a text description for our controller. It could (in fact it should) return the I/O and IRQ information, driver version, and other valuable information too.
The bios_param function maps our SCSI disk to a PC BIOS faked geometry. Real disks don’t have the simple geometry assumed by PCs, but everyone has carried on faking it rather than fixing all the operating systems. Thus we have to continue this fiction. We need to use the same algorithm as the controller’s BIOS or life will be messy.
The example in Figure 8 is taken from the Symbios 53c416 driver and is quite typical.Given the disk size, we fill in an array of integers for the heads, sectors, and cylinders of our disk. We want to be sure that these are right. Getting the mapping wrong will give people who use mixed Linux/DOS disks corrupted file systems and generate unhappy e-mail to the developers.
Figure 8: The sym53c416_bios_param Function
int sym53c416_bios_param(Disk *disk, kdev_t dev, int *ip)
size = disk->capacity;
ip = 64; /* heads */
ip = 32; /* sectors */
if((ip = size >> 11) > 1024)
/* cylinders, test for
big disk */
ip = 255; /* heads */
ip = 63; /* sectors */
ip = size / (255 * 63); /* cylinders */
To unload the kernel module, we need to clean up our resources. We provide a release function for this [ see Figure 9 ].Of course a real driver should have also allocated and freed the I/O ports it used.
Figure 9: The myscsi_release Function
int myscsi_release(struct Scsi_Host *SChost)
To make our driver a SCSI module, we have to include some magic at the end of the file ( myscsi.c , in this case):
This generates the init_module and cleanup_module code needed for a SCSI device, rather than having the author replicate it each time a new driver is written. The MYSCSI object is a macro that we need to define in a header file, myscsi.h . It is defined in a separate file, as it is used for both kernel modules and drivers compiled into the kernel.
Our myscsi.h file looks like the example in Figure 10. Here, the field: value format is a gcc extension which initializes a given field in a structure.
There are several fields of note here. The name field specifies a textual name for this controller. The detect through bios_param fields specify the names of the functions in our driver. To tell the kernel we can queue commands asynchronously, can_queue is set to 1. The SCSI host ID is specified by this_id . The sg_tablesize field is set to SG_NONE since we don’t support scatter-gather (a useful extension for performance).
The cmd_per_lun field is set to 1 as we can have at most one command outstanding per LUN (logical unit). If you set this to zero the kernel will do the hard work of ensuring all the disk buffers are copied into ISA bus-accessible memory when needed. This only matters to ISA bus controllers which do DMA.
The use_clustering field enables clustering, which tells the SCSI layer that it is worth trying to merge multiple disk read or write requests into a single SCSI command. A very intelligent controller may not bother to set this.
Finally, we define our directory name for /proc/scsi . We need to add the code from Figure 11 to myscsi.h in order to do this.
Figure 11: The myscsi_proc Structure
struct proc_dir myscsi_proc =
6, /* Length of name */
S _ IFDIR|S _ IRUGO|S _ IXUGO,
This structure will be used to install our directory in /proc/scsi . You need to add an entry for PROC_SCSI_ MYSCSI to include/linux/proc_fs.h in order to obtain a unique inode number for this directory in /proc/ scsi . Add it to the scsi_directory_inos enumeration, before the debugging driver entry.
/* ... */
PROC_SCSI_MYSCSI, /* here */
Hopefully this article has provided enough grounding that those interested in writing SCSI drivers can now read through existing driver code (especially simple ones, like the Symbios 53c416 driver) and see how to implement a new one.
Fatal error: Call to undefined function aa_author_bios() in /opt/apache/dms/b2b/linux-mag.com/site/www/htdocs/wp-content/themes/linuxmag/single.php on line 62