Linux CDROM驱动分析 - linux CDROM驱动标准

在kernel/Documentation/cdrom/下执行make,可以生成cdrom-standard.dvi,这篇类似于CDROM标准的文档,是Linux kernel CDROM驱动的几个主要作者维护的。因此,了解CDROM设计思想,一定要了解这篇文档。

1.介绍

LINUX大概是类Unix操作系统中支持硬件设备最广泛的操作系统,原因大概有这个几点:

  • 当前LINUX支持许多平台下的大量硬件设备。
  • 操作系统的开放性设计,因此任何人都可以为LINUX写驱动。
  • 有大量的源码可以作为写驱动的例子。

LINUX的开放性,以及不同类型的硬件允许LINUX支持不同的硬件设备。不幸的是,这种支持各种不同设备的开放性,也导致各个设备驱动程序之间有着很大差异。CD-ROM驱动尤其明显:比如,一个特定驱动响应标准ioctl调用的方式可能和另外一个驱动完全不同。为了避免他们的驱动不一致,驱动的作者往往创建一个新的驱动,通过理解,复制和改变现存的一个驱动程序。不幸的是,这种行为并没有维护LINUX CD-ROM驱动的一致性行为。

本文描述了在LINUX操作系统下,为不同的CD-ROM设备驱动建立一致性行为的努力。本文档定义了各种ioctls,以及低级CD-ROM设备驱动程序如果实现他们。当前,几个低级CD-ROM设备驱动程序,包括IDE/ATAPI和SCSI,都使用这个统一接口。

在以前开发CD-ROM驱动时,CD-ROM驱动和计算机的接口并没有在标准中说明。结果导致了各种CD-ROM接口被开发出来。他们有些使用私有的设计(Sony, Mitsumi, Panasonic, Philips),其他的制造商采用现存的电气接口并修改了功能(CreativeLabs/SoundBlaster, Teac, Funai),还有些采用已经存在的电气接口(Aztech, Sanyo, Funai, Vertos, Longshine, Optics Storage以及大部分无名制造商)。在这种情况下,一个新的驱动可能导致一个新的接口,命令集或者控制流,因此需要一个新的驱动,或者对现在的驱动进行增强。CD-ROM 历史上支持很多种不同的接口。今天,大部分新的驱动或者是IDE/ATAPI或者是SCSI,很少再有制造商会创建一个新的电气接口了。而那些老的私有接口也很难找到了。

当我在查看现有的软件接口时(在cdrom.h中描述),它是各种命令和数据格式的大杂烩。为了处理某些特定的驱动器,一些软件接口以特定的方式被加进来。最主要的是,一些标准命令在不同的驱动中表现出不同的行为:比如,一些驱动在open()系统调用后会关闭已经打开的tray,而其他的驱动不会;一些驱动会在打开设备时锁上仓门,防止文件系统的不一致性,但是其他的驱动不会。毫无疑问,不同的驱动器能力不同,但是甚至当两个驱动器具有相同的能力时,他们的表现行为仍然不同。

因此,我决定发起一个讨论:如何是的LINUX CD-ROM驱动的表现更一致。我开始联系Linux内核中CD-ROM驱动的作者。他们的反映鼓舞了我,我决定写一个统一的CD-ROM驱动,也就是这篇文档所要描述的。统一的CD-ROM驱动在文件cdrom.c中实现。这个驱动在每个CD-ROM光驱的低级驱动之上增加一个软件层。通过增加这个软件层,使得统一不同CD-ROM驱动表现行为成为可能。

统一CD-ROM驱动的目的不是要挑拨那些还没有开始的开发者支持这个努力。统一CD-ROM驱动的目的是使得应用程序开发者提供统一的编程接口。此外,也在LINUX kernel和底层设备驱动之间提供了统一的接口。当然,对现存于cdrom.h中定义的数据结构和编程接口是100%兼容的。本指南是帮助CD-ROM驱动的开发者采用cdrom.c中定义的统一驱动代码。

从我个人来说,我认为最终要的硬件接口是IDE/ATAPI驱动和SCSI驱动,随着硬件价格的不断下降,人们可能拥有多个CDROM驱动器,甚至是混合类型的。这些驱动器表现出一致的行为,是很重要的。在1994年12月,最便宜的CD-ROM驱动器是Philips cm206,双速私有驱动器。在我忙着写LINUX驱动的时候,私有驱动器已经被抛弃了,IDE/ATAPI驱动器成为标准。在最后一次更新这个文档的时候(1997 11月),已经很难发现一个16倍速以下的CD-ROM驱动器了,24倍速驱动器已经相当的普遍


2.通过另外一个软件层实现标准化

在酝酿这篇文档时,所有的驱动都是在他们自己的代码中直接实现CD-ROM ioctl()。这就导致某个驱动可能会忘记做一些重要的事情:比如检查用户数据的有效性。更主要的是,这导致了驱动行为的不一致,我们在第一节已经讨论过了。

出于这个原因,统一CD-ROM驱动用来增强CD-ROM驱动的一致性行为,为底层CD-ROM驱动提供一组公共的服务。统一CD-ROM驱动因此产生了另外一个软件层次,把ioctl和open实现从确定的硬件实现中分离出来。注意这个努力所做的改变几乎不会影响用户的应用程序。最大的变化涉及到移动各种底层CD-ROM驱动的头文件到内核的cdrom目录。这确保仅仅有一个cdrom接口存在,这个接口就定义在cdrom.h中。

CD-ROM驱动器是足够特定的驱动器(它完全不同与其他的块设备驱动,比如软盘和硬盘驱动器),需要为它定义一组通用的CD-ROM设备操作:cdrom-device_dops。这些操作和那些传统的块设备文件操作是完全不同的。

通用CD-ROM驱动接口层在文件cdrom.c中实现。在这个文件中,通用CD-ROM驱动作为一个块设备和内核交互,通过注册下面的通用file_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 */
        cdrom_media_changed,   /* media change */
        NULL                   /* revalidate */
};

每一个活动的CD-ROM设备共享这个接口。以上声明的函数都在cdrom.c中实现,因此这个文件是CD-ROM行为标准化的最好位置。各种类型CD-ROM硬件接口仍然在底层CD-ROM设备驱动中实现。这些函数仅仅实现所有CD-ROM共有的能力。

注册一个低级的CD-ROM驱动程序也是在cdrom.c中实现的,而不是再通过Virtual File System。在cdrom.c中实现的接口操作两个通用的结构:cdrom_device_ops和cdrom_device_info。他们包含了驱动以及驱动操作的特定设备的能力信息。

  • cdrom_device_ops:这个结构包含了CD-ROM设备的低级驱动信息。这个结构概念上和设备的主设备号相连接
  • cdrom_device_info:这个结构包含了一个特定设备的信息,比如设备名称,速度等等。这个结构概念上和设备的从设备号连接


在统一CD-ROM驱动中注册一个特定的CD-ROM驱动需要在低级设备驱动中调用:

register_cdrom(struct cdrom_device_info *(device)_info)

设备信息结构,(device)_info 包含内核需要和低级CD-ROM设备驱动交互的所有信息。在这个结构中最重要的一项是cdrom_device_ops。

设备操作结构 cdrom_device_ops,包含一个函数指针列表,这些函数在底层设备驱动中实现。当cdrom.c访问一个CD-ROM设备时,他实际上是通过这个结构中的函数来进行的。cdrom.c是不可能知道所有未来CD-ROM驱动器的能力,所以这个列表可能随着新技术的出现做出扩展。比如CD-R和CD-R/W驱动器已经越来越流行,需要在这个列表中增加对这些驱动器的支持。当前这个结构如下:

struct cdrom_device_ops {
/* routines */
	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 *cdi,
				      unsigned int clearing, int slot);
	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 *, int);
	int (*select_disc) (struct cdrom_device_info *, int);
	int (*get_last_session) (struct cdrom_device_info *,
				 struct cdrom_multisession *);
	int (*get_mcn) (struct cdrom_device_info *,
			struct cdrom_mcn *);
	/* hard reset device */
	int (*reset) (struct cdrom_device_info *);
	/* play stuff */
	int (*audio_ioctl) (struct cdrom_device_info *,unsigned int, void *);

/* driver specifications */
	const int capability;   /* capability flags */
	int n_minors;           /* number of active minor devices */
	/* handle uniform packets for scsi type devices (scsi,atapi) */
	int (*generic_packet) (struct cdrom_device_info *,
			       struct packet_command *);
};

当一个底层设备驱动实现其中一个功能时,它需要向这个结构增加一个函数指针。当一个特定的功能不需要实现时,那么只需要把相应的指针设置为NULL。

@capability,标识CD-ROM与底层CD-ROM驱动的能力。

@n_minors,底层驱动所支持的从设备的数目,正常情况下为1.

尽管capablity和n_minors是信息型成员,而不是操作型成员,但是还是把他们放在这个结构中,因为他们描述的驱动的能力而不是驱动器。命名法已经变成计算机编程最困难的一部分。

注意,大部分函数和blkdev_fops中对应函数相比,参数都变少了。这是因为结构inode和file中的信息很少被用到。对于大多数驱动来说,主要参数是cdrom_device_info,主从设备号都可以从这个结构中得到。(对于大部分低级CD-ROM驱动,甚至不需要使用主从设备号,因为他们大部分只支持一个设备。)

像minor这种驱动特定的信息在cdrom.c中注册,保存在cdrom_device_info

/* Uniform cdrom data structures for cdrom.c */
struct cdrom_device_info {
    struct cdrom_device_ops  *ops;  /* link to device_ops */
    struct list_head list;        /* linked list of all device_info */
    struct gendisk *disk;        /* matching block layer disk */
    void *handle;                /* driver-dependent data */
/* specifications */
    int mask;                       /* mask of capability: disables them */
    int speed;            /* maximum speed for reading data */
    int capacity;            /* number of discs in jukebox */
/* device-related storage */
    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 opened */
        char name[20];                  /* name of the device type */
/* per-device flags */
        __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 flags */
    __u8 last_sense;
    __u8 media_written;        /* dirty flag, DVD+RW bookkeeping */
    unsigned short mmc3_profile;    /* current MMC3 profile */
    int for_data;
    int (*exit)(struct cdrom_device_info *);
    int mrw_mode_page;
};

@list,从设备链表

@mask,用来mask out在ops->capability中定义的能力,如果一个特定的驱动器不支持驱动的一个feature。

@speed用来标识驱动器的最大磁头速率,计量单位是正常的audio速度(176kB/sec原始数据或者150kB/sec文件数据)

@capacity,碟盒中可同时容纳的磁碟数量

@options,用来描述几个常用CD-ROM函数如何运行,这些标志提供给用户足够的灵活性来控制底层驱动的运行方式。

@mc_flags,用来缓冲media_changed()的信息到两个队列中。


cdrom.c组成的中间层软件还执行一些额外的记账工作,@user_count用来记录设备的打开数目。函数cdrom_ioctl要验证读写使用的用户内存区,同时把对底层驱动的访问请求转换成标准格式,并且在用户层软件和底层驱动之间进行格式转换。这使得底层驱动不需要再考虑内存检查,格式检查和格式转换。此外这个软件栈包含了一些必要的数据结构。

下面几个小节定义了如何实现这些函数。两个函数必须要实现,他们是open和release函数。其他的函数是可选的,如果不实现,在注册时相应的能力标记要标记为清除。通常来说,一个函数返回零表示成功,返回负数表示失败,一个函数需要在命令完成后才能返回,当然在等待设备返回时不应该占用处理器时间。


2.1 int open(struct cdrom_device_info *cdi, int purpose)

open()函数应该为了某个特定的目的打开设备,他们是:

  • 0 打开设备读数据,比如mount,或者用户命令dd cat
  • 1 打开设备执行ioctl命令,比如audio-CD播放程序。

注意所有的策略代码(open()操作时关闭tray)都在cdrom.c中做了,对于底层函数只需要关注正确的初始化即可,比如启动盘片等等。


2.2 void release(struct cdrom_device_info *cdi)

执行设备特定的操作,比如盘片停转。然而像弹出托盘,打开仓门应该留给常规函数cdrom_release()执行。


2.3 int drive_status(struct cdrom_device_info *cdi, int slot_nr)

如果实现了drive_staus函数,这个函数用来提供驱动器的状态信息(不是磁盘信息)。如果驱动器不是一个可更换的,slot_nr应该被忽略。在cdrom.h中列出了可能的返回值:

/* drive status possibilities returned by CDROM_DRIVE_STATUS ioctl */
#define CDS_NO_INFO		0	/* if not implemented */
#define CDS_NO_DISC		1
#define CDS_TRAY_OPEN		2
#define CDS_DRIVE_NOT_READY	3
#define CDS_DISC_OK		4


2.4 int media_changed(struct cdrom_device_info *cdi, int disc_nr)

返回1如果设备cdi->dev中的介质自从最后一次调用以来发生了变化,否则返回0。参数disc_nr用来标识碟盒的一个特定slot,对于单磁盘驱动器来说,可以忽略掉这个参数。


2.5 int tray_move(struct cdrom_device_info *cdi, int position)

如果实现了这个函数,那么这个函数用来控制托盘的移动,参数@position用来控制托盘的移动方向。

0 close tray
1 open tray

成功时,函数返回0;失败时返回一个非0值。注意如果托盘已经在需要的位置,那么这个函数什么都不做,返回0。


2.6 int lock_door(struct cdrom_device_info *cdi, int lock)

如果驱动器允许操作仓门,这个函数控制仓门的开关。参数@lock控制需要的开关状态:

0 开仓,允许手动打开
1 关仓,托盘不能手动弹出

成功时返回0;失败时返回非零值。注意如果仓门已经处于请求的状态,那么不做任何操作,直接返回0。


2.7 int select_speed(struct cdrom_device_info *cdi, int speed)

一些CD-ROM驱动器可以改变磁头速度。有几个原因需要改变CD-ROM驱动器的速度。

  • 以较低速率运行,可以使得那些压制不太好的盘片受益。现代CD-ROM驱动器可以运行在非常高的速率(最高24x)。但是在这个高度率下运行,驱动器可能会发生读擦,如果降低速度就可以防止在这些环境下的数据丢失。
  • 此外有些驱动器在高速运行的情况下,会发出很大的噪声。

这个函数用来修改读数据和播放audio的速率。@speed用来标识磁头的速率,计量单位为标准cdrom速度(176kB/sec原始数据,150kB/sec文件数据)。所以如果希望CD-ROM驱动器工作在300kB/sec速率,那么需要调用CDROM_SELECT_SPEED ioctl设置speed为2。值0有特殊含义,表示auto-selection,比如最大数据速率或者实时audio速率。如果驱动器不支持这个自动选择能力,决定权交给当前装载的碟片,返回一个正数。负数表示出错。


2.8 int select_disc(struct cdrom_device_info *cdi, int number)

如果驱动器可以保存多个盘片,这个函数用来执行碟片选择。成功时,这个函数返回选择的盘片,失败时返回负值。当前,仅仅ide-cd驱动器支持这个功能。


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

这个函数对应着老的ioctl实现。对于设备cdi->dev,当前盘片的最后一个session的起始位置在指针参数ms_info中返回。注意,在cdrom.c中已经对这个参数进行了处理:请求格式一直使用CDROM_LBA类型,而不管上层的请求是什么。实际上标准化处理走的更远:低级驱动可能返回的是CDROM_MSF格式的信息,cdrom.c将会对这种信息进行转化。返回值为0表示成功。


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

许多磁盘都带有一个媒体分类号(Media Catalog Number MCN),也称为Universal Product Code(UPC)。这个号码通常也会在产品的bar-code上反映出来,不幸的是,很少有磁盘携带这个号码。返回参数是一个预先定义的内存区,类型为struct cdrom_mcn。MCN通常是一个13个字符的字符串,并带有一个空字符。


2.11 int reset(struct cdrom_device_info *cdi)

这个调用应该对驱动器执行一个硬件reset(有时硬件reset是必要的,驱动器可能不再听从发送的命令)。最好的情况是在驱动器完成reset之后再返回。如果驱动器不再接受命令,那么底层驱动最好增加time out机制。


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

定义在cdrom.h中的有些CD-ROM ioctls,可以通过上面描述的常规函数实现,函数cdrom_ioctl因此可以使用这些函数。但是,很多ioctls使用来处理audio控制的。我们决定把这些留给一个函数来实现,并使用参数cmd和arg,注意后者是void *类型,而不是unsigned long int。函数cdrom_ioctl并没做什么有用的事情。它只是为所有的audio calls,把地址格式类型转化为CDROM_MSF。同时它也验证了参数arg的内存位置,以及为参数保留的栈内存。这使得audio_ioctl的实现更简单。

一个未实现的ioctl应该返回-ENOSYS,但是一个无害的请求可以返回0,其他的错误则不管他们是什么,按着标准来做。当一个错误被底层驱动返回,统一CD-ROM驱动尽可能的把错误码返回给调用程序。(当然我们可以在cdrom-ioctl中对返回值做处理,以便保证audio-player软件有一个统一的接口)。


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

有些ioctls看起来是特定于某些CD-ROM驱动器的。也就是说他们针对的是特定驱动器的某些能力,事实上,共有6个不同的ioctls来读取数据,每一个都是某种特定的格式,或者audio data。有些驱动器并不支持读取audio tracks数据。我相信这是因为版权保护的原因,然后,我认为如果支持audio-tracks,那么应该通过VFS而不是通过ioctl。另外一个问题是audio-frames是2352个字节长度,所以要么audio-file-system一次请求75234字节(512和2352的倍数),要么驱动器想办法兼容这种情况。此外,对于硬件来说是很难发现帧边界的,因为audio frames并没有同步头。一旦这些问题解决了,这个代码应该在cdrom.c中标准化。

因为需要引入很多ioctls来满足特定的驱动,任何非标准的ioctls应该放在dev_ioctl中。理论上,"私有"ioctl应该在主设备号编号,而不是使用通用的CD-ROM ioctl号0x53。当前非标准的ioctl包括如下:

  • CDROMREADMODE1
  • CDROMREADMODE2
  • CDROMREADAUDIO
  • CDROMREADRAW
  • CDROMREADCOOKED
  • CDROMSEEK
  • CDROMPLAYBLK
  • CDROMREADALL


2.14 CD-ROM capabilities

除了实现一个ioctl调用,cdrom.c中的接口支持指示一个CD-ROM驱动器的能力。这是在注册阶段,或操作一些能力常量。当前支持的能力包括如下:

/* capability flags used with the uniform CD-ROM driver */ 
#define CDC_CLOSE_TRAY		0x1     /* caddy systems _can't_ close */
#define CDC_OPEN_TRAY		0x2     /* but _can_ eject.  */
#define CDC_LOCK		0x4     /* disable manual eject */
#define CDC_SELECT_SPEED 	0x8     /* programmable speed */
#define CDC_SELECT_DISC		0x10    /* select disc from juke-box */
#define CDC_MULTI_SESSION 	0x20    /* read sessions>1 */
#define CDC_MCN			0x40    /* Medium Catalog Number */
#define CDC_MEDIA_CHANGED 	0x80    /* media changed */
#define CDC_PLAY_AUDIO		0x100   /* audio functions */
#define CDC_RESET               0x200   /* hard reset device */
#define CDC_DRIVE_STATUS        0x800   /* driver implements drive status */
#define CDC_GENERIC_PACKET	0x1000	/* driver implements generic packets */
#define CDC_CD_R		0x2000	/* drive is a CD-R */
#define CDC_CD_RW		0x4000	/* drive is a CD-RW */
#define CDC_DVD			0x8000	/* drive is a DVD */
#define CDC_DVD_R		0x10000	/* drive can write DVD-R */
#define CDC_DVD_RAM		0x20000	/* drive can write DVD-RAM */
#define CDC_MO_DRIVE		0x40000 /* drive is an MO device */
#define CDC_MRW			0x80000 /* drive can read MRW */
#define CDC_MRW_W		0x100000 /* drive can write MRW */
#define CDC_RAM			0x200000 /* ok to open for WRITE */

capability标记被声明为const,以防止驱动不经意的修改它的内容。capability 标记告诉cdrom.c驱动可以做什么。如果驱动器发现它没有驱动的能力,那么可以通过cdrom_device_info变量中的mask来取消这个能力。例如,SCSI CD-ROM驱动已经实现了加载和弹出CD-ROM的代码,因此相应的能力标志被设置了。但是一个SCSI CD-ROM驱动器可能是卡匣式,不能使用托盘。对于这种情况,这个驱动器的cdrom_device_info结构的mask应该设置CDC_CLOSE_TRAY位。


2.15 Options

用来控制CD-ROM驱动器行为的一个标志,为了满足不同用户的需要,为用户提供了一些可配置选项,当前支持如下行为选项:

/* User-configurable behavior options for the uniform CD-ROM driver */
#define CDO_AUTO_CLOSE		0x1     /* close tray on first open() */
#define CDO_AUTO_EJECT		0x2     /* open tray on last release() */
#define CDO_USE_FFLAGS		0x4     /* use O_NONBLOCK information on open */
#define CDO_LOCK		0x8     /* lock tray on open files */
#define CDO_CHECK_TYPE		0x10    /* check type on open for data */

缺省值是CDO_AUTO_CLOSE | CDO_USE_FFLAGS | CDO_LOCK,这个缺省值反映了我个人对用户接口和软件标准的观点。在cdrom.c中有两个ioctls调用允许你控制CD-ROM的行为,他们是:

#define CDROM_SET_OPTIONS	0x5320  /* Set behavior options */
#define CDROM_CLEAR_OPTIONS	0x5321  /* Clear behavior options */


有一个选项需要特别说明:那就是CDO_USB_FFLAGS。在下一节我们会解释这个选项。

一个软件包setcd,允许用户级别控制这些标志。


3 打开CD-ROM设备的目的

传统上,Unix设备可以使用两种不同的模式,或者读写设备文件,或者通过ioctl发送一个控制命令到设备上。但是对于CD-ROM驱动器来说,它们会有两种完全不同的使用目的:

  • 一个是mount可移动的文件系统
  • 另外一个是播放audio CD's

Audio命令完全是通过ioctls来实现的,假设因为第一个实现已经那样了。理论上这并没有什么错,但是CD player需要设备能够一直打开着,以便随时发送ioctl命令,而不用管驱动器的状态。

另一方面,当我们使用一个可移动介质的磁盘驱动器时(CD-ROMS问世的最初目的)我们想要确保一旦打开设备,磁盘驱动器就已经准备好操作。老的模式是,一些CD-ROM驱动在open时并不会做任何行完整性检查,从而导致mount一个空的CDROM驱动器时,VFS上报i/o错误。这种发现CD-ROM没有插入的方法并不优雅;这和老式IBM-PC试图从一个空的软盘驱动器读取内容的方式很类似,经过了几秒钟,系统才抱怨无法从软驱读取数据。今天我们可以感知驱动器中介质的存在以及介质移除,我们可以利用这种感知能力。在打卡设备时有必要验证CD-ROM的能力以及它的数据类型是否确。

有两种使用CD-ROM的方式,第一是读取数据;第二是播放audio。他们对于open操作有不同的需求。Audio播放仅仅想打开设备获取一个文件描述符,以便能够使用ioctl命令;而读取数据则需要打开正确的可靠的传输。应用程序可以指明打开设备目地的唯一方法是使用flags参数。对于CD-ROM设备来说,这些flags标志还没有实现(有些驱动实现了对写相关标记的检查,但是严格来说如果正确的设置了权限标记,这并是必要的)。大部分选项标记对于CD-ROM设备来说都是无意义的,如:O_CREAT, _NOCTTY, OTRUNC, O_APPEND以及O_SYNC。

我们因此提出使用标志O_NONBLOCK来指示打开操作是为了ioctl命令。严格的说,O_NONBLOCK的意思是打开以及接下来的操作都不会促使进程等待。我们把这解释成“不需要等待某人插入有效的CD-ROM数据”。因此,打开CD-ROMs的目的可以解释如下:

  • 如果只有O_RDONLY,那么打开设备来进行数据传输的,仅仅成功的初始化传输后,才会返回成功值0。open调用甚至会触发CD-ROM的动作,比如关闭托盘。
  • 如果O_NONBLOCK被设置,打开操作一直成功,除非整个设备并不存在。打开操作不会对驱动器做任何动作。


3.1 有没有打开的标准

你可能对这个来自Linux社区而不是某个标准组织的提议表示怀疑。像SUN SGI HP以及其他的Unix和硬件开发商是如何定义打开操作的?这些公司处在一个很幸运的位置,因为他们通常控制着他们支持产品的软件和硬件,并且这些公司大到可以定义他们自己的标准。他们不需要处理那么多不同的互相竞争的硬件配置。

我相信使用O_NONBLOCK来指明一个设备打开的目的仅仅在Linux社区推广。我们不得不通知所有的CD-player作者,甚至亲自为这些打patch。使用O_NONBLOCK标记并不会对CD-player程序在其他操作系统上的行为产生影响。最后,用户可以使用老式的方法,通过调用ioctl(file_descriptor, CDROM_CLEAR_OPTIONS, CDO_USE_FFLAGS)。


3.2 推荐的open策略

cdrom.c中的函数设计为可在运行时配置CD-ROM devices的行为方式,通过CDROM_SET和CLEAR_OPTIONS ioctls。因此,我们可以设置各种操作模式:

  • CDO_AUTO_CLOSE | CDO_USE_FFLAGS | CDO_LOCK,这是缺省的模式。如果设备还没有被其他进程打开,打开设备是为了读取数据,当托盘处在打开状态时,会尝试关闭托盘。然后,验证是否有一个盘片放在驱动器中。如果CDO_CHECK_TYPE被设置,那么会检查盘片是否包含'data mode 1'。仅当所有的测试都通过后,才会返回0。仓门会被锁住以防止文件系统出错。如果打开设备的目的是播放audio,那么不执行任何操作,直接返回0即可。
  • CDO_AUTO_CLOSE | CDO_AUTO_EJECT | CDO_LOCK,模仿当前sbpcd-driver的行为。选项标记被忽略,第一次打开时托盘被关闭。类似的,托盘在最后一次释放时被打开。比如,如果一个CD-ROM被unmounted,那么自动弹出,以便用户更换盘片。

我们希望这些选项可以说服每个人,包括驱动维护人员和应用程序开发者,采用这种新的CD-ROM驱动模式和选项标记解释。


4 cdrom.c中例程描述

在cdrom.c中仅有几个函数导出给驱动使用的。在这一节中我们会讨论这些函数,以及CD-ROM和kernel之间的接口。属于cdrom.c的头文件称为cdrom.h,以前这个文件的部分内容放在ucdrom.h中,现在已经被merge回cdrom.h中了。

4.1 struct file_operations cdrom_fops

4.2 struct register_cdrom(struct cdrom_device_info *cdi)

这个函数用来注册设备操作和信息结构到kernel,底层的cdrom驱动通过这个函数把这个信息注册到统一CD-ROM驱动中:

register_cdrom(&_info);

成功时返回0,失败返回非0。结构_info应该包含一个指针指向驱动的_dops,如下

struct cdrom_device_info _info = {
    _dops;
    ...
}

4.3 void unregister_cdrom(struct cdrom_device_info *cdi)

注销设备@cdi


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

原文:

This function is not called directly by the low-level drivers, it is listed in the standard cdrom_fops. If the VFS opens a file, 
this function becomes active. A strategy is implemented in this routine, taking care of all capabilities and options that are set in the
cdrom_device_ops connected to the device. Then, the program flow is transferred to the device_dependent open() call.
当前的实现,该函数是cdrom.c为底层CD-ROM驱动提供的一个标准回调函数,在scsi/sr.c, cdrom/gdrom.c以及ide/ide-cd.c中会调用这个函数。调用地点都是这些块设备驱动的open回调函数。在这个函数中会实现一些策略,比如capbalities和options。然后调用设备本身的open函数


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

这个函数实现cdrom_open()的反向逻辑,然后调用设备本身的release函数,当用户引用计数use-count变为0时,那么要处理这个设备相关的buffers。


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

对于这部分的文档,已经和当前的Linux CDROM 驱动实现有了较大差异。

当前Linux CDROM驱动的cdrom_ioctl实现了ioctl大部分策略逻辑,这部分逻辑代码会调用驱动器相关的代码。









你可能感兴趣的:(Linux CDROM驱动分析 - linux CDROM驱动标准)