Buffalo PC-SDVD/U2 を Raspberry Pi で動かしたかった話

# はじめに

:::note warn
この記事は「成功**しなかった**」内容です。
成功しなかった記録を残すものです。どうかこの屍を超えてください。
:::

今回はちょっとニッチな挑戦記録です。タイトルの通り、Buffalo製のUSBビデオキャプチャデバイス「PC-SDVD/U2」を、Raspberry Pi 3で動かしてみようというお話。

え？そんなの挿せば動くでしょ？──そう思っていた時期が私にもありました...(これが言いたかっただけ)

ニッチなデバイスをを使おうとすると一筋縄ではいかぬのがLinux。

# 環境

* マシン：Raspberry Pi 3 Model B
* OS：Raspberry Pi OS Bookworm 64bit Lite（GUIなし）
* キャプチャデバイス：Buffalo PC-SDVD/U2
  * `dmesg`上では`USB 2863`として認識
  * VID: `0411`, PID: `015a`
* カーネル：6.12.25 → 6.12.29（手動ビルド）
* その他ツール：v4l-utils, i2c-tools, media-ctl など

---

# 初期の動作

結論から言えば、「何も起きなかった」です。

`dmesg` を見ても、VID/PID は確かに表示されるものの、デバイスとしては `/dev/video0` はおろか、`/dev/mediaX` すら出てこない。つまり、ドライバ（`em28xx` 系）があたっていない？

```dmesg
usb 1-1.3: New USB device found, idVendor=0411, idProduct=015a, bcdDevice= 1.10
```

この段階では、`modprobe em28xx` や `modprobe em28xx-v4l` をしても `/dev/video0` は出現せず、`v4l2-ctl --list-devices` でも "Cannot open device /dev/video0" のエラーしか出ない。USBデバイスが接続されていても、ドライバがデバイスのVID/PIDとマッチングしない限り、何も起きないようだ。

紆余曲折ののち、ようやく `/dev/media3` は現れるようにはなった。

# カーネルビルド

em28xx カーネルモジュールをビルドし、VID:0411, PID:015a を持つデバイスを `em28xx_id_table[]` に追加していく。調べてみると、どうやら `em28xx` ドライバ自体はもともと存在していて、VID/PIDがテーブル上に無い為、USBが接続されても無視されているらしい。

ここからは実際の作業。

## ステップ1：カーネルソースを取ってくる

```bash
sudo apt install raspberrypi-kernel-headers git bc bison flex libssl-dev make

~$ git clone --depth=1 https://github.com/raspberrypi/linux
~$ cd linux
~$ KERNEL=kernel8
~$ make bcm2711_defconfig
```

## ステップ2：em28xxに自作デバイスを登録

`drivers/media/usb/em28xx/em28xx-cards.c` に、自分で定義したカード（EM2863\_BOARD\_CUSTOM）を追加。
これがないとVID/PIDを見てもドライバ君に「んなボードは知らん」と言われて無視されます。

ボードの定義 (drivers/media/usb/em28xx/em28xx-cards.c)
```c
/*
 *  Board definitions
 */
const struct em28xx_board em28xx_boards[] = {
	[EM2863_BOARD_CUSTOM] = {
		.name = "Buffalo USB 2863 (custom)",
		.tuner_type = TUNER_ABSENT,
		.decoder = EM28XX_SAA711X,
		.has_msp34xx = 1,
		.has_ir_i2c = 1,
	},
	[EM2750_BOARD_UNKNOWN] = {
		.name          = "EM2710/EM2750/EM2751 webcam grabber",
		.xclk          = EM28XX_XCLK_FREQUENCY_20MHZ,
```

VID/PIDを自作の定義へ割り当て
```c
/* table of devices that work with this driver */
struct usb_device_id em28xx_id_table[] = {
	{ USB_DEVICE(0x0411, 0x015a),
  			.driver_info = EM2863_BOARD_CUSTOM },
	{ USB_DEVICE(0xeb1a, 0x2750),
			.driver_info = EM2750_BOARD_UNKNOWN },
```

ヘッダーファイルにcard IDの登録
```c
/* Boards supported by driver */
#define EM2863_BOARD_CUSTOM			  110
#define EM2800_BOARD_UNKNOWN			  0
```

この時点で `decoder` は `EM28XX_SAA711X` に。
理由はあとで触れますが、「tvp5150」ではなさそうだったからです。

## ステップ3：ビルドして入れる！

以下ではRaspberry Pi上でのビルドを想定したコマンドになっているが、母艦PCなどでクロスコンパイルした方が圧倒的に楽で高速なのでそっちがおすすめ。うちの環境ではwslでコンパイルしてscpで転送してました。

```bash
make -j$(nproc) Image modules dtbs # Module.symvers is missingの表示が出るので初回だけ実行
make M=drivers/media/usb/em28xx modules
sudo make M=drivers/media/usb/em28xx modules_install
sudo depmod -a
```

そして……新カーネルを `/boot/firmware` に適用

```bash
sudo cp /boot/firmware/$KERNEL.img /boot/firmware/$KERNEL-backup.img
sudo cp arch/arm64/boot/Image.gz /boot/firmware/$KERNEL.img
sudo cp arch/arm64/boot/dts/broadcom/*.dtb /boot/firmware/
sudo cp arch/arm64/boot/dts/overlays/*.dtb* /boot/firmware/overlays/
sudo cp arch/arm64/boot/dts/overlays/README /boot/firmware/overlays/
sudo reboot
```

# ステップ4：あらためて動作チェック (失敗)

`dmesg`を叩いてどういう挙動になったか見てみる

```bash
pi@RPi3:~ $ dmesg | grep em28
[    6.698677] em28xx: loading out-of-tree module taints kernel.
[    6.700503] em28xx 1-1.3:1.0: New device  USB 2863 Device @ 480 Mbps (0411:015a, interface 0, class 0)
[    6.700534] em28xx 1-1.3:1.0: Audio interface 0 found (Vendor Class)
[    6.700542] em28xx 1-1.3:1.0: Video interface 0 found: isoc
[    6.758247] em28xx 1-1.3:1.0: chip ID is em2860
[    6.880902] em28xx 1-1.3:1.0: EEPROM ID = 1a eb 67 95, EEPROM hash = 0x9a2fcb2c
[    6.880934] em28xx 1-1.3:1.0: EEPROM info:
[    6.880941] em28xx 1-1.3:1.0:        I2S audio, 5 sample rates
[    6.880948] em28xx 1-1.3:1.0:        500mA max power
[    6.880954] em28xx 1-1.3:1.0:        Table at offset 0x24, strings=0x206a, 0x0e8a, 0x0000
[    6.958092] em28xx 1-1.3:1.0: Identified as Buffalo USB 2863 (custom) (card=110)
[    6.958127] em28xx 1-1.3:1.0: analog set to isoc mode.
[    6.982407] usbcore: registered new interface driver em28xx
[    6.998213] em28xx 1-1.3:1.0: Registering V4L2 extension
[    7.125565] em28xx 1-1.3:1.0: Config register raw data: 0xf0
[    7.125595] em28xx 1-1.3:1.0: I2S Audio (5 sample rate(s))
[    7.125603] em28xx 1-1.3:1.0: No AC97 audio processor
[    7.798703] em28xx 1-1.3:1.0: failed to create media graph
[    7.798712] em28xx 1-1.3:1.0: V4L2 device vbi0 deregistered
[    7.798953] em28xx 1-1.3:1.0: V4L2 device video0 deregistered
[    7.799137] em28xx: Registered (Em28xx v4l2 Extension) extension
[    7.827879] em28xx 1-1.3:1.0: Binding audio extension
[    7.827917] em28xx 1-1.3:1.0: em28xx-audio.c: Copyright (C) 2006 Markus Rechberger
[    7.827924] em28xx 1-1.3:1.0: em28xx-audio.c: Copyright (C) 2007-2016 Mauro Carvalho Chehab
[    7.828010] em28xx 1-1.3:1.0: Endpoint 0x83 high-speed on intf 0 alt 7 interval = 8, size 196
[    7.828024] em28xx 1-1.3:1.0: Number of URBs: 1, with 64 packets and 192 size
[    7.830579] em28xx 1-1.3:1.0: Audio extension successfully initialized
[    7.830613] em28xx: Registered (Em28xx Audio Extension) extension
[    7.911779] em28xx 1-1.3:1.0: Registering input extension
[    7.911834] em28xx: Registered (Em28xx Input Extension) extension
```

ここで、

```bash
[    6.958092] em28xx 1-1.3:1.0: Identified as Buffalo USB 2863 (custom) (card=110)
```

とあることから、VID/PIDが正しく識別されてデバイスドライバが当たっていることが確認できる。

デバイスドライバの読み込み状況は

```bash
pi@RPi3:~ $ lsmod | grep em28
em28xx_rc              16384  0
em28xx_alsa            16384  0
em28xx_v4l             45056  0
em28xx                 94208  3 em28xx_rc,em28xx_alsa,em28xx_v4l
videobuf2_vmalloc      12288  2 em28xx_v4l,bcm2835_v4l2
tveeprom               20480  1 em28xx
videobuf2_v4l2         32768  5 bcm2835_codec,em28xx_v4l,bcm2835_v4l2,v4l2_mem2mem,bcm2835_isp
videodev              303104  8 bcm2835_codec,videobuf2_v4l2,em28xx_v4l,msp3400,bcm2835_v4l2,em28xx,v4l2_mem2mem,bcm2835_isp
videobuf2_common       73728  9 bcm2835_codec,videobuf2_vmalloc,videobuf2_dma_contig,videobuf2_v4l2,em28xx_v4l,bcm2835_v4l2,v4l2_mem2mem,videobuf2_memops,bcm2835_isp
mc                     61440  9 videodev,bcm2835_codec,videobuf2_v4l2,em28xx_v4l,msp3400,videobuf2_common,em28xx,v4l2_mem2mem,bcm2835_isp
snd_pcm               139264  6 em28xx_alsa,snd_bcm2835,snd_soc_hdmi_codec,snd_compress,snd_soc_core,snd_pcm_dmaengine
snd                   110592  7 em28xx_alsa,snd_bcm2835,snd_soc_hdmi_codec,snd_timer,snd_compress,snd_soc_core,snd_pcm
```

となり、em28周辺のドライバが自動で読み込まれていることがわかる。

`v4l2-ctl`上にも/dev/media3が出現するようになった。

```bash
pi@RPi3:~ $ v4l2-ctl --list-devices
bcm2835-codec-decode (platform:bcm2835-codec):
        /dev/video10
        /dev/video11
        /dev/video12
        /dev/video18
        /dev/video31

bcm2835-isp (platform:bcm2835-isp):
        /dev/video13
        /dev/video14
        /dev/video15
        /dev/video16
        /dev/video20
        /dev/video21
        /dev/video22
        /dev/video23
        /dev/media0
        /dev/media1

USB 2863 Device (usb-3f980000.usb-1.3):
        /dev/media3

bcm2835-codec (vchiq:bcm2835-codec):
        /dev/media2

Cannot open device /dev/video0, exiting.
```

一方で、

```bash 
[    7.798703] em28xx 1-1.3:1.0: failed to create media graph
[    7.798712] em28xx 1-1.3:1.0: V4L2 device vbi0 deregistered
[    7.798953] em28xx 1-1.3:1.0: V4L2 device video0 deregistered
```

と表示されており、`media graph`(こいつがなんなのかよくわからない)の構築に失敗してvideo0デバイスとしての扱いを解除されてしまっている。


また`media-ctl`でも

```bash
pi@RPi3:~ $ media-ctl -d /dev/media3 -p
Media controller API version 6.12.29

Media device information
------------------------
driver          em28xx
model           USB 2863 Device
serial          200806
bus info        usb-3f980000.usb-1.3
hw revision     0x110
driver version  6.12.29

Device topology
pi@RPi3:~ $
```

となっており、デバイスの基本的な情報は取得できているものの、それ以上の詳細な情報は得られていないようだ。

つまり「em28xx本体」は動いているけれど、それに接続されるべきI2Cデバイスなどとの接続が確立できていない、ということなのだろう。

# i2cバス上の謎のデバイス

ChatGPTに言われるがままI2Cを探索 (`i2cdetect -l`) してみる。

```
pi@RPi3:~ $ i2cdetect -l
i2c-1   i2c             bcm2835 (i2c@7e804000)                  I2C adapter
i2c-2   i2c             bcm2835 (i2c@7e805000)                  I2C adapter
i2c-11  i2c             1-1.3:1.0                               I2C adapter
```

i2c-11というのがPC-SDVD/U2内部のI2Cアダプタを指しているらしい。そこで、11を指定して更にその配下の応答を探ると、

```
pi@RPi3:~ $ sudo i2cdetect -y 11
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- 41 -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: 50 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
pi@RPi3:~ $
```

となる。見えているのは`0x41`と`0x50`の二つ。

`0x50`はEEPROMのアドレスで、 `0x41` がどうやら`saa7113h` らしい？

とりあえず`saa711x`系と目星をつけて、decoderを `EM28XX_SAA711X` に。

# dmesgにログを吐くように仕掛ける

それでも `/dev/video0` は出てこない。
ならば、と `em28xx` のソースに `pr_info()` を埋め込んでみる。

```c
	if (dev->board.decoder == EM28XX_SAA711X) {
		struct v4l2_subdev *sd;
        
		pr_info("em28xx: v4l2_init: Attempting to probe SAA711X decoder (expected addr 0x%02x or 0x%02x)...\n",
                saa711x_addrs[0], saa711x_addrs[1]);
        
		sd = v4l2_i2c_new_subdev(&v4l2->v4l2_dev,
                                 &dev->i2c_adap[dev->def_i2c_bus],
                                 "saa7115_auto", 0, saa711x_addrs);
        if (sd) {
            pr_info("em28xx: v4l2_init: SAA711X probed successfully at 0x%02x\n",
                    v4l2_i2c_subdev_addr(sd));
        } else {
            pr_info("em28xx: v4l2_init: SAA711X probe failed.\n");
        }
		
		// v4l2_i2c_new_subdev(&v4l2->v4l2_dev,
		// 		    &dev->i2c_adap[dev->def_i2c_bus],
		// 		    "saa7115_auto", 0, saa711x_addrs);
	}
```

ログはこうなった

```dmesg
em28xx: v4l2_init: Attempting to probe SAA711X decoder (expected addr 0x25 or 0x24)...
em28xx: v4l2_init: SAA711X probe failed.
```

`0x25` や `0x24` を見ているのは、I2C通信時のデバイスアドレスが7bitだかららしい。

```c
/* I2C possible address to saa7115, tvp5150, msp3400, tvaudio */
static unsigned short saa711x_addrs[] = {
	0x4a >> 1, 0x48 >> 1,   /* SAA7111, SAA7111A and SAA7113 */
	0x42 >> 1, 0x40 >> 1,   /* SAA7114, SAA7115 and SAA7118 */
	I2C_CLIENT_END };
```

で、`0x4a >> 1` は `0x25`に、`0x48 >> 1` は `0x24`になる。
試しに8bit→7bit変換無しでそのまま`0x41`を入れてみるも変化なし。
I2Cの仕様に精通していないだけに、決定的な勘違いをしている気がする。

ChatGPTもGeminiも `.i2c_dev[]` の再構成が必要と言うが、そんな記載は見つけられず…。カーネルバージョン差なのかハルシネーションなのかイマイチ判別も付かず。だれか助けて。

---

# メモ

* USBキャプチャは「認識された」だけじゃ終わらない。中の構成部品まで繋がって初めて完成
* `dmesg`, `media-ctl`, `i2cdump` の三種の神器でひたすら地道に追う

---

# おわりに

USBキャプチャをRaspberry Piで動かそうとすると、気軽に始めたつもりがいつの間にか泥沼のカーネル沼に沈んでいた──そんな体験でした。

とはいえ、今後同じようなことで悩む人にとって、この記事が「ここまでやったぞ」「ここで詰まったぞ」という道標になれば嬉しいです。

続報があれば追記します。
以上、PC-SDVD/U2との奮闘記でした。