//https://github.com/andersson/qrtr
/******************************************************************************
  @file    QrtrCM.c
  @brief   GobiNet driver.

  DESCRIPTION
  Connectivity Management Tool for USB network adapter of Quectel wireless cellular modules.

  INITIALIZATION AND SEQUENCING REQUIREMENTS
  None.

  ---------------------------------------------------------------------------
  Copyright (c) 2016 - 2023 Quectel Wireless Solution, Co., Ltd.  All Rights Reserved.
  Quectel Wireless Solution Proprietary and Confidential.
  ---------------------------------------------------------------------------
******************************************************************************/
#include <stdio.h>
#include <string.h>
#include <termios.h>
#include <stdio.h>
#include <ctype.h>
#include "QMIThread.h"

typedef struct {
    uint32_t service;
    uint32_t version;
    uint32_t instance;
    uint32_t node;
    uint32_t port;
} QrtrService;

#define QRTR_MAX (QMUX_TYPE_WDS_ADMIN + 1)
static QrtrService service_list[QRTR_MAX];
static int qmiclientId[QRTR_MAX];
static int get_client(UCHAR QMIType);
static uint32_t node_modem = 3; //IPQ ~ 3, QCM ~ 0

#ifdef USE_LINUX_MSM_IPC
#include <linux/msm_ipc.h>

struct xport_ipc_router_server_addr {
        uint32_t service;
        uint32_t instance;
        uint32_t node_id;
        uint32_t port_id;
};

union ctl_msg {
        uint32_t cmd;
                struct {
                uint32_t cmd;
                        uint32_t service;
                        uint32_t instance;
                uint32_t node_id;
                uint32_t port_id;
        } srv;
                struct {
                uint32_t cmd;
                uint32_t node_id;
                uint32_t port_id;
        } cli;
        };
#define CTL_CMD_NEW_SERVER          4
#define CTL_CMD_REMOVE_SERVER       5

#define VERSION_MASK 0xff
#define GET_VERSION(x) (x & 0xff)
#define GET_XPORT_SVC_INSTANCE(x) GET_VERSION(x)
#define GET_INSTANCE(x) ((x & 0xff00) >> 8)

static int msm_ipc_socket(const char *name)
{
    int sock;
    int flags;

    sock = socket(AF_MSM_IPC, SOCK_DGRAM, 0);
    if (sock < 0) {
        dbg_time("%s(%s) errno: %d (%s)\n", __func__, name, errno, strerror(errno));
        return -1;
    }

    fcntl(sock, F_SETFD, FD_CLOEXEC);
    flags = fcntl(sock, F_GETFL, 0);
    fcntl(sock, F_SETFL, flags | O_NONBLOCK);

    return sock;
}

static uint32_t xport_lookup
(
 int lookup_sock_fd,
 uint32_t service_id,
 uint32_t version
)
{
  uint32_t num_servers_found = 0;
  uint32_t num_entries_to_fill = 4;
  struct server_lookup_args *lookup_arg;
  int i;

  lookup_arg = (struct server_lookup_args *)malloc(sizeof(*lookup_arg)
      + (num_entries_to_fill * sizeof(struct msm_ipc_server_info)));
  if (!lookup_arg)
  {
    dbg_time("%s: Malloc failed\n", __func__);
    return 0;
  }

  lookup_arg->port_name.service = service_id;
  lookup_arg->port_name.instance = GET_XPORT_SVC_INSTANCE(version);
  lookup_arg->num_entries_in_array = num_entries_to_fill;
  lookup_arg->lookup_mask =  VERSION_MASK;
  lookup_arg->num_entries_found = 0;
  if (ioctl(lookup_sock_fd, IPC_ROUTER_IOCTL_LOOKUP_SERVER, lookup_arg) < 0)
  {
    dbg_time("%s: Lookup failed for %08x: %08x\n", __func__, service_id, version);
    free(lookup_arg);
    return 0;
  }

  dbg_time("%s: num_entries_found %d for type=%d instance=%d", __func__,
    lookup_arg->num_entries_found, service_id, version);
  num_servers_found = 0;
  for (i = 0; ((i < (int)num_entries_to_fill) && (i < lookup_arg->num_entries_found)); i++)
  {
     QrtrService service_info[1];

    if (lookup_arg->srv_info[i].node_id != node_modem)
        continue;
    num_servers_found++;

    service_info[0].service = lookup_arg->srv_info[i].service;
    service_info[0].version = GET_VERSION(lookup_arg->srv_info[i].instance);
    service_info[0].instance = GET_INSTANCE(lookup_arg->srv_info[i].instance);
    service_info[0].node = lookup_arg->srv_info[i].node_id;
    service_info[0].port = lookup_arg->srv_info[i].port_id;

    service_list[service_id] = service_info[0];
    qmiclientId[service_id] = get_client(service_id);
  }

  free(lookup_arg);
  return num_servers_found;
}

static int xport_send(int sock, uint32_t node, uint32_t port, const void *data, unsigned int sz)
{
    struct sockaddr_msm_ipc addr = {};
    int rc;

    addr.family = AF_MSM_IPC;
    addr.address.addrtype = MSM_IPC_ADDR_ID;
    addr.address.addr.port_addr.node_id = node;
    addr.address.addr.port_addr.port_id = port;

    rc = sendto(sock, data, sz, MSG_DONTWAIT, (void *)&addr, sizeof(addr));
    if (rc < 0) {
        dbg_time("xport_send errno: %d (%s)\n", errno, strerror(errno));
        return -1;
    }

    return 0;
}

static int xport_recv(int sock, void *data, unsigned int sz, uint32_t *node, uint32_t *port)
{
    struct sockaddr_msm_ipc addr = {};
    socklen_t addr_size = sizeof(struct sockaddr_msm_ipc);
    int rc;

    rc = recvfrom(sock, data, sz, MSG_DONTWAIT, (void *)&addr, &addr_size);
    if (rc < 0) {
        dbg_time("xport_recv errno: %d (%s)\n", errno, strerror(errno));
    }
    else if (addr.address.addrtype != MSM_IPC_ADDR_ID) {
        dbg_time("xport_recv addrtype is NOT MSM_IPC_ADDR_ID\n");
        rc = -1;
    }

    *node = addr.address.addr.port_addr.node_id;
    *port = addr.address.addr.port_addr.port_id;
    return rc;
}
#define qmi_recv xport_recv

static int xport_ctrl_init(void)
{
    int ctrl_sock;
    int rc;
    uint32_t instance = 1; //modem
    uint32_t version;

    ctrl_sock = msm_ipc_socket("ctrl_port");
    if (ctrl_sock == -1)
        return -1;

    rc = ioctl(ctrl_sock, IPC_ROUTER_IOCTL_GET_VERSION, &version);
    if (rc < 0) {
        dbg_time("%s: failed to get ipc version\n", __func__);
        goto init_close_ctrl_fd;
    }
    dbg_time("%s ipc_version = %d", __func__, version);

    rc = ioctl(ctrl_sock, IPC_ROUTER_IOCTL_BIND_CONTROL_PORT, NULL);
    if (rc < 0) {
        dbg_time("%s: failed to bind as control port\n", __func__);
        goto init_close_ctrl_fd;
    }

    //cat /sys/kernel/debug/msm_ipc_router/dump_servers
    rc = 0;
    rc += xport_lookup(ctrl_sock, QMUX_TYPE_WDS, instance);
    if (service_list[QMUX_TYPE_WDS].port) {
        qmiclientId[QMUX_TYPE_WDS_IPV6] = get_client(QMUX_TYPE_WDS);
    }
    rc += xport_lookup(ctrl_sock, QMUX_TYPE_NAS, instance);
    rc += xport_lookup(ctrl_sock, QMUX_TYPE_UIM, instance);
    rc += xport_lookup(ctrl_sock, QMUX_TYPE_DMS, instance);
    rc += xport_lookup(ctrl_sock, QMUX_TYPE_WDS_ADMIN, instance);

    if (rc == 0) {
        dbg_time("%s: failed to lookup qmi service\n", __func__);
        goto init_close_ctrl_fd;
    }

    return ctrl_sock;

init_close_ctrl_fd:
    close(ctrl_sock);
    return -1;
}

static void handle_ctrl_pkt(int sock) {
    union ctl_msg pkt;
    uint32_t type;
    int rc;

    rc = recvfrom(sock, &pkt, sizeof(pkt), 0, NULL, NULL);
    if (rc < 0)
        return;

    type = le32toh(pkt.cmd);
    if (CTL_CMD_NEW_SERVER == type || CTL_CMD_REMOVE_SERVER == type) {
        QrtrService s;

        s.service = le32toh(pkt.srv.service);
        s.version = le32toh(pkt.srv.instance) & 0xff;
        s.instance = le32toh(pkt.srv.instance) >> 8;
        s.node = le32toh(pkt.srv.node_id);
        s.port = le32toh(pkt.srv.port_id);

        if (debug_qmi)
            dbg_time ("[qrtr] %s  server on %u:%u -> service %u, version %u, instance %u",
                    CTL_CMD_NEW_SERVER == type ? "add" : "remove",
                     s.node, s.port, s.service, s.version, s.instance);

        if (CTL_CMD_NEW_SERVER == type) {
            if (s.service < QRTR_MAX) {
                service_list[s.service] = s;
            }
        }
        else if (CTL_CMD_REMOVE_SERVER == type) {
            if (s.service < QRTR_MAX) {
                memset(&service_list[s.service], 0, sizeof(QrtrService));
            }
        }
    }
}
#else
#include <linux/socket.h>
#include "qrtr.h"
#endif

static int qrtr_socket(void)
{
    struct sockaddr_qrtr sq;
    socklen_t sl = sizeof(sq);
    int sock;
    int rc;

    sock = socket(AF_QIPCRTR, SOCK_DGRAM, 0);
    if (sock < 0) {
        dbg_time("qrtr_socket errno: %d (%s)\n", errno, strerror(errno));
        return -1;
    }

    rc = getsockname(sock, (void *)&sq, &sl);
    if (rc || sq.sq_family != AF_QIPCRTR || sl != sizeof(sq)) {
        dbg_time("getsockname: %d (%s)\n", errno, strerror(errno));
        close(sock);
        return -1;
    }

    return sock;
}

static int qrtr_send(int sock, uint32_t node, uint32_t port, const void *data, unsigned int sz)
{
    struct sockaddr_qrtr sq = {};
    int rc;

    sq.sq_family = AF_QIPCRTR;
    sq.sq_node = node;
    sq.sq_port = port;

    rc = sendto(sock, data, sz, MSG_DONTWAIT, (void *)&sq, sizeof(sq));
    if (rc < 0) {
        dbg_time("sendto errno: %d (%s)\n", errno, strerror(errno));
        return -1;
    }

    return 0;
}

static int qrtr_recv(int sock, void *data, unsigned int sz, uint32_t *node, uint32_t *port)
{
    struct sockaddr_qrtr sq = {};
    socklen_t sl = sizeof(sq);
    int rc;

    rc = recvfrom(sock, data, sz, MSG_DONTWAIT, (void *)&sq, &sl);
    if (rc < 0) {
        dbg_time("qrtr_recv errno: %d (%s)\n", errno, strerror(errno));
    }

    *node  = sq.sq_node;
    *port  = sq.sq_port;
    return rc;
    }
#define qmi_recv qrtr_recv

static int qrtr_ctrl_init(void) {
    int sock;
    int rc;
    struct qrtr_ctrl_pkt pkt;
    struct sockaddr_qrtr sq;
    socklen_t sl = sizeof(sq);

    sock = qrtr_socket();
    if (sock == -1)
        return -1;

    memset(&pkt, 0, sizeof(pkt));
    pkt.cmd = htole32(QRTR_TYPE_NEW_LOOKUP);

    getsockname(sock, (void *)&sq, &sl);
    rc = qrtr_send(sock, sq.sq_node, QRTR_PORT_CTRL, &pkt, sizeof(pkt));
    if (rc == -1) {
        dbg_time("qrtr_send errno: %d (%s)\n", errno, strerror(errno));
        close(sock);
        return -1;
    }

    return sock;
}

static void handle_server_change(uint32_t type, struct qrtr_ctrl_pkt *ppkt) {
    struct qrtr_ctrl_pkt pkt = *ppkt;
        QrtrService s;

        s.service = le32toh(pkt.server.service);
        s.version = le32toh(pkt.server.instance) & 0xff;
        s.instance = le32toh(pkt.server.instance) >> 8;
        s.node = le32toh(pkt.server.node);
        s.port = le32toh(pkt.server.port);

        if (debug_qmi)
            dbg_time ("[qrtr] %s  server on %u:%u -> service %u, version %u, instance %u",
                    QRTR_TYPE_NEW_SERVER == type ? "add" : "remove",
                     s.node, s.port, s.service, s.version, s.instance);

        if (s.node != node_modem)
            return; //we only care modem

        if (QRTR_TYPE_NEW_SERVER == type) {
            if (s.service < QRTR_MAX) {
                service_list[s.service] = s;
            }
        }
        else if (QRTR_TYPE_DEL_SERVER == type) {
            if (s.service < QRTR_MAX) {
                memset(&service_list[s.service], 0, sizeof(QrtrService));
            }
        }
    }

static void handle_ctrl_pkt(int sock) {
    struct qrtr_ctrl_pkt pkt;
    struct sockaddr_qrtr sq;
    socklen_t sl = sizeof(sq);
    uint32_t type;
    int rc;

    rc = recvfrom(sock, &pkt, sizeof(pkt), 0, (void *)&sq, &sl);
    if (rc < 0)
        return;

    type = le32toh(pkt.cmd);
    if (debug_qmi)
        dbg_time("type %u, node %u, sq.port %x, len: %d", type, sq.sq_node, sq.sq_port, rc);

    if (sq.sq_port != QRTR_PORT_CTRL)
        return;

    if (QRTR_TYPE_NEW_SERVER == type || QRTR_TYPE_DEL_SERVER == type) {
        handle_server_change(type, &pkt);
    }
}

static int get_client(UCHAR QMIType) {
    int ClientId;
    QrtrService *s = &service_list[QMIType];

    if (!s ->service) {
        dbg_time("%s service: %d for QMIType: %d", __func__, s ->service, QMIType);
        return -ENODEV;
    }

#ifdef USE_LINUX_MSM_IPC
    ClientId = msm_ipc_socket("xport");
#else
    ClientId = qrtr_socket();
#endif
    if (ClientId == -1) {
        return 0;
    }

    switch (QMIType) {
        case QMUX_TYPE_WDS: dbg_time("Get clientWDS = %d", ClientId); break;
        case QMUX_TYPE_DMS: dbg_time("Get clientDMS = %d", ClientId); break;
        case QMUX_TYPE_NAS: dbg_time("Get clientNAS = %d", ClientId); break;
        case QMUX_TYPE_QOS: dbg_time("Get clientQOS = %d", ClientId); break;
        case QMUX_TYPE_WMS: dbg_time("Get clientWMS = %d", ClientId); break;
        case QMUX_TYPE_PDS: dbg_time("Get clientPDS = %d", ClientId); break;
        case QMUX_TYPE_UIM: dbg_time("Get clientUIM = %d", ClientId); break;
        case QMUX_TYPE_WDS_ADMIN: dbg_time("Get clientWDA = %d", ClientId);
        break;
        default: break;
    }

    return ClientId;
}

static void handle_alloc_client(PROFILE_T *profile) {
    int srv_list[] = {QMUX_TYPE_WDS, QMUX_TYPE_NAS, QMUX_TYPE_UIM, QMUX_TYPE_DMS, QMUX_TYPE_WDS_ADMIN};
    size_t i = 0, srv_ready = 0;
    static int report = -1;

    if (report != -1)
        return;

    for(i = 0; i < sizeof(srv_list)/sizeof(srv_list[0]); i++) {
        int srv = srv_list[i];

        if (service_list[srv].service)
            srv_ready++;
        else
            continue;

        if (qmiclientId[srv] == 0) {
            qmiclientId[srv] = get_client(srv);

            if (qmiclientId[srv] != 0) {
                if (srv == QMUX_TYPE_WDS) {
                    qmiclientId[QMUX_TYPE_WDS_IPV6] = get_client(QMUX_TYPE_WDS);
                }
                else if (srv == QMUX_TYPE_WDS_ADMIN) {
                     profile->wda_client = qmiclientId[QMUX_TYPE_WDS_ADMIN];
                }
            }
        }
    }

    if (srv_ready == sizeof(srv_list)/sizeof(srv_list[0])) {
        if (qmiclientId[QMUX_TYPE_WDS]) {
            qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_CONNECTED);
        } else {
            qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_DISCONNECTED);
        }
        report = 1;
    }
}

static int qmi_send(PQCQMIMSG pRequest) {
    uint8_t QMIType = pRequest->QMIHdr.QMIType;
    int sock;
    QrtrService *s = &service_list[QMIType == QMUX_TYPE_WDS_IPV6 ? QMUX_TYPE_WDS: QMIType];
    sock = qmiclientId[QMIType];

    pRequest->QMIHdr.ClientId = 0xaa;
    if (!s ->service || !sock) {
        dbg_time("%s service: %d, sock: %d for QMIType: %d", __func__, s ->service, sock, QMIType);
        return -ENODEV;
    }

#ifdef USE_LINUX_MSM_IPC
    return xport_send(sock, s->node, s->port, &pRequest->MUXMsg,
            le16_to_cpu(pRequest->QMIHdr.Length) + 1 - sizeof(QCQMI_HDR));
#else
    return qrtr_send(sock, s->node, s->port, &pRequest->MUXMsg,
            le16_to_cpu(pRequest->QMIHdr.Length) + 1 - sizeof(QCQMI_HDR));
#endif
}

static int qmi_deinit(void) {
    unsigned int i;

    for (i = 0; i < sizeof(qmiclientId)/sizeof(qmiclientId[0]); i++)
    {
        if (qmiclientId[i] != 0)
        {
            close(qmiclientId[i]);
            qmiclientId[i] = 0;
        }
    }

    return 0;
}

static void * qmi_read(void *pData) {
    PROFILE_T *profile = (PROFILE_T *)pData;
    int ctrl_sock;
    int wait_for_request_quit = 0;   

#ifdef USE_LINUX_MSM_IPC
    ctrl_sock = xport_ctrl_init();
    if (ctrl_sock != -1)
        qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_CONNECTED);
#else
    ctrl_sock = qrtr_ctrl_init();
#endif

    if (ctrl_sock == -1)
        goto _quit;

    while (1) {
        struct pollfd pollfds[16] = {{qmidevice_control_fd[1], POLLIN, 0}, {ctrl_sock, POLLIN, 0}};
        int ne, ret, nevents = 2;
        unsigned int i;

        for (i = 0; i < sizeof(qmiclientId)/sizeof(qmiclientId[0]); i++)
        {
            if (qmiclientId[i] != 0)
            {
                pollfds[nevents].fd = qmiclientId[i];
                pollfds[nevents].events = POLLIN;
                pollfds[nevents].revents = 0;
                nevents++;
            }
        }

        do {
            ret = poll(pollfds, nevents, wait_for_request_quit ? 1000 : -1);
         } while ((ret < 0) && (errno == EINTR));

	if (ret == 0 && wait_for_request_quit) {
            QmiThreadRecvQMI(NULL); //main thread may pending on QmiThreadSendQMI()
            continue;
	}

        if (ret <= 0) {
            dbg_time("%s poll=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno));
            break;
        }

        for (ne = 0; ne < nevents; ne++) {
            int fd = pollfds[ne].fd;
            short revents = pollfds[ne].revents;

            if (revents & (POLLERR | POLLHUP | POLLNVAL)) {
                dbg_time("%s poll err/hup/inval", __func__);
                dbg_time("epoll fd = %d, events = 0x%04x", fd, revents);
                if (fd == qmidevice_control_fd[1]) {
                } else {
                }
                if (revents & (POLLERR | POLLHUP | POLLNVAL))
                    goto _quit;
            }

            if ((revents & POLLIN) == 0)
                continue;

            if (fd == qmidevice_control_fd[1]) {
                int triger_event;
                if (read(fd, &triger_event, sizeof(triger_event)) == sizeof(triger_event)) {
                    //DBG("triger_event = 0x%x", triger_event);
                    switch (triger_event) {
                        case RIL_REQUEST_QUIT:
                            goto _quit;
                        break;
                        case SIG_EVENT_STOP:
                            wait_for_request_quit = 1;   
                        break;
                        default:
                        break;
                    }
                }
            }
            else  if (fd == ctrl_sock) {
                handle_ctrl_pkt(ctrl_sock);
                handle_alloc_client(profile);
            }
            else
            {
                PQCQMIMSG pResponse = (PQCQMIMSG)cm_recv_buf;
                int rc;
                uint32_t sq_node = 0;
                uint32_t sq_port = 0;

                rc = qmi_recv(fd, &pResponse->MUXMsg, sizeof(cm_recv_buf) - sizeof(QCQMI_HDR), &sq_node, &sq_port);
                if (debug_qmi)
                    dbg_time("fd %d, node %u, port %x, len: %d", fd, sq_node, sq_port, rc);

                if (rc <= 0)
                {
                    dbg_time("%s read=%d errno: %d (%s)",  __func__, rc, errno, strerror(errno));
                    break;
                }

                for (i = 0; i < sizeof(qmiclientId)/sizeof(qmiclientId[0]); i++)
                {
                    if (qmiclientId[i] == fd)
                    {
                        pResponse->QMIHdr.QMIType = i;

                        if (service_list[i].node != sq_node || service_list[i].port != sq_port) {
                            continue;
                        }
                    }
                }

                pResponse->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI;
                pResponse->QMIHdr.Length = cpu_to_le16(rc + sizeof(QCQMI_HDR)  - 1);
                pResponse->QMIHdr.CtlFlags = 0x00;
                pResponse->QMIHdr.ClientId = 0xaa;

                QmiThreadRecvQMI(pResponse);
            }
        }
    }

_quit:
    qmi_deinit();
    close(ctrl_sock);
    qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_DISCONNECTED);
    QmiThreadRecvQMI(NULL); //main thread may pending on QmiThreadSendQMI()
    dbg_time("%s exit", __func__);
    pthread_exit(NULL);
    return NULL;
}

const struct qmi_device_ops qrtr_qmidev_ops = {
	.deinit = qmi_deinit,
	.send = qmi_send,
	.read = qmi_read,
};

