/*
 * 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 <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "FreeRTOS.h"

#include "common/platform.h"

#include "mgos_core_dump.h"
#include "mgos_hal.h"

#ifndef MGOS_ENABLE_CORE_DUMP
#define MGOS_ENABLE_CORE_DUMP 1
#endif

struct arm_exc_frame {
  uint32_t r0;
  uint32_t r1;
  uint32_t r2;
  uint32_t r3;
  uint32_t r12;
  uint32_t lr;
  uint32_t pc;
  uint32_t xpsr;
#ifdef ARM_HAVE_FPU
  uint32_t s[16];
  uint32_t fpscr;
  uint32_t reserved;
#endif
} __attribute__((packed));

struct arm_gdb_reg_file {
  uint32_t r[13];
  uint32_t sp;
  uint32_t lr;
  uint32_t pc;
  uint32_t cpsr;
  uint64_t d[16];
  uint32_t fpscr;
} __attribute__((packed));

#if ARM_HAVE_FPU && !defined(MGOS_BOOT_BUILD)
static void save_s16_s31(uint32_t *dst) {
  __asm volatile(
      "\
      vmov r1, s16\n str  r1, [%0, 0]\n\
      vmov r1, s17\n str  r1, [%0, 4]\n\
      vmov r1, s18\n str  r1, [%0, 8]\n\
      vmov r1, s19\n str  r1, [%0, 12]\n\
      vmov r1, s20\n str  r1, [%0, 16]\n\
      vmov r1, s21\n str  r1, [%0, 20]\n\
      vmov r1, s22\n str  r1, [%0, 24]\n\
      vmov r1, s23\n str  r1, [%0, 28]\n\
      vmov r1, s24\n str  r1, [%0, 32]\n\
      vmov r1, s25\n str  r1, [%0, 36]\n\
      vmov r1, s26\n str  r1, [%0, 40]\n\
      vmov r1, s27\n str  r1, [%0, 44]\n\
      vmov r1, s28\n str  r1, [%0, 48]\n\
      vmov r1, s29\n str  r1, [%0, 52]\n\
      vmov r1, s30\n str  r1, [%0, 56]\n\
      vmov r1, s31\n str  r1, [%0, 60]\n\
      "
      :
      : "r"(dst)
      : "r1");
}

static void print_fpu_regs(const uint32_t *regs, int off, int n) {
  for (int i = 0, j = off; i < n; i++, j++) {
    if (j % 4 == 0) mgos_cd_putc('\n');
    mgos_cd_printf("  S%d: %s0x%08lx", j, (j < 10 ? " " : ""), regs[i]);
  }
}
#endif

void arm_exc_handler_bottom(uint8_t isr_no, struct arm_exc_frame *ef,
                            struct arm_gdb_reg_file *rf) {
  char buf[8];
  const char *name;
  (void) ef;
  portDISABLE_INTERRUPTS();
  switch (isr_no) {
    case 0:
      name = "ThreadMode";
      break;

    case 1:
    case 7:
    case 8:
    case 9:
    case 10:
    case 13:
      name = "Reserved";
      break;

    case 2:
      name = "NMI";
      break;
    case 3:
      name = "HardFault";
      break;
    case 4:
      name = "MemManage";
      break;
    case 5:
      name = "BusFault";
      break;
    case 6:
      name = "UsageFault";
      break;
    case 11:
      name = "SVCall";
      break;
    case 12:
      name = "ReservedDebug";
      break;
    case 14:
      name = "PendSV";
      break;
    case 15:
      name = "SysTick";
      break;
    default: {
#ifndef MGOS_BOOT_BUILD
      sprintf(buf, "IRQ%u", isr_no - 16);
#endif
      name = buf;
    }
  }
  mgos_cd_printf("\n\n--- Exception %u (%s) ---\n", isr_no, name);
  if (rf != NULL) {
    mgos_cd_printf(
        "  R%d:  0x%08lx  R%d:  0x%08lx  R%d:  0x%08lx  R%d:  0x%08lx\n", 0,
        rf->r[0], 1, rf->r[1], 2, rf->r[2], 3, rf->r[3]);
    mgos_cd_printf(
        "  R%d:  0x%08lx  R%d:  0x%08lx  R%d:  0x%08lx  R%d:  0x%08lx\n", 4,
        rf->r[4], 5, rf->r[5], 6, rf->r[6], 7, rf->r[7]);
    mgos_cd_printf("  R8:  0x%08lx  R9:  0x%08lx  R10: 0x%08lx  R11: 0x%08lx\n",
                   rf->r[8], rf->r[9], rf->r[10], rf->r[11]);
    mgos_cd_printf("  R12: 0x%08lx  SP:  0x%08lx  LR:  0x%08lx  PC:  0x%08lx\n",
                   rf->r[12], rf->sp, rf->lr, rf->pc);
    mgos_cd_printf("  PSR: 0x%08lx\n", rf->cpsr);
  }
  memset(rf->d, 0, sizeof(rf->d));
#if ARM_HAVE_FPU && !defined(MGOS_BOOT_BUILD)
  rf->fpscr = ef->fpscr;
  memcpy((uint8_t *) rf->d, ef->s, sizeof(ef->s));
  print_fpu_regs((uint32_t *) rf->d, 0, ARRAY_SIZE(ef->s));
  save_s16_s31(ef->s);
  memcpy(((uint8_t *) rf->d) + sizeof(ef->s), ef->s, sizeof(ef->s));
  print_fpu_regs((uint32_t *) (((uint8_t *) rf->d) + sizeof(ef->s)), 16,
                 ARRAY_SIZE(ef->s));
  mgos_cd_putc('\n');
  mgos_cd_printf("FPSCR: 0x%08lx\n", rf->fpscr);
#else
  rf->fpscr = 0;
#endif
#if MGOS_ENABLE_CORE_DUMP
  mgos_cd_emit_header();
  mgos_cd_emit_section(MGOS_CORE_DUMP_SECTION_REGS, rf, sizeof(*rf));
  mgos_cd_emit_section("SRAM", (void *) SRAM_BASE_ADDR, SRAM_SIZE);
  mgos_cd_emit_footer();
#endif
#ifdef MGOS_HALT_ON_EXCEPTION
  mgos_cd_printf("Halting\n");
  while (1) {
    mgos_wdt_feed();
  }
#else
  mgos_cd_printf("Rebooting\n");
  mgos_dev_system_restart();
#endif
}
