/*
 * Copyright (c) 2014-2018 Cesanta Software Limited
 * All rights reserved
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "stub_flasher.h"

#include <stdint.h>
#include <string.h>

#include "rom_functions.h"

#if defined(ESP8266)
#include "eagle_soc.h"
#include "ets_sys.h"
#include "miniz.h"
#elif defined(ESP32)
#include "esp32/rom/efuse.h"
#include "esp32/rom/miniz.h"
#include "esp32/rom/spi_flash.h"
#include "soc/uart_reg.h"

#include "led.h"
#elif defined(ESP32C3)
#include "esp32c3/rom/efuse.h"
#include "esp32c3/rom/miniz.h"
#include "esp32c3/rom/spi_flash.h"
#include "soc/spi_mem_reg.h"
#include "soc/uart_reg.h"
#endif

#include "slip.h"
#include "uart.h"

/* Param: baud rate. */
uint32_t params[2] __attribute__((section(".params")));

#define FLASH_BLOCK_SIZE 65536
#define FLASH_SECTOR_SIZE 4096
#define FLASH_PAGE_SIZE 256

/* These consts should be in sync with flasher_client.go */
#define NUM_BUFS 4
#define BUF_SIZE 4096
#define FLASH_WRITE_SIZE BUF_SIZE

#define UART_RX_INTS (UART_RXFIFO_FULL_INT_ENA | UART_RXFIFO_TOUT_INT_ENA)

extern uint32_t _bss_start, _bss_end;

#ifdef ESP8266
#define REG_SPI_BASE(i) (0x60000200 - i * 0x100)

#define SPI_CMD_REG(i) (REG_SPI_BASE(i) + 0x0)
#define SPI_FLASH_WREN (BIT(30))
#define SPI_FLASH_RDID (BIT(28))
#define SPI_FLASH_SE (BIT(24))
#define SPI_FLASH_BE (BIT(23))

#define SPI_ADDR_REG(i) (REG_SPI_BASE(i) + 0x4)

#define SPI_USER_REG(i) (REG_SPI_BASE(i) + 0x1C)

#define SPI_W0_REG(i) (REG_SPI_BASE(i) + 0x40)
#elif defined(ESP32C3)
#define SPI_CMD_REG(i) SPI_MEM_CMD_REG(i)
#define SPI_FLASH_WREN SPI_MEM_FLASH_WREN
#define SPI_FLASH_RDID SPI_MEM_FLASH_RDID
#define SPI_FLASH_SE SPI_MEM_FLASH_SE
#define SPI_FLASH_BE SPI_MEM_FLASH_BE

#define SPI_ADDR_REG(i) SPI_MEM_ADDR_REG(i)

#define SPI_USER_REG(i) SPI_MEM_USER_REG(i)

#define SPI_W0_REG(i) SPI_MEM_W0_REG(i)
#endif

enum read_state {
  READ_WAIT_START = 0,
  READ_FLAGS,
  READ_DATA,
  READ_UNESCAPE,
  READ_ERROR,
};

struct data_buf {
  uint32_t len;
  uint8_t data[BUF_SIZE];
  uint8_t flags;
};

#define FLAG_COMPRESSED 1

struct uart_buf {
  enum read_state state;
  struct data_buf bufs[NUM_BUFS];
  uint32_t bri, bwi;
  uint32_t ps;
};

static inline uint32_t ccount(void) {
  uint32_t r;
#if defined(ESP32C3)
  __asm volatile ("csrr %0, 0x7e2" : "=r"(r));
#else
  __asm volatile("rsr.ccount %0" : "=a"(r));
#endif
  return r;
}

struct write_progress {
  uint32_t num_written;
  uint32_t buf_level;
  uint8_t digest[16];
};

struct write_result {
  uint32_t wait_time;
  uint32_t decomp_time;
  uint32_t write_time;
  uint32_t erase_time;
  uint32_t total_time;
  uint8_t digest[16];
};

static struct uart_buf ub;
static uint32_t inflate_buf[TINFL_LZ_DICT_SIZE / sizeof(uint32_t)];

static void next_write_buf(void) {
  ub.bwi++;
  if (ub.bwi == NUM_BUFS) ub.bwi = 0;
  ub.bufs[ub.bwi].len = 0;
  ub.bufs[ub.bwi].flags = 0;
}

static void add_byte(uint8_t byte) {
  struct data_buf *buf = &ub.bufs[ub.bwi];
  if (buf->len < BUF_SIZE) {
    buf->data[buf->len++] = byte;
    ub.ps++;
  }
}

void uart_isr(void *arg) {
  uint32_t int_st = READ_PERI_REG(UART_INT_ST_REG(0));
  uint8_t fifo_len, i;
  while ((fifo_len = READ_PERI_REG(UART_STATUS_REG(0))) > 0) {
    for (i = 0; i < fifo_len; i++) {
      uint8_t byte = READ_PERI_REG(UART_FIFO_REG(0));
      switch (ub.state) {
        case READ_WAIT_START: {
          if (byte == 0xc0) {
            ub.state = READ_FLAGS;
            ub.ps = 0;
          }
          break;
        }
        case READ_FLAGS:
        case READ_DATA: {
          struct data_buf *buf = &ub.bufs[ub.bwi];
          if (byte == 0xdb) {
            ub.state = READ_UNESCAPE;
          } else if (byte == 0xc0) {
            next_write_buf();
            if (ub.ps == 0) {
              /* Empty packet, sender aborted. */
              ub.state = READ_ERROR;
              SET_PERI_REG_MASK(UART_INT_ENA_REG(0), 0);
              goto out;
            } else {
              ub.state = READ_WAIT_START;
            }
          } else {
            if (ub.state == READ_FLAGS) {
              buf->flags = byte;
              ub.state = READ_DATA;
            } else {
              add_byte(byte);
            }
          }
          break;
        }
        case READ_UNESCAPE: {
          if (byte == 0xdc) {
            byte = 0xc0;
          } else if (byte == 0xdd) {
            byte = 0xdb;
          } else {
            ub.state = READ_ERROR;
            SET_PERI_REG_MASK(UART_INT_ENA_REG(0), 0);
            goto out;
          }
          add_byte(byte);
          ub.state = READ_DATA;
          break;
        }
        case READ_ERROR: {
          goto out;
        }
      }
    }
  }
out:
  WRITE_PERI_REG(UART_INT_CLR_REG(0), int_st);
  (void) arg;
}

size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len,
                                   const void *pSrc_buf, size_t src_buf_len,
                                   int flags);
#define TINFL_DECOMPRESS_MEM_TO_MEM_FAILED ((size_t)(-1))
#define TINFL_FLAG_PARSE_ZLIB_HEADER 1

#if defined(ESP8266)
int esp_rom_spiflash_erase_start(uint32_t addr, uint32_t cmd) {
  SPI_write_enable(flashchip);
  WRITE_PERI_REG(SPI_ADDR_REG(0), addr);
  WRITE_PERI_REG(SPI_CMD_REG(0), cmd);
  while (READ_PERI_REG(SPI_CMD_REG(0)) & cmd) {
  }
  return 0;
}
#elif defined(ESP32)
extern esp_rom_spiflash_chip_t g_rom_spiflash_chip;

esp_rom_spiflash_result_t esp_rom_spiflash_erase_start(uint32_t addr,
                                                       uint32_t cmd) {
  esp_rom_spiflash_chip_t *spi = &g_rom_spiflash_chip;
  esp_rom_spiflash_wait_idle(spi);

  REG_CLR_BIT(PERIPHS_SPI_FLASH_USRREG, SPI_USR_DUMMY);
  REG_SET_FIELD(PERIPHS_SPI_FLASH_USRREG1, SPI_USR_ADDR_BITLEN,
                ESP_ROM_SPIFLASH_W_SIO_ADDR_BITSLEN);

  WRITE_PERI_REG(PERIPHS_SPI_FLASH_CMD, SPI_FLASH_WREN);
  while (READ_PERI_REG(PERIPHS_SPI_FLASH_CMD) != 0) {
  }

  WRITE_PERI_REG(PERIPHS_SPI_FLASH_ADDR, addr);
  WRITE_PERI_REG(PERIPHS_SPI_FLASH_CMD, cmd);

  return ESP_ROM_SPIFLASH_RESULT_OK;
}
#elif defined(ESP32C3)
esp_rom_spiflash_result_t esp_rom_spiflash_erase_start(uint32_t addr,
                                                       uint32_t cmd) {
  uint32_t length = 0;
  
  if (cmd == SPI_FLASH_BE) {
    length = FLASH_BLOCK_SIZE;
  } else if (cmd == SPI_FLASH_SE) {
    length = FLASH_SECTOR_SIZE;
  }
  
  return esp_rom_spiflash_erase_area(addr, length);
}
#endif

int do_flash_write(uint32_t addr, uint32_t len, uint32_t erase) {
  int ret = 0;
  uint32_t num_erased = 0;
  struct MD5Context ctx;
  MD5Init(&ctx);

  if (addr % FLASH_SECTOR_SIZE != 0) return 0x32;
  if (len % FLASH_SECTOR_SIZE != 0) return 0x33;
  if (esp_rom_spiflash_unlock() != 0) return 0x34;

  memset(&ub, 0, sizeof(ub));
  memset(&inflate_buf, 0, sizeof(inflate_buf));
  ets_isr_attach(ETS_UART0_INUM, uart_isr, &ub);
  uint32_t saved_conf1 = READ_PERI_REG(UART_CONF1_REG(0));
  /* Reduce frequency of UART interrupts */
  WRITE_PERI_REG(UART_CONF1_REG(0), UART_RX_TOUT_EN |
                                        (20 << UART_RX_TOUT_THRHD_S) |
                                        (100 << UART_RXFIFO_FULL_THRHD_S));
  SET_PERI_REG_MASK(UART_INT_ENA_REG(0), UART_RX_INTS);
  ets_isr_unmask(1 << ETS_UART0_INUM);

  struct write_result wr;
  memset(&wr, 0, sizeof(wr));

  struct write_progress wp = {.num_written = 0, .buf_level = 0};
  wp.buf_level = (uint32_t) &addr;
  SLIP_send(&wp, sizeof(wp));
  wr.total_time = ccount();
  while (wp.num_written < len) {
    /* Prepare the space ahead. */
    uint32_t start_count = ccount();
    while (erase && num_erased < wp.num_written + FLASH_WRITE_SIZE) {
      const uint32_t num_left = (len - num_erased);
      if (num_left >= FLASH_BLOCK_SIZE && addr % FLASH_BLOCK_SIZE == 0) {
        if (esp_rom_spiflash_erase_start(addr, SPI_FLASH_BE) != 0) {
          ret = 0x35;
          goto out;
        }
        num_erased += FLASH_BLOCK_SIZE;
      } else {
        /* len % FLASH_SECTOR_SIZE == 0 is enforced, no further checks needed */
        if (esp_rom_spiflash_erase_start(addr, SPI_FLASH_SE) != 0) {
          ret = 0x36;
          goto out;
        }
        num_erased += FLASH_SECTOR_SIZE;
      }
    }
    wr.erase_time += ccount() - start_count;
    start_count = ccount();
    /* Wait for data to arrive. */
    wp.buf_level = 0;
    for (int i = 0; i < NUM_BUFS; i++) wp.buf_level += ub.bufs[i].len;
    volatile uint32_t *bwi = &ub.bwi;
    while (*bwi == ub.bri && ub.state != READ_ERROR) {
    }
    struct data_buf *buf = &ub.bufs[ub.bri];
    if (ub.state == READ_ERROR) {
      ret = 0x37;
      goto out;
    }
    wr.wait_time += ccount() - start_count;
    start_count = ccount();
    uint32_t *data = (uint32_t *) buf->data;
    uint32_t write_len = buf->len;
    if (buf->flags & FLAG_COMPRESSED) {
      data = inflate_buf;
      write_len = tinfl_decompress_mem_to_mem(
          &inflate_buf[0], sizeof(inflate_buf), buf->data, write_len,
          TINFL_FLAG_PARSE_ZLIB_HEADER);
      if (write_len == TINFL_DECOMPRESS_MEM_TO_MEM_FAILED) {
        ret = 0x40;
        goto out;
      }
    }
    wr.decomp_time += ccount() - start_count;
    MD5Update(&ctx, (uint8_t *) data, write_len);
    start_count = ccount();
    wr.erase_time += ccount() - start_count;
    start_count = ccount();
    if (esp_rom_spiflash_write(addr, data, write_len) != 0) {
      ret = 0x38;
      goto out;
    }
    wr.write_time += ccount() - start_count;
    buf->len = 0;
    ub.bri++;
    if (ub.bri == NUM_BUFS) ub.bri = 0;
    addr += write_len;
    wp.num_written += write_len;
    struct MD5Context ctx2;
    memcpy(&ctx2, &ctx, sizeof(ctx));
    MD5Final(wp.digest, &ctx2);
    SLIP_send(&wp, sizeof(wp));
  }

  MD5Final(wr.digest, &ctx);

  wr.total_time = ccount() - wr.total_time;
  SLIP_send(&wr, sizeof(wr));

out:
  WRITE_PERI_REG(UART_CONF1_REG(0), saved_conf1);
  ets_isr_mask(1 << ETS_UART0_INUM);
  return ret;
}

int do_flash_read(uint32_t addr, uint32_t len, uint32_t read_size) {
  uint32_t buf[FLASH_SECTOR_SIZE / 4];
  uint8_t digest[16];
  struct MD5Context ctx;
  uint32_t num_sent = 0;
  if (read_size > sizeof(buf)) return 0x52;
  MD5Init(&ctx);
  while (num_sent < len) {
    uint32_t n = len - num_sent;
    if (n > read_size) n = read_size;
    if (esp_rom_spiflash_read(addr + num_sent, buf, n) != 0) {
      return 0x53;
    }
    send_packet((uint8_t *) buf, n);
    uint32_t num_acked = 0;
    if (SLIP_recv(&num_acked, sizeof(num_acked)) != 4) return 0x54;
    if (num_acked > num_sent) {
      MD5Update(&ctx, (uint8_t *) buf, num_acked - num_sent);
      num_sent = num_acked;
    }
  }
  MD5Final(digest, &ctx);
  send_packet(digest, sizeof(digest));
  return 0;
}

int do_flash_digest(uint32_t addr, uint32_t len, uint32_t digest_block_size) {
  uint8_t buf[FLASH_SECTOR_SIZE];
  uint8_t digest[16];
  uint32_t read_block_size =
      digest_block_size ? digest_block_size : sizeof(buf);
  struct MD5Context ctx;
  if (digest_block_size > sizeof(buf)) return 0x62;
  MD5Init(&ctx);
  while (len > 0) {
    uint32_t n = len;
    struct MD5Context block_ctx;
    MD5Init(&block_ctx);
    if (n > read_block_size) n = read_block_size;
    if (esp_rom_spiflash_read(addr, (uint32_t *) buf, n) != 0) return 0x63;
    MD5Update(&ctx, buf, n);
    if (digest_block_size > 0) {
      MD5Update(&block_ctx, buf, n);
      MD5Final(digest, &block_ctx);
      send_packet(digest, sizeof(digest));
    }
    addr += n;
    len -= n;
  }
  MD5Final(digest, &ctx);
  send_packet(digest, sizeof(digest));
  return 0;
}

int do_flash_read_chip_id(void) {
  uint32_t chip_id = 0;
  WRITE_PERI_REG(SPI_CMD_REG(0), SPI_FLASH_RDID);
  while (READ_PERI_REG(SPI_CMD_REG(0)) & SPI_FLASH_RDID) {
  }
  chip_id = READ_PERI_REG(SPI_W0_REG(0)) & 0xFFFFFF;
  send_packet((uint8_t *) &chip_id, sizeof(chip_id));
  return 0;
}

uint8_t cmd_loop(void) {
  uint8_t cmd = 0x55;
  do {
    /* Reset FIFO to re-sync */
    SET_PERI_REG_MASK(UART_CONF0_REG(0), UART_RXFIFO_RST);
    CLEAR_PERI_REG_MASK(UART_CONF0_REG(0), UART_RXFIFO_RST);
    uint32_t args[4];
    uint32_t len = SLIP_recv(&cmd, 1);
    if (len != 1) {
      continue;
    }
    uint8_t resp = 0xff;
    switch (cmd) {
      case CMD_FLASH_WRITE: {
        len = SLIP_recv(args, sizeof(args));
        if (len == 12) {
          resp = do_flash_write(args[0] /* addr */, args[1] /* len */,
                                args[2] /* erase */);
        } else {
          resp = 0x41;
        }
        break;
      }
      case CMD_FLASH_READ: {
        len = SLIP_recv(args, sizeof(args));
        if (len == 12) {
          resp = do_flash_read(args[0] /* addr */, args[1], /* len */
                               args[2] /* read_size */);
        } else {
          resp = 0x51;
        }
        break;
      }
      case CMD_FLASH_DIGEST: {
        len = SLIP_recv(args, sizeof(args));
        if (len == 12) {
          resp = do_flash_digest(args[0] /* addr */, args[1], /* len */
                                 args[2] /* digest_block_size */);
        } else {
          resp = 0x61;
        }
        break;
      }
      case CMD_FLASH_READ_CHIP_ID: {
        resp = do_flash_read_chip_id();
        break;
      }
      case CMD_FLASH_ERASE_CHIP: {
        resp = esp_rom_spiflash_erase_chip();
        break;
      }
      case CMD_BOOT_FW:
      case CMD_REBOOT: {
        resp = 0;
        SLIP_send(&resp, 1);
        return cmd;
      }
      case CMD_ECHO: {
        len = SLIP_recv(args, sizeof(args));
        SLIP_send(args, len);
        resp = 0;
        break;
      }
      case CMD_READ_REG: {
        len = SLIP_recv(args, sizeof(args));
        if (len == 4) {
          uint32_t value = READ_PERI_REG((uint32_t *) args[0]);
          SLIP_send(&value, sizeof(value));
          resp = 0;
        } else {
          resp = 0x91;
        }
        break;
      }
      case CMD_WRITE_REG: {
        len = SLIP_recv(args, sizeof(args));
        if (len == 8) {
          WRITE_PERI_REG((uint32_t *) args[0], args[1]);
          resp = 0;
        } else {
          resp = 0xa1;
        }
        break;
      }
    }
    SLIP_send(&resp, 1);
  } while (cmd != CMD_BOOT_FW && cmd != CMD_REBOOT);
  return cmd;
}

void stub_main1(void) {
  uint32_t old_baud_rate = params[0], new_baud_rate = params[1];
  uint8_t last_cmd;

  /* This points at us right now, reset for next boot. */
  ets_set_user_start(0);

/* Selects SPI functions for flash pins. */
#if defined(ESP8266)
  SelectSpiFunction();
  spi_flash_attach();
  SET_PERI_REG_MASK(0x3FF00014, 1); /* Switch to 160 MHz */
#elif defined(ESP32)
  esp_rom_spiflash_attach(ets_efuse_get_spiconfig(), 0 /* legacy */);
#endif

  esp_rom_spiflash_config_param(
      0 /* deviceId */, 16 * 1024 * 1024 /* chip_size */, FLASH_BLOCK_SIZE,
      FLASH_SECTOR_SIZE, FLASH_PAGE_SIZE, 0xffff /* status_mask */);

  uint32_t old_div = 0;
  if (new_baud_rate > 0) {
    ets_delay_us(10000);
    old_div = set_baud_rate(0, old_baud_rate, new_baud_rate);
  }

  /* Give host time to get ready too. */
  ets_delay_us(50000);

#ifdef BAUD_TEST
  while (1) {
    WRITE_PERI_REG(UART_FIFO_REG(0), 0x55);
  }
#else
  SLIP_send(&old_div, 4);
#endif

  last_cmd = cmd_loop();

  ets_delay_us(10000);

  if (last_cmd == CMD_BOOT_FW) {
#if defined(ESP8266)
    /*
     * Find the return address in our own stack and change it.
     * "flash_finish" it gets to the same point, except it doesn't need to
     * patch up its RA: it returns from UartDwnLdProc, then from f_400011ac,
     * then jumps to 0x4000108a, then checks strapping bits again (which will
     * not have changed), and then proceeds to 0x400010a8.
     */
    volatile uint32_t *sp = &old_baud_rate;
    while (*sp != (uint32_t) 0x40001100) sp++;
    *sp = 0x400010a8;
    /*
     * The following dummy asm fragment acts as a barrier, to make sure function
     * epilogue, including return address loading, is added after our stack
     * patching.
     */
    __asm volatile("nop.n");
    return; /* To 0x400010a8 */
#elif defined(ESP32)
/* TODO(rojer) */
#endif
  } else {
    software_reset();
  }
  /* Not reached */
}

/* miniz requires at least 12K of stack */
uint32_t _stack[3072];

void stub_main(void) {
  memset(&_bss_start, 0, (&_bss_end - &_bss_start));
  uint32_t *stack_end = &_stack[3071];
#if defined(ESP32C3)
  __asm volatile("mv sp, %0\n"
                 :                 // output
                 : "r"(stack_end)  // input
                 :                 // scratch
  );
#else
  __asm volatile("mov a1, %0\n"
                 :                 // output
                 : "a"(stack_end)  // input
                 :                 // scratch
  );
#endif
  stub_main1();
}
