diff options
Diffstat (limited to 'Radio/HW/BladeRF/src/backend/usb/usb.c')
-rw-r--r-- | Radio/HW/BladeRF/src/backend/usb/usb.c | 1430 |
1 files changed, 1430 insertions, 0 deletions
diff --git a/Radio/HW/BladeRF/src/backend/usb/usb.c b/Radio/HW/BladeRF/src/backend/usb/usb.c new file mode 100644 index 0000000..fba2c33 --- /dev/null +++ b/Radio/HW/BladeRF/src/backend/usb/usb.c @@ -0,0 +1,1430 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2014-2017 Nuand LLC + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <string.h> +#include <inttypes.h> + +#include "rel_assert.h" +#include "log.h" +#include "minmax.h" +#include "conversions.h" +#include "usb.h" + +#include "board/board.h" +#include "backend/backend.h" +#include "backend/backend_config.h" +#include "backend/usb/usb.h" +#include "driver/fx3_fw.h" +#include "streaming/async.h" +#include "helpers/version.h" + +#include "bladeRF.h" +#include "nios_pkt_formats.h" +#include "nios_legacy_access.h" +#include "nios_access.h" + +#if ENABLE_USB_DEV_RESET_ON_OPEN +bool bladerf_usb_reset_device_on_open = true; +#endif + +static const struct usb_driver *usb_driver_list[] = BLADERF_USB_BACKEND_LIST; + +/* FW declaration of fn table declared at the end of this file */ +const struct backend_fns backend_fns_usb_legacy; + +/* Vendor command wrapper to gets a 32-bit integer and supplies a wIndex */ +static inline int vendor_cmd_int_windex(struct bladerf *dev, uint8_t cmd, + uint16_t windex, int32_t *val) +{ + struct bladerf_usb *usb = dev->backend_data; + + return usb->fn->control_transfer(usb->driver, + USB_TARGET_DEVICE, + USB_REQUEST_VENDOR, + USB_DIR_DEVICE_TO_HOST, + cmd, 0, windex, + val, sizeof(uint32_t), + CTRL_TIMEOUT_MS); +} + +/* Vendor command wrapper to get a 32-bit integer and supplies wValue */ +static inline int vendor_cmd_int_wvalue(struct bladerf *dev, uint8_t cmd, + uint16_t wvalue, int32_t *val) +{ + struct bladerf_usb *usb = dev->backend_data; + + return usb->fn->control_transfer(usb->driver, + USB_TARGET_DEVICE, + USB_REQUEST_VENDOR, + USB_DIR_DEVICE_TO_HOST, + cmd, wvalue, 0, + val, sizeof(uint32_t), + CTRL_TIMEOUT_MS); +} + + +/* Vendor command that gets/sets a 32-bit integer value */ +static inline int vendor_cmd_int(struct bladerf *dev, uint8_t cmd, + usb_direction dir, int32_t *val) +{ + struct bladerf_usb *usb = dev->backend_data; + + return usb->fn->control_transfer(usb->driver, + USB_TARGET_DEVICE, + USB_REQUEST_VENDOR, + dir, cmd, 0, 0, + val, sizeof(int32_t), + CTRL_TIMEOUT_MS); +} + +static inline int change_setting(struct bladerf *dev, uint8_t setting) +{ + int status; + struct bladerf_usb *usb = dev->backend_data; + + log_verbose("Changing to USB alt setting %u\n", setting); + + status = usb->fn->change_setting(usb->driver, setting); + if (status != 0) { + log_debug("Failed to change setting: %s\n", bladerf_strerror(status)); + } + + return status; +} + +static int usb_is_fpga_configured(struct bladerf *dev) +{ + int result = -1; + int status; + + /* This environment variable provides a means to force libbladeRF to not + * attempt to access the FPGA. + * + * This provides a workaround for the situation where a user did not remove + * an FPGA in SPI flash prior to flashing new firmware and updating + * libbladeRF. Specifically, this has proven to be a problem with pre-v0.0.1 + * FPGA images, as they do not provide version readback functionality. + */ + if (getenv("BLADERF_FORCE_NO_FPGA_PRESENT")) { + log_debug("Reporting no FPGA present - " + "BLADERF_FORCE_NO_FPGA_PRESENT is set.\n"); + return 0; + } + + status = vendor_cmd_int(dev, BLADE_USB_CMD_QUERY_FPGA_STATUS, + USB_DIR_DEVICE_TO_HOST, &result); + + if (status < 0) { + return status; + } else if (result == 0 || result == 1) { + return result; + } else { + log_debug("Unexpected result from FPGA status query: %d\n", result); + return BLADERF_ERR_UNEXPECTED; + } +} + +static int usb_set_fpga_protocol(struct bladerf *dev, backend_fpga_protocol fpga_protocol) +{ + if (fpga_protocol == BACKEND_FPGA_PROTOCOL_NIOSII_LEGACY) { + dev->backend = &backend_fns_usb_legacy; + } else if (fpga_protocol == BACKEND_FPGA_PROTOCOL_NIOSII) { + dev->backend = &backend_fns_usb; + } else { + log_error("Unknown FPGA protocol: %d\n", fpga_protocol); + return BLADERF_ERR_INVAL; + } + + return 0; +} + +static bladerf_fpga_source usb_get_fpga_source(struct bladerf *dev) +{ + int result = -1; + int status; + + status = vendor_cmd_int(dev, BLADE_USB_CMD_QUERY_FPGA_SOURCE, + USB_DIR_DEVICE_TO_HOST, &result); + + if (status < 0) { + log_debug("%s: vendor_cmd_int returned %s\n", __FUNCTION__, + bladerf_strerror(status)); + return BLADERF_FPGA_SOURCE_UNKNOWN; + } else if (0 == result || 1 == result || 2 == result) { + return (bladerf_fpga_source)result; + } else { + log_debug("Unexpected result from FPGA source query: %d\n", result); + return BLADERF_FPGA_SOURCE_UNKNOWN; + } +} + +/* After performing a flash operation, switch back to either RF_LINK or the + * FPGA loader. + */ +static int restore_post_flash_setting(struct bladerf *dev) +{ + int fpga_loaded = usb_is_fpga_configured(dev); + int status; + + if (fpga_loaded < 0) { + status = fpga_loaded; + log_debug("Failed to determine if FPGA is loaded (%d)\n", fpga_loaded); + } else if (fpga_loaded) { + status = change_setting(dev, USB_IF_RF_LINK); + } else { + status = change_setting(dev, USB_IF_CONFIG); + } + + if (status < 0) { + log_debug("Failed to restore alt setting: %s\n", + bladerf_strerror(status)); + } + return status; +} + +static bool usb_matches(bladerf_backend backend) +{ + return backend == BLADERF_BACKEND_ANY || + backend == BLADERF_BACKEND_LINUX || + backend == BLADERF_BACKEND_LIBUSB || + backend == BLADERF_BACKEND_CYPRESS; +} + +static int usb_probe(backend_probe_target probe_target, + struct bladerf_devinfo_list *info_list) +{ + int status; + size_t i; + + for (i = status = 0; i < ARRAY_SIZE(usb_driver_list); i++) { + status = usb_driver_list[i]->fn->probe(probe_target, info_list); + } + + return status; +} + +static void usb_close(struct bladerf *dev) +{ + int status; + struct bladerf_usb *usb = dev->backend_data; + + if (usb != NULL) { + /* It seems we need to switch back to our NULL interface before closing, + * or else our device doesn't close upon exit in OSX and then fails to + * re-open cleanly */ + status = usb->fn->change_setting(usb->driver, USB_IF_NULL); + if (status != 0) { + log_error("Failed to switch to NULL interface: %s\n", + bladerf_strerror(status)); + } + + usb->fn->close(usb->driver); + free(usb); + dev->backend_data = NULL; + } +} + +static int usb_is_fw_ready(struct bladerf *dev) +{ + int status; + int result; + + status = vendor_cmd_int(dev, BLADE_USB_CMD_QUERY_DEVICE_READY, + USB_DIR_DEVICE_TO_HOST, &result); + if (status < 0) { + return status; + } else if (result == 0 || result == 1) { + return result; + } else { + log_debug("Unexpected result from firmware status query: %d\n", result); + return BLADERF_ERR_UNEXPECTED; + } +} + +static int usb_get_handle(struct bladerf *dev, + void **handle) +{ + struct bladerf_usb *usb = dev->backend_data; + int status; + + status = usb->fn->get_handle(usb->driver, handle); + + return status; +} + +static int usb_get_fw_version(struct bladerf *dev, + struct bladerf_version *version) +{ + struct bladerf_usb *usb = dev->backend_data; + int status; + + status = usb->fn->get_string_descriptor(usb->driver, + BLADE_USB_STR_INDEX_FW_VER, + (unsigned char *)version->describe, + BLADERF_VERSION_STR_MAX); + if (status == 0) { + status = str2version(version->describe, version); + } else { + log_warning("Failed to retrieve firmware version. This may be due " + "to an old firmware version that does not support " + "this request. A firmware update via the bootloader is " + "required.\n\n"); + status = BLADERF_ERR_UPDATE_FW; + } + + return status; +} + +static int usb_get_fpga_version(struct bladerf *dev, + struct bladerf_version *version) +{ + int status; + + status = change_setting(dev, USB_IF_RF_LINK); + if (status < 0) { + return status; + } + + /* Read and store FPGA version info. This is only possible after + * we've entered RF link mode. + * + * The legacy mode is used here since we can't yet determine if + * the FPGA is capable of using the newer packet formats. */ + return nios_legacy_get_fpga_version(dev, version); +} + +static int usb_open(struct bladerf *dev, struct bladerf_devinfo *info) +{ + int status; + size_t i; + struct bladerf_usb *usb; + + usb = malloc(sizeof(*usb)); + if (usb == NULL) { + return BLADERF_ERR_MEM; + } + + /* Try each matching usb driver */ + for (i = 0; i < ARRAY_SIZE(usb_driver_list); i++) { + if (info->backend == BLADERF_BACKEND_ANY + || usb_driver_list[i]->id == info->backend) { + usb->fn = usb_driver_list[i]->fn; + status = usb->fn->open(&usb->driver, info, &dev->ident); + if (status == 0) { + break; + } else if (status == BLADERF_ERR_NODEV) { + continue; + } else { + free(usb); + return status; + } + } + } + + /* If no usb driver was found */ + if (i == ARRAY_SIZE(usb_driver_list)) { + free(usb); + return BLADERF_ERR_NODEV; + } + + /* Default to legacy-mode access until we determine the FPGA is + * capable of handling newer request formats */ + dev->backend = &backend_fns_usb_legacy; + dev->backend_data = usb; + + /* Just out of paranoia, put the device into a known state */ + status = change_setting(dev, USB_IF_NULL); + if (status < 0) { + log_debug("Failed to switch to USB_IF_NULL\n"); + goto error; + } + +error: + if (status != 0) { + usb_close(dev); + } + + return status; +} + +static int usb_get_vid_pid(struct bladerf *dev, uint16_t *vid, uint16_t *pid) +{ + struct bladerf_usb *usb = dev->backend_data; + + return usb->fn->get_vid_pid(usb->driver, vid, pid); +} + +static int usb_get_flash_id(struct bladerf *dev, uint8_t *mid, uint8_t *did) +{ + int status; + int result; + + status = vendor_cmd_int(dev, BLADE_USB_CMD_QUERY_FLASH_ID, + USB_DIR_DEVICE_TO_HOST, &result); + if (status < 0) { + log_debug("Could not read flash manufacturer ID and/or device ID. %s.\n", + bladerf_strerror(status)); + } else { + *did = result & 0xFF; + *mid = (result >> 8) & 0xFF; + } + return status; +} + +static int begin_fpga_programming(struct bladerf *dev) +{ + int result; + int status = vendor_cmd_int(dev, BLADE_USB_CMD_BEGIN_PROG, + USB_DIR_DEVICE_TO_HOST, &result); + + if (status != 0) { + return status; + } else if (result != 0) { + log_debug("Startg fpga programming, result = %d\n", result); + return BLADERF_ERR_UNEXPECTED; + } else { + return 0; + } +} + +static int usb_load_fpga(struct bladerf *dev, const uint8_t *image, size_t image_size) +{ + struct bladerf_usb *usb = dev->backend_data; + + unsigned int wait_count; + const unsigned int timeout_ms = (3 * CTRL_TIMEOUT_MS); + int status; + + /* Switch to the FPGA configuration interface */ + status = change_setting(dev, USB_IF_CONFIG); + if(status < 0) { + log_debug("Failed to switch to FPGA config setting: %s\n", + bladerf_strerror(status)); + return status; + } + + /* Begin programming */ + status = begin_fpga_programming(dev); + if (status < 0) { + log_debug("Failed to initiate FPGA programming: %s\n", + bladerf_strerror(status)); + return status; + } + + /* Send the file down */ + assert(image_size <= UINT32_MAX); + status = usb->fn->bulk_transfer(usb->driver, PERIPHERAL_EP_OUT, + (void *)image, + (uint32_t)image_size, + timeout_ms); + if (status < 0) { + log_debug("Failed to write FPGA bitstream to FPGA: %s\n", + bladerf_strerror(status)); + return status; + } + + /* Poll FPGA status to determine if programming was a success */ + wait_count = 10; + status = 0; + + while (wait_count > 0 && status == 0) { + status = usb_is_fpga_configured(dev); + if (status == 1) { + break; + } + + usleep(200000); + wait_count--; + } + + /* Failed to determine if FPGA is loaded */ + if (status < 0) { + log_debug("Failed to determine if FPGA is loaded: %s\n", + bladerf_strerror(status)); + return status; + } else if (wait_count == 0 && status != 0) { + log_debug("Timeout while waiting for FPGA configuration status\n"); + return BLADERF_ERR_TIMEOUT; + } + + return 0; +} + +static inline int perform_erase(struct bladerf *dev, uint16_t block) +{ + int status, erase_ret; + struct bladerf_usb *usb = dev->backend_data; + + status = usb->fn->control_transfer(usb->driver, + USB_TARGET_DEVICE, + USB_REQUEST_VENDOR, + USB_DIR_DEVICE_TO_HOST, + BLADE_USB_CMD_FLASH_ERASE, + 0, block, + &erase_ret, sizeof(erase_ret), + CTRL_TIMEOUT_MS); + + + return status; +} + +static int usb_erase_flash_blocks(struct bladerf *dev, + uint32_t eb, + uint16_t count) +{ + int status, restore_status; + uint16_t i; + + status = change_setting(dev, USB_IF_SPI_FLASH); + if (status != 0) { + return status; + } + + log_info("Erasing %u block%s starting at block %u\n", count, + 1 == count ? "" : "s", eb); + + for (i = 0; i < count; i++) { + log_info("Erasing block %u (%u%%)...%c", eb + i, + (i + 1) == count ? 100 : 100 * i / count, + (i + 1) == count ? '\n' : '\r'); + + status = perform_erase(dev, eb + i); + if (status != 0) { + log_debug("Failed to erase block %u: %s\n", eb + i, + bladerf_strerror(status)); + goto error; + } + } + + log_info("Done erasing %u block%s\n", count, 1 == count ? "" : "s"); + +error: + restore_status = restore_post_flash_setting(dev); + return status != 0 ? status : restore_status; +} + +static inline int read_page(struct bladerf *dev, uint8_t read_operation, + uint16_t page, uint8_t *buf) +{ + struct bladerf_usb *usb = dev->backend_data; + bladerf_dev_speed usb_speed; + int status; + int32_t op_status; + uint16_t read_size; + uint16_t offset; + uint8_t request; + + if (usb->fn->get_speed(usb->driver, &usb_speed) != 0) { + log_debug("Error getting USB speed in %s\n", __FUNCTION__); + return BLADERF_ERR_UNEXPECTED; + } + + if (usb_speed == BLADERF_DEVICE_SPEED_SUPER) { + read_size = dev->flash_arch->psize_bytes; + } else if (usb_speed == BLADERF_DEVICE_SPEED_HIGH) { + read_size = 64; + } else { + log_debug("Encountered unknown USB speed in %s\n", __FUNCTION__); + return BLADERF_ERR_UNEXPECTED; + } + + if (read_operation == BLADE_USB_CMD_FLASH_READ || + read_operation == BLADE_USB_CMD_READ_OTP) { + + status = vendor_cmd_int_windex(dev, read_operation, page, &op_status); + if (status != 0) { + return status; + } else if (op_status != 0) { + log_error("Firmware page read (op=%d) failed at page %u: %d\n", + read_operation, page, op_status); + return BLADERF_ERR_UNEXPECTED; + } + + /* Both of these operations require a read from the FW's page buffer */ + request = BLADE_USB_CMD_READ_PAGE_BUFFER; + + } else if (read_operation == BLADE_USB_CMD_READ_CAL_CACHE) { + request = read_operation; + } else { + assert(!"Bug - invalid read_operation value"); + return BLADERF_ERR_UNEXPECTED; + } + + /* Retrieve data from the firmware page buffer */ + for (offset = 0; offset < dev->flash_arch->psize_bytes; offset += read_size) { + status = usb->fn->control_transfer(usb->driver, + USB_TARGET_DEVICE, + USB_REQUEST_VENDOR, + USB_DIR_DEVICE_TO_HOST, + request, + 0, + offset, /* in bytes */ + buf + offset, + read_size, + CTRL_TIMEOUT_MS); + + if(status < 0) { + log_debug("Failed to read page buffer at offset 0x%02x: %s\n", + offset, bladerf_strerror(status)); + return status; + } + } + + return 0; +} + +static int usb_read_flash_pages(struct bladerf *dev, + uint8_t *buf, + uint32_t page_u32, + uint32_t count_u32) +{ + int status; + size_t n_read; + uint16_t i; + + /* 16-bit control transfer fields are used for these. + * The current bladeRF build only has a 4MiB flash, anyway. */ + const uint16_t page = (uint16_t)page_u32; + const uint16_t count = (uint16_t)count_u32; + + assert(page == page_u32); + assert(count == count_u32); + + status = change_setting(dev, USB_IF_SPI_FLASH); + if (status != 0) { + return status; + } + + log_info("Reading %u page%s starting at page %u\n", count, + 1 == count ? "" : "s", page); + + for (n_read = i = 0; i < count; i++) { + log_info("Reading page %u (%u%%)...%c", page + i, + (i + 1) == count ? 100 : 100 * i / count, + (i + 1) == count ? '\n' : '\r'); + + status = + read_page(dev, BLADE_USB_CMD_FLASH_READ, page + i, buf + n_read); + if (status != 0) { + goto error; + } + + n_read += dev->flash_arch->psize_bytes; + } + + log_info("Done reading %u page%s\n", count, 1 == count ? "" : "s"); + +error: + status = restore_post_flash_setting(dev); + return status; +} + +static int write_page(struct bladerf *dev, uint8_t write_operation, + uint16_t page, const uint8_t *buf) +{ + int status; + int32_t commit_status; + uint16_t offset; + uint16_t write_size; + struct bladerf_usb *usb = dev->backend_data; + bladerf_dev_speed usb_speed; + + if (usb->fn->get_speed(usb->driver, &usb_speed) != 0) { + log_debug("Error getting USB speed in %s\n", __FUNCTION__); + return BLADERF_ERR_UNEXPECTED; + } + + if (usb_speed == BLADERF_DEVICE_SPEED_SUPER) { + write_size = dev->flash_arch->psize_bytes; + } else if (usb_speed == BLADERF_DEVICE_SPEED_HIGH) { + write_size = 64; + } else { + assert(!"BUG - unexpected device speed"); + return BLADERF_ERR_UNEXPECTED; + } + + /* Write the data to the firmware's page buffer. + * Casting away the buffer's const-ness here is gross, but this buffer + * will not be written to on an out transfer. */ + for (offset = 0; offset < dev->flash_arch->psize_bytes; offset += write_size) { + status = usb->fn->control_transfer(usb->driver, + USB_TARGET_DEVICE, + USB_REQUEST_VENDOR, + USB_DIR_HOST_TO_DEVICE, + BLADE_USB_CMD_WRITE_PAGE_BUFFER, + 0, + offset, + (uint8_t*)&buf[offset], + write_size, + CTRL_TIMEOUT_MS); + + if(status < 0) { + log_error("Failed to write page buffer at offset 0x%02x " + "for page %u: %s\n", + offset, page, bladerf_strerror(status)); + return status; + } + } + + /* Commit the page to flash */ + status = vendor_cmd_int_windex(dev, write_operation, page, &commit_status); + + if (status != 0) { + log_error("Failed to commit page %u: %s\n", page, + bladerf_strerror(status)); + return status; + + } else if (commit_status != 0) { + log_error("Failed to commit page %u, FW returned %d\n", page, + commit_status); + + return BLADERF_ERR_UNEXPECTED; + } + + return 0; +} + +static int usb_write_flash_pages(struct bladerf *dev, + const uint8_t *buf, + uint32_t page_u32, + uint32_t count_u32) + +{ + int status, restore_status; + uint16_t i; + size_t n_written; + + /* 16-bit control transfer fields are used for these. + * The current bladeRF build only has a 4MiB flash, anyway. */ + const uint16_t page = (uint16_t)page_u32; + const uint16_t count = (uint16_t)count_u32; + + assert(page == page_u32); + assert(count == count_u32); + + status = change_setting(dev, USB_IF_SPI_FLASH); + if (status != 0) { + return status; + } + + log_info("Writing %u page%s starting at page %u\n", count, + 1 == count ? "" : "s", page); + + n_written = 0; + for (i = 0; i < count; i++) { + log_info("Writing page %u (%u%%)...%c", page + i, + (i + 1) == count ? 100 : 100 * i / count, + (i + 1) == count ? '\n' : '\r'); + + status = write_page(dev, BLADE_USB_CMD_FLASH_WRITE, page + i, buf + n_written); + if (status) { + goto error; + } + + n_written += dev->flash_arch->psize_bytes; + } + log_info("Done writing %u page%s\n", count, 1 == count ? "" : "s"); + +error: + restore_status = restore_post_flash_setting(dev); + if (status != 0) { + return status; + } else if (restore_status != 0) { + return restore_status; + } else { + return 0; + } +} + +static int usb_device_reset(struct bladerf *dev) +{ + struct bladerf_usb *usb = dev->backend_data; + + return usb->fn->control_transfer(usb->driver, USB_TARGET_DEVICE, + USB_REQUEST_VENDOR, + USB_DIR_HOST_TO_DEVICE, + BLADE_USB_CMD_RESET, + 0, 0, 0, 0, CTRL_TIMEOUT_MS); + +} + +static int usb_jump_to_bootloader(struct bladerf *dev) +{ + struct bladerf_usb *usb = dev->backend_data; + + return usb->fn->control_transfer(usb->driver, USB_TARGET_DEVICE, + USB_REQUEST_VENDOR, + USB_DIR_HOST_TO_DEVICE, + BLADE_USB_CMD_JUMP_TO_BOOTLOADER, + 0, 0, 0, 0, CTRL_TIMEOUT_MS); +} + +static int usb_get_cal(struct bladerf *dev, char *cal) +{ + const uint16_t dummy_page = 0; + int status, restore_status; + + assert(CAL_BUFFER_SIZE == dev->flash_arch->psize_bytes); + + status = change_setting(dev, USB_IF_SPI_FLASH); + if (status) { + return status; + } + + status = read_page(dev, BLADE_USB_CMD_READ_CAL_CACHE, + dummy_page, (uint8_t*)cal); + + restore_status = restore_post_flash_setting(dev); + return status == 0 ? restore_status : status; +} + +static int usb_get_otp(struct bladerf *dev, char *otp) +{ + int status, restore_status; + const uint16_t dummy_page = 0; + + status = change_setting(dev, USB_IF_SPI_FLASH); + if (status) { + return status; + } + + status = read_page(dev, BLADE_USB_CMD_READ_OTP, dummy_page, (uint8_t*)otp); + restore_status = restore_post_flash_setting(dev); + return status == 0 ? restore_status : status; +} + +static int usb_write_otp(struct bladerf *dev, char *otp) +{ + int status, restore_status; + const uint16_t dummy_page = 0; + + status = change_setting(dev, USB_IF_SPI_FLASH); + if (status) { + return status; + } + + status = write_page(dev, BLADE_USB_CMD_WRITE_OTP, dummy_page, (uint8_t*)otp); + restore_status = restore_post_flash_setting(dev); + return status == 0 ? restore_status : status; +} + +static int usb_lock_otp(struct bladerf *dev) +{ + int status, restore_status, commit_status; + + status = change_setting(dev, USB_IF_SPI_FLASH); + if (status) { + return status; + } + + status = vendor_cmd_int_windex(dev, BLADE_USB_CMD_LOCK_OTP, + 0, &commit_status); + + if (commit_status != 0) { + log_error("Failed to lock OTP, FW returned %d\n", commit_status); + if (status == 0) + status = commit_status; + } + + restore_status = restore_post_flash_setting(dev); + return status == 0 ? restore_status : status; +} + +static int usb_get_device_speed(struct bladerf *dev, bladerf_dev_speed *speed) +{ + struct bladerf_usb *usb = dev->backend_data; + + return usb->fn->get_speed(usb->driver, speed); +} + +static int usb_set_firmware_loopback(struct bladerf *dev, bool enable) { + int result; + int status; + + status = vendor_cmd_int_wvalue(dev, BLADE_USB_CMD_SET_LOOPBACK, + enable, &result); + if (status != 0) { + return status; + } + + + status = change_setting(dev, USB_IF_NULL); + if (status == 0) { + status = change_setting(dev, USB_IF_RF_LINK); + } + + return status; +} + +static int usb_get_firmware_loopback(struct bladerf *dev, bool *is_enabled) +{ + int status, result; + + status = vendor_cmd_int(dev, BLADE_USB_CMD_GET_LOOPBACK, + USB_DIR_DEVICE_TO_HOST, &result); + + if (status == 0) { + *is_enabled = (result != 0); + } + + return status; +} + +static int usb_enable_module(struct bladerf *dev, bladerf_direction dir, bool enable) +{ + int status; + int32_t fx3_ret = -1; + const uint16_t val = enable ? 1 : 0; + const uint8_t cmd = (dir == BLADERF_RX) ? + BLADE_USB_CMD_RF_RX : BLADE_USB_CMD_RF_TX; + + status = vendor_cmd_int_wvalue(dev, cmd, val, &fx3_ret); + if (status != 0) { + log_debug("Could not enable RF %s (%d): %s\n", + (dir == BLADERF_RX) ? "RX" : "TX", + status, bladerf_strerror(status)); + + } else if (fx3_ret != 0) { + log_warning("FX3 reported error=0x%x when %s RF %s\n", + fx3_ret, + enable ? "enabling" : "disabling", + (dir == BLADERF_RX) ? "RX" : "TX"); + + /* FIXME: Work around what seems to be a harmless failure. + * It appears that in firmware or in the lib, we may be + * attempting to disable an already disabled channel, or + * enabling an already enabled channel. + * + * Further investigation required + * + * 0x44 corresponds to CY_U3P_ERROR_ALREADY_STARTED + */ + if (fx3_ret != 0x44) { + status = BLADERF_ERR_UNEXPECTED; + } + } + + return status; +} + +static int usb_init_stream(struct bladerf_stream *stream, size_t num_transfers) +{ + struct bladerf_usb *usb = stream->dev->backend_data; + return usb->fn->init_stream(usb->driver, stream, num_transfers); +} + +static int usb_stream(struct bladerf_stream *stream, bladerf_channel_layout layout) +{ + struct bladerf_usb *usb = stream->dev->backend_data; + return usb->fn->stream(usb->driver, stream, layout); +} + +int usb_submit_stream_buffer(struct bladerf_stream *stream, void *buffer, + size_t *length, unsigned int timeout_ms, bool nonblock) +{ + struct bladerf_usb *usb = stream->dev->backend_data; + return usb->fn->submit_stream_buffer(usb->driver, stream, buffer, + length, timeout_ms, nonblock); +} + +static void usb_deinit_stream(struct bladerf_stream *stream) +{ + struct bladerf_usb *usb = stream->dev->backend_data; + usb->fn->deinit_stream(usb->driver, stream); +} + +/* + * Information about the boot image format and boot over USB can be found in + * Cypress AN76405: EZ-USB (R) FX3 (TM) Boot Options: + * http://www.cypress.com/?docID=49862 + * + * There's a request (bRequset = 0xc0) for the bootloader revision. + * However, there doesn't appear to be any documented reason to check this and + * behave differently depending upon the returned value. + */ + +/* Command fields for FX3 firmware upload vendor requests */ +#define FX3_BOOTLOADER_LOAD_BREQUEST 0xa0 +#define FX3_BOOTLOADER_ADDR_WVALUE(addr) (HOST_TO_LE16(addr & 0xffff)) +#define FX3_BOOTLOADER_ADDR_WINDEX(addr) (HOST_TO_LE16(((addr >> 16) & 0xffff))) +#define FX3_BOOTLOADER_MAX_LOAD_LEN 4096 + +static int write_and_verify_fw_chunk(struct bladerf_usb *usb, uint32_t addr, + uint8_t *data, uint32_t len, + uint8_t *readback_buf) { + + int status; + log_verbose("Writing %u bytes to bootloader @ 0x%08x\n", len, addr); + status = usb->fn->control_transfer(usb->driver, + USB_TARGET_DEVICE, + USB_REQUEST_VENDOR, + USB_DIR_HOST_TO_DEVICE, + FX3_BOOTLOADER_LOAD_BREQUEST, + FX3_BOOTLOADER_ADDR_WVALUE(addr), + FX3_BOOTLOADER_ADDR_WINDEX(addr), + data, + len, + CTRL_TIMEOUT_MS); + + if (status != 0) { + log_debug("Failed to write FW chunk (%d)\n", status); + return status; + } + + log_verbose("Reading back %u bytes from bootloader @ 0x%08x\n", len, addr); + status = usb->fn->control_transfer(usb->driver, + USB_TARGET_DEVICE, + USB_REQUEST_VENDOR, + USB_DIR_DEVICE_TO_HOST, + FX3_BOOTLOADER_LOAD_BREQUEST, + FX3_BOOTLOADER_ADDR_WVALUE(addr), + FX3_BOOTLOADER_ADDR_WINDEX(addr), + readback_buf, + len, + CTRL_TIMEOUT_MS); + + if (status != 0) { + log_debug("Failed to read back FW chunk (%d)\n", status); + return status; + } + + if (memcmp(data, readback_buf, len) != 0) { + log_debug("Readback did match written data.\n"); + status = BLADERF_ERR_UNEXPECTED; + } + + return status; +} + +static int execute_fw_from_bootloader(struct bladerf_usb *usb, uint32_t addr) +{ + int status; + + status = usb->fn->control_transfer(usb->driver, + USB_TARGET_DEVICE, + USB_REQUEST_VENDOR, + USB_DIR_HOST_TO_DEVICE, + FX3_BOOTLOADER_LOAD_BREQUEST, + FX3_BOOTLOADER_ADDR_WVALUE(addr), + FX3_BOOTLOADER_ADDR_WINDEX(addr), + NULL, + 0, + CTRL_TIMEOUT_MS); + + if (status != 0 && status != BLADERF_ERR_IO) { + log_debug("Failed to exec firmware: %s\n:", + bladerf_strerror(status)); + + } else if (status == BLADERF_ERR_IO) { + /* The device might drop out from underneath us as it starts executing + * the new firmware */ + log_verbose("Device returned IO error due to FW boot.\n"); + status = 0; + } else { + log_verbose("Booting new FW.\n"); + } + + return status; +} + +static int write_fw_to_bootloader(void *driver, struct fx3_firmware *fw) +{ + int status = 0; + uint32_t to_write; + uint32_t data_len; + uint32_t addr; + uint8_t *data; + bool got_section; + + uint8_t *readback = malloc(FX3_BOOTLOADER_MAX_LOAD_LEN); + if (readback == NULL) { + return BLADERF_ERR_MEM; + } + + do { + got_section = fx3_fw_next_section(fw, &addr, &data, &data_len); + if (got_section) { + /* data_len should never be zero, as fw->num_sections should NOT + * include the terminating section in its count */ + assert(data_len != 0); + + do { + to_write = u32_min(data_len, FX3_BOOTLOADER_MAX_LOAD_LEN); + + status = write_and_verify_fw_chunk(driver, + addr, data, to_write, + readback); + + data_len -= to_write; + addr += to_write; + data += to_write; + } while (data_len != 0 && status == 0); + } + } while (got_section && status == 0); + + if (status == 0) { + status = execute_fw_from_bootloader(driver, fx3_fw_entry_point(fw)); + } + + free(readback); + return status; +} + +static int usb_load_fw_from_bootloader(bladerf_backend backend, + uint8_t bus, uint8_t addr, + struct fx3_firmware *fw) +{ + int status = 0; + size_t i; + struct bladerf_usb usb; + + for (i = 0; i < ARRAY_SIZE(usb_driver_list); i++) { + + if ((backend == BLADERF_BACKEND_ANY) || + (usb_driver_list[i]->id == backend)) { + + usb.fn = usb_driver_list[i]->fn; + status = usb.fn->open_bootloader(&usb.driver, bus, addr); + if (status == 0) { + status = write_fw_to_bootloader(&usb, fw); + usb.fn->close_bootloader(usb.driver); + break; + } + } + } + + return status; +} + +/* Default handlers for operations unsupported by the NIOS II legacy packet + * format */ +static int set_vctcxo_tamer_mode_unsupported(struct bladerf *dev, + bladerf_vctcxo_tamer_mode mode) +{ + log_debug("Operation not supported with legacy NIOS packet format.\n"); + return BLADERF_ERR_UNSUPPORTED; +} + +static int get_vctcxo_tamer_mode_unsupported(struct bladerf *dev, + bladerf_vctcxo_tamer_mode *mode) +{ + *mode = BLADERF_VCTCXO_TAMER_INVALID; + log_debug("Operation not supported with legacy NIOS packet format.\n"); + return BLADERF_ERR_UNSUPPORTED; +} + +static int usb_read_fw_log(struct bladerf *dev, logger_entry *e) +{ + int status; + *e = LOG_EOF; + + status = vendor_cmd_int(dev, BLADE_USB_CMD_READ_LOG_ENTRY, + USB_DIR_DEVICE_TO_HOST, (int32_t*) e); + + return status; +} + +static int config_gpio_write(struct bladerf *dev, uint32_t val) +{ + struct bladerf_usb *usb = dev->backend_data; + bladerf_dev_speed usb_speed; + + if (usb->fn->get_speed(usb->driver, &usb_speed) != 0) { + log_debug("Error getting USB speed in %s\n", __FUNCTION__); + return BLADERF_ERR_UNEXPECTED; + } + + /* If we're connected at HS, we need to use smaller DMA transfers */ + if (usb_speed == BLADERF_DEVICE_SPEED_HIGH) { + val |= BLADERF_GPIO_FEATURE_SMALL_DMA_XFER; + } else if (usb_speed == BLADERF_DEVICE_SPEED_SUPER) { + val &= ~BLADERF_GPIO_FEATURE_SMALL_DMA_XFER; + } else { + assert(!"Encountered unknown USB speed"); + return BLADERF_ERR_UNEXPECTED; + } + + return nios_config_write(dev, val); +} + +static int set_agc_dc_correction_unsupported(struct bladerf *dev, + int16_t q_max, int16_t i_max, + int16_t q_mid, int16_t i_mid, + int16_t q_low, int16_t i_low) +{ + log_debug("Operation not supported with legacy NIOS packet format.\n"); + return BLADERF_ERR_UNSUPPORTED; +} + +static int legacy_config_gpio_write(struct bladerf *dev, uint32_t val) +{ + struct bladerf_usb *usb = dev->backend_data; + bladerf_dev_speed usb_speed; + + if (usb->fn->get_speed(usb->driver, &usb_speed) != 0) { + log_debug("Error getting USB speed in %s\n", __FUNCTION__); + return BLADERF_ERR_UNEXPECTED; + } + + /* If we're connected at HS, we need to use smaller DMA transfers */ + if (usb_speed == BLADERF_DEVICE_SPEED_HIGH) { + val |= BLADERF_GPIO_FEATURE_SMALL_DMA_XFER; + } else if (usb_speed == BLADERF_DEVICE_SPEED_SUPER) { + val &= ~BLADERF_GPIO_FEATURE_SMALL_DMA_XFER; + } else { + assert(!"Encountered unknown USB speed"); + return BLADERF_ERR_UNEXPECTED; + } + + return nios_legacy_config_write(dev, val); +} + +/* USB backend that used legacy format for communicating with NIOS II */ +const struct backend_fns backend_fns_usb_legacy = { + FIELD_INIT(.matches, usb_matches), + + FIELD_INIT(.probe, usb_probe), + + FIELD_INIT(.get_vid_pid, usb_get_vid_pid), + FIELD_INIT(.get_flash_id, usb_get_flash_id), + FIELD_INIT(.open, usb_open), + FIELD_INIT(.set_fpga_protocol, usb_set_fpga_protocol), + FIELD_INIT(.close, usb_close), + + FIELD_INIT(.is_fw_ready, usb_is_fw_ready), + + FIELD_INIT(.get_handle, usb_get_handle), + + FIELD_INIT(.load_fpga, usb_load_fpga), + FIELD_INIT(.is_fpga_configured, usb_is_fpga_configured), + FIELD_INIT(.get_fpga_source, usb_get_fpga_source), + + FIELD_INIT(.get_fw_version, usb_get_fw_version), + FIELD_INIT(.get_fpga_version, usb_get_fpga_version), + + FIELD_INIT(.erase_flash_blocks, usb_erase_flash_blocks), + FIELD_INIT(.read_flash_pages, usb_read_flash_pages), + FIELD_INIT(.write_flash_pages, usb_write_flash_pages), + + FIELD_INIT(.device_reset, usb_device_reset), + FIELD_INIT(.jump_to_bootloader, usb_jump_to_bootloader), + + FIELD_INIT(.get_cal, usb_get_cal), + FIELD_INIT(.get_otp, usb_get_otp), + FIELD_INIT(.write_otp, usb_write_otp), + FIELD_INIT(.lock_otp, usb_lock_otp), + FIELD_INIT(.get_device_speed, usb_get_device_speed), + + FIELD_INIT(.config_gpio_write, legacy_config_gpio_write), + FIELD_INIT(.config_gpio_read, nios_legacy_config_read), + + FIELD_INIT(.expansion_gpio_write, nios_legacy_expansion_gpio_write), + FIELD_INIT(.expansion_gpio_read, nios_legacy_expansion_gpio_read), + FIELD_INIT(.expansion_gpio_dir_write, nios_legacy_expansion_gpio_dir_write), + FIELD_INIT(.expansion_gpio_dir_read, nios_legacy_expansion_gpio_dir_read), + + FIELD_INIT(.set_iq_gain_correction, nios_legacy_set_iq_gain_correction), + FIELD_INIT(.set_iq_phase_correction, nios_legacy_set_iq_phase_correction), + FIELD_INIT(.get_iq_gain_correction, nios_legacy_get_iq_gain_correction), + FIELD_INIT(.get_iq_phase_correction, nios_legacy_get_iq_phase_correction), + + FIELD_INIT(.set_agc_dc_correction, set_agc_dc_correction_unsupported), + + FIELD_INIT(.get_timestamp, nios_legacy_get_timestamp), + + FIELD_INIT(.si5338_write, nios_legacy_si5338_write), + FIELD_INIT(.si5338_read, nios_legacy_si5338_read), + + FIELD_INIT(.lms_write, nios_legacy_lms6_write), + FIELD_INIT(.lms_read, nios_legacy_lms6_read), + + FIELD_INIT(.ina219_write, nios_legacy_ina219_write), + FIELD_INIT(.ina219_read, nios_legacy_ina219_read), + + FIELD_INIT(.ad9361_spi_write, nios_legacy_ad9361_spi_write), + FIELD_INIT(.ad9361_spi_read, nios_legacy_ad9361_spi_read), + + FIELD_INIT(.adi_axi_write, nios_legacy_adi_axi_write), + FIELD_INIT(.adi_axi_read, nios_legacy_adi_axi_read), + + FIELD_INIT(.rfic_command_write, nios_legacy_rfic_command_write), + FIELD_INIT(.rfic_command_read, nios_legacy_rfic_command_read), + + FIELD_INIT(.rffe_control_write, nios_legacy_rffe_control_write), + FIELD_INIT(.rffe_control_read, nios_legacy_rffe_control_read), + + FIELD_INIT(.rffe_fastlock_save, nios_legacy_rffe_fastlock_save), + + FIELD_INIT(.ad56x1_vctcxo_trim_dac_write, nios_legacy_ad56x1_vctcxo_trim_dac_write), + FIELD_INIT(.ad56x1_vctcxo_trim_dac_read, nios_legacy_ad56x1_vctcxo_trim_dac_read), + + FIELD_INIT(.adf400x_write, nios_legacy_adf400x_write), + FIELD_INIT(.adf400x_read, nios_legacy_adf400x_read), + + FIELD_INIT(.vctcxo_dac_write, nios_legacy_vctcxo_trim_dac_write), + FIELD_INIT(.vctcxo_dac_read, nios_vctcxo_trim_dac_read), + + FIELD_INIT(.set_vctcxo_tamer_mode, set_vctcxo_tamer_mode_unsupported), + FIELD_INIT(.get_vctcxo_tamer_mode, get_vctcxo_tamer_mode_unsupported), + + FIELD_INIT(.xb_spi, nios_legacy_xb200_synth_write), + + FIELD_INIT(.set_firmware_loopback, usb_set_firmware_loopback), + FIELD_INIT(.get_firmware_loopback, usb_get_firmware_loopback), + + FIELD_INIT(.enable_module, usb_enable_module), + + FIELD_INIT(.init_stream, usb_init_stream), + FIELD_INIT(.stream, usb_stream), + FIELD_INIT(.submit_stream_buffer, usb_submit_stream_buffer), + FIELD_INIT(.deinit_stream, usb_deinit_stream), + + FIELD_INIT(.retune, nios_retune), + FIELD_INIT(.retune2, nios_retune2), + + FIELD_INIT(.load_fw_from_bootloader, usb_load_fw_from_bootloader), + + FIELD_INIT(.read_fw_log, usb_read_fw_log), + + FIELD_INIT(.read_trigger, nios_legacy_read_trigger), + FIELD_INIT(.write_trigger, nios_legacy_write_trigger), + + FIELD_INIT(.name, "usb"), +}; + +/* USB backend for use with FPGA supporting update NIOS II packet formats */ +const struct backend_fns backend_fns_usb = { + FIELD_INIT(.matches, usb_matches), + + FIELD_INIT(.probe, usb_probe), + + FIELD_INIT(.get_vid_pid, usb_get_vid_pid), + FIELD_INIT(.get_flash_id, usb_get_flash_id), + FIELD_INIT(.open, usb_open), + FIELD_INIT(.set_fpga_protocol, usb_set_fpga_protocol), + FIELD_INIT(.close, usb_close), + + FIELD_INIT(.is_fw_ready, usb_is_fw_ready), + + FIELD_INIT(.get_handle, usb_get_handle), + + FIELD_INIT(.load_fpga, usb_load_fpga), + FIELD_INIT(.is_fpga_configured, usb_is_fpga_configured), + FIELD_INIT(.get_fpga_source, usb_get_fpga_source), + + FIELD_INIT(.get_fw_version, usb_get_fw_version), + FIELD_INIT(.get_fpga_version, usb_get_fpga_version), + + FIELD_INIT(.erase_flash_blocks, usb_erase_flash_blocks), + FIELD_INIT(.read_flash_pages, usb_read_flash_pages), + FIELD_INIT(.write_flash_pages, usb_write_flash_pages), + + FIELD_INIT(.device_reset, usb_device_reset), + FIELD_INIT(.jump_to_bootloader, usb_jump_to_bootloader), + + FIELD_INIT(.get_cal, usb_get_cal), + FIELD_INIT(.get_otp, usb_get_otp), + FIELD_INIT(.write_otp, usb_write_otp), + FIELD_INIT(.lock_otp, usb_lock_otp), + FIELD_INIT(.get_device_speed, usb_get_device_speed), + + FIELD_INIT(.config_gpio_write, config_gpio_write), + FIELD_INIT(.config_gpio_read, nios_config_read), + + FIELD_INIT(.expansion_gpio_write, nios_expansion_gpio_write), + FIELD_INIT(.expansion_gpio_read, nios_expansion_gpio_read), + FIELD_INIT(.expansion_gpio_dir_write, nios_expansion_gpio_dir_write), + FIELD_INIT(.expansion_gpio_dir_read, nios_expansion_gpio_dir_read), + + FIELD_INIT(.set_iq_gain_correction, nios_set_iq_gain_correction), + FIELD_INIT(.set_iq_phase_correction, nios_set_iq_phase_correction), + FIELD_INIT(.get_iq_gain_correction, nios_get_iq_gain_correction), + FIELD_INIT(.get_iq_phase_correction, nios_get_iq_phase_correction), + + FIELD_INIT(.set_agc_dc_correction, nios_set_agc_dc_correction), + + FIELD_INIT(.get_timestamp, nios_get_timestamp), + + FIELD_INIT(.si5338_write, nios_si5338_write), + FIELD_INIT(.si5338_read, nios_si5338_read), + + FIELD_INIT(.lms_write, nios_lms6_write), + FIELD_INIT(.lms_read, nios_lms6_read), + + FIELD_INIT(.ina219_write, nios_ina219_write), + FIELD_INIT(.ina219_read, nios_ina219_read), + + FIELD_INIT(.ad9361_spi_write, nios_ad9361_spi_write), + FIELD_INIT(.ad9361_spi_read, nios_ad9361_spi_read), + + FIELD_INIT(.adi_axi_write, nios_adi_axi_write), + FIELD_INIT(.adi_axi_read, nios_adi_axi_read), + + FIELD_INIT(.wishbone_master_write, nios_wishbone_master_write), + FIELD_INIT(.wishbone_master_read, nios_wishbone_master_read), + + FIELD_INIT(.rfic_command_write, nios_rfic_command_write), + FIELD_INIT(.rfic_command_read, nios_rfic_command_read), + + FIELD_INIT(.rffe_control_write, nios_rffe_control_write), + FIELD_INIT(.rffe_control_read, nios_rffe_control_read), + + FIELD_INIT(.rffe_fastlock_save, nios_rffe_fastlock_save), + + FIELD_INIT(.ad56x1_vctcxo_trim_dac_write, nios_ad56x1_vctcxo_trim_dac_write), + FIELD_INIT(.ad56x1_vctcxo_trim_dac_read, nios_ad56x1_vctcxo_trim_dac_read), + + FIELD_INIT(.adf400x_write, nios_adf400x_write), + FIELD_INIT(.adf400x_read, nios_adf400x_read), + + FIELD_INIT(.vctcxo_dac_write, nios_vctcxo_trim_dac_write), + FIELD_INIT(.vctcxo_dac_read, nios_vctcxo_trim_dac_read), + + FIELD_INIT(.set_vctcxo_tamer_mode, nios_set_vctcxo_tamer_mode), + FIELD_INIT(.get_vctcxo_tamer_mode, nios_get_vctcxo_tamer_mode), + + FIELD_INIT(.xb_spi, nios_xb200_synth_write), + + FIELD_INIT(.set_firmware_loopback, usb_set_firmware_loopback), + FIELD_INIT(.get_firmware_loopback, usb_get_firmware_loopback), + + FIELD_INIT(.enable_module, usb_enable_module), + + FIELD_INIT(.init_stream, usb_init_stream), + FIELD_INIT(.stream, usb_stream), + FIELD_INIT(.submit_stream_buffer, usb_submit_stream_buffer), + FIELD_INIT(.deinit_stream, usb_deinit_stream), + + FIELD_INIT(.retune, nios_retune), + FIELD_INIT(.retune2, nios_retune2), + + FIELD_INIT(.load_fw_from_bootloader, usb_load_fw_from_bootloader), + + FIELD_INIT(.read_fw_log, usb_read_fw_log), + + FIELD_INIT(.read_trigger, nios_read_trigger), + FIELD_INIT(.write_trigger, nios_write_trigger), + + FIELD_INIT(.name, "usb"), +}; |