libusb-1.0のメモ

twitterの極一部で熱狂的ブームになっているYUREXを入手したので,libusb-1.0で遊んでます.
ということで,その辺のc⌒っ゜д゜)っφ メモメモ...
コードは割とやっつけです・・・
libusbはこちらの 「Introduction to using LibUSB-1.0」を参考にしてます.

ドライバ関連は,「第三回+カーネル/vm探検隊まとめ」にある@yojiroさん作のOpenBSDのドライバを参考にしてます.
何か変なこと言ってたら私の間違いですorz

ソースはgithubに置いてあります.

今はこんな感じで動いてます.最後の方にある0x23がbbuです.

[masami@moonlight:~/experiment/yurex]% sudo ./yurex
[sudo] password for masami: 
Yurex info
Vendor: 0xc45
Product: 0x1010
Open Yurex device is success
Kernel Driver Active
Kernel Driver Detached!
Claim to Yurex device
ret is 0
Start search endpoint
find endpoint
Number of alternate settings: 0x1 | Interface Number: 0 | Number of endpoints: 0x1 | Descriptor Type: 0x5 | EP Address: 0x81 | 
ret is 0 : actual is 0x8
Writing Successful!
ret is 0 : actual is 0x8
Reading Successful!
0x43:0:0:0:0:0x23:0xd:0:
Done.

libusbでデバイスを使うには,デバイスの検索,デバイスのオープンって手順が必要です.

最初はlibusb_init()を呼んで初期化します.
ついでに,libusb_set_debug()でデバッグ用の情報も出るようにセットしておきます.

void Yurex::init() throw (const char *)
{
     int ret;
     libusb_context *ctx = NULL;

     ret = libusb_init(&ctx);

     if (ret < 0) 
	  throw "libusb_init was failed";

     libusb_set_debug(ctx, 3);

}

初期化が済んだら,libusb_get_device_list()を呼んでデバイスを検索します.結果はdevsに入ります.

int Yurex::findDevices() throw (const char *)
{
     int cnt = 0;
     libusb_device **devs;

     cnt = libusb_get_device_list(NULL, &devs);
     if (cnt < 0)
	  throw "There are no USB devices";
     
     setDevices(devs);

     return cnt;
}

デバイスを検索したらその中に自分が弄ろうとしているデバイスがあるかを調べます.
libusb_device_descriptor構造体のidVendorとidProductをチェックしてデバイスがあるかを判断してます.
YUREXはVendor: 0xc45,Product: 0x1010となってます.

bool Yurex::checkYurexDevice() throw (const char *)
{
     libusb_device *dev;
     libusb_device **devs = getDevices();
     int i = 0;
     bool ret = false;
     
     while ((dev = devs[i++]) != NULL) {
	  struct libusb_device_descriptor desc;
	  int r = libusb_get_device_descriptor(dev, &desc);
	  if (r < 0) 
	       throw ("failed to get device descriptor");

	  if (desc.idVendor == YUREX_VENDOR_ID &&
	      desc.idProduct == YUREX_PRODUCT_ID) {
	       setDescriptor(desc);
	       setDevice(dev);
	       ret = true;
	       break;
	  }
     }

     return ret;
}

デバイスのオープンにはibusb_open_device_with_vid_pid()を使ってます.
デバイスを開いた後は,さっきの検索結果は不要になるので,消してます.というか,さっきの関数内でやれば良いんですけどね.

bool Yurex::openYurex()
{
     libusb_device_handle *h;

     h = libusb_open_device_with_vid_pid(NULL, 
					 getDescriptor()->idVendor, 
					 getDescriptor()->idProduct);
     if (h)
	  setHandle(h);
     
     clearDeviceList();

     return h ? true : false;
}

次にカーネルのドライバを外す処理が入ります.ドキュメントをちゃんと読んでないので理由は確かめてないですが,実施するみたいです.

bool Yurex::detachKernelDriver()
{
     libusb_device_handle *handle = getHandle();
     bool ret = true;

     //Detach driver if kernel driver is attached.
     if (libusb_kernel_driver_active(handle, 0) == 1) { 
	  std::cout<< "Kernel Driver Active" << std::endl;
	  
	  if(libusb_detach_kernel_driver(handle, 0) == 0) {
	       std::cout<< "Kernel Driver Detached!" << std::endl;
	       ret = true;
	  } else {
	       ret = false;
	  }
     }

     return ret;
}

そして,エンドポイントを探します.このエンドポイントはデータの読み書きに使用するものなので検索が必須です.

void Yurex::findEndPoint()
{
     libusb_config_descriptor *config = getConfig();
     const libusb_interface_descriptor *interdesc;
     const libusb_endpoint_descriptor *epdesc;
     const libusb_interface *inter;

     libusb_get_config_descriptor(getDevice(), 0, &config);

     std::cout << "find endpoint" << std::endl;

     // Actually, yulex may only have one endpoint.
     for(int i = 0; i < (int) config->bNumInterfaces; i++) {
	  inter = &config->interface[i];

	  std::cout << "Number of alternate settings: "<< inter->num_altsetting << " | ";

	  for(int j = 0; j < inter->num_altsetting; j++) {
	       interdesc = &inter->altsetting[j];

	       std::cout << "Interface Number: " << (int) interdesc->bInterfaceNumber << " | ";
	       std::cout << "Number of endpoints: "<< (int) interdesc->bNumEndpoints << " | ";

	       for(int k = 0; k < (int) interdesc->bNumEndpoints; k++) {
		    epdesc = &interdesc->endpoint[k];
		    std::cout << "Descriptor Type: " << (int) epdesc->bDescriptorType << " | ";
		    std::cout <<"EP Address: " << (int) epdesc->bEndpointAddress << " | " << std::endl;
		    setEndPoint(epdesc);
	       }
	  }
     }

}

ここまで来てやっとデータの読み書きが出来るようになりました(∩´∀`)∩ワーイ
データの読み書きにはlibusb_bulk_transfer()を使ってます.これは同期処理です.libusbは非同期の処理もあります.
コマンドは,配列を不要な箇所をパディング(0xff)で埋めて,先頭にCMD_READ(0x52),次にデータの終わりを示すCMD_EOF(0x0d)をセットします.
これで,libusb_bulk_transfer()を使えば,デバイスからデータの読み込みができます.

bool Yurex::readDataASync()
{
     unsigned char data[8] = { CMD_PADDING };
     int ret;
     int actual = 0;

     data[0] = CMD_READ;
     data[1] = CMD_EOF;
     
     ret = libusb_bulk_transfer(getHandle(), getEndPoint()->bEndpointAddress, data, sizeof(data), &actual, 2000);
     std::cout << "ret is " << ret << " : actual is " << actual << std::endl;
     if(ret < 0) {
	  std::cout << "Reading Error" << std::endl;
     } else { 
	  std::cout << "Reading Successful!" << std::endl;
	  for (int i = 0; i < sizeof(data); i++)
	       std::cout << std::hex << std::showbase << (int) data[i] << ":";
	  std::cout << std::endl;
     }


     return true;
}

読み込んだ結果はこんな感じですね.

Reading Successful!
0x43:0:0:0:0:0x23:0xd:0:

5バイト目のところの0x23が現在のbbuになってます.

デバイスを使い終わったら当然,終了処理がありますが,そこは略で.