Using Linux Libusb

Using Linux Libusb

This article briefly explains how to write a Libusb application to read USB Mouse data. Libusb mainly supports synchronous and asynchronous communication methods, with corresponding Libusb interfaces introduced later.

1. Environment Preparation

First, download the Libusb library.

git clone https://github.com/libusb/libusb.git

Compile the Libusb library.

./configure --disable-udev    \
--host=arm-linux-gnueabi     \
--build=x86_64-pc-linux-gnu     \
--prefix=/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/libusb/output     \
CC=/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gcc     \
AR=/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-ar     \
LD=gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-ld

Compile the test application and install it to the lib directory or usr/lib.

../../aarch64-none-linux-gnu-gcc hid_test.c -o hid_test .libs/libusb-1.0.a -lpthread
../../aarch64-none-linux-gnu-gcc a.c -o a .libs/libusb-1.0.a -lpthread

(Use -v to find header files)

2. Test Code

2.1 Using Synchronous Method

#include <stdio.h>
#include <stdlib.h>
#include "libusb.h"

int main(void)
{
        int r;
        libusb_device *dev = NULL, **devs;
        libusb_device_handle *dev_handle = NULL;
        ssize_t num_devices; // Change to ssize_t type
        int endpoint = 0;
        int found = 0;
        int interface_num = -1; // Initialize to invalid value
        struct libusb_config_descriptor *config_desc;
        libusb_context *ctx = NULL;

        /* libusb_init */
        r = libusb_init(&ctx);
        if (r < 0) {
                fprintf(stderr, "failed to init libusb: %s\n", libusb_error_name(r));
                return 1;
        }

        /* get device list */
        num_devices = libusb_get_device_list(ctx, &devs);
        if (num_devices < 0) {
                fprintf(stderr, "libusb_get_device_list() failed: %s\n", libusb_error_name((int)num_devices));
                libusb_exit(ctx);
                return 1;
        }

        /* for each device, get config desc */
        for (int i = 0; i < num_devices; i++)
        {
                dev = devs[i];
                r = libusb_get_config_descriptor(dev, 0, &config_desc);
                if (r) {
                        fprintf(stderr, "libusb_get_config_desc failed: %s\n", libusb_error_name(r));
                        continue;
                }

                for(int interface = 0; interface < config_desc->bNumInterfaces; interface++) {
                        const struct libusb_interface_descriptor *intf_desc = &config_desc->interface[interface].altsetting[0];
                        interface_num = intf_desc->bInterfaceNumber;

                        if (intf_desc->bInterfaceClass != 3 || intf_desc->bInterfaceProtocol != 2)
                                continue;
                        else {
                                for (int ep = 0; ep < intf_desc->bNumEndpoints; ep++) {
                                        if ((intf_desc->endpoint[ep].bmAttributes & 3) == LIBUSB_TRANSFER_TYPE_INTERRUPT ||
                                                    (intf_desc->endpoint[ep].bEndpointAddress & 0x80) == LIBUSB_ENDPOINT_IN) {
                                                endpoint = intf_desc->endpoint[ep].bEndpointAddress;
                                                found = 1;
                                                printf("found interface %d ep %x\n", interface_num, endpoint);
                                                break;
                                        }
                                }
                        }
                        if (found)
                                break;
                }

                libusb_free_config_descriptor(config_desc);
                if (found)
                        break;
        }

        if (!found) {
                fprintf(stderr, "No compatible device found\n");
                libusb_free_device_list(devs, 1);
                libusb_exit(ctx);
                return 1;
        }

        /* libusb open */
        if (dev) {
                r = libusb_open(dev, &dev_handle);
                if (r < 0) {
                        fprintf(stderr, "failed to open device: %s\n", libusb_error_name(r));
                        libusb_free_device_list(devs, 1);
                        libusb_exit(ctx);
                        return 1;
                }
        } else {
                fprintf(stderr, "No device to open\n");
                libusb_free_device_list(devs, 1);
                libusb_exit(ctx);
                return 1;
        }

        /* set detach */
        r = libusb_set_auto_detach_kernel_driver(dev_handle, 1);
        if (r < 0) {
                fprintf(stderr, "failed to set auto detach: %s\n", libusb_error_name(r));
        }

        /* claim interface */
        r = libusb_claim_interface(dev_handle, interface_num);
        if (r < 0) {
                fprintf(stderr, "failed to claim interface: %s\n", libusb_error_name(r));
                libusb_close(dev_handle);
                libusb_free_device_list(devs, 1);
                libusb_exit(ctx);
                return 1;
        }

        /* Now we can safely free the device list since we have opened the device */
        libusb_free_device_list(devs, 1);
        devs = NULL;

        /* libusb_interrupt_transfer */
        unsigned char report_buffer[4];
        int actual_length;

        printf("Starting interrupt transfer loop...\n");

        while (1) {
                r = libusb_interrupt_transfer(dev_handle, endpoint, report_buffer, sizeof(report_buffer), &actual_length, 0);
                if (r == 0) {
                        printf("Received %d bytes: ", actual_length);
                        for (int i = 0; i < actual_length; i++) {
                                printf("%02x ", report_buffer[i]);
                        }
                        printf("\n");
                } else {
                        fprintf(stderr, "failed to transfer: %s\n", libusb_error_name(r));
                        // Do not exit, it may just be a temporary error
                }

                // Add a short delay to avoid high CPU usage
                usleep(10000); // 10ms
        }

        /* libusb close */
        libusb_release_interface(dev_handle, interface_num);
        libusb_close(dev_handle);
        libusb_exit(ctx);

        return 0;
}

2.2 Using Asynchronous Method

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "libusb.h"

#define NUM_TRANSFERS 4  // Number of simultaneous asynchronous transfers

struct transfer_context {
    libusb_device_handle *dev_handle;
    unsigned char endpoint;
    unsigned char *buffer;
    int interface_num;
};

static void LIBUSB_CALL async_callback(struct libusb_transfer *transfer)
{
    struct transfer_context *ctx = (struct transfer_context *)transfer->user_data;
    
    if (transfer->status == LIBUSB_TRANSFER_COMPLETED) {
        printf("Received %d bytes: ", transfer->actual_length);
        for (int i = 0; i < transfer->actual_length; i++) {
            printf("%02x ", transfer->buffer[i]);
        }
        printf("\n");
        
        // Resubmit transfer
        int r = libusb_submit_transfer(transfer);
        if (r < 0) {
            fprintf(stderr, "failed to resubmit transfer: %s\n", libusb_error_name(r));
        }
    } else {
        fprintf(stderr, "transfer failed: status %d\n", transfer->status);
        
        // Decide whether to retry based on different statuses
        if (transfer->status != LIBUSB_TRANSFER_CANCELLED) {
            // Retry after a short delay
            usleep(100000);
            int r = libusb_submit_transfer(transfer);
            if (r < 0) {
                fprintf(stderr, "failed to resubmit transfer: %s\n", libusb_error_name(r));
            }
        }
    }
}

int main(void)
{
    int r;
    libusb_device *dev = NULL, **devs;
    libusb_device_handle *dev_handle = NULL;
    ssize_t num_devices;
    int endpoint = 0;
    int found = 0;
    int interface_num = -1;
    struct libusb_config_descriptor *config_desc;
    libusb_context *ctx = NULL;

    /* libusb_init */
    r = libusb_init(&ctx);
    if (r < 0) {
        fprintf(stderr, "failed to init libusb: %s\n", libusb_error_name(r));
        return 1;
    }

    /* get device list */
    num_devices = libusb_get_device_list(ctx, &devs);
    if (num_devices < 0) {
        fprintf(stderr, "libusb_get_device_list() failed: %s\n", libusb_error_name((int)num_devices));
        libusb_exit(ctx);
        return 1;
    }

    /* for each device, get config desc */
    for (int i = 0; i < num_devices; i++)
    {
        dev = devs[i];
        r = libusb_get_config_descriptor(dev, 0, &config_desc);
        if (r) {
            fprintf(stderr, "libusb_get_config_desc failed: %s\n", libusb_error_name(r));
            continue;
        }

        for(int interface = 0; interface < config_desc->bNumInterfaces; interface++) {
            const struct libusb_interface_descriptor *intf_desc = &config_desc->interface[interface].altsetting[0];
            interface_num = intf_desc->bInterfaceNumber;

            if (intf_desc->bInterfaceClass != 3 || intf_desc->bInterfaceProtocol != 2)
                continue;
            else {
                for (int ep = 0; ep < intf_desc->bNumEndpoints; ep++) {
                    if ((intf_desc->endpoint[ep].bmAttributes & 3) == LIBUSB_TRANSFER_TYPE_INTERRUPT ||
                        (intf_desc->endpoint[ep].bEndpointAddress & 0x80) == LIBUSB_ENDPOINT_IN) {
                        endpoint = intf_desc->endpoint[ep].bEndpointAddress;
                        found = 1;
                        printf("found interface %d ep %x\n", interface_num, endpoint);
                        break;
                    }
                }
            }
            if (found)
                break;
        }

        libusb_free_config_descriptor(config_desc);
        if (found)
            break;
    }

    if (!found) {
        fprintf(stderr, "No compatible device found\n");
        libusb_free_device_list(devs, 1);
        libusb_exit(ctx);
        return 1;
    }

    /* libusb open */
    if (dev) {
        r = libusb_open(dev, &dev_handle);
        if (r < 0) {
            fprintf(stderr, "failed to open device: %s\n", libusb_error_name(r));
            libusb_free_device_list(devs, 1);
            libusb_exit(ctx);
            return 1;
        }
    } else {
        fprintf(stderr, "No device to open\n");
        libusb_free_device_list(devs, 1);
        libusb_exit(ctx);
        return 1;
    }

    /* set detach */
    r = libusb_set_auto_detach_kernel_driver(dev_handle, 1);
    if (r < 0) {
        fprintf(stderr, "failed to set auto detach: %s\n", libusb_error_name(r));
    }

    /* claim interface */
    r = libusb_claim_interface(dev_handle, interface_num);
    if (r < 0) {
        fprintf(stderr, "failed to claim interface: %s\n", libusb_error_name(r));
        libusb_close(dev_handle);
        libusb_free_device_list(devs, 1);
        libusb_exit(ctx);
        return 1;
    }

    /* Now we can safely free the device list since we have opened the device */
    libusb_free_device_list(devs, 1);
    devs = NULL;

    /* Set up asynchronous transfers */
    struct libusb_transfer *transfers[NUM_TRANSFERS];
    struct transfer_context contexts[NUM_TRANSFERS];
    
    printf("Starting async interrupt transfers...\n");
    
    for (int i = 0; i < NUM_TRANSFERS; i++) {
        // Allocate buffer for each transfer
        unsigned char *buffer = malloc(4);
        if (!buffer) {
            fprintf(stderr, "failed to allocate buffer\n");
            continue;
        }
        
        // Create transfer
        struct libusb_transfer *transfer = libusb_alloc_transfer(0);
        if (!transfer) {
            fprintf(stderr, "failed to allocate transfer\n");
            free(buffer);
            continue;
        }
        
        // Set transfer context
        contexts[i].dev_handle = dev_handle;
        contexts[i].endpoint = endpoint;
        contexts[i].buffer = buffer;
        contexts[i].interface_num = interface_num;
        
        // Fill transfer
        libusb_fill_interrupt_transfer(
            transfer,
            dev_handle,
            endpoint,
            buffer,
            4,
            async_callback,
            &contexts[i],
            0
        );
        
        transfers[i] = transfer;
        
        // Submit transfer
        r = libusb_submit_transfer(transfer);
        if (r < 0) {
            fprintf(stderr, "failed to submit transfer: %s\n", libusb_error_name(r));
            libusb_free_transfer(transfer);
            free(buffer);
            transfers[i] = NULL;
        }
    }

    /* Event handling loop */
    printf("Entering event handling loop...\n");
    while (1) {
        r = libusb_handle_events(ctx);
        if (r < 0) {
            fprintf(stderr, "libusb_handle_events failed: %s\n", libusb_error_name(r));
            // Serious error, exit loop
            break;
        }
    }

    /* Clean up resources */
    printf("Cleaning up...\n");
    for (int i = 0; i < NUM_TRANSFERS; i++) {
        if (transfers[i]) {
            libusb_cancel_transfer(transfers[i]);
            // In a real application, you should wait for the transfer to complete cancellation before freeing
            // Here we simplify the handling
            if (transfers[i]->buffer) {
                free(transfers[i]->buffer);
            }
            libusb_free_transfer(transfers[i]);
        }
    }

    /* libusb close */
    libusb_release_interface(dev_handle, interface_num);
    libusb_close(dev_handle);
    libusb_exit(ctx);

    return 0;
}

3. Testing

You can see the output corresponding to the interface and endpoint. If you click the mouse button at this time, there will be report output.

First byte data for left button

Received 4 bytes: 01 00 00 00 
Received 4 bytes: 00 00 00 00 

First byte data for right button

Received 4 bytes: 02 00 00 00 
Received 4 bytes: 00 00 00 00 

The second and third byte data represent X and Y axis data.

# ./test 
found interface 0 ep 81
Starting interrupt transfer loop...
Received 4 bytes: 01 00 00 00 
Received 4 bytes: 00 00 00 00 
Received 4 bytes: 02 00 00 00 
Received 4 bytes: 00 00 00 00 
Received 4 bytes: 00 fe 01 00 
Received 4 bytes: 00 fa 03 00 
Received 4 bytes: 00 f8 03 00 
Received 4 bytes: 00 f8 03 00 
Received 4 bytes: 00 f4 03 00 
Received 4 bytes: 00 f2 02 00 
Received 4 bytes: 00 f3 03 00 
Received 4 bytes: 00 f3 01 00 
Received 4 bytes: 00 f1 03 00 
Received 4 bytes: 00 f0 00 00 
Received 4 bytes: 00 ee 00 00 
Received 4 bytes: 00 ee 00 00 
Received 4 bytes: 00 ee 01 00 
Received 4 bytes: 00 ef 02 00 
Received 4 bytes: 00 ee 03 00 
Received 4 bytes: 00 f0 01 00 
Received 4 bytes: 00 f0 03 00 
Received 4 bytes: 00 ee 02 00 
Received 4 bytes: 00 f0 01 00 
Received 4 bytes: 00 f0 00 00 
Received 4 bytes: 00 f5 00 00 
Received 4 bytes: 00 f8 02 00 

Leave a Comment