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