A Linux CD-ROM standard

Author:

David van Leeuwen <david@ElseWare.cistron.nl>

Date:

12 March 1999

Updated by:

Erik Andersen (andersee@debian.org)

Updated by:

Jens Axboe (axboe@image.dk)

Introduction

Linux is probably the Unix-like operating system that supportsthe widest variety of hardware devices. The reasons for this arepresumably

  • The large list of hardware devices available for the many platformsthat Linux now supports (i.e., i386-PCs, Sparc Suns, etc.)

  • The open design of the operating system, such that anybody can write adriver for Linux.

  • There is plenty of source code around as examples of how to write a driver.

The openness of Linux, and the many different types of availablehardware has allowed Linux to support many different hardware devices.Unfortunately, the very openness that has allowed Linux to supportall these different devices has also allowed the behavior of eachdevice driver to differ significantly from one device to another.This divergence of behavior has been very significant for CD-ROMdevices; the way a particular drive reacts to astandardioctl()call varies greatly from one device driver to another. To avoid makingtheir drivers totally inconsistent, the writers of Linux CD-ROMdrivers generally created new device drivers by understanding, copying,and then changing an existing one. Unfortunately, this practice did notmaintain uniform behavior across all the Linux CD-ROM drivers.

This document describes an effort to establish Uniform behavior acrossall the different CD-ROM device drivers for Linux. This document alsodefines the variousioctl()’s, and how the low-level CD-ROM devicedrivers should implement them. Currently (as of the Linux 2.1.xdevelopment kernels) several low-level CD-ROM device drivers, includingboth IDE/ATAPI and SCSI, now use this Uniform interface.

When the CD-ROM was developed, the interface between the CD-ROM driveand the computer was not specified in the standards. As a result, manydifferent CD-ROM interfaces were developed. Some of them had theirown proprietary design (Sony, Mitsumi, Panasonic, Philips), othermanufacturers adopted an existing electrical interface and changedthe functionality (CreativeLabs/SoundBlaster, Teac, Funai) or simplyadapted their drives to one or more of the already existing electricalinterfaces (Aztech, Sanyo, Funai, Vertos, Longshine, Optics Storage andmost of theNoName manufacturers). In cases where a new drive reallybrought its own interface or used its own command set and flow controlscheme, either a separate driver had to be written, or an existingdriver had to be enhanced. History has delivered us CD-ROM support formany of these different interfaces. Nowadays, almost all new CD-ROMdrives are either IDE/ATAPI or SCSI, and it is very unlikely that anymanufacturer will create a new interface. Even finding drives for theold proprietary interfaces is getting difficult.

When (in the 1.3.70’s) I looked at the existing software interface,which was expressed throughcdrom.h, it appeared to be a rather wildset of commands and data formats[1]. It seemed that manyfeatures of the software interface had been added to accommodate thecapabilities of a particular drive, in anad hoc manner. Moreimportantly, it appeared that the behavior of thestandard commandswas different for most of the different drivers: e. g., some driversclose the tray if anopen() call occurs when the tray is open, whileothers do not. Some drivers lock the door upon opening the device, toprevent an incoherent file system, but others don’t, to allow softwareejection. Undoubtedly, the capabilities of the different drives vary,but even when two drives have the same capability their drivers’behavior was usually different.

[1]

I cannot recollect what kernel version I looked at, then,presumably 1.2.13 and 1.3.34 --- the latest kernel that I wasindirectly involved in.

I decided to start a discussion on how to make all the Linux CD-ROMdrivers behave more uniformly. I began by contacting the developers ofthe many CD-ROM drivers found in the Linux kernel. Their reactionsencouraged me to write the Uniform CD-ROM Driver which this document isintended to describe. The implementation of the Uniform CD-ROM Driver isin the filecdrom.c. This driver is intended to be an additional softwarelayer that sits on top of the low-level device drivers for each CD-ROM drive.By adding this additional layer, it is possible to have all the differentCD-ROM devices behaveexactly the same (insofar as the underlyinghardware will allow).

The goal of the Uniform CD-ROM Driver isnot to alienate driver developerswhohave not yet taken steps to support this effort. The goal of Uniform CD-ROMDriver is simply to give people writing application programs for CD-ROM drivesone Linux CD-ROM interface with consistent behavior for allCD-ROM devices. In addition, this also provides a consistent interfacebetween the low-level device driver code and the Linux kernel. Careis taken that 100% compatibility exists with the data structures andprogrammer’s interface defined incdrom.h. This guide was written tohelp CD-ROM driver developers adapt their code to use the Uniform CD-ROMDriver code defined incdrom.c.

Personally, I think that the most important hardware interfaces arethe IDE/ATAPI drives and, of course, the SCSI drives, but as pricesof hardware drop continuously, it is also likely that people may havemore than one CD-ROM drive, possibly of mixed types. It is importantthat these drives behave in the same way. In December 1994, one of thecheapest CD-ROM drives was a Philips cm206, a double-speed proprietarydrive. In the months that I was busy writing a Linux driver for it,proprietary drives became obsolete and IDE/ATAPI drives became thestandard. At the time of the last update to this document (November1997) it is becoming difficult to evenfind anything less than a16 speed CD-ROM drive, and 24 speed drives are common.

Standardizing through another software level

At the time this document was conceived, all drivers directlyimplemented the CD-ROMioctl() calls through their own routines. Thisled to the danger of different drivers forgetting to do important thingslike checking that the user was giving the driver valid data. Moreimportantly, this led to the divergence of behavior, which has alreadybeen discussed.

For this reason, the Uniform CD-ROM Driver was created to enforce consistentCD-ROM drive behavior, and to provide a common set of services to the variouslow-level CD-ROM device drivers. The Uniform CD-ROM Driver now provides anothersoftware-level, that separates theioctl() andopen() implementationfrom the actual hardware implementation. Note that this effort hasmade few changes which will affect a user’s application programs. Thegreatest change involved moving the contents of the various low-levelCD-ROM drivers' header files to the kernel’s cdrom directory. This wasdone to help ensure that the user is only presented with only one cdrominterface, the interface defined incdrom.h.

CD-ROM drives are specific enough (i. e., different from otherblock-devices such as floppy or hard disc drives), to define a setof commonCD-ROM device operations,<cdrom-device>_dops.These operations are different from the classical block-device fileoperations,<block-device>_fops.

The routines for the Uniform CD-ROM Driver interface level are implementedin the filecdrom.c. In this file, the Uniform CD-ROM Driver interfaceswith the kernel as a block device by registering the following generalstructfile_operations:

struct file_operations cdrom_fops = {        NULL,                   /* lseek */        block _read ,           /* read--general block-dev read */        block _write,           /* write--general block-dev write */        NULL,                   /* readdir */        NULL,                   /* select */        cdrom_ioctl,            /* ioctl */        NULL,                   /* mmap */        cdrom_open,             /* open */        cdrom_release,          /* release */        NULL,                   /* fsync */        NULL,                   /* fasync */        NULL                    /* revalidate */};

Every active CD-ROM device shares thisstruct. The routinesdeclared above are all implemented incdrom.c, since this file is theplace where the behavior of all CD-ROM-devices is defined andstandardized. The actual interface to the various types of CD-ROMhardware is still performed by various low-level CD-ROM-devicedrivers. These routines simply implement certaincapabilitiesthat are common to all CD-ROM (and really, all removable-mediadevices).

Registration of a low-level CD-ROM device driver is now done throughthe general routines incdrom.c, not through the Virtual File System(VFS) any more. The interface implemented incdrom.c is carried outthrough two general structures that contain information about thecapabilities of the driver, and the specific drives on which thedriver operates. The structures are:

cdrom_device_ops

This structure contains information about the low-level driver for aCD-ROM device. This structure is conceptually connected to the majornumber of the device (although some drivers may have differentmajor numbers, as is the case for the IDE driver).

cdrom_device_info

This structure contains information about a particular CD-ROM drive,such as its device name, speed, etc. This structure is conceptuallyconnected to the minor number of the device.

Registering a particular CD-ROM drive with the Uniform CD-ROM Driveris done by the low-level device driver though a call to:

register_cdrom(struct cdrom_device_info * <device>_info)

The device information structure,<device>_info, contains all theinformation needed for the kernel to interface with the low-levelCD-ROM device driver. One of the most important entries in thisstructure is a pointer to thecdrom_device_ops structure of thelow-level driver.

The device operations structure,cdrom_device_ops, contains a listof pointers to the functions which are implemented in the low-leveldevice driver. Whencdrom.c accesses a CD-ROM device, it does itthrough the functions in this structure. It is impossible to know allthe capabilities of future CD-ROM drives, so it is expected that thislist may need to be expanded from time to time as new technologies aredeveloped. For example, CD-R and CD-R/W drives are beginning to becomepopular, and support will soon need to be added for them. For now, thecurrentstruct is:

struct cdrom_device_ops {        int (*open)(struct cdrom_device_info *, int)        void (*release)(struct cdrom_device_info *);        int (*drive_status)(struct cdrom_device_info *, int);        unsigned int (*check_events)(struct cdrom_device_info *,                                     unsigned int, int);        int (*media_changed)(struct cdrom_device_info *, int);        int (*tray_move)(struct cdrom_device_info *, int);        int (*lock_door)(struct cdrom_device_info *, int);        int (*select_speed)(struct cdrom_device_info *, unsigned long);        int (*get_last_session) (struct cdrom_device_info *,                                 struct cdrom_multisession *);        int (*get_mcn)(struct cdrom_device_info *, struct cdrom_mcn *);        int (*reset)(struct cdrom_device_info *);        int (*audio_ioctl)(struct cdrom_device_info *,                           unsigned int, void *);        const int capability;           /* capability flags */        int (*generic_packet)(struct cdrom_device_info *,                              struct packet_command *);};

When a low-level device driver implements one of these capabilities,it should add a function pointer to thisstruct. When a particularfunction is not implemented, however, thisstruct should contain aNULL instead. Thecapability flags specify the capabilities of theCD-ROM hardware and/or low-level CD-ROM driver when a CD-ROM driveis registered with the Uniform CD-ROM Driver.

Note that most functions have fewer parameters than theirblkdev_fops counterparts. This is because very little of theinformation in the structuresinode andfile is used. For mostdrivers, the main parameter is thestructcdrom_device_info, fromwhich the major and minor number can be extracted. (Most low-levelCD-ROM drivers don’t even look at the major and minor number though,since many of them only support one device.) This will be availablethroughdev incdrom_device_info described below.

The drive-specific, minor-like information that is registered withcdrom.c, currently contains the following fields:

struct cdrom_device_info {      const struct cdrom_device_ops * ops;    /* device operations for this major */      struct list_head list;                  /* linked list of all device_info */      struct gendisk * disk;                  /* matching block layer disk */      void *  handle;                         /* driver-dependent data */      int mask;                               /* mask of capability: disables them */      int speed;                              /* maximum speed for reading data */      int capacity;                           /* number of discs in a jukebox */      unsigned int options:30;                /* options flags */      unsigned mc_flags:2;                    /*  media-change buffer flags */      unsigned int vfs_events;                /*  cached events for vfs path */      unsigned int ioctl_events;              /*  cached events for ioctl path */      int use_count;                          /*  number of times device is opened */      char name[20];                          /*  name of the device type */      __u8 sanyo_slot : 2;                    /*  Sanyo 3-CD changer support */      __u8 keeplocked : 1;                    /*  CDROM_LOCKDOOR status */      __u8 reserved : 5;                      /*  not used yet */      int cdda_method;                        /*  see CDDA_* flags */      __u8 last_sense;                        /*  saves last sense key */      __u8 media_written;                     /*  dirty flag, DVD+RW bookkeeping */      unsigned short mmc3_profile;            /*  current MMC3 profile */      int for_data;                           /*  unknown:TBD */      int mrw_mode_page;                      /*  which MRW mode page is in use */};

Using thisstruct, a linked list of the registered minor devices isbuilt, using thenext field. The device number, the device operationsstructand specifications of properties of the drive are stored in thisstructure.

Themask flags can be used to mask out some of the capabilities listedinops->capability, if a specific drive doesn’t support a featureof the driver. The valuespeed specifies the maximum head-rate of thedrive, measured in units of normal audio speed (176kB/sec raw data or150kB/sec file system data). The parameters are declaredconstbecause they describe properties of the drive, which don’t change afterregistration.

A few registers contain variables local to the CD-ROM drive. Theflagsoptions are used to specify how the general CD-ROM routinesshould behave. These various flags registers should provide enoughflexibility to adapt to the different users’ wishes (andnot thearbitrary wishes of the author of the low-level device driver, as isthe case in the old scheme). The registermc_flags is used to bufferthe information frommedia_changed() to two separate queues. Otherdata that is specific to a minor drive, can be accessed throughhandle,which can point to a data structure specific to the low-level driver.The fieldsuse_count,next,options andmc_flags need not beinitialized.

The intermediate software layer thatcdrom.c forms will perform someadditional bookkeeping. The use count of the device (the number ofprocesses that have the device opened) is registered inuse_count. Thefunctioncdrom_ioctl() will verify the appropriate user-memory regionsfor read and write, and in case a location on the CD is transferred,it willsanitize the format by making requests to the low-leveldrivers in a standard format, and translating all formats between theuser-software and low level drivers. This relieves much of the drivers’memory checking and format checking and translation. Also, the necessarystructures will be declared on the program stack.

The implementation of the functions should be as defined in thefollowing sections. Two functionsmust be implemented, namelyopen() andrelease(). Other functions may be omitted, theircorresponding capability flags will be cleared upon registration.Generally, a function returns zero on success and negative on error. Afunction call should return only after the command has completed, but ofcourse waiting for the device should not use processor time.

int open(struct cdrom_device_info *cdi, int purpose)

Open() should try to open the device for a specificpurpose, whichcan be either:

  • Open for reading data, as done bymount() (2), or theuser commandsdd orcat.

  • Open forioctl commands, as done by audio-CD playing programs.

Notice that any strategic code (closing tray uponopen(), etc.) isdone by the calling routine incdrom.c, so the low-level routineshould only be concerned with proper initialization, such as spinningup the disc, etc.

void release(struct cdrom_device_info *cdi)

Device-specific actions should be taken such as spinning down the device.However, strategic actions such as ejection of the tray, or unlockingthe door, should be left over to the general routinecdrom_release().This is the only function returning typevoid.

int drive_status(struct cdrom_device_info *cdi, int slot_nr)

The functiondrive_status, if implemented, should provideinformation on the status of the drive (not the status of the disc,which may or may not be in the drive). If the drive is not a changer,slot_nr should be ignored. Incdrom.h the possibilities are listed:

CDS_NO_INFO             /* no information available */CDS_NO_DISC             /* no disc is inserted, tray is closed */CDS_TRAY_OPEN           /* tray is opened */CDS_DRIVE_NOT_READY     /* something is wrong, tray is moving? */CDS_DISC_OK             /* a disc is loaded and everything is fine */
int tray_move(struct cdrom_device_info *cdi, int position)

This function, if implemented, should control the tray movement. (Noother function should control this.) The parameterposition controlsthe desired direction of movement:

  • 0 Close tray

  • 1 Open tray

This function returns 0 upon success, and a non-zero value uponerror. Note that if the tray is already in the desired position, noaction need be taken, and the return value should be 0.

int lock_door(struct cdrom_device_info *cdi, int lock)

This function (and no other code) controls locking of the door, if thedrive allows this. The value oflock controls the desired lockingstate:

  • 0 Unlock door, manual opening is allowed

  • 1 Lock door, tray cannot be ejected manually

This function returns 0 upon success, and a non-zero value uponerror. Note that if the door is already in the requested state, noaction need be taken, and the return value should be 0.

int select_speed(struct cdrom_device_info *cdi, unsigned long speed)

Some CD-ROM drives are capable of changing their head-speed. Thereare several reasons for changing the speed of a CD-ROM drive. Badlypressed CD-ROM s may benefit from less-than-maximum head rate. ModernCD-ROM drives can obtain very high head rates (up to24x iscommon). It has been reported that these drives can make readingerrors at these high speeds, reducing the speed can prevent data lossin these circumstances. Finally, some of these drives canmake an annoyingly loud noise, which a lower speed may reduce.

This function specifies the speed at which data is read or audio isplayed back. The value ofspeed specifies the head-speed of thedrive, measured in units of standard cdrom speed (176kB/sec raw dataor 150kB/sec file system data). So to request that a CD-ROM driveoperate at 300kB/sec you would call the CDROM_SELECT_SPEEDioctlwithspeed=2. The special value0 meansauto-selection, i. e.,maximum data-rate or real-time audio rate. If the drive doesn’t havethisauto-selection capability, the decision should be made on thecurrent disc loaded and the return value should be positive. A negativereturn value indicates an error.

int get_last_session(struct cdrom_device_info *cdi,                     struct cdrom_multisession *ms_info)

This function should implement the old correspondingioctl(). Fordevicecdi->dev, the start of the last session of the current discshould be returned in the pointer argumentms_info. Note thatroutines incdrom.c have sanitized this argument: its requestedformat willalways be of the typeCDROM_LBA (linear blockaddressing mode), whatever the calling software requested. Butsanitization goes even further: the low-level implementation mayreturn the requested information inCDROM_MSF format if it wishes so(setting thems_info->addr_format field appropriately, ofcourse) and the routines incdrom.c will make the transformation ifnecessary. The return value is 0 upon success.

int get_mcn(struct cdrom_device_info *cdi,            struct cdrom_mcn *mcn)

Some discs carry aMedia Catalog Number (MCN), also calledUniversal Product Code (UPC). This number should reflect the numberthat is generally found in the bar-code on the product. Unfortunately,the few discs that carry such a number on the disc don’t even use thesame format. The return argument to this function is a pointer to apre-declared memory region of typestructcdrom_mcn. The MCN isexpected as a 13-character string, terminated by a null-character.

int reset(struct cdrom_device_info *cdi)

This call should perform a hard-reset on the drive (although incircumstances that a hard-reset is necessary, a drive may very well notlisten to commands anymore). Preferably, control is returned to thecaller only after the drive has finished resetting. If the drive is nolonger listening, it may be wise for the underlying low-level cdromdriver to time out.

int audio_ioctl(struct cdrom_device_info *cdi,                unsigned int cmd, void *arg)

Some of the CD-ROM-ioctl()‘s defined incdrom.h can beimplemented by the routines described above, and hence the functioncdrom_ioctl will use those. However, mostioctl()‘s deal withaudio-control. We have decided to leave these to be accessed through asingle function, repeating the argumentscmd andarg. Note thatthe latter is of typevoid, rather thanunsigned long int.The routinecdrom_ioctl() does do some useful things,though. It sanitizes the address format type toCDROM_MSF (Minutes,Seconds, Frames) for all audio calls. It also verifies the memorylocation ofarg, and reserves stack-memory for the argument. Thismakes implementation of theaudio_ioctl() much simpler than in theold driver scheme. For example, you may look up the functioncm206_audio_ioctl()cm206.c that should be updated withthis documentation.

An unimplemented ioctl should return-ENOSYS, but a harmless request(e. g.,CDROMSTART) may be ignored by returning 0 (success). Othererrors should be according to the standards, whatever they are. Whenan error is returned by the low-level driver, the Uniform CD-ROM Drivertries whenever possible to return the error code to the calling program.(We may decide to sanitize the return value incdrom_ioctl() though, inorder to guarantee a uniform interface to the audio-player software.)

int dev_ioctl(struct cdrom_device_info *cdi,              unsigned int cmd, unsigned long arg)

Someioctl()’s seem to be specific to certain CD-ROM drives. That is,they are introduced to service some capabilities of certain drives. Infact, there are 6 differentioctl()’s for reading data, either in someparticular kind of format, or audio data. Not many drives supportreading audio tracks as data, I believe this is because of protectionof copyrights of artists. Moreover, I think that if audio-tracks aresupported, it should be done through the VFS and not viaioctl()’s. Aproblem here could be the fact that audio-frames are 2352 bytes long,so either the audio-file-system should ask for 75264 bytes at once(the least common multiple of 512 and 2352), or the drivers shouldbend their backs to cope with this incoherence (to which I would beopposed). Furthermore, it is very difficult for the hardware to findthe exact frame boundaries, since there are no synchronization headersin audio frames. Once these issues are resolved, this code should bestandardized incdrom.c.

Because there are so manyioctl()’s that seem to be introduced tosatisfy certain drivers[2], any non-standardioctl()sare routed through the calldev_ioctl(). In principle,privateioctl()‘s should be numbered after the device’s major number, and notthe general CD-ROMioctl number,0x53. Currently thenon-supportedioctl()’s are:

CDROMREADMODE1, CDROMREADMODE2, CDROMREADAUDIO, CDROMREADRAW,CDROMREADCOOKED, CDROMSEEK, CDROMPLAY-BLK and CDROM-READALL

[2]

Is there software around that actually uses these? I’d be interested!

CD-ROM capabilities

Instead of just implementing someioctl calls, the interface incdrom.c supplies the possibility to indicate thecapabilitiesof a CD-ROM drive. This can be done by ORing any number ofcapability-constants that are defined incdrom.h at the registrationphase. Currently, the capabilities are any of:

CDC_CLOSE_TRAY          /* can close tray by software control */CDC_OPEN_TRAY           /* can open tray */CDC_LOCK                /* can lock and unlock the door */CDC_SELECT_SPEED        /* can select speed, in units of * sim*150 ,kB/s */CDC_SELECT_DISC         /* drive is juke-box */CDC_MULTI_SESSION       /* can read sessions *> rm1* */CDC_MCN                 /* can read Media Catalog Number */CDC_MEDIA_CHANGED       /* can report if disc has changed */CDC_PLAY_AUDIO          /* can perform audio-functions (play, pause, etc) */CDC_RESET               /* hard reset device */CDC_IOCTLS              /* driver has non-standard ioctls */CDC_DRIVE_STATUS        /* driver implements drive status */

The capability flag is declaredconst, to prevent drivers fromaccidentally tampering with the contents. The capability flags actuallyinformcdrom.c of what the driver can do. If the drive foundby the driver does not have the capability, is can be masked out bythecdrom_device_info variablemask. For instance, the SCSI CD-ROMdriver has implemented the code for loading and ejecting CD-ROM’s, andhence its corresponding flags incapability will be set. But a SCSICD-ROM drive might be a caddy system, which can’t load the tray, andhence for this drive thecdrom_device_infostructwill have settheCDC_CLOSE_TRAY bit inmask.

In the filecdrom.c you will encounter many constructions of the type:

if (cdo->capability & ~cdi->mask & CDC _<capability>) ...

There is noioctl to set the mask... The reason is thatI think it is better to control thebehavior rather than thecapabilities.

Options

A final flag register controls thebehavior of the CD-ROMdrives, in order to satisfy different users’ wishes, hopefullyindependently of the ideas of the respective author who happened tohave made the drive’s support available to the Linux community. Thecurrent behavior options are:

CDO_AUTO_CLOSE  /* try to close tray upon device open() */CDO_AUTO_EJECT  /* try to open tray on last device close() */CDO_USE_FFLAGS  /* use file_pointer->f_flags to indicate purpose for open() */CDO_LOCK        /* try to lock door if device is opened */CDO_CHECK_TYPE  /* ensure disc type is data if opened for data */

The initial value of this register isCDO_AUTO_CLOSE | CDO_USE_FFLAGS | CDO_LOCK, reflecting my own view on userinterface and software standards. Before you protest, there are twonewioctl()’s implemented incdrom.c, that allow you to control thebehavior by software. These are:

CDROM_SET_OPTIONS       /* set options specified in (int)arg */CDROM_CLEAR_OPTIONS     /* clear options specified in (int)arg */

One option needs some more explanation:CDO_USE_FFLAGS. In the nextnewsection we explain what the need for this option is.

A software packagesetcd, available from the Debian distributionandsunsite.unc.edu, allows user level control of these flags.

The need to know the purpose of opening the CD-ROM device

Traditionally, Unix devices can be used in two differentmodes,either by reading/writing to the device file, or by issuingcontrolling commands to the device, by the device’sioctl()call. The problem with CD-ROM drives, is that they can be used fortwo entirely different purposes. One is to mount removablefile systems, CD-ROM’s, the other is to play audio CD’s. Audio commandsare implemented entirely throughioctl()'s, presumably because thefirst implementation (SUN?) has been such. In principle there isnothing wrong with this, but a good control of theCD player demandsthat the device canalways be opened in order to give theioctl commands, regardless of the state the drive is in.

On the other hand, when used as a removable-media disc drive (what theoriginal purpose of CD-ROM s is) we would like to make sure that thedisc drive is ready for operation upon opening the device. In the oldscheme, some CD-ROM drivers don’t do any integrity checking, resultingin a number of i/o errors reported by the VFS to the kernel when anattempt for mounting a CD-ROM on an empty drive occurs. This is not aparticularly elegant way to find out that there is no CD-ROM inserted;it more-or-less looks like the old IBM-PC trying to read an empty floppydrive for a couple of seconds, after which the system complains itcan’t read from it. Nowadays we cansense the existence of aremovable medium in a drive, and we believe we should exploit thatfact. An integrity check on opening of the device, that verifies theavailability of a CD-ROM and its correct type (data), would bedesirable.

These two ways of using a CD-ROM drive, principally for data andsecondarily for playing audio discs, have different demands for thebehavior of theopen() call. Audio use simply wants to open thedevice in order to get a file handle which is needed for issuingioctl commands, while data use wants to open for correct andreliable data transfer. The only way user programs can indicate whattheirpurpose of opening the device is, is through theflagsparameter (seeopen(2)). For CD-ROM devices, these flags aren’timplemented (some drivers implement checking for write-related flags,but this is not strictly necessary if the device file has correctpermission flags). Most option flags simply don’t make sense toCD-ROM devices:O_CREAT,O_NOCTTY,O_TRUNC,O_APPEND, andO_SYNC have no meaning to a CD-ROM.

We therefore propose to use the flagO_NONBLOCK to indicatethat the device is opened just for issuingioctlcommands. Strictly, the meaning ofO_NONBLOCK is that opening andsubsequent calls to the device don’t cause the calling process towait. We could interpret this as don’t wait until someone hasinserted some valid data-CD-ROM. Thus, our proposal of theimplementation for theopen() call for CD-ROM s is:

  • If no other flags are set thanO_RDONLY, the device is openedfor data transfer, and the return value will be 0 only upon successfulinitialization of the transfer. The call may even induce some actionson the CD-ROM, such as closing the tray.

  • If the option flagO_NONBLOCK is set, opening will always besuccessful, unless the whole device doesn’t exist. The drive will takeno actions whatsoever.

And what about standards?

You might hesitate to accept this proposal as it comes from theLinux community, and not from some standardizing institute. Whatabout SUN, SGI, HP and all those other Unix and hardware vendors?Well, these companies are in the lucky position that they generallycontrol both the hardware and software of their supported products,and are large enough to set their own standard. They do not have todeal with a dozen or more different, competing hardwareconfigurations[3].

[3]

Incidentally, I think that SUN’s approach to mounting CD-ROM s is verygood in origin: under Solaris a volume-daemon automatically mounts anewly inserted CD-ROM under/cdrom/*<volume-name>*.

In my opinion they should have pushed thisfurther and haveevery CD-ROM on the local area network bemounted at the similar location, i. e., no matter in which particularmachine you insert a CD-ROM, it will always appear at the sameposition in the directory tree, on every system. When I wanted toimplement such a user-program for Linux, I came across thedifferences in behavior of the various drivers, and the need for anioctl informing about media changes.

We believe that usingO_NONBLOCK to indicate that a device is being openedforioctl commands only can be easily introduced in the Linuxcommunity. All the CD-player authors will have to be informed, we caneven send in our own patches to the programs. The use ofO_NONBLOCKhas most likely no influence on the behavior of the CD-players onother operating systems than Linux. Finally, a user can always revertto old behavior by a call toioctl(file_descriptor, CDROM_CLEAR_OPTIONS, CDO_USE_FFLAGS).

The preferred strategy ofopen()

The routines incdrom.c are designed in such a way that run-timeconfiguration of the behavior of CD-ROM devices (ofany type)can be carried out, by theCDROM_SET/CLEAR_OPTIONSioctls. Thus, variousmodes of operation can be set:

CDO_AUTO_CLOSE | CDO_USE_FFLAGS | CDO_LOCK

This is the default setting. (WithCDO_CHECK_TYPE it will be better, inthe future.) If the device is not yet opened by any other process, and ifthe device is being opened for data (O_NONBLOCK is not set) and thetray is found to be open, an attempt to close the tray is made. Then,it is verified that a disc is in the drive and, ifCDO_CHECK_TYPE isset, that it contains tracks of typedata mode 1. Only if all testsare passed is the return value zero. The door is locked to prevent filesystem corruption. If the drive is opened for audio (O_NONBLOCK isset), no actions are taken and a value of 0 will be returned.

CDO_AUTO_CLOSE | CDO_AUTO_EJECT | CDO_LOCK

This mimics the behavior of the current sbpcd-driver. The option flags areignored, the tray is closed on the first open, if necessary. Similarly,the tray is opened on the last release, i. e., if a CD-ROM is unmounted,it is automatically ejected, such that the user can replace it.

We hope that these option can convince everybody (both drivermaintainers and user program developers) to adopt the new CD-ROMdriver scheme and option flag interpretation.

Description of routines incdrom.c

Only a few routines incdrom.c are exported to the drivers. In thisnew section we will discuss these, as well as the functions thattakeover the CD-ROM interface to the kernel. The header file belongingtocdrom.c is calledcdrom.h. Formerly, some of the contents of thisfile were placed in the fileucdrom.h, but this file has now beenmerged back intocdrom.h.

struct file_operations cdrom_fops

The contents of this structure were described incdrom_api.A pointer to this structure is assigned to thefops fieldof thestructgendisk.

int register_cdrom(struct cdrom_device_info *cdi)

This function is used in about the same way one registerscdrom_fopswith the kernel, the device operations and information structures,as described incdrom_api, should be registered with theUniform CD-ROM Driver:

register_cdrom(&<device>_info);

This function returns zero upon success, and non-zero uponfailure. The structure<device>_info should have a pointer to thedriver’s<device>_dops, as in:

struct cdrom_device_info <device>_info = {        <device>_dops;        ...}

Note that a driver must have one static structure,<device>_dops, whileit may have as many structures<device>_info as there are minor devicesactive.Register_cdrom() builds a linked list from these.

void unregister_cdrom(struct cdrom_device_info *cdi)

Unregistering devicecdi with minor numberMINOR(cdi->dev) removesthe minor device from the list. If it was the last registered minor forthe low-level driver, this disconnects the registered device-operationroutines from the CD-ROM interface. This function returns zero uponsuccess, and non-zero upon failure.

int cdrom_open(struct inode * ip, struct file * fp)

This function is not called directly by the low-level drivers, it islisted in the standardcdrom_fops. If the VFS opens a file, thisfunction becomes active. A strategy is implemented in this routine,taking care of all capabilities and options that are set in thecdrom_device_ops connected to the device. Then, the program flow istransferred to the device_dependentopen() call.

void cdrom_release(struct inode *ip, struct file *fp)

This function implements the reverse-logic ofcdrom_open(), and thencalls the device-dependentrelease() routine. When the use-count hasreached 0, the allocated buffers are flushed by calls tosync_dev(dev)andinvalidate_buffers(dev).

int cdrom_ioctl(struct inode *ip, struct file *fp,                unsigned int cmd, unsigned long arg)

This function handles all the standardioctl requests for CD-ROMdevices in a uniform way. The different calls fall into threecategories:ioctl()’s that can be directly implemented by deviceoperations, ones that are routed through the callaudio_ioctl(), andthe remaining ones, that are presumable device-dependent. Generally, anegative return value indicates an error.

Directly implementedioctl()’s

The followingold CD-ROMioctl()‘s are implemented by directlycalling device-operations incdrom_device_ops, if implemented andnot masked:

CDROMMULTISESSION

Requests the last session on a CD-ROM.

CDROMEJECT

Open tray.

CDROMCLOSETRAY

Close tray.

CDROMEJECT_SW

Ifargnot=0, set behavior to auto-close (closetray on first open) and auto-eject (eject on last release), otherwiseset behavior to non-moving onopen() andrelease() calls.

CDROM_GET_MCN

Get the Media Catalog Number from a CD.

Ioctl*s routed through *audio_ioctl()

The following set ofioctl()’s are all implemented through a call tothecdrom_fops functionaudio_ioctl(). Memory checks andallocation are performed incdrom_ioctl(), and also sanitization ofaddress format (CDROM_LBA/CDROM_MSF) is done.

CDROMSUBCHNL

Get sub-channel data in argumentarg of typestructcdrom_subchnl *.

CDROMREADTOCHDR

Read Table of Contents header, inarg of typestructcdrom_tochdr *.

CDROMREADTOCENTRY

Read a Table of Contents entry inarg and specified byargof typestructcdrom_tocentry *.

CDROMPLAYMSF

Play audio fragment specified in Minute, Second, Frame format,delimited byarg of typestructcdrom_msf *.

CDROMPLAYTRKIND

Play audio fragment in track-index format delimited byargof typestructcdrom_ti *.

CDROMVOLCTRL

Set volume specified byarg of typestructcdrom_volctrl *.

CDROMVOLREAD

Read volume into byarg of typestructcdrom_volctrl *.

CDROMSTART

Spin up disc.

CDROMSTOP

Stop playback of audio fragment.

CDROMPAUSE

Pause playback of audio fragment.

CDROMRESUME

Resume playing.

Newioctl()’s incdrom.c

The followingioctl()’s have been introduced to allow user programs tocontrol the behavior of individual CD-ROM devices. Newioctlcommands can be identified by the underscores in their names.

CDROM_SET_OPTIONS

Set options specified byarg. Returns the option flag registerafter modification. Usearg = rm0 for reading the current flags.

CDROM_CLEAR_OPTIONS

Clear options specified byarg. Returns the option flag registerafter modification.

CDROM_SELECT_SPEED

Select head-rate speed of disc specified as byarg in unitsof standard cdrom speed (176,kB/sec raw data or150kB/sec file system data). The value 0 meansauto-select,i. e., play audio discs at real time and data discs at maximum speed.The valuearg is checked against the maximum head rate of thedrive found in thecdrom_dops.

CDROM_SELECT_DISC

Select disc numberedarg from a juke-box.

First disc is numbered 0. The numberarg is checked against themaximum number of discs in the juke-box found in thecdrom_dops.

CDROM_MEDIA_CHANGED

Returns 1 if a disc has been changed since the last call.For juke-boxes, an extra argumentargspecifies the slot for which the information is given. The specialvalueCDSL_CURRENT requests that information about the currentlyselected slot be returned.

CDROM_TIMED_MEDIA_CHANGE

Checks whether the disc has been changed since a user supplied timeand returns the time of the last disc change.

arg is a pointer to acdrom_timed_media_change_info struct.arg->last_media_change may be set by calling code to signalthe timestamp of the last known media change (by the caller).Upon successful return, this ioctl call will setarg->last_media_change to the latest media change timestamp (in ms)known by the kernel/driver and setarg->has_changed to 1 ifthat timestamp is more recent than the timestamp set by the caller.

CDROM_DRIVE_STATUS

Returns the status of the drive by a call todrive_status(). Return values are defined incdrom_drive_status.Note that this call doesn’t return information on thecurrent playing activity of the drive; this can be polled throughanioctl call toCDROMSUBCHNL. For juke-boxes, an extra argumentarg specifies the slot for which (possibly limited) information isgiven. The special valueCDSL_CURRENT requests that informationabout the currently selected slot be returned.

CDROM_DISC_STATUS

Returns the type of the disc currently in the drive.It should be viewed as a complement toCDROM_DRIVE_STATUS.Thisioctl can providesome information about the currentdisc that is inserted in the drive. This functionality used to beimplemented in the low level drivers, but is now carried outentirely in Uniform CD-ROM Driver.

The history of development of the CD’s use as a carrier medium forvarious digital information has lead to many different disc types.Thisioctl is useful only in the case that CDs have emph {onlyone} type of data on them. While this is often the case, it isalso very common for CDs to have some tracks with data, and sometracks with audio. Because this is an existing interface, ratherthan fixing this interface by changing the assumptions it was madeunder, thereby breaking all user applications that use thisfunction, the Uniform CD-ROM Driver implements thisioctl asfollows: If the CD in question has audio tracks on it, and it hasabsolutely no CD-I, XA, or data tracks on it, it will be reportedasCDS_AUDIO. If it has both audio and data tracks, it willreturnCDS_MIXED. If there are no audio tracks on the disc, andif the CD in question has any CD-I tracks on it, it will bereported asCDS_XA_2_2. Failing that, if the CD in questionhas any XA tracks on it, it will be reported asCDS_XA_2_1.Finally, if the CD in question has any data tracks on it,it will be reported as a data CD (CDS_DATA_1).

Thisioctl can return:

CDS_NO_INFO     /* no information available */CDS_NO_DISC     /* no disc is inserted, or tray is opened */CDS_AUDIO       /* Audio disc (2352 audio bytes/frame) */CDS_DATA_1      /* data disc, mode 1 (2048 user bytes/frame) */CDS_XA_2_1      /* mixed data (XA), mode 2, form 1 (2048 user bytes) */CDS_XA_2_2      /* mixed data (XA), mode 2, form 1 (2324 user bytes) */CDS_MIXED       /* mixed audio/data disc */

For some information concerning frame layout of the various disctypes, see a recent version ofcdrom.h.

CDROM_CHANGER_NSLOTS

Returns the number of slots in a juke-box.

CDROMRESET

Reset the drive.

CDROM_GET_CAPABILITY

Returns thecapability flags for the drive. Refer to sectioncdrom_capabilities for more information on these flags.

CDROM_LOCKDOOR

Locks the door of the drive.arg == 0 unlocks the door,any other value locks it.

CDROM_DEBUG

Turns on debugging info. Only root is allowed to do this.Same semantics as CDROM_LOCKDOOR.

Device dependentioctl()’s

Finally, all otherioctl()’s are passed to the functiondev_ioctl(),if implemented. No memory allocation or verification is carried out.

How to update your driver

  • Make a backup of your current driver.

  • Get hold of the filescdrom.c andcdrom.h, they should be inthe directory tree that came with this documentation.

  • Make sure you includecdrom.h.

  • Change the 3rd argument ofregister_blkdev from&<your-drive>_fopsto&cdrom_fops.

  • Just after that line, add the following to register with the UniformCD-ROM Driver:

    register_cdrom(&<your-drive>_info);*

    Similarly, add a call tounregister_cdrom() at the appropriate place.

  • Copy an example of the device-operationsstruct to yoursource, e. g., fromcm206.ccm206_dops, and change allentries to names corresponding to your driver, or names you justhappen to like. If your driver doesn’t support a certain function,make the entryNULL. At the entrycapability you should list allcapabilities your driver currently supports. If your driverhas a capability that is not listed, please send me a message.

  • Copy thecdrom_device_info declaration from the same exampledriver, and modify the entries according to your needs. If yourdriver dynamically determines the capabilities of the hardware, thisstructure should also be declared dynamically.

  • Implement all functions in your<device>_dops structure,according to prototypes listed incdrom.h, and specifications givenincdrom_api. Most likely you have already implementedthe code in a large part, and you will almost certainly need to adapt theprototype and return values.

  • Rename your<device>_ioctl() function toaudio_ioctl andchange the prototype a little. Remove entries listed in the firstpart incdrom_ioctl, if your code was OK, these arejust calls to the routines you adapted in the previous step.

  • You may remove all remaining memory checking code in theaudio_ioctl() function that deals with audio commands (these arelisted in the second part ofcdrom_ioctl. There is noneed for memory allocation either, so mostcase*s in the *switchstatement look similar to:

    case CDROMREADTOCENTRY:        get_toc_entry\bigl((struct cdrom_tocentry *) arg);
  • All remainingioctl cases must be moved to a separatefunction,<device>_ioctl, the device-dependentioctl()’s. Note thatmemory checking and allocation must be kept in this code!

  • Change the prototypes of<device>_open() and<device>_release(), and remove any strategic code (i. e., traymovement, door locking, etc.).

  • Try to recompile the drivers. We advise you to use modules, bothforcdrom.o and your driver, as debugging is much easier thisway.

Thanks

Thanks to all the people involved. First, Erik Andersen, who hastaken over the torch in maintainingcdrom.c and integrating muchCD-ROM-related code in the 2.1-kernel. Thanks to Scott Snyder andGerd Knorr, who were the first to implement this interface for SCSIand IDE-CD drivers and added many ideas for extension of the datastructures relative to kernel~2.0. Further thanks to Heiko Eißfeldt,Thomas Quinot, Jon Tombs, Ken Pizzini, Eberhard Mönkeberg and Andrew Kroll,the Linux CD-ROM device driver developers who were kindenough to give suggestions and criticisms during the writing. Finallyof course, I want to thank Linus Torvalds for making this possible inthe first place.