diff --git a/Makefile b/Makefile index f0fb7b0..7dbe0e7 100644 --- a/Makefile +++ b/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 diff --git a/fel.c b/fel.c index a7ef408..029210d 100644 --- a/fel.c +++ b/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 -#include -#include #include +#include +#include +#include +#include #include #include -#include -#include -#include -#include #include #include -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; diff --git a/fel_lib.c b/fel_lib.c new file mode 100644 index 0000000..0d46205 --- /dev/null +++ b/fel_lib.c @@ -0,0 +1,414 @@ +/* + * Copyright (C) 2012 Henrik Nordstrom + * Copyright (C) 2015 Siarhei Siamashka + * Copyright (C) 2016 Bernhard Nortmann + * + * 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 . + */ + +/********************************************************************** + * USB library and helper functions for the FEL utility + **********************************************************************/ + +#include "portable_endian.h" +#include "fel_lib.h" +#include + +#include +#include +#include +#include +#include + +#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); +} diff --git a/fel_lib.h b/fel_lib.h new file mode 100644 index 0000000..7f0524e --- /dev/null +++ b/fel_lib.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2016 Bernhard Nortmann + * + * 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 . + */ +#ifndef _SUNXI_TOOLS_FEL_LIB_H +#define _SUNXI_TOOLS_FEL_LIB_H + +#include +#include +#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 */