mirror of https://github.com/nirenjan/libx52.git
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
parent
dc7300db26
commit
ea14d1132d
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
Loading…
Reference in New Issue