1
0
Fork 0
forked from len0rd/rockbox

imxtools: rewrite sbloader to handle both versions + user friendly

The tool can now load sb1 and sb files to devices. Detection has
been improved and the tool can infer the packet size for the HID
description as well. The command line interface has been vastly
improved too, this breaks the old one.

Change-Id: I01a0ff8f8a007514aa81c56f114c0f0a86e3303c
This commit is contained in:
Amaury Pouly 2013-06-13 02:29:36 +02:00
parent 1dae3a82e2
commit 07e82c163e

View file

@ -23,12 +23,20 @@
#include <string.h>
#include <libusb.h>
#include <stdint.h>
#include <stdbool.h>
#include <getopt.h>
bool g_debug = false;
#ifndef MIN
#define MIN(a,b) ((a) < (b) ? (a) : (b))
#endif
void put32le(uint8_t *buf, uint32_t i)
#ifndef MAX
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#endif
static void put32le(uint8_t *buf, uint32_t i)
{
*buf++ = i & 0xff;
*buf++ = (i >> 8) & 0xff;
@ -36,7 +44,7 @@ void put32le(uint8_t *buf, uint32_t i)
*buf++ = (i >> 24) & 0xff;
}
void put32be(uint8_t *buf, uint32_t i)
static void put32be(uint8_t *buf, uint32_t i)
{
*buf++ = (i >> 24) & 0xff;
*buf++ = (i >> 16) & 0xff;
@ -46,6 +54,7 @@ void put32be(uint8_t *buf, uint32_t i)
enum dev_type_t
{
PROBE_DEVICE,
HID_DEVICE,
RECOVERY_DEVICE,
};
@ -66,13 +75,10 @@ struct dev_info_t g_dev_info[] =
{0x066f, 0x3600, 4096, RECOVERY_DEVICE}, /* STMP36xx */
};
int send_hid(libusb_device_handle *dev, int xfer_size, uint8_t *data, int size, int nr_xfers)
static int send_hid(libusb_device_handle *dev, int xfer_size, uint8_t *data, int size, int nr_xfers)
{
libusb_detach_kernel_driver(dev, 0);
libusb_detach_kernel_driver(dev, 4);
libusb_claim_interface(dev, 0);
libusb_claim_interface(dev, 4);
uint8_t *xfer_buf = malloc(1 + xfer_size);
uint8_t *p = xfer_buf;
@ -131,7 +137,7 @@ int send_hid(libusb_device_handle *dev, int xfer_size, uint8_t *data, int size,
return ret;
}
int send_recovery(libusb_device_handle *dev, int xfer_size, uint8_t *data, int size, int nr_xfers)
static int send_recovery(libusb_device_handle *dev, int xfer_size, uint8_t *data, int size, int nr_xfers)
{
(void) nr_xfers;
// there should be no kernel driver attached but in doubt...
@ -159,52 +165,356 @@ int send_recovery(libusb_device_handle *dev, int xfer_size, uint8_t *data, int s
return 0;
}
static void usage(void)
{
printf("sbloader [options] file\n");
printf("options:\n");
printf(" -h/-?/--help Display this help\n");
printf(" -d/--debug Enable debug output\n");
printf(" -x <size> Force transfer size\n");
printf(" -u <vid>:<pid> Force USB PID and VID\n");
printf(" -b <bus>:<dev> Force USB bus and device\n");
printf(" -p <protocol> Force protocol ('hid' or 'recovery')\n");
printf("The following devices are known to this tool:\n");
for(unsigned i = 0; i < sizeof(g_dev_info) / sizeof(g_dev_info[0]); i++)
{
const char *type = "unk";
if(g_dev_info[i].dev_type == HID_DEVICE)
type = "hid";
else if(g_dev_info[i].dev_type == RECOVERY_DEVICE)
type = "recovery";
else if(g_dev_info[i].dev_type == PROBE_DEVICE)
type = "probe";
printf(" %04x:%04x %s (%d bytes/xfer)\n", g_dev_info[i].vendor_id,
g_dev_info[i].product_id, type, g_dev_info[i].xfer_size);
}
printf("You can select a particular device by USB PID and VID.\n");
printf("In case this is ambiguous, use bus and device number.\n");
printf("Protocol is infered if possible and unspecified.\n");
printf("Transfer size is infered if possible.\n");
exit(1);
}
static bool dev_match(libusb_device *dev, struct dev_info_t *arg_di,
int usb_bus, int usb_dev, int *db_idx)
{
// match bus/dev
if(usb_bus != -1)
return libusb_get_bus_number(dev) == usb_bus && libusb_get_device_address(dev) == usb_dev;
// get device descriptor
struct libusb_device_descriptor desc;
if(libusb_get_device_descriptor(dev, &desc))
return false;
// match command line vid/pid if specified
if(arg_di->vendor_id != 0)
return desc.idVendor == arg_di->vendor_id && desc.idProduct == arg_di->product_id;
// match known vid/pid
for(unsigned i = 0; i < sizeof(g_dev_info) / sizeof(g_dev_info[0]); i++)
if(desc.idVendor == g_dev_info[i].vendor_id && desc.idProduct == g_dev_info[i].product_id)
{
if(db_idx)
*db_idx = i;
return true;
}
return false;
}
static void print_match(libusb_device *dev)
{
struct libusb_device_descriptor desc;
if(libusb_get_device_descriptor(dev, &desc))
printf("????:????");
else
printf("%04x:%04x", desc.idVendor, desc.idProduct);
printf(" @ %d.%d\n", libusb_get_bus_number(dev), libusb_get_device_address(dev));
}
static bool is_hid_dev(struct libusb_config_descriptor *desc)
{
if(desc->bNumInterfaces != 1)
return false;
if(desc->interface[0].num_altsetting != 1)
return false;
const struct libusb_interface_descriptor *intf = &desc->interface[0].altsetting[0];
if(intf->bNumEndpoints != 1)
return false;
if(intf->bInterfaceClass != LIBUSB_CLASS_HID || intf->bInterfaceSubClass != 0 ||
intf->bInterfaceProtocol != 0)
return false;
return true;
}
static bool is_recovery_dev(struct libusb_config_descriptor *desc)
{
return false;
}
static enum dev_type_t probe_protocol(libusb_device_handle *dev)
{
struct libusb_config_descriptor *desc;
if(libusb_get_config_descriptor(libusb_get_device(dev), 0, &desc))
goto Lerr;
if(is_hid_dev(desc))
return HID_DEVICE;
if(is_recovery_dev(desc))
return RECOVERY_DEVICE;
Lerr:
printf("Cannot probe protocol, please specify it on command line.\n");
exit(11);
return PROBE_DEVICE;
}
struct hid_item_t
{
int tag;
int type;
int total_size;
int data_offset;
int data_size;
};
static bool hid_parse_short_item(uint8_t *buf, int size, struct hid_item_t *item)
{
if(size == 0)
return false;
item->tag = buf[0] >> 4;
item->data_size = buf[0] & 3;
item->type = (buf[0] >> 2) & 3;
item->data_offset = 1;
item->total_size = 1 + item->data_size;
return size >= item->total_size;
}
static bool hid_parse_item(uint8_t *buf, int size, struct hid_item_t *item)
{
if(!hid_parse_short_item(buf, size, item))
return false;
/* long item ? */
if(item->data_size == 2 && item->type == 3 && item->tag == 15)
{
item->tag = buf[2];
item->data_size = buf[1];
item->total_size = 3 + item->data_size;
return size >= item->total_size;
}
else
return true;
}
static int probe_hid_xfer_size(libusb_device_handle *dev)
{
// FIXME detahc kernel and claim interface here ?
/* get HID descriptor */
uint8_t buffer[1024];
int ret = libusb_control_transfer(dev, LIBUSB_ENDPOINT_IN | LIBUSB_RECIPIENT_INTERFACE,
LIBUSB_REQUEST_GET_DESCRIPTOR, (LIBUSB_DT_REPORT << 8) | 0, 0, buffer,
sizeof(buffer), 1000);
if(ret <= 0)
goto Lerr;
/* this is not a real parse, since the HID descriptor of the device is
* is mostly trivial, we assume that all reports are made up of one item
* and simply compute the maximum of report size * report count */
int xfer_size = 0;
int report_size = 0;
int report_count = 0;
uint8_t *buf = buffer;
int size = ret;
while(true)
{
struct hid_item_t item;
if(!hid_parse_item(buf, size, &item))
break;
if(item.type == /*global*/1)
{
if(item.tag == /*report count*/9)
report_count = buf[item.data_offset];
if(item.tag == /*report size*/7)
report_size = buf[item.data_offset];
}
else if(item.type == /*main*/0)
{
if(item.tag == /*output*/9)
xfer_size = MAX(xfer_size, report_count * report_size);
}
buf += item.total_size;
size -= item.total_size;
}
return xfer_size / 8;
Lerr:
printf("Cannot probe transfer size, please specify it on command line.\n");
exit(11);
return 0;
}
static int probe_xfer_size(enum dev_type_t prot, libusb_device_handle *dev)
{
if(prot == HID_DEVICE)
return probe_hid_xfer_size(dev);
printf("Cannot probe transfer size, please specify it on command line.\n");
exit(10);
return 0;
}
int main(int argc, char **argv)
{
if(argc != 3)
if(argc <= 1)
usage();
struct dev_info_t di = {.vendor_id = 0, .product_id = 0, .xfer_size = 0,
.dev_type = PROBE_DEVICE};
int usb_bus = -1;
int usb_dev = -1;
/* parse command line */
while(1)
{
printf("usage: %s <xfer size> <file>\n", argv[0]);
printf("If <xfer size> is set to zero, the preferred one is used.\n");
return 1;
static struct option long_options[] =
{
{"help", no_argument, 0, '?'},
{"debug", no_argument, 0, 'd'},
{0, 0, 0, 0}
};
int c = getopt_long(argc, argv, "?dx:u:b:p:", long_options, NULL);
if(c == -1)
break;
switch(c)
{
case -1:
break;
case 'd':
g_debug = true;
break;
case '?':
usage();
break;
case 'x':
{
char *end;
di.xfer_size = strtoul(optarg, &end, 0);
if(*end)
{
printf("Invalid transfer size!\n");
exit(2);
}
break;
}
case 'u':
{
char *end;
di.vendor_id = strtoul(optarg, &end, 16);
if(*end != ':')
{
printf("Invalid USB PID!\n");
exit(3);
}
di.product_id = strtoul(end + 1, &end, 16);
if(*end)
{
printf("Invalid USB VID!\n");
exit(4);
}
break;
}
case 'b':
{
char *end;
usb_bus = strtol(optarg, &end, 0);
if(*end != ':')
{
printf("Invalid USB bus!\n");
exit(5);
}
usb_dev = strtol(end, &end, 0);
if(*end)
{
printf("Invalid USB device!\n");
exit(6);
}
break;
}
case 'p':
if(strcmp(optarg, "hid") == 0)
di.dev_type = HID_DEVICE;
else if(strcmp(optarg, "recovery") == 0)
di.dev_type = RECOVERY_DEVICE;
else
{
printf("Invalid protocol!\n");
exit(7);
}
break;
default:
abort();
}
}
char *end;
int xfer_size = strtol(argv[1], &end, 0);
if(end != (argv[1] + strlen(argv[1])))
{
printf("Invalid transfer size !\n");
return 1;
}
libusb_device_handle *dev;
if(optind + 1 != argc)
usage();
const char *filename = argv[optind];
/* lookup device */
libusb_init(NULL);
libusb_set_debug(NULL, 3);
libusb_device **list;
ssize_t list_size = libusb_get_device_list(NULL, &list);
libusb_device_handle *dev = NULL;
int db_idx = -1;
unsigned i;
for(i = 0; i < sizeof(g_dev_info) / sizeof(g_dev_info[0]); i++)
{
dev = libusb_open_device_with_vid_pid(NULL,
g_dev_info[i].vendor_id, g_dev_info[i].product_id);
if(dev == NULL)
continue;
if(xfer_size == 0)
xfer_size = g_dev_info[i].xfer_size;
printf("Found a match for %04x:%04x\n",
g_dev_info[i].vendor_id, g_dev_info[i].product_id);
break;
libusb_device *mdev = NULL;
int nr_matches = 0;
for(int i = 0; i < list_size; i++)
{
// match bus/dev if specified
if(dev_match(list[i], &di, usb_bus, usb_dev, &db_idx))
{
mdev = list[i];
nr_matches++;
}
}
if(nr_matches == 0)
{
printf("No device found\n");
exit(8);
}
if(nr_matches > 1)
{
printf("Several devices match the specified parameters:\n");
for(int i = 0; i < list_size; i++)
{
// match bus/dev if specified
if(dev_match(list[i], &di, usb_bus, usb_dev, NULL))
{
printf(" ");
print_match(list[i]);
}
}
}
printf("Device: ");
print_match(mdev);
libusb_open(mdev, &dev);
}
if(dev == NULL)
{
printf("Cannot open device\n");
return 1;
}
FILE *f = fopen(argv[2], "r");
/* get protocol */
enum dev_type_t dev_type = PROBE_DEVICE;
int xfer_size = di.xfer_size;
if(db_idx >= 0)
{
dev_type = g_dev_info[db_idx].dev_type;
xfer_size = g_dev_info[db_idx].xfer_size;
}
if(dev_type == PROBE_DEVICE)
dev_type = probe_protocol(dev);
if(xfer_size == 0)
xfer_size = probe_xfer_size(dev_type, dev);
/* open file */
FILE *f = fopen(filename, "r");
if(f == NULL)
{
perror("cannot open file");
perror("Cannot open file");
return 1;
}
fseek(f, 0, SEEK_END);
@ -222,8 +532,8 @@ int main(int argc, char **argv)
return 1;
}
fclose(f);
switch(g_dev_info[i].dev_type)
/* send file */
switch(dev_type)
{
case HID_DEVICE:
send_hid(dev, xfer_size, file_buf, size, nr_xfers);