/* * 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 #include #include #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; }