LoginSignup
0

More than 1 year has passed since last update.

【Ubuntu】【倒立振子】Windowsで設計されているデバイスドライバを、Ubuntuで使えるようにする

Last updated at Posted at 2022-11-23

以前までの記事

MCUXpressoを使ってファームウェアの移植

Ubuntu環境でファームウェアを開発出来る環境の整備

目標

  • udevの新しいドライバとして、オリジナルのビュートバランサー2のドライバの一部機能を設計する

参考サイト

RTL2832uを使った例
ここから参考に、いかにリバースエンジニアリングしていくかの方針を決める

Windows環境によるリバースエンジニアリング

CUIツールの配布元

バランサー2SDKをダウンロードし、
Visual Studio C++を実行できる環境でソリューションを開く。

ビュートバランサー2を接続した状態で、実行すると、次のような画面となる.

image.png

もともとビュートバランサー2 SDKには、
「接続」「非接続」「メモリリード」「メモリライト」「デフォルト値への挿入」「RAM値のバックアップ」
といった機能が備えられている。

ここで、例えば

> r 68 2

などと書くと、ジャイロセンサの値を符号付き16bit表現で読み取ることができる。

image.png

従って、基本的にはこのメモリリードを使えさえすれば、デバイスを経由して、
センサやモータの電流値などを得ることができる。

それが出来る証拠として、
提供されているアプリケーションに「バランサー2プログラマー」というものがあり、

image.png

ここに赤丸で囲った部分は、すべてメモリリードで読み取っているものと考えられる。
Ubuntuでは、この機能を中心に実装してみるとする。

WiresharkによるUSB-HIDのパケット解析

再び、ビュートバランサー2に戻り、
今度はWiresharkを使って、
USB-HIDでどのようなパケットがやり取りされているのかを確認しよう。

Wiresharkはこちら

インストール時に、USBデバイス用の拡張機能を入れるのを忘れないように注意。

image.png

Wiresharkでは、「USBPcap2」を使う。これは各USBデバイス毎のパケットを知ることができる。
ここで設定にて、
以下のデバイスにチェックを入れよう。

image.png

HID-complicant vendor-defined deviceと呼ばれるもの。
これはヴィストン株式会社が提供してますよ、というのを表すデバイスと言える。

キャプチャを行う

image.png

バランサーSDKからは以下のコマンドを送信

image.png

以下のようなキャプチャが確認された。

image.png

以降で使う重要な情報もいくつか含まれているのだが、
Ubuntuで接続しないと意味がないのでここでは置いといて、URB_INTERRUPT OUTとなっているキャプチャを確認する。

image.png

すると、赤く囲った部分でHID Dataとして、

72 68 00 02 00 00 ...

という並びがあることが分かる。

0x72はASCIIコードではrである。
従って、rというASCIIテキストを送った後、バイナリデータでビッグエンディアン方式で0x0068、0x0002を送っているということが判明した。

※エンディアンについては適当な解説サイトをご覧ください

コマンド伝送時のデータの配置方法

まとめると、
以下のような形式でデータで
パケットを送るとメモリリードとなる。

無題のプレゼンテーション (1).jpg

ビュートバランサー2のメモリマップ

メモリマップそのものは公式の取扱説明書に載っている。
この通りメモリリードをすることで、すべての値を取り出せる。

しかし、とりあえず全部はいらないので、使いそうなのを並べておく

項目 オフセット 長さ
プロダクトID 0x00 ushort(2byte)
ジャイロセンサのAD値 0x68 short(2byte)
左輪モータエンコーダ値 0x58 double(8byte)
右輪モータエンコーダ値 0x60 double(8byte)
左輪モータ電流測定値 0x04 short(2byte)
右輪モータ電流測定値 0x06 short(2byte)
左輪モータ電流指令値 0x08 short(2byte)
右輪モータ電流指令値 0x0A short(2byte)
本体角速度 0x10 double(8byte)
本体角度 0x18 double(8byte)
本体角速度オフセット 0x20 double(8byte)

Ubuntu環境で調査すること

デバッグ環境用に、usb機能を遮断するためのシェルスクリプトを作成

まず、前回の記事で書いたusbhidの解除を毎回やるのは面倒なので、シェルで書いてしまおう。

例えば、適当に

driver_recog.sh
rmmod usbhid
insmod /lib/modules/$(uname -r)/kernel/drivers/hid/usbhid/usbmouse.ko

として

$ sudo bash driver_recog.sh

とすれば、自動でデバイスドライバのusbhidを非アクティブにしてくれる。

この作業をしていると、ドライバがクラッシュしたりみたいなことでubuntuの調査をしないと行けないこともあり、
起動時以外でも割と使うことがあるので、まとめておいた方が良い。

USBディスクリプタの情報取得

前回でもlsusbを実行したが、
もう少し詳しい情報を知ろうと思う。

Screenshot from 2022-11-23 18-26-50.png

前回は認識がメインだったので、ベンダーIDとプロダクトIDしか見なかったが、
実を言うとUSBディスクリプタというのはもっと色々な情報が書いてある。

これとかわかりやすいかな

image.png

つまり、Device Descriptorの下にConfiguration Descriptorが、その下にInterface Descriptorが、最後にEndpoint Descriptorがいる格好になる。

そして、このConfiguration Descriptor以下はデバイスごとによって、複数個存在しうるものである。
実際、今回もEndpoint Descriptorを見ると・・・。

Screenshot from 2022-11-23 18-30-06.png

EP 1 INと、EP 1 OUTと名付けられた別々のEndpoint Descriptorが見えている。

これ、先程WiresharkでINとOUTがそれぞれある、と書いたが、
実際のメモリリードの処理はこの両方を使っているということを意味する。

また、Transfer TypeにInterruptという表記があることだろう。
これはデータ転送時に割込み方式によってデータ転送することを表す。

データパケットがUSBを伝わった時に、PCに通達される情報は
割込みとして処理されますよ、ということである。

例えばコマンドを送信すると、
コマンド送信に成功したことが割込みで通達される。

注意として、割込み以外にDMAだとか、バルク転送とか、
この辺りはUSBインタフェースの仕様で決まるところであり、
linux kernelとして使うべき関数がちょっと異なってくる点、注意が必要。

まとめると、次のような処理をメモリリードで行えば良いということだ。

image.png

コーディング

プロジェクト

各要素のブロック図

linux kernelは古き良きC言語による設計になるので、
レガシーになり分かりづらい。
ブロック図として、何をどうするのかをまとめておく。

image.png

USB Request Block (URB)

※Microsoftのサイトです

USB Request Blockは、USBデータの指示を送るために使うまとまりのことであり、
これを送信・受信することにより、デバイス間での相互通知を可能とする。
現在のUSB通信で使われていないものを探すほうが珍しいもの。

これにより、制御ルーチンを直接管理する必要がないメリットが出来る一方で、データを送信したタイミングとコールバックのタイミング、割込みのタイミングなどで遅延が発生し必ず同期されないというデメリットもある。
しかし、多くの場合、非同期通信をケアした設計にするのがドライバ設計では半ば常識だと思うので、特段問題になることはないはず。

各関数の注意ポイント

エンドポイントの設定 (skel_probe)

今回、エンドポイントがIn, Out2つ存在することから、
どちらがInなのか、どちらがOutなのかを判定していく必要がある。

myBeuatoDriver.c

// endpointとして指定されている設定を取得
struct usb_host_interface* pHostIf = ip->cur_altsetting;

for(int i = 0 ; i < pHostIf->desc.bNumEndpoints; ++i ) 
{
  struct usb_endpoint_descriptor* endpoint = &pHostIf->endpoint[i].desc;
  if(usb_endpoint_dir_in(endpoint)) 
  {
    /* endpoint : inの処理 */
  }
  if(usb_endpoint_dir_out(endpoint)) 
  {
    /* endpoint : outの処理 */
  }
}

カーネルに入力用のバッファを割り当て (skel_probe)

別途prepare_readという関数を作り、
カーネルを割り当てた。

myBeuatoDriver.c
int prepare_read(struct usb_interface* ip, const struct usb_device_id* pID, struct usb_skel* pDev) 
{
  pDev->int_in_urb = usb_alloc_urb(0, GFP_KERNEL);
  if(!pDev->int_in_urb) 
  {
    DMESG_ERR("Memory allocation failed");
    return -1;
  }

  pDev->int_in_buffer = kmalloc(pDev->int_in_buffer_length, GFP_KERNEL);
  memset(pDev->int_in_buffer, 0, pDev->int_in_buffer_length);
 
  read_results.buffer = kmalloc(MAX_IN_TEXT_SIZE, GFP_ATOMIC);
  read_results.buffer_length = MAX_IN_TEXT_SIZE;

  return 0;
}

usb_alloc_urbはURBブロックの領域をカーネルで割り当てる処理。
kmallocはmallocのlinux kernel版。

なお、"read_results"は、割込みでメモリリードで読み取ったデータを取得する処理であり、
割込みと同時に取得してほしいのでGFP_ATOMICを指定しておいた(不要かもしれない)。

なお、ここで割り当てたpDev->int_in_urb, pDev->int_in_buffer, read_results.bufferは
disconnectで破棄する。

private_dataにURBを割り当て (skel_open)

myBeuatoDriver.c
int skel_open(struct inode *inode, struct file *file)
{
  /** 省略 **/
  file->private_data = (void*)pDev;

  return 0;
}

デバイスオープンしたときに、
URBの情報を取得できるように、pDevをprivate_dataに割り当てている。

usb_fill_int_urb (skel_write, skel_read)

割込み転送するときに使用する関数。

myBeuatoDriver.c
usb_fill_int_urb(
  urb_header, pDev->udev, 
  usb_sndintpipe(pDev->udev, pDev->int_out_endpoint->bEndpointAddress),
  transmit_buff,
  pDev->int_out_buffer_length,
  urb_out_complete,
  pDev,
  pDev->int_out_endpoint->bInterval);

例えばwrite側の処理はこのように記述されているが、urb_out_completeという割込み関数を上方に定義しておき、それを割り当てるようにしている。

usb_class_driver

myBeuatoDriver.c
/** ドライバクラスの登録 */
struct usb_class_driver skel_class = {
	.name = "usb/BeuatoCtrl%d",
	.fops = &skel_fops,
	.minor_base = MINOR_BASE
};

こう定義した場合、/usb/BeuatoCtrl〜という名前で
udevから展開されるようになる。

ビルド

$ make

でビルドすると、

カーネルオブジェクトmyBeuatoDriver.koが生成される。
これをインストールするときは、

$ sudo insmod myBeuatoDriver.ko

とする。

この状態で

$ ls -all /dev/BeuatoCtrl0

とすると、確かにlsコマンドが実行されるのが確認できる。

crwxrwxrwx 1 root root 180, 0 11月 27 13:10 /dev/BeuatoCtrl0

アンインストールするときは、

$ sudo rmmod myBeuatoDriver

とする。

動作確認

メモリリードで、例えば0x68のジャイロセンサの値を2byte取得する場合は、次のように入力。

$ echo "r 68 2 " > /dev/BeuatoCtrl0 &

注意として、「&」を付けないとrelease処理が行われなくなり、命令後に動作異常として現れてしまう。

この状態で、dmesgと入力すると


$ dmesg
(省略)
[ 3450.025684] [+] BeuatoCtrl: report_in_handler: Data Size:2
[ 3450.025686] [+] BeuatoCtrl: report_in_handler: Data:41 0 

と出てきており、ジャイロセンサの値が取得されていることを確認。

入力はcatを使って行う。
例えば

$ cat < /dev/BeuatoCtrl0

r 2 41 0

としてジャイロの値が読まれる。終了時はCtrl+Cを押して終了。

Pythonによる動作確認

ここまで来ると、echoとcatのみで入出力を表現できるので、
C以外の標準入出力をサポートされている言語を使って、
データの交換を行うことが出来る。

そこで、例えばpythonのプログラムで以下の通り組み、

test_echo_and_receive.py

import time

if __name__ == '__main__':
  for i in range(0,5) :
    with open('/dev/BeuatoCtrl0', mode='w') as f:
      f.write("r 68 2 ")

    time.sleep(1)
        
    with open('/dev/BeuatoCtrl0', mode='r') as f:
      print(f.readline())

車体を傾けたりすると・・・

r 2 43 0 
r 2 40 0 
r 2 d5 ff 
r 2 e 0 
r 2 e4 1 

車体を動かさないようにすると

r 2 45 0 
r 2 45 0 
r 2 45 0 
r 2 45 0 
r 2 45 0 

となり、ジャイロセンサが機能していることが理解できる。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0