/* -*- c++ -*- */
/*
* Copyright 2009 Ettus Research LLC
*
* 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 3 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 .
*/
#include "loader_parser.h"
#include
#include
#include
#include
#include
//#include
#include
#include
#include
#include "ethernet.h"
#include "qr_settings.h"
#define min(a,b) ((a) < (b) ? (a) : (b))
static spi_flash_async_state_t async_state;
static caldiv_eeprom_setter_t _caldiv_set_rev = NULL;
static caldiv_eeprom_setter_t _caldiv_set_ser = NULL;
static caldiv_eeprom_setter_t _caldiv_set_mod = NULL;
void
register_caldiv_eeprom_setters(caldiv_eeprom_setter_t set_rev,
caldiv_eeprom_setter_t set_ser,
caldiv_eeprom_setter_t set_mod)
{
_caldiv_set_rev = set_rev;
_caldiv_set_ser = set_ser;
_caldiv_set_mod = set_mod;
}
// big-endian
static uint32_t
get32(const unsigned char *s)
{
return (s[0] << 24) | (s[1] << 16) | (s[2] << 8) | s[3];
}
// big-endian
static unsigned char *
put32(unsigned char *s, uint32_t v)
{
s[0] = (v >> 24) & 0xff;
s[1] = (v >> 16) & 0xff;
s[2] = (v >> 8) & 0xff;
s[3] = v & 0xff;
return s + 4;
}
static bool
erased_p(uint32_t flash_addr, size_t nbytes)
{
unsigned char buf[64];
size_t n;
for (size_t i = 0; i < nbytes; i += n, flash_addr += n){
n = min(nbytes - i, sizeof(buf));
spi_flash_read(flash_addr, n, buf);
for (size_t j = 0; j < n; j++)
if (buf[j] != 0xff)
return false;
}
return true;
}
static bool
erase_flash(uint32_t addr, uint32_t len)
{
if (addr % spi_flash_sector_size() != 0)
return false;
if (len % spi_flash_sector_size() != 0)
return false;
spi_flash_async_erase_start(&async_state, addr, len);
// FIXME? check to see if erase was successful
return true;
}
static bool
map_slot(uint32_t slot, uint32_t *slot_start, uint32_t *slot_len, uint32_t *status)
{
// This case doesn't require a valid flashdir, and in fact can be used as
// part of writing the intial flashdir.
if (QLD_SLOT_DOM(slot) == QLD_DOM_UNMAPPED){
int flash_size = get_flash_size();
if (flash_size == 0){
*status = QLDS_FAILED; // Can't find the flash. most likely a h/w problem.
return false;
}
*slot_start = 0;
*slot_len = flash_size;
return true;
}
const struct flashdir *fd = get_flashdir();
if (fd == 0)
return false;
uint32_t slot_num = QLD_SLOT_NUM(slot);
switch(QLD_SLOT_DOM(slot)){
case QLD_DOM_FPGA:
if (slot_num >= fd->fpga_nslots){
*status = QLDS_INVALID_ARG;
return false;
}
*slot_start = fd->slot[slot_num + fd->fpga_slot0].start << spi_flash_log2_sector_size();
*slot_len = fd->slot[slot_num + fd->fpga_slot0].len << spi_flash_log2_sector_size();
return true;
case QLD_DOM_FW:
if (slot_num >= fd->fw_nslots){
*status = QLDS_INVALID_ARG;
return false;
}
*slot_start = fd->slot[slot_num + fd->fw_slot0].start << spi_flash_log2_sector_size();
*slot_len = fd->slot[slot_num + fd->fw_slot0].len << spi_flash_log2_sector_size();
return true;
default:
*status = QLDS_INVALID_ARG;
return false;
}
}
static bool
check_flashdir(void)
{
return get_flashdir() != 0;
}
void
loader_parser(const unsigned char *input, size_t ilen,
unsigned char *output, size_t max_olen, size_t *actual_olen)
{
//assert (max_olen >= 8);
if (!(max_olen >= 8))
abort();
*actual_olen = 0;
uint32_t status = QLDS_BAD_PKT;
uint32_t cmd = get32(input);
uint32_t nonce = get32(input+4);
uint32_t slot = 0;
uint32_t addr = 0;
uint32_t len = 0;
if (ilen < 8){
nonce = -1;
goto done;
}
uint32_t slot_start; // offset in flash
uint32_t slot_len; // length in bytes
if (ilen >= 5 * sizeof(uint32_t)){
slot = get32(input+8);
addr = get32(input+12);
len = get32(input+16);
}
switch (cmd){
case QLD_FLASH_ERASE_START:
//
if (ilen != 5 * sizeof(uint32_t))
goto done;
if (!check_flashdir()){
status = QLDS_BAD_FLASHDIR;
goto done;
}
if (!map_slot(slot, &slot_start, &slot_len, &status))
goto done;
if (QLD_SLOT_DOM(slot) != QLD_DOM_UNMAPPED){
addr = slot_start;
len = slot_len;
}
//printf("flash_erase: addr = 0x%x, len=0x%x\n", addr, len);
if (0 && erased_p(addr, len)){ // already erased?
async_state.first = async_state.last = async_state.current = 0;
goto ok;
}
if (erase_flash(addr, len))
goto ok;
status = QLDS_FAILED;
goto done;
case QLD_FLASH_ERASE_POLL:
//
if (ilen != 2 * sizeof(uint32_t))
goto done;
if (spi_flash_async_erase_poll(&async_state))
goto ok;
status = QLDS_BUSY;
goto done;
case QLD_FLASH_WRITE:
//
if (ilen < 5 * sizeof(uint32_t))
goto done;
if (ilen != 5 * sizeof(uint32_t) + len)
goto done;
if (!check_flashdir()){
status = QLDS_BAD_FLASHDIR;
goto done;
}
if (!map_slot(slot, &slot_start, &slot_len, &status))
goto done;
addr += slot_start;
len = min(len, slot_len);
if (spi_flash_program(addr, len, &input[5*sizeof(uint32_t)]))
goto ok;
status = QLDS_FAILED;
goto done;
case QLD_FLASH_READ:
case QLD_MEM_READ:
case QLD_MEM_WRITE:
case QLD_GOTO:
status = QLDS_NOTIMPLEMENTED;
goto done;
case QLD_PING:
//
if (ilen != 2 * sizeof(uint32_t))
goto done;
goto ok;
#if 0
case QLD_EEPROM_SET_XXX:
//
{
uint32_t arg = get32(input+2*sizeof(uint32_t));
uint32_t idlen = get32(input+3*sizeof(uint32_t));
uint8_t *idstr = (uint8_t*)input+4*sizeof(uint32_t);
uint8_t *data_p = idstr+idlen;
//handle the ethernet cases
if (strncmp((char*)idstr, "ip", idlen) == 0){
struct ip_addr addr = {get32(data_p)};
ethernet_set_ip_addr(arg, addr);
}
else if (strncmp((char*)idstr, "mac", idlen) == 0){
eth_mac_addr_t addr;
memcpy(&addr, data_p, sizeof(addr));
ethernet_set_mac_addr(arg, &addr);
}
//handle the main board eeprom
else if (strncmp((char*)idstr, "qrrev", idlen) == 0){
qr_set_revision(get32(data_p));
}
else if (strncmp((char*)idstr, "qrser", idlen) == 0){
qr_set_serial(get32(data_p));
}
else if (strncmp((char*)idstr, "qrmod", idlen) == 0){
qr_set_model(get32(data_p));
}
//handle the caldiv eeprom
else if (strncmp((char*)idstr, "cdrev", idlen) == 0){
if (_caldiv_set_rev) _caldiv_set_rev(get32(data_p));
}
else if (strncmp((char*)idstr, "cdser", idlen) == 0){
if (_caldiv_set_ser) _caldiv_set_ser(get32(data_p));
}
else if (strncmp((char*)idstr, "cdmod", idlen) == 0){
if (_caldiv_set_ser) _caldiv_set_mod(get32(data_p));
}
else {
goto done;
}
}
goto ok;
#endif
default:
status = QLDS_UNKNOWN_CMD;
goto done;
}
ok:
status = QLDS_OK;
done:
put32(output, nonce);
put32(output+4, status);
*actual_olen = 2*sizeof(uint32_t);
}