Asynchronous bulk transfer using libusb



The 'linusb' is one of the most used open source library for work with usb devices. It allow to make the basic usb data transfers operation in a little bit easier way compared to standard system calls. Here a short tutorial regarding how to use asynchronous bulk transfer.


Asynchronous transfer allow to launch the transfer command and return immediately, a callback function will be called once the data will be transfer/received completely. Using such transfer mode is quite simple, let's go to see a practical example. At first we have to initialize libusb library and look for the USB device we want to communicate to. Usually device is referred using the two values called Vendor ID and Product ID. The first is a unique number "assigned" to each hardware device producer and the second is a number reporting the device type and model.

libusb_device_handle *dev_handle = NULL;
libusb_device **list;
size_t count, i;

// Initialize library
libusb_init(NULL);

// Get list of USB devices currently connected
count = libusb_get_device_list(NULL, &list);

for(i = 0; i < count; i++) 
{
   struct libusb_device_descriptor desc;

   libusb_get_device_descriptor(list[i], &desc);
 
   // Is our device?
   if(desc.idVendor == 0x1234 && desc.idProduct == 0x5678)
   {
      // Open USB device and get handle
      libusb_open(list[i], &dev_handle);
      break;
   }
}

libusb_free_device_list(list, 1);

Now we have the handle to the USB device we want to communicate. The steps to performs an asynchronous bulk transfer are basically two, first prepare the structure with all the data for "instruct" USB controller to make request operation and then submit the data for start the transfer. Please note that the transfer direction (IN or OUT) is determined by the USB endpoint type we want to communicate to/from. An USB device usually provide some connections called Endpoint for allow data transfer between host and device. An Endpoint is usually marked as IN and OUT where, please note, in/out direction are from host point of view. This mean an endpoint marked as IN, for example, transfer data from device to host (usually the PC where the USB device is connected). Once clarified this point we can move to see the asynchronous transfer procedure as follow:

struct libusb_transfer *xfr;
unsigned char *data;
const unsigned int size = 1024;

data = malloc(size);
xfr = libusb_alloc_transfer(0);

libusb_fill_bulk_transfer(xfr,
                          dev_handle,
                          0x82, // Endpoint ID
                          data,
                          size,
                          callbackUSBTransferComplete,
                          NULL,
                          5000
                          );

if(libusb_submit_transfer(xfr) < 0)
{
    // Error
    libusb_free_transfer(xfr);
    free(data);
}

Detailed explanation about each params used can be found at official documentation here. In this example we prepare for a bulk transfer from the device using endpoint 0x82. Data size to transfer are 1024 bytes and timeout is 5000 milliseconds. callbackUSBTransferComplete is the name of the function automatically called when the transfer will be completed. Please note here the function will be called also in case of errors. It's necessary to check params returned  as follow:

void callbackUSBTransferComplete(struct libusb_transfer *xfr)
{
    switch(xfr->status)
    {
        case LIBUSB_TRANSFER_COMPLETED:
            // Success here, data transfered are inside 
            // xfr->buffer
            // and the length is
            // xfr->actual_length
            break;
        case LIBUSB_TRANSFER_CANCELLED:
        case LIBUSB_TRANSFER_NO_DEVICE:
        case LIBUSB_TRANSFER_TIMED_OUT:
        case LIBUSB_TRANSFER_ERROR:
        case LIBUSB_TRANSFER_STALL:
        case LIBUSB_TRANSFER_OVERFLOW:
            // Various type of errors here
            break;
    }
}

Now the transfer code seem complete. However an additional step have to be made. What is not very clear here (at least it wasn't clear for me in the past) is that the "internal USB engine" who make the transfer and automatically call the callback function is not in a separate thread working in parallel but, for have it working as expected, need to be "keep alive" by continuously calling the function libusb_handle_events as follow:

while(1)
{
    if(libusb_handle_events(NULL) != LIBUSB_SUCCESS) break;
}

As you can note this is an infinite loop with minimum of error check used just for example purpose. In case you need to manage additional error conditions and provide some way to exit from loop if your software require it. This small snippet is only to explain that if you don't continuously call the libusb_handle_events function your callback function will never be called also if the USB transfer was completed (and this can result in many hours lost in search for the problem... ^_^).


Comments

  1. THANK YOU!!!! This has to be the dumbest part of their design. If I wanted to poll, I wouldn't have used their 'asynchronous' API.

    ReplyDelete
  2. Holy shit! Now there are other methods, but you give me the point!

    ReplyDelete
  3. Hello, If I want to send a command to USB device first and then receive a response, what is the calling sequence for libusb_fill_bulk_transfer()?

    ReplyDelete
    Replies
    1. Did you ever find out an answer for this?

      Delete
    2. Calling sequence is send command to the USB device (the command depends by device type) and use same function to receive commands. As explaiend in the article out or in direction, that mean send or receive data, depends by the USB endpoit you use. Function is the same but you have to make your code asyncronous for support use of this library. Check the examples provided with the source library package.

      Delete
    3. Say I have to read block number 10 from a USB stick. In synchronous transfer, I would use libusb_bulk_transfer first to send a command and then again use the same function to read the data. In case of async how would it look?

      Do I send a command 1st asynchronously and then in the callback function read the data or can I use just 1 transfer to send command and read the data?

      Delete
    4. Do you really need asynchronous mode for this task? I gues in this case synchronous mode would be better to keep code less complex.

      Delete
    5. Yeah i agree with you but I need to write huge chunk of data say like 1 GB using async transfer, read it back and compare it. That's the requirement. Using synchronous transfer, I can write/read 15 MB of data using single libusb_bulk_transfer and then I just change the block numbers while passing the command and write/read next 15 MB of data. Is there a way to do it using asyn?

      Delete
    6. Just for know, have you already tried to move synchronous USB write/read/compare code in a secondary thread? This will help you to keep the code clean and have same behaviour od synchronous mode. Onestly I never tried a task as you require in "pure" asynchronous mode, I'm sorry. I guess you can call the next write/read operation from the callback function since you are going to execute antoher asynchronous task but, in any case, data comparison will lock the main thread equally. This is the reasons I suggested to move all in a secondary thread...

      Delete
    7. Data comparison is not an issue for me. I need to make sure that read and write is at max speed. I will try your secondary thread option. Looks feasible and promising. Thanks for all your help.

      Delete
  4. Thanks for this, I wouldn't have found libusb_handle_events for a long time :)

    ReplyDelete
  5. Thanks for confirming that the callbacks are called in libusb_handle_events() and not in a separate thread. I was quite confused by this at first but I expected it to be like that cause I read that libusb doesn't create its own thread. That is not really the behavior I'd expect from an asynchronous IO transfer, therefore the confusion.

    ReplyDelete
  6. This is MOST excellent, except I think you meant "== LIBUSB_SUCCESS" not "!= LIBUSB_SUCCESS" to check the return from libusb_handle_events. That's what worked for me. Otherwise it hangs the program.

    ReplyDelete
  7. Per the note on their docs page, it sounds like the intent here is for that function to be explicitly placed in a separate event handling thread: https://libusb.sourceforge.io/api-1.0/group__libusb__asyncio.html#eventthread

    ReplyDelete

Post a Comment

Popular posts from this blog

Access GPIO from Linux user space

Android: adb push and read-only file system error

Tree in SQL database: The Nested Set Model