Exportable version of WizziLab's modem driver.

Dependents:   modem_ref_helper

src/modem_ref.cpp

Committer:
Jeej
Date:
2021-01-27
Revision:
56:67e3d9608403
Parent:
51:92667569acc7
Child:
57:5444cfda9889

File content as of revision 56:67e3d9608403:

#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;
    uint8_t*                     data;
    uint8_t*                     istatus;
} modem_ref_user_t;

typedef struct {
    uint8_t*                     data;
    uint8_t                      type;
    uint8_t                      length;
} modem_ref_istatus_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];
    modem_ref_istatus_t     istatus;
    uint8_t                  state;
    uint8_t                  tx_sequ;
    s32                 action;
} 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

static void serial_send(uint8_t flowid, uint8_t* data, int size)
{
    uint8_t len = (uint8_t)size;
    uint8_t wch[WC_HEADER_SIZE] = {
        WC_SYNC_BYTE_0,
        WC_SYNC_BYTE_1,
        len,
        g_modem.tx_sequ++,
        flowid
        };

    ASSERT(size < 256, "serial_send too big for serial protocol (%d/%dmax)", size, 255);

    g_modem.send(wch, WC_HEADER_SIZE, data, len);
}

void modem_ref_input(uint8_t flowid, uint8_t* payload, uint8_t size)
{
    int rem =size;
    uint8_t* p = payload;
    alp_parsed_chunk_t r;
    int id = -1;
    int8_t err = ALP_ERR_NONE;
    uint8_t eop;
    uint32_t parsed;

    g_modem.action = -1;

    //REF_PRINT("input 0x%x/%d Bytes\n", flowid, size);
    switch (flowid)
    {
        case WC_FLOW_ALP_UNS:
        case WC_FLOW_ALP_CMD:
            parsed = alp_parse_chunk(&p, &r);
            if (!parsed)
            {
                // Discard the payload in case of parsing error.
                REF_PRINT("Parsing error line %d!\r\n", __LINE__);
                break;
            }
            rem -= parsed;
            g_modem.action++;
            //REF_PRINT("Rem %d Bytes\n", rem);
            // This should always be a TAG'ed request in case of FS access...
            if (r.type == ALP_OPCODE_TAG)
            {
                REF_PRINT("ACT %d: TAG[%d]\n", g_modem.action, r.meta.tag.id);

                id = r.meta.tag.id;

                while(rem>0)
                {
                    // Parse File Operation
                    parsed = alp_parse_chunk(&p, &r);
                    if (!parsed)
                    {
                        // Discard the payload in case of parsing error.
                        REF_PRINT("Parsing error line %d!\r\n", __LINE__);
                        break;
                    }
                    rem -= parsed;
                    g_modem.action++;
                    //REF_PRINT("ALP OP[%d]\n", r.type);
                    switch (r.type)
                    {
                        case ALP_OPCODE_F_RD_DATA:
                            REF_PRINT("ACT %d: F_RD_DATA[%d] @%d %d bytes\n", g_modem.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(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", g_modem.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(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", g_modem.action, r.meta.f_data.fid);
                            if (g_modem.cb->read_fprop)
                            {
                                g_modem.cb->read_fprop(r.meta.f_data.fid, id);
                            }
                            break;
                        case ALP_OPCODE_F_DELETE:
                            REF_PRINT("ACT %d: F_DELETE[%d]\n", g_modem.action, r.meta.f_data.fid);
                            if (g_modem.cb->remove)
                            {
                                g_modem.cb->remove(r.meta.f_data.fid, id);
                            }
                            break;
                        case ALP_OPCODE_F_FLUSH:
                            REF_PRINT("ACT %d: F_FLUSH[%d]\n", g_modem.action, r.meta.f_data.fid);
                            if (g_modem.cb->flush)
                            {
                                g_modem.cb->flush(r.meta.f_data.fid, id);
                            }
                            break;
                        default:
                            ASSERT(false, "ASSERT: Unsupported ALP File Operation: %d\n", r.type);
                            break;
                    }
                    //REF_PRINT("ALP Parsing done. rem %d\n", rem);
                }
                //REF_PRINT("ALP Packet done.\n");
            }
            else
            {
                // ... or a 'real' URC (or ISTATUS if enabled on modem)
                g_modem.istatus = (modem_ref_istatus_t){0};
                do
                {
                    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", g_modem.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", g_modem.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", g_modem.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", g_modem.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(payload, size);
                            }
                            rem = 0;
                            break;
                    }

                    int tem = alp_parse_chunk(&p, &r);
                    if (!tem)
                    {
                        break;
                    }
                    rem -= tem;
                } while (rem>=0);
            }
            break;
        case WC_FLOW_ALP_RESP:
            // This should always be a TAG'ed response as we tag our requests
            parsed = alp_parse_chunk(&p, &r);
            if (!parsed)
            {
                // Discard the payload in case of parsing error.
                REF_PRINT("Parsing error line %d!\r\n", __LINE__);
                break;
            }
            rem -= parsed;
            g_modem.action++;
            //REF_PRINT("Rem %d Bytes\n", rem);

            ASSERT((r.type == ALP_OPCODE_RSP_TAG), "ASSERT: expecting RESP_TAG got %d\n", r.type);
            REF_PRINT("ACT %d: TAG[%d]\n", g_modem.action, r.meta.tag.id);
            id  = r.meta.tag.id;
            eop = r.meta.tag.eop;
            ASSERT(g_modem.user[id].cb != NULL, "ASSERT: NULL Callback for ID %d\n", id);
            // Empty response
            if (rem <= 0)
            {
                // TODO: still no info on error...
                err = r.meta.tag.err ? ALP_ERR_UNKNOWN : ALP_ERR_NONE;

                if (ALP_ERR_NONE != err)
                {
                    REF_PRINT("NO INFO ON ERROR.\n");
                }
                else if (eop)
                {
                    REF_PRINT("EOP.\n");
                }

                g_modem.user[id].cb(eop, err, id);
                return;
            }

            // Actual response(s)
            while(rem>0)
            {
                parsed = alp_parse_chunk(&p, &r);
                if (!parsed)
                {
                    // Discard the payload in case of parsing error.
                    REF_PRINT("Parsing error line %d!\r\n", __LINE__);
                    break;
                }
                rem -= parsed;
                g_modem.action++;
                //REF_PRINT("Rem %d Bytes\n", rem);

                switch (r.type)
                {
                    case ALP_OPCODE_RSP_TAG:
                        REF_PRINT("ACT %d: RSP_TAG[%d]\n", g_modem.action, r.meta.tag.id);
                        break;
                    case ALP_OPCODE_RSP_F_DATA:
                        REF_PRINT("ACT %d: RSP_F_DATA[%d]\n", g_modem.action, r.meta.f_data.length);
                        ASSERT(g_modem.user[id].data != NULL, "ASSERT: NULL Data Buffer for RD on ID %d\n", id);
                        memcpy(g_modem.user[id].data, r.data, r.meta.f_data.length);
                        break;
                    case ALP_OPCODE_RSP_F_PROP:
                        REF_PRINT("ACT %d: RSP_F_PROP[%d]\n", g_modem.action, r.meta.f_data.length);
                        ASSERT(g_modem.user[id].data != NULL, "ASSERT: NULL Data Buffer for RD on ID %d\n", id);
                        memcpy(g_modem.user[id].data, r.data, r.meta.f_data.length);
                        break;
                    case ALP_OPCODE_RSP_STATUS:
                        REF_PRINT("ACT %d: RSP_STATUS[%d]\n", g_modem.action, r.meta.status.code);
                        err = r.meta.status.code;
                        break;
                    case ALP_OPCODE_RSP_ISTATUS:
                        REF_PRINT("ACT %d: RSP_ISTATUS[%d]\n", g_modem.action, r.meta.itf.length);
                        ASSERT(g_modem.user[id].istatus != NULL, "ASSERT: NULL ISTAT Buffer for RD on ID %d\n", id);
                        memcpy(g_modem.user[id].istatus, r.data, r.meta.itf.length);
                        break;
                    case ALP_OPCODE_RSP_EOPISTATUS:
                        REF_PRINT("ACT %d: RSP_EOPISTATUS[%02X]\n", g_modem.action, r.meta.istatus.itf);
                        err = r.meta.istatus.err ? ALP_ERR_ITF_START + r.meta.istatus.err : ALP_ERR_NONE;
                        break;
                    default:
                        ASSERT(false, "ASSERT: Unsupported ALP Response: %d\n", r.type);
                        break;
                }
            }
            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
            if (g_modem.user[id].cb)
            {
                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;
                uint8_t 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 uint32_t modem_ref_add_root_permission(uint8_t* p, uint8_t* root_key, uint8_t* action, uint32_t action_size)
{
    uint8_t 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 int 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++;
            //REF_PRINT("Get ID:%d/0x%08x\n", i, cb);
            return id;
        }
    }
    ASSERT(i < MAX_USER_NB, "ASSERT: out of users\n");
    return -1;
}

int modem_ref_free_id(int id)
{
    if (id < MAX_USER_NB)
    {
        g_modem.user[id].cb     = NULL;
        g_modem.user[id].data   = NULL;
        g_modem.user[id].istatus= NULL;
        //REF_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(WC_FLOW_SYS_RST, NULL, 0);
}

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

static void _modem_ref_alp(void* itf, void* istatus, alp_pub_payload_t* alp_user, int id)
{
    alp_pub_payload_t* alp = NULL;

    REF_PRINT("ALP %d Bytes\n", alp_user->len);

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

    // Forward on itf if any
    if (itf)
    {
        alp = alp_payload_forward(alp, itf);
    }

    // add nop to get istatus if any
    if (istatus)
    {
        // NOP action to get istats on ACKs
        alp = alp_payload_nop(alp);
    }

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

    // Send to modem
    serial_send(WC_FLOW_ALP_UNS, alp->d, alp->len);

    FREE(alp);
}

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

    REF_PRINT("RSP %d Bytes\n", alp_user->len);

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

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

    // Send to modem
    serial_send(WC_FLOW_ALP_UNS, alp->d, alp->len);

    FREE(alp);
}

static void _modem_ref_read(void* itf, void* istatus , uint8_t fid, void* data, uint32_t offset, uint32_t length, int id)
{
    alp_pub_payload_t* alp = NULL;

    // Expecting data and istatus
    g_modem.user[id].data    = (uint8_t*)data;
    g_modem.user[id].istatus = (uint8_t*)istatus;

    alp = alp_payload_f_rd_data(alp, fid, offset, length, false);

    _modem_ref_alp(itf, istatus, alp, id);
}

static void _modem_ref_write(void* itf, void* istatus, uint8_t fid, void* data, uint32_t offset, uint32_t length, int id)
{
    alp_pub_payload_t* alp = NULL;

    alp = alp_payload_f_wr_data(alp, fid, data, offset, length, false);

    _modem_ref_alp(itf, istatus, alp, id);
}

static void _modem_ref_flush(void* itf, void* istatus, uint8_t fid, int id)
{
    alp_pub_payload_t* alp = NULL;

    alp = alp_payload_f_flush(alp, fid, false);

    _modem_ref_alp(itf, istatus, alp, id);
}

void modem_ref_raw_alp(alp_pub_payload_t* alp_user, int id)
{
    _modem_ref_alp(NULL, NULL, alp_user, id);
}

void modem_ref_remote_raw_alp(void* itf, void* istatus, alp_pub_payload_t* alp_user, int id)
{
    _modem_ref_alp(itf, istatus, alp_user, id);
}

void modem_ref_read_file(uint8_t fid, void* data, uint32_t offset, uint32_t length, int id)
{
    _modem_ref_read(NULL, NULL, fid, data, offset, length, id);
}

void modem_ref_remote_read_file(void* itf, void* istatus , uint8_t fid, void* data, uint32_t offset, uint32_t length, int id)
{
    _modem_ref_read(itf, istatus, fid, data, offset, length, id);
}

void modem_ref_write_file(uint8_t fid, void* data, uint32_t offset, uint32_t length, int id)
{
    _modem_ref_write(NULL, NULL, fid, data, offset, length, id);
}

void modem_ref_remote_write_file(void* itf, void* istatus , uint8_t fid, void* data, uint32_t offset, uint32_t length, int id)
{
    _modem_ref_write(itf, istatus, fid, data, offset, length, id);
}

void modem_ref_flush_file(uint8_t fid, int id)
{
    _modem_ref_flush(NULL, NULL, fid, id);
}

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

    alp = alp_payload_f_flush(alp, fid, false);

    _modem_ref_alp(NULL, NULL, alp, id);
}

void modem_ref_enable_urc(uint8_t type, uint8_t ifid, uint8_t val, int id)
{
    alp_pub_payload_t* alp = NULL;

    alp = alp_payload_urcc_en(alp, type, ifid, val);

    _modem_ref_alp(NULL, NULL, alp, id);
}

void modem_ref_activate_itf(uint8_t type, uint8_t nb_dev, uint8_t ifid, uint8_t flags, uint8_t enable, int id)
{
    alp_pub_payload_t* alp = NULL;

    alp = alp_payload_activate_itf(alp, type, nb_dev, ifid, flags, enable);

    _modem_ref_alp(NULL, NULL, alp, id);
}

void modem_ref_respond(uint8_t action, int8_t status, int id)
{
    alp_pub_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(uint8_t fid, alp_file_header_t* hdr, int id)
{
    alp_pub_payload_t* alp = NULL;

    alp = alp_payload_rsp_fprop(alp, fid, hdr);

    _modem_ref_rsp(alp, false, id);
}

void modem_ref_respond_read(uint8_t fid, void* data, uint32_t offset, uint32_t length, int id)
{
    alp_pub_payload_t* alp = NULL;

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

    _modem_ref_rsp(alp, false, id);
}