fel: Factor out a new FEL library
The FEL utility had accumulated enough (mostly USB-related) "low-level" code to justify moving that to a separate code unit. This will allow us to keep better focus on the higher level functionality in fel.c. Signed-off-by: Bernhard Nortmann <bernhard.nortmann@web.de>
This commit is contained in:
parent
ea3f7fee3d
commit
aaa677d552
7
Makefile
7
Makefile
@ -121,10 +121,11 @@ endif
|
||||
|
||||
HOST_CFLAGS = $(DEFAULT_CFLAGS) $(CFLAGS)
|
||||
|
||||
PROGRESS = progress.c progress.h
|
||||
SOC_INFO = soc_info.c soc_info.h
|
||||
PROGRESS := progress.c progress.h
|
||||
SOC_INFO := soc_info.c soc_info.h
|
||||
FEL_LIB := fel_lib.c fel_lib.h
|
||||
|
||||
sunxi-fel: fel.c fel-to-spl-thunk.h $(PROGRESS) $(SOC_INFO)
|
||||
sunxi-fel: fel.c fel-to-spl-thunk.h $(PROGRESS) $(SOC_INFO) $(FEL_LIB)
|
||||
$(CC) $(HOST_CFLAGS) $(LIBUSB_CFLAGS) $(LDFLAGS) -o $@ $(filter %.c,$^) $(LIBS) $(LIBUSB_LIBS)
|
||||
|
||||
sunxi-nand-part: nand-part-main.c nand-part.c nand-part-a10.h nand-part-a20.h
|
||||
|
||||
390
fel.c
390
fel.c
@ -17,68 +17,18 @@
|
||||
|
||||
#include "common.h"
|
||||
#include "portable_endian.h"
|
||||
#include "progress.h"
|
||||
#include "soc_info.h"
|
||||
#include "fel_lib.h"
|
||||
|
||||
#include <libusb.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <stdarg.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
static const uint16_t AW_USB_VENDOR_ID = 0x1F3A;
|
||||
static const uint16_t AW_USB_PRODUCT_ID = 0xEFE8;
|
||||
|
||||
/* a helper function to report libusb errors */
|
||||
void usb_error(int rc, const char *caption, int exitcode)
|
||||
{
|
||||
if (caption)
|
||||
fprintf(stderr, "%s ", caption);
|
||||
|
||||
#if defined(LIBUSBX_API_VERSION) && (LIBUSBX_API_VERSION >= 0x01000102)
|
||||
fprintf(stderr, "ERROR %d: %s\n", rc, libusb_strerror(rc));
|
||||
#else
|
||||
/* assume that libusb_strerror() is missing in the libusb API */
|
||||
fprintf(stderr, "ERROR %d\n", rc);
|
||||
#endif
|
||||
|
||||
if (exitcode != 0)
|
||||
exit(exitcode);
|
||||
}
|
||||
|
||||
/* 'Private' data type that will be used as "USB handle" */
|
||||
typedef struct _felusb_handle {
|
||||
libusb_device_handle *handle;
|
||||
int endpoint_out, endpoint_in;
|
||||
bool iface_detached;
|
||||
} felusb_handle;
|
||||
|
||||
/* More general FEL "device" handle, to be extended later */
|
||||
typedef struct {
|
||||
felusb_handle *usb;
|
||||
} feldev_handle;
|
||||
|
||||
struct aw_usb_request {
|
||||
char signature[8];
|
||||
uint32_t length;
|
||||
uint32_t unknown1; /* 0x0c000000 */
|
||||
uint16_t request;
|
||||
uint32_t length2; /* Same as length */
|
||||
char pad[10];
|
||||
} __attribute__((packed));
|
||||
|
||||
static const int AW_USB_READ = 0x11;
|
||||
static const int AW_USB_WRITE = 0x12;
|
||||
|
||||
static int timeout = 10000; /* 10 seconds */
|
||||
|
||||
static bool verbose = false; /* If set, makes the 'fel' tool more talkative */
|
||||
static uint32_t uboot_entry = 0; /* entry point (address) of U-Boot */
|
||||
static uint32_t uboot_size = 0; /* size of U-Boot binary */
|
||||
@ -93,55 +43,6 @@ static void pr_info(const char *fmt, ...)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* AW_USB_MAX_BULK_SEND and the timeout constant are related.
|
||||
* Both need to be selected in a way that transferring the maximum chunk size
|
||||
* with (SoC-specific) slow transfer speed won't time out.
|
||||
*
|
||||
* The 512 KiB here are chosen based on the assumption that we want a 10 seconds
|
||||
* timeout, and "slow" transfers take place at approx. 64 KiB/sec - so we can
|
||||
* expect the maximum chunk being transmitted within 8 seconds or less.
|
||||
*/
|
||||
static const int AW_USB_MAX_BULK_SEND = 512 * 1024; /* 512 KiB per bulk request */
|
||||
|
||||
void usb_bulk_send(libusb_device_handle *usb, int ep, const void *data,
|
||||
size_t length, bool progress)
|
||||
{
|
||||
/*
|
||||
* With no progress notifications, we'll use the maximum chunk size.
|
||||
* Otherwise, it's useful to lower the size (have more chunks) to get
|
||||
* more frequent status updates. 128 KiB per request seem suitable.
|
||||
* (Worst case of "slow" transfers -> one update every two seconds.)
|
||||
*/
|
||||
size_t max_chunk = progress ? 128 * 1024 : AW_USB_MAX_BULK_SEND;
|
||||
|
||||
size_t chunk;
|
||||
int rc, sent;
|
||||
while (length > 0) {
|
||||
chunk = length < max_chunk ? length : max_chunk;
|
||||
rc = libusb_bulk_transfer(usb, ep, (void *)data, chunk, &sent, timeout);
|
||||
if (rc != 0)
|
||||
usb_error(rc, "usb_bulk_send()", 2);
|
||||
length -= sent;
|
||||
data += sent;
|
||||
|
||||
if (progress)
|
||||
progress_update(sent); /* notification after each chunk */
|
||||
}
|
||||
}
|
||||
|
||||
void usb_bulk_recv(libusb_device_handle *usb, int ep, void *data, int length)
|
||||
{
|
||||
int rc, recv;
|
||||
while (length > 0) {
|
||||
rc = libusb_bulk_transfer(usb, ep, data, length, &recv, timeout);
|
||||
if (rc != 0)
|
||||
usb_error(rc, "usb_bulk_recv()", 2);
|
||||
length -= recv;
|
||||
data += recv;
|
||||
}
|
||||
}
|
||||
|
||||
/* Constants taken from ${U-BOOT}/include/image.h */
|
||||
#define IH_MAGIC 0x27051956 /* Image Magic Number */
|
||||
#define IH_ARCH_ARM 2 /* ARM */
|
||||
@ -182,87 +83,6 @@ int get_image_type(const uint8_t *buf, size_t len)
|
||||
return buf[30];
|
||||
}
|
||||
|
||||
static void aw_send_usb_request(feldev_handle *dev, int type, int length)
|
||||
{
|
||||
struct aw_usb_request req = {
|
||||
.signature = "AWUC",
|
||||
.request = htole16(type),
|
||||
.length = htole32(length),
|
||||
.unknown1 = htole32(0x0c000000)
|
||||
};
|
||||
req.length2 = req.length;
|
||||
usb_bulk_send(dev->usb->handle, dev->usb->endpoint_out,
|
||||
&req, sizeof(req), false);
|
||||
}
|
||||
|
||||
static void aw_read_usb_response(feldev_handle *dev)
|
||||
{
|
||||
char buf[13];
|
||||
usb_bulk_recv(dev->usb->handle, dev->usb->endpoint_in,
|
||||
buf, sizeof(buf));
|
||||
assert(strcmp(buf, "AWUS") == 0);
|
||||
}
|
||||
|
||||
static void aw_usb_write(feldev_handle *dev, const void *data, size_t len,
|
||||
bool progress)
|
||||
{
|
||||
aw_send_usb_request(dev, AW_USB_WRITE, len);
|
||||
usb_bulk_send(dev->usb->handle, dev->usb->endpoint_out,
|
||||
data, len, progress);
|
||||
aw_read_usb_response(dev);
|
||||
}
|
||||
|
||||
static void aw_usb_read(feldev_handle *dev, const void *data, size_t len)
|
||||
{
|
||||
aw_send_usb_request(dev, AW_USB_READ, len);
|
||||
usb_bulk_send(dev->usb->handle, dev->usb->endpoint_in,
|
||||
data, len, false);
|
||||
aw_read_usb_response(dev);
|
||||
}
|
||||
|
||||
struct aw_fel_request {
|
||||
uint32_t request;
|
||||
uint32_t address;
|
||||
uint32_t length;
|
||||
uint32_t pad;
|
||||
};
|
||||
|
||||
static const int AW_FEL_VERSION = 0x001;
|
||||
static const int AW_FEL_1_WRITE = 0x101;
|
||||
static const int AW_FEL_1_EXEC = 0x102;
|
||||
static const int AW_FEL_1_READ = 0x103;
|
||||
|
||||
void aw_send_fel_request(feldev_handle *dev, int type,
|
||||
uint32_t addr, uint32_t length)
|
||||
{
|
||||
struct aw_fel_request req = {
|
||||
.request = htole32(type),
|
||||
.address = htole32(addr),
|
||||
.length = htole32(length)
|
||||
};
|
||||
aw_usb_write(dev, &req, sizeof(req), false);
|
||||
}
|
||||
|
||||
void aw_read_fel_status(feldev_handle *dev)
|
||||
{
|
||||
char buf[8];
|
||||
aw_usb_read(dev, buf, sizeof(buf));
|
||||
}
|
||||
|
||||
void aw_fel_get_version(feldev_handle *dev, struct aw_fel_version *buf)
|
||||
{
|
||||
aw_send_fel_request(dev, AW_FEL_VERSION, 0, 0);
|
||||
aw_usb_read(dev, buf, sizeof(*buf));
|
||||
aw_read_fel_status(dev);
|
||||
|
||||
buf->soc_id = (le32toh(buf->soc_id) >> 8) & 0xFFFF;
|
||||
buf->unknown_0a = le32toh(buf->unknown_0a);
|
||||
buf->protocol = le32toh(buf->protocol);
|
||||
buf->scratchpad = le16toh(buf->scratchpad);
|
||||
buf->pad[0] = le32toh(buf->pad[0]);
|
||||
buf->pad[1] = le32toh(buf->pad[1]);
|
||||
}
|
||||
|
||||
void aw_fel_print_version(feldev_handle *dev)
|
||||
{
|
||||
struct aw_fel_version buf;
|
||||
@ -290,31 +110,9 @@ void aw_fel_print_version(feldev_handle *dev)
|
||||
buf.scratchpad, buf.pad[0], buf.pad[1]);
|
||||
}
|
||||
|
||||
void aw_fel_read(feldev_handle *dev, uint32_t offset, void *buf, size_t len)
|
||||
{
|
||||
aw_send_fel_request(dev, AW_FEL_1_READ, offset, len);
|
||||
aw_usb_read(dev, buf, len);
|
||||
aw_read_fel_status(dev);
|
||||
}
|
||||
|
||||
void aw_fel_write(feldev_handle *dev, void *buf, uint32_t offset, size_t len)
|
||||
{
|
||||
aw_send_fel_request(dev, AW_FEL_1_WRITE, offset, len);
|
||||
aw_usb_write(dev, buf, len, false);
|
||||
aw_read_fel_status(dev);
|
||||
}
|
||||
|
||||
void aw_fel_execute(feldev_handle *dev, uint32_t offset)
|
||||
{
|
||||
aw_send_fel_request(dev, AW_FEL_1_EXEC, offset, 0);
|
||||
aw_read_fel_status(dev);
|
||||
}
|
||||
|
||||
/*
|
||||
* This function is a higher-level wrapper for the FEL write functionality.
|
||||
* Unlike aw_fel_write() above - which is reserved for internal use - this
|
||||
* routine is meant to be called from "user" code, and supports (= allows)
|
||||
* progress callbacks.
|
||||
* This wrapper for the FEL write functionality safeguards against overwriting
|
||||
* an already loaded U-Boot binary.
|
||||
* The return value represents elapsed time in seconds (needed for execution).
|
||||
*/
|
||||
double aw_write_buffer(feldev_handle *dev, void *buf, uint32_t offset,
|
||||
@ -331,9 +129,7 @@ double aw_write_buffer(feldev_handle *dev, void *buf, uint32_t offset,
|
||||
exit(1);
|
||||
}
|
||||
double start = gettime();
|
||||
aw_send_fel_request(dev, AW_FEL_1_WRITE, offset, len);
|
||||
aw_usb_write(dev, buf, len, progress);
|
||||
aw_read_fel_status(dev);
|
||||
aw_fel_write_buffer(dev, buf, offset, len, progress);
|
||||
return gettime() - start;
|
||||
}
|
||||
|
||||
@ -1227,46 +1023,6 @@ void pass_fel_information(feldev_handle *dev,
|
||||
}
|
||||
}
|
||||
|
||||
static int feldev_get_endpoint(feldev_handle *dev)
|
||||
{
|
||||
struct libusb_device *usb = libusb_get_device(dev->usb->handle);
|
||||
struct libusb_config_descriptor *config;
|
||||
int if_idx, set_idx, ep_idx, ret;
|
||||
|
||||
ret = libusb_get_active_config_descriptor(usb, &config);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
for (if_idx = 0; if_idx < config->bNumInterfaces; if_idx++) {
|
||||
const struct libusb_interface *iface = config->interface + if_idx;
|
||||
|
||||
for (set_idx = 0; set_idx < iface->num_altsetting; set_idx++) {
|
||||
const struct libusb_interface_descriptor *setting =
|
||||
iface->altsetting + set_idx;
|
||||
|
||||
for (ep_idx = 0; ep_idx < setting->bNumEndpoints; ep_idx++) {
|
||||
const struct libusb_endpoint_descriptor *ep =
|
||||
setting->endpoint + ep_idx;
|
||||
|
||||
/* Test for bulk transfer endpoint */
|
||||
if ((ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) !=
|
||||
LIBUSB_TRANSFER_TYPE_BULK)
|
||||
continue;
|
||||
|
||||
if ((ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) ==
|
||||
LIBUSB_ENDPOINT_IN)
|
||||
dev->usb->endpoint_in = ep->bEndpointAddress;
|
||||
else
|
||||
dev->usb->endpoint_out = ep->bEndpointAddress;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
libusb_free_config_descriptor(config);
|
||||
|
||||
return LIBUSB_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function stores a given entry point to the RVBAR address for CPU0,
|
||||
* and then writes the Reset Management Register to request a warm boot.
|
||||
@ -1362,134 +1118,6 @@ static unsigned int file_upload(feldev_handle *dev, size_t count,
|
||||
return i; /* return number of files that were processed */
|
||||
}
|
||||
|
||||
void feldev_claim(feldev_handle *dev)
|
||||
{
|
||||
int rc = libusb_claim_interface(dev->usb->handle, 0);
|
||||
#if defined(__linux__)
|
||||
if (rc != LIBUSB_SUCCESS) {
|
||||
libusb_detach_kernel_driver(dev->usb->handle, 0);
|
||||
dev->usb->iface_detached = true;
|
||||
rc = libusb_claim_interface(dev->usb->handle, 0);
|
||||
}
|
||||
#endif
|
||||
if (rc)
|
||||
usb_error(rc, "libusb_claim_interface()", 1);
|
||||
|
||||
rc = feldev_get_endpoint(dev);
|
||||
if (rc)
|
||||
usb_error(rc, "FAILED to get FEL mode endpoint addresses!", 1);
|
||||
}
|
||||
|
||||
void feldev_release(feldev_handle *dev)
|
||||
{
|
||||
libusb_release_interface(dev->usb->handle, 0);
|
||||
#if defined(__linux__)
|
||||
if (dev->usb->iface_detached)
|
||||
libusb_attach_kernel_driver(dev->usb->handle, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* open handle to desired FEL device */
|
||||
static feldev_handle *open_fel_device(int busnum, int devnum,
|
||||
uint16_t vendor_id, uint16_t product_id)
|
||||
{
|
||||
feldev_handle *result = calloc(1, sizeof(feldev_handle));
|
||||
if (!result) {
|
||||
fprintf(stderr, "FAILED to allocate feldev_handle memory.\n");
|
||||
exit(1);
|
||||
}
|
||||
result->usb = calloc(1, sizeof(felusb_handle));
|
||||
if (!result->usb) {
|
||||
fprintf(stderr, "FAILED to allocate felusb_handle memory.\n");
|
||||
free(result);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (busnum < 0 || devnum < 0) {
|
||||
/* With the default values (busnum -1, devnum -1) we don't care
|
||||
* for a specific USB device; so let libusb open the first
|
||||
* device that matches VID/PID.
|
||||
*/
|
||||
result->usb->handle = libusb_open_device_with_vid_pid(NULL, vendor_id, product_id);
|
||||
if (!result->usb->handle) {
|
||||
switch (errno) {
|
||||
case EACCES:
|
||||
fprintf(stderr, "ERROR: You don't have permission to access Allwinner USB FEL device\n");
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "ERROR: Allwinner USB FEL device not found!\n");
|
||||
break;
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
/* look for specific bus and device number */
|
||||
bool found = false;
|
||||
ssize_t rc, i;
|
||||
libusb_device **list;
|
||||
|
||||
rc = libusb_get_device_list(NULL, &list);
|
||||
if (rc < 0)
|
||||
usb_error(rc, "libusb_get_device_list()", 1);
|
||||
for (i = 0; i < rc; i++) {
|
||||
if (libusb_get_bus_number(list[i]) == busnum
|
||||
&& libusb_get_device_address(list[i]) == devnum) {
|
||||
found = true; /* bus:devnum matched */
|
||||
struct libusb_device_descriptor desc;
|
||||
libusb_get_device_descriptor(list[i], &desc);
|
||||
if (desc.idVendor != vendor_id
|
||||
|| desc.idProduct != product_id) {
|
||||
fprintf(stderr, "ERROR: Bus %03d Device %03d not a FEL device "
|
||||
"(expected %04x:%04x, got %04x:%04x)\n", busnum, devnum,
|
||||
vendor_id, product_id, desc.idVendor, desc.idProduct);
|
||||
exit(1);
|
||||
}
|
||||
/* open handle to this specific device (incrementing its refcount) */
|
||||
rc = libusb_open(list[i], &result->usb->handle);
|
||||
if (rc != 0)
|
||||
usb_error(rc, "libusb_open()", 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
libusb_free_device_list(list, true);
|
||||
|
||||
if (!found) {
|
||||
fprintf(stderr, "ERROR: Bus %03d Device %03d not found in libusb device list\n",
|
||||
busnum, devnum);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
feldev_claim(result); /* claim interface, detect USB endpoints */
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void feldev_close(feldev_handle *dev)
|
||||
{
|
||||
if (dev) {
|
||||
if (dev->usb->handle) {
|
||||
feldev_release(dev);
|
||||
libusb_close(dev->usb->handle);
|
||||
}
|
||||
free(dev->usb); /* release memory allocated for felusb_handle */
|
||||
}
|
||||
}
|
||||
|
||||
void feldev_init(void)
|
||||
{
|
||||
int rc = libusb_init(NULL);
|
||||
if (rc != 0)
|
||||
usb_error(rc, "libusb_init()", 1);
|
||||
}
|
||||
|
||||
void feldev_done(feldev_handle *dev)
|
||||
{
|
||||
feldev_close(dev);
|
||||
free(dev);
|
||||
libusb_exit(NULL);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
bool uboot_autostart = false; /* flag for "uboot" command = U-Boot autostart */
|
||||
@ -1567,7 +1195,7 @@ int main(int argc, char **argv)
|
||||
}
|
||||
|
||||
feldev_init();
|
||||
handle = open_fel_device(busnum, devnum, AW_USB_VENDOR_ID, AW_USB_PRODUCT_ID);
|
||||
handle = feldev_open(busnum, devnum, AW_USB_VENDOR_ID, AW_USB_PRODUCT_ID);
|
||||
|
||||
while (argc > 1 ) {
|
||||
int skip = 1;
|
||||
|
||||
414
fel_lib.c
Normal file
414
fel_lib.c
Normal file
@ -0,0 +1,414 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Henrik Nordstrom <henrik@henriknordstrom.net>
|
||||
* Copyright (C) 2015 Siarhei Siamashka <siarhei.siamashka@gmail.com>
|
||||
* Copyright (C) 2016 Bernhard Nortmann <bernhard.nortmann@web.de>
|
||||
*
|
||||
* 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, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**********************************************************************
|
||||
* USB library and helper functions for the FEL utility
|
||||
**********************************************************************/
|
||||
|
||||
#include "portable_endian.h"
|
||||
#include "fel_lib.h"
|
||||
#include <libusb.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define USB_TIMEOUT 10000 /* 10 seconds */
|
||||
|
||||
|
||||
/* This is out 'private' data type that will be part of a "FEL device" handle */
|
||||
struct _felusb_handle {
|
||||
libusb_device_handle *handle;
|
||||
int endpoint_out, endpoint_in;
|
||||
bool iface_detached;
|
||||
};
|
||||
|
||||
/* a helper function to report libusb errors */
|
||||
void usb_error(int rc, const char *caption, int exitcode)
|
||||
{
|
||||
if (caption)
|
||||
fprintf(stderr, "%s ", caption);
|
||||
|
||||
#if defined(LIBUSBX_API_VERSION) && (LIBUSBX_API_VERSION >= 0x01000102)
|
||||
fprintf(stderr, "ERROR %d: %s\n", rc, libusb_strerror(rc));
|
||||
#else
|
||||
/* assume that libusb_strerror() is missing in the libusb API */
|
||||
fprintf(stderr, "ERROR %d\n", rc);
|
||||
#endif
|
||||
|
||||
if (exitcode != 0)
|
||||
exit(exitcode);
|
||||
}
|
||||
|
||||
/*
|
||||
* AW_USB_MAX_BULK_SEND and the timeout constant USB_TIMEOUT are related.
|
||||
* Both need to be selected in a way that transferring the maximum chunk size
|
||||
* with (SoC-specific) slow transfer speed won't time out.
|
||||
*
|
||||
* The 512 KiB here are chosen based on the assumption that we want a 10 seconds
|
||||
* timeout, and "slow" transfers take place at approx. 64 KiB/sec - so we can
|
||||
* expect the maximum chunk being transmitted within 8 seconds or less.
|
||||
*/
|
||||
static const int AW_USB_MAX_BULK_SEND = 512 * 1024; /* 512 KiB per bulk request */
|
||||
|
||||
void usb_bulk_send(libusb_device_handle *usb, int ep, const void *data,
|
||||
size_t length, bool progress)
|
||||
{
|
||||
/*
|
||||
* With no progress notifications, we'll use the maximum chunk size.
|
||||
* Otherwise, it's useful to lower the size (have more chunks) to get
|
||||
* more frequent status updates. 128 KiB per request seem suitable.
|
||||
* (Worst case of "slow" transfers -> one update every two seconds.)
|
||||
*/
|
||||
size_t max_chunk = progress ? 128 * 1024 : AW_USB_MAX_BULK_SEND;
|
||||
|
||||
size_t chunk;
|
||||
int rc, sent;
|
||||
while (length > 0) {
|
||||
chunk = length < max_chunk ? length : max_chunk;
|
||||
rc = libusb_bulk_transfer(usb, ep, (void *)data, chunk,
|
||||
&sent, USB_TIMEOUT);
|
||||
if (rc != 0)
|
||||
usb_error(rc, "usb_bulk_send()", 2);
|
||||
length -= sent;
|
||||
data += sent;
|
||||
|
||||
if (progress)
|
||||
progress_update(sent); /* notification after each chunk */
|
||||
}
|
||||
}
|
||||
|
||||
void usb_bulk_recv(libusb_device_handle *usb, int ep, void *data, int length)
|
||||
{
|
||||
int rc, recv;
|
||||
while (length > 0) {
|
||||
rc = libusb_bulk_transfer(usb, ep, data, length,
|
||||
&recv, USB_TIMEOUT);
|
||||
if (rc != 0)
|
||||
usb_error(rc, "usb_bulk_recv()", 2);
|
||||
length -= recv;
|
||||
data += recv;
|
||||
}
|
||||
}
|
||||
|
||||
struct aw_usb_request {
|
||||
char signature[8];
|
||||
uint32_t length;
|
||||
uint32_t unknown1; /* 0x0c000000 */
|
||||
uint16_t request;
|
||||
uint32_t length2; /* Same as length */
|
||||
char pad[10];
|
||||
} __attribute__((packed));
|
||||
|
||||
#define AW_USB_READ 0x11
|
||||
#define AW_USB_WRITE 0x12
|
||||
|
||||
struct aw_fel_request {
|
||||
uint32_t request;
|
||||
uint32_t address;
|
||||
uint32_t length;
|
||||
uint32_t pad;
|
||||
};
|
||||
|
||||
/* FEL request types */
|
||||
#define AW_FEL_VERSION 0x001
|
||||
#define AW_FEL_1_WRITE 0x101
|
||||
#define AW_FEL_1_EXEC 0x102
|
||||
#define AW_FEL_1_READ 0x103
|
||||
|
||||
static void aw_send_usb_request(feldev_handle *dev, int type, int length)
|
||||
{
|
||||
struct aw_usb_request req = {
|
||||
.signature = "AWUC",
|
||||
.request = htole16(type),
|
||||
.length = htole32(length),
|
||||
.unknown1 = htole32(0x0c000000)
|
||||
};
|
||||
req.length2 = req.length;
|
||||
usb_bulk_send(dev->usb->handle, dev->usb->endpoint_out,
|
||||
&req, sizeof(req), false);
|
||||
}
|
||||
|
||||
static void aw_read_usb_response(feldev_handle *dev)
|
||||
{
|
||||
char buf[13];
|
||||
usb_bulk_recv(dev->usb->handle, dev->usb->endpoint_in,
|
||||
buf, sizeof(buf));
|
||||
assert(strcmp(buf, "AWUS") == 0);
|
||||
}
|
||||
|
||||
static void aw_usb_write(feldev_handle *dev, const void *data, size_t len,
|
||||
bool progress)
|
||||
{
|
||||
aw_send_usb_request(dev, AW_USB_WRITE, len);
|
||||
usb_bulk_send(dev->usb->handle, dev->usb->endpoint_out,
|
||||
data, len, progress);
|
||||
aw_read_usb_response(dev);
|
||||
}
|
||||
|
||||
static void aw_usb_read(feldev_handle *dev, const void *data, size_t len)
|
||||
{
|
||||
aw_send_usb_request(dev, AW_USB_READ, len);
|
||||
usb_bulk_send(dev->usb->handle, dev->usb->endpoint_in,
|
||||
data, len, false);
|
||||
aw_read_usb_response(dev);
|
||||
}
|
||||
|
||||
void aw_send_fel_request(feldev_handle *dev, int type,
|
||||
uint32_t addr, uint32_t length)
|
||||
{
|
||||
struct aw_fel_request req = {
|
||||
.request = htole32(type),
|
||||
.address = htole32(addr),
|
||||
.length = htole32(length)
|
||||
};
|
||||
aw_usb_write(dev, &req, sizeof(req), false);
|
||||
}
|
||||
|
||||
void aw_read_fel_status(feldev_handle *dev)
|
||||
{
|
||||
char buf[8];
|
||||
aw_usb_read(dev, buf, sizeof(buf));
|
||||
}
|
||||
|
||||
/* AW_FEL_VERSION request */
|
||||
void aw_fel_get_version(feldev_handle *dev, struct aw_fel_version *buf)
|
||||
{
|
||||
aw_send_fel_request(dev, AW_FEL_VERSION, 0, 0);
|
||||
aw_usb_read(dev, buf, sizeof(*buf));
|
||||
aw_read_fel_status(dev);
|
||||
|
||||
buf->soc_id = (le32toh(buf->soc_id) >> 8) & 0xFFFF;
|
||||
buf->unknown_0a = le32toh(buf->unknown_0a);
|
||||
buf->protocol = le32toh(buf->protocol);
|
||||
buf->scratchpad = le16toh(buf->scratchpad);
|
||||
buf->pad[0] = le32toh(buf->pad[0]);
|
||||
buf->pad[1] = le32toh(buf->pad[1]);
|
||||
}
|
||||
|
||||
/* AW_FEL_1_READ request */
|
||||
void aw_fel_read(feldev_handle *dev, uint32_t offset, void *buf, size_t len)
|
||||
{
|
||||
aw_send_fel_request(dev, AW_FEL_1_READ, offset, len);
|
||||
aw_usb_read(dev, buf, len);
|
||||
aw_read_fel_status(dev);
|
||||
}
|
||||
|
||||
/* AW_FEL_1_WRITE request */
|
||||
void aw_fel_write(feldev_handle *dev, void *buf, uint32_t offset, size_t len)
|
||||
{
|
||||
aw_send_fel_request(dev, AW_FEL_1_WRITE, offset, len);
|
||||
aw_usb_write(dev, buf, len, false);
|
||||
aw_read_fel_status(dev);
|
||||
}
|
||||
|
||||
/* AW_FEL_1_EXEC request */
|
||||
void aw_fel_execute(feldev_handle *dev, uint32_t offset)
|
||||
{
|
||||
aw_send_fel_request(dev, AW_FEL_1_EXEC, offset, 0);
|
||||
aw_read_fel_status(dev);
|
||||
}
|
||||
|
||||
/*
|
||||
* This function is a higher-level wrapper for the FEL write functionality.
|
||||
* Unlike aw_fel_write() above - which is reserved for internal use - this
|
||||
* routine optionally allows progress callbacks.
|
||||
*/
|
||||
void aw_fel_write_buffer(feldev_handle *dev, void *buf, uint32_t offset,
|
||||
size_t len, bool progress)
|
||||
{
|
||||
aw_send_fel_request(dev, AW_FEL_1_WRITE, offset, len);
|
||||
aw_usb_write(dev, buf, len, progress);
|
||||
aw_read_fel_status(dev);
|
||||
}
|
||||
|
||||
/* general functions, "FEL device" management */
|
||||
|
||||
static int feldev_get_endpoint(feldev_handle *dev)
|
||||
{
|
||||
struct libusb_device *usb = libusb_get_device(dev->usb->handle);
|
||||
struct libusb_config_descriptor *config;
|
||||
int if_idx, set_idx, ep_idx, ret;
|
||||
const struct libusb_interface *iface;
|
||||
const struct libusb_interface_descriptor *setting;
|
||||
const struct libusb_endpoint_descriptor *ep;
|
||||
|
||||
ret = libusb_get_active_config_descriptor(usb, &config);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
for (if_idx = 0; if_idx < config->bNumInterfaces; if_idx++) {
|
||||
iface = config->interface + if_idx;
|
||||
|
||||
for (set_idx = 0; set_idx < iface->num_altsetting; set_idx++) {
|
||||
setting = iface->altsetting + set_idx;
|
||||
|
||||
for (ep_idx = 0; ep_idx < setting->bNumEndpoints; ep_idx++) {
|
||||
ep = setting->endpoint + ep_idx;
|
||||
|
||||
/* Test for bulk transfer endpoint */
|
||||
if ((ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK)
|
||||
!= LIBUSB_TRANSFER_TYPE_BULK)
|
||||
continue;
|
||||
|
||||
if ((ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK)
|
||||
== LIBUSB_ENDPOINT_IN)
|
||||
dev->usb->endpoint_in = ep->bEndpointAddress;
|
||||
else
|
||||
dev->usb->endpoint_out = ep->bEndpointAddress;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
libusb_free_config_descriptor(config);
|
||||
return LIBUSB_SUCCESS;
|
||||
}
|
||||
|
||||
/* claim USB interface associated with the libusb handle for a FEL device */
|
||||
void feldev_claim(feldev_handle *dev)
|
||||
{
|
||||
int rc = libusb_claim_interface(dev->usb->handle, 0);
|
||||
#if defined(__linux__)
|
||||
if (rc != LIBUSB_SUCCESS) {
|
||||
libusb_detach_kernel_driver(dev->usb->handle, 0);
|
||||
dev->usb->iface_detached = true;
|
||||
rc = libusb_claim_interface(dev->usb->handle, 0);
|
||||
}
|
||||
#endif
|
||||
if (rc)
|
||||
usb_error(rc, "libusb_claim_interface()", 1);
|
||||
|
||||
rc = feldev_get_endpoint(dev);
|
||||
if (rc)
|
||||
usb_error(rc, "FAILED to get FEL mode endpoint addresses!", 1);
|
||||
}
|
||||
|
||||
/* release USB interface associated with the libusb handle for a FEL device */
|
||||
void feldev_release(feldev_handle *dev)
|
||||
{
|
||||
libusb_release_interface(dev->usb->handle, 0);
|
||||
#if defined(__linux__)
|
||||
if (dev->usb->iface_detached)
|
||||
libusb_attach_kernel_driver(dev->usb->handle, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* open handle to desired FEL device */
|
||||
feldev_handle *feldev_open(int busnum, int devnum,
|
||||
uint16_t vendor_id, uint16_t product_id)
|
||||
{
|
||||
feldev_handle *result = calloc(1, sizeof(feldev_handle));
|
||||
if (!result) {
|
||||
fprintf(stderr, "FAILED to allocate feldev_handle memory.\n");
|
||||
exit(1);
|
||||
}
|
||||
result->usb = calloc(1, sizeof(felusb_handle));
|
||||
if (!result->usb) {
|
||||
fprintf(stderr, "FAILED to allocate felusb_handle memory.\n");
|
||||
free(result);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (busnum < 0 || devnum < 0) {
|
||||
/* With the default values (busnum -1, devnum -1) we don't care
|
||||
* for a specific USB device; so let libusb open the first
|
||||
* device that matches VID/PID.
|
||||
*/
|
||||
result->usb->handle = libusb_open_device_with_vid_pid(NULL, vendor_id, product_id);
|
||||
if (!result->usb->handle) {
|
||||
switch (errno) {
|
||||
case EACCES:
|
||||
fprintf(stderr, "ERROR: You don't have permission to access Allwinner USB FEL device\n");
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "ERROR: Allwinner USB FEL device not found!\n");
|
||||
break;
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
/* look for specific bus and device number */
|
||||
bool found = false;
|
||||
ssize_t rc, i;
|
||||
libusb_device **list;
|
||||
|
||||
rc = libusb_get_device_list(NULL, &list);
|
||||
if (rc < 0)
|
||||
usb_error(rc, "libusb_get_device_list()", 1);
|
||||
for (i = 0; i < rc; i++) {
|
||||
if (libusb_get_bus_number(list[i]) == busnum
|
||||
&& libusb_get_device_address(list[i]) == devnum) {
|
||||
found = true; /* bus:devnum matched */
|
||||
struct libusb_device_descriptor desc;
|
||||
libusb_get_device_descriptor(list[i], &desc);
|
||||
if (desc.idVendor != vendor_id
|
||||
|| desc.idProduct != product_id) {
|
||||
fprintf(stderr, "ERROR: Bus %03d Device %03d not a FEL device "
|
||||
"(expected %04x:%04x, got %04x:%04x)\n", busnum, devnum,
|
||||
vendor_id, product_id, desc.idVendor, desc.idProduct);
|
||||
exit(1);
|
||||
}
|
||||
/* open handle to this specific device (incrementing its refcount) */
|
||||
rc = libusb_open(list[i], &result->usb->handle);
|
||||
if (rc != 0)
|
||||
usb_error(rc, "libusb_open()", 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
libusb_free_device_list(list, true);
|
||||
|
||||
if (!found) {
|
||||
fprintf(stderr, "ERROR: Bus %03d Device %03d not found in libusb device list\n",
|
||||
busnum, devnum);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
feldev_claim(result); /* claim interface, detect USB endpoints */
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* close FEL device (optional, dev may be NULL) */
|
||||
void feldev_close(feldev_handle *dev)
|
||||
{
|
||||
if (dev) {
|
||||
if (dev->usb->handle) {
|
||||
feldev_release(dev);
|
||||
libusb_close(dev->usb->handle);
|
||||
}
|
||||
free(dev->usb); /* release memory allocated for felusb_handle */
|
||||
}
|
||||
}
|
||||
|
||||
void feldev_init(void)
|
||||
{
|
||||
int rc = libusb_init(NULL);
|
||||
if (rc != 0)
|
||||
usb_error(rc, "libusb_init()", 1);
|
||||
}
|
||||
|
||||
void feldev_done(feldev_handle *dev)
|
||||
{
|
||||
feldev_close(dev);
|
||||
free(dev);
|
||||
libusb_exit(NULL);
|
||||
}
|
||||
54
fel_lib.h
Normal file
54
fel_lib.h
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Bernhard Nortmann <bernhard.nortmann@web.de>
|
||||
*
|
||||
* 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, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef _SUNXI_TOOLS_FEL_LIB_H
|
||||
#define _SUNXI_TOOLS_FEL_LIB_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "progress.h"
|
||||
#include "soc_info.h"
|
||||
|
||||
/* USB identifiers for Allwinner device in FEL mode */
|
||||
#define AW_USB_VENDOR_ID 0x1F3A
|
||||
#define AW_USB_PRODUCT_ID 0xEFE8
|
||||
|
||||
typedef struct _felusb_handle felusb_handle; /* opaque data type */
|
||||
|
||||
/* More general FEL "device" handle, to be extended later */
|
||||
typedef struct {
|
||||
felusb_handle *usb;
|
||||
} feldev_handle;
|
||||
|
||||
/* FEL device management */
|
||||
|
||||
void feldev_init(void);
|
||||
void feldev_done(feldev_handle *dev);
|
||||
|
||||
feldev_handle *feldev_open(int busnum, int devnum,
|
||||
uint16_t vendor_id, uint16_t product_id);
|
||||
void feldev_close(feldev_handle *dev);
|
||||
|
||||
/* FEL functions */
|
||||
|
||||
void aw_fel_get_version(feldev_handle *dev, struct aw_fel_version *buf);
|
||||
void aw_fel_read(feldev_handle *dev, uint32_t offset, void *buf, size_t len);
|
||||
void aw_fel_write(feldev_handle *dev, void *buf, uint32_t offset, size_t len);
|
||||
void aw_fel_write_buffer(feldev_handle *dev, void *buf, uint32_t offset,
|
||||
size_t len, bool progress);
|
||||
void aw_fel_execute(feldev_handle *dev, uint32_t offset);
|
||||
|
||||
#endif /* _SUNXI_TOOLS_FEL_LIB_H */
|
||||
Loading…
x
Reference in New Issue
Block a user