说明:本流程基于Android4.4.2中的fastboot源代码讲解,该代码路径在"/system/core/fastboot"下。
从fastboot.c文件的main函数开始
int main(int argc, char **argv)
{
int wants_wipe = 0;
int wants_reboot = 0;
int wants_reboot_bootloader = 0;
int erase_first = 1;
void *data;
unsigned sz;
int status;
int c;
int r;
const struct option longopts[] = {
{"base", required_argument, 0, 'b'},
{"kernel_offset", required_argument, 0, 'k'},
{"page_size", required_argument, 0, 'n'},
{"ramdisk_offset", required_argument, 0, 'r'},
{"help", 0, 0, 'h'},
{0, 0, 0, 0}
};
serial = getenv("ANDROID_SERIAL");
while (1) {
int option_index = 0;
c = getopt_long(argc, argv, "wub:k:n:r:s:S:lp:c:i:m:h", longopts, NULL);
if (c < 0) {
break;
}
/* Alphabetical cases */
switch (c) {
case 'b':
base_addr = strtoul(optarg, 0, 16);
break;
case 'c':
cmdline = optarg;
break;
case 'h':
usage();
return 1;
case 'i': {
char *endptr = NULL;
unsigned long val;
val = strtoul(optarg, &endptr, 0);
if (!endptr || *endptr != '\0' || (val & ~0xffff))
die("invalid vendor id '%s'", optarg);
vendor_id = (unsigned short)val;
break;
}
case 'k':
kernel_offset = strtoul(optarg, 0, 16);
break;
case 'l':
long_listing = 1;
break;
case 'n':
page_size = (unsigned)strtoul(optarg, NULL, 0);
if (!page_size) die("invalid page size");
break;
case 'p':
product = optarg;
break;
case 'r':
ramdisk_offset = strtoul(optarg, 0, 16);
break;
case 's':
serial = optarg;
break;
case 'S':
sparse_limit = parse_num(optarg);
if (sparse_limit < 0) {
die("invalid sparse limit");
}
break;
case 'u':
erase_first = 0;
break;
case 'w':
wants_wipe = 1;
break;
case '?':
return 1;
default:
abort();
}
}
argc -= optind;
argv += optind;
if (argc == 0 && !wants_wipe) {
usage();
return 1;
}
if (argc > 0 && !strcmp(*argv, "devices")) {
skip(1);
list_devices();
return 0;
}
if (argc > 0 && !strcmp(*argv, "help")) {
usage();
return 0;
}
usb = open_device();
while (argc > 0) {
if(!strcmp(*argv, "getvar")) {
require(2);
fb_queue_display(argv[1], argv[1]);
skip(2);
} else if(!strcmp(*argv, "erase")) {
require(2);
if (fb_format_supported(usb, argv[1])) {
fprintf(stderr, "******** Did you mean to fastboot format this partition?\n");
}
fb_queue_erase(argv[1]);
skip(2);
} else if(!strcmp(*argv, "format")) {
require(2);
if (erase_first && needs_erase(argv[1])) {
fb_queue_erase(argv[1]);
}
fb_queue_format(argv[1], 0);
skip(2);
} else if(!strcmp(*argv, "signature")) {
require(2);
data = load_file(argv[1], &sz);
if (data == 0) die("could not load '%s': %s", argv[1], strerror(errno));
if (sz != 256) die("signature must be 256 bytes");
fb_queue_download("signature", data, sz);
fb_queue_command("signature", "installing signature");
skip(2);
} else if(!strcmp(*argv, "reboot")) {
wants_reboot = 1;
skip(1);
} else if(!strcmp(*argv, "reboot-bootloader")) {
wants_reboot_bootloader = 1;
skip(1);
} else if (!strcmp(*argv, "continue")) {
fb_queue_command("continue", "resuming boot");
skip(1);
} else if(!strcmp(*argv, "boot")) {
char *kname = 0;
char *rname = 0;
skip(1);
if (argc > 0) {
kname = argv[0];
skip(1);
}
if (argc > 0) {
rname = argv[0];
skip(1);
}
data = load_bootable_image(kname, rname, &sz, cmdline);
if (data == 0) return 1;
fb_queue_download("boot.img", data, sz);
fb_queue_command("boot", "booting");
} else if(!strcmp(*argv, "flash")) {
char *pname = argv[1];
char *fname = 0;
require(2);
if (argc > 2) {
fname = argv[2];
skip(3);
} else {
fname = find_item(pname, product);
skip(2);
}
if (fname == 0) die("cannot determine image filename for '%s'", pname);
if (erase_first && needs_erase(pname)) {
fb_queue_erase(pname);
}
do_flash(usb, pname, fname);
} else if(!strcmp(*argv, "flash:raw")) {
char *pname = argv[1];
char *kname = argv[2];
char *rname = 0;
require(3);
if(argc > 3) {
rname = argv[3];
skip(4);
} else {
skip(3);
}
data = load_bootable_image(kname, rname, &sz, cmdline);
if (data == 0) die("cannot load bootable image");
fb_queue_flash(pname, data, sz);
} else if(!strcmp(*argv, "flashall")) {
skip(1);
do_flashall(usb, erase_first);
wants_reboot = 1;
} else if(!strcmp(*argv, "update")) {
if (argc > 1) {
do_update(usb, argv[1], erase_first);
skip(2);
} else {
do_update(usb, "update.zip", erase_first);
skip(1);
}
wants_reboot = 1;
} else if(!strcmp(*argv, "oem")) {
argc = do_oem_command(argc, argv);
} else {
usage();
return 1;
}
}
if (wants_wipe) {
fb_queue_erase("userdata");
fb_queue_format("userdata", 1);
fb_queue_erase("cache");
fb_queue_format("cache", 1);
}
if (wants_reboot) {
fb_queue_reboot();
} else if (wants_reboot_bootloader) {
fb_queue_command("reboot-bootloader", "rebooting into bootloader");
}
if (fb_queue_is_empty())
return 0;
status = fb_execute_queue(usb);
return (status) ? 1 : 0;
}
第108行:usb = open_device(); //打开单板设备对应的usb设备
1、 open_device代码如下
总结:打开特定vendor的usb设备,白名单在 match_fastboot_with_serial函数中,详情请往下看。usb_handle *open_device(void)
{
static usb_handle *usb = 0;
int announce = 1;
if(usb) return usb;
for(;;) {
usb = usb_open(match_fastboot);
if(usb) return usb;
if(announce) {
announce = 0;
fprintf(stderr,"< waiting for device >\n");
}
sleep(1);
}
}
第8行到第15行是一个循环结构,如果没有找到可用的usb设备,则一直循环,并在第一次没有找到设备时打印“waiting for devece”;
第9行,使用usb_open函数打开usb设备,该函数针对不同的操作系统(windows,linux,os)有三种不同的实现,本文以linux为例。
1.1 usb_open
usb_handle *usb_open(ifc_match_func callback)
{
return find_usb_device("/dev/bus/usb", callback);
}
usb_open函数的第三行,它调用了find_usb_device函数,并以"/dev/bus/usb"作为第一个参数,以callback作为第二个参数。
1.1.1 find_usb_device
static usb_handle *find_usb_device(const char *base, ifc_match_func callback){ usb_handle *usb = 0; char busname[64], devname[64]; char desc[1024]; int n, in, out, ifc; DIR *busdir, *devdir; struct dirent *de; int fd; int writable; busdir = opendir(base); if(busdir == 0) return 0; while((de = readdir(busdir)) && (usb == 0)) { if(badname(de->d_name)) continue; sprintf(busname, "%s/%s", base, de->d_name); devdir = opendir(busname); if(devdir == 0) continue;// DBG("[ scanning %s ]\n", busname); while((de = readdir(devdir)) && (usb == 0)) { if(badname(de->d_name)) continue; sprintf(devname, "%s/%s", busname, de->d_name);// DBG("[ scanning %s ]\n", devname); writable = 1; if((fd = open(devname, O_RDWR)) < 0) { // Check if we have read-only access, so we can give a helpful // diagnostic like "adb devices" does. writable = 0; if((fd = open(devname, O_RDONLY)) < 0) { continue; } } n = read(fd, desc, sizeof(desc)); if(filter_usb_device(fd, desc, n, writable, callback, &in, &out, &ifc) == 0) { usb = calloc(1, sizeof(usb_handle)); strcpy(usb->fname, devname); usb->ep_in = in; usb->ep_out = out; usb->desc = fd; n = ioctl(fd, USBDEVFS_CLAIMINTERFACE, &ifc); if(n != 0) { close(fd); free(usb); usb = 0; continue; } } else { close(fd); } } closedir(devdir); } closedir(busdir); return usb;}先来看一下linux下usb设备节点的目录结构:
由以上信息可以得知,在"/dev/bus/usb"目录下有子目录001,002等,在子目录下有对应usb设备对应的文件节点,这样我们看find_usb_device函数的第13行到第24行,就更容易理解了。
第13行:打开"/dev/bus/usb"目录;
第14行:打开失败,返回;
第16行:循环遍历"/dev/bus/usb"的第一级子目录,如002;
第20行:打开某一个第一级子目录,如002;
第24行:循环遍历第一级子目录(如/dev/bus/usb/002")下的usb驱动节点文件;
第31行:以读写方式打开usb驱动节点文件,其实就是以读写方式打开usb设备,如果返回值小于于等于0,则说明打开成功失败;设置writable值。
第35行:以读方式打开usb驱动节点文件,打开失败则终止本次循环,测试下一个usb节点;
第40行:读取usb节点文件信息,包含了vid,pid等。
第42行:过滤usb设备,代码如下
1.1.1.1 filter_usb_device
static int filter_usb_device(int fd, char *ptr, int len, int writable,第5行到第8行的这四个结构体定义在android4.2.2的external\kernel-headers\original\linux\usb\Ch9.h文件中。
ifc_match_func callback,
int *ept_in_id, int *ept_out_id, int *ifc_id)
{
struct usb_device_descriptor *dev;
struct usb_config_descriptor *cfg;
struct usb_interface_descriptor *ifc;
struct usb_endpoint_descriptor *ept;
struct usb_ifc_info info;
int in, out;
unsigned i;
unsigned e;
struct stat st;
int result;
if(check(ptr, len, USB_DT_DEVICE, USB_DT_DEVICE_SIZE))
return -1;
dev = (void*) ptr;
len -= dev->bLength;
ptr += dev->bLength;
if(check(ptr, len, USB_DT_CONFIG, USB_DT_CONFIG_SIZE))
return -1;
cfg = (void*) ptr;
len -= cfg->bLength;
ptr += cfg->bLength;
info.dev_vendor = dev->idVendor;
info.dev_product = dev->idProduct;
info.dev_class = dev->bDeviceClass;
info.dev_subclass = dev->bDeviceSubClass;
info.dev_protocol = dev->bDeviceProtocol;
info.writable = writable;
// read device serial number (if there is one)
info.serial_number[0] = 0;
if (dev->iSerialNumber) {
struct usbdevfs_ctrltransfer ctrl;
// Keep it short enough because some bootloaders are borked if the URB len is > 255
// 128 is too big by 1.
__u16 buffer[127];
memset(buffer, 0, sizeof(buffer));
ctrl.bRequestType = USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_DEVICE;
ctrl.bRequest = USB_REQ_GET_DESCRIPTOR;
ctrl.wValue = (USB_DT_STRING << 8) | dev->iSerialNumber;
//language ID (en-us) for serial number string
ctrl.wIndex = 0x0409;
ctrl.wLength = sizeof(buffer);
ctrl.data = buffer;
ctrl.timeout = 50;
result = ioctl(fd, USBDEVFS_CONTROL, &ctrl);
if (result > 0) {
int i;
// skip first word, and copy the rest to the serial string, changing shorts to bytes.
result /= 2;
for (i = 1; i < result; i++)
info.serial_number[i - 1] = buffer[i];
info.serial_number[i - 1] = 0;
}
}
/* We need to get a path that represents a particular port on a particular
* hub. We are passed an fd that was obtained by opening an entry under
* /dev/bus/usb. Unfortunately, the names of those entries change each
* time devices are plugged and unplugged. So how to get a repeatable
* path? udevadm provided the inspiration. We can get the major and
* minor of the device file, read the symlink that can be found here:
* /sys/dev/char/<major>:<minor>
* and then use the last element of that path. As a concrete example, I
* have an Android device at /dev/bus/usb/001/027 so working with bash:
* $ ls -l /dev/bus/usb/001/027
* crw-rw-r-- 1 root plugdev 189, 26 Apr 9 11:03 /dev/bus/usb/001/027
* $ ls -l /sys/dev/char/189:26
* lrwxrwxrwx 1 root root 0 Apr 9 11:03 /sys/dev/char/189:26 ->
* ../../devices/pci0000:00/0000:00:1a.7/usb1/1-4/1-4.2/1-4.2.3
* So our device_path would be 1-4.2.3 which says my device is connected
* to port 3 of a hub on port 2 of a hub on port 4 of bus 1 (per
* http://www.linux-usb.org/FAQ.html).
*/
info.device_path[0] = '\0';
result = fstat(fd, &st);
if (!result && S_ISCHR(st.st_mode)) {
char cdev[128];
char link[256];
char *slash;
ssize_t link_len;
snprintf(cdev, sizeof(cdev), "/sys/dev/char/%d:%d",
major(st.st_rdev), minor(st.st_rdev));
link_len = readlink(cdev, link, sizeof(link) - 1);
if (link_len > 0) {
link[link_len] = '\0';
slash = strrchr(link, '/');
if (slash)
snprintf(info.device_path, sizeof(info.device_path), "usb:%s", slash+1);
}
}
for(i = 0; i < cfg->bNumInterfaces; i++) {
if(check(ptr, len, USB_DT_INTERFACE, USB_DT_INTERFACE_SIZE))
return -1;
ifc = (void*) ptr;
len -= ifc->bLength;
ptr += ifc->bLength;
in = -1;
out = -1;
info.ifc_class = ifc->bInterfaceClass;
info.ifc_subclass = ifc->bInterfaceSubClass;
info.ifc_protocol = ifc->bInterfaceProtocol;
for(e = 0; e < ifc->bNumEndpoints; e++) {
if(check(ptr, len, USB_DT_ENDPOINT, USB_DT_ENDPOINT_SIZE))
return -1;
ept = (void*) ptr;
len -= ept->bLength;
ptr += ept->bLength;
if((ept->bmAttributes & 0x03) != 0x02)
continue;
if(ept->bEndpointAddress & 0x80) {
in = ept->bEndpointAddress;
} else {
out = ept->bEndpointAddress;
}
}
info.has_bulk_in = (in != -1);
info.has_bulk_out = (out != -1);
if(callback(&info) == 0) {
*ept_in_id = in;
*ept_out_id = out;
*ifc_id = ifc->bInterfaceNumber;
return 0;
}
}
return -1;
}
整个函数就是为了找到正确的数据填充这四个结构体,并最终转存到第9行的info变量中,还有就是找到usb对应的in与out端口用来传输数据。
第136行:callback(&info),callback为函数指针,由外层函数 1、open_device 传进来的,实际指向match_fastboot函数,代码如下所示:
int match_fastboot(usb_ifc_info *info)
{
return match_fastboot_with_serial(info, serial);
}
int match_fastboot_with_serial(usb_ifc_info *info, const char *local_serial)
{
if(!(vendor_id && (info->dev_vendor == vendor_id)) &&
(info->dev_vendor != 0x18d1) && // Google
(info->dev_vendor != 0x8087) && // Intel
(info->dev_vendor != 0x0451) &&
(info->dev_vendor != 0x0502) &&
(info->dev_vendor != 0x0fce) && // Sony Ericsson
(info->dev_vendor != 0x05c6) && // Qualcomm
(info->dev_vendor != 0x22b8) && // Motorola
(info->dev_vendor != 0x0955) && // Nvidia
(info->dev_vendor != 0x413c) && // DELL
(info->dev_vendor != 0x2314) && // INQ Mobile
(info->dev_vendor != 0x0b05) && // Asus
(info->dev_vendor != 0x0bb4)) // HTC
return -1;
if(info->ifc_class != 0xff) return -1;
if(info->ifc_subclass != 0x42) return -1;
if(info->ifc_protocol != 0x03) return -1;
// require matching serial number or device path if requested
// at the command line with the -s option.
if (local_serial && (strcmp(local_serial, info->serial_number) != 0 &&
strcmp(local_serial, info->device_path) != 0)) return -1;
return 0;
}
第3-15行:如果能够匹配其中的某一个vid,则跳转到第17行,并执行后面的语句。
回到1.1.1.1 filter_usb_device;
第137行:获取到in端口;
第138行:获取到out端口;
回到1.1.1 find_usb_device:
第44行:申请usb对象内存;
第45-48行:将usb的节点路径,in、out端口,设备句柄保存在usb变量中。
第65,返回该usb变量。这个就是我们以后
再回到1.1 usb_open函数也执行完了,那么open_device返回的就是usb的句柄。
回到main函数来:
第164行:此处就是更新系统的分支;(更新命令如:fastboot flash boot boot.img为例)
第165行:pname保存分区名;
第166-174:找到boot.img在pc机上的位置;
第175行:检查文件名是否合法;
第179行:将boot.img写到设备上,下面我们分析该函数。
2、do_flash
总结:将要更新的系统数据保存到了action_list链表中。void do_flash(usb_handle *usb, const char *pname, const char *fname)
{
struct fastboot_buffer buf;
if (load_buf(usb, fname, &buf)) {
die("cannot load '%s'", fname);
}
flash_buf(pname, &buf);
}
2.1、load_buf
static int load_buf(usb_handle *usb, const char *fname,
struct fastboot_buffer *buf)
{
int fd;
fd = open(fname, O_RDONLY | O_BINARY);
if (fd < 0) {
die("cannot open '%s'\n", fname);
}
return load_buf_fd(usb, fd, buf);
}
2.1.1、load_buf_fd
static int load_buf_fd(usb_handle *usb, int fd,将数据加载到了buf。
struct fastboot_buffer *buf)
{
int64_t sz64;
void *data;
int64_t limit;
sz64 = file_size(fd);
if (sz64 < 0) {
return -1;
}
limit = get_sparse_limit(usb, sz64);
if (limit) {
struct sparse_file **s = load_sparse_files(fd, limit);
if (s == NULL) {
return -1;
}
buf->type = FB_BUFFER_SPARSE;
buf->data = s;
} else {
unsigned int sz;
data = load_fd(fd, &sz);
if (data == 0) return -1;
buf->type = FB_BUFFER;
buf->data = data;
buf->sz = sz;
}
return 0;
}
2.2、flash_buf
static void flash_buf(const char *pname, struct fastboot_buffer *buf)2.2.1、fb_queue_flash
{
struct sparse_file **s;
switch (buf->type) {
case FB_BUFFER_SPARSE:
s = buf->data;
while (*s) {
int64_t sz64 = sparse_file_len(*s, true, false);
fb_queue_flash_sparse(pname, *s++, sz64);
}
break;
case FB_BUFFER:
fb_queue_flash(pname, buf->data, buf->sz);
break;
default:
die("unknown buffer type: %d", buf->type);
}
}
void fb_queue_flash(const char *ptn, void *data, unsigned sz)这个函数看似将数据放到了变量a里面,我们来看一下queue_action函数
{
Action *a;
a = queue_action(OP_DOWNLOAD, "");
a->data = data;
a->size = sz;
a->msg = mkmsg("sending '%s' (%d KB)", ptn, sz / 1024);
a = queue_action(OP_COMMAND, "flash:%s", ptn);
a->msg = mkmsg("writing '%s'", ptn);
}
static Action *queue_action(unsigned op, const char *fmt, ...)原来a是action_list链表中的一个元素,也就是将要写的数据保存到了链表action_list中。
{
Action *a;
va_list ap;
size_t cmdsize;
a = calloc(1, sizeof(Action));
if (a == 0) die("out of memory");
va_start(ap, fmt);
cmdsize = vsnprintf(a->cmd, sizeof(a->cmd), fmt, ap);
va_end(ap);
if (cmdsize >= sizeof(a->cmd)) {
free(a);
die("Command length (%d) exceeds maximum size (%d)", cmdsize, sizeof(a->cmd));
}
if (action_last) {
action_last->next = a;
} else {
action_list = a;
}
action_last = a;
a->op = op;
a->func = cb_default;
a->start = -1;
return a;
}
2.3、调用流程图如下
回到main函数,第230行:status = fb_execute_queue(usb),如下
3、fb_execute_queue
总结:最终调用ioctl将数据写到usb设备上。int fb_execute_queue(usb_handle *usb)这里,我们只分析OP_DOWNLOAD分支,请看: 第21行:fb_download_data,代码如下:
{
Action *a;
char resp[FB_RESPONSE_SZ+1];
int status = 0;
a = action_list;
if (!a)
return status;
resp[FB_RESPONSE_SZ] = 0;
double start = -1;
for (a = action_list; a; a = a->next) {
a->start = now();
if (start < 0) start = a->start;
if (a->msg) {
// fprintf(stderr,"%30s... ",a->msg);
fprintf(stderr,"%s...\n",a->msg);
}
if (a->op == OP_DOWNLOAD) {
status = fb_download_data(usb, a->data, a->size);
status = a->func(a, status, status ? fb_get_error() : "");
if (status) break;
} else if (a->op == OP_COMMAND) {
status = fb_command(usb, a->cmd);
status = a->func(a, status, status ? fb_get_error() : "");
if (status) break;
} else if (a->op == OP_QUERY) {
status = fb_command_response(usb, a->cmd, resp);
status = a->func(a, status, status ? fb_get_error() : resp);
if (status) break;
} else if (a->op == OP_NOTICE) {
fprintf(stderr,"%s\n",(char*)a->data);
} else if (a->op == OP_FORMAT) {
status = fb_format(a, usb, (int)a->data);
status = a->func(a, status, status ? fb_get_error() : "");
if (status) break;
} else if (a->op == OP_DOWNLOAD_SPARSE) {
status = fb_download_data_sparse(usb, a->data);
status = a->func(a, status, status ? fb_get_error() : "");
if (status) break;
} else {
die("bogus action");
}
}
fprintf(stderr,"finished. total time: %.3fs\n", (now() - start));
return status;
}
int fb_download_data(usb_handle *usb, const void *data, unsigned size)再看_command_send:
{
char cmd[64];
int r;
sprintf(cmd, "download:%08x", size);
r = _command_send(usb, cmd, data, size, 0);
if(r < 0) {
return -1;
} else {
return 0;
}
}
static int _command_send(usb_handle *usb, const char *cmd,这里调用了三个函数:_command_start、_command_data与_command_end,如下:
const void *data, unsigned size,
char *response)
{
int r;
if (size == 0) {
return -1;
}
r = _command_start(usb, cmd, size, response);
if (r < 0) {
return -1;
}
r = _command_data(usb, data, size);
if (r < 0) {
return -1;
}
r = _command_end(usb);
if(r < 0) {
return -1;
}
return size;
}
static int _command_start(usb_handle *usb, const char *cmd, unsigned size,
char *response)
{
int cmdsize = strlen(cmd);
int r;
if(response) {
response[0] = 0;
}
if(cmdsize > 64) {
sprintf(ERROR,"command too large");
return -1;
}
if(usb_write(usb, cmd, cmdsize) != cmdsize) {
sprintf(ERROR,"command write failed (%s)", strerror(errno));
usb_close(usb);
return -1;
}
return check_response(usb, size, response);
}
static int _command_data(usb_handle *usb, const void *data, unsigned size){ int r; r = usb_write(usb, data, size); if(r < 0) { sprintf(ERROR, "data transfer failure (%s)", strerror(errno)); usb_close(usb); return -1; } if(r != ((int) size)) { sprintf(ERROR, "data transfer failure (short transfer)"); usb_close(usb); return -1; } return r;}
static int _command_end(usb_handle *usb){ int r; r = check_response(usb, 0, 0); if(r < 0) { return -1; } return 0;}前面两个函数最终都调用了usb_write将数据写下去,该函数实现如下:
int usb_write(usb_handle *h, const void *_data, int len)第36行:调用ioctl将数据写到usb设备上。
{
unsigned char *data = (unsigned char*) _data;
unsigned count = 0;
struct usbdevfs_bulktransfer bulk;
int n;
if(h->ep_out == 0) {
return -1;
}
if(len == 0) {
bulk.ep = h->ep_out;
bulk.len = 0;
bulk.data = data;
bulk.timeout = 0;
n = ioctl(h->desc, USBDEVFS_BULK, &bulk);
if(n != 0) {
fprintf(stderr,"ERROR: n = %d, errno = %d (%s)\n",
n, errno, strerror(errno));
return -1;
}
return 0;
}
while(len > 0) {
int xfer;
xfer = (len > MAX_USBFS_BULK_SIZE) ? MAX_USBFS_BULK_SIZE : len;
bulk.ep = h->ep_out;
bulk.len = xfer;
bulk.data = data;
bulk.timeout = 0;
n = ioctl(h->desc, USBDEVFS_BULK, &bulk);
if(n != xfer) {
DBG("ERROR: n = %d, errno = %d (%s)\n",
n, errno, strerror(errno));
return -1;
}
count += xfer;
len -= xfer;
data += xfer;
}
return count;
}