TAPE device driver

Copyright ©1997 Peter Urbanec. All rights reserved


Author

   EMail: peteru@null.net

   Phone: +61 2 9398 3456

   Snail: Peter Urbanec
          PO Box 752
          Randwick NSW 2031
          AUSTRALIA

The name

TAPE is a device driver for SCSI-2 tape devices. It is not called tape or Tape, the proper name is in all capitals - TAPE.

An associated program called tape comes with the TAPE driver. It can be used to control the operation of the TAPE driver. See the documentation for tape for instructions on how to use it.

This file contains the documentation for the TAPE device driver. The documentation has two target audiences.

  1. The user needs to know how the driver handles tapes.
  2. The application programmer needs to understand the API to the driver.


Version

This document describes version 1.01 of the TAPE device driver.


Installation

Copy the file named TAPE to /boot/system/drivers


Overview

The main goal is a minimal, yet sufficient driver for all your tape backup purposes. The driver implements a non-rewinding model, that is, there are no implicit rewind operations performed at any stage. The tape keeps on moving forward, unless you tell it otherwise. There is no planned compatibility with UNIX st, or any other platform or implementation for that matter. This is a BeOS native driver written from scratch.

The device driver automatically configures any SCSI-2 tape devices it can find. It will scan all SCSI buses and look for all supported targets and logical units. The driver supports WIDE SCSI-2 (upto 15 targets per bus.) The theoretical maximum number of tape drives that this driver will support is 1050. 10 SCSI-buses, 15 targets per bus, 7 logical units per target.

The devices are named /dev/scsi/tape/XYZ, where X is the bus number and can range from 0 to 9. Y is the target number, also known as the SCSI id of the device. It can range from 0 to 7 for standard 8-bit SCSI configurations and from 0 to F (ie. all valid hex digits) for exclusively WIDE SCSI configurations. Z is the logical unit number, also known as LUN. Some tape libraries use the LUN to select which tape to use. Normal desktop style tape drives usually only respond to LUN 0.

A lot of care and time was spent studying the SCSI-2 specification and making sure that the driver only utilises features that must be provided by all SCSI-2 compliant units. The driver implements very few operations which use optional SCSI-2 commands, all of these are non-essential to correct operation of basic data transfer.

The driver has code to completely decode all SCSI-2 specified sense information and present it in a very usable manner to the application. Error handling is a piece of cake for the clients.

I am very reluctant to add support for vendor specific extensions to this driver. As much as this would allow users to take advantage of any extras that a particular vendor might provide, it would hurt the uniform API to ALL SCSI-2 tape devices and in the long run make life harder for both, the application writers and end users.

If you would like to have some special features supported for a custom solution, I will be more than happy to negotiate a deal to provide a custom driver. Please contact me as indicated above if you wish to negotiate a deal.

Although the driver has been in daily use and tested extensively for a few weeks before release, the author does not provide any guarantees or warranties as far as the performance of the software is concerned and shall not be held liable for any losses associated with the use of this software. If you do find bugs or compatibility problems, please report them so that they can be fixed.

When reporting bugs or incompatibilities, please include the following information:


Future plans

Examination of the SCSI-3 draft documents promises to enhance, but also complicate the interface to sequential devices even further than SCSI-2. My plan is to support SCSI-3 when it is ratified and products begin to appear on the market. One of the great things about SCSI-3 is the fact that the delivery mechanism can be a lot more diverse than the current SCSI-2 specification allows. SCSI-3 will run over your FireWire, SSA or even fibre channel connections. All this will ofcourse require CAM-3 support in BeOS ...


ioctl() style

I believe that the compiler is (most of the time) your friend. It is good at catching silly bugs. If you let it. One of the best ways of letting the compiler help you is to make a good use of types.

The ioctl() philosophy seems to be exactly the opposite. All it gives you is a pointer to a void. Good luck sorting it all out. Well, things are not that bad. It only takes a couple of structures and you can come up with a workable interface that the compiler will be able to check for type correctness.

Now, keep in mind that since this is a kernel level driver, it is written in C, not C++. The good thing is, that you can still keep the compiler in C++ mode to get the convenience of doing such things as being able to declare variables anywhere you like.

Now back to ioctl() calls and the philosophy behind them. The idea is, that every command that needs to take arguments or return some data has a command structure associated with it. To make it easier to work with, the naming is kept consistent between the commands and their command structures. For example, if a command to get blerks from the drive exists, the command would be called TAPE_CMD_GET_BLERKS and the corresponding structure would be called TAPE_GET_BLERKS. The structure would of course contain all the parameters required to communicate a blerk. See TAPE.h for concrete examples of real commands.

Now, when you actually want to go and ask the tape drive for some blerks, you create a TAPE_GET_BLERKS structure and fill in its contents as appropriate. You then call the ioctl() like this:

  TAPE_GET_BLERKS *blerker = (TAPE_GET_BLERKS *) malloc(sizeof(TAPE_GET_BLERKS));
  // Fill in blerker with details
  status_t result = ioctl(tape_file, TAPE_CMD_GET_BLERKS, blerker);

Some commands do not require any arguments and do not return any data (except for success or failure), such commands do not have a structure defined for them. For example, the command TAPE_CMD_EXPLODE does not need any arguments.

  status_t result = ioctl(tape_file, TAPE_CMD_EXPLODE, NULL);
If there wasn't enough semtex in the drive to complete the operation, the ioctl() would return a failure.


The details

OK, so here are the commands that are available, together with a bit of a description for each one of them.


TAPE_CMD_REWIND

Arguments: none

Returns when the medium has been positioned at the beginning of recordable area. If there was data written to the tape, a filemark will be written at the current position before the tape is rewound.


TAPE_CMD_EJECT

Arguments: none

Attempts to physically eject medium from the drive if the device is capable of this. An eject often (not always) implies a rewind of the medium. If there was data written to the tape, a filemark will be written at the current position before the tape is ejected.


TAPE_CMD_FILE_SEEK

Arguments:
file_count - the number of filemarks

Will cause the medium to be re-positioned file_count filemarks from the current position. Negative numbers will move backwards, positive numbers will move forwards. The resulting position will be the begining of the first block after the filemark. If there was data written to the tape, a filemark will be written at the current position before the tape is repositioned.


TAPE_CMD_GET_BLOCK_SIZES

Arguments:
smallest - the smallest supported block size
largest - the largest supported block size

Reports the range of block sizes supported by the tape drive. Tape drives which only support fixed size blocks will return the same number in both variables. The maximum block size achievable is 16 MB, which is certainly not a recommended default value. Use your discretion when choosing block size for your application, but think hard. Here are some points to consider:

Given those comments above, my suggestion would be to pick an upper limit on block size in your application (64 k seems like a good number) and then use which ever is smaller out of the limit or the largest value. Once you pick your block size, stick to it. It is possible to write every block with different size, but it might be a bit difficult to read your data back.


TAPE_CMD_GET_LAST_ERROR

Arguments:
severity - how serious was the error
residue - the difference between the request and actual operation
key - SCSI-2 sense key
asc - SCSI-2 ASC
ascq - SCSI-2 ASCQ
message - the text of the error message (NULL terminated)

This ioctl() call will always return B_NO_ERROR and fill in the arguments with the last encountered error values. If there were no previous errors, the return values are undefined. Note that this information is not preserved across close() and open() calls, it is therefore impossible to use one application to find out which tape related error occured in another application.

It is suggested that this command be used as soon as an error occurs, however, the values returned will be those of the last error condition, not just the last call. In particular, do not use this command to check for errors. Use the return values from read(), write() and ioctl() to discover the existence of an error condition, then use this command to find out the severity and cause.

All arguments except for severity and message are bitwise copies of the SCSI-2 defined fields. See the SCSI-2 standard for a detailed break down of all tape related error conditions.

severity is an attempt at simplifying the application programmers life. The errors are classified into different severities depending on their impact. Here are some guidelines as to how to treat different severities:

message is a text string that the client might want to record in a log or present to the user as part of an error message. It is a NULL terminated ASCII string, as specified in the SCSI-2 standard. The string is copied into the message buffer.


TAPE_CMD_WRITE_FILEMARK

Arguments: none

Writes a filemark to tape. Filemarks are used to divide the tape into multiple files. Typically filemarks are used to implement different save sets and backups and provide fast positioning on the tape.

Be carefull when using filemarks, they should be only written in sequence with data. Some devices might erase a large section of physical media after writing a filemark, that is why you should never attempt to write any filemarks into the middle of existing data if you wish to preserve this data.

Some devices use two filemarks in a row to mark the end of tape. It is in your best interest to not write two filemarks without at least some data in between them if you wish to remain compatible with all devices.


TAPE_CMD_GET_WRITE_PROTECT

Arguments:
protection - true if tape is write protected

Reports the current status of the write protect flag for the media. It is a good idea to check this flag before attempting to write to the media.


TAPE_CMD_GET_WRITE_BUFFERING
TAPE_CMD_SET_WRITE_BUFFERING

Arguments:
buffering - true if drive is buffering writes

Do not change the default setting carelessly. Turning off buffering might prevent a device from streaming data, resulting in horrible performance, waste of tape, excessive repositioning and unnecessary wear on the mechanism. On the other hand, some devices prefer not to buffer writes as they might not be able to write out the entire buffer if they encounter an early end of tape warning. This is a rare case, however if you do encounter it, the residue field should tell you how much data remains unwritten.


TAPE_CMD_GET_DATA_COMPRESSION
TAPE_CMD_SET_DATA_COMPRESSION

Arguments:
compression - true if compression is enabled

Since most drives default to the best speed / capacity configuration, you should not need to change the compression setting. In rare circumstances, you might want to disable compression to achieve backward compatibility with older drives. Note that not all drives support compression. Some devices which do support compression may not honour this flag. A notable example of this is the SONY SDT-5000 drive.


TAPE_CMD_GET_VERSION_STRING

Arguments:
version - NULL terminated version string

This command provides a way for an application to report to the user the version of the TAPE driver currently in use. The application may also use this to provide compatibility between different versions of this driver, should the need arise in the future.

The format of the string is fairly simple and not too difficult to parse. There are 5 fields with separators between them:

"<NAME> <VERSION>.<REVISION> (<DATE>) <COPYRIGHT>"

The <NAME> field is "TAPE_Driver" followed by a space.

<VERSION> is a decimal integer numerical quantity, suitable for conversion to an int. For example ver=atoi(VERSION)

<REVISION> is a decimal integer quantity, just like <VERSION>. Note that <REVISION> may or may not have leading zeros, but it is always treated as an integer, not a fractional part of the <VERSION> field. Revisions 0001, 01 and 1 are exactly the same, they are both earlier revisions than 10. To re-iterate this; the revisions would chronologicaly progress in this sequence: ... 7, 8, 9, 10, 11, 12, ... The <REVISION> field is terminated by a space

<DATE> is the string generated by the __DATE__ directive given to the compiler. It is in whatever format the Metrowerks CodeWarrior compiler will generate. If / when BeOS supports localisation and if the Metrowerks __DATE__ implementation will respect it, the format will be as defined by the Australia/NSW timezone. I would advise to treat this field as informative only and not to try to compare different versions of the driver based on this field. Because this field could become unpredictable depending on localisation support, it is enclosed in brackets to allow for easy parsing. The entire <DATE> field is terminated by a space.

<COPYRIGHT> is the rest of the string, up to the terminating NULL. It contains author and copyright information for the driver.