CDemu で CD-ROMイメージをマウントする

CDemuとは

Windowsでは daemontoolsが有名ですが
それと同様の機能を提供します
  • 仮想ドライブ /dev/sr〜 の提供
  • b6t,c2d,ccd,cdi,cif,cue,daa,iso,mds,nrg,toc 形式のサポート
  • CDオーディオのサポート

パッケージ構成

パッケージ構成の説明
cdemu-daemon CDemuのサーバ クライアントからの制御を受け付ける
cdemu-client コマンドラインのフロントエンド 仮想イメージの追加/削除をサーバに要求する
VHBA カーネルモジュール /dev/sr〜 の処理を行う /dev/vhba_ctl をサポート
libdaemon 常駐機能を実装するライブラリ
libmirage CD/DVDのイメージファイルを処理するライブラリ

このブログでは CDemuのソースコードからのインストールの注意点と 利用方法の概要説明 CDemu動作の問題点とその解決方法について記載しています

インストール

検証時の構成
CPU AMD Turion64 MT-37 (2GHz)
OS Linux-2.6.30.5
DVDドライブ /DC-DW1670
ATAPI内蔵
DVD-RW x6, DVD+RW x8, DVD-R x16, DVD+R x16
DVD-RAM x5, CD-R x48, CD-RW x32
コンパイル環境 GCC-4.1.2(i686), GLIBC-2.5
D-Bus dbus-1.0.2
VHBA vhba-module-1.2.1
libmirage libmirage-1.2.0
cdemu-daemon cdemu-daemon-1.2.0
cdemu-client cdemu-client-1.2.0
最終検証日 2010-5-23
ここでは cdemuのインストールについて 注意点等を記載します

libdaemon
libmirage については
ソースコードを展開して ./configure → make → make install (root権限で)
でインストールできました

vhba-module については
ソースコードを展開後 以下の手順でインストールします

$ make modules
$ su
# make module_install
 

ただし (Linuxの古いバージョン等) 以下のエラーが発生する場合

CC [M]  〜/vhba-module-1.0.0/vhba.o
〜/vhba-module-1.0.0/vhba.c:793:51: error: マクロ "INIT_WORK" は引数を 3 要求しますが、2 個しか与えられていません
 

INIT_WORKマクロの仕様が linuxカーネル側で修正になっているので vhba.c 793行目 を修正します

INIT_WORK(&vhost->scan_devices, vhba_scan_devices);
↓
INIT_WORK(&vhost->scan_devices, vhba_scan_devices, &vhost->scan_devices);

また 少なくとも kernel-2.6.24 以降では CFLAGSの設定でエラーが出るようです

make -C /lib/modules/`uname -r`/build M=/home/admin/vhba-module-1.0.0 modules
make[1]: Entering directory `/usr/src/linux-2.6.24'
scripts/Makefile.build:46: *** CFLAGS was changed in "/home/admin/vhba-module-1.0.0/Makefile". Fix it to use EXTRA_CFLAGS。中止。
make[1]: *** [_module_/home/admin/vhba-module-1.0.0] エラー 2
make[1]: Leaving directory `/usr/src/linux-2.6.24'
make: *** [modules] エラー 2
 

CFLAGS を EXTRA_CFLAGS に変えてくださいとのエラーのように見えます Makefile を以下のように修正します

2 PACKAGE = vhba-module-$(VHBA_VERSION)
3 
4 CFLAGS += -DVHBA_VERSION=\"$(VHBA_VERSION)\"
5 
6 obj-m += vhba.o
↓
2 PACKAGE = vhba-module-$(VHBA_VERSION)
3 
4 EXTRA_CFLAGS += -DVHBA_VERSION=\"$(VHBA_VERSION)\"
5 
6 obj-m += vhba.o
 

さらに以下のようなエラーが出る場合

make -C /lib/modules/`uname -r`/build M=/home/admin/vhba-module-1.0.0 modules
make[1]: Entering directory `/usr/src/linux-2.6.24'
  CC [M]  /home/admin/vhba-module-1.0.0/vhba.o
/home/admin/vhba-module-1.0.0/vhba.c: In function 'do_response':
/home/admin/vhba-module-1.0.0/vhba.c:523: error: 'struct scatterlist' has no member named 'page'
make[2]: *** [/home/admin/vhba-module-1.0.0/vhba.o] エラー 1
make[1]: *** [_module_/home/admin/vhba-module-1.0.0] エラー 2
make[1]: Leaving directory `/usr/src/linux-2.6.24'
make: *** [modules] エラー 2
 

少なくともカーネル 2.4.24以降の struct scatter_list のメンバ要素が仕様修正されてるようで vhba.c に以下の修正を加えました

522 
523             kaddr = kmap_atomic(sg[i].page, KM_USER0);
524             memcpy(kaddr + sg[i].offset, kbuf, len);
525             kunmap_atomic(kaddr, KM_USER0);
526 
↓
522 
523             kaddr = kmap_atomic(sg[i].page_link, KM_USER0);
524             memcpy(kaddr + sg[i].offset, kbuf, len);
525             kunmap_atomic(kaddr, KM_USER0);
526 
 

また udevのルール設定に追加が必要です 次のような 1行を追加しておきます

KERNEL=="vhba_ctl", NAME="%k", MODE="0660", OWNER="root", GROUP="users"
 

インストールがうまくいったら モジュールがロードできるようになります

# insmod /lib/modules/〜/extra/vhba.ko
# lsmod
Module                  Size  Used by
vhba                    7168  0 
nvidia               6816276  22 
 

vhba.ko は Linuxのシステム起動時に自動的に読み込まれるように設定しておくと便利です

次に cdemu-daemon 本体をインストールします ソースコードを展開後

$ ./configure --with-dbus-system-dir=/usr/local/etc/dbus-1/system.d
〜
 cdemu-daemon 1.2.0 configure summary:
  Prefix:                    /usr/local
  dbus-1 system.d dir:       /usr/local/etc/dbus-1/system.d
  C Compiler:                gcc -std=gnu99
  CFLAGS:                    -g -O2
$ make
$ su
# make install
 

–with-dbus-system-dir の設定は環境によっては /etc/dbus-1/system.d かもしれません
インストールが完了すれば cdemud のコマンドが使えるようになっています

最後に cdemu-client をインストールします ソースコードを展開後

$ make
$ su
# make install
 

インストール後は cdemu コマンドが使えるようになります

利用方法

まず 前提が 2つあり
1つ目は VHBAのカーネルモジュールがロードされている必要があるということです
(lsmodで確認 なければ modprobe vhba コマンドでロード完了です)
2つ目は D-Busも正しく起動して常駐されている必要があるということです
(ps -axで確認 なければ root権限にて dbus-daemon –system で起動されるか試してください)

いよいよ本番ですが cdemud を常駐させます rootのコンソールを 1枚立ち上げて

# cdemud -a alsa
 - num devices: 1
 - ctl device: /dev/vhba_ctl
 - audio driver: alsa
 - bus type: system
 

とりあえず フロントエンドで常駐した状態になります

  • num_devices は cdemud に -n オプションを付けることで指定できる
  • バックエンドで常駐するには -d をつければよいはずですが 手元の環境では確認できませんでした
  • audio_backend は -a オプションで指定します libaoが適切に導入されている必要があります

cdemudが起動することで /dev/sr0 が 仮想CDデバイスとして見えるようになるはずです

以降は主に cdemu コマンドで CDイメージを操作することになります
まずは イメージのロードから

# cdemu load 0 ~/test.cue
#
 

上記コマンドでは test.cue のキューシートを指定して読み込む指定です
何事もエラーがでなければ OKです /dev/sr0 に CDイメージがセットされているはずです

仮想CDデバイスの利用状況は以下のように確認できます

# cdemu status
Devices status:
DEV   LOADED     TYPE       FILENAME
0     1          PARSER-CUE /root/test.cue
 

では 実際にロードしたイメージが 通常のCDデバイスと同じく扱えるか確認してみます

# mount -t iso9660 -o ro /dev/sr0 /mnt/cdrom
# ls /mnt/cdrom
AUTORUN.INF              Setup.exe     congrats.avi  layout.bin
Autorun.exe              _INST32I.EX_  credits.avi   lemmings.box
DATA.TAG                 _ISDel.exe    data1.cab     os.dat
DirectX7.0a              _Setup.dll    data1.hdr     setup.bmp
Intel Indeo Readme.txt   _sys1.cab     delreg.exe    setup.ins
Lemmings Revolution.exe  _sys1.hdr     failed.avi    setup.lid
Patch.box                _user1.cab    frontend.avi  splash256.bmp
SETUP.INI                _user1.hdr    gameover.avi
SavedGame.dat            complete.avi  lang.dat
 

Windows用のゲームの CD-ROMをマウントした結果 正しくファイルシステムが見えていることが分かります

イメージを利用し終えたら /dev/sr0 での利用を次のように解除します

# umount /mnt/cdrom
# cdemu unload 0
 

以上が CDemuの基本的な流れとなります
cdemu load と cdemu unload コマンドで LinuxにおいてもCDイメージが扱え非常に便利です

注意点としては cdemu load する時のイメージパスに日本語を含む場合は EUC-JP でなく UTF-8 で指定しないと受け付けてもらえず EUC-JPの環境では苦労することです

CDemuの問題について

検証環境にて CDemuを利用して遭遇した問題点について 解決できたのがあるので紹介します
問題点については以下のとおりです
  • CD-DAオーディオの問題 cdda-playerによる演奏がうまく鳴らない現象
  • データトラックへアクセスできない不具合 PC-EngineのCD-ROMなど特殊な構成で発生
まず CD-DAオーディオの問題ですが
下記のように cdda-player コマンドによる CD-DA演奏でどのトラックも音が鳴らず一瞬でトラック終了に達してしまいます

  $ cdda-player /dev/sr0
 

CDemu + cdda-player 失敗画面
実際は 一瞬音が鳴るのですが すぐに停止状態になってしまいます

ちなみに cdda-playerは libcdioパッケージの付属ツールです コマンドラインからCD演奏できます
cdemu-daemonでは 実際の CDデバイスに対する ioctl()処理をエミュレートする機能がありますが
その動作を解析した結果 問題箇所と回避策を見つけました

cdda-playerでは 演奏開始直後に CDデバイスに対して MODE SENSEコマンドを発行していたのです
MODE SENSEとは CDドライブの能力情報をドライブから取得するコマンドです
cdemuにおいては 下記のソースコード(cdemu-device.c)で

2770     } packet_commands[] = {
2771         { PC_GET_EVENT_STATUS_NOTIFICATION,
2772           "GET EVENT/STATUS NOTIFICATION",
2773           __cdemud_device_pc_get_event_status_notification,
2774           FALSE },
2775         { PC_GET_CONFIGURATION,
2776           "GET CONFIGURATION",
2777           __cdemud_device_pc_get_configuration,
2778           TRUE, },
2779         { PC_INQUIRY,
2780           "INQUIRY",
2781           __cdemud_device_pc_inquiry,
2782           FALSE },
2783         { PC_MODE_SELECT_6,
2784           "MODE SELECT (6)",
2785           __cdemud_device_pc_mode_select,
2786           TRUE },
2787         { PC_MODE_SELECT_10,
2788           "MODE SELECT (10)",
2789           __cdemud_device_pc_mode_select,
2790           TRUE },
2791         { PC_MODE_SENSE_6,
2792           "MODE SENSE (6)",
2793           __cdemud_device_pc_mode_sense,
2794           TRUE },
2795         { PC_MODE_SENSE_10,
2796           "MODE SENSE (10)",
2797           __cdemud_device_pc_mode_sense,
2798           TRUE },
2799         { PC_PAUSE_RESUME,
2800           "PAUSE/RESUME",
2801           __cdemud_device_pc_pause_resume,
2802           FALSE /* Well, it does... but in it's own, unique way :P */ },
 

各コマンドの 処理方法を定義していますが
それぞれ TRUE FALSE とある部分が コマンドを処理するときに CD-DAを停止するかどうかのフラグで
MODE_SENSE においては TRUE つまり 停止となっています

2792           "MODE SENSE (6)",
2793           __cdemud_device_pc_mode_sense,
2794           FALSE },
2795         { PC_MODE_SENSE_10,
2796           "MODE SENSE (10)",
2797           __cdemud_device_pc_mode_sense,
2798           FALSE },
 

上記のように FALSEに設定することで cdda-playerが正常に演奏されるようになりました

(実際のデバイスの振舞いと一致しているかどうかは未確認です
また cdda-player以外の再生環境では問題はでないかもしれません)

ちなみに CDemuでは 音声ファイルを libsndfileに任せているので
sndfile-play コマンドで演奏できるフォーマットは CUEシートに入力できます

FILE "test1.ogg" WAVE
 TRACK 1 AUDIO
 INDEX 1 00:00:00
FILE "test2.ogg" WAVE
 TRACK 2 AUDIO
 INDEX 1 00:00:00
 

上記のようなCUEシートで Oggのような圧縮音声形式もCDイメージ化でき大変コンパクトに収まります
(残念ながら mp3 は sndfileで扱ってなかったので 検証環境でCDイメージ化は不可でした)

次に データトラックへのアクセスの問題です
現象としては Mednafen(マルチゲームエミュレータ) + 「アルナムの牙」(PC-Engine CD-ROM2ゲーム)の CUEシートで全く動作しないという問題です
CUEシートは 以下のとおりです

FILE "track_01.wav" WAVE
 TRACK 1 AUDIO
 INDEX 1 00:00:00
FILE "track_02.bin" BINARY
 TRACK 2 MODE1/2352
 INDEX 1 00:00:00
FILE "track_03.wav" WAVE
 TRACK 3 AUDIO
 INDEX 1 00:00:00
FILE "track_04.wav" WAVE
 TRACK 4 AUDIO
 INDEX 1 00:00:00
FILE "track_05.wav" WAVE
 TRACK 5 AUDIO
 INDEX 1 00:00:00
FILE "track_06.wav" WAVE
 TRACK 6 AUDIO
 INDEX 1 00:00:00
FILE "track_07.wav" WAVE
 TRACK 7 AUDIO
 INDEX 1 00:00:00
FILE "track_08.wav" WAVE
 TRACK 8 AUDIO
 INDEX 1 00:00:00
FILE "track_09.wav" WAVE
 TRACK 9 AUDIO
 INDEX 1 00:00:00
FILE "track_10.wav" WAVE
 TRACK 10 AUDIO
 INDEX 1 00:00:00
FILE "track_11.wav" WAVE
 TRACK 11 AUDIO
 INDEX 1 00:00:00
FILE "track_12.wav" WAVE
 TRACK 12 AUDIO
 INDEX 1 00:00:00
FILE "track_13.wav" WAVE
 TRACK 13 AUDIO
 INDEX 1 00:00:00
FILE "track_14.wav" WAVE
 TRACK 14 AUDIO
 INDEX 1 00:00:00
FILE "track_15.wav" WAVE
 TRACK 15 AUDIO
 INDEX 1 00:00:00
FILE "track_16.wav" WAVE
 TRACK 16 AUDIO
 INDEX 1 00:00:00
FILE "track_17.wav" WAVE
 TRACK 17 AUDIO
 INDEX 1 00:00:00
FILE "track_18.wav" WAVE
 TRACK 18 AUDIO
 INDEX 1 00:00:00
FILE "track_19.wav" WAVE
 TRACK 19 AUDIO
 INDEX 1 00:00:00
FILE "track_20.wav" WAVE
 TRACK 20 AUDIO
 INDEX 1 00:00:00
FILE "track_21.wav" WAVE
 TRACK 21 AUDIO
 INDEX 1 00:00:00
FILE "track_22.wav" WAVE
 TRACK 22 AUDIO
 INDEX 1 00:00:00
FILE "track_23.wav" WAVE
 TRACK 23 AUDIO
 INDEX 1 00:00:00
FILE "track_24.wav" WAVE
 TRACK 24 AUDIO
 INDEX 1 00:00:00
FILE "track_25.wav" WAVE
 TRACK 25 AUDIO
 INDEX 1 00:00:00
FILE "track_26.wav" WAVE
 TRACK 26 AUDIO
 INDEX 1 00:00:00
FILE "track_27.wav" WAVE
 TRACK 27 AUDIO
 INDEX 1 00:00:00
FILE "track_28.wav" WAVE
 TRACK 28 AUDIO
 INDEX 1 00:00:00
FILE "track_29.wav" WAVE
 TRACK 29 AUDIO
 INDEX 1 00:00:00
FILE "track_30.wav" WAVE
 TRACK 30 AUDIO
 INDEX 1 00:00:00
FILE "track_31.wav" WAVE
 TRACK 31 AUDIO
 INDEX 1 00:00:00
FILE "track_32.wav" WAVE
 TRACK 32 AUDIO
 INDEX 1 00:00:00
FILE "track_33.wav" WAVE
 TRACK 33 AUDIO
 INDEX 1 00:00:00
FILE "track_34.wav" WAVE
 TRACK 34 AUDIO
 INDEX 1 00:00:00
FILE "track_35.wav" WAVE
 TRACK 35 AUDIO
 INDEX 1 00:00:00
FILE "track_36.wav" WAVE
 TRACK 36 AUDIO
 INDEX 1 00:00:00
FILE "track_37.wav" WAVE
 TRACK 37 AUDIO
 INDEX 1 00:00:00
FILE "track_38.wav" WAVE
 TRACK 38 AUDIO
 INDEX 1 00:00:00
FILE "track_39.wav" WAVE
 TRACK 39 AUDIO
 INDEX 1 00:00:00
FILE "track_40.wav" WAVE
 TRACK 40 AUDIO
 INDEX 1 00:00:00
FILE "track_41.wav" WAVE
 TRACK 41 AUDIO
 INDEX 1 00:00:00
FILE "track_42.wav" WAVE
 TRACK 42 AUDIO
 INDEX 1 00:00:00
 

PC-Engineの CDトラック構成は特殊で
1トラック目が音声(警告用) 2トラック目がデータ(プログラム) それ以降は 音声(CD-DA)
となっています ゲームによっては データトラックが最後に入ってくることもあります

Mednafenの起動時に固まってしまう問題で 問題のでないゲームもあり調査に時間がかかりました
結果 上記特殊トラック構成を扱う上での Linux(カーネル?)との相性問題にまで及んでいました

CUEシートのトラック構成は libmirageが扱っていますが 各トラックの開始セクタは次のようになっています

track01   start=-150   length=3743(sectors)    AUDIO  (2352bytes/sector)
track02   start=3593   length=45336(sectors)   BINARY (2352bytes/sector)
track03   start=48929  length=2925(sectors)    AUDIO  (2352bytes/sector)
〜
 

track01 の 開始セクタが -150 と負の値になっているのは プリギャップを加味してるためでここは問題ありません

問題となっているのは track02 です
現行の CDemudでは 2048bytes/sector しかデータトラックの読み出しをサポートしていません
ソースコードでは cdemud-device.c にあたります

1462         /* READ 10/12 should support only sectors with 2048-byte user data      */
1463         gint tmp_len = 0;
1464         guint8 *tmp_buf = NULL;
1465         guint8 *cache_ptr = _priv->buffer+_priv->buffer_size;
1466 
1467         mirage_sector_get_data(MIRAGE_SECTOR(cur_sector), &tmp_buf, &tmp_le     n, NULL);
1468         if (tmp_len != 2048) {
1469             CDEMUD_DEBUG(self, DAEMON_DEBUG_MMC, "%s: sector 0x%X does not      have 2048-byte user data (%i)\n", __debug__, sector, tmp_len);
1470             g_object_unref(cur_sector);
1471             __cdemud_device_write_sense_full(self, SK_ILLEGAL_REQUEST, ILLE     GAL_MODE_FOR_THIS_TRACK, 1, sector);
1472             return FALSE;
1473         }
1474
 

mirage_sector_get_data() でデータを読みだした結果 2048バイトでないとエラーとなります
ここを 以下のように修正しました

1462         /* READ 10/12 should support only sectors with 2048-byte user data */
1463         gint tmp_len = 0;
1464         guint8 *tmp_buf = NULL;
1465         guint8 *cache_ptr = _priv->buffer+_priv->buffer_size;
1466         gint tmp_type = 0;
1467         
1468         mirage_sector_get_data(MIRAGE_SECTOR(cur_sector), &tmp_buf, &tmp_len, NULL)     ;
1469 /*
1470  Add support some 2352bytes/sector format.
1471 */
1472         switch (tmp_len) {
1473             case 2048:  /* MODE1 */
1474                 memcpy(cache_ptr, tmp_buf, 2048);
1475                 _priv->buffer_size += 2048;
1476                 break;
1477             case 2352:  /* MODE2 FORM1 etc.*/
1478                 mirage_sector_get_sector_type(MIRAGE_SECTOR(cur_sector), &tmp_type, NULL);         
1479                 if ((tmp_type == MIRAGE_MODE_MODE1) || (tmp_type==MIRAGE_MODE_MODE2_FORM1)) {      
1480                     memcpy(cache_ptr, tmp_buf + 16, 2048);
1481                     _priv->buffer_size += 2048; 
1482                     break;
1483                 }   
1484                 /* Otherwise, I dont know where 2048-byte user data is in. */
1485             default:
1486                 CDEMUD_DEBUG(self, DAEMON_DEBUG_MMC, "%s: sector 0x%X does not have 2048-byte user data (%i)\n", __debug__, sector, tmp_len);
1487                 g_object_unref(cur_sector);
1488                 __cdemud_device_write_sense_full(self, SK_ILLEGAL_REQUEST, ILLEGAL_MODE_FOR_THIS_TRACK, 1, sector);
1489                 return FALSE;
1490         }
1491         
 

CD-ROMのデータトラックは 通常 1セクタあたり 2048バイトのデータ構成となっていますが
実際は ヘッダ情報や CRCなどの付加情報が前後に含まれています

上記コードは 2352バイトのフォーマットにおいても データ部分 2048バイトを抜き出す対応を追加したものです
ただし MODE1 など 一部のフォーマットのみの対応なので完全ではありません
そもそも CD-ROMからイメージを抜き出す際に MODE1(2048bytes/sector)を指定すれば ここの問題は発生しません

で 問題はさらに続きます
Mednafenで PC-Engineの処理を解析した結果 次の不具合が確認されました
「Mednafenでは データトラックの読み込み開始で 開始セクタ=3593 を指定しているのに
CDemuにおいては 開始セクタ=3592 のセクタリードが要求されている」

Mednafenとしてのデバイスアクセス ioctl() 開始セクタ=3593 で要求

Linuxカーネルのデバイスドライバ処理

VHBA (仮想ドライバ)

CDemu 開始セクタ=3592 の要求をイベントとして受ける

Linuxカーネルを経由することで セクタ番号がずれているように思われます
Linuxカーネルの内部動作まで確認しきれませんので いろいろ検証した結果以下の対応としました

「(Linuxでは?)CDトラックの開始セクタが 偶数セクタ番号となっているため
libmirageに調整処理を追加して トラックの開始セクタ番号が必ず偶数となるように合わせる」

これが一番 手短な改修でした libmirage のソースコード mirage-session.c に追加しました

  91         cur_track_address += track_length;
  92 
  93 /* track start address may be even-number.
  94    I found a troube at following environment.
  95     (MIXED-MODE)
  96      track01  start=-150  length=3743(sectors)   AUDIO  (2352bytes/sector)
  97      track02  start=3593  length=45336(sectors)  BINARY (2048bytes/sector)
  98      track03  start=48929 length=2925(sectors)   AUDIO  (2352bytes/sector)
  99      ...
 100    track02(DATA) start sector is just 3593, but Linux? set ioctl() start a     ddress 3592 (-1).
 101    So, CDemu is going to access to "track01" and fails.
 102    (Because CDemu fails to access to 2352bytes/sector at current version.)
 103 */
 104         if ( cur_track_address % 2 )  cur_track_address++;
 105     }
 106 
 107     return TRUE;
 

CUEシートからトラック情報を読み取る処理 に追加し 開始セクタ位置を調整しました

以上の作業で ようやく 「アルナムの牙」が動作しました
Mednafen + CDemu(.CUE) アルナムの牙
パソコンで PC-Engineのゲームができるようになるとは いい時代です
残りの CD-ROMゲームも吸い出してしまおう