summaryrefslogtreecommitdiff
path: root/Radio/HW/BladeRF/src/driver/fx3_fw.c
blob: 423322ba6f611697d5f14f4343feac918ce458fe (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
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;
}