/*
 * sci_boot.c
 *
 *  Created on: 27/04/2022
 *      Author: Александр Литягин
 * ----------------------------------------------------------------------------------------------------------------
 *  host for Renesas RA2 chips SCI boot
 *  Хост загрузки Renesas RA2 чипа по sci
 *
 */

#include "boot_sci.h"
#include "boot_sci_protocol.h"
#include <project-conf.h>

#include <OsProcess.h>
#include <OsTime.h>
#include <ptx.h>
#include "hal/halos_types.h"




///////////////////////////////////////////////////////////////////////////

#if !defined(RELEASE)

#if 1
#include <assert.h>
#define ASSERT(...) assert(__VA_ARGS__)
#else
#define ASSERT(...)
#endif

#endif


//------------------------------------------------------------------------------

#include <sys/log.h>

LOG_MODULE_LOCAL("boot sci");
#ifdef LOG_LEVEL_BOOT
#define LOG_LEVEL LOG_LEVEL_BOOT
#else
#define LOG_LEVEL LOG_LEVEL_TRACE
#endif



//------------------------------------------------------------------------------
#include "trace_probes.h"
#ifndef trace_bootsci_io_to
trace_need(bootsci_io_to);
#endif



///////////////////////////////////////////////////////////////////////////

typedef struct RBOOTCtx{
    struct pt   pt;
    struct pt   ptio;
    RBOOTState  online;

    unsigned    to;     // op timeout [ticks]
    os_time_t   start;
}RBOOTCtx;

RBOOTCtx        boot_sci;

RBOOTState  rboot_state(void){ return boot_sci.online; };


#ifndef RBOOTSCI_UART
UART_Device* boot_sci_uart;
#else
#define boot_sci_uart   (RBOOTSCI_UART)
#endif


enum{
    RBOOTSCI_FRAME_LIMIT    = RBOOTSCI_FRAME_LOAD_LIMIT + 1,
    RBOOTSCI_SIZE_LIMIT     = RBOOTSCI_FRAME_LIMIT + sizeof(RBOOT_SCIFrameAck),
};

union RBOOT_SCIPacketBuf{
    RBOOT_SCIAnyFrame   frame;
    uint8_t             buf[RBOOTSCI_SIZE_LIMIT];
};

// TODO: использовать буферы MU_SRV
union RBOOT_SCIPacketBuf bootsci_rx;
union RBOOT_SCIPacketBuf bootsci_tx;



///////////////////////////////////////////////////////////////////////////
uint8_t rboot_sci_frame_sum(const void* src){
    const RBOOT_SCIFrame* frame = (const RBOOT_SCIFrame*)src;

    unsigned len = rboot_sci_frameload_len(&frame->head);

    uint8_t sum = frame->head.lenh + frame->head.lenl;
    const uint8_t* pd = frame->data;
    for (; len > 0; --len )
        sum += *pd++;

    return 0-sum;
}

int rboot_sci_frame_is_valid(const void* src){
    const RBOOT_SCIFrame* frame = (const RBOOT_SCIFrame*)src;

    uint8_t soh = frame->head.soh;
    bool ok = (soh == RBOOTSCI_SOH) || (soh == RBOOTSCI_SOD);
    if ( UNLIKELY(!ok) )
        return false;

    unsigned len = rboot_sci_frameload_len(&frame->head);
    if ( UNLIKELY(len > RBOOTSCI_FRAME_LIMIT) )
        return false;

    if ( UNLIKELY(frame->data[len+1] != RBOOTSCI_ETX) )
        return false;

    uint8_t sck = frame->data[len];

    if (rboot_sci_frame_sum(src) == sck)
        return len;

    return -1;
}

int rboot_sci_ack_is_valid(const void* ack, uint8_t cmd){
    int len = rboot_sci_frame_is_valid(ack);
    if ( UNLIKELY(len < 0) )
        return len;

    const RBOOT_SCIFrameAck* frame = (const RBOOT_SCIFrameAck*)ack;

    if ( (frame->head.res & ~RBOOTSCI_ACK_ERR) == cmd)
        return len;
    return -1;
}

/// @brief подписывает sck фрефма, валидирует что он корректный
int rboot_sci_frame_build( void* src){
    RBOOT_SCIFrame* frame = (RBOOT_SCIFrame*)src;

    uint8_t soh = frame->head.soh;
    bool ok = (soh == RBOOTSCI_SOH) || (soh == RBOOTSCI_SOD);
    if ( UNLIKELY(!ok) )
        return -1;

    unsigned len = rboot_sci_frameload_len(&frame->head);
    if ( UNLIKELY(len > RBOOTSCI_FRAME_LIMIT) )
        return -1;

    frame->data[len]    = rboot_sci_frame_sum(src);
    frame->data[len+1]  = RBOOTSCI_ETX;

    return  len;
}

/// @brief строит фрейм SOH
int rboot_sci_cmd_build(void* src){
    RBOOT_SCIFrameHead* frame = (RBOOT_SCIFrameHead*)src;
    frame->soh = RBOOTSCI_SOH;

    return rboot_sci_frame_build(src);
}

/// @brief строит фрейм SOD
int rboot_sci_data_build(void* src){
    RBOOT_SCIFrameHead* frame = (RBOOT_SCIFrameHead*)src;
    frame->soh = RBOOTSCI_SOD;

    return rboot_sci_frame_build(src);
}




///////////////////////////////////////////////////////////////////////////

/// @ brief инициализировать демон загрузчика
void rboot_sci_init(UART_Device* uart){
    PT_INIT(&boot_sci.pt);
    boot_sci.online = RBOOT_STATE_INIT;

#ifndef RBOOTSCI_UART
    boot_sci_uart = uart;
#else
    ASSERT( boot_sci_uart == uart );
#endif

}


/// @ brief ожидание завершения текущей операции
PT_THREAD( rboot_sci_wait() ){
    if ( PT_IS_NULL(&boot_sci.pt) )
        return ptOK;

    return ptWAITING;
}


/// @ brief сбросить теущий процесс
PT_THREAD( rboot_sci_abort() ){

    // TODO: остановить и выключитть UART
    PT_INIT(&boot_sci.pt);
    return ptOK;
}



//==============================================================================================

static
bool rboot_sci_is_valid_ack(void){
    const void* ack = bootsci_rx.buf;
    uint8_t cmd     = bootsci_tx.frame.cmd.head.cmd;

    return rboot_sci_ack_is_valid(ack, cmd);
}

/// @return > 0 - длина принятого фрейма
/// @return = 1 - приемник остановил прием
/// @return < 0 - длина принимаемого фрейма
static
int rboot_rx_have_frame(void){

    if (bootsci_rx.buf[0] == 0xff) return 0;

    UART_Device* uart    = boot_sci_uart;

    uint8_t* tail = UART_rx_tail(uart);
    int have;

    if (tail != NULL)
        have = tail - bootsci_rx.buf;
    else {
        // прекращен прием фрейма, загружен полный буфер

        //have = sizeof(bootsci_rx);
        return sizeof(bootsci_rx);
    }

    if (have < (int)sizeof(RBOOT_SCIFrameHead) )
        return -sizeof(RBOOT_SCIFrameHead);

    int framesz = rboot_sci_frameload_len(&bootsci_rx.frame.head);

    if (framesz > (int)(RBOOTSCI_FRAME_LIMIT) ) {
        // прекращу принимать этот фрейм ибо он точно не уместится в буфер
        return 1;
    }

    framesz = RBOOT_FRAME_SIZE(framesz);

    if (have >= framesz)
        return have;
    else
        return -framesz;
}


void rboot_sci_req_make(uint8_t cmd){
    RBOOT_SCIFrameCmd* f = &bootsci_tx.frame.cmd;
    f->head.cmd = cmd;
    rboot_sci_cmd_build(f);
}

void rboot_sci_cmd_make(uint8_t cmd){
    RBOOT_SCIFrameCmd* f = &bootsci_tx.frame.cmd;
    f->head.cmd = cmd;

    rboot_sci_frame_len_assign(&f->head.head, sizeof(*f) );
    rboot_sci_cmd_build(f);
}




os_timeout_t    boot_sci_to;

#if 0

// nonblocking wait RX
#define RBOOT_WAIT_RX( uart, ticks ) ostimeout_init(&boot_sci_to, (ticks) );\
                            PT_WAIT_UNTIL(pt, UART_rx_busy(uart) || ostimeout_expired(&boot_sci_to) );\
                            ostimeout_stop(&boot_sci_to);

#else
// blocking wait RX
static
void RBOOT_WAIT_RX( uart_ctrl_t* uart, unsigned ticks ){
    os_time_t start = clock_now();
    while( UART_rx_busy(uart) ){
        if ( (clock_now() - start) >= ticks ) break;
    }
}
#endif



PT_THREAD(rboot_io(unsigned ticks) ){
    struct pt*      pt   = &boot_sci.ptio;
    UART_Device* uart    = boot_sci_uart;
    uint8_t*       ptail;

    int             ok;
    unsigned        to;

    enum {
        RX_POLL_TO  = CLOCKMS_MORE(2), //  период опроса приемника ответа
    };

    PT_BEGIN(pt)

    ok = rboot_sci_frame_is_valid(bootsci_tx.buf);
    if (ok < 0)
        PT_RESULT(pt, DEV_FAIL);

    bootsci_rx.buf[0] = 0xff;

    ok = UART_read(uart, bootsci_rx.buf, sizeof(bootsci_rx) );
    if (ok != UARTERR_OK)
        PT_RESULT(pt, DEV_TOTAL + ok);


    to = rboot_sci_frame_len(&bootsci_tx.frame.head);
    ok = UART_write(uart, bootsci_tx.buf, to );
    if (ok != UARTERR_OK)
        PT_RESULT(pt, DEV_TOTAL + ok);

    // wait for sent command
    // calculate frame io time as 1ms/byte. for lowest speed 9600 it must enough
    to = CLOCKMS_MORE( to )+1;
    ostimeout_init(&boot_sci_to, to );

    PT_WAIT_WHILE(pt, !UART_flush(uart) && ostimeout_waiting(&boot_sci_to) );\
    ostimeout_stop(&boot_sci_to);

    if ( !UART_flush(uart) ){
        trace_bootsci_io_to_twist();

        UART_abort(uart, UART_DIR_TX);
        LOG_ERR("send req timeout\n");
        PT_RESULT(pt, ptNOK);
    }

    // wait for receive ack
    boot_sci.start = clock_now();
    ok = 0;
    while ( (ok == 0) ) {

        to = clock_now() - boot_sci.start;
        if ( to > ticks ){
            LOG_ERR("ack timeout\n");
            PT_RESULT(pt, ptNOK);
        }

        ostimeout_init(&boot_sci_to, RX_POLL_TO );
        PT_WAIT_UNTIL(pt, ( ok = rboot_rx_have_frame(), ok != 0)
                        //|| !UART_rx_busy(uart)
                        || ostimeout_expired(&boot_sci_to)
                        );

        if ( !UART_rx_busy(uart) ){
            LOG_ERR("req aborted\n");
            PT_RESULT(pt, ptNOK);
        }
    }

    ostimeout_stop(&boot_sci_to);

    if (ok < 0) {

    ptail = UART_rx_tail(uart);
    if (ptail != NULL){
        boot_sci.start = ptail - bootsci_rx.buf;
    }
    else{
        boot_sci.start = 0;
    }

    trace_bootsci_io_to_twist();

    while ( (ok < 0) ){

        // начали прием фрейма, и он в процессе приема
        ostimeout_init(&boot_sci_to, RX_POLL_TO );

        PT_WAIT_UNTIL(pt, (ok = rboot_rx_have_frame(), ok > 0)
                        //|| !UART_rx_busy(uart)
                        || ostimeout_expired(&boot_sci_to)
                        );

        if ( ostimeout_expired(&boot_sci_to)) {

            trace_bootsci_io_to_twist();

            // отслежу прием новых байт
            ptail = UART_rx_tail(uart);
            if (ptail != NULL)
                to = UART_rx_tail(uart) - bootsci_rx.buf;
            else
                to = sizeof(bootsci_rx.buf);

            if (to > boot_sci.start)
                boot_sci.start = to;
            else {
                // прекращу ожидание фрейма если не поступает новых байтов
                LOG_ERR("frame breaks\n");
                ok = 0;
            }
        }
    }
    ostimeout_stop(&boot_sci_to);

    UART_abort(uart, UART_DIR_RX);

    } // if (ok < 0)

    if ( rboot_sci_is_valid_ack() ) {
        PT_RESULT(pt, ptOK);
    }
    else {
        if ( rboot_rx_have_frame() )
            LOG_ERR("invalid ack\n");
        else
            LOG_ERR("absent ack\n");
        PT_RESULT(pt, ptNOK);
    }

    PT_END(pt);
}



//==============================================================================================

/// @brief переводит целевой чип в режим загрузки, и устанавливает связь
PT_THREAD( rboot_sci_start() ){
    struct pt*      pt      = &boot_sci.pt;
    UART_Device* uart    = boot_sci_uart;
    static const uint8_t    zeros[] = {0,0};

    enum {
        // таймаут ожидания байта ответа
        BYTE_ACK_TO = CLOCKMS_MORE(2),
        BOOT_CMD    = 0x55,
        BOOT_ACK    = 0xC3,

        CMD_ACK_TO  = CLOCKMS_MORE(2),
    };

    UARTError       ok;
    static int      i;

    PT_BEGIN(pt);

    boot_sci.online = RBOOT_STATE_INIT;

    ok = UART_baud_set(uart, 9600);
    if (ok != UARTERR_OK)
        PT_RESULT(pt, DEV_TOTAL + ok);

    ok = UART_write(uart, zeros, 1);
    if (ok != UARTERR_OK)
        PT_RESULT(pt, DEV_TOTAL + ok);

    PT_WAIT_UNTIL(pt, UART_flush(uart) );

    for(i = 5; i > 0; --i){

        bootsci_rx.buf[0] = 0xff;
        ok = UART_read(uart, bootsci_rx.buf, 1);
        if (ok != UARTERR_OK)
            PT_RESULT(pt, DEV_TOTAL + ok);

        ok = UART_write(uart, zeros, 1 );
        if (ok != UARTERR_OK)
            PT_RESULT(pt, DEV_TOTAL + ok);

        RBOOT_WAIT_RX(uart, BYTE_ACK_TO);

        if (bootsci_rx.buf[0] == 0) {
            boot_sci.online = RBOOT_STATE_0ACK;
            break;
        }
    }

    if (boot_sci.online != RBOOT_STATE_0ACK){
        LOG_ERR("chip not answer 0\n");
        PT_RESULT(pt, ptNOK);
    }

    // now take boot code


    bootsci_tx.buf[0] = BOOT_CMD;
    for(i = 3; i > 0; --i){

        bootsci_rx.buf[0] = 0xff;
        ok = UART_read(uart, bootsci_rx.buf, 1);
        if (ok != UARTERR_OK)
            PT_RESULT(pt, DEV_TOTAL + ok);

        ok = UART_write(uart, bootsci_tx.buf, 1 );
        if (ok != UARTERR_OK)
            PT_RESULT(pt, DEV_TOTAL + ok);

        RBOOT_WAIT_RX(uart, BYTE_ACK_TO);

        if (bootsci_rx.buf[0] == BOOT_ACK) {
            boot_sci.online = RBOOT_STATE_BOOT;
            break;
        }
    }

    if (boot_sci.online != RBOOT_STATE_BOOT){
        LOG_ERR("chip not boot\n");
        PT_RESULT(pt, ptNOK);
    }


    // now check auth-mode
    rboot_sci_cmd_make(RBOOTSCI_INQUIRY);
    PT_SPAWN_RESULT(pt, &boot_sci.ptio, ok, rboot_io(CMD_ACK_TO) );
    if (ok != ptOK){
        PT_RESULT(pt, ok);
    }

    ok = bootsci_rx.frame.ack.head.res;
    if (ok == RBOOTSCI_INQUIRY) {
        // inquiry passed, so looks in command-mode, no need auth
        boot_sci.online = RBOOT_STATE_CMD;
        PT_RESULT(pt, ptOK);
    }
    else {
        // inquiry failes
        if (bootsci_rx.frame.ack.head.sts == RBOOTSCI_ERR_FLOW) {
            LOG_INFO("wanna auth\n");
            boot_sci.online = RBOOT_STATE_AUTH;
            PT_RESULT(pt, ptOK);
        }

        LOG_INFO("inquiry failed$%x\n", (unsigned)bootsci_rx.frame.ack.head.sts );
        PT_RESULT(pt, ptNOK);
    }

    PT_END(pt);
}

void rboot_sci_ping() {
    rboot_sci_cmd_make(RBOOTSCI_INQUIRY);
}

PT_THREAD( rboot_sci_io(unsigned to_ticks ) ){
    struct pt*      pt      = &boot_sci.pt;
    UARTError       ok;

    PT_BEGIN(pt);


    PT_SPAWN_RESULT(pt, &boot_sci.ptio, ok, rboot_io(to_ticks) );
    if (ok != ptOK){
        PT_RESULT(pt, ok);
    }

    ok = bootsci_rx.frame.ack.head.res;
    if (ok == bootsci_tx.frame.cmd.head.cmd) {
        LOG_DBG("cmd%x ok\n", bootsci_tx.frame.cmd.head.cmd);
        PT_RESULT(pt, ptOK);
    }
    else {
        LOG_INFO("cmd%x failed$%x\n", bootsci_tx.frame.cmd.head.cmd, (unsigned)bootsci_rx.frame.ack.head.sts );
        PT_RESULT(pt, ptNOK);
    }

    PT_END(pt);
}



PT_THREAD( rboot_sci_auth( const RBootId* id) ){
    struct pt* pt = &boot_sci.pt;
    enum {
        CMD_ACK_TO  = CLOCKMS_MORE(2),
    };

    UARTError       ok;

    PT_BEGIN(pt);

    if (boot_sci.online > RBOOT_STATE_AUTH)
        PT_RESULT(pt, ptOK);
    else if (boot_sci.online < RBOOT_STATE_AUTH)
        PT_RESULT(pt, ptFAIL);


    // соберу каманду ID
    memcpy(bootsci_tx.frame.id.id.raw, id->raw, sizeof(*id) );
    rboot_sci_frame_len_assign(&bootsci_tx.frame.head, sizeof(bootsci_tx.frame.id) );
    rboot_sci_req_make(RBOOTSCI_ID);

    PT_SPAWN_RESULT(pt, &boot_sci.ptio, ok, rboot_io(CMD_ACK_TO) );
    if (ok != ptOK){
        PT_RESULT(pt, ok);
    }

    ok = bootsci_rx.frame.ack.head.res;
    if (ok == RBOOTSCI_ID) {
        // ID passed, so looks in command-mode, no need auth
        LOG_INFO("ID passed\n");
        boot_sci.online = RBOOT_STATE_CMD;
        PT_RESULT(pt, ptOK);
    }
    else {
        LOG_INFO("ID failed$%x\n", (unsigned)bootsci_rx.frame.ack.head.sts );
        PT_RESULT(pt, ptNOK);
    }

    PT_END(pt);
}



PT_THREAD( rboot_sci_baud( unsigned baud) ){
    struct pt* pt = &boot_sci.pt;
    UART_Device* uart    = boot_sci_uart;
    enum {
        CMD_ACK_TO  = CLOCKMS_MORE(2),
        CMD = RBOOTSCI_BAUD,
    };

    UARTError       ok;

    PT_BEGIN(pt);

    if (boot_sci.online < RBOOT_STATE_CMD)
        PT_RESULT(pt, ptFAIL);

    bootsci_tx.frame.baud.baud = __htonl(baud);
    rboot_sci_frame_len_assign(&bootsci_tx.frame.head, sizeof(bootsci_tx.frame.baud) );
    rboot_sci_req_make(CMD);

    PT_SPAWN_RESULT(pt, &boot_sci.ptio, ok, rboot_io(CMD_ACK_TO) );
    if (ok != ptOK){
        PT_RESULT(pt, ok);
    }

    ok = bootsci_rx.frame.ack.head.res;
    if (ok == CMD) {
        ok = UART_baud_set(uart, baud);
        LOG_INFO_ERR( (ok == UARTERR_OK), "baud %d assigned ok%d\n", baud, ok);
        PT_RESULT(pt, ptOK);
    }
    else {
        LOG_INFO("baud failed$%x\n", (unsigned)bootsci_rx.frame.ack.head.sts );
        PT_RESULT(pt, ptNOK);
    }

    PT_END(pt);
}

PT_THREAD( rboot_sci_sign( RBOOTSign* y ) ){
    struct pt* pt = &boot_sci.pt;
    enum {
        CMD_ACK_TO  = CLOCKMS_MORE(2),
        CMD = RBOOTSCI_SIGN,
    };

    UARTError       ok;

    PT_BEGIN(pt);

    if (boot_sci.online < RBOOT_STATE_CMD)
        PT_RESULT(pt, ptFAIL);

    rboot_sci_frame_len_assign(&bootsci_tx.frame.head, sizeof(bootsci_tx.frame.cmd) );
    rboot_sci_req_make(CMD);

    PT_SPAWN_RESULT(pt, &boot_sci.ptio, ok, rboot_io(CMD_ACK_TO) );
    if (ok != ptOK){
        PT_RESULT(pt, ok);
    }

    ok = bootsci_rx.frame.ack.head.res;
    if (ok == CMD) {
        RBOOT_SCIAckSign* x = &bootsci_rx.frame.sign;
        y->baud_limit   = __ntohl(x->rmb);
        y->sci_freq     = __ntohl(x->sci);
        y->area_num     = x->noa;
        y->chip_type    = x->typ;
        y->verl         = x->bfv & 0xff;
        y->verh         = x->bfv >> 8;

        LOG_INFO_ERR( (ok == UARTERR_OK), "sign get ok%d\n", ok);
        PT_RESULT(pt, ptOK);
    }
    else {
        LOG_INFO("sign failed$%x\n", (unsigned)bootsci_rx.frame.ack.head.sts );
        PT_RESULT(pt, ptNOK);
    }

    PT_END(pt);
}


PT_THREAD( rboot_sci_area( RBOOTArea* y, unsigned n ) ){
    struct pt* pt = &boot_sci.pt;
    enum {
        CMD_ACK_TO  = CLOCKMS_MORE(2),
        CMD = RBOOTSCI_AREA,
    };

    UARTError       ok;

    PT_BEGIN(pt);

    if (boot_sci.online < RBOOT_STATE_CMD)
        PT_RESULT(pt, ptFAIL);

    bootsci_tx.frame.area.narea = n;
    rboot_sci_frame_len_assign(&bootsci_tx.frame.head, sizeof(bootsci_tx.frame.area) );
    rboot_sci_req_make(CMD);

    PT_SPAWN_RESULT(pt, &boot_sci.ptio, ok, rboot_io(CMD_ACK_TO) );
    if (ok != ptOK){
        PT_RESULT(pt, ok);
    }

    ok = bootsci_rx.frame.ack.head.res;
    if (ok == CMD) {
        RBOOT_SCIAckArea* x = &bootsci_rx.frame.ack_area;
        y->start    = __ntohl(x->start);
        y->end      = __ntohl(x->end);
        y->sec_size = __ntohl(x->sector_size);
        y->page_size= __ntohl(x->page_size);
        y->type     = x->koa;

        LOG_INFO_ERR( (ok == UARTERR_OK), "area[%d] get ok$%x\n", n, ok);
        PT_RESULT(pt, ptOK);
    }
    else {
        LOG_INFO("area failed$%x\n", (unsigned)bootsci_rx.frame.ack.head.sts );
        PT_RESULT(pt, ptNOK);
    }

    PT_END(pt);
}


void rboot_sci_erase( uintptr_t start, uintptr_t end ){
    bootsci_tx.frame.erase.start    = __htonl(start);
    bootsci_tx.frame.erase.end      = __htonl(end);
    rboot_sci_frame_len_assign(&bootsci_tx.frame.head, sizeof(bootsci_tx.frame.erase) );
    rboot_sci_req_make(RBOOTSCI_ERASE);
}

void rboot_sci_write( uintptr_t start, uintptr_t end ){
    bootsci_tx.frame.wr.start    = __htonl(start);
    bootsci_tx.frame.wr.end      = __htonl(end);
    rboot_sci_frame_len_assign(&bootsci_tx.frame.head, sizeof(bootsci_tx.frame.wr) );
    rboot_sci_req_make(RBOOTSCI_WR);
}

void rboot_sci_send( void* src, unsigned len ){
    ASSERT( len <= RBOOTSCI_FRAME_LIMIT );

    bootsci_tx.frame.wr_data.head.cmd = RBOOTSCI_WR;
    memcpy(bootsci_tx.frame.wr_data.data, src, len);
    rboot_sci_frameload_len_assign(&bootsci_tx.frame.head, len + 1 );
    rboot_sci_data_build(&bootsci_tx.frame);
}
