Add implementation of stub libusb library

This commit adds the implementation of the stub libusb library in order
to use it in an LD_PRELOAD environment. This also adds the utility
programs to create a device list and sample output to compare against.
pull/13/head
nirenjan 2017-07-27 16:43:01 -07:00
parent dc7300db26
commit ea14d1132d
7 changed files with 436 additions and 14 deletions

1
.gitignore vendored
View File

@ -9,6 +9,7 @@ cli/x52cli*
test/x52test* test/x52test*
util/util_char_map.c util/util_char_map.c
util/x52charmapgen* util/x52charmapgen*
libusbx52/x52test*
x52pro-linux-*.tar.gz x52pro-linux-*.tar.gz
# Module files # Module files

View File

@ -0,0 +1,35 @@
LibUSB X52 stub library
=======================
The libusbx52 stub library is a convenience library to help test the libusb
functions used by libx52 and associated code. It simulates the behavior of the
libusb functions used by the libx52 library, but doesn't actually control any
real hardware, and simply updates a few in-memory data structures.
The idea behind `libusbx52.so` is to use it as an LD_PRELOAD library, where it
will override the real functions used by `libx52.so`. The use case for this
scenario is in an automated testing environment, where a test runner could set
up the list of devices manually and simulate various scenarios.
# Design Overview
Unfortunately, the automake infrastructure does not support the use of
LD_PRELOAD because it is deemed "non-portable" in the automake sense. As a
result, this is now up to a test runner application to implement a method to
control the data passed between two processes.
# Data Structures
The server process is responsible for setting up the initial set of USB devices.
As far as libx52 is concerned, the only fields that it uses in the USB
descriptor are the idVendor and idProduct fields. These are written to a file
that is read by the libusbx52 stubs to populate the device list.
Once the file has been written by the server, the libusb_init stub function in
the client can read the file and populate the internal data structures as
necessary.
The client can also write to a separate file to record the USB communication
sent across to the simulated device.

View File

@ -1,8 +1,18 @@
ACLOCAL_AMFLAGS = -I m4 ACLOCAL_AMFLAGS = -I m4
# libusb stub library for use by test programs # libusb stub library for use by test programs
check_LTLIBRARIES = libusbx52.la noinst_LTLIBRARIES = libusbx52.la
libusbx52_la_SOURCES = usb_x52_stub.c libusbx52_la_SOURCES = usb_x52_stub.c
libusbx52_la_LDFLAGS = -rpath /nowhere
# Utility programs for use by tests
noinst_PROGRAMS = x52test_create_device_list x52test_log_actions
x52test_create_device_list_SOURCES = util/create_device_list.c
x52test_log_actions_SOURCES = util/log_actions.c
x52test_log_actions_CFLAGS = @X52_INCLUDE@
x52test_log_actions_LDADD = libusbx52.la
EXTRA_DIST = README.md EXTRA_DIST = README.md

View File

@ -0,0 +1,53 @@
/*
* LibUSB stub driver for testing the Saitek X52/X52 Pro
*
* Copyright (C) 2017 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, version 2.
*
*/
#include <stdio.h>
#include <libusb-1.0/libusb.h>
struct libusb_device {
struct libusb_context *context;
int index;
int ref_count;
struct libusb_device_descriptor desc;
};
struct libusb_context {
int block_size; // Set to LIBUSBX52_MEMORY_BLOCK_SIZE
int max_devices; // Calculated based on block_size
int debug_level;
int num_devices;
struct libusb_device *devices;
};
struct libusb_device_handle {
struct libusb_context *ctx;
struct libusb_device *dev;
int packets_written;
FILE *packet_data_file;
};
/**
* @brief File location of the device list file
*
* This file contains a list of VIDs and PIDs in hexadecimal format separated
* by spaces. There must be an even number of entries, each pair corresponding
* to a (VID, PID) tuple identifying a single USB device.
*/
#define INPUT_DEVICE_LIST_FILE "/tmp/libusbx52_device_list"
/**
* @brief File location of the communication data file
*
* This file contains the libusb APIs called by libx52, after a device has
* been opened, i.e., all APIs that operate on a libusb_device_handle
*/
#define OUTPUT_DATA_FILE "/tmp/libusbx52_output_data"

View File

@ -9,52 +9,219 @@
* *
*/ */
#include <stdio.h>
#include <stdlib.h>
#include <libusb-1.0/libusb.h> #include <libusb-1.0/libusb.h>
#include "libusbx52.h"
int libusb_init(libusb_context **ctx) int libusb_init(libusb_context **ctx)
{ {
// TODO int rc;
return 0; int dev_count;
int vid;
int pid;
int parsed;
FILE *dev_list;
/*
* Technically, libusb_init can be called with a NULL context pointer,
* in which case, libusb will allocate a default context. However, in
* the case of libx52, the libusb APIs are always called with a non-NULL
* context pointer, which is then initialized with the allocated context
* pointer.
*/
libusb_context *tmp_ctx = calloc(1, sizeof(*tmp_ctx));
if (tmp_ctx == NULL) {
rc = LIBUSB_ERROR_NO_MEM;
goto init_err_recovery;
}
dev_list = fopen(INPUT_DEVICE_LIST_FILE, "r");
if (dev_list == NULL) {
rc = LIBUSB_ERROR_IO;
goto init_err_recovery;
}
/* Determine the number of devices in the file */
dev_count = 0;
do {
parsed = fscanf(dev_list, "%x %x", &vid, &pid);
/*
* If we have read fewer than 2 items, then quit.
* We've read all we can
*/
if (parsed < 2) {
break;
}
dev_count++;
} while (!feof(dev_list));
/* Make sure we have at least 1 device */
if (dev_count == 0) {
rc = LIBUSB_ERROR_NOT_FOUND;
goto init_err_recovery;
}
/* We now have the number of devices, allocate memory for them */
tmp_ctx->devices = calloc(dev_count, sizeof(*(tmp_ctx->devices)));
if (tmp_ctx->devices == NULL) {
rc = LIBUSB_ERROR_NO_MEM;
goto init_err_recovery;
}
tmp_ctx->num_devices = dev_count;
/* Rewind and read the file again, but now put them into the device list */
rewind(dev_list);
for (int i = 0; i < dev_count && !feof(dev_list); i++) {
/* Set the base fields */
tmp_ctx->devices[i].context = tmp_ctx;
tmp_ctx->devices[i].index = i;
parsed = fscanf(dev_list, "%x %x", &vid, &pid);
if (parsed < 2) {
/* Parse error, skip this device */
continue;
}
/* Set the VID & PID */
tmp_ctx->devices[i].desc.idVendor = vid;
tmp_ctx->devices[i].desc.idProduct = pid;
}
/* Done, close the file and return */
fclose(dev_list);
*ctx = tmp_ctx;
return LIBUSB_SUCCESS;
init_err_recovery:
/* Close the device list file if it is open */
if (dev_list) {
fclose(dev_list);
}
/* Free up any allocated memory */
libusb_exit(tmp_ctx);
return rc;
} }
void libusb_exit(libusb_context *ctx) void libusb_exit(libusb_context *ctx)
{ {
// TODO if (ctx) {
if (ctx->devices) {
free(ctx->devices);
}
free(ctx);
}
} }
void libusb_set_debug(libusb_context *ctx, int level) void libusb_set_debug(libusb_context *ctx, int level)
{ {
// TODO /* Set the debug level appropriately */
ctx->debug_level = level;
} }
ssize_t libusb_get_device_list(libusb_context *ctx, libusb_device ***list) ssize_t libusb_get_device_list(libusb_context *ctx, libusb_device ***list)
{ {
// TODO /* Allocate a list of num_devices, each pointing to a corresponding
return 0; * libusb_device structure.
*
* Actually, the allocation adds one more than needed, since the list
* is supposed to be NULL terminated, according to the libusb docs.
*/
libusb_device **tmp_list = calloc(ctx->num_devices + 1, sizeof(*tmp_list));
libusb_device *dev;
if (tmp_list == NULL) {
return LIBUSB_ERROR_NO_MEM;
}
/* Initialize the list with pointers to the individual devices */
for (int i = 0; i < ctx->num_devices; i++) {
dev = &(ctx->devices[i]);
/* Increment the refcount */
dev->ref_count += 1;
tmp_list[i] = dev;
}
*list = tmp_list;
return ctx->num_devices;
} }
void libusb_free_device_list(libusb_device **list, int unref_devices) void libusb_free_device_list(libusb_device **list, int unref_devices)
{ {
// TODO if (unref_devices) {
for (libusb_device **dev = list; *dev; dev++) {
/* Decrement the refcount */
(*dev)->ref_count -= 1;
}
}
free(list);
} }
int libusb_get_device_descriptor(libusb_device *dev, int libusb_get_device_descriptor(libusb_device *dev,
struct libusb_device_descriptor *desc) struct libusb_device_descriptor *desc)
{ {
// TODO /* Copy the descriptor to the destination address */
*desc = dev->desc;
return 0; return 0;
} }
#define LIBUSB_DUMP_LOG_FILE(hdl, loglevel, fmt_str, ...) do { \
if (hdl->ctx->debug_level != LIBUSB_LOG_LEVEL_NONE && \
hdl->ctx->debug_level >= loglevel) { \
fprintf(hdl->packet_data_file, "%s: " fmt_str, __func__, __VA_ARGS__); \
} \
} while (0)
int libusb_open(libusb_device *dev, libusb_device_handle **handle) int libusb_open(libusb_device *dev, libusb_device_handle **handle)
{ {
// TODO /* Allocate a handle for the application */
return 0; libusb_device_handle *tmp_hdl = calloc(1, sizeof(*tmp_hdl));
if (tmp_hdl == NULL) {
return LIBUSB_ERROR_NO_MEM;
}
/* Increment the reference count for the underlying device */
dev->ref_count += 1;
/* Populate the handle structure with the right values */
tmp_hdl->ctx = dev->context;
tmp_hdl->dev = dev;
tmp_hdl->packet_data_file = fopen(OUTPUT_DATA_FILE, "w");
/* Make sure that the file opened correctly */
if (tmp_hdl->packet_data_file == NULL) {
/* Error condition, return with a failure */
free(tmp_hdl);
return LIBUSB_ERROR_IO;
}
LIBUSB_DUMP_LOG_FILE(tmp_hdl, LIBUSB_LOG_LEVEL_DEBUG,
"VID: %04x PID: %04x idx: %d ref: %d\n",
dev->desc.idVendor, dev->desc.idProduct, dev->index, dev->ref_count);
*handle = tmp_hdl;
return LIBUSB_SUCCESS;
} }
void libusb_close(libusb_device_handle *dev_handle) void libusb_close(libusb_device_handle *dev_handle)
{ {
// TODO /* Decrement the refcount for the underlying device */
dev_handle->dev->ref_count -= 1;
libusb_device *dev = dev_handle->dev;
LIBUSB_DUMP_LOG_FILE(dev_handle, LIBUSB_LOG_LEVEL_DEBUG,
"VID: %04x PID: %04x idx: %d ref: %d\n",
dev->desc.idVendor, dev->desc.idProduct, dev->index, dev->ref_count);
/* Close the file */
fclose(dev_handle->packet_data_file);
/* Free any memory used */
free(dev_handle);
} }
int libusb_control_transfer(libusb_device_handle *dev_handle, int libusb_control_transfer(libusb_device_handle *dev_handle,
@ -66,6 +233,18 @@ int libusb_control_transfer(libusb_device_handle *dev_handle,
uint16_t wLength, uint16_t wLength,
unsigned int timeout) unsigned int timeout)
{ {
// TODO /* Always log the control transfer */
fprintf(dev_handle->packet_data_file,
"%s: RqType: %02x bRequest: %02x wValue: %04x wIndex: %04x timeout: %d\n",
__func__, request_type, bRequest, wValue, wIndex, timeout);
if (data != NULL) {
fprintf(dev_handle->packet_data_file, "%s: Data[%d]: ", __func__,
wLength);
for (int i = 0; i < wLength; i++) {
fprintf(dev_handle->packet_data_file, "%02x ", data[i]);
}
fprintf(dev_handle->packet_data_file, "\n");
}
return 0; return 0;
} }

View File

@ -0,0 +1,47 @@
/*
* LibUSB test utility library
*
* This program generates a list of USB devices for use by libusbx52.so
*
* Copyright (C) Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, version 2.
*
*/
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "libusbx52.h"
int main(int argc, char *argv[])
{
FILE *data;
char **id_pair;
int vid;
int pid;
int parsed;
data = fopen(INPUT_DEVICE_LIST_FILE, "w");
if (data == NULL) {
fprintf(stderr, "Unable to open %s for writing\n",
INPUT_DEVICE_LIST_FILE);
fprintf(stderr, "%s\n", strerror(errno));
}
/* Process arguments until there are fewer than 2 remaining */
for (int i = 1; i < argc && (argc - i) >= 2; i += 2) {
parsed = sscanf(argv[i], "%x", &vid);
if (parsed != 1) break;
parsed = sscanf(argv[i+1], "%x", &pid);
if (parsed != 1) break;
fprintf(data, "%04x %04x\n", vid, pid);
}
fclose(data);
return 0;
}

View File

@ -0,0 +1,97 @@
/*
* LibUSB test utility library
*
* This program calls the libusb_control_transfer API to log the bytes to
* the given file. This is useful to generate a file with the same syntax
* and verify that they match.
*
* Copyright (C) Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, version 2.
*
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include "libusbx52.h"
#include "x52_commands.h"
libusb_context *global_context;
static int send_command(libusb_device_handle *hdl, uint16_t index, uint16_t value)
{
return libusb_control_transfer(hdl,
LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE | LIBUSB_ENDPOINT_OUT,
X52_VENDOR_REQUEST, value, index, NULL, 0, 5000);
}
static libusb_device_handle *libusbx52_init(void)
{
int rc;
ssize_t count;
int i;
libusb_device **list;
libusb_device *device;
libusb_device_handle *hdl = NULL;
struct libusb_device_descriptor desc;
rc = libusb_init(&global_context);
if (rc) {
return NULL;
}
libusb_set_debug(global_context, LIBUSB_LOG_LEVEL_ERROR);
count = libusb_get_device_list(global_context, &list);
for (i = 0; i < count; i++) {
device = list[i];
if (!libusb_get_device_descriptor(device, &desc)) {
if (desc.idVendor == 0x06a3) {
if (desc.idProduct == 0x0762) {
rc = libusb_open(device, &hdl);
if (rc) {
if (hdl) free(hdl);
hdl = NULL;
break;
}
}
}
}
}
libusb_free_device_list(list, 1);
return hdl;
}
int main(int argc, char *argv[])
{
int index;
int value;
int parsed;
libusb_device_handle *hdl;
libusb_context *ctx;
hdl = libusbx52_init();
ctx = hdl->ctx;
/* Process arguments until there are fewer than 2 remaining */
for (int i = 1; i < argc && (argc - i) >= 2; i += 2) {
parsed = sscanf(argv[i], "%x", &index);
if (parsed != 1) break;
parsed = sscanf(argv[i+1], "%x", &value);
if (parsed != 1) break;
send_command(hdl, index, value);
}
libusb_close(hdl);
libusb_exit(ctx);
return 0;
}