mirror of https://github.com/nirenjan/libx52.git
251 lines
7.0 KiB
C
251 lines
7.0 KiB
C
/*
|
|
* 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 <stdlib.h>
|
|
#include <libusb-1.0/libusb.h>
|
|
#include "libusbx52.h"
|
|
|
|
int libusb_init(libusb_context **ctx)
|
|
{
|
|
int rc;
|
|
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)
|
|
{
|
|
if (ctx) {
|
|
if (ctx->devices) {
|
|
free(ctx->devices);
|
|
}
|
|
free(ctx);
|
|
}
|
|
}
|
|
|
|
void libusb_set_debug(libusb_context *ctx, int level)
|
|
{
|
|
/* Set the debug level appropriately */
|
|
ctx->debug_level = level;
|
|
}
|
|
|
|
ssize_t libusb_get_device_list(libusb_context *ctx, libusb_device ***list)
|
|
{
|
|
/* Allocate a list of num_devices, each pointing to a corresponding
|
|
* 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)
|
|
{
|
|
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,
|
|
struct libusb_device_descriptor *desc)
|
|
{
|
|
/* Copy the descriptor to the destination address */
|
|
*desc = dev->desc;
|
|
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)
|
|
{
|
|
/* Allocate a handle for the application */
|
|
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)
|
|
{
|
|
/* 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,
|
|
uint8_t request_type,
|
|
uint8_t bRequest,
|
|
uint16_t wValue,
|
|
uint16_t wIndex,
|
|
unsigned char *data,
|
|
uint16_t wLength,
|
|
unsigned int timeout)
|
|
{
|
|
/* 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;
|
|
}
|