/* Recreated Espressif libmain user_interface.o contents.

   Copyright (C) 2015 Espressif Systems. Derived from MIT Licensed SDK libraries.
   BSD Licensed as described in the file LICENSE
*/
#include "open_esplibs.h"
#if OPEN_LIBMAIN_USER_INTERFACE
// The contents of this file are only built if OPEN_LIBMAIN_USER_INTERFACE is set to true

#include "FreeRTOS.h"
#include "task.h"
#include "string.h"

#include "lwip/dhcp.h"

#include "esp/types.h"
#include "esp/rom.h"
#include "esp/dport_regs.h"
#include "esp/rtcmem_regs.h"
#include "esp/iomux_regs.h"
#include "esp/sar_regs.h"
#include "esp/wdev_regs.h"

#include "etstimer.h"
#include "espressif/sdk_private.h"
#include "espressif/esp_system.h"
#include "espressif/esp_wifi.h"
#include "espressif/esp_sta.h"
#include "espressif/esp_softap.h"
#include "espressif/esp_misc.h"
#include "espressif/osapi.h"
#include "espressif/user_interface.h"

#include "sdk_internal.h"
#include "esplibs/libmain.h"
#include "esplibs/libpp.h"
#include "esplibs/libphy.h"
#include "esplibs/libnet80211.h"

// Structure for the data contained in the last sector of Flash which contains
// meta-info about the saved wifi param sectors.
struct param_dir_st {
    uint8_t current_sector;   // 0x00
    uint32_t cksum_magic;     // 0x04
    uint32_t save_count;      // 0x08
    uint32_t cksum_len[2];    // 0x0c
    uint32_t cksum_value[2];  // 0x14
};
_Static_assert(sizeof(struct param_dir_st) == 28, "param_dir_st is the wrong size");

enum sdk_dhcp_status sdk_dhcpc_flag = DHCP_STARTED;
bool sdk_cpu_overclock;
struct sdk_rst_info sdk_rst_if;
sdk_wifi_promiscuous_cb_t sdk_promiscuous_cb;

static uint8_t _system_upgrade_flag; // Ldata009

// Prototypes for static functions
static bool _check_boot_version(void);
static void _deep_sleep_phase2(void *timer_arg);
static struct netif *_get_netif(uint32_t mode);

// Linker-created values used by sdk_system_print_meminfo
extern uint32_t _data_start, _data_end;
extern uint32_t _rodata_start, _rodata_end;
extern uint32_t _bss_start, _bss_end;
extern uint32_t _heap_start;

#define _rom_reset_vector ((void (*)(void))0x40000080)

void IRAM sdk_system_restart_in_nmi(void) {
    uint32_t buf[8];

    sdk_system_rtc_mem_read(0, buf, 32);
    if (buf[0] != 2) {
        memset(buf, 0, 32);
        buf[0] = 3;
        sdk_system_rtc_mem_write(0, buf, 32);
    }
    if (!sdk_NMIIrqIsOn) {
        portENTER_CRITICAL();
        do {
            DPORT.DPORT0 = SET_FIELD(DPORT.DPORT0, DPORT_DPORT0_FIELD0, 0);
        } while (DPORT.DPORT0 & 1);
    }
    ESPSAR.UNKNOWN_48 |= 3;
    DPORT.CLOCKGATE_WATCHDOG |= DPORT_CLOCKGATE_WATCHDOG_UNKNOWN_8;
    ESPSAR.UNKNOWN_48 &= ~3;
    DPORT.CLOCKGATE_WATCHDOG &= ~DPORT_CLOCKGATE_WATCHDOG_UNKNOWN_8;
    Cache_Read_Disable();
    DPORT.SPI_CACHE_RAM &= ~(DPORT_SPI_CACHE_RAM_BANK0 | DPORT_SPI_CACHE_RAM_BANK1);
    // This calls directly to 0x40000080, the "reset" exception vector address.
    _rom_reset_vector();
}

bool IRAM sdk_system_rtc_mem_write(uint32_t des_addr, void *src_addr, uint16_t save_size) {
    uint32_t volatile *src_buf = (uint32_t *)src_addr;

    if (des_addr > 191) {
        return false;
    }
    if ((intptr_t)src_addr & 3) {
        return false;
    }
    if ((768 - (des_addr * 4)) < save_size) {
        return false;
    }
    if ((save_size & 3) != 0) {
        save_size = (save_size & ~3) + 4;
    }
    for (uint8_t i = 0; i < (save_size >> 2); i++) {
        RTCMEM_SYSTEM[i] = src_buf[i];
    }
    return true;
}

bool IRAM sdk_system_rtc_mem_read(uint32_t src_addr, void *des_addr, uint16_t save_size) {
    uint32_t *dest_buf = (uint32_t *)des_addr;

    if (src_addr > 191) {
        return false;
    }
    if ((intptr_t)des_addr & 3) {
        return false;
    }
    if ((768 - (src_addr * 4)) < save_size) {
        return false;
    }
    if ((save_size & 3) != 0) {
        save_size = (save_size & ~3) + 4;
    }
    for (uint8_t i = 0; i < (save_size >> 2); i++) {
        dest_buf[i] = RTCMEM_SYSTEM[i];
    }
    return true;
}

void sdk_system_pp_recycle_rx_pkt(void *eb) {
        sdk_ppRecycleRxPkt(eb);
}

uint16_t sdk_system_adc_read(void) {
        return sdk_test_tout(false);
}

void sdk_system_restart(void) {
    if (sdk_wifi_get_opmode() != 2) {
        sdk_wifi_station_stop();
    }
    if (sdk_wifi_get_opmode() != 1) {
        sdk_wifi_softap_stop();
    }
    vTaskDelay(6);
    IOMUX_GPIO12 |= IOMUX_PIN_PULLUP;
    sdk_wDev_MacTim1SetFunc(sdk_system_restart_in_nmi);
    sdk_wDev_MacTim1Arm(3);
}

void sdk_system_restore(void) {
    struct sdk_g_ic_saved_st *buf;

    buf = malloc(sizeof(struct sdk_g_ic_saved_st));
    memset(buf, 0xff, sizeof(struct sdk_g_ic_saved_st));
    memcpy(buf, &sdk_g_ic.s, 8);
    sdk_wifi_param_save_protect(buf);
    free(buf);
}

uint8_t sdk_system_get_boot_version(void) {
    return sdk_g_ic.s.boot_info & 0x1f;
}

static bool _check_boot_version(void) {
    uint8_t ver = sdk_system_get_boot_version();
    if (ver < 3 || ver == 31) {
        printf("failed: need boot >= 1.3\n");
        return false;
    }
    return true;
}

int sdk_system_get_test_result(void) {
    if (_check_boot_version()) {
        return (sdk_g_ic.s.boot_info >> 5) & 1;
    } else {
        return -1;
    }
}

uint32_t sdk_system_get_userbin_addr(void) {
    uint8_t buf[8];
    uint16_t unknown_var = 0; //FIXME: read but never written?
    uint32_t addr;
    uint32_t flash_size_code;

    if (!(sdk_g_ic.s.boot_info >> 7)) {
        if (sdk_g_ic.s._unknown1d8 & 0x4) {
            addr = sdk_g_ic.s.user1_addr[0] | (sdk_g_ic.s.user1_addr[1] << 8) | 
(sdk_g_ic.s.user1_addr[2] << 16);
        } else {
            addr = sdk_g_ic.s.user0_addr[0] | (sdk_g_ic.s.user0_addr[1] << 8) | (sdk_g_ic.s.user0_addr[2] << 16);
        }
    } else {
        if (!sdk_system_upgrade_userbin_check()) {
            addr = 0x00001000;
        } else {
            sdk_spi_flash_read(0, (uint32_t *)buf, 8);
            flash_size_code = buf[3] >> 4;
            if (flash_size_code >= 2 && flash_size_code < 5) {
                flash_size_code = 0x81;
            } else if (flash_size_code == 1) {
                flash_size_code = 0x41;
            } else {
                // FIXME: In the original code, this loads from a local stack
                // variable, which is never actually assigned to anywhere.
                // It's unclear what this value is actually supposed to be.
                flash_size_code = unknown_var;
            }
            addr = flash_size_code << 12;
        }
    }
    return addr;
}

uint8_t sdk_system_get_boot_mode(void) {
    int boot_version = sdk_g_ic.s.boot_info & 0x1f;
    if (boot_version < 3 || boot_version == 0x1f) {
        return 1;
    }
    return sdk_g_ic.s.boot_info >> 7;
}

bool sdk_system_restart_enhance(uint8_t bin_type, uint32_t bin_addr) {
    uint32_t current_addr;

    if (!_check_boot_version()) {
        return false;
    }
    if (bin_type == 0) {
        current_addr = sdk_system_get_userbin_addr();
        printf("restart to use user bin @ %x\n", bin_addr);
        sdk_g_ic.s.user1_addr[0] = bin_addr;
        sdk_g_ic.s.user1_addr[1] = bin_addr >> 8;
        sdk_g_ic.s.user1_addr[2] = bin_addr >> 16;
        sdk_g_ic.s.user0_addr[0] = current_addr;
        sdk_g_ic.s.user0_addr[1] = current_addr >> 8;
        sdk_g_ic.s.user0_addr[2] = current_addr >> 16;
        sdk_g_ic.s._unknown1d8 = (sdk_g_ic.s._unknown1d8 & 0xfb) | 0x04;
        sdk_g_ic.s.boot_info &= 0x7f;
        sdk_wifi_param_save_protect(&sdk_g_ic.s);
        sdk_system_restart();
        return true;
    } else {
        if (bin_type != 1) {
            printf("don't supported type.\n");
            return false;
        }
        if (!sdk_system_get_test_result()) {
            printf("test already passed.\n");
            return false;
        }
       
        printf("reboot to use test bin @ %x\n", bin_addr);
        sdk_g_ic.s.user0_addr[0] = bin_addr;
        sdk_g_ic.s.user0_addr[1] = bin_addr >> 8;
        sdk_g_ic.s.user0_addr[2] = bin_addr >> 16;
        sdk_g_ic.s.boot_info &= 0xbf;
        sdk_wifi_param_save_protect(&sdk_g_ic.s);
        sdk_system_restart();
       
        return true;
    }
}

bool sdk_system_upgrade_userbin_set(uint8_t userbin) {
    uint8_t userbin_val, userbin_mask;
    uint8_t boot_ver = sdk_system_get_boot_version();

    if (userbin >= 2) {
        return false;
    } else {
        if (boot_ver == 2 || boot_ver == 0x1f) {
            userbin_val = userbin & 0x0f;
            userbin_mask = 0xf0;
        } else {
            userbin_val = userbin & 0x03;
            userbin_mask = 0xfc;
        }
        sdk_g_ic.s._unknown1d8 = (sdk_g_ic.s._unknown1d8 & userbin_mask) | userbin_val;
        return true;
    }
}

uint8_t sdk_system_upgrade_userbin_check(void) {
    uint8_t boot_ver = sdk_system_get_boot_version();
    if (boot_ver != 0x1f && boot_ver != 2) {
        if ((sdk_g_ic.s._unknown1d8 & 0x03) == 1) {
            if (sdk_g_ic.s._unknown1d8 & 0x4) {
                return 1;
            } else {
                return 0;
            }
        } else {
            if (sdk_g_ic.s._unknown1d8 & 0x4) {
                return 0;
            } else {
                return 1;
            }
        }
    } else {
        if ((sdk_g_ic.s._unknown1d8 & 0x0f) == 1) {
            return 1;
        } else {
            return 0;
        }
    }
}

bool sdk_system_upgrade_flag_set(uint8_t flag) {
    if (flag < 3) {
        _system_upgrade_flag = flag;
        return true;
    }
    return false;
}

uint8_t sdk_system_upgrade_flag_check(void) {
    return _system_upgrade_flag;
}

bool sdk_system_upgrade_reboot(void) {
    uint8_t boot_ver = sdk_system_get_boot_version();
    uint8_t new__unknown1d8;

    if (_system_upgrade_flag != 2) {
        return false;
    }
    printf("reboot to use");
    if (boot_ver != 2 && boot_ver != 0x1f) {
        sdk_g_ic.s.boot_info = (sdk_g_ic.s.boot_info & 0x7f) | 0x80;
        sdk_g_ic.s._unknown1d8 = (sdk_g_ic.s._unknown1d8 & 0xfb) | 0x04;
        if ((sdk_g_ic.s._unknown1d8 & 0x03) == 1) {
            printf("1\n");
            new__unknown1d8 = sdk_g_ic.s._unknown1d8 & 0xfc;
        } else {
            printf("2\n");
            new__unknown1d8 = (sdk_g_ic.s._unknown1d8 & 0xfc) | 0x01;
        }
    } else {
        if ((sdk_g_ic.s._unknown1d8 & 0x0f) == 1) {
            printf("1\n");
            new__unknown1d8 = sdk_g_ic.s._unknown1d8 & 0xf0;
        } else {
            printf("2\n");
            new__unknown1d8 = (sdk_g_ic.s._unknown1d8 & 0xf0) | 0x01;
        }
    }
    sdk_g_ic.s._unknown1d8 = new__unknown1d8;
    sdk_wifi_param_save_protect(&sdk_g_ic.s);
    sdk_system_restart();
    return true;
}

static void _deep_sleep_phase2(void *timer_arg) {
    uint32_t time_in_us = (uint32_t)timer_arg;

    printf("deep sleep %ds\n\n", time_in_us / 1000000);
    while (FIELD2VAL(UART_STATUS_TXFIFO_COUNT, UART(0).STATUS)) {}
    while (FIELD2VAL(UART_STATUS_TXFIFO_COUNT, UART(1).STATUS)) {}
    RTC.CTRL0 = 0;
    RTC.CTRL0 &= 0xffffbfff;
    RTC.CTRL0 |= 0x00000030;
    RTC._unknown44 = 0x00000004;
    RTC._unknownc = 0x00010010;
    RTC._unknown48 = (RTC._unknown48 & 0xffff01ff) | 0x0000fc00;
    RTC._unknown48 = (RTC._unknown48 & 0xfffffe00) | 0x00000080;
    RTC.COUNTER_ALARM = RTC.COUNTER + 136;
    RTC.RESET_REASON2 = 0x00000008;
    RTC.RESET_REASON0 = 0x00100000;
    sdk_os_delay_us(200);
    RTC.GPIO_CFG[2] = 0x00000011;
    RTC.GPIO_CFG[3] = 0x00000003;
    RTC._unknownc = 0x000640c8;
    RTC.CTRL0 &= 0xffffffcf;
    sdk_pm_rtc_clock_cali_proc();
    sdk_pm_set_sleep_time(time_in_us);
    RTC.GPIO_CFG[2] = 0x00000011;
    RTC.GPIO_CFG[3] = 0x00000003;
    DPORT.INT_ENABLE &= ~(DPORT_INT_ENABLE_WDT);
    _xt_isr_mask(1 << ETS_WDT_INUM);
    RTC._unknown40 = 0xffffffff;
    RTC._unknown44 = 0x00000020;
    RTC._unknown10 = 0x00000000;
    if (time_in_us == 0) {
        RTC.RESET_REASON2 = 0x00000000;
    } else {
        RTC.RESET_REASON2 = 0x00000008;
    }
    RTC.RESET_REASON0 = 0x00100000;
}

void sdk_system_deep_sleep(uint32_t time_in_us) {
    if (sdk_wifi_get_opmode() != 2) {
        sdk_wifi_station_stop();
    }
    if (sdk_wifi_get_opmode() != 1) {
        sdk_wifi_softap_stop();
    }
    sdk_os_timer_disarm(&sdk_sta_con_timer);
    sdk_os_timer_setfn(&sdk_sta_con_timer, _deep_sleep_phase2, (void *)time_in_us);
    sdk_os_timer_arm(&sdk_sta_con_timer, 100, 0);
}

bool sdk_system_update_cpu_freq(uint8_t freq) {
    if (freq == 80) {
        DPORT.CPU_CLOCK &= ~(DPORT_CPU_CLOCK_X2);
        sdk_os_update_cpu_frequency(80);
    } else if (freq == 160) {
        DPORT.CPU_CLOCK |= DPORT_CPU_CLOCK_X2;
        sdk_os_update_cpu_frequency(160);
    } else {
        return false;
    }
    return true;
}

uint8_t sdk_system_get_cpu_freq(void) {
    return sdk_os_get_cpu_frequency();
}

bool sdk_system_overclock(void) {
    if (sdk_system_get_cpu_freq() == 80) {
        sdk_cpu_overclock = true;
        sdk_system_update_cpu_freq(160);
        return true;
    }
    return false;
}

bool sdk_system_restoreclock(void) {
    if (sdk_system_get_cpu_freq() == 160 && sdk_cpu_overclock) {
        sdk_cpu_overclock = false;
        sdk_system_update_cpu_freq(80);
        return true;
    }
    return false;
}

uint32_t sdk_system_get_time(void) {
    return WDEV.SYS_TIME + sdk_WdevTimOffSet;
}

uint32_t sdk_system_relative_time(uint32_t reltime) {
    return WDEV.SYS_TIME - reltime;
}

void sdk_system_station_got_ip_set(struct ip_addr *ip, struct ip_addr *mask, struct ip_addr *gw) {
    uint8_t *ip_bytes = (uint8_t *)&ip->addr;
    uint8_t *mask_bytes = (uint8_t *)&mask->addr;
    uint8_t *gw_bytes = (uint8_t *)&gw->addr;
    uint32_t gpio_mask;

    sdk_g_ic.v.station_netif_info->connect_status = STATION_GOT_IP;
    printf("ip:%d.%d.%d.%d,mask:%d.%d.%d.%d,gw:%d.%d.%d.%d", ip_bytes[0], ip_bytes[1], ip_bytes[2], ip_bytes[3], mask_bytes[0], mask_bytes[1], mask_bytes[2], mask_bytes[3], gw_bytes[0], gw_bytes[1], gw_bytes[2], gw_bytes[3]);
    printf("\n");
    if ((sdk_g_ic.s.wifi_led_enable == 1) && (sdk_g_ic.s.wifi_mode == 1)) {
        sdk_os_timer_disarm(&sdk_sta_con_timer);
        gpio_mask = 1 << sdk_g_ic.s.wifi_led_gpio;
        sdk_gpio_output_set(0, gpio_mask, gpio_mask, 0);
    }
}

void sdk_system_print_meminfo(void) {
    printf("%s: 0x%x ~ 0x%x, len: %d\n", "data  ", _data_start, _data_end, _data_end - _data_start);
    printf("%s: 0x%x ~ 0x%x, len: %d\n", "rodata", _rodata_start, _rodata_end, _rodata_end - _rodata_start);
    printf("%s: 0x%x ~ 0x%x, len: %d\n", "bss   ", _bss_start, _bss_end, _bss_end - _bss_start);
    printf("%s: 0x%x ~ 0x%x, len: %d\n", "heap  ", _heap_start, 0x3fffc000, 0x3fffc000 - _heap_start);
}

uint32_t sdk_system_get_free_heap_size(void) {
    return xPortGetFreeHeapSize();
}

uint32_t sdk_system_get_chip_id(void) {
    uint32_t mac0 = DPORT.OTP_MAC0 & 0xff000000;
    uint32_t mac1 = DPORT.OTP_MAC1 & 0x00ffffff;
    return (mac1 << 8) | (mac0 >> 24);
}

uint32_t sdk_system_rtc_clock_cali_proc(void) {
    return sdk_pm_rtc_clock_cali_proc();
}
    
uint32_t sdk_system_get_rtc_time(void) {
    return RTC.COUNTER;
}

struct sdk_rst_info *sdk_system_get_rst_info(void) {
    return &sdk_rst_if;
}

static struct netif *_get_netif(uint32_t mode) {
    struct sdk_g_ic_netif_info *info;

    if (mode >= 2) {
        return NULL;
    }
    if (mode == 0) {
        info = sdk_g_ic.v.station_netif_info;
    } else {
        info = sdk_g_ic.v.softap_netif_info;
    }
    if (info) {
        return info->netif;
    }
    return NULL;
}

bool sdk_wifi_station_dhcpc_start(void) {
    struct netif *netif = _get_netif(0);
    if (sdk_wifi_get_opmode() == 2) {
        return false;
    }
    if (netif && sdk_dhcpc_flag == DHCP_STOPPED) {
        sdk_info.ipaddr.addr = 0;
        sdk_info.netmask.addr = 0;
        sdk_info.gw.addr = 0;
        netif_set_addr(netif, &sdk_info.ipaddr, &sdk_info.netmask, &sdk_info.gw);
        if (dhcp_start(netif)) {
            return false;
        }
    }
    sdk_dhcpc_flag = DHCP_STARTED;
    return true;
}

bool sdk_wifi_station_dhcpc_stop(void) {
    struct netif *netif = _get_netif(0);
    if (sdk_wifi_get_opmode() == 2) {
        return false;
    }
    if (netif && sdk_dhcpc_flag == DHCP_STARTED) {
        dhcp_stop(netif);
    }
    sdk_dhcpc_flag = DHCP_STOPPED;
    return true;
}

enum sdk_dhcp_status sdk_wifi_station_dhcpc_status(void) {
    return sdk_dhcpc_flag;
}

#endif /* OPEN_LIBMAIN_USER_INTERFACE */
