2011/02/14

libusb 初探4-device IO

usb 作為一個通用 IO bus,當然就具備同步與非同步,當然非同步相對複雜,我們就看同步IO的就好了

同步 IO 有二種(其實還有第三種叫 stream, 似乎沒被實作):
libusb_bulk_transfer() : Perform a USB bulk transfer.
及 libusb_interrupt_transfer() : Perform a USB interrupt transfer.

而要跟 device 溝通,很重要的一個函數叫 libusb_control_transfer()
Function Documentation

int libusb_control_transfer ( libusb_device_handle *  dev_handle,
    uint8_t  bmRequestType,
    uint8_t  bRequest,
    uint16_t  wValue,
    uint16_t  wIndex,
    unsigned char *  data,
    uint16_t  wLength,
    unsigned int  timeout  
)   
Perform a USB control transfer.

The direction of the transfer is inferred from the bmRequestType field of the setup packet.

The wValue, wIndex and wLength fields values should be given in host-endian byte order.

Parameters:
    dev_handle  a handle for the device to communicate with
    bmRequestType  the request type field for the setup packet
    bRequest  the request field for the setup packet
    wValue  the value field for the setup packet
    wIndex  the index field for the setup packet
    data  a suitably-sized data buffer for either input or output (depending on direction bits within bmRequestType)
    wLength  the length field for the setup packet. The data buffer should be at least this size.
    timeout  timeout (in millseconds) that this function should wait before giving up due to no response being received. For an unlimited timeout, use value 0.

Returns:
  on success, the number of bytes actually transferred
  LIBUSB_ERROR_TIMEOUT if the transfer timed out
  LIBUSB_ERROR_PIPE if the control request was not supported by the device
  LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
  another LIBUSB_ERROR code on other failures
上面有句話不知道有沒注意到: The wValue, wIndex and wLength fields values should be given in host-endian byte order

底下來寫一點心得:

.首先就是找到 usb device 有兩種方法,一種是透過 cnt = libusb_get_device_list(NULL, &devs) 這樣的呼叫,另一種則是透過 devh = libusb_open_device_with_vid_pid(NULL, 0x05ba, 0x000a);

.在 libusb_get_device_list() 的第一個參數 libusb_context,這是讓你可以同時控制兩個 libusb sessions, 一般情況不必管這個值的話就給 NULL 即可

.底下有一段利用 libusb_control_transfer() 來跟 device 溝通的範例:
#define CTRL_IN                 (LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_IN)
#define USB_RQ                  0x04

static int print_f0_data(void)
{
        unsigned char data[0x10];
        int r;
        unsigned int i;

        r = libusb_control_transfer(devh, CTRL_IN, USB_RQ, 0xf0, 0, data,
                sizeof(data), 0);
        if (r < 0) {
                fprintf(stderr, "F0 error %d\n", r);
                return r;
        }
        if ((unsigned int) r < sizeof(data)) {
                fprintf(stderr, "short read (%d)\n", r);
                return -1;
        }

        printf("F0 data:");
        for (i = 0; i < sizeof(data); i++)
                printf("%02x ", data[i]);
        printf("\n");
        return 0;
}
其中libusb_control_transfer() 裡的第二個參數決定 request 的型態,以上例是 LIBUSB_REQUEST_TYPE_VENDOR, 這個是非 STANDARD, 因此視 application(device) 而定。 .這邊再舉個例子:
int config;
    uint8_t tmp = 0;
    r = libusb_control_transfer(dev, LIBUSB_ENDPOINT_IN,
           LIBUSB_REQUEST_GET_CONFIGURATION, 0, 0, &tmp, 1, 1000);
    if (r == 1) {
          config = tmp;
    }
.最後再來看一個例子
void check_device(libusb_device *dev)
{
  ...
  int r = libusb_get_device_descriptor(dev, &desc);  // 先取得 device descriptor,因為內藏豐富資訊
  ...
  if (!is_adb_interface (desc.idVendor, desc.idProduct,
                         ADB_CLASS, ADB_SUBCLASS, ADB_PROTOCOL))
  { .... }

  /* 底下兩行再取得 device handler 的 bus, address 資訊, 以便給 libusb_open() 使用 */
  uh.dev_bus = libusb_get_bus_number(dev);
  uh.dev_addr = libusb_get_device_address(dev);

  /* 底下四行取得 device 的 active config descriptor,必須其 interface 有值才合法
     因為每個 usb device 是可以 config 成多個不同 device type,
     在此只需要取回現行的 config 即可(即 active config)
  */
  r = libusb_get_active_config_descriptor(dev, &config);
  if (config->interface != NULL) {
        found = check_usb_interfaces(config, &desc, &uh); // 檢查是否 support android adb
  }

  /* 底下開始要進一步與 device 溝通取回更多資訊 */

  r = libusb_open(dev, &uh.devh); // 類似 libusb_open_device_with_vid_pid()
  uh.dev = dev;

  if (found >= 0) {
        ....
        // 在對 endpoint 進行 I/O 之前,一定要 claim interface
        uh.interface = found;
        r = libusb_claim_interface(uh.devh, uh.interface);
        ....
        if (desc.iSerialNumber) {
            // reading serial
            uint16_t    buffer[128] = {0};
            uint16_t    languages[128] = {0};
            int languageCount = 0;

            memset(languages, 0, sizeof(languages));
            r = libusb_control_transfer(uh.devh,
                LIBUSB_ENDPOINT_IN |  LIBUSB_REQUEST_TYPE_STANDARD | LIBUSB_RECIPIENT_DEVICE,
                LIBUSB_REQUEST_GET_DESCRIPTOR, LIBUSB_DT_STRING << 8,
                0, (uint8_t *)languages, sizeof(languages), 0);
            ....
            languageCount = (r - 2) / 2;
            for (i = 1; i <= languageCount; ++i) {
                memset(buffer, 0, sizeof(buffer));

                /* 底下透過 request type 為 LIBUSB_REQUEST_TYPE_STANDARD 來指定
                   LIBUSB_REQUEST_GET_DESCRIPTOR 的方式取回 android device ID string
                   其傳回值型態由 LIBUSB_DT_STRING 指定,其值為 iSerialNumber
                */
                r = libusb_control_transfer(uh.devh,
                    LIBUSB_ENDPOINT_IN |  LIBUSB_REQUEST_TYPE_STANDARD | LIBUSB_RECIPIENT_DEVICE,
                    LIBUSB_REQUEST_GET_DESCRIPTOR, (LIBUSB_DT_STRING << 8) | desc.iSerialNumber,
                    languages[i], (uint8_t *)buffer, sizeof(buffer), 0);
                .... // 處理存在 buffer 中的 serial 資訊,也就是 android device ID
            }
            if (register_device(&uh, serial) == 0) {
                ..... // fail
            }
            else { // success
                libusb_ref_device(dev);
            }
        }
  }
  ......
}

0 意見: