Exportable version of WizziLab's modem driver.

Dependents:   modem_ref_helper

src/modem_ref.cpp

Committer:
Jeej
Date:
2021-02-19
Revision:
60:08efaaca0e83
Parent:
59:3b38b5f499db
Child:
61:820395fc5572

File content as of revision 60:08efaaca0e83:

#include "mbed.h"
#include "WizziDebug.h"

#include "modem_ref.h"
#include "kal_crypto.h"
#include "kal_codec.h"
#include "d7a_1x_fs.h"

#if 0
#define REF_PRINT(...)  PRINT(__VA_ARGS__)
#define URC_PRINT(...)  PRINT(__VA_ARGS__)
#else
#define REF_PRINT(...);
#define URC_PRINT(...);
#endif

typedef struct {
    action_callback_t*      cb;
    alp_payload_t**         alp_rsp;
} modem_ref_user_t;

typedef struct {
    fx_serial_send_t*       send;
    modem_ref_callbacks_t*  cb;
    modem_lwan_callbacks_t* lwan_cb;
    modem_ref_user_t        user[MAX_USER_NB];
    u8                      state;
} modem_ref_ctx_t;

static modem_ref_ctx_t g_modem;

#define STATE_OPEN      0xab
#define STATE_CLOSED    0

// Flows
#define WC_FLOWID_CMD       0x10
#define WC_FLOWID_ALP       0x20
#define WC_FLOWID_SYS       0x30

// Sub-Flows
#define WC_FLOW_ALP_CMD     0x20
#define WC_FLOW_ALP_RESP    0x21
#define WC_FLOW_ALP_UNS     0x22
#define WC_FLOW_SYS_RST     0x30
#define WC_FLOW_SYS_PING    0x34
#define WC_FLOW_SYS_PONG    0x35
#define WC_FLOW_SYS_CFG     0x36
#define WC_FLOW_SYS_XON     0x39
#define WC_FLOW_SYS_XOFF    0x3a
#define WC_FLOW_SYS_XACK    0x3b

// Misc CMD...
#define WM_BOOT             0x81

#define PKT_MAX_SIZE 255
static void serial_send(WizziComPacketType type, alp_payload_t* alp)
{
    u8 buf[PKT_MAX_SIZE];
    
    //PRINT("<--\n");
    //alp_payload_print(alp);
    
    u32 len = alp_payload_to_buf(alp, buf, 0, PKT_MAX_SIZE);
        
    g_modem.send(type, buf, len);
    
    alp_payload_free(alp);
}

static void _call_fs_user_cb(alp_payload_t* alp, int id, u32 action)
{
    alp_parsed_chunk_t r;
    u8* p = alp->d;
    
    if (NULL == alp)
    {
        return;
    }
    
    alp_parse_chunk(&p, &r);
            
    switch (r.type)
    {
        case ALP_OPCODE_TAG:
            id  = r.meta.tag.id;
            REF_PRINT("ACT %d: TAG[%d] ID:%d ERR:%d EOP:%d\n", action, id, r.meta.tag.err, r.meta.tag.eop);
            break;
        case ALP_OPCODE_F_RD_DATA:
            REF_PRINT("ACT %d: F_RD_DATA[%d] @%d %d bytes\n", action, r.meta.f_data.fid, r.meta.f_data.offset, r.meta.f_data.length);
            if (g_modem.cb->read)
            {
                g_modem.cb->read(action, r.meta.f_data.fid, r.meta.f_data.offset, r.meta.f_data.length, id);
            }
            break;
        case ALP_OPCODE_F_WR_DATA:
            REF_PRINT("ACT %d: F_WR_DATA[%d] @%d %d bytes\n", action, r.meta.f_data.fid, r.meta.f_data.offset, r.meta.f_data.length);
            if (g_modem.cb->write)
            {
                g_modem.cb->write(action, r.meta.f_data.fid, r.data, r.meta.f_data.offset, r.meta.f_data.length, id);
            }
            break;
        case ALP_OPCODE_F_RD_PROP:
            REF_PRINT("ACT %d: F_RD_PROP[%d]\n", action, r.meta.f_data.fid);
            if (g_modem.cb->read_fprop)
            {
                g_modem.cb->read_fprop(action, r.meta.f_data.fid, id);
            }
            break;
        case ALP_OPCODE_F_DELETE:
            REF_PRINT("ACT %d: F_DELETE[%d]\n", action, r.meta.f_data.fid);
            if (g_modem.cb->remove)
            {
                g_modem.cb->remove(action, r.meta.f_data.fid, id);
            }
            break;
        case ALP_OPCODE_F_FLUSH:
            REF_PRINT("ACT %d: F_FLUSH[%d]\n", action, r.meta.f_data.fid);
            if (g_modem.cb->flush)
            {
                g_modem.cb->flush(action, r.meta.f_data.fid, id);
            }
            break;
        default:
            ASSERT(false, "ASSERT: Unsupported ALP File Operation: %d\n", r.type);
            break;
    }
    
    ASSERT(id >= 0, "ASSERT: Wrong id %d\n", id);
    
    _call_fs_user_cb(alp->next, id, ++action);
}

static void call_fs_user_cb(alp_payload_t* alp)
{
    _call_fs_user_cb(alp, -1, 0);
}

static void _call_urc_user_cb(alp_payload_t* alp, u32 action)
{
    alp_parsed_chunk_t r;
    u8* p = alp->d;
    
    if (NULL == alp)
    {
        return;
    }
    
    alp_parse_chunk(&p, &r);

    switch (r.type)
    {
        case ALP_OPCODE_RSP_URC:
            if (ALP_URC_TYPE_LQUAL == r.meta.urc.type)
            {
                REF_PRINT("ACT %d: RSP_URC: LQUAL[%d] %d\n", action, r.meta.urc.ifid, r.meta.urc.per);
                if (g_modem.cb->lqual)
                {
                    g_modem.cb->lqual(r.meta.urc.ifid, r.meta.urc.per);
                }
            }
            else if (ALP_URC_TYPE_LDOWN == r.meta.urc.type)
            {
                REF_PRINT("ACT %d: RSP_URC: LDOWN[%d]\n", action, r.meta.urc.ifid);
                if (g_modem.cb->ldown)
                {
                    g_modem.cb->ldown(r.meta.urc.ifid);
                }
            }
            else if (ALP_URC_TYPE_BUSY == r.meta.urc.type)
            {
                REF_PRINT("ACT %d: RSP_URC: BUSY[%d]\n", action, r.meta.urc.ifid);
                if (g_modem.cb->busy)
                {
                    g_modem.cb->busy(r.meta.urc.ifid);
                }
            }
            else if (ALP_URC_TYPE_ITF_BUSY == r.meta.urc.type)
            {
                REF_PRINT("ACT %d: RSP_URC: ITF_BUSY[%d] for %d seconds\n", action, r.meta.urc.ifid, r.meta.urc.per);

                if (FID_LWAN_ITF == r.meta.urc.ifid)
                {
                    if (g_modem.lwan_cb)
                    {
                        if (MAX_U32 == r.meta.urc.per)
                        {
                            if (g_modem.lwan_cb->join_failed)
                            {
                                g_modem.lwan_cb->join_failed();
                            }
                        }
                        else if (0 == r.meta.urc.per)
                        {
                            if (g_modem.lwan_cb->packet_sent)
                            {
                                g_modem.lwan_cb->packet_sent();
                            }
                        }
                        else
                        {
                            if (g_modem.lwan_cb->itf_busy)
                            {
                                g_modem.lwan_cb->itf_busy(r.meta.urc.per);
                            }
                        }
                    }
                }
                else
                {
                    if (g_modem.cb->itf_busy)
                    {
                        g_modem.cb->itf_busy(r.meta.urc.ifid, r.meta.urc.per);
                    }
                }
            }
            else
            {
                ASSERT(false, "ASSERT: Unsupported ALP URC: %d\n", r.meta.urc.type);
            }
            break;
        default:
            // This could be anything
            // Let user deal with the payload
            if (g_modem.cb->udata)
            {
                g_modem.cb->udata(alp);
            }
            break;
    }
    
    _call_urc_user_cb(alp->next, ++action);
}

static void call_urc_user_cb(alp_payload_t* alp)
{
    _call_urc_user_cb(alp, 0);
}

void modem_ref_input(u8 flowid, u8* payload, u8 size)
{
    int rem =size;
    u8* p = payload;
    alp_parsed_chunk_t r;
    int id = -1;
    s8 err = ALP_ERR_NONE;
    u8 eop;
    u32 parsed;
    alp_payload_t* alp;    

    REF_PRINT("input 0x%x/%d Bytes\n", flowid, size);
    switch (flowid)
    {
        case WC_FLOW_ALP_UNS:
        case WC_FLOW_ALP_CMD:

            // pre-parse payload
            alp = alp_payload_parse(p, size);
            
            //PRINT("-->\n");
            //alp_payload_print(alp);
            
            if (ALP_OPCODE_TAG == (alp->d[0] & 0x3F))
            {
                // Payload starts with a tag, this must be a FS command
                call_fs_user_cb(alp);
            }
            else
            {
                // else this is a 'real' URC (or ISTATUS if enabled on modem)
                call_urc_user_cb(alp);
            }
            
            alp_payload_free(alp);

            break;
        case WC_FLOW_ALP_RESP:
        
            // pre-parse payload
            alp = alp_payload_parse(p, size);
            
            //PRINT("-->\n");
            //alp_payload_print(alp);
            
            p = alp->d;
            alp_parse_chunk(&p, &r);

            // This should always be a TAG'ed response as we tag our requests
            ASSERT((r.type == ALP_OPCODE_RSP_TAG), "ASSERT: expecting RESP_TAG got 0x%02X\n", r.type);
            
            id  = r.meta.tag.id;
            eop = r.meta.tag.eop;
            
            if (g_modem.user[id].cb == NULL)
            {
                PRINT("WARNING: NULL Callback for ID %d\n", id);
                alp_payload_print(alp);
                alp_payload_free(alp);
                break;
            }
            //ASSERT(g_modem.user[id].cb != NULL, "ASSERT: NULL Callback for ID %d\n", id);

            if (g_modem.user[id].alp_rsp)
            {
                // Give the remaining of the payload to the user to deal with
                *(g_modem.user[id].alp_rsp) = alp_payload_append(*(g_modem.user[id].alp_rsp), alp);
            }
            else
            {
                // Discard payload
                alp_payload_free(alp);
            }
            
            if(eop)
            { // This is mainly for debug, catch old pointers
                //g_modem.user[id].data   = NULL;
                //g_modem.user[id].istatus= NULL;
                REF_PRINT("EOP\n");
            }
            
            // User Callback
            g_modem.user[id].cb(eop, err, id);
            
            break;
        case WC_FLOW_SYS_RST:
            // 'Software' reset request
            if (g_modem.cb->reset)
            {
                g_modem.cb->reset();
            }
            break;
        case WC_FLOWID_CMD:
            if (*p == WM_BOOT)
            {
                uint16_t nb_boot;
                u8 cause;
                p++;
                cause = p[0];
                nb_boot = HAL_TO_U16(p[3], p[2]);
                if (g_modem.cb->boot)
                {
                    g_modem.cb->boot(cause, nb_boot);
                }
            }
            else
            {
                REF_PRINT("Unsupported CMD :0x%x\n", *p);
            }
            break;
        default:
            REF_PRINT("Unsupported WC Flowid:0x%x / %d Bytes\n", flowid, size);
            break;
    }
}

static u32 modem_ref_add_root_permission(u8* p, u8* root_key, u8* action, u32 action_size)
{
    u8 hash[32];

    // Calculate hash on action
    kal_sha256_init();
    kal_sha256_update(action, action_size);
    kal_sha256_update(root_key, D7A_FS_ROOT_KEY_SIZE);
    kal_sha256_final(hash);
    // Prepend Permission-request...
    ALP_ACTION_PERM_REQ(p, false, ALP_PERM_REQ_ROOT, ALP_WIZZILAB_AUTH_PROTOCOL_ID);
    // ... and hash
    memcpy(p, hash, ALP_ACTION_PERM_REQ_TOKEN_SIZE);

    return ALP_ACTION_PERM_REQ_OFFSET;
}

int modem_ref_get_id(action_callback_t* cb)
{
    int i;
    // Use rolling ids
    static u8 rolling;

    for(i = 0; i < MAX_USER_NB; i++)
    {
        int id = (i + rolling) % MAX_USER_NB;

        if (g_modem.user[id].cb == NULL)
        {
            g_modem.user[id].cb = cb;
            rolling++;
            //PRINT("Get ID:%d/0x%08x\n", id, cb);
            return id;
        }
    }
    ASSERT(i < MAX_USER_NB, "ASSERT: out of users\n");
    return -1;
}

int modem_ref_free_id(u8 id)
{
    if (id < MAX_USER_NB)
    {
        g_modem.user[id].cb     = NULL;
        //PRINT("Free ID:%d\n", id);
        return id;
    }
    return -1;
}

void modem_ref_open(fx_serial_send_t* send, modem_ref_callbacks_t* callbacks)
{
    if (g_modem.state != STATE_OPEN)
    {
        g_modem.send = send;
        g_modem.cb  = callbacks;
        memset(g_modem.user, 0, (MAX_USER_NB * sizeof(modem_ref_user_t)));

        REF_PRINT("Open Modem - Max users:%d\n", MAX_USER_NB);

        g_modem.state = STATE_OPEN;
    }
}

void modem_ref_close(void)
{
    if (g_modem.state == STATE_OPEN)
    {
        g_modem.state = STATE_CLOSED;
    }
}

void modem_ref_reset(void)
{
    serial_send(WizziComPacketSysReset, NULL);
}

void modem_ref_set_lwan_cb(modem_lwan_callbacks_t* callbacks)
{
    g_modem.lwan_cb = callbacks;
}

void modem_ref_alp(void* itf, alp_payload_t* alp_user, alp_payload_t** alp_rsp, int id)
{
    alp_payload_t* alp = NULL;

    if (alp_rsp)
    {
        // Expecting a response

        // Make sure pointer value is NULL
        *alp_rsp = NULL;
    }

    // response pointer
    g_modem.user[id].alp_rsp = alp_rsp;

    // Always a tag
    alp = alp_payload_tag(alp, id);

    // Forward on itf if any
    if (itf)
    {
        alp = alp_payload_forward(alp, itf);
        // NOP action to get istats on ACKs
        alp = alp_payload_nop(alp, false);
    }

    // Add user payload
    alp = alp_payload_append(alp, alp_user);

    // Send to modem
    serial_send(WizziComPacketAlpUns, alp);
}

static void _modem_ref_rsp(alp_payload_t* alp_user, u8 err, int id)
{
    alp_payload_t* alp = NULL;

    // response pointer
    g_modem.user[id].alp_rsp = NULL;

    // Always a tag
    alp = alp_payload_rsp_tag(alp, id, true, err);

    // Add user payload
    alp = alp_payload_append(alp, alp_user);

    // Send to modem
    serial_send(WizziComPacketAlpResp, alp);
}

void modem_ref_respond(u8 action, s8 status, int id)
{
    alp_payload_t* alp = NULL;

    alp = alp_payload_rsp_status(alp, action, status);

    _modem_ref_rsp(alp, (status < ALP_ERR_NONE)? true: false, id);
}

void modem_ref_respond_fprop(u8 fid, alp_file_header_t* hdr, int id)
{
    alp_payload_t* alp = NULL;

    alp = alp_payload_rsp_fprop(alp, fid, hdr);

    _modem_ref_rsp(alp, false, id);
}

void modem_ref_respond_read(u8 fid, void* data, u32 offset, u32 length, int id)
{
    alp_payload_t* alp = NULL;

    alp = alp_payload_rsp_f_data(alp, fid, data, offset, length);

    _modem_ref_rsp(alp, false, id);
}