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


# 以前までの記事

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

https://qiita.com/belre/items/9512379324b6c2699cc7

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

https://qiita.com/belre/items/ac9eeae49ee74fbe2692

# 目標

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

## 参考サイト

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

https://um6ra1.blog.fc2.com/blog-entry-9.html

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

## CUIツールの配布元

https://www.vstone.co.jp/products/beauto_balancer_2/download.html

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

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/8dc22bf6-942f-694b-fe7c-f93b958108f6.png)

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

ここで、例えば

```
> r 68 2
```

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/d5ccc0db-bac5-2bde-b5c3-7e948f943a67.png)

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

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/a910e51b-1249-22a7-926f-7206d853c2ba.png)

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


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

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

Wiresharkはこちら

https://www.wireshark.org/download.html

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/a7664f3a-933a-b7cd-be6c-2172d6a67b04.png)

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/c715d35c-a3f7-505c-7105-bf1a4edd2b3f.png)

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

キャプチャを行う

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/7e43fd8b-3004-d6b9-c7e8-28cbd0a2b43b.png)

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/944090c1-dcb1-0ff6-beb3-f0d632cd81e2.png)

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/eb68e55e-57f1-9e08-c9e5-e41d5401503f.png)

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/b1cbbd79-157d-4ca0-d1e8-d5d656606aea.png)

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

```
72 68 00 02 00 00 ...
```

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

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

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

https://rainbow-engine.com/little-endian-big-endian/


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

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

![無題のプレゼンテーション (1).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/fc9c1007-6cf4-40c3-3bba-d0db3753ef6d.jpeg)

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

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

https://www.vstone.co.jp/products/beauto_balancer_2/download/BeautoBalancer2_Manual_1_05.pdf

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

|項目|オフセット|長さ|
|---|---|---|
|プロダクト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機能を遮断するためのシェルスクリプトを作成

まず、[前回の記事](https://qiita.com/belre/items/ac9eeae49ee74fbe2692)で書いたusbhidの解除を毎回やるのは面倒なので、シェルで書いてしまおう。

例えば、適当に

```shell: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/c74d2e29-a08b-abd0-376e-28bebd9df6b1.png)

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

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

https://monoist.itmedia.co.jp/mn/articles/1007/20/news097.html

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/979f1e96-cabf-e993-2c28-c981258fbb47.png)

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

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


![Screenshot from 2022-11-23 18-30-06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/202c33c3-39ca-5e7f-5276-7e7260f3b050.png)

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

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

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

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

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

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

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/03039e42-bf77-4492-c283-bdf345cf7e39.png)


# コーディング

## プロジェクト

https://github.com/belre/Balancer2/tree/master-ubuntu/cmake-cpp/usb_register_test/beuato_usb

## 各要素のブロック図

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/085fe5d8-af62-5fbd-3ad1-91c297bffda7.png)

## USB Request Block (URB)

https://learn.microsoft.com/ja-jp/windows-hardware/drivers/usbcon/communicating-with-a-usb-device

※Microsoftのサイトです

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

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

## 各関数の注意ポイント

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

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

```c: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という関数を作り、
カーネルを割り当てた。

```c: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)

```c: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)

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

```c: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

```c: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のプログラムで以下の通り組み、

```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 
```

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