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