Raspberry Pi Zero (W) をUSBキーボード(USBガジェット)にする


概要

PCに繋ぐとUSBキーボードとして振る舞うことができるものを作って遊びたいと思っていたところ、RaspberryPiZero(W)がUSB OTGという技術に対応しているということで試してみた。

早い話が以下の情報元サイトに従って作業をすればOKなのだが、英語で書かれたWebサイトを読むのが苦手な人もいると思うので要点だけ書き留めておく。

情報元:Composite USB Gadgets on the Raspberry Pi Zero | iSticktoit.net

実験環境

ハードウェアはRaspberry Pi Zero Wを使用。
OSは、最初に2017-07-05-raspbian-jessie-liteで試したら何故かうまく行かず(本稿作成時点でも解決方法がわからぬまま)、ちょっと古い2016-03-18-raspbian-jessie-liteを使ってみたらうまく行った。もしかしたら手順を間違えたのかも知れないが、Linuxカーネルとかモジュールとかの違いでうまく行かない可能性があるので注意すると良い。
最新ではないOSのイメージは以下から辿って入手が可能:https://downloads.raspberrypi.org/
(raspbian_liteであればhttps://downloads.raspberrypi.org/raspbian_lite/images/

具体的な手順

導入

普通にRaspbianをインストールする。apt-get upgradeやapt-get updateもしておく。USBがホストPCへの接続に使われてしまう(有線LANアダプタ等を繋ぐ分のポートがない)ことを見越してwifiやシリアル接続の有効化もしておく。emacsやviなど自分の使いやすいエディタも導入しておくべき。

さらに、Kernelが4.4未満の場合には更新する。apr-get update/upgradeの時点で更新されている気はするが気を付ける。uname -rコマンドで4.4以上の数字が返ってくれば良いと思われる。ちなみに実際に試した環境では4.9.48+であった。
(uname -aで返ってきた結果は「Linux raspberrypi 4.9.48+ #1034 Fri Sep 8 13:55:13 BST 2017 armv6l GNU/Linux」)

OS起動時パラメタの調整

  • /boot/config.txt に dtoverlay=dwc2 という行を追記する
  • /etc/modules に dwc2 と libcomposite という行を追記する

引用元サイトのように以下のようなコマンドで追記しても良いし、もちろんエディタで追記しても良い。

echo "dtoverlay=dwc2" | sudo tee -a /boot/config.txt
echo "dwc2" | sudo tee -a /etc/modules
echo "libcomposite" | sudo tee -a /etc/modules

USBデバイスの作成

適当な場所にスクリプトを用意してUSBデバイスに関する情報を生成する。具体的な置き場所は自由だが、ここでは情報元サイトに準拠して /usr/bin/isticktoit_usb を採用する。

sudo touch /usr/bin/isticktoit_usb
sudo chmod +x /usr/bin/isticktoit_usb

作成したファイルの中身については、情報元の「Creating the gadget」の最初に書かれているのが各種USB機器に共通する内容で、その後はUSB機器の振る舞いにあわせて書き足すことになる。今回はUSBキーボード化したいため、ファイルの中身(全体)は以下のようになる。
(具体的に行っている作業がcd, mkdir, echo, ln, lsしかないのでパスを変更したいときも簡単である。)

/usr/bin/isticktoit_usb
#!/bin/bash
cd /sys/kernel/config/usb_gadget/
mkdir -p isticktoit
cd isticktoit
echo 0x1d6b > idVendor # Linux Foundation
echo 0x0104 > idProduct # Multifunction Composite Gadget
echo 0x0100 > bcdDevice # v1.0.0
echo 0x0200 > bcdUSB # USB2
mkdir -p strings/0x409
echo "fedcba9876543210" > strings/0x409/serialnumber
echo "Tobias Girstmair" > strings/0x409/manufacturer
echo "iSticktoit.net USB Device" > strings/0x409/product
mkdir -p configs/c.1/strings/0x409
echo "Config 1: ECM network" > configs/c.1/strings/0x409/configuration
echo 250 > configs/c.1/MaxPower
# Add functions here
mkdir -p functions/hid.usb0
echo 1 > functions/hid.usb0/protocol
echo 1 > functions/hid.usb0/subclass
echo 8 > functions/hid.usb0/report_length
echo -ne \\x05\\x01\\x09\\x06\\xa1\\x01\\x05\\x07\\x19\\xe0\\x29\\xe7\\x15\\x00\\x25\\x01\\x75\\x01\\x95\\x08\\x81\\x02\\x95\\x01\\x75\\x08\\x81\\x03\\x95\\x05\\x75\\x01\\x05\\x08\\x19\\x01\\x29\\x05\\x91\\x02\\x95\\x01\\x75\\x03\\x91\\x03\\x95\\x06\\x75\\x08\\x15\\x00\\x25\\x65\\x05\\x07\\x19\\x00\\x29\\x65\\x81\\x00\\xc0 > functions/hid.usb0/report_desc
ln -s functions/hid.usb0 configs/c.1/
# End functions
ls /sys/class/udc > UDC

strings/0x409 以下のファイルへ書き込む内容を変更すればホスト機器から見える機器の情報に反映される。(ただしWindows10のデバイスマネージャーから「バスによって報告されるデバイスの説明」に「product」が反映されていることしかよくわかっていない。)

起動

前節で作成した/usr/bin/isticktoit_usbを起動すれば良い。起動してからUSBケーブルをホスト機器に接続しても、ホスト機器に接続してから起動しても、特に問題は無いように見える。

そもそも、RaspberryPiZero(W)は電源用ではなく機器接続用のUSBからも受給電起動が可能なので、ここまでの作業を全てホスト機器にUSBケーブルを繋いだ状態で行い、USB OTG有効化の時点でデバイスを認識、ということも可能。(なハズだが、その作業方法を試したわけではない。)

具体的にホスト機器からデバイスが見えるようになるのは最終行でUDCファイルに書き込みを行った瞬間。

OS起動時に自動的に起動するようにしたい場合には /etc/rc.local の最終行(exit 0)より前に上記のスクリプトを起動するための一行を書き足すなどすれば良い。もちろん、OSが起動してから手動で叩いても良い。
ホスト機器からUSB入力機器として見えるようになるはず。Windows10(64bit)ではデバイスマネージャーに「USB入力デバイス」が出現した。
※ 2017-07-05-raspbian-jessie-liteベースでやったところ、ここでUSB機器として問題のあるデバイスが認識されてしまい、RaspberryPiZero側でもエラーメッセージが出ていた。詳細は(どうせ解決方法がわかっていないので)割愛。

入力

RaspberryPiZero(W)側に/dev/hidg0 というデバイスが生えているので、ここにキー入力情報を送りつければ良い。要root権限。情報元にある通り、「echo -ne "\0\0\x4\0\0\0\0\0" > /dev/hidg0」でaキーを押した状態になり、「echo -ne "\0\0\0\0\0\0\0\0" > /dev/hidg0」で全てのキーを離した状態になる。ホスト機器側でテキストエディタを開いて試すとよくわかる。

情報元からリンクされている以下のツールを使えばもっと単純に「文字列」を送りつけたりできるので便利。
girst/hardpass-sendHID: A hardware password manager, built around a Paspberry Pi Zero and passwordstore.org

終了

UDCファイルを直接消すことはできない。
その前の「ln -s functions/hid.usb0 configs/c.1/」によって作られたconfigs/c.1/hid.usb0をunlinkすればホスト機器から見えなくなる。(デバイスマネージャーから「USB入力デバイス」が消滅する。)

まだちゃんと調べていないこと

具体的にどうすればどのようなキー入力が認識されるのか。CtrlやAltやマルチメディアキーなどがどのように与えられるかを含む。

補足

そもそもの機能についての大雑把な説明

Raspberry Pi Zero (W) を含むある種のUSB機器はUSBデバイスとして振る舞うための機能を備えている。そのUSB機器上で動いているLinuxから機能を有効化してやれば、機器を接続した先のホストUSB機器からUSBデバイスとして見えるようになる。

他の(古い)Webサイトにある情報について

他のWebサイトにはg_hidというモジュールを使ってUSB入力機器化する情報が掲載されている可能性がある。これは古いOSでしかできない方法のようで、実際にやろうとしてもg_hidが見当たらず読み込めないハズ。最近のOSの場合にはlibcompositeを使う必要があるようだ。

USB入力デバイスの作成について

「BadUSB」でググればわかるように、USB入力デバイスを自作すること自体はArduinoでも簡単にできる。Arduinoの方が起動が速い(RaspberryPiZero(W)を使う場合はRasbianの起動に時間がかかる)し消費電力も低いので単純にちょっとした入力をさせるだけならArduinoの方が良い気もするけど、RaspberryPiZero(W)は他のLinuxプログラム等と組み合わせて遊べると面白いと思う。