summaryrefslogtreecommitdiff
path: root/Radio/HW/BladeRF/src/driver/fx3_fw.c
diff options
context:
space:
mode:
Diffstat (limited to 'Radio/HW/BladeRF/src/driver/fx3_fw.c')
-rw-r--r--Radio/HW/BladeRF/src/driver/fx3_fw.c343
1 files changed, 343 insertions, 0 deletions
diff --git a/Radio/HW/BladeRF/src/driver/fx3_fw.c b/Radio/HW/BladeRF/src/driver/fx3_fw.c
new file mode 100644
index 0000000..423322b
--- /dev/null
+++ b/Radio/HW/BladeRF/src/driver/fx3_fw.c
@@ -0,0 +1,343 @@
+/*
+ * This file implements functionality for reading and validating an FX3 firmware
+ * image, and providing access to the image contents.
+ *
+ * Details about the image format can be found and FX3 bootloader can be found
+ * in Cypress AN76405: EZ-USB (R) FX3 (TM) Boot Options:
+ * http://www.cypress.com/?docID=49862
+ *
+ * This file is part of the bladeRF project:
+ * http://www.github.com/nuand/bladeRF
+ *
+ * Copyright (C) 2014 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 <string.h>
+#include <stdint.h>
+
+#include <libbladeRF.h>
+
+#include "rel_assert.h"
+#include "host_config.h"
+#include "log.h"
+
+#include "fx3_fw.h"
+
+#define FX3_IMAGE_TYPE_NORMAL 0xb0 /* "Normal" image with checksum */
+
+#define FX3_HDR_SIG_IDX 0x00
+#define FX3_HDR_IMAGE_CTL_IDX 0x02
+#define FX3_HDR_IMAGE_TYPE_IDX 0x03
+#define FX3_HDR_IMAGE_LEN0_IDX 0x04
+#define FX3_HDR_IMAGE_ADDR0_IDX 0x08
+#define FX3_HDR_IMAGE_DATA0_IDX 0x0c
+
+#define FX3_HDR_LEN FX3_HDR_IMAGE_DATA0_IDX
+
+#define FX3_RAM_SIZE_WORDS (256 * 1024 / sizeof(uint32_t))
+
+struct fx3_firmware {
+ uint8_t *data;
+ uint32_t data_len;
+
+ uint32_t entry_addr;
+
+ uint32_t num_sections;
+ uint32_t curr_section;
+ uint32_t section_offset;
+};
+
+static inline uint32_t to_uint32(struct fx3_firmware *fw, uint32_t offset)
+{
+ uint32_t ret;
+
+ assert((offset + sizeof(uint32_t)) <= fw->data_len);
+
+ memcpy(&ret, &fw->data[offset], sizeof(ret));
+
+ return LE32_TO_HOST(ret);
+}
+
+static inline bool is_valid_fx3_ram_addr(uint32_t addr, uint32_t len) {
+ bool valid = true;
+
+ /* If you're doing something fun, wild, and crazy with the FX3 and your
+ * modifications of linker scripts has changed the firmware entry point,
+ * you'll need to add this compile-time definition to suppress this check.
+ *
+ * One potential improvement here would be to define the I-TCM and SYSMEM
+ * addresses at configuration/compilation-time to ensure they match
+ * what's in the FX3's linker script. The default values are assumed here.
+ */
+# ifndef BLADERF_SUPPRESS_FX3_FW_ENTRY_POINT_CHECK
+ const uint32_t itcm_base = 0x00000000;
+ const uint32_t itcm_len = 0x4000;
+ const uint32_t itcm_end = itcm_base + itcm_len;
+
+ const uint32_t sysmem_base = 0x40000000;
+ const uint32_t sysmem_len = 0x80000;
+ const uint32_t sysmem_end = sysmem_base + sysmem_len;
+
+ const bool in_itcm = (addr < itcm_end) &&
+ (len <= itcm_len) &&
+ ((addr + len) < itcm_end);
+
+ const bool in_sysmem = (addr >= sysmem_base) &&
+ (addr < sysmem_end) &&
+ (len <= sysmem_len) &&
+ ((addr + len) < sysmem_end);
+
+ /* In lieu of compilers issuing warnings over the fact that the condition
+ * (addr >= itcm_base) is always true, this condition has been removed.
+ *
+ * Instead, an assertion has been added to catch the attention of anyone
+ * making a change to the above itcm_base definition, albeit a *very*
+ * unlikely change to make. */
+ assert(itcm_base == 0); /* (addr >= itcm_base) guaranteed */
+
+ valid = in_itcm || in_sysmem;
+# endif
+
+ return valid;
+}
+
+static int scan_fw_sections(struct fx3_firmware *fw)
+{
+ int status = 0;
+ bool done = false; /* Have we read all the sections? */
+ uint32_t checksum = 0;
+
+ uint32_t offset, i; /* In bytes */
+ uint32_t next_section; /* Section offset in bytes */
+ uint32_t section_len_words; /* FW uses units of 32-bit words */
+ uint32_t section_len_bytes; /* Section length converted to bytes */
+
+ /* Byte offset where the checksum is expected to be */
+ const uint32_t checksum_off = fw->data_len - sizeof(uint32_t);
+
+ /* These assumptions should have been verified earlier */
+ assert(checksum_off > FX3_HDR_IMAGE_DATA0_IDX);
+ assert((checksum_off % 4) == 0);
+
+ offset = FX3_HDR_IMAGE_LEN0_IDX;
+
+ while (!done) {
+
+ /* Fetch the length of the current section */
+ section_len_words = to_uint32(fw, offset);
+
+ if (section_len_words > FX3_RAM_SIZE_WORDS) {
+ log_debug("Firmware section %u is unexpectedly large.\n",
+ fw->num_sections);
+ status = BLADERF_ERR_INVAL;
+ goto error;
+ } else {
+ section_len_bytes = (uint32_t)(section_len_words * sizeof(uint32_t));
+ offset += sizeof(uint32_t);
+ }
+
+ /* The list of sections is terminated by a 0 section length field */
+ if (section_len_bytes == 0) {
+ fw->entry_addr = to_uint32(fw, offset);
+ if (!is_valid_fx3_ram_addr(fw->entry_addr, 0)) {
+ status = BLADERF_ERR_INVAL;
+ goto error;
+ }
+
+ offset += sizeof(uint32_t);
+ done = true;
+ } else {
+# if LOGGING_ENABLED
+ /* Just a value to print in verbose output */
+ uint32_t section_start_offset = offset - sizeof(uint32_t);
+# endif
+
+ uint32_t addr = to_uint32(fw, offset);
+ if (!is_valid_fx3_ram_addr(addr, section_len_bytes)) {
+ status = BLADERF_ERR_INVAL;
+ goto error;
+ }
+
+ offset += sizeof(uint32_t);
+ if (offset >= checksum_off) {
+ log_debug("Firmware truncated after section address.\n");
+ status = BLADERF_ERR_INVAL;
+ goto error;
+ }
+
+ next_section = offset + section_len_bytes;
+
+ if (next_section >= checksum_off) {
+ log_debug("Firmware truncated in section %u\n",
+ fw->num_sections);
+ status = BLADERF_ERR_INVAL;
+ goto error;
+ }
+
+ for (i = offset; i < next_section; i += sizeof(uint32_t)) {
+ checksum += to_uint32(fw, i);
+ }
+
+ offset = next_section;
+ log_verbose("Scanned section %u at offset 0x%08x: "
+ "addr=0x%08x, len=0x%08x\n",
+ fw->num_sections, section_start_offset,
+ addr, section_len_words);
+
+ fw->num_sections++;
+ }
+ }
+
+ if (offset != checksum_off) {
+ log_debug("Invalid offset or junk at the end of the firmware image.\n");
+ status = BLADERF_ERR_INVAL;
+ } else {
+ const uint32_t expected_checksum = to_uint32(fw, checksum_off);
+
+ if (checksum != expected_checksum) {
+ log_debug("Bad checksum. Expected 0x%08x, got 0x%08x\n",
+ expected_checksum, checksum);
+
+ status = BLADERF_ERR_INVAL;
+ } else {
+ log_verbose("Firmware checksum OK.\n");
+ fw->section_offset = FX3_HDR_IMAGE_LEN0_IDX;
+ }
+ }
+
+error:
+ return status;
+}
+
+int fx3_fw_parse(struct fx3_firmware **fw, uint8_t *buf, size_t buf_len)
+{
+ int status;
+
+ if (buf_len > UINT32_MAX) {
+ /* This is just intended to catch a crazy edge case, since we're casting
+ * to 32-bits below. If this passes, the data length might still be well
+ * over the 512 KiB RAM limit. */
+ log_debug("Size of provided image is too large.\n");
+ return BLADERF_ERR_INVAL;
+ }
+
+ if (buf_len < FX3_HDR_LEN) {
+ log_debug("Provided image is too short.");
+ return BLADERF_ERR_INVAL;
+ }
+
+ if ((buf_len % 4) != 0) {
+ log_debug("Size of provided image is not a multiple of 4 bytes.\n");
+ return BLADERF_ERR_INVAL;
+ }
+
+ if (buf[FX3_HDR_SIG_IDX] != 'C' && buf[FX3_HDR_SIG_IDX + 1] != 'Y') {
+ log_debug("FX3 firmware does have 'CY' marker.\n");
+ return BLADERF_ERR_INVAL;
+ }
+
+ if (buf[3] != FX3_IMAGE_TYPE_NORMAL) {
+ log_debug("FX3 firmware header contained unexpected image type: "
+ "0x%02x\n", buf[FX3_HDR_IMAGE_TYPE_IDX]);
+ return BLADERF_ERR_INVAL;
+ }
+
+ *fw = calloc(1, sizeof(struct fx3_firmware));
+ if (*fw == NULL) {
+ return BLADERF_ERR_MEM;
+ }
+
+ (*fw)->data = malloc(buf_len);
+ if ((*fw)->data == NULL) {
+ free(*fw);
+ return BLADERF_ERR_MEM;
+ }
+
+ memcpy((*fw)->data, buf, buf_len);
+ (*fw)->data_len = (uint32_t)buf_len;
+
+ status = scan_fw_sections(*fw);
+ if (status != 0) {
+ goto error;
+ }
+
+ return 0;
+
+error:
+ fx3_fw_free(*fw);
+ return status;
+}
+
+void fx3_fw_free(struct fx3_firmware *fw)
+{
+ free(fw->data);
+ free(fw);
+}
+
+bool fx3_fw_next_section(struct fx3_firmware *fw, uint32_t *section_addr,
+ uint8_t **section_data, uint32_t *section_len)
+{
+ uint32_t len;
+ uint32_t addr;
+ uint8_t *data;
+
+ /* Max offset is the checksum address */
+ const uint32_t max_offset = fw->data_len - sizeof(uint32_t);
+
+ assert(fw != NULL);
+ assert(fw->data != NULL);
+
+ *section_addr = 0;
+ *section_data = NULL;
+ *section_len = 0;
+
+ if (fw->curr_section >= fw->num_sections) {
+ return false;
+ }
+
+ /* Length in bytes (as converted from 32-bit words) */
+ len = to_uint32(fw, fw->section_offset) * sizeof(uint32_t);
+ if (len == 0) {
+ return false;
+ }
+
+ /* Advance to address field */
+ fw->section_offset += sizeof(uint32_t);
+ assert(fw->section_offset < max_offset);
+ addr = to_uint32(fw, fw->section_offset);
+
+ /* Advance to data field */
+ fw->section_offset += sizeof(uint32_t);
+ assert(fw->section_offset < max_offset);
+ data = &fw->data[fw->section_offset];
+
+ /* Advance to the next section for the next call */
+ fw->section_offset += len;
+ assert(fw->section_offset < max_offset);
+ fw->curr_section++;
+
+ *section_addr = addr;
+ *section_data = data;
+ *section_len = len;
+ return true;
+}
+
+uint32_t fx3_fw_entry_point(const struct fx3_firmware *fw)
+{
+ assert(fw != NULL);
+ return fw->entry_addr;
+}