Linux CD-ROM 標準

作者:

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

日期:

1999 年 3 月 12 日

更新者:

Erik Andersen (andersee@debian.org)

更新者:

Jens Axboe (axboe@image.dk)

引言

Linux 可能是支援硬體裝置種類最廣泛的類 Unix 作業系統。原因大概是:

  • Linux 現在支援的眾多平臺(即 i386-PC、Sparc Suns 等)可用的硬體裝置列表很長。

  • 作業系統的開放設計,使得任何人都可以為 Linux 編寫驅動程式。

  • 有大量的原始碼可作為編寫驅動程式的示例。

Linux 的開放性以及多種不同型別的可用硬體使得 Linux 能夠支援許多不同的硬體裝置。不幸的是,正是這種開放性使得 Linux 能夠支援所有這些不同的裝置,但也使得每個裝置驅動程式的行為在裝置之間差異很大。這種行為差異對於 CD-ROM 裝置來說非常顯著;特定的驅動器對“標準” ioctl() 呼叫的反應因裝置驅動程式而異。為了避免使他們的驅動程式完全不一致,Linux CD-ROM 驅動程式的編寫者通常透過理解、複製然後更改現有驅動程式來建立新的裝置驅動程式。不幸的是,這種做法並未在所有 Linux CD-ROM 驅動程式之間保持統一的行為。

本文件旨在建立所有 Linux CD-ROM 裝置驅動程式之間的統一行為。本文件還定義了各種 ioctl() 以及低階 CD-ROM 裝置驅動程式應如何實現它們。目前(截至 Linux 2.1.x 開發核心),包括 IDE/ATAPI 和 SCSI 在內的幾個低階 CD-ROM 裝置驅動程式,現在都使用這個統一介面。

CD-ROM 開發時,CD-ROM 驅動器與計算機之間的介面並未在標準中指定。因此,開發了許多不同的 CD-ROM 介面。其中一些具有自己的專有設計(Sony、Mitsumi、Panasonic、Philips),其他製造商採用了現有的電氣介面並改變了功能(CreativeLabs/SoundBlaster、Teac、Funai)或簡單地使其驅動器適應一個或多個現有電氣介面(Aztech、Sanyo、Funai、Vertos、Longshine、Optics Storage 和大多數“無名”製造商)。在新的驅動器確實帶來自己的介面或使用自己的命令集和流控制方案的情況下,要麼需要編寫一個單獨的驅動程式,要麼需要增強現有驅動程式。歷史為我們提供了對許多這些不同介面的 CD-ROM 支援。如今,幾乎所有新的 CD-ROM 驅動器都是 IDE/ATAPI 或 SCSI,並且製造商建立新介面的可能性非常小。即使是尋找舊專有介面的驅動器也變得困難。

當我(在 1.3.70 年代)檢視現有的軟體介面時,它透過 cdrom.h 表達,它似乎是一組相當混亂的命令和資料格式 [1]。似乎軟體介面的許多功能都是為了適應特定驅動器的能力而臨時新增的。更重要的是,似乎“標準”命令的行為在大多數不同的驅動程式中是不同的:例如,一些驅動程式在托盤開啟時發生 open() 呼叫時會關閉托盤,而另一些則不會。一些驅動程式在開啟裝置時會鎖定門,以防止檔案系統不一致,而另一些則不會,以允許軟體彈出。毫無疑問,不同驅動器的功能各不相同,但即使兩個驅動器具有相同的功能,其驅動程式的行為通常也不同。

我決定發起一場討論,探討如何使所有 Linux CD-ROM 驅動程式的行為更加統一。我首先聯絡了 Linux 核心中許多 CD-ROM 驅動程式的開發者。他們的反饋鼓勵我編寫統一 CD-ROM 驅動程式,本文件旨在描述該驅動程式。統一 CD-ROM 驅動程式的實現位於 cdrom.c 檔案中。該驅動程式旨在成為一個額外的軟體層,位於每個 CD-ROM 驅動器的低階裝置驅動程式之上。透過新增這個額外的層,所有不同的 CD-ROM 裝置都能夠表現得**完全**相同(只要底層硬體允許)。

統一 CD-ROM 驅動程式的目標**不是**疏遠尚未採取措施支援這項工作的驅動程式開發人員。統一 CD-ROM 驅動程式的目標是簡單地為編寫 CD-ROM 驅動器應用程式的人們提供**一個** Linux CD-ROM 介面,該介面對所有 CD-ROM 裝置都具有一致的行為。此外,這也為低階裝置驅動程式程式碼和 Linux 核心之間提供了一致的介面。我們注意確保與 cdrom.h 中定義的資料結構和程式設計師介面實現 100% 相容。本指南旨在幫助 CD-ROM 驅動程式開發人員調整其程式碼以使用 cdrom.c 中定義的統一 CD-ROM 驅動程式程式碼。

我個人認為,最重要的硬體介面是 IDE/ATAPI 驅動器,當然還有 SCSI 驅動器,但隨著硬體價格的不斷下降,人們也可能擁有不止一個 CD-ROM 驅動器,可能是混合型別。重要的是這些驅動器以相同的方式執行。1994 年 12 月,最便宜的 CD-ROM 驅動器之一是 Philips cm206,一款雙速專有驅動器。在我忙於為其編寫 Linux 驅動程式的幾個月裡,專有驅動器變得過時,IDE/ATAPI 驅動器成為標準。截至本文件最後一次更新時(1997 年 11 月),甚至**找到**低於 16 倍速的 CD-ROM 驅動器也變得困難,而 24 倍速驅動器已很常見。

透過另一個軟體層實現標準化

本文件構思時,所有驅動程式都直接透過自己的例程實現 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。這些操作與經典的塊裝置檔案操作 <block-device>_fops 不同。

統一 CD-ROM 驅動程式介面層的例程在檔案 cdrom.c 中實現。在此檔案中,統一 CD-ROM 驅動程式透過註冊以下通用 struct 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 */
        NULL                    /* revalidate */
};

每個活動的 CD-ROM 裝置都共享這個 struct。上面宣告的例程都在 cdrom.c 中實現,因為這個檔案是定義和標準化所有 CD-ROM 裝置的行為的地方。與各種型別 CD-ROM 硬體的實際介面仍然由各種低階 CD-ROM 裝置驅動程式執行。這些例程只是實現了所有 CD-ROM(以及實際上所有可移動介質裝置)共有的某些**功能**。

低階 CD-ROM 裝置驅動程式的註冊現在透過 cdrom.c 中的通用例程完成,而不再透過虛擬檔案系統 (VFS)。cdrom.c 中實現的介面透過兩個通用結構進行,這些結構包含有關驅動程式功能以及驅動程式操作的特定驅動器的資訊。這些結構是:

cdrom_device_ops

此結構包含有關 CD-ROM 裝置低階驅動程式的資訊。此結構在概念上與裝置的主編號相關聯(儘管某些驅動程式可能具有不同的主編號,IDE 驅動程式就是這種情況)。

cdrom_device_info

此結構包含有關特定 CD-ROM 驅動器的資訊,例如其裝置名稱、速度等。此結構在概念上與裝置的次編號相關聯。

透過呼叫以下函式,低階裝置驅動程式將特定 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 裝置時,它透過此結構中的函式進行。不可能知道未來 CD-ROM 驅動器的所有功能,因此預計隨著新技術的開發,此列表可能需要不時擴充套件。例如,CD-R 和 CD-R/W 驅動器正變得流行,很快就需要為它們新增支援。目前,當前的 struct 是:

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 *);
};

當低階裝置驅動程式實現其中一項功能時,它應向此 struct 新增一個函式指標。但是,如果未實現特定函式,則此 struct 應包含一個 NULL。當 CD-ROM 驅動器註冊到統一 CD-ROM 驅動程式時,capability 標誌指定 CD-ROM 硬體和/或低階 CD-ROM 驅動程式的功能。

請注意,大多數函式的引數少於其 blkdev_fops 對應項。這是因為 inodefile 結構中的資訊很少被使用。對於大多數驅動程式,主要引數是 struct cdrom_device_info,從中可以提取主編號和次編號。(不過,大多數低階 CD-ROM 驅動程式甚至不檢視主編號和次編號,因為它們許多隻支援一個裝置。)這將透過下面描述的 cdrom_device_info 中的 dev 可用。

當前,已註冊到 cdrom.c 的特定於驅動器、類似次裝置的資訊包含以下欄位:

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 (*exit)(struct cdrom_device_info *);/*  unknown:TBD */
      int mrw_mode_page;                      /*  which MRW mode page is in use */
};

使用此 struct,透過 next 欄位構建已註冊次裝置的連結串列。裝置號、裝置操作結構和驅動器屬性的規範儲存在此結構中。

如果特定驅動器不支援驅動器的某個功能,則可以使用 mask 標誌來遮蔽 ops->capability 中列出的一些功能。speed 值指定驅動器的最大磁頭速率,以普通音訊速度(176KB/秒原始資料或 150KB/秒檔案系統資料)為單位測量。引數宣告為 const,因為它們描述了驅動器的屬性,這些屬性在註冊後不會改變。

少數暫存器包含 CD-ROM 驅動器本地的變數。options 標誌用於指定通用 CD-ROM 例程的行為。這些各種標誌暫存器應提供足夠的靈活性以適應不同使用者的需求(而**不是**低階裝置驅動程式作者的“任意”需求,如舊方案中所示)。mc_flags 暫存器用於將 media_changed() 的資訊緩衝到兩個獨立的佇列中。特定於次要驅動器的其他資料可以透過 handle 訪問,該 handle 可以指向特定於低階驅動程式的資料結構。欄位 use_countnextoptionsmc_flags 無需初始化。

cdrom.c 形成的中間軟體層將執行一些額外的簿記。裝置的利用計數(開啟裝置的程序數)註冊在 use_count 中。函式 cdrom_ioctl() 將驗證讀寫操作的適當使用者記憶體區域,並且在傳輸 CD 上的某個位置時,它將透過以標準格式向低階驅動程式發出請求,並在使用者軟體和低階驅動程式之間轉換所有格式來“清理”格式。這大大減輕了驅動程式的記憶體檢查、格式檢查和轉換工作。此外,必要的結構將在程式堆疊上宣告。

函式的實現應按照以下部分中的定義進行。必須實現兩個函式,即 open()release()。其他函式可以省略,它們對應的功能標誌將在註冊時被清除。通常,函式成功時返回零,錯誤時返回負值。函式呼叫應在命令完成後才返回,但當然等待裝置不應占用處理器時間。

int open(struct cdrom_device_info *cdi, int purpose)

Open() 應嘗試以特定**目的**開啟裝置,目的可以是:

  • 為讀取資料而開啟,如 mount() (2) 或使用者命令 ddcat 所做。

  • ioctl 命令而開啟,如音訊 CD 播放程式所做。

請注意,任何策略性程式碼(在 open() 時關閉托盤等)都由 cdrom.c 中的呼叫例程完成,因此低階例程只需關注正確的初始化,例如啟動光碟等。

void release(struct cdrom_device_info *cdi)

應執行裝置特定的操作,例如使裝置停轉。然而,策略性的操作,例如彈出托盤或解鎖艙門,應留給通用例程 cdrom_release()。這是唯一返回型別為 void 的函式。

int drive_status(struct cdrom_device_info *cdi, int slot_nr)

如果實現,函式 drive_status 應提供有關驅動器狀態的資訊(而不是光碟狀態,光碟可能在驅動器中也可能不在)。如果驅動器不是換碟機,則應忽略 slot_nr。在 cdrom.h 中列出了可能性:

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)

此函式(沒有其他程式碼)如果實現,應控制托盤的移動。(沒有其他函式應該控制此功能。) 引數 position 控制期望的移動方向。

  • 0 關閉托盤

  • 1 開啟托盤

此函式成功時返回 0,錯誤時返回非零值。請注意,如果托盤已處於所需位置,則無需採取任何操作,並且返回值應為 0。

int lock_door(struct cdrom_device_info *cdi, int lock)

此函式(而不是其他程式碼)控制艙門的鎖定,如果驅動器允許的話。lock 的值控制期望的鎖定狀態:

  • 0 解鎖艙門,允許手動開啟

  • 1 鎖定艙門,無法手動彈出托盤

此函式成功時返回 0,錯誤時返回非零值。請注意,如果門已處於請求狀態,則無需採取任何操作,並且返回值應為 0。

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

某些 CD-ROM 驅動器能夠改變其磁頭速度。改變 CD-ROM 驅動器速度有幾個原因。壓制不良的 CD-ROM 可能會受益於低於最大磁頭速率。現代 CD-ROM 驅動器可以獲得非常高的磁頭速率(高達 24x 很常見)。據報道,這些驅動器在這些高速下會產生讀取錯誤,降低速度可以防止在這種情況下資料丟失。最後,其中一些驅動器會發出惱人的大噪音,較低的速度可以減少這種噪音。

此函式指定讀取資料或播放音訊的速度。speed 值指定驅動器的磁頭速度,以標準 CD-ROM 速度(176kB/秒原始資料或 150kB/秒檔案系統資料)為單位測量。因此,要請求 CD-ROM 驅動器以 300kB/秒的速度執行,您將呼叫 CDROM_SELECT_SPEED ioctl 並設定 speed=2。特殊值“0”表示“自動選擇”,即最大資料速率或即時音訊速率。如果驅動器沒有此“自動選擇”功能,則應根據當前載入的光碟做出決定,並且返回值應為正。負返回值表示錯誤。

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

此函式應實現舊的相應 ioctl()。對於裝置 cdi->dev,當前光碟的最後一次會話的開始應在指標引數 ms_info 中返回。請注意,cdrom.c 中的例程已對該引數進行了淨化:無論呼叫軟體請求的格式如何,其請求的格式**始終**為 CDROM_LBA 型別(線性塊定址模式)。但淨化甚至更進一步:如果低階實現願意(當然,適當設定 ms_info->addr_format 欄位),它可以以 CDROM_MSF 格式返回請求的資訊,並且 cdrom.c 中的例程將在必要時進行轉換。成功時返回值為 0。

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

一些光碟帶有“媒體目錄號”(MCN),也稱為“通用產品程式碼”(UPC)。此編號應反映通常在產品條形碼上找到的編號。不幸的是,少數光碟上帶有此類編號的光碟甚至不使用相同的格式。此函式的返回引數是指向型別為 struct cdrom_mcn 的預宣告記憶體區域的指標。MCN 預期為 13 個字元的字串,以空字元終止。

int reset(struct cdrom_device_info *cdi)

此呼叫應執行對驅動器的硬復位(儘管在需要硬復位的情況下,驅動器很可能不再響應命令)。優選地,控制應在驅動器完成復位後才返回給呼叫者。如果驅動器不再響應,底層低階 cdrom 驅動器超時可能是明智之舉。

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

cdrom.h 中定義的一些 CD-ROM-ioctl() 可以透過上述例程實現,因此函式 cdrom_ioctl 將使用它們。然而,大多數 ioctl() 處理音訊控制。我們已決定將這些操作透過單個函式訪問,重複引數 cmdarg。請注意,後者是 void 型別,而不是 unsigned long int。然而,例程 cdrom_ioctl() 確實做了一些有用的事情。它將所有音訊呼叫的地址格式型別標準化為 CDROM_MSF(分、秒、幀)。它還驗證 arg 的記憶體位置,併為引數保留堆疊記憶體。這使得 audio_ioctl() 的實現比舊驅動程式方案簡單得多。例如,您可以檢視應根據本文件更新的 cm206.c 中的函式 cm206_audio_ioctl()

未實現的 ioctl 應返回 -ENOSYS,但無害的請求(例如,CDROMSTART)可以透過返回 0(成功)來忽略。其他錯誤應符合標準,無論它們是什麼。當低階驅動程式返回錯誤時,統一 CD-ROM 驅動程式會盡可能地將錯誤程式碼返回給呼叫程式。(儘管我們可能決定在 cdrom_ioctl() 中清理返回值,以保證音訊播放器軟體的統一介面。)

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

有些 ioctl() 似乎是特定於某些 CD-ROM 驅動器的。也就是說,它們是為了服務某些驅動器的某些功能而引入的。事實上,有 6 種不同的 ioctl() 用於讀取資料,無論是某種特定格式還是音訊資料。支援將音訊軌道作為資料讀取的驅動器不多,我認為這是因為藝術家版權保護的原因。此外,我認為如果支援音訊軌道,它應該透過 VFS 而不是透過 ioctl() 來完成。這裡可能存在一個問題是音訊幀長 2352 位元組,因此音訊檔案系統要麼一次請求 75264 位元組(512 和 2352 的最小公倍數),要麼驅動程式應克服這種不一致(對此我持反對態度)。此外,硬體很難找到精確的幀邊界,因為音訊幀中沒有同步頭。一旦這些問題得到解決,此程式碼應在 cdrom.c 中標準化。

因為有太多的 ioctl() 似乎是為了滿足某些驅動程式而引入的 [2],所以任何非標準 ioctl() 都會透過 dev_ioctl() 呼叫進行路由。原則上,“私有” ioctl() 應該在其裝置的主裝置號之後編號,而不是通用 CD-ROM ioctl0x53。目前不支援的 ioctl() 包括:

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

CD-ROM 功能

除了僅僅實現一些 ioctl 呼叫之外,cdrom.c 中的介面還提供了指示 CD-ROM 驅動器**能力**的可能性。這可以透過在註冊階段對 cdrom.h 中定義的任意數量的功能常量進行邏輯或運算來實現。目前,功能可以是以下任何一項:

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 */

功能標誌被宣告為 const,以防止驅動程式意外篡改內容。功能標誌實際上通知 cdrom.c 驅動程式能做什麼。如果驅動程式找到的驅動器不具備該功能,可以透過 cdrom_device_info 變數 mask 將其遮蔽掉。例如,SCSI CD-ROM 驅動程式已經實現了載入和彈出 CD-ROM 的程式碼,因此其在 capability 中的相應標誌將被設定。但是 SCSI CD-ROM 驅動器可能是一個光碟盒系統,無法載入托盤,因此對於該驅動器,cdrom_device_info 結構將設定 mask 中的 CDC_CLOSE_TRAY 位。

在檔案 cdrom.c 中,您會遇到許多類似以下型別的結構:

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

沒有 ioctl 來設定掩碼... 原因是我認為控制**行為**比控制**能力**更好。

選項

最後一個標誌暫存器控制 CD-ROM 驅動器的**行為**,以滿足不同使用者的需求,希望這能獨立於為 Linux 社群提供驅動器支援的各自作者的想法。當前的控制行為選項有:

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 */

此暫存器的初始值為 CDO_AUTO_CLOSE | CDO_USE_FFLAGS | CDO_LOCK,反映了我對使用者介面和軟體標準的看法。在您抗議之前,cdrom.c 中實現了兩個新的 ioctl(),允許您透過軟體控制行為。它們是:

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

有一個選項需要更多解釋:CDO_USE_FFLAGS。在下一個新章節中,我們將解釋為什麼需要此選項。

一個名為 setcd 的軟體包,可從 Debian 發行版和 sunsite.unc.edu 獲取,允許使用者級別控制這些標誌。

需要知道開啟 CD-ROM 裝置的目的

傳統上,Unix 裝置可以透過兩種不同的“模式”使用,要麼透過讀/寫裝置檔案,要麼透過裝置的 ioctl() 呼叫向裝置發出控制命令。CD-ROM 驅動器的問題在於,它們可以用於兩個完全不同的目的。一個是掛載可移動檔案系統,即 CD-ROM,另一個是播放音訊 CD。音訊命令完全透過 ioctl() 實現,大概是因為第一個實現(SUN?)就是如此。原則上這沒有錯,但要很好地控制“CD 播放器”,要求裝置**始終**可以開啟,以便發出 ioctl 命令,無論驅動器處於什麼狀態。

另一方面,當用作可移動介質光碟驅動器(CD-ROM 的最初目的)時,我們希望確保光碟驅動器在開啟裝置時已準備好操作。在舊方案中,一些 CD-ROM 驅動器不進行任何完整性檢查,導致在嘗試將 CD-ROM 掛載到空驅動器時,VFS 向核心報告大量 I/O 錯誤。這不是一種特別優雅的方法來發現沒有插入 CD-ROM;它或多或少看起來像舊的 IBM-PC 嘗試從空的軟盤驅動器讀取幾秒鐘,之後系統抱怨無法從中讀取。如今,我們可以**感知**驅動器中是否存在可移動介質,我們相信我們應該利用這個事實。在開啟裝置時進行完整性檢查,驗證 CD-ROM 的可用性及其正確型別(資料),將是可取的。

這兩種使用 CD-ROM 驅動器的方式,主要是用於資料,其次是用於播放音訊光碟,對 open() 呼叫的行為有不同的要求。音訊使用只是想開啟裝置以獲取檔案控制代碼,該控制代碼是發出 ioctl 命令所必需的,而資料使用則希望開啟以進行正確和可靠的資料傳輸。使用者程式指示其開啟裝置**目的**的唯一方法是透過 flags 引數(參見 open(2))。對於 CD-ROM 裝置,這些標誌未實現(某些驅動程式實現了對寫入相關標誌的檢查,但如果裝置檔案具有正確的許可權標誌,則這不是嚴格必要的)。大多數選項標誌對 CD-ROM 裝置來說根本沒有意義:O_CREATO_NOCTTYO_TRUNCO_APPENDO_SYNC 對 CD-ROM 沒有任何意義。

因此,我們建議使用標誌 O_NONBLOCK 來表示裝置僅為發出 ioctl 命令而開啟。嚴格來說,O_NONBLOCK 的含義是開啟裝置和隨後的呼叫不會導致呼叫程序等待。我們可以將其解釋為不要等待直到有人插入了有效的資料 CD-ROM。因此,我們對 CD-ROM 的 open() 呼叫的實現建議如下:

  • 如果除了 O_RDONLY 之外沒有設定其他標誌,則裝置將開啟以進行資料傳輸,並且僅在成功初始化傳輸後返回值為 0。此呼叫甚至可能在 CD-ROM 上引發一些操作,例如關閉托盤。

  • 如果設定了 O_NONBLOCK 選項標誌,則除非整個裝置不存在,否則開啟將始終成功。驅動器將不採取任何行動。

那標準呢?

您可能會猶豫是否接受這份提案,因為它來自 Linux 社群,而不是某個標準化機構。那麼 SUN、SGI、HP 以及所有其他 Unix 和硬體廠商呢?嗯,這些公司處於幸運的境地,他們通常可以控制其支援產品的硬體和軟體,並且足夠大,可以制定自己的標準。他們不必處理十幾種或更多不同的、相互競爭的硬體配置[3]

我們相信,在 Linux 社群中,使用 O_NONBLOCK 來指示裝置僅用於 ioctl 命令可以很容易地引入。所有 CD 播放器作者都必須被告知,我們甚至可以向程式傳送我們自己的補丁。使用 O_NONBLOCK 很可能不會影響 CD 播放器在 Linux 以外的其他作業系統上的行為。最後,使用者總是可以透過呼叫 ioctl(file_descriptor, CDROM_CLEAR_OPTIONS, CDO_USE_FFLAGS) 來恢復舊行為。

open() 的首選策略

cdrom.c 中的例程設計成能夠透過 CDROM_SET/CLEAR_OPTIONS ioctls 來實現 CD-ROM 裝置(**任何**型別)的執行時行為配置。因此,可以設定各種操作模式:

CDO_AUTO_CLOSE | CDO_USE_FFLAGS | CDO_LOCK

這是預設設定。(將來,有了 CDO_CHECK_TYPE 會更好。)如果裝置尚未被任何其他程序開啟,並且裝置正在為資料傳輸而開啟(O_NONBLOCK 未設定),並且發現托盤是開啟的,則會嘗試關閉托盤。然後,驗證驅動器中是否有光碟,並且如果設定了 CDO_CHECK_TYPE,則驗證其是否包含“資料模式 1”型別的軌道。只有所有測試都透過,返回值才為零。門被鎖定以防止檔案系統損壞。如果驅動器是為音訊而開啟(O_NONBLOCK 已設定),則不採取任何操作,並返回 0 值。

CDO_AUTO_CLOSE | CDO_AUTO_EJECT | CDO_LOCK

這模仿了當前 sbpcd 驅動程式的行為。選項標誌被忽略,如果需要,托盤在第一次開啟時關閉。類似地,托盤在最後一次釋放時開啟,即,如果 CD-ROM 被解除安裝,它會自動彈出,以便使用者可以更換它。

我們希望這些選項能說服所有人(驅動程式維護者和使用者程式開發人員)採納新的 CD-ROM 驅動程式方案和選項標誌解釋。

cdrom.c 中例程的描述

cdrom.c 中只有少數例程匯出給驅動程式。在本節中,我們將討論這些例程,以及接管 CD-ROM 與核心介面的函式。cdrom.c 對應的標頭檔案名為 cdrom.h。以前,此檔案的一些內容放在 ucdrom.h 檔案中,但該檔案現在已合併回 cdrom.h

struct file_operations cdrom_fops

此結構的內容在 cdrom_api 中描述。指向此結構的指標被分配給 struct gendiskfops 欄位。

int register_cdrom(struct cdrom_device_info *cdi)

此函式的使用方式與向核心註冊 cdrom_fops 大致相同,裝置操作和資訊結構,如 cdrom_api 中所述,應註冊到統一 CD-ROM 驅動程式:

register_cdrom(&<device>_info);

此函式成功時返回零,失敗時返回非零。結構 <device>_info 應包含指向驅動程式的 <device>_dops 的指標,如:

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

請注意,一個驅動程式必須有一個靜態結構 <device>_dops,而它可以有任意數量的 <device>_info 結構,只要有活動的次裝置。Register_cdrom() 從這些結構構建一個連結串列。

void unregister_cdrom(struct cdrom_device_info *cdi)

取消註冊次裝置號為 MINOR(cdi->dev) 的裝置 cdi 會將該次裝置從列表中移除。如果它是低階驅動程式註冊的最後一個次裝置,則這將斷開已註冊的裝置操作例程與 CD-ROM 介面的連線。此函式成功時返回零,失敗時返回非零。

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

此函式不是由低階驅動程式直接呼叫,它列在標準 cdrom_fops 中。如果 VFS 開啟檔案,此函式將啟用。此例程中實現了一種策略,處理裝置中設定的所有功能和選項,這些功能和選項與 cdrom_device_ops 相關聯。然後,程式流程轉移到裝置相關的 open() 呼叫。

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

此函式實現了 cdrom_open() 的逆向邏輯,然後呼叫裝置相關的 release() 例程。當使用計數達到 0 時,透過呼叫 sync_dev(dev)invalidate_buffers(dev) 來重新整理已分配的緩衝區。

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

此函式以統一的方式處理所有標準的 CD-ROM 裝置 ioctl 請求。不同的呼叫分為三類:可由裝置操作直接實現的 ioctl();透過 audio_ioctl() 呼叫路由的 ioctl();以及其餘的(可能是裝置相關的)ioctl()。通常,負返回值表示錯誤。

直接實現的 ioctl()

如果實現且未被遮蔽,以下“舊”CD-ROM ioctl() 透過直接呼叫 cdrom_device_ops 中的裝置操作來實施:

CDROMMULTISESSION

請求 CD-ROM 上的最後一個會話。

CDROMEJECT

開啟托盤。

CDROMCLOSETRAY

關閉托盤。

CDROMEJECT_SW

如果 argnot=0,則將行為設定為自動關閉(第一次開啟時關閉托盤)和自動彈出(最後一次釋放時彈出),否則將行為設定為在 open()release() 呼叫時不移動。

CDROM_GET_MCN

從 CD 獲取媒體目錄號。

Ioctl*s 透過 *audio_ioctl() 路由

以下一組 ioctl() 都透過呼叫 cdrom_fops 函式 audio_ioctl() 來實現。記憶體檢查和分配在 cdrom_ioctl() 中執行,並且還對地址格式(CDROM_LBA/CDROM_MSF)進行淨化。

CDROMSUBCHNL

獲取型別為 struct cdrom_subchnl * 的引數 arg 中的子通道資料。

CDROMREADTOCHDR

讀取目錄表頭,在型別為 struct cdrom_tochdr *arg 中。

CDROMREADTOCENTRY

讀取目錄表條目,在型別為 struct cdrom_tocentry *arg 中指定。

CDROMPLAYMSF

播放由型別為 struct cdrom_msf *arg 定界的分鐘、秒、幀格式的音訊片段。

CDROMPLAYTRKIND

播放由型別為 struct cdrom_ti *arg 定界的音軌-索引格式的音訊片段。

CDROMVOLCTRL

設定由型別為 struct cdrom_volctrl *arg 指定的音量。

CDROMVOLREAD

讀取音量,透過型別為 struct cdrom_volctrl *arg

CDROMSTART

啟動光碟。

CDROMSTOP

停止播放音訊片段。

CDROMPAUSE

暫停播放音訊片段。

CDROMRESUME

恢復播放。

cdrom.c 中的新 ioctl()

以下 ioctl() 已被引入,以允許使用者程式控制單個 CD-ROM 裝置的行為。新的 ioctl 命令可以透過其名稱中的下劃線進行識別。

CDROM_SET_OPTIONS

設定由 arg 指定的選項。返回修改後的選項標誌暫存器。使用 arg = rm0 讀取當前標誌。

CDROM_CLEAR_OPTIONS

清除由 arg 指定的選項。返回修改後的選項標誌暫存器。

CDROM_SELECT_SPEED

選擇光碟的磁頭速率,由 arg 指定,以標準 cdrom 速度(176KB/秒原始資料或 150KB/秒檔案系統資料)為單位。值為 0 表示“自動選擇”,即即時播放音訊光碟,以最大速度播放資料光碟。arg 的值會與 cdrom_dops 中找到的驅動器的最大磁頭速率進行檢查。

CDROM_SELECT_DISC

從自動點唱機中選擇編號為 arg 的光碟。

第一張光碟編號為 0。arg 的值將與 cdrom_dops 中找到的自動點唱機中光碟的最大數量進行檢查。

CDROM_MEDIA_CHANGED

如果自上次呼叫以來光碟已更改,則返回 1。對於自動點唱機,額外的引數 arg 指定了提供資訊的插槽。特殊值 CDSL_CURRENT 請求返回有關當前選定插槽的資訊。

CDROM_TIMED_MEDIA_CHANGE

檢查自使用者提供的時間以來光碟是否已更改,並返回上次光碟更改的時間。

arg 是指向 cdrom_timed_media_change_info 結構的指標。arg->last_media_change 可由呼叫程式碼設定,以表示上次已知媒體更改的時間戳(由呼叫者)。成功返回後,此 ioctl 呼叫將把 arg->last_media_change 設定為核心/驅動器知道的最新媒體更改時間戳(以毫秒為單位),如果該時間戳比呼叫者設定的時間戳更近,則將 arg->has_changed 設定為 1。

CDROM_DRIVE_STATUS

透過呼叫 drive_status() 返回驅動器狀態。返回值在 cdrom_drive_status 中定義。請注意,此呼叫不返回有關驅動器當前播放活動的資訊;這可以透過 CDROMSUBCHNLioctl 呼叫進行輪詢。對於點唱機,額外的引數 arg 指定了(可能受限的)資訊所對應的插槽。特殊值 CDSL_CURRENT 請求返回有關當前選定插槽的資訊。

CDROM_DISC_STATUS

返回當前在驅動器中的光碟型別。它應被視為 CDROM_DRIVE_STATUS 的補充。此 ioctl 可以提供有關當前插入驅動器的光碟的**一些**資訊。此功能以前在低階驅動程式中實現,但現在完全在統一 CD-ROM 驅動程式中執行。

CD 作為各種數字資訊載體的開發歷史導致了許多不同的光碟型別。此 ioctl 僅在 CD 僅包含**一種**資料型別時才有用。雖然這種情況經常發生,但 CD 上有資料軌道和音訊軌道的情況也很常見。由於這是一個現有介面,而不是透過改變其建立時的假設來修復此介面,從而破壞所有使用此功能的使用者應用程式,統一 CD-ROM 驅動程式實現了此 ioctl 如下:如果所討論的 CD 上有音訊軌道,並且絕對沒有 CD-I、XA 或資料軌道,則會報告為 CDS_AUDIO。如果它同時有音訊和資料軌道,則會返回 CDS_MIXED。如果光碟上沒有音訊軌道,並且所討論的 CD 上有任何 CD-I 軌道,則會報告為 CDS_XA_2_2。如果失敗,如果所討論的 CD 上有任何 XA 軌道,則會報告為 CDS_XA_2_1。最後,如果所討論的 CD 上有任何資料軌道,則會報告為資料 CD (CDS_DATA_1)。

ioctl 可以返回:

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 */

有關各種光碟型別的幀佈局的一些資訊,請參閱最新版本的 cdrom.h

CDROM_CHANGER_NSLOTS

返回自動點唱機中的插槽數。

CDROMRESET

重置驅動器。

CDROM_GET_CAPABILITY

返回驅動器的 capability 標誌。有關這些標誌的更多資訊,請參閱 cdrom_capabilities 部分。

CDROM_LOCKDOOR

鎖定驅動器艙門。arg == 0 解鎖艙門,其他任何值鎖定艙門。

CDROM_DEBUG

開啟除錯資訊。只有 root 允許這樣做。與 CDROM_LOCKDOOR 語義相同。

裝置相關的 ioctl()

最後,所有其他 ioctl() 都(如果已實現)傳遞給函式 dev_ioctl()。不執行記憶體分配或驗證。

如何更新您的驅動程式

  • 備份您當前的驅動程式。

  • 獲取 cdrom.ccdrom.h 檔案,它們應位於本文件隨附的目錄樹中。

  • 確保您包含 cdrom.h

  • register_blkdev 的第三個引數從 &<your-drive>_fops 更改為 &cdrom_fops

  • 緊接著那一行,新增以下程式碼以註冊到統一 CD-ROM 驅動程式:

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

    同樣,在適當位置新增對 unregister_cdrom() 的呼叫。

  • 將裝置操作 struct 的示例複製到您的原始碼中,例如從 cm206.ccm206_dops 中複製,並將所有條目更改為與您的驅動程式對應的名稱,或者您喜歡的名稱。如果您的驅動程式不支援某個功能,則將該條目設定為 NULL。在 capability 條目中,您應該列出您的驅動程式當前支援的所有功能。如果您的驅動程式具有未列出的功能,請給我傳送訊息。

  • 從相同的示例驅動程式中複製 cdrom_device_info 宣告,並根據您的需求修改條目。如果您的驅動程式動態確定硬體功能,則此結構也應動態宣告。

  • 根據 cdrom.h 中列出的原型和 cdrom_api 中給出的規範,在您的 <device>_dops 結構中實現所有函式。您很可能已經實現了大部分程式碼,並且您幾乎肯定需要調整原型和返回值。

  • 將您的 <device>_ioctl() 函式重新命名為 audio_ioctl 並稍微更改原型。刪除 cdrom_ioctl 第一部分中列出的條目,如果您的程式碼正常,這些只是對您在上一步中調整的例程的呼叫。

  • 您可以刪除 audio_ioctl() 函式中所有剩餘的處理音訊命令的記憶體檢查程式碼(這些在 cdrom_ioctl 的第二部分列出)。也不需要記憶體分配,因此 switch 語句中的大多數 case*s 看起來與以下類似:

    case CDROMREADTOCENTRY:
            get_toc_entry\bigl((struct cdrom_tocentry *) arg);
    
  • 所有剩餘的 ioctl 情況必須移到一個單獨的函式 <device>_ioctl 中,即裝置相關的 ioctl()。請注意,記憶體檢查和分配必須保留在此程式碼中!

  • 更改 <device>_open()<device>_release() 的原型,並移除任何策略性程式碼(即托盤移動、門鎖定等)。

  • 嘗試重新編譯驅動程式。我們建議您同時使用 cdrom.o 和您的驅動程式的模組,因為這樣除錯更容易。

鳴謝

感謝所有參與人員。首先,Erik Andersen,他接過了維護 cdrom.c 並將許多 CD-ROM 相關程式碼整合到 2.1 核心中的火炬。感謝 Scott Snyder 和 Gerd Knorr,他們是第一個為 SCSI 和 IDE-CD 驅動程式實現此介面併為核心 2.0 相關資料結構的擴充套件添加了許多想法。進一步感謝 Heiko Eißfeldt、Thomas Quinot、Jon Tombs、Ken Pizzini、Eberhard Mönkeberg 和 Andrew Kroll,這些 Linux CD-ROM 裝置驅動程式開發人員在編寫過程中慷慨地提供了建議和批評。最後,當然,我要感謝 Linus Torvalds,他首先使這一切成為可能。