/*
    Copyright 2023 Quectel Wireless Solutions Co.,Ltd

    Quectel hereby grants customers of Quectel a license to use, modify,
    distribute and publish the Software in binary form provided that
    customers shall have no right to reverse engineer, reverse assemble,
    decompile or reduce to source code form any portion of the Software. 
    Under no circumstances may customers modify, demonstrate, use, deliver 
    or disclose any portion of the Software in source code form.
*/

#include "QMIThread.h"
#include <inttypes.h>

#ifndef MIN
#define MIN(a, b)	((a) < (b)? (a): (b))
#endif

#define qmi_rsp_check_and_return() do { \
        if (err < 0 || pResponse == NULL) { \
            dbg_time("%s err = %d", __func__, err); \
            return err; \
        } \
        pMUXMsg = &pResponse->MUXMsg; \
        if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { \
            USHORT QMUXError = le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); \
            dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, \
                le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), QMUXError); \
            free(pResponse); \
            return QMUXError; \
        } \
} while(0)

#define qmi_rsp_check() do { \
        if (err < 0 || pResponse == NULL) { \
            dbg_time("%s err = %d", __func__, err); \
            return err; \
        } \
        pMUXMsg = &pResponse->MUXMsg; \
        if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { \
            USHORT QMUXError = le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); \
            dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, \
                le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), QMUXError); \
        } \
} while(0)

static uint32_t WdsConnectionIPv4Handle = 0;
static uint32_t WdsConnectionIPv6Handle = 0;
static int s_is_cdma = 0;
static int s_5g_type = WWAN_DATA_CLASS_NONE;
static int s_hdr_personality = 0; // 0x01-HRPD, 0x02-eHRPD
static char *qstrcpy(char *to, const char *from) { //no __strcpy_chk
	char *save = to;
	for (; (*to = *from) != '\0'; ++from, ++to);
	return(save);
}

static void uchar2char(char *dst_ptr, size_t dst_len, const UCHAR *src_ptr, size_t src_len) { 
    size_t copy = MIN(dst_len-1, src_len);

    if (copy)
        memcpy(dst_ptr, src_ptr, copy);
    dst_ptr[copy] = 0;
}

static int s_9x07 = 1;

typedef USHORT (*CUSTOMQMUX)(PQMUX_MSG pMUXMsg, void *arg);

// To retrieve the ith (Index) TLV
PQMI_TLV_HDR GetTLV (PQCQMUX_MSG_HDR pQMUXMsgHdr, int TLVType) {
    int TLVFind = 0;
    USHORT Length = le16_to_cpu(pQMUXMsgHdr->Length);
    PQMI_TLV_HDR pTLVHdr = (PQMI_TLV_HDR)(pQMUXMsgHdr + 1);

    while (Length >= sizeof(QMI_TLV_HDR)) {
        TLVFind++;
        if (TLVType > 0x1000) {
            if ((TLVFind + 0x1000) == TLVType)
                return pTLVHdr;
        } else  if (pTLVHdr->TLVType == TLVType) {
            return pTLVHdr;
        }

        Length -= (le16_to_cpu((pTLVHdr->TLVLength)) + sizeof(QMI_TLV_HDR));
        pTLVHdr = (PQMI_TLV_HDR)(((UCHAR *)pTLVHdr) + le16_to_cpu(pTLVHdr->TLVLength) + sizeof(QMI_TLV_HDR));
    }

   return NULL;
}

static USHORT GetQMUXTransactionId(void) {
    static int TransactionId = 0;
    if (++TransactionId > 0xFFFF)
        TransactionId = 1;
    return TransactionId;
}

static PQCQMIMSG ComposeQMUXMsg(UCHAR QMIType, USHORT Type, CUSTOMQMUX customQmuxMsgFunction, void *arg) {
    UCHAR QMIBuf[WDM_DEFAULT_BUFSIZE];
    PQCQMIMSG pRequest = (PQCQMIMSG)QMIBuf;
    int Length;

    memset(QMIBuf, 0x00, sizeof(QMIBuf));
    pRequest->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI;
    pRequest->QMIHdr.CtlFlags = 0x00;
    pRequest->QMIHdr.QMIType = QMIType;

    pRequest->MUXMsg.QMUXHdr.CtlFlags = QMUX_CTL_FLAG_SINGLE_MSG | QMUX_CTL_FLAG_TYPE_CMD;
    pRequest->MUXMsg.QMUXHdr.TransactionId = cpu_to_le16(GetQMUXTransactionId());
    pRequest->MUXMsg.QMUXMsgHdr.Type = cpu_to_le16(Type);
    if (customQmuxMsgFunction)
        pRequest->MUXMsg.QMUXMsgHdr.Length = cpu_to_le16(customQmuxMsgFunction(&pRequest->MUXMsg, arg) - sizeof(QCQMUX_MSG_HDR));
    else
        pRequest->MUXMsg.QMUXMsgHdr.Length = cpu_to_le16(0x0000);

    pRequest->QMIHdr.Length = cpu_to_le16(le16_to_cpu(pRequest->MUXMsg.QMUXMsgHdr.Length) + sizeof(QCQMUX_MSG_HDR) + sizeof(QCQMUX_HDR)
        + sizeof(QCQMI_HDR) - 1);
    Length = le16_to_cpu(pRequest->QMIHdr.Length) + 1;

    pRequest = (PQCQMIMSG)malloc(Length);
    if (pRequest == NULL) {
        dbg_time("%s fail to malloc", __func__);
    } else {
        memcpy(pRequest, QMIBuf, Length);
    }

    return pRequest;
}

#if 0
static USHORT NasSetEventReportReq(PQMUX_MSG pMUXMsg, void *arg) {
    pMUXMsg->SetEventReportReq.TLVType = 0x10;
    pMUXMsg->SetEventReportReq.TLVLength = 0x04;
    pMUXMsg->SetEventReportReq.ReportSigStrength = 0x00;
    pMUXMsg->SetEventReportReq.NumTresholds = 2;
    pMUXMsg->SetEventReportReq.TresholdList[0] = -113;
    pMUXMsg->SetEventReportReq.TresholdList[1] = -50;
    return sizeof(QMINAS_SET_EVENT_REPORT_REQ_MSG);
}

static USHORT WdsSetEventReportReq(PQMUX_MSG pMUXMsg, void *arg) {
    pMUXMsg->EventReportReq.TLVType = 0x10;          // 0x10 -- current channel rate indicator
    pMUXMsg->EventReportReq.TLVLength = 0x0001;        // 1
    pMUXMsg->EventReportReq.Mode = 0x00;             // 0-do not report; 1-report when rate changes

    pMUXMsg->EventReportReq.TLV2Type = 0x11;         // 0x11
    pMUXMsg->EventReportReq.TLV2Length = 0x0005;       // 5
    pMUXMsg->EventReportReq.StatsPeriod = 0x00;      // seconds between reports; 0-do not report
    pMUXMsg->EventReportReq.StatsMask = 0x000000ff;        //

    pMUXMsg->EventReportReq.TLV3Type = 0x12;          // 0x12 -- current data bearer indicator
    pMUXMsg->EventReportReq.TLV3Length = 0x0001;        // 1
    pMUXMsg->EventReportReq.Mode3 = 0x01;             // 0-do not report; 1-report when changes

    pMUXMsg->EventReportReq.TLV4Type = 0x13;          // 0x13 -- dormancy status indicator
    pMUXMsg->EventReportReq.TLV4Length = 0x0001;        // 1
    pMUXMsg->EventReportReq.DormancyStatus = 0x00;    // 0-do not report; 1-report when changes
    return sizeof(QMIWDS_SET_EVENT_REPORT_REQ_MSG);
}

static USHORT DmsSetEventReportReq(PQMUX_MSG pMUXMsg) {
    PPIN_STATUS pPinState = (PPIN_STATUS)(&pMUXMsg->DmsSetEventReportReq + 1);
    PUIM_STATE pUimState = (PUIM_STATE)(pPinState + 1);
    // Pin State
    pPinState->TLVType = 0x12;
    pPinState->TLVLength = 0x01;
    pPinState->ReportPinState = 0x01;
    // UIM State
    pUimState->TLVType = 0x15;
    pUimState->TLVLength = 0x01;
    pUimState->UIMState = 0x01;
    return sizeof(QMIDMS_SET_EVENT_REPORT_REQ_MSG) + sizeof(PIN_STATUS) + sizeof(UIM_STATE);
}
#endif

static USHORT WdsStartNwInterfaceReq(PQMUX_MSG pMUXMsg, void *arg) {
    PQMIWDS_TECHNOLOGY_PREFERECE pTechPref;
    PQMIWDS_AUTH_PREFERENCE pAuthPref;
    PQMIWDS_USERNAME pUserName;
    PQMIWDS_PD pPd;
    PQMIWDS_APNNAME pApnName;
    PQMIWDS_IP_FAMILY_TLV pIpFamily;
    USHORT TLVLength = 0;
    UCHAR *pTLV;
    PROFILE_T *profile = (PROFILE_T *)arg;
    const char *profile_user = profile->user;
    const char *profile_pd = profile->pd;
    int profile_auth = profile->auth;

    if (s_is_cdma && (profile_user == NULL || profile_user[0] == '\0') && (profile_pd == NULL || profile_pd[0] == '\0')) {
        profile_user = "ctnet@mycdma.cn";
        profile_pd = "vnet.mobi";
        profile_auth = 2; //chap
    }

    pTLV = (UCHAR *)(&pMUXMsg->StartNwInterfaceReq + 1);
    pMUXMsg->StartNwInterfaceReq.Length = 0;

    // Set technology Preferece
    pTechPref = (PQMIWDS_TECHNOLOGY_PREFERECE)(pTLV + TLVLength);
    pTechPref->TLVType = 0x30;
    pTechPref->TLVLength = cpu_to_le16(0x01);
    if (s_is_cdma == 0)
        pTechPref->TechPreference = 0x01;
    else
        pTechPref->TechPreference = 0x02;
    TLVLength +=(le16_to_cpu(pTechPref->TLVLength) + sizeof(QCQMICTL_TLV_HDR));

    // Set APN Name
    if (profile->apn && !s_is_cdma) { //cdma no apn
        pApnName = (PQMIWDS_APNNAME)(pTLV + TLVLength);
        pApnName->TLVType = 0x14;
        pApnName->TLVLength = cpu_to_le16(strlen(profile->apn));
        qstrcpy((char *)&pApnName->ApnName, profile->apn);
        TLVLength +=(le16_to_cpu(pApnName->TLVLength) + sizeof(QCQMICTL_TLV_HDR));
    }

    // Set User Name
    if (profile_user) {
        pUserName = (PQMIWDS_USERNAME)(pTLV + TLVLength);
        pUserName->TLVType = 0x17;
        pUserName->TLVLength = cpu_to_le16(strlen(profile_user));
        qstrcpy((char *)&pUserName->UserName, profile_user);
        TLVLength += (le16_to_cpu(pUserName->TLVLength) + sizeof(QCQMICTL_TLV_HDR));
    }

    // Set Password
    if (profile_pd) {
        pPd = (PQMIWDS_PD)(pTLV + TLVLength);
        pPd->TLVType = 0x18;
        pPd->TLVLength = cpu_to_le16(strlen(profile_pd));
        qstrcpy((char *)&pPd->Pd, profile_pd);
	TLVLength += (le16_to_cpu(pPd->TLVLength) + sizeof(QCQMICTL_TLV_HDR));
    }

    // Set Auth Protocol
    if (profile_user && profile_pd) {
        pAuthPref = (PQMIWDS_AUTH_PREFERENCE)(pTLV + TLVLength);
        pAuthPref->TLVType = 0x16;
        pAuthPref->TLVLength = cpu_to_le16(0x01);
        pAuthPref->AuthPreference = profile_auth; // 0 ~ None, 1 ~ Pap, 2 ~ Chap, 3 ~ MsChapV2
        TLVLength += (le16_to_cpu(pAuthPref->TLVLength) + sizeof(QCQMICTL_TLV_HDR));
    }

    // Add IP Family Preference
    pIpFamily = (PQMIWDS_IP_FAMILY_TLV)(pTLV + TLVLength);
    pIpFamily->TLVType = 0x19;
    pIpFamily->TLVLength = cpu_to_le16(0x01);
    pIpFamily->IpFamily = profile->curIpFamily;
    TLVLength += (le16_to_cpu(pIpFamily->TLVLength) + sizeof(QCQMICTL_TLV_HDR));

    if(profile->usb_dev.idProduct != 0x0316){
    
      //Set Profile Index
      if (profile->profile_index && !s_is_cdma) { //cdma only support one pdp, so no need to set profile index
          PQMIWDS_PROFILE_IDENTIFIER pProfileIndex = (PQMIWDS_PROFILE_IDENTIFIER)(pTLV + TLVLength);
          pProfileIndex->TLVLength = cpu_to_le16(0x01);
          pProfileIndex->TLVType = 0x31;
          pProfileIndex->ProfileIndex = profile->profile_index;
          if (s_is_cdma && s_hdr_personality == 0x02) {
              pProfileIndex->TLVType = 0x32; //profile_index_3gpp2
              pProfileIndex->ProfileIndex = 101;
          }
          TLVLength += (le16_to_cpu(pProfileIndex->TLVLength) + sizeof(QCQMICTL_TLV_HDR));
      }
    }
    else if(profile->usb_dev.idProduct == 0x0316){
   
      if(profile->bring_up_by_apn_name)
      {
          PQMIWDS_BRINGUP_APNNAME pBring_up_by_apn_name = (PQMIWDS_BRINGUP_APNNAME)(pTLV + TLVLength);        
          pBring_up_by_apn_name->TLVLength = cpu_to_le16(0x01);  
          pBring_up_by_apn_name->TLVType = 0x3C;
          pBring_up_by_apn_name->bring_up_by_apn_name  =   profile->bring_up_by_apn_name;
          TLVLength += (le16_to_cpu(pBring_up_by_apn_name->TLVLength) + sizeof(QCQMICTL_TLV_HDR));
      }

      if(profile->bring_up_by_apn_type)
      {
          PQMIWDS_BRINGUP_APNTYPE pBring_up_by_apn_type = (PQMIWDS_BRINGUP_APNTYPE)(pTLV + TLVLength); 
          pBring_up_by_apn_type->TLVLength = cpu_to_le16(0x01);  
          pBring_up_by_apn_type->TLVType = 0x3D;
          pBring_up_by_apn_type->bring_up_by_apn_type  =   profile->bring_up_by_apn_type;
          TLVLength += (le16_to_cpu(pBring_up_by_apn_type->TLVLength) + sizeof(QCQMICTL_TLV_HDR));
      }
    
      if(profile->apn_type)
      {
          PQMIWDS_APN_TYPE pApn_type = (PQMIWDS_APN_TYPE)(pTLV + TLVLength); 
          pApn_type->TLVLength = cpu_to_le32(0x04);  
          pApn_type->TLVType = 0x38;
          pApn_type->apn_type = cpu_to_le32(profile->apn_type);
          TLVLength += (le16_to_cpu(pApn_type->TLVLength) + sizeof(QCQMICTL_TLV_HDR));
      }  
   }
      return sizeof(QMIWDS_START_NETWORK_INTERFACE_REQ_MSG) + TLVLength;
}

static USHORT WdsStopNwInterfaceReq(PQMUX_MSG pMUXMsg, void *arg) {
    pMUXMsg->StopNwInterfaceReq.TLVType = 0x01;
    pMUXMsg->StopNwInterfaceReq.TLVLength = cpu_to_le16(0x04);
    if (*((int *)arg) == IpFamilyV4)
        pMUXMsg->StopNwInterfaceReq.Handle =  cpu_to_le32(WdsConnectionIPv4Handle);
    else
        pMUXMsg->StopNwInterfaceReq.Handle =  cpu_to_le32(WdsConnectionIPv6Handle);
    return sizeof(QMIWDS_STOP_NETWORK_INTERFACE_REQ_MSG);
}

static USHORT WdsSetClientIPFamilyPref(PQMUX_MSG pMUXMsg, void *arg) {
    pMUXMsg->SetClientIpFamilyPrefReq.TLVType = 0x01;
    pMUXMsg->SetClientIpFamilyPrefReq.TLVLength = cpu_to_le16(0x01);
    pMUXMsg->SetClientIpFamilyPrefReq.IpPreference = *((UCHAR *)arg);
    return sizeof(QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ_MSG);
}

static USHORT WdsSetAutoConnect(PQMUX_MSG pMUXMsg, void *arg) {
    pMUXMsg->SetAutoConnectReq.TLVType = 0x01;
    pMUXMsg->SetAutoConnectReq.TLVLength = cpu_to_le16(0x01);
    pMUXMsg->SetAutoConnectReq.autoconnect_setting = *((UCHAR *)arg);
    return sizeof(QMIWDS_SET_AUTO_CONNECT_REQ_MSG);
}

enum peripheral_ep_type {
	DATA_EP_TYPE_RESERVED	= 0x0,
	DATA_EP_TYPE_HSIC	= 0x1,
	DATA_EP_TYPE_HSUSB	= 0x2,
	DATA_EP_TYPE_PCIE	= 0x3,
	DATA_EP_TYPE_EMBEDDED	= 0x4,
	DATA_EP_TYPE_BAM_DMUX	= 0x5,
};
        
static USHORT WdsSetQMUXBindMuxDataPort(PQMUX_MSG pMUXMsg, void *arg) {
    QMAP_SETTING *qmap_settings = (QMAP_SETTING *)arg;

    pMUXMsg->BindMuxDataPortReq.TLVType = 0x10;
    pMUXMsg->BindMuxDataPortReq.TLVLength = cpu_to_le16(0x08);
    pMUXMsg->BindMuxDataPortReq.ep_type = cpu_to_le32(qmap_settings->ep_type);
    pMUXMsg->BindMuxDataPortReq.iface_id = cpu_to_le32(qmap_settings->iface_id);
    pMUXMsg->BindMuxDataPortReq.TLV2Type = 0x11;
    pMUXMsg->BindMuxDataPortReq.TLV2Length = cpu_to_le16(0x01);
    pMUXMsg->BindMuxDataPortReq.MuxId = qmap_settings->MuxId;
    pMUXMsg->BindMuxDataPortReq.TLV3Type = 0x13;
    pMUXMsg->BindMuxDataPortReq.TLV3Length = cpu_to_le16(0x04);
    pMUXMsg->BindMuxDataPortReq.client_type = cpu_to_le32(1); //WDS_CLIENT_TYPE_TETHERED
    
    return sizeof(QMIWDS_BIND_MUX_DATA_PORT_REQ_MSG);
}

static int qmap_version = 0x05;
static USHORT WdaSetDataFormat(PQMUX_MSG pMUXMsg, void *arg) {
    QMAP_SETTING *qmap_settings = (QMAP_SETTING *)arg;

    if (qmap_settings->rx_urb_size == 0) {
        PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS pWdsAdminQosTlv;
        PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV linkProto;
        PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV dlTlp;

        pWdsAdminQosTlv = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS)(&pMUXMsg->QMUXMsgHdr + 1);
        pWdsAdminQosTlv->TLVType = 0x10;
        pWdsAdminQosTlv->TLVLength = cpu_to_le16(0x0001);
        pWdsAdminQosTlv->QOSSetting = 0; /* no-QOS header */

        linkProto = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)(pWdsAdminQosTlv + 1);
        linkProto->TLVType = 0x11;
        linkProto->TLVLength = cpu_to_le16(4);
        linkProto->Value = cpu_to_le32(0x01);     /* Set Ethernet  mode */

        dlTlp = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)(linkProto + 1);;
        dlTlp->TLVType = 0x13;
        dlTlp->TLVLength = cpu_to_le16(4);
        dlTlp->Value = cpu_to_le32(0x00);

        if (sizeof(*linkProto) != 7 )
            dbg_time("%s sizeof(*linkProto) = %zu, is not 7!", __func__, sizeof(*linkProto) );

        return sizeof(QCQMUX_MSG_HDR) + sizeof(*pWdsAdminQosTlv) + sizeof(*linkProto) + sizeof(*dlTlp);
    } 
    else {
    //Indicates whether the Quality of Service(QOS) data format is used by the client.
        pMUXMsg->SetDataFormatReq.QosDataFormatTlv.TLVType = 0x10;
        pMUXMsg->SetDataFormatReq.QosDataFormatTlv.TLVLength = cpu_to_le16(0x0001);
        pMUXMsg->SetDataFormatReq.QosDataFormatTlv.QOSSetting = 0; /* no-QOS header */
		
    //Underlying Link Layer Protocol
        pMUXMsg->SetDataFormatReq.UnderlyingLinkLayerProtocolTlv.TLVType = 0x11; 
        pMUXMsg->SetDataFormatReq.UnderlyingLinkLayerProtocolTlv.TLVLength = cpu_to_le16(4);
        pMUXMsg->SetDataFormatReq.UnderlyingLinkLayerProtocolTlv.Value = cpu_to_le32(0x02);     /* Set IP  mode */

    //Uplink (UL) data aggregation protocol to be used for uplink data transfer.
        pMUXMsg->SetDataFormatReq.UplinkDataAggregationProtocolTlv.TLVType = 0x12; 
        pMUXMsg->SetDataFormatReq.UplinkDataAggregationProtocolTlv.TLVLength = cpu_to_le16(4);
        pMUXMsg->SetDataFormatReq.UplinkDataAggregationProtocolTlv.Value = cpu_to_le32(qmap_version); //UL QMAP is enabled
        
    //Downlink (DL) data aggregation protocol to be used for downlink data transfer
        pMUXMsg->SetDataFormatReq.DownlinkDataAggregationProtocolTlv.TLVType = 0x13; 
        pMUXMsg->SetDataFormatReq.DownlinkDataAggregationProtocolTlv.TLVLength = cpu_to_le16(4);
        pMUXMsg->SetDataFormatReq.DownlinkDataAggregationProtocolTlv.Value = cpu_to_le32(qmap_version); //DL QMAP is enabled
        
    //Maximum number of datagrams in a single aggregated packet on downlink
        pMUXMsg->SetDataFormatReq.DownlinkDataAggregationMaxDatagramsTlv.TLVType = 0x15; 
        pMUXMsg->SetDataFormatReq.DownlinkDataAggregationMaxDatagramsTlv.TLVLength = cpu_to_le16(4);
        pMUXMsg->SetDataFormatReq.DownlinkDataAggregationMaxDatagramsTlv.Value = cpu_to_le32(qmap_settings->rx_urb_size/512);
		
    //Maximum size in bytes of a single aggregated packet allowed on downlink
        pMUXMsg->SetDataFormatReq.DownlinkDataAggregationMaxSizeTlv.TLVType = 0x16; 
        pMUXMsg->SetDataFormatReq.DownlinkDataAggregationMaxSizeTlv.TLVLength = cpu_to_le16(4);
        pMUXMsg->SetDataFormatReq.DownlinkDataAggregationMaxSizeTlv.Value = cpu_to_le32(qmap_settings->rx_urb_size);

    //Peripheral End Point ID
        pMUXMsg->SetDataFormatReq.epTlv.TLVType = 0x17; 
        pMUXMsg->SetDataFormatReq.epTlv.TLVLength = cpu_to_le16(8);
        pMUXMsg->SetDataFormatReq.epTlv.ep_type = cpu_to_le32(qmap_settings->ep_type);
        pMUXMsg->SetDataFormatReq.epTlv.iface_id = cpu_to_le32(qmap_settings->iface_id); 

#ifdef QUECTEL_UL_DATA_AGG
        if (!qmap_settings->ul_data_aggregation_max_datagrams) {
            return ((size_t)&((QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG *)0)->DlMinimumPassingTlv);
        }
        
     //Maximum number of datagrams in a single aggregated packet on uplink
        pMUXMsg->SetDataFormatReq.DlMinimumPassingTlv.TLVType = 0x19; 
        pMUXMsg->SetDataFormatReq.DlMinimumPassingTlv.TLVLength = cpu_to_le16(4);
        pMUXMsg->SetDataFormatReq.DlMinimumPassingTlv.Value = cpu_to_le32(qmap_settings->dl_minimum_padding);
		
     //Maximum number of datagrams in a single aggregated packet on uplink
        pMUXMsg->SetDataFormatReq.UplinkDataAggregationMaxDatagramsTlv.TLVType = 0x1B; 
        pMUXMsg->SetDataFormatReq.UplinkDataAggregationMaxDatagramsTlv.TLVLength = cpu_to_le16(4);
        pMUXMsg->SetDataFormatReq.UplinkDataAggregationMaxDatagramsTlv.Value = cpu_to_le32(qmap_settings->ul_data_aggregation_max_datagrams);
		
    //Maximum size in bytes of a single aggregated packet allowed on downlink
        pMUXMsg->SetDataFormatReq.UplinkDataAggregationMaxSizeTlv.TLVType = 0x1C; 
        pMUXMsg->SetDataFormatReq.UplinkDataAggregationMaxSizeTlv.TLVLength = cpu_to_le16(4);
        pMUXMsg->SetDataFormatReq.UplinkDataAggregationMaxSizeTlv.Value = cpu_to_le32(qmap_settings->ul_data_aggregation_max_size);
#endif

        return sizeof(QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG);
    }
}

#ifdef CONFIG_SIM
static USHORT DmsUIMVerifyPinReqSend(PQMUX_MSG pMUXMsg, void *arg) {
    pMUXMsg->UIMVerifyPinReq.TLVType = 0x01;
    pMUXMsg->UIMVerifyPinReq.PINID = 0x01; //Pin1, not Puk
    pMUXMsg->UIMVerifyPinReq.PINLen = strlen((const char *)arg);
    qstrcpy((char *)&pMUXMsg->UIMVerifyPinReq.PINValue, ((const char *)arg));
    pMUXMsg->UIMVerifyPinReq.TLVLength = cpu_to_le16(2 + strlen((const char *)arg));
    return sizeof(QMIDMS_UIM_VERIFY_PIN_REQ_MSG) + (strlen((const char *)arg) - 1);
}

static USHORT UimVerifyPinReqSend(PQMUX_MSG pMUXMsg, void *arg)
{
    pMUXMsg->UIMUIMVerifyPinReq.TLVType = 0x01;
    pMUXMsg->UIMUIMVerifyPinReq.TLVLength = cpu_to_le16(0x02);
    pMUXMsg->UIMUIMVerifyPinReq.Session_Type = 0x00;
    pMUXMsg->UIMUIMVerifyPinReq.Aid_Len = 0x00;
    pMUXMsg->UIMUIMVerifyPinReq.TLV2Type = 0x02;
    pMUXMsg->UIMUIMVerifyPinReq.TLV2Length = cpu_to_le16(2 + strlen((const char *)arg));
    pMUXMsg->UIMUIMVerifyPinReq.PINID = 0x01;  //Pin1, not Puk
    pMUXMsg->UIMUIMVerifyPinReq.PINLen= strlen((const char *)arg);
    qstrcpy((char *)&pMUXMsg->UIMUIMVerifyPinReq.PINValue, ((const char *)arg));
    return sizeof(QMIUIM_VERIFY_PIN_REQ_MSG) + (strlen((const char *)arg) - 1);
}

#ifdef CONFIG_IMSI_ICCID
static USHORT UimReadTransparentIMSIReqSend(PQMUX_MSG pMUXMsg, void *arg) {
    PREAD_TRANSPARENT_TLV pReadTransparent;

    pMUXMsg->UIMUIMReadTransparentReq.TLVType =  0x01;
    pMUXMsg->UIMUIMReadTransparentReq.TLVLength = cpu_to_le16(0x02);
    if (!strcmp((char *)arg, "EF_ICCID")) {
        pMUXMsg->UIMUIMReadTransparentReq.Session_Type = 0x06;
        pMUXMsg->UIMUIMReadTransparentReq.Aid_Len = 0x00;

        pMUXMsg->UIMUIMReadTransparentReq.TLV2Type = 0x02;
        pMUXMsg->UIMUIMReadTransparentReq.file_id = cpu_to_le16(0x2FE2);
        pMUXMsg->UIMUIMReadTransparentReq.path_len = 0x02;
        pMUXMsg->UIMUIMReadTransparentReq.path[0] = 0x00;
        pMUXMsg->UIMUIMReadTransparentReq.path[1] = 0x3F;
    }
    else if(!strcmp((char *)arg, "EF_IMSI")) {
        pMUXMsg->UIMUIMReadTransparentReq.Session_Type = 0x00;
        pMUXMsg->UIMUIMReadTransparentReq.Aid_Len = 0x00;

        pMUXMsg->UIMUIMReadTransparentReq.TLV2Type = 0x02;
        pMUXMsg->UIMUIMReadTransparentReq.file_id = cpu_to_le16(0x6F07);
        pMUXMsg->UIMUIMReadTransparentReq.path_len = 0x04;
        pMUXMsg->UIMUIMReadTransparentReq.path[0] = 0x00;
        pMUXMsg->UIMUIMReadTransparentReq.path[1] = 0x3F;
        pMUXMsg->UIMUIMReadTransparentReq.path[2] = 0xFF;
        pMUXMsg->UIMUIMReadTransparentReq.path[3] = 0x7F;
    }

    pMUXMsg->UIMUIMReadTransparentReq.TLV2Length = cpu_to_le16(3 +  pMUXMsg->UIMUIMReadTransparentReq.path_len);

    pReadTransparent = (PREAD_TRANSPARENT_TLV)(&pMUXMsg->UIMUIMReadTransparentReq.path[pMUXMsg->UIMUIMReadTransparentReq.path_len]);
    pReadTransparent->TLVType = 0x03;
    pReadTransparent->TLVLength = cpu_to_le16(0x04);
    pReadTransparent->Offset = cpu_to_le16(0x00);
    pReadTransparent->Length = cpu_to_le16(0x00);

    return (sizeof(QMIUIM_READ_TRANSPARENT_REQ_MSG) + pMUXMsg->UIMUIMReadTransparentReq.path_len + sizeof(READ_TRANSPARENT_TLV));
}
#endif
#endif

#ifdef CONFIG_APN

static USHORT WdsGetProfileListReqSend(PQMUX_MSG pMUXMsg, void *arg) {
    (void)(arg);
    pMUXMsg->GetProfileListReq.Length = cpu_to_le16(sizeof(QMIWDS_GET_PROFILE_LIST_REQ_MSG) - 4);
    return sizeof(QMIWDS_GET_PROFILE_LIST_REQ_MSG);
}

static USHORT WdsCreateProfileSettingsReqSend(PQMUX_MSG pMUXMsg, void *arg) {
    PROFILE_T *profile = (PROFILE_T *)arg;
    pMUXMsg->CreatetProfileSettingsReq.Length = cpu_to_le16(sizeof(QMIWDS_CREATE_PROFILE_SETTINGS_REQ_MSG) - 4);
    pMUXMsg->CreatetProfileSettingsReq.TLVType = 0x01;
    pMUXMsg->CreatetProfileSettingsReq.TLVLength = cpu_to_le16(0x01);
    pMUXMsg->CreatetProfileSettingsReq.ProfileType = 0x00; // 0 ~ 3GPP, 1 ~ 3GPP2
    pMUXMsg->CreatetProfileSettingsReq.TLV2Type = 0x25;
    pMUXMsg->CreatetProfileSettingsReq.TLV2Length = cpu_to_le16(0x01);
    pMUXMsg->CreatetProfileSettingsReq.pdp_context = profile->pdp; // 0 ~ 3GPP, 1 ~ 3GPP2
    return sizeof(QMIWDS_CREATE_PROFILE_SETTINGS_REQ_MSG);
}

static USHORT WdsGetProfileSettingsReqSend(PQMUX_MSG pMUXMsg, void *arg) {
    PROFILE_T *profile = (PROFILE_T *)arg;
    pMUXMsg->GetProfileSettingsReq.Length = cpu_to_le16(sizeof(QMIWDS_GET_PROFILE_SETTINGS_REQ_MSG) - 4);
    pMUXMsg->GetProfileSettingsReq.TLVType = 0x01;
    pMUXMsg->GetProfileSettingsReq.TLVLength = cpu_to_le16(0x02);
    pMUXMsg->GetProfileSettingsReq.ProfileType = 0x00; // 0 ~ 3GPP, 1 ~ 3GPP2
    pMUXMsg->GetProfileSettingsReq.ProfileIndex = profile->profile_index;
    return sizeof(QMIWDS_GET_PROFILE_SETTINGS_REQ_MSG);
}

static USHORT WdsModifyProfileSettingsReq(PQMUX_MSG pMUXMsg, void *arg) {
    USHORT TLVLength = 0;
    UCHAR *pTLV;
    PROFILE_T *profile = (PROFILE_T *)arg;
    PQMIWDS_PDPTYPE pPdpType;

    pMUXMsg->ModifyProfileSettingsReq.Length = cpu_to_le16(sizeof(QMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG) - 4);
    pMUXMsg->ModifyProfileSettingsReq.TLVType = 0x01;
    pMUXMsg->ModifyProfileSettingsReq.TLVLength = cpu_to_le16(0x02);
    pMUXMsg->ModifyProfileSettingsReq.ProfileType = 0x00; // 0 ~ 3GPP, 1 ~ 3GPP2
    pMUXMsg->ModifyProfileSettingsReq.ProfileIndex = profile->profile_index;

    pTLV = (UCHAR *)(&pMUXMsg->ModifyProfileSettingsReq + 1);

    pPdpType = (PQMIWDS_PDPTYPE)(pTLV + TLVLength);
    pPdpType->TLVType = 0x11;
    pPdpType->TLVLength = cpu_to_le16(0x01);
    pPdpType->PdpType = profile->iptype;
    TLVLength +=(le16_to_cpu(pPdpType->TLVLength) + sizeof(QCQMICTL_TLV_HDR));

    // Set APN Name
    if (profile->apn) {
        PQMIWDS_APNNAME pApnName = (PQMIWDS_APNNAME)(pTLV + TLVLength);
        pApnName->TLVType = 0x14;
        pApnName->TLVLength = cpu_to_le16(strlen(profile->apn));
        qstrcpy((char *)&pApnName->ApnName, profile->apn);
        TLVLength +=(le16_to_cpu(pApnName->TLVLength) + sizeof(QCQMICTL_TLV_HDR));
    }

    // Set User Name
    if (profile->user) {
        PQMIWDS_USERNAME pUserName = (PQMIWDS_USERNAME)(pTLV + TLVLength);
        pUserName->TLVType = 0x1B;
        pUserName->TLVLength = cpu_to_le16(strlen(profile->user));
        qstrcpy((char *)&pUserName->UserName, profile->user);
        TLVLength += (le16_to_cpu(pUserName->TLVLength) + sizeof(QCQMICTL_TLV_HDR));
    }

    // Set Password
    if (profile->pd) {
        PQMIWDS_PD pPd = (PQMIWDS_PD)(pTLV + TLVLength);
        pPd->TLVType = 0x1C;
        pPd->TLVLength = cpu_to_le16(strlen(profile->pd));
        qstrcpy((char *)&pPd->Pd, profile->pd);
        TLVLength +=(le16_to_cpu(pPd->TLVLength) + sizeof(QCQMICTL_TLV_HDR));
    }

    // Set Auth Protocol
    if (profile->user && profile->pd) {
        PQMIWDS_AUTH_PREFERENCE pAuthPref = (PQMIWDS_AUTH_PREFERENCE)(pTLV + TLVLength);
        pAuthPref->TLVType = 0x1D;
        pAuthPref->TLVLength = cpu_to_le16(0x01);
        pAuthPref->AuthPreference = profile->auth; // 0 ~ None, 1 ~ Pap, 2 ~ Chap, 3 ~ MsChapV2
        TLVLength += (le16_to_cpu(pAuthPref->TLVLength) + sizeof(QCQMICTL_TLV_HDR));
    }

    return sizeof(QMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG) + TLVLength;
}
#endif

static USHORT WdsGetRuntimeSettingReq(PQMUX_MSG pMUXMsg, void *arg)
{
    (void)arg;
    pMUXMsg->GetRuntimeSettingsReq.TLVType = 0x10;
    pMUXMsg->GetRuntimeSettingsReq.TLVLength = cpu_to_le16(0x04);
    // the following mask also applies to IPV6
    pMUXMsg->GetRuntimeSettingsReq.Mask = cpu_to_le32(QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4DNS_ADDR |
                                                      QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4_ADDR |
                                                      QMIWDS_GET_RUNTIME_SETTINGS_MASK_MTU |
                                                      QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4GATEWAY_ADDR) |
                                          QMIWDS_GET_RUNTIME_SETTINGS_MASK_PCSCF_SV_ADDR |
                                          QMIWDS_GET_RUNTIME_SETTINGS_MASK_PCSCF_DOM_NAME;

    return sizeof(QMIWDS_GET_RUNTIME_SETTINGS_REQ_MSG);
}

static PQCQMIMSG s_pRequest;
static PQCQMIMSG s_pResponse;

static int is_response(const PQCQMIMSG pRequest, const PQCQMIMSG pResponse) {
    if ((pRequest->QMIHdr.QMIType == pResponse->QMIHdr.QMIType)
        && (pRequest->QMIHdr.ClientId == pResponse->QMIHdr.ClientId)) {
            USHORT requestTID, responseTID;
        if (pRequest->QMIHdr.QMIType == QMUX_TYPE_CTL) {
            requestTID = pRequest->CTLMsg.QMICTLMsgHdr.TransactionId;
            responseTID = pResponse->CTLMsg.QMICTLMsgHdr.TransactionId;
        } else {
            requestTID = le16_to_cpu(pRequest->MUXMsg.QMUXHdr.TransactionId);
            responseTID = le16_to_cpu(pResponse->MUXMsg.QMUXHdr.TransactionId);
        }
        return (requestTID == responseTID);
    }
    return 0;
}

int (*qmidev_send)(PQCQMIMSG pRequest);

int QmiThreadSendQMITimeout(PQCQMIMSG pRequest, PQCQMIMSG *ppResponse, unsigned msecs, const char *funcname) {
    int ret;
    
    if (!pRequest)
        return -EINVAL;

    pthread_mutex_lock(&cm_command_mutex);

    if (ppResponse)
        *ppResponse = NULL;

    dump_qmi(pRequest, le16_to_cpu(pRequest->QMIHdr.Length) + 1);

    s_pRequest = pRequest;
    s_pResponse = NULL;

    ret = qmidev_send(pRequest);
    if (ret == 0) {
        ret = pthread_cond_timeout_np(&cm_command_cond, &cm_command_mutex, msecs);
        if (!ret) {
            if (s_pResponse && ppResponse) {
                *ppResponse = s_pResponse;
            }
            else if (s_pResponse) {
                free(s_pResponse);
                s_pResponse = NULL;
            }
            else {
                dbg_time("%s message null", funcname);
                ret = -EINVAL;
            }
        } else {
            dbg_time("%s message timeout", funcname);
        }
    }

    pthread_mutex_unlock(&cm_command_mutex);

    return ret;
}

void QmiThreadRecvQMI(PQCQMIMSG pResponse) {
    pthread_mutex_lock(&cm_command_mutex);
    if (pResponse == NULL) {
        if (s_pRequest) {
            free(s_pRequest);
            s_pRequest = NULL;
            s_pResponse = NULL;
            pthread_cond_signal(&cm_command_cond);
        }
        pthread_mutex_unlock(&cm_command_mutex);
        return;
    }
    dump_qmi(pResponse, le16_to_cpu(pResponse->QMIHdr.Length) + 1);
    if (s_pRequest && is_response(s_pRequest, pResponse)) {
        free(s_pRequest);
        s_pRequest = NULL;
        s_pResponse = malloc(le16_to_cpu(pResponse->QMIHdr.Length) + 1);
        if (s_pResponse != NULL) {
            memcpy(s_pResponse, pResponse, le16_to_cpu(pResponse->QMIHdr.Length) + 1);
        }
        pthread_cond_signal(&cm_command_cond);
    } else if ((pResponse->QMIHdr.QMIType == QMUX_TYPE_CTL)
                    && (le16_to_cpu(pResponse->CTLMsg.QMICTLMsgHdrRsp.QMICTLType == QMICTL_REVOKE_CLIENT_ID_IND))) {
        qmidevice_send_event_to_main(MODEM_REPORT_RESET_EVENT);
    } else if ((pResponse->QMIHdr.QMIType == QMUX_TYPE_NAS)
                    && (le16_to_cpu(pResponse->MUXMsg.QMUXMsgHdrResp.Type) == QMINAS_SERVING_SYSTEM_IND)) {
        qmidevice_send_event_to_main(RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED);
    } else if ((pResponse->QMIHdr.QMIType == QMUX_TYPE_WDS)
                    && (le16_to_cpu(pResponse->MUXMsg.QMUXMsgHdrResp.Type) == QMIWDS_GET_PKT_SRVC_STATUS_IND)) {
        qmidevice_send_event_to_main(RIL_UNSOL_DATA_CALL_LIST_CHANGED);
    } else if ((pResponse->QMIHdr.QMIType == QMUX_TYPE_NAS)
                    && (le16_to_cpu(pResponse->MUXMsg.QMUXMsgHdrResp.Type) == QMINAS_SYS_INFO_IND)) {
        qmidevice_send_event_to_main(RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED);
    } else if ((pResponse->QMIHdr.QMIType == QMUX_TYPE_WDS_ADMIN)
                    && (le16_to_cpu(pResponse->MUXMsg.QMUXMsgHdrResp.Type) == QMI_WDA_SET_LOOPBACK_CONFIG_IND)) {
    	qmidevice_send_event_to_main_ext(RIL_UNSOL_LOOPBACK_CONFIG_IND,
            &pResponse->MUXMsg.SetLoopBackInd, sizeof(pResponse->MUXMsg.SetLoopBackInd));
    }
#ifdef CONFIG_REG_QOS_IND
    else if ((pResponse->QMIHdr.QMIType == QMUX_TYPE_QOS)
          && (le16_to_cpu(pResponse->MUXMsg.QMUXMsgHdrResp.Type) == QMI_QOS_GLOBAL_QOS_FLOW_IND)) {
        UINT qos_id = 0;
        UCHAR new_flow = ql_get_global_qos_flow_ind_qos_id(pResponse, &qos_id);
        if (qos_id != 0 && new_flow == 1)
            qmidevice_send_event_to_main_ext(RIL_UNSOL_GLOBAL_QOS_FLOW_IND_QOS_ID, &qos_id, sizeof(qos_id));
#ifdef CONFIG_GET_QOS_DATA_RATE
        if (new_flow) {
            ULONG64 max_data_rate[2] = {0};
            if (ql_get_global_qos_flow_ind_data_rate(pResponse, (void *)max_data_rate) == 0){}
        }
#endif
    }
#endif
    else {
        if (debug_qmi)
            dbg_time("nobody care this qmi msg!!");
    }
    pthread_mutex_unlock(&cm_command_mutex);
}

#ifdef CONFIG_COEX_WWAN_STATE
static int requestGetCoexWWANState(void) {
    PQCQMIMSG pRequest;
    PQCQMIMSG pResponse;
    PQMUX_MSG pMUXMsg;
    PQMI_COEX_GET_WWAN_STATE_RESP_MSG_LTE_BAND pLteBand;
    static QMI_COEX_GET_WWAN_STATE_RESP_MSG_LTE_BAND oldLteBand = {-1, -1};
    int err;

    pRequest = ComposeQMUXMsg(QMUX_TYPE_COEX, QMI_COEX_GET_WWAN_STATE_REQ, NULL, NULL);
    err = QmiThreadSendQMI(pRequest, &pResponse);

    if (err < 0 || pResponse == NULL) {
        dbg_time("%s err = %d", __func__, err);
        return err;
    }

    pMUXMsg = &pResponse->MUXMsg;
    if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) {
        dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError));
        err = le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError);
        free(pResponse);
        return err;
    } 
    pLteBand = (PQMI_COEX_GET_WWAN_STATE_RESP_MSG_LTE_BAND)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x10);

    if (pLteBand && memcmp(pLteBand, &oldLteBand, sizeof(oldLteBand))) {
        oldLteBand = *pLteBand;
        dbg_time("%s ul_freq %d ul_bandwidth %d", __func__, le32_to_cpu(pLteBand->ul_band.freq), le32_to_cpu(pLteBand->ul_band.bandwidth));
        dbg_time("%s dl_freq %d dl_bandwidth %d", __func__, le32_to_cpu(pLteBand->dl_band.freq), le32_to_cpu(pLteBand->dl_band.bandwidth));
    }
    free(pResponse);
    return 0;
}
#endif

static int requestSetEthMode(PROFILE_T *profile) {
    PQCQMIMSG pRequest;
    PQCQMIMSG pResponse = NULL;
    PQMUX_MSG pMUXMsg;
    int err;
    PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV linkProto;
    UCHAR IpPreference;
    UCHAR autoconnect_setting = 0;
    QMAP_SETTING qmap_settings = {0};

    qmap_settings.size = sizeof(qmap_settings);
    
    if (profile->qmap_mode) {
        profile->rawIP = 1;
        s_9x07 = profile->rawIP;
        
        qmap_settings.MuxId = profile->muxid;

        if (profile->hardware_interface == HARDWARE_PCIE) { //SDX20_PCIE
            qmap_settings.rx_urb_size = profile->qmap_size; //SDX24&SDX55 support 32KB 
            qmap_settings.ep_type = DATA_EP_TYPE_PCIE;
            qmap_settings.iface_id = 0x04;
        }
        else if (profile->hardware_interface == HARDWARE_IPA) { // for inside sdx
            qmap_settings.rx_urb_size = profile->qmap_size; //SDX24&SDX55 support 32KB
            qmap_settings.ep_type = DATA_EP_TYPE_EMBEDDED;
            qmap_settings.iface_id = 1; //DSI_DEFAULT_EPID_IPA;
            //skip for netmgrd send QMI_WDA_SET_DATA_FORMAT_REQ_V01 & QMI_DPM_OPEN_PORT_REQ_V01
            goto skip_WdaSetDataFormat;
        }
        else { // for MDM9x07&MDM9x40&SDX20 USB
            qmap_settings.rx_urb_size = profile->qmap_size; //SDX24&SDX55 support 32KB
            qmap_settings.ep_type = DATA_EP_TYPE_HSUSB;
            qmap_settings.iface_id = 0x04;
            if(profile->usb_dev.idProduct == 0x0316) //X35 Rmnet interface is 3
                qmap_settings.iface_id = 0x03;
        }

        qmap_settings.ul_data_aggregation_max_datagrams = 11; //by test result, 11 can get best TPUT
        qmap_settings.ul_data_aggregation_max_size = 8*1024;
        qmap_settings.dl_minimum_padding = 0; //no effect when register to real netowrk
        if(profile->qmap_version != 0x09)
            profile->qmap_version = 0x05;

        qmap_version = profile->qmap_version;
        if (profile->rmnet_info.size) {
            qmap_settings.rx_urb_size = profile->rmnet_info.rx_urb_size;
            qmap_settings.ep_type = profile->rmnet_info.ep_type;
            qmap_settings.iface_id = profile->rmnet_info.iface_id;
            qmap_settings.dl_minimum_padding = profile->rmnet_info.dl_minimum_padding;
            qmap_version = profile->rmnet_info.qmap_version;
        }

        if (!profile->wda_client) {
            if (qmidev_is_gobinet(profile->qmichannel)) {
                //when QMAP enabled, set data format in GobiNet driver
            }
            else if (profile->proxy[0]) {
                /* the first running 'quectel-cm' had alloc wda client and set data format,
                   so we can ingore to set data format here. */
            }
            goto skip_WdaSetDataFormat;
        }
    }

    pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS_ADMIN, QMIWDS_ADMIN_SET_DATA_FORMAT_REQ, WdaSetDataFormat, (void *)&qmap_settings);
    err = QmiThreadSendQMI(pRequest, &pResponse);
    qmi_rsp_check_and_return();

    linkProto = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11);
    if (linkProto != NULL) {
        profile->rawIP = (le32_to_cpu(linkProto->Value) == 2);
        s_9x07 = profile->rawIP; //MDM90x7 only support RAW IP, do not support Eth
    }

    linkProto = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x16);
    if (linkProto != NULL && profile->qmap_mode) {
        qmap_settings.rx_urb_size = le32_to_cpu(linkProto->Value);
        dbg_time("qmap_settings.rx_urb_size = %u", qmap_settings.rx_urb_size); //must same as rx_urb_size defined in GobiNet&qmi_wwan driver
    }

#ifdef QUECTEL_UL_DATA_AGG
    if (qmap_settings.ul_data_aggregation_max_datagrams)
    {
        linkProto = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x17);
        if (linkProto != NULL) {
            qmap_settings.ul_data_aggregation_max_datagrams = MIN(qmap_settings.ul_data_aggregation_max_datagrams, le32_to_cpu(linkProto->Value));
            dbg_time("qmap_settings.ul_data_aggregation_max_datagrams  = %u", qmap_settings.ul_data_aggregation_max_datagrams);
        }

        linkProto = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x18);
        if (linkProto != NULL) {
            qmap_settings.ul_data_aggregation_max_size = MIN(qmap_settings.ul_data_aggregation_max_size, le32_to_cpu(linkProto->Value));
            dbg_time("qmap_settings.ul_data_aggregation_max_size       = %u", qmap_settings.ul_data_aggregation_max_size);
        }

        linkProto = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x1A);
        if (linkProto != NULL) {
            qmap_settings.dl_minimum_padding = le32_to_cpu(linkProto->Value);
            dbg_time("qmap_settings.dl_minimum_padding                 = %u", qmap_settings.dl_minimum_padding);
        }

	if (qmap_settings.ul_data_aggregation_max_datagrams > 1) {
		ql_set_driver_qmap_setting(profile, &qmap_settings);
	}
    }
#endif

    free(pResponse);

skip_WdaSetDataFormat:
    if (profile->enable_ipv4) {
    	if (profile->qmapnet_adapter[0]) {
            // bind wds mux data port
        	pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_BIND_MUX_DATA_PORT_REQ , WdsSetQMUXBindMuxDataPort, (void *)&qmap_settings);
        	err = QmiThreadSendQMI(pRequest, &pResponse);
        	qmi_rsp_check_and_return();
        	if (pResponse) free(pResponse);
        }

        // set ipv4
    	IpPreference = IpFamilyV4;
    	pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ, WdsSetClientIPFamilyPref, (void *)&IpPreference);
    	err = QmiThreadSendQMI(pRequest, &pResponse);
    	if (pResponse) free(pResponse);
    }

    if (profile->enable_ipv6) {
        if (profile->qmapnet_adapter[0]) {
            // bind wds ipv6 mux data port
            pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS_IPV6, QMIWDS_BIND_MUX_DATA_PORT_REQ , WdsSetQMUXBindMuxDataPort, (void *)&qmap_settings);
            err = QmiThreadSendQMI(pRequest, &pResponse);
            qmi_rsp_check_and_return();
            if (pResponse) free(pResponse);
        }

        // set ipv6
        IpPreference = IpFamilyV6;
        pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS_IPV6, QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ, WdsSetClientIPFamilyPref, (void *)&IpPreference);
        err = QmiThreadSendQMI(pRequest, &pResponse);
        qmi_rsp_check_and_return();
        if (pResponse) free(pResponse);
    }

    pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_SET_AUTO_CONNECT_REQ , WdsSetAutoConnect, (void *)&autoconnect_setting);
    QmiThreadSendQMI(pRequest, &pResponse);
    if (pResponse) free(pResponse);

    return 0;
}

#ifdef CONFIG_SIM
static int requestGetPINStatus(SIM_Status *pSIMStatus) {
    PQCQMIMSG pRequest;
    PQCQMIMSG pResponse;
    PQMUX_MSG pMUXMsg;
    int err;
    PQMIDMS_UIM_PIN_STATUS pPin1Status = NULL;
    //PQMIDMS_UIM_PIN_STATUS pPin2Status = NULL;

    if (s_9x07)
        pRequest = ComposeQMUXMsg(QMUX_TYPE_UIM, QMIUIM_GET_CARD_STATUS_REQ, NULL, NULL);
    else
        pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_UIM_GET_PIN_STATUS_REQ, NULL, NULL);
    err = QmiThreadSendQMI(pRequest, &pResponse);
    qmi_rsp_check_and_return();

    pPin1Status = (PQMIDMS_UIM_PIN_STATUS)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11);
    //pPin2Status = (PQMIDMS_UIM_PIN_STATUS)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x12);

    if (pPin1Status != NULL) {
        if (pPin1Status->PINStatus == QMI_PIN_STATUS_NOT_VERIF) {
            *pSIMStatus = SIM_PIN;
        } else if (pPin1Status->PINStatus == QMI_PIN_STATUS_BLOCKED) {
            *pSIMStatus = SIM_PUK;
        } else if (pPin1Status->PINStatus == QMI_PIN_STATUS_PERM_BLOCKED) {
            *pSIMStatus = SIM_BAD;
        }
    }

    free(pResponse);
    return 0;
}

static int requestGetSIMStatus(SIM_Status *pSIMStatus) { //RIL_REQUEST_GET_SIM_STATUS
    PQCQMIMSG pRequest;
    PQCQMIMSG pResponse;
    PQMUX_MSG pMUXMsg;
    int err;
    const char * SIM_Status_String[] = {
        "SIM_ABSENT",
        "SIM_NOT_READY",
        "SIM_READY", /* SIM_READY means the radio state is RADIO_STATE_SIM_READY */
        "SIM_PIN",
        "SIM_PUK",
        "SIM_NETWORK_PERSONALIZATION"
    };

    if (s_9x07)
        pRequest = ComposeQMUXMsg(QMUX_TYPE_UIM, QMIUIM_GET_CARD_STATUS_REQ, NULL, NULL);
    else
        pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_UIM_GET_STATE_REQ, NULL, NULL);

    err = QmiThreadSendQMI(pRequest, &pResponse);
    qmi_rsp_check_and_return();

    *pSIMStatus = SIM_ABSENT;
    if (s_9x07)
    {
        PQMIUIM_CARD_STATUS pCardStatus = NULL;
        PQMIUIM_PIN_STATE pPINState = NULL;
        UCHAR CardState = 0x01;
        UCHAR PIN1State = QMI_PIN_STATUS_NOT_VERIF;
        //UCHAR PIN1Retries;
        //UCHAR PUK1Retries;
        //UCHAR PIN2State;
        //UCHAR PIN2Retries;
        //UCHAR PUK2Retries;

        pCardStatus = (PQMIUIM_CARD_STATUS)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x10);
        if (pCardStatus != NULL)
        {
            pPINState = (PQMIUIM_PIN_STATE)((PUCHAR)pCardStatus + sizeof(QMIUIM_CARD_STATUS) + pCardStatus->AIDLength);
            CardState  = pCardStatus->CardState;
            if (CardState == UIM_CARD_STATE_PRESENT) {
                if (pPINState->UnivPIN == 1)
                {
                   PIN1State = pCardStatus->UPINState;
                   //PIN1Retries = pCardStatus->UPINRetries;
                   //PUK1Retries = pCardStatus->UPUKRetries;
                }
                else
                {
                   PIN1State = pPINState->PIN1State;
                   //PIN1Retries = pPINState->PIN1Retries;
                   //PUK1Retries = pPINState->PUK1Retries;
                }
                //PIN2State = pPINState->PIN2State;
                //PIN2Retries = pPINState->PIN2Retries;
                //PUK2Retries = pPINState->PUK2Retries;
            }
        }

        *pSIMStatus = SIM_ABSENT;
        if ((CardState == 0x01) &&  ((PIN1State == QMI_PIN_STATUS_VERIFIED)|| (PIN1State == QMI_PIN_STATUS_DISABLED)))
        {
            *pSIMStatus = SIM_READY;
        }
        else if (CardState == 0x01)
        {
            if (PIN1State == QMI_PIN_STATUS_NOT_VERIF)
            {
                *pSIMStatus = SIM_PIN;
            }
            if ( PIN1State == QMI_PIN_STATUS_BLOCKED)
            {
                *pSIMStatus = SIM_PUK;
            }
            else if (PIN1State == QMI_PIN_STATUS_PERM_BLOCKED)
            {
                *pSIMStatus = SIM_BAD;
            }
            else if (PIN1State == QMI_PIN_STATUS_NOT_INIT || PIN1State == QMI_PIN_STATUS_VERIFIED || PIN1State == QMI_PIN_STATUS_DISABLED)
            {
                *pSIMStatus = SIM_READY;
            }
        }
        else if (CardState == 0x00 || CardState == 0x02)
        {
        }
        else
        {
        }
    }
    else
    {
    //UIM state. Values:
    // 0x00  UIM initialization completed
    // 0x01  UIM is locked or the UIM failed
    // 0x02  UIM is not present
    // 0x03  Reserved
    // 0xFF  UIM state is currently
    //unavailable
        if (pResponse->MUXMsg.UIMGetStateResp.UIMState == 0x00) {
            *pSIMStatus = SIM_READY;
        } else if (pResponse->MUXMsg.UIMGetStateResp.UIMState == 0x01) {
            *pSIMStatus = SIM_ABSENT;
            err = requestGetPINStatus(pSIMStatus);
        } else if ((pResponse->MUXMsg.UIMGetStateResp.UIMState == 0x02) || (pResponse->MUXMsg.UIMGetStateResp.UIMState == 0xFF)) {
            *pSIMStatus = SIM_ABSENT;
        } else {
            *pSIMStatus = SIM_ABSENT;
        }
    }
    dbg_time("%s SIMStatus: %s", __func__, SIM_Status_String[*pSIMStatus]);

    free(pResponse);

    return 0;
}

static int requestEnterSimPin(const char *pPinCode) {
    PQCQMIMSG pRequest;
    PQCQMIMSG pResponse;
    PQMUX_MSG pMUXMsg;
    int err;

    if (s_9x07)
        pRequest = ComposeQMUXMsg(QMUX_TYPE_UIM, QMIUIM_VERIFY_PIN_REQ, UimVerifyPinReqSend, (void *)pPinCode);
    else
        pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_UIM_VERIFY_PIN_REQ, DmsUIMVerifyPinReqSend, (void *)pPinCode);
    err = QmiThreadSendQMI(pRequest, &pResponse);
    qmi_rsp_check_and_return();

    free(pResponse);
    return 0;
}
#endif

#ifdef CONFIG_IMSI_ICCID
static int requestGetICCID(void) { //RIL_REQUEST_GET_IMSI
    PQCQMIMSG pRequest;
    PQCQMIMSG pResponse;
    PQMUX_MSG pMUXMsg;
    PQMIUIM_CONTENT pUimContent;
    int err;

    if (s_9x07) {
        pRequest = ComposeQMUXMsg(QMUX_TYPE_UIM, QMIUIM_READ_TRANSPARENT_REQ, UimReadTransparentIMSIReqSend, (void *)"EF_ICCID");
        err = QmiThreadSendQMI(pRequest, &pResponse);
    } else {
        return 0;
    }
    qmi_rsp_check_and_return();

    pUimContent = (PQMIUIM_CONTENT)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11);
    if (pUimContent != NULL) {
        static char DeviceICCID[32] = {'\0'};
        int i = 0, j = 0;

        for (i = 0, j = 0; i < le16_to_cpu(pUimContent->content_len); ++i) {
            char charmaps[] = "0123456789ABCDEF";

            DeviceICCID[j++] = charmaps[(pUimContent->content[i] & 0x0F)];
            DeviceICCID[j++] = charmaps[((pUimContent->content[i] & 0xF0) >> 0x04)];
        }
        DeviceICCID[j] = '\0';

        dbg_time("%s DeviceICCID: %s", __func__, DeviceICCID);
    }

    free(pResponse);
    return 0;
}

static int requestGetIMSI(void) { //RIL_REQUEST_GET_IMSI
    PQCQMIMSG pRequest;
    PQCQMIMSG pResponse;
    PQMUX_MSG pMUXMsg;
    PQMIUIM_CONTENT pUimContent;
    int err;

    if (s_9x07) {
        pRequest = ComposeQMUXMsg(QMUX_TYPE_UIM, QMIUIM_READ_TRANSPARENT_REQ, UimReadTransparentIMSIReqSend, (void *)"EF_IMSI");
        err = QmiThreadSendQMI(pRequest, &pResponse);
    } else {
        return 0;
    }
    qmi_rsp_check_and_return();

    pUimContent = (PQMIUIM_CONTENT)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11);
    if (pUimContent != NULL) {
        static char DeviceIMSI[32] = {'\0'};
        int i = 0, j = 0;

        for (i = 0, j = 0; i < le16_to_cpu(pUimContent->content[0]); ++i) {
            if (i != 0)
                DeviceIMSI[j++] = (pUimContent->content[i+1] & 0x0F) + '0';
            DeviceIMSI[j++] = ((pUimContent->content[i+1] & 0xF0) >> 0x04) + '0';
        }
        DeviceIMSI[j] = '\0';

        dbg_time("%s DeviceIMSI: %s", __func__, DeviceIMSI);
    }

    free(pResponse);
    return 0;
}
#endif

#if 1
static void quectel_convert_cdma_mcc_2_ascii_mcc( USHORT *p_mcc, USHORT mcc )
{
  unsigned int d1, d2, d3, buf = mcc + 111;

  if ( mcc == 0x3FF ) // wildcard
  {
    *p_mcc = 3;
  }
  else
  {
    d3 = buf % 10;
    buf = ( d3 == 0 ) ? (buf-10)/10 : buf/10;

    d2 = buf % 10;
    buf = ( d2 == 0 ) ? (buf-10)/10 : buf/10;

    d1 = ( buf == 10 ) ? 0 : buf;

//dbg_time("d1:%d, d2:%d,d3:%d",d1,d2,d3);
    if ( d1<10 && d2<10 && d3<10 )
    {
    *p_mcc = d1*100+d2*10+d3;
#if 0
      *(p_mcc+0) = '0' + d1;
      *(p_mcc+1) = '0' + d2;
      *(p_mcc+2) = '0' + d3;
#endif
    }
    else
    {
      //dbg_time( "invalid digits %d %d %d", d1, d2, d3 );
      *p_mcc = 0;
    }
  }
}

static void quectel_convert_cdma_mnc_2_ascii_mnc( USHORT *p_mnc, USHORT imsi_11_12)
{
  unsigned int d1, d2, buf = imsi_11_12 + 11;

  if ( imsi_11_12 == 0x7F ) // wildcard
  {
    *p_mnc = 7;
  }
  else
  {
    d2 = buf % 10;
    buf = ( d2 == 0 ) ? (buf-10)/10 : buf/10;

    d1 = ( buf == 10 ) ? 0 : buf;

    if ( d1<10 && d2<10 )
    {
     *p_mnc = d1*10 + d2;
    }
    else
    {
      //dbg_time( "invalid digits %d %d", d1, d2, 0 );
      *p_mnc = 0;
    }
  }
}

static int requestGetHomeNetwork(USHORT *p_mcc, USHORT *p_mnc, USHORT *p_sid, USHORT *p_nid) {
    PQCQMIMSG pRequest;
    PQCQMIMSG pResponse;
    PQMUX_MSG pMUXMsg;
    int err;
    PHOME_NETWORK pHomeNetwork;
    PHOME_NETWORK_SYSTEMID pHomeNetworkSystemID;

    pRequest = ComposeQMUXMsg(QMUX_TYPE_NAS, QMINAS_GET_HOME_NETWORK_REQ, NULL, NULL);
    err = QmiThreadSendQMI(pRequest, &pResponse);
    qmi_rsp_check_and_return();

    pHomeNetwork = (PHOME_NETWORK)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x01);
    if (pHomeNetwork && p_mcc && p_mnc ) {
        *p_mcc = le16_to_cpu(pHomeNetwork->MobileCountryCode);
        *p_mnc = le16_to_cpu(pHomeNetwork->MobileNetworkCode);
        //dbg_time("%s MobileCountryCode: %d, MobileNetworkCode: %d", __func__, *pMobileCountryCode, *pMobileNetworkCode);
    }

    pHomeNetworkSystemID = (PHOME_NETWORK_SYSTEMID)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x10);
    if (pHomeNetworkSystemID && p_sid && p_nid) {
        *p_sid = le16_to_cpu(pHomeNetworkSystemID->SystemID); //china-hefei: sid 14451
        *p_nid = le16_to_cpu(pHomeNetworkSystemID->NetworkID);
        //dbg_time("%s SystemID: %d, NetworkID: %d", __func__, *pSystemID, *pNetworkID);
    }

    free(pResponse);

    return 0;
}
#endif

#if 0
// Lookup table for carriers known to produce SIMs which incorrectly indicate MNC length.
static const char * MCCMNC_CODES_HAVING_3DIGITS_MNC[] = {
    "302370", "302720", "310260",
    "405025", "405026", "405027", "405028", "405029", "405030", "405031", "405032",
    "405033", "405034", "405035", "405036", "405037", "405038", "405039", "405040",
    "405041", "405042", "405043", "405044", "405045", "405046", "405047", "405750",
    "405751", "405752", "405753", "405754", "405755", "405756", "405799", "405800",
    "405801", "405802", "405803", "405804", "405805", "405806", "405807", "405808",
    "405809", "405810", "405811", "405812", "405813", "405814", "405815", "405816",
    "405817", "405818", "405819", "405820", "405821", "405822", "405823", "405824",
    "405825", "405826", "405827", "405828", "405829", "405830", "405831", "405832",
    "405833", "405834", "405835", "405836", "405837", "405838", "405839", "405840",
    "405841", "405842", "405843", "405844", "405845", "405846", "405847", "405848",
    "405849", "405850", "405851", "405852", "405853", "405875", "405876", "405877",
    "405878", "405879", "405880", "405881", "405882", "405883", "405884", "405885",
    "405886", "405908", "405909", "405910", "405911", "405912", "405913", "405914",
    "405915", "405916", "405917", "405918", "405919", "405920", "405921", "405922",
    "405923", "405924", "405925", "405926", "405927", "405928", "405929", "405930",
    "405931", "405932", "502142", "502143", "502145", "502146", "502147", "502148"
};

static const char * MCC_CODES_HAVING_3DIGITS_MNC[] = {
    "302",    //Canada
    "310",    //United States of America
    "311",    //United States of America
    "312",    //United States of America
    "313",    //United States of America
    "314",    //United States of America
    "315",    //United States of America
    "316",    //United States of America
    "334",    //Mexico
    "338",    //Jamaica
    "342", //Barbados
    "344",    //Antigua and Barbuda
    "346",    //Cayman Islands
    "348",    //British Virgin Islands
    "365",    //Anguilla
    "708",    //Honduras (Republic of)
    "722",    //Argentine Republic
    "732"    //Colombia (Republic of)
};

int requestGetIMSI(const char **pp_imsi, USHORT *pMobileCountryCode, USHORT *pMobileNetworkCode) {
    PQCQMIMSG pRequest;
    PQCQMIMSG pResponse;
    PQMUX_MSG pMUXMsg;
    int err;

    if (pp_imsi) *pp_imsi = NULL;
    if (pMobileCountryCode) *pMobileCountryCode = 0;
    if (pMobileNetworkCode) *pMobileNetworkCode = 0;

    pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_UIM_GET_IMSI_REQ, NULL, NULL);
    err = QmiThreadSendQMI(pRequest, &pResponse);
    qmi_rsp_check_and_return();

    if (pMUXMsg->UIMGetIMSIResp.TLV2Type == 0x01 &&  le16_to_cpu(pMUXMsg->UIMGetIMSIResp.TLV2Length) >= 5) {
        int mnc_len = 2;
        unsigned i;
        char tmp[4];

        if (pp_imsi) *pp_imsi = strndup((const char *)(&pMUXMsg->UIMGetIMSIResp.IMSI), le16_to_cpu(pMUXMsg->UIMGetIMSIResp.TLV2Length));

        for (i = 0; i < sizeof(MCCMNC_CODES_HAVING_3DIGITS_MNC)/sizeof(MCCMNC_CODES_HAVING_3DIGITS_MNC[0]); i++) {
            if (!strncmp((const char *)(&pMUXMsg->UIMGetIMSIResp.IMSI), MCCMNC_CODES_HAVING_3DIGITS_MNC[i], 6)) {
                mnc_len = 3;
                break;
            }
        }
        if (mnc_len == 2) {
            for (i = 0; i < sizeof(MCC_CODES_HAVING_3DIGITS_MNC)/sizeof(MCC_CODES_HAVING_3DIGITS_MNC[0]); i++) {
                if (!strncmp((const char *)(&pMUXMsg->UIMGetIMSIResp.IMSI), MCC_CODES_HAVING_3DIGITS_MNC[i], 3)) {
                    mnc_len = 3;
                    break;
                }
            }
        }

        tmp[0] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[0];
        tmp[1] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[1];
        tmp[2] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[2];
        tmp[3] = 0;
        if (pMobileCountryCode) *pMobileCountryCode = atoi(tmp);
        tmp[0] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[3];
        tmp[1] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[4];
        tmp[2] = 0;
        if (mnc_len == 3) {
            tmp[2] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[6];
        }
        if (pMobileNetworkCode) *pMobileNetworkCode = atoi(tmp);
    }

    free(pResponse);

    return 0;
}
#endif

static struct wwan_data_class_str class2str[] = {
    {WWAN_DATA_CLASS_NONE, "UNKNOWN"},
    {WWAN_DATA_CLASS_GPRS, "GPRS"},
    {WWAN_DATA_CLASS_EDGE, "EDGE"},
    {WWAN_DATA_CLASS_UMTS, "UMTS"},
    {WWAN_DATA_CLASS_HSDPA, "HSDPA"},
    {WWAN_DATA_CLASS_HSUPA, "HSUPA"},
    {WWAN_DATA_CLASS_LTE, "LTE"},
    {WWAN_DATA_CLASS_5G_NSA, "5G_NSA"},
    {WWAN_DATA_CLASS_5G_SA, "5G_SA"},
    {WWAN_DATA_CLASS_1XRTT, "1XRTT"},
    {WWAN_DATA_CLASS_1XEVDO, "1XEVDO"},
    {WWAN_DATA_CLASS_1XEVDO_REVA, "1XEVDO_REVA"},
    {WWAN_DATA_CLASS_1XEVDV, "1XEVDV"},
    {WWAN_DATA_CLASS_3XRTT, "3XRTT"},
    {WWAN_DATA_CLASS_1XEVDO_REVB, "1XEVDO_REVB"},
    {WWAN_DATA_CLASS_UMB, "UMB"},
    {WWAN_DATA_CLASS_CUSTOM, "CUSTOM"},
};

static const char *wwan_data_class2str(ULONG class)
{
    unsigned int i = 0;
    for (i = 0; i < sizeof(class2str)/sizeof(class2str[0]); i++) {
        if (class2str[i].class == class) {
            return class2str[i].str;
        }
    }
    return "UNKNOWN";
}

static USHORT char2ushort(UCHAR str[3]) {
    int i;
    char temp[4];
    USHORT ret= 0;

    memcpy(temp, str, 3);
    temp[3] = '\0';

    for (i = 0; i < 4; i++) {
        if ((UCHAR)temp[i] == 0xFF) {
            temp[i] = '\0';
        }
    }
    ret = (USHORT)atoi(temp);

    return ret;
}

static int requestRegistrationState2(UCHAR *pPSAttachedState) {
    PQCQMIMSG pRequest;
    PQCQMIMSG pResponse;
    PQMUX_MSG pMUXMsg;
    int err;
    USHORT MobileCountryCode = 0;
    USHORT MobileNetworkCode = 0;
    const char *pDataCapStr = "UNKNOW";
    LONG remainingLen;
    PSERVICE_STATUS_INFO pServiceStatusInfo;
    int is_lte = 0;
    PCDMA_SYSTEM_INFO pCdmaSystemInfo;
    PHDR_SYSTEM_INFO pHdrSystemInfo;
    PGSM_SYSTEM_INFO pGsmSystemInfo;
    PWCDMA_SYSTEM_INFO pWcdmaSystemInfo;
    PLTE_SYSTEM_INFO pLteSystemInfo;
    PTDSCDMA_SYSTEM_INFO pTdscdmaSystemInfo;
    PNR5G_SYSTEM_INFO pNr5gSystemInfo;
    UCHAR DeviceClass = 0;
    ULONG DataCapList = 0;
    
    /*  Additional LTE System Info - Availability of Dual connectivity of E-UTRA with NR5G */
    uint8_t endc_available_valid = 0;  /**< Must be set to true if endc_available is being passed */
    uint8_t endc_available = 0x00;
    /**<  
       Upper layer indication in LTE SIB2. Values: \n
       - 0x00 -- 5G Not available \n
       - 0x01 -- 5G Available
       
    */
    /*  Additional LTE System Info - DCNR restriction Info */
    uint8_t restrict_dcnr_valid = 0;  /**< Must be set to true if restrict_dcnr is being passed */
    uint8_t restrict_dcnr = 0x01;
    /**<  
       DCNR restriction in NAS attach/TAU accept. Values: \n
       - 0x00 -- Not restricted \n
       - 0x01 -- Restricted
    */

    *pPSAttachedState = 0;
    pRequest = ComposeQMUXMsg(QMUX_TYPE_NAS, QMINAS_GET_SYS_INFO_REQ, NULL, NULL);
    err = QmiThreadSendQMI(pRequest, &pResponse);
    qmi_rsp_check_and_return();

    pServiceStatusInfo = (PSERVICE_STATUS_INFO)(((PCHAR)&pMUXMsg->GetSysInfoResp) + QCQMUX_MSG_HDR_SIZE);
    remainingLen = le16_to_cpu(pMUXMsg->GetSysInfoResp.Length);

    s_is_cdma = 0;
    s_5g_type = WWAN_DATA_CLASS_NONE;
    s_hdr_personality = 0;
    while (remainingLen > 0) {
        switch (pServiceStatusInfo->TLVType) {
        case 0x10: // CDMA
            if (pServiceStatusInfo->SrvStatus == 0x02) {
                DataCapList = WWAN_DATA_CLASS_1XRTT|
                              WWAN_DATA_CLASS_1XEVDO|
                              WWAN_DATA_CLASS_1XEVDO_REVA|
                              WWAN_DATA_CLASS_1XEVDV|
                              WWAN_DATA_CLASS_1XEVDO_REVB;
                DeviceClass = DEVICE_CLASS_CDMA;
                s_is_cdma = (0 == is_lte);
            }
            break;
        case 0x11: // HDR
            if (pServiceStatusInfo->SrvStatus == 0x02) {
                DataCapList = WWAN_DATA_CLASS_3XRTT|
                              WWAN_DATA_CLASS_UMB;
                DeviceClass = DEVICE_CLASS_CDMA;
                s_is_cdma = (0 == is_lte);
            }
            break;
        case 0x12: // GSM
            if (pServiceStatusInfo->SrvStatus == 0x02) {
                DataCapList = WWAN_DATA_CLASS_GPRS|
                              WWAN_DATA_CLASS_EDGE;
                DeviceClass = DEVICE_CLASS_GSM;
            }
            break;
        case 0x13: // WCDMA
            if (pServiceStatusInfo->SrvStatus == 0x02) {
                DataCapList = WWAN_DATA_CLASS_UMTS;
                DeviceClass = DEVICE_CLASS_GSM;
            }
            break;
        case 0x14: // LTE
            if (pServiceStatusInfo->SrvStatus == 0x02) {
                DataCapList = WWAN_DATA_CLASS_LTE;
                DeviceClass = DEVICE_CLASS_GSM;
                is_lte = 1;
                s_is_cdma = 0;
            }
            break;
        case 0x4A: // NR5G Service Status Info
            if (pServiceStatusInfo->SrvStatus == NAS_SYS_SRV_STATUS_SRV_V01) {
                DataCapList |= WWAN_DATA_CLASS_5G_SA;
                DeviceClass = DEVICE_CLASS_GSM;
                is_lte = 1;
                s_is_cdma = 0;
            }
            break;
        case 0x4B: // NR5G System Info
            pNr5gSystemInfo = (PNR5G_SYSTEM_INFO)pServiceStatusInfo;
            if (pNr5gSystemInfo->srv_domain_valid == 0x01) {
                if (pNr5gSystemInfo->srv_domain & SYS_SRV_DOMAIN_PS_ONLY_V01) {
                    *pPSAttachedState = 1;
                }
            }

            if (pNr5gSystemInfo->network_id_valid == 0x01) {
                MobileCountryCode = (USHORT)char2ushort(pNr5gSystemInfo->MCC);
                MobileNetworkCode = (USHORT)char2ushort(pNr5gSystemInfo->MNC);
            }
            break;
        case 0x4E: //Additional LTE System Info - Availability of Dual Connectivity of E-UTRA with NR5G
            endc_available_valid = 1;
            endc_available = pServiceStatusInfo->SrvStatus;
            break;
            
        case 0x4F: //Additional LTE System Info - DCNR restriction Info
            restrict_dcnr_valid = 1;
            restrict_dcnr = pServiceStatusInfo->SrvStatus;
            break;

        case 0x24: // TDSCDMA
            if (pServiceStatusInfo->SrvStatus == 0x02) {
                pDataCapStr = "TD-SCDMA";
            }
            break;
        case 0x15: // CDMA
            // CDMA_SYSTEM_INFO
            pCdmaSystemInfo = (PCDMA_SYSTEM_INFO)pServiceStatusInfo;
            if (pCdmaSystemInfo->SrvDomainValid == 0x01) {
                if (pCdmaSystemInfo->SrvDomain & 0x02) {
                    *pPSAttachedState = 1;
                    s_is_cdma = (0 == is_lte);
                }
            }
#if 0
            if (pCdmaSystemInfo->SrvCapabilityValid == 0x01) {
                *pPSAttachedState = 0;
                if (pCdmaSystemInfo->SrvCapability & 0x02) {
                    *pPSAttachedState = 1;
                    s_is_cdma = (0 == is_lte);
                }
            }
#endif
            if (pCdmaSystemInfo->NetworkIdValid == 0x01) {
                MobileCountryCode = (USHORT)char2ushort(pCdmaSystemInfo->MCC);
                MobileNetworkCode = (USHORT)char2ushort(pCdmaSystemInfo->MNC);
            }
            break;
        case 0x16: // HDR
            // HDR_SYSTEM_INFO
            pHdrSystemInfo = (PHDR_SYSTEM_INFO)pServiceStatusInfo;
            if (pHdrSystemInfo->SrvDomainValid == 0x01) {
                if (pHdrSystemInfo->SrvDomain & 0x02) {
                    *pPSAttachedState = 1;
                    s_is_cdma = (0 == is_lte);
                }
            }
#if 0
            if (pHdrSystemInfo->SrvCapabilityValid == 0x01) {
                *pPSAttachedState = 0;
                if (pHdrSystemInfo->SrvCapability & 0x02) {
                    *pPSAttachedState = 1;
                    s_is_cdma = (0 == is_lte);
                }
            }
#endif
            if (*pPSAttachedState && pHdrSystemInfo->HdrPersonalityValid == 0x01) {
                if (pHdrSystemInfo->HdrPersonality == 0x03)
                    s_hdr_personality = 0x02;
                //else if (pHdrSystemInfo->HdrPersonality == 0x02)
                //    s_hdr_personality = 0x01;
            }
            USHORT cmda_mcc = 0, cdma_mnc = 0;
            if(!requestGetHomeNetwork(&cmda_mcc, &cdma_mnc,NULL, NULL) && cmda_mcc) {
                quectel_convert_cdma_mcc_2_ascii_mcc(&MobileCountryCode, cmda_mcc);
                quectel_convert_cdma_mnc_2_ascii_mnc(&MobileNetworkCode, cdma_mnc);
            }
            break;
        case 0x17: // GSM
            // GSM_SYSTEM_INFO
            pGsmSystemInfo = (PGSM_SYSTEM_INFO)pServiceStatusInfo;
            if (pGsmSystemInfo->SrvDomainValid == 0x01) {
                if (pGsmSystemInfo->SrvDomain & 0x02) {
                    *pPSAttachedState = 1;
                }
            }
#if 0
            if (pGsmSystemInfo->SrvCapabilityValid == 0x01) {
                *pPSAttachedState = 0;
                if (pGsmSystemInfo->SrvCapability & 0x02) {
                    *pPSAttachedState = 1;
                }
            }
#endif
            if (pGsmSystemInfo->NetworkIdValid == 0x01) {
                MobileCountryCode = (USHORT)char2ushort(pGsmSystemInfo->MCC);
                MobileNetworkCode = (USHORT)char2ushort(pGsmSystemInfo->MNC);
            }
            break;
        case 0x18: // WCDMA
            // WCDMA_SYSTEM_INFO
            pWcdmaSystemInfo = (PWCDMA_SYSTEM_INFO)pServiceStatusInfo;
            if (pWcdmaSystemInfo->SrvDomainValid == 0x01) {
                if (pWcdmaSystemInfo->SrvDomain & 0x02) {
                    *pPSAttachedState = 1;
                }
            }
#if 0
            if (pWcdmaSystemInfo->SrvCapabilityValid == 0x01) {
                *pPSAttachedState = 0;
                if (pWcdmaSystemInfo->SrvCapability & 0x02) {
                    *pPSAttachedState = 1;
                }
            }
#endif
            if (pWcdmaSystemInfo->NetworkIdValid == 0x01) {
                MobileCountryCode = (USHORT)char2ushort(pWcdmaSystemInfo->MCC);
                MobileNetworkCode = (USHORT)char2ushort(pWcdmaSystemInfo->MNC);
            }
            break;
        case 0x19: // LTE_SYSTEM_INFO
            // LTE_SYSTEM_INFO
            pLteSystemInfo = (PLTE_SYSTEM_INFO)pServiceStatusInfo;
            if (pLteSystemInfo->SrvDomainValid == 0x01) {
                if (pLteSystemInfo->SrvDomain & 0x02) {
                    *pPSAttachedState = 1;
                    is_lte = 1;
                    s_is_cdma = 0;
                }
            }
#if 0
            if (pLteSystemInfo->SrvCapabilityValid == 0x01) {
                *pPSAttachedState = 0;
                if (pLteSystemInfo->SrvCapability & 0x02) {
                    *pPSAttachedState = 1;
                    is_lte = 1;
                    s_is_cdma = 0;
                }
            }
#endif
            if (pLteSystemInfo->NetworkIdValid == 0x01) {
                MobileCountryCode = (USHORT)char2ushort(pLteSystemInfo->MCC);
                MobileNetworkCode = (USHORT)char2ushort(pLteSystemInfo->MNC);
            }
            break;
        case 0x25: // TDSCDMA
            // TDSCDMA_SYSTEM_INFO
            pTdscdmaSystemInfo = (PTDSCDMA_SYSTEM_INFO)pServiceStatusInfo;
            if (pTdscdmaSystemInfo->SrvDomainValid == 0x01) {
                if (pTdscdmaSystemInfo->SrvDomain & 0x02) {
                    *pPSAttachedState = 1;
                }
            }
#if 0
            if (pTdscdmaSystemInfo->SrvCapabilityValid == 0x01) {
                *pPSAttachedState = 0;
                if (pTdscdmaSystemInfo->SrvCapability & 0x02) {
                    *pPSAttachedState = 1;
                }
            }
#endif
            if (pTdscdmaSystemInfo->NetworkIdValid == 0x01) {
                MobileCountryCode = (USHORT)char2ushort(pTdscdmaSystemInfo->MCC);
                MobileNetworkCode = (USHORT)char2ushort(pTdscdmaSystemInfo->MNC);
            }
            break;
        default:
            break;
        } /* switch (pServiceStatusInfo->TLYType) */

        remainingLen -= (le16_to_cpu(pServiceStatusInfo->TLVLength) + 3);
        pServiceStatusInfo = (PSERVICE_STATUS_INFO)((PCHAR)&pServiceStatusInfo->TLVLength + le16_to_cpu(pServiceStatusInfo->TLVLength) + sizeof(USHORT));
    } /* while (remainingLen > 0) */

    if (DataCapList & WWAN_DATA_CLASS_LTE) {
        if (endc_available_valid && restrict_dcnr_valid) {
            if (endc_available && !restrict_dcnr) {
                DataCapList |= WWAN_DATA_CLASS_5G_NSA;
            }
        }
    }

    if (DeviceClass == DEVICE_CLASS_CDMA) {
        if (s_hdr_personality == 2) {
            pDataCapStr = s_hdr_personality == 2 ? "eHRPD" : "HRPD";
        } else if (DataCapList & WWAN_DATA_CLASS_1XEVDO_REVB) {
            pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_1XEVDO_REVB);
        } else if (DataCapList & WWAN_DATA_CLASS_1XEVDO_REVA) {
            pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_1XEVDO_REVA);
        } else if (DataCapList & WWAN_DATA_CLASS_1XEVDO) {
            pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_1XEVDO);
        } else if (DataCapList & WWAN_DATA_CLASS_1XRTT) {
            pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_1XRTT);
        } else if (DataCapList & WWAN_DATA_CLASS_3XRTT) {
            pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_3XRTT);
        } else if (DataCapList & WWAN_DATA_CLASS_UMB) {
            pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_UMB);
        }
    } else {
        if (DataCapList & WWAN_DATA_CLASS_5G_SA) {
            s_5g_type = WWAN_DATA_CLASS_5G_SA;
            pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_5G_SA);
        } else if (DataCapList & WWAN_DATA_CLASS_5G_NSA) {
            s_5g_type = WWAN_DATA_CLASS_5G_NSA;
            pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_5G_NSA);
        } else if (DataCapList & WWAN_DATA_CLASS_LTE) {
            pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_LTE);
        } else if ((DataCapList & WWAN_DATA_CLASS_HSDPA) && (DataCapList & WWAN_DATA_CLASS_HSUPA)) {
            pDataCapStr = "HSDPA_HSUPA";
        } else if (DataCapList & WWAN_DATA_CLASS_HSDPA) {
            pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_HSDPA);
        } else if (DataCapList & WWAN_DATA_CLASS_HSUPA) {
            pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_HSUPA);
        } else if (DataCapList & WWAN_DATA_CLASS_UMTS) {
            pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_UMTS);
        } else if (DataCapList & WWAN_DATA_CLASS_EDGE) {
            pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_EDGE);
        } else if (DataCapList & WWAN_DATA_CLASS_GPRS) {
            pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_GPRS);
        }
    }

    dbg_time("%s MCC: %d, MNC: %d, PS: %s, DataCap: %s", __func__,
        MobileCountryCode, MobileNetworkCode, (*pPSAttachedState == 1) ? "Attached" : "Detached" , pDataCapStr);

    free(pResponse);

    return 0;
}

static int requestRegistrationState(UCHAR *pPSAttachedState) {
    PQCQMIMSG pRequest;
    PQCQMIMSG pResponse;
    PQMUX_MSG pMUXMsg;
    int err;
    PQMINAS_CURRENT_PLMN_MSG pCurrentPlmn;
    PSERVING_SYSTEM pServingSystem;
    PQMINAS_DATA_CAP pDataCap;
    USHORT MobileCountryCode = 0;
    USHORT MobileNetworkCode = 0;
    const char *pDataCapStr = "UNKNOW";

    if (s_9x07) {
        return requestRegistrationState2(pPSAttachedState);
    }

    pRequest = ComposeQMUXMsg(QMUX_TYPE_NAS, QMINAS_GET_SERVING_SYSTEM_REQ, NULL, NULL);
    err = QmiThreadSendQMI(pRequest, &pResponse);
    qmi_rsp_check_and_return();

    pCurrentPlmn = (PQMINAS_CURRENT_PLMN_MSG)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x12);
    if (pCurrentPlmn) {
        MobileCountryCode = le16_to_cpu(pCurrentPlmn->MobileCountryCode);
        MobileNetworkCode = le16_to_cpu(pCurrentPlmn->MobileNetworkCode);
    }

    *pPSAttachedState = 0;
    pServingSystem = (PSERVING_SYSTEM)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x01);
    if (pServingSystem) {
    //Packet-switched domain attach state of the mobile.
    //0x00    PS_UNKNOWN ?Unknown or not applicable
    //0x01    PS_ATTACHED ?Attached
    //0x02    PS_DETACHED ?Detached
        *pPSAttachedState = pServingSystem->RegistrationState;
        if (pServingSystem->RegistrationState == 0x01) //0x01 ?C REGISTERED ?C Registered with a network
            *pPSAttachedState  = pServingSystem->PSAttachedState;
        else {
            //MobileCountryCode = MobileNetworkCode = 0;
            *pPSAttachedState  = 0x02;
        }
    }

    pDataCap = (PQMINAS_DATA_CAP)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11);
    if (pDataCap && pDataCap->DataCapListLen) {
        UCHAR *DataCap = &pDataCap->DataCap;
        if (pDataCap->DataCapListLen == 2) {
            if ((DataCap[0] == 0x06) && ((DataCap[1] == 0x08) || (DataCap[1] == 0x0A)))
                DataCap[0] = DataCap[1];
        }
        switch (DataCap[0]) {
             case 0x01: pDataCapStr = "GPRS"; break;
             case 0x02: pDataCapStr = "EDGE"; break;
             case 0x03: pDataCapStr = "HSDPA"; break;
             case 0x04: pDataCapStr = "HSUPA"; break;
             case 0x05: pDataCapStr = "UMTS"; break;
             case 0x06: pDataCapStr = "1XRTT"; break;
             case 0x07: pDataCapStr = "1XEVDO"; break;
             case 0x08: pDataCapStr = "1XEVDO_REVA"; break;
             case 0x09: pDataCapStr = "GPRS"; break;
             case 0x0A: pDataCapStr = "1XEVDO_REVB"; break;
             case 0x0B: pDataCapStr = "LTE"; break;
             case 0x0C: pDataCapStr = "HSDPA"; break;
             case 0x0D: pDataCapStr = "HSDPA"; break;
             default: pDataCapStr = "UNKNOW"; break;
        }
    }

    if (pServingSystem && pServingSystem->RegistrationState == 0x01 && pServingSystem->InUseRadioIF && pServingSystem->RadioIF == 0x09) {
        pDataCapStr = "TD-SCDMA";
    }

    s_is_cdma = 0;
    if (pServingSystem && pServingSystem->RegistrationState == 0x01 && pServingSystem->InUseRadioIF && (pServingSystem->RadioIF == 0x01 || pServingSystem->RadioIF == 0x02)) {
        USHORT cmda_mcc = 0, cdma_mnc = 0;
        s_is_cdma = 1;
        if(!requestGetHomeNetwork(&cmda_mcc, &cdma_mnc,NULL, NULL) && cmda_mcc) {
            quectel_convert_cdma_mcc_2_ascii_mcc(&MobileCountryCode, cmda_mcc);
            quectel_convert_cdma_mnc_2_ascii_mnc(&MobileNetworkCode, cdma_mnc);
        }
        if (1) {
            PQCQMUX_TLV pTLV = (PQCQMUX_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x23);
            if (pTLV)
                s_hdr_personality = pTLV->Value;
            else
                s_hdr_personality = 0;
            if (s_hdr_personality == 2)
                pDataCapStr = "eHRPD";
        }
    }

    dbg_time("%s MCC: %d, MNC: %d, PS: %s, DataCap: %s", __func__,
        MobileCountryCode, MobileNetworkCode, (*pPSAttachedState == 1) ? "Attached" : "Detached" , pDataCapStr);

    free(pResponse);

    return 0;
}

static int requestQueryDataCall(UCHAR  *pConnectionStatus, int curIpFamily) {
    PQCQMIMSG pRequest;
    PQCQMIMSG pResponse;
    PQMUX_MSG pMUXMsg;
    int err;
    PQMIWDS_PKT_SRVC_TLV pPktSrvc;
    UCHAR oldConnectionStatus = *pConnectionStatus;
    UCHAR QMIType = (curIpFamily == IpFamilyV4) ? QMUX_TYPE_WDS : QMUX_TYPE_WDS_IPV6;

    pRequest = ComposeQMUXMsg(QMIType, QMIWDS_GET_PKT_SRVC_STATUS_REQ, NULL, NULL);
    err = QmiThreadSendQMI(pRequest, &pResponse);
    qmi_rsp_check_and_return();

    *pConnectionStatus = QWDS_PKT_DATA_DISCONNECTED;
    pPktSrvc = (PQMIWDS_PKT_SRVC_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x01);
    if (pPktSrvc) {
        *pConnectionStatus = pPktSrvc->ConnectionStatus;
        if ((le16_to_cpu(pPktSrvc->TLVLength) == 2) && (pPktSrvc->ReconfigReqd == 0x01))
            *pConnectionStatus = QWDS_PKT_DATA_DISCONNECTED;
    }

    if (*pConnectionStatus == QWDS_PKT_DATA_DISCONNECTED) {
        if (curIpFamily == IpFamilyV4)
            WdsConnectionIPv4Handle = 0;
        else
            WdsConnectionIPv6Handle = 0;
    }

    if (oldConnectionStatus != *pConnectionStatus || debug_qmi) {
        dbg_time("%s %sConnectionStatus: %s", __func__, (curIpFamily == IpFamilyV4) ? "IPv4" : "IPv6",
            (*pConnectionStatus == QWDS_PKT_DATA_CONNECTED) ? "CONNECTED" : "DISCONNECTED");
    }

    free(pResponse);
    return 0;
}

static int requestSetupDataCall(PROFILE_T *profile, int curIpFamily) {
    PQCQMIMSG pRequest;
    PQCQMIMSG pResponse;
    PQMUX_MSG pMUXMsg;
    int err = 0;
    UCHAR QMIType = (curIpFamily == IpFamilyV4) ? QMUX_TYPE_WDS : QMUX_TYPE_WDS_IPV6;

//DualIPSupported means can get ipv4 & ipv6 address at the same time, one wds for ipv4, the other wds for ipv6
    profile->curIpFamily = curIpFamily;
    pRequest = ComposeQMUXMsg(QMIType, QMIWDS_START_NETWORK_INTERFACE_REQ, WdsStartNwInterfaceReq, profile);
    err = QmiThreadSendQMITimeout(pRequest, &pResponse, 120 * 1000, __func__);
    qmi_rsp_check();

    if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) {
        PQMI_TLV_HDR pTLVHdr;

        pTLVHdr = GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x10);
        if (pTLVHdr) {
            uint16_t *data16 = (uint16_t *)(pTLVHdr+1);
            uint16_t call_end_reason = le16_to_cpu(data16[0]);
            dbg_time("call_end_reason is %d", call_end_reason);
        }

        pTLVHdr = GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11);
        if (pTLVHdr) {
            uint16_t *data16 = (uint16_t *)(pTLVHdr+1);
            uint16_t call_end_reason_type = le16_to_cpu(data16[0]);
            uint16_t verbose_call_end_reason  = le16_to_cpu(data16[1]);

            dbg_time("call_end_reason_type is %d", call_end_reason_type);
            dbg_time("call_end_reason_verbose is %d", verbose_call_end_reason);
        }

        err = le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError);
        free(pResponse);
        return err;
    }

    if (curIpFamily == IpFamilyV4) {
        WdsConnectionIPv4Handle = le32_to_cpu(pResponse->MUXMsg.StartNwInterfaceResp.Handle);
        dbg_time("%s WdsConnectionIPv4Handle: 0x%08x", __func__, WdsConnectionIPv4Handle);
    } else {
        WdsConnectionIPv6Handle = le32_to_cpu(pResponse->MUXMsg.StartNwInterfaceResp.Handle);
        dbg_time("%s WdsConnectionIPv6Handle: 0x%08x", __func__, WdsConnectionIPv6Handle);
    }

    free(pResponse);

    return 0;
}

static int requestDeactivateDefaultPDP(PROFILE_T *profile, int curIpFamily) {
    PQCQMIMSG pRequest;
    PQCQMIMSG pResponse;
    PQMUX_MSG pMUXMsg;
    int err;
    UCHAR QMIType = (curIpFamily == 0x04) ? QMUX_TYPE_WDS : QMUX_TYPE_WDS_IPV6;

    (void)profile;
    if (curIpFamily == IpFamilyV4 && WdsConnectionIPv4Handle == 0)
        return 0;
    if (curIpFamily == IpFamilyV6 && WdsConnectionIPv6Handle == 0)
        return 0;

    dbg_time("%s WdsConnectionIPv%dHandle", __func__, curIpFamily == IpFamilyV4 ? 4 : 6);

    pRequest = ComposeQMUXMsg(QMIType, QMIWDS_STOP_NETWORK_INTERFACE_REQ , WdsStopNwInterfaceReq, &curIpFamily);
    err = QmiThreadSendQMI(pRequest, &pResponse);
    qmi_rsp_check_and_return();

    if (curIpFamily == IpFamilyV4)
        WdsConnectionIPv4Handle = 0;
    else
         WdsConnectionIPv6Handle = 0;
    free(pResponse);
    return 0;
}

static int requestGetIPAddress(PROFILE_T *profile, int curIpFamily) {
    PQCQMIMSG pRequest;
    PQCQMIMSG pResponse;
    PQMUX_MSG pMUXMsg;
    int err;
    PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR pIpv4Addr;
    PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR pIpv6Addr = NULL;
    PQMIWDS_GET_RUNTIME_SETTINGS_TLV_MTU pMtu;
    IPV4_T *pIpv4 = &profile->ipv4;
    IPV6_T *pIpv6 = &profile->ipv6;
    UCHAR QMIType = (curIpFamily == 0x04) ? QMUX_TYPE_WDS : QMUX_TYPE_WDS_IPV6;
    PQMIWDS_GET_RUNNING_SETTINGS_PCSCF_IPV6_ADDR pPCSCFIpv6Addr;
	PQMIWDS_GET_RUNNING_SETTINGS_PCSCF_IPV4_ADDR pPCSCFIpv4Addr;

    if (curIpFamily == IpFamilyV4) {
        memset(pIpv4, 0x00, sizeof(IPV4_T));
        if (WdsConnectionIPv4Handle == 0)
            return 0;
    } else if (curIpFamily == IpFamilyV6) {
        memset(pIpv6, 0x00, sizeof(IPV6_T));
        if (WdsConnectionIPv6Handle == 0)
            return 0;
    }

    pRequest = ComposeQMUXMsg(QMIType, QMIWDS_GET_RUNTIME_SETTINGS_REQ, WdsGetRuntimeSettingReq, NULL);
    err = QmiThreadSendQMI(pRequest, &pResponse);
    qmi_rsp_check_and_return();

	pPCSCFIpv6Addr = (PQMIWDS_GET_RUNNING_SETTINGS_PCSCF_IPV6_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x2e);    // 0x2e - pcscf ipv6 address 
	if (pPCSCFIpv6Addr) {
    	if (pPCSCFIpv6Addr->PCSCFNumber == 1) {
        	UCHAR *PCSCFIpv6Addr1 = (UCHAR *)(pPCSCFIpv6Addr + 1);
        	memcpy(profile->PCSCFIpv6Addr1, PCSCFIpv6Addr1, 16);
        }else if (pPCSCFIpv6Addr->PCSCFNumber == 2) {
        	UCHAR *PCSCFIpv6Addr1 = (UCHAR *)(pPCSCFIpv6Addr + 1);
        	UCHAR *PCSCFIpv6Addr2 = PCSCFIpv6Addr1 + 16;
        	memcpy(profile->PCSCFIpv6Addr1, PCSCFIpv6Addr1, 16);
        	memcpy(profile->PCSCFIpv6Addr2, PCSCFIpv6Addr2, 16);
        }
    }
    
	pPCSCFIpv4Addr = (PQMIWDS_GET_RUNNING_SETTINGS_PCSCF_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x23);    // 0x23 - pcscf ipv4 address 
	if (pPCSCFIpv4Addr) {
    	if (pPCSCFIpv4Addr->PCSCFNumber == 1) {
        	UCHAR *PCSCFIpv4Addr1 = (UCHAR *)(pPCSCFIpv4Addr + 1);
        	memcpy(&profile->PCSCFIpv4Addr1, PCSCFIpv4Addr1, 4);
        }else if (pPCSCFIpv4Addr->PCSCFNumber == 2) {
        	UCHAR *PCSCFIpv4Addr1 = (UCHAR *)(pPCSCFIpv4Addr + 1);
        	UCHAR *PCSCFIpv4Addr2 = PCSCFIpv4Addr1 + 4;
        	memcpy(&profile->PCSCFIpv4Addr1, PCSCFIpv4Addr1, 4);
        	memcpy(&profile->PCSCFIpv4Addr2, PCSCFIpv4Addr2, 4);
        }
    }

    pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4PRIMARYDNS);
    if (pIpv4Addr) {
        pIpv4->DnsPrimary = pIpv4Addr->IPV4Address;
    }

    pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4SECONDARYDNS);
    if (pIpv4Addr) {
        pIpv4->DnsSecondary = pIpv4Addr->IPV4Address;
    }

    pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4GATEWAY);
    if (pIpv4Addr) {
        pIpv4->Gateway = pIpv4Addr->IPV4Address;
    }

    pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4SUBNET);
    if (pIpv4Addr) {
        pIpv4->SubnetMask = pIpv4Addr->IPV4Address;
    }

    pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4);
    if (pIpv4Addr) {
        pIpv4->Address = pIpv4Addr->IPV4Address;
    }

    pIpv6Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6PRIMARYDNS);
    if (pIpv6Addr) {
        memcpy(pIpv6->DnsPrimary, pIpv6Addr->IPV6Address, 16);
    }

    pIpv6Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6SECONDARYDNS);
    if (pIpv6Addr) {
        memcpy(pIpv6->DnsSecondary, pIpv6Addr->IPV6Address, 16);
    }

    pIpv6Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6GATEWAY);
    if (pIpv6Addr) {
        memcpy(pIpv6->Gateway, pIpv6Addr->IPV6Address, 16);
        pIpv6->PrefixLengthGateway = pIpv6Addr->PrefixLength;
    }

    pIpv6Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6);
    if (pIpv6Addr) {
        memcpy(pIpv6->Address, pIpv6Addr->IPV6Address, 16);
        pIpv6->PrefixLengthIPAddr = pIpv6Addr->PrefixLength;
    }

    pMtu = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_MTU)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_MTU);
    if (pMtu) {
        if (curIpFamily == IpFamilyV4)
            pIpv4->Mtu =  le32_to_cpu(pMtu->Mtu);
        else
            pIpv6->Mtu =  le32_to_cpu(pMtu->Mtu);
    }

    free(pResponse);
    return 0;
}

#ifdef CONFIG_APN
static int requestSetProfile(PROFILE_T *profile) {
    PQCQMIMSG pRequest;
    PQCQMIMSG pResponse;
    PQMUX_MSG pMUXMsg;
    int err;
    const char *new_apn = profile->apn ? profile->apn : "";
    const char *new_user = profile->user ? profile->user : "";
    const char *new_pd = profile->pd ? profile->pd : "";
    const char *ipStr[] = {"IPV4", "NULL", "IPV6", "IPV4V6"};

    dbg_time("%s[pdp:%d index:%d] %s/%s/%s/%d/%s", __func__, profile->pdp, profile->profile_index, profile->apn, profile->user, profile->pd, profile->auth,ipStr[profile->iptype]);
    if (!profile->profile_index)
        return -1;

    if ( !strcmp(profile->old_apn, new_apn) && !strcmp(profile->old_user, new_user)
        && !strcmp(profile->old_pd, new_pd)
        && profile->old_iptype == profile->iptype
        && profile->old_auth == profile->auth)
    {
        dbg_time("no need to set skip the rest");
        return 0;
    }

    pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_MODIFY_PROFILE_SETTINGS_REQ, WdsModifyProfileSettingsReq, profile);
    err = QmiThreadSendQMI(pRequest, &pResponse);
    qmi_rsp_check_and_return();

    free(pResponse);
    return 1;
}

static int requestGetProfile(PROFILE_T *profile) {
    PQCQMIMSG pRequest;
    PQCQMIMSG pResponse;
    PQMUX_MSG pMUXMsg;
    int err;
    PQMIWDS_APNNAME pApnName;
    PQMIWDS_USERNAME pUserName;
    PQMIWDS_PD pPd;
    PQMIWDS_AUTH_PREFERENCE pAuthPref;
    PQMIWDS_IPTYPE pIpType;
    PQMIWDS_PDPCONTEXT pPdpContext;
    PQMIWDS_PROFILELIST pProfileList;
    
    const char *ipStr[] = {"IPV4", "NULL", "IPV6", "IPV4V6"};

    profile->old_apn[0] = profile->old_user[0] = profile->old_pd[0] = '\0';
    profile->old_auth = 0;
    profile->old_iptype = 0;
    if (profile->enable_ipv4 && profile->enable_ipv6)
        profile->iptype = 3;
    else if (profile->enable_ipv6)
        profile->iptype = 2;
    else
        profile->iptype = 0;

    if (!profile->pdp)
        return 0;

_re_check:
    pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_GET_PROFILE_LIST_REQ, WdsGetProfileListReqSend, profile);
    err = QmiThreadSendQMI(pRequest, &pResponse);s_pResponse = malloc(le16_to_cpu(pResponse->QMIHdr.Length) + 1);
    qmi_rsp_check_and_return();
    
    pProfileList = (PQMIWDS_PROFILELIST)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x01);
    uint8 profile_indexs[42] = {0};
    uint8 profile_num = pProfileList->ProfileList[0];
    uint8 i;
    if(profile_num >= 1)
    {
        uint8 j = 0;
        uint8 k = 2;
        for(i=0; i<profile_num; i++)
        {
            profile_indexs[j++] = pProfileList->ProfileList[k];
            if(pProfileList->ProfileList[++k] == 0)
                k+=2;
            else
                k+=2+pProfileList->ProfileList[k];
        }
    }
    free(pResponse);
    
    for(i=0; i<profile_num; i++)
    {
        profile->profile_index = profile_indexs[i];
        
        pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_GET_PROFILE_SETTINGS_REQ, WdsGetProfileSettingsReqSend, profile);
        err = QmiThreadSendQMI(pRequest, &pResponse);
        qmi_rsp_check_and_return();
        
        pPdpContext = (PQMIWDS_PDPCONTEXT)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x25);
        if(pPdpContext->pdp_context == profile->pdp)
            break;
        else
            free(pResponse);

        if(i == profile_num-1)
        {
            pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_CREATE_PROFILE_REQ, WdsCreateProfileSettingsReqSend, profile);
            err = QmiThreadSendQMI(pRequest, &pResponse);
            qmi_rsp_check_and_return();
            free(pResponse);
            goto _re_check;
        }
    }
    

    pApnName = (PQMIWDS_APNNAME)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x14);
    pUserName = (PQMIWDS_USERNAME)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x1B);
    pPd = (PQMIWDS_PD)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x1C);
    pAuthPref = (PQMIWDS_AUTH_PREFERENCE)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x1D);
    pIpType = (PQMIWDS_IPTYPE)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11);

    if (pApnName/* && le16_to_cpu(pApnName->TLVLength)*/)
        uchar2char(profile->old_apn, sizeof(profile->old_apn), &pApnName->ApnName, le16_to_cpu(pApnName->TLVLength));
    if (pUserName/*  && pUserName->UserName*/)
        uchar2char(profile->old_user, sizeof(profile->old_user), &pUserName->UserName, le16_to_cpu(pUserName->TLVLength));
    if (pPd/*  && le16_to_cpu(pPd->TLVLength)*/)
        uchar2char(profile->old_pd, sizeof(profile->old_pd), &pPd->Pd, le16_to_cpu(pPd->TLVLength));
    if (pAuthPref/*  && le16_to_cpu(pAuthPref->TLVLength)*/) {
        profile->old_auth = pAuthPref->AuthPreference;
    }
    if (pIpType) {
        profile->old_iptype = pIpType->IPType;
    }

    dbg_time("%s[pdp:%d index:%d] %s/%s/%s/%d/%s", __func__, profile->pdp, profile->profile_index, profile->old_apn, profile->old_user, profile->old_pd, profile->old_auth, ipStr[profile->old_iptype]);

    free(pResponse);
    return 0;
}
#endif

#ifdef CONFIG_SIGNALINFO
static int requestGetSignalInfo(void)
{
    PQCQMIMSG pRequest;
    PQCQMIMSG pResponse;
    PQMUX_MSG pMUXMsg;
    int err;

    pRequest = ComposeQMUXMsg(QMUX_TYPE_NAS, QMINAS_GET_SIG_INFO_REQ, NULL, NULL);
    err = QmiThreadSendQMI(pRequest, &pResponse);
    qmi_rsp_check_and_return();

    
    // CDMA
    {
        PQMINAS_SIG_INFO_CDMA_TLV_MSG ptlv = (PQMINAS_SIG_INFO_CDMA_TLV_MSG)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x10);
        if (ptlv && ptlv->TLVLength)
        {
            dbg_time("%s CDMA: RSSI %d dBm, ECIO %.1lf dBm", __func__,
                ptlv->rssi, (-0.5) * (double)ptlv->ecio);
        }
    }

    // HDR
    {
        PQMINAS_SIG_INFO_HDR_TLV_MSG ptlv = (PQMINAS_SIG_INFO_HDR_TLV_MSG)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11);
        if (ptlv && ptlv->TLVLength)
        {
            dbg_time("%s HDR: RSSI %d dBm, ECIO %.1lf dBm, IO %d dBm", __func__,
                ptlv->rssi, (-0.5) * (double)ptlv->ecio, ptlv->io);
        }
    }

    // GSM
    {
        PQMINAS_SIG_INFO_GSM_TLV_MSG ptlv = (PQMINAS_SIG_INFO_GSM_TLV_MSG)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x12);
        if (ptlv && ptlv->TLVLength)
        {
            dbg_time("%s GSM: RSSI %d dBm", __func__, ptlv->rssi);
        }
    }

    // WCDMA
    {
        PQMINAS_SIG_INFO_WCDMA_TLV_MSG ptlv = (PQMINAS_SIG_INFO_WCDMA_TLV_MSG)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x13);
        if (ptlv && ptlv->TLVLength)
        {
            dbg_time("%s WCDMA: RSSI %d dBm, ECIO %.1lf dBm", __func__,
                ptlv->rssi, (-0.5) * (double)ptlv->ecio);
        }
    }

    // LTE
    {
        PQMINAS_SIG_INFO_LTE_TLV_MSG ptlv = (PQMINAS_SIG_INFO_LTE_TLV_MSG)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x14);
        if (ptlv && ptlv->TLVLength)
        {
            dbg_time("%s LTE: RSSI %d dBm, RSRQ %d dB, RSRP %d dBm, SNR %.1lf dB", __func__,
                ptlv->rssi, ptlv->rsrq, ptlv->rsrp, (0.1) * (double)ptlv->snr);
        }
    }

    // TDSCDMA
    {
        PQMINAS_SIG_INFO_TDSCDMA_TLV_MSG ptlv = (PQMINAS_SIG_INFO_TDSCDMA_TLV_MSG)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x15);
        if (ptlv && ptlv->TLVLength)
        {
            dbg_time("%s LTE: RSCP %d dBm", __func__, ptlv->rscp);
        }
    }

    // 5G_NSA
    if (s_5g_type == WWAN_DATA_CLASS_5G_NSA)
    {
        PQMINAS_SIG_INFO_5G_NSA_TLV_MSG ptlv = (PQMINAS_SIG_INFO_5G_NSA_TLV_MSG)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x17);
        if (ptlv && ptlv->TLVLength)
        {
            dbg_time("%s 5G_NSA: RSRP %d dBm, SNR %.1lf dB", __func__, ptlv->rsrp, (0.1) * (double)ptlv->snr);
        }
    }

    // 5G_SA
    if (s_5g_type == WWAN_DATA_CLASS_5G_SA)
    {
        PQMINAS_SIG_INFO_5G_SA_TLV_MSG ptlv = (PQMINAS_SIG_INFO_5G_SA_TLV_MSG)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x18);
        if (ptlv && ptlv->TLVLength)
        {
            dbg_time("%s 5G_SA: NR5G_RSRQ %d dB", __func__, ptlv->nr5g_rsrq);
        }
    }

    free(pResponse);
    return 0;
}
#endif

#ifdef CONFIG_VERSION
static int requestBaseBandVersion(PROFILE_T *profile) {
    PQCQMIMSG pRequest;
    PQCQMIMSG pResponse;
    PQMUX_MSG pMUXMsg;
    PDEVICE_REV_ID revId;
    int err;

    pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_GET_DEVICE_REV_ID_REQ, NULL, NULL);
    err = QmiThreadSendQMI(pRequest, &pResponse);
    qmi_rsp_check_and_return();

    revId = (PDEVICE_REV_ID)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x01);

    if (revId && le16_to_cpu(revId->TLVLength))
    {
        uchar2char(profile->BaseBandVersion, sizeof(profile->BaseBandVersion), &revId->RevisionID, le16_to_cpu(revId->TLVLength));
        dbg_time("%s %s", __func__, profile->BaseBandVersion);
    }

    free(pResponse);
    return 0;
}
#endif

static USHORT DmsSetOperatingModeReq(PQMUX_MSG pMUXMsg, void *arg) {
    pMUXMsg->SetOperatingModeReq.TLVType = 0x01;
    pMUXMsg->SetOperatingModeReq.TLVLength = cpu_to_le16(1);
    pMUXMsg->SetOperatingModeReq.OperatingMode = *((UCHAR *)arg);

    return sizeof(QMIDMS_SET_OPERATING_MODE_REQ_MSG);
}

static USHORT UimSetCardSlotReq(PQMUX_MSG pMUXMsg, void *arg) {
    pMUXMsg->UIMSetCardSlotReq.TLVType = 0x01;
    pMUXMsg->UIMSetCardSlotReq.TLVLength = cpu_to_le16(1);
    pMUXMsg->UIMSetCardSlotReq.slot = *((UCHAR *)arg);

    return sizeof(QMIUIM_SET_CARD_SLOT_REQ_MSG);
}

static int requestRadioPower(int state) {
    PQCQMIMSG pRequest;
    PQCQMIMSG pResponse;
    PQMUX_MSG pMUXMsg;
    int err;
    UCHAR OperatingMode = (!!state) ? DMS_OP_MODE_ONLINE : DMS_OP_MODE_LOW_POWER;
    USHORT SimOp = (!!state) ? QMIUIM_POWER_UP : QMIUIM_POWER_DOWN;
    UCHAR cardSlot = 0x01;

    dbg_time("%s(%d)", __func__, state);

    pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_SET_OPERATING_MODE_REQ, DmsSetOperatingModeReq, &OperatingMode);
    err = QmiThreadSendQMI(pRequest, &pResponse);
    qmi_rsp_check_and_return();
    free(pResponse);

    pRequest = ComposeQMUXMsg(QMUX_TYPE_UIM, SimOp, UimSetCardSlotReq, &cardSlot);
    err = QmiThreadSendQMI(pRequest, &pResponse);
    qmi_rsp_check_and_return();
    free(pResponse);

    return 0;
}

static USHORT WdaSetLoopBackReq(PQMUX_MSG pMUXMsg, void *arg) {
    (void)arg;
	pMUXMsg->SetLoopBackReq.loopback_state.TLVType  = 0x01;
	pMUXMsg->SetLoopBackReq.loopback_state.TLVLength = cpu_to_le16(1);

	pMUXMsg->SetLoopBackReq.replication_factor.TLVType	= 0x10;
	pMUXMsg->SetLoopBackReq.replication_factor.TLVLength = cpu_to_le16(4);

	return sizeof(QMI_WDA_SET_LOOPBACK_CONFIG_REQ_MSG);
}

static int requestSetLoopBackState(UCHAR loopback_state, ULONG replication_factor) {
    PQCQMIMSG pRequest;
    PQCQMIMSG pResponse;
    PQMUX_MSG pMUXMsg;
    int err;

    dbg_time("%s(loopback_state=%d, replication_factor=%u)", __func__, loopback_state, replication_factor);
    
    pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS_ADMIN, QMI_WDA_SET_LOOPBACK_CONFIG_REQ, WdaSetLoopBackReq, NULL);
    pRequest->MUXMsg.SetLoopBackReq.loopback_state.TLVVaule = loopback_state;
    pRequest->MUXMsg.SetLoopBackReq.replication_factor.TLVVaule = cpu_to_le16(replication_factor);

    err = QmiThreadSendQMI(pRequest, &pResponse);
    qmi_rsp_check_and_return();

    free(pResponse);
    return 0;
}

#ifdef CONFIG_ENABLE_QOS
static USHORT QosSetBindMuxDataPort(PQMUX_MSG pMUXMsg, void *arg) {
    PROFILE_T *profile = (PROFILE_T *)arg;
    pMUXMsg->QosBindDataPortReq.EpIdTlv.TLVType = 0x10;
    pMUXMsg->QosBindDataPortReq.EpIdTlv.TLVLength = cpu_to_le16(8);
    pMUXMsg->QosBindDataPortReq.EpIdTlv.ep_type = cpu_to_le32(profile->rmnet_info.ep_type);
    pMUXMsg->QosBindDataPortReq.EpIdTlv.iface_id = cpu_to_le32(profile->rmnet_info.iface_id);
    pMUXMsg->QosBindDataPortReq.MuxIdTlv.TLVType = 0x11;
    pMUXMsg->QosBindDataPortReq.MuxIdTlv.TLVLength = cpu_to_le16(1);
    pMUXMsg->QosBindDataPortReq.MuxIdTlv.mux_id = profile->muxid;
    return sizeof(QMI_QOS_BIND_DATA_PORT_REQ_MSG);
}

#ifdef CONFIG_REG_QOS_IND
static USHORT QosIndRegReq(PQMUX_MSG pMUXMsg, void *arg) {
    pMUXMsg->QosIndRegReq.ReportGlobalQosFlowTlv.TLVType = 0x10;
    pMUXMsg->QosIndRegReq.ReportGlobalQosFlowTlv.TLVLength = cpu_to_le16(1);
    pMUXMsg->QosIndRegReq.ReportGlobalQosFlowTlv.report_global_qos_flows = 1;
    return sizeof(QMI_QOS_INDICATION_REGISTER_REQ_MSG);
}
#endif

static int requestRegisterQos(PROFILE_T *profile) {
    PQCQMIMSG pRequest;
    PQCQMIMSG pResponse = NULL;
    PQMUX_MSG pMUXMsg;
    int err;

    pRequest = ComposeQMUXMsg(QMUX_TYPE_QOS, QMI_QOS_BIND_DATA_PORT_REQ , QosSetBindMuxDataPort, (void *)profile);
    err = QmiThreadSendQMI(pRequest, &pResponse);
    dbg_time("%s QosSetBindMuxDataPort", __func__);
    qmi_rsp_check_and_return();
    if (pResponse) free(pResponse);
	
#ifdef CONFIG_REG_QOS_IND
    pRequest = ComposeQMUXMsg(QMUX_TYPE_QOS, QMI_QOS_INDICATION_REGISTER_REQ , QosIndRegReq, NULL);
    err = QmiThreadSendQMI(pRequest, &pResponse);
    dbg_time("%s QosIndRegReq", __func__);
    qmi_rsp_check_and_return();
    if (pResponse) free(pResponse);
#endif
    return 0;
}

#ifdef CONFIG_GET_QOS_INFO
UCHAR ql_get_qos_info_data_rate(PQCQMIMSG pResponse, void *max_data_rate)
{
    PQMI_QOS_GET_QOS_INFO_TLV_GRANTED_FLOW qos_tx_granted_flow = NULL;
    PQMI_QOS_GET_QOS_INFO_TLV_GRANTED_FLOW qos_rx_granted_flow = NULL;
    qos_tx_granted_flow = (PQMI_QOS_GET_QOS_INFO_TLV_GRANTED_FLOW)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11);
    if(qos_tx_granted_flow != NULL)
    {
        *(ULONG64 *)(max_data_rate) = le64_to_cpu(qos_tx_granted_flow->data_rate_max);
        dbg_time("GET_QOS_INFO: tx_data_rate_max=%llu", *(ULONG64 *)(max_data_rate+0));
    }
    else
        dbg_time("GET_QOS_INFO: No qos_tx_granted_flow");
    qos_rx_granted_flow = (PQMI_QOS_GET_QOS_INFO_TLV_GRANTED_FLOW)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x12);
    if(qos_rx_granted_flow != NULL)
    {
        *(ULONG64 *)(max_data_rate+sizeof(ULONG64)) = le64_to_cpu(qos_rx_granted_flow->data_rate_max);
        dbg_time("GET_QOS_INFO: rx_data_rate_max=%llu", *(ULONG64 *)(max_data_rate+sizeof(ULONG64)));
    }
    else
        dbg_time("GET_QOS_INFO: No qos_rx_granted_flow");
    if(qos_tx_granted_flow != NULL || qos_rx_granted_flow != NULL)
        return 0;
    else
        return 1;
}

static USHORT QosGetQosInfoReq(PQMUX_MSG pMUXMsg, void *arg) {
    PROFILE_T *profile = (PROFILE_T *)arg;
    pMUXMsg->QosGetQosInfoReq.QosIdTlv.TLVType = 0x01;
    pMUXMsg->QosGetQosInfoReq.QosIdTlv.TLVLength = cpu_to_le16(4);
    pMUXMsg->QosGetQosInfoReq.QosIdTlv.qos_id = cpu_to_le32(profile->qos_id);
    return sizeof(QMI_QOS_GET_QOS_INFO_REQ_MSG);
}

static int requestGetQosInfo(PROFILE_T *profile) {
    PQCQMIMSG pRequest;
    PQCQMIMSG pResponse = NULL;
    PQMUX_MSG pMUXMsg;
    int err;

    if(profile->qos_id == 0)
    {
        dbg_time("%s request not send: invalid qos_id", __func__);
        return 0;
    }
    pRequest = ComposeQMUXMsg(QMUX_TYPE_QOS, QMI_QOS_GET_QOS_INFO_REQ , QosGetQosInfoReq, (void *)profile);
    err = QmiThreadSendQMI(pRequest, &pResponse);
    qmi_rsp_check_and_return();
    if (pResponse)
    {
#ifdef CONFIG_GET_QOS_DATA_RATE
        ULONG64 max_data_rate[2] = {0};
        if(ql_get_qos_info_data_rate(pResponse, (void *)max_data_rate) == 0){}
#endif
        free(pResponse);
    }
    return 0;
}
#endif //#ifdef CONFIG_GET_QOS_INFO

#ifdef CONFIG_REG_QOS_IND
UCHAR ql_get_global_qos_flow_ind_qos_id(PQCQMIMSG pResponse, UINT *qos_id)
{
    PQMI_QOS_GLOBAL_QOS_FLOW_TLV_FLOW_STATE qos_flow_state = NULL;
    qos_flow_state = (PQMI_QOS_GLOBAL_QOS_FLOW_TLV_FLOW_STATE)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x01);
    if(qos_flow_state != NULL)
    {
        if(le32_to_cpu(qos_flow_state->state_change) == QOS_IND_FLOW_STATE_ACTIVATED && qos_flow_state->new_flow == 1)
        {
            *qos_id = le32_to_cpu(qos_flow_state->qos_id);
            dbg_time("QMI_QOS_GLOBAL_QOS_FLOW_IND: qos_id=%u state=QOS_IND_FLOW_STATE_ACTIVATED", *qos_id);
        }
        return (qos_flow_state->new_flow);
    }
    return (0);
}

#ifdef CONFIG_GET_QOS_DATA_RATE
UCHAR ql_get_global_qos_flow_ind_data_rate(PQCQMIMSG pResponse, void *max_data_rate)
{
    PQMI_QOS_GLOBAL_QOS_FLOW_TLV_FLOW_GRANTED qos_tx_flow_granted = NULL;
    PQMI_QOS_GLOBAL_QOS_FLOW_TLV_FLOW_GRANTED qos_rx_flow_granted = NULL;
    qos_tx_flow_granted = (PQMI_QOS_GLOBAL_QOS_FLOW_TLV_FLOW_GRANTED)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x10);
    if(qos_tx_flow_granted != NULL)
    {
        *(ULONG64 *)(max_data_rate) = le64_to_cpu(qos_tx_flow_granted->data_rate_max);
        dbg_time("QMI_QOS_GLOBAL_QOS_FLOW_IND: tx_data_rate_max=%llu", *(ULONG64 *)(max_data_rate+0));
    }
    else
    dbg_time("QMI_QOS_GLOBAL_QOS_FLOW_IND: No qos_tx_flow_granted");
    qos_rx_flow_granted = (PQMI_QOS_GLOBAL_QOS_FLOW_TLV_FLOW_GRANTED)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11);
    if(qos_rx_flow_granted != NULL)
    {
        *(ULONG64 *)(max_data_rate+sizeof(ULONG64)) = le64_to_cpu(qos_rx_flow_granted->data_rate_max);
        dbg_time("QMI_QOS_GLOBAL_QOS_FLOW_IND: rx_data_rate_max=%llu", *(ULONG64 *)(max_data_rate+sizeof(ULONG64)));
    }
    else
        dbg_time("QMI_QOS_GLOBAL_QOS_FLOW_IND: No qos_rx_flow_granted");
    if(qos_tx_flow_granted != NULL || qos_rx_flow_granted != NULL)
        return 0;
    else
        return 1;
}
#endif
#endif //#ifdef CONFIG_REG_QOS_IND
#endif //#ifdef CONFIG_ENABLE_QOS

#ifdef CONFIG_CELLINFO
/*
    at+qeng="servingcell" and at+qeng="neighbourcell"
    https://gitlab.freedesktop.org/mobile-broadband/libqmi/-/blob/master/src/qmicli/qmicli-nas.c
*/
static int nas_get_cell_location_info(void);
static int nas_get_rf_band_information(void);

static int requestGetCellInfoList(void) {
    dbg_time("%s", __func__);
    nas_get_cell_location_info();
    nas_get_rf_band_information();
    return 0;
}
#endif

const struct request_ops qmi_request_ops = {
#ifdef CONFIG_VERSION
    .requestBaseBandVersion = requestBaseBandVersion,
#endif
    .requestSetEthMode = requestSetEthMode,
#ifdef CONFIG_SIM
    .requestGetSIMStatus = requestGetSIMStatus,
    .requestEnterSimPin = requestEnterSimPin,
#endif
#ifdef CONFIG_IMSI_ICCID
    .requestGetICCID = requestGetICCID,
    .requestGetIMSI = requestGetIMSI,
#endif
#ifdef CONFIG_APN
    .requestSetProfile = requestSetProfile,
    .requestGetProfile = requestGetProfile,
#endif
    .requestRegistrationState = requestRegistrationState,
    .requestSetupDataCall = requestSetupDataCall,
    .requestQueryDataCall = requestQueryDataCall,
    .requestDeactivateDefaultPDP = requestDeactivateDefaultPDP,
    .requestGetIPAddress = requestGetIPAddress,
#ifdef CONFIG_SIGNALINFO
    .requestGetSignalInfo = requestGetSignalInfo,
#endif
#ifdef CONFIG_CELLINFO
    .requestGetCellInfoList = requestGetCellInfoList,
#endif
    .requestSetLoopBackState = requestSetLoopBackState,
    .requestRadioPower = requestRadioPower,
#ifdef CONFIG_ENABLE_QOS
    .requestRegisterQos = requestRegisterQos,
#endif
#ifdef CONFIG_GET_QOS_INFO
    .requestGetQosInfo = requestGetQosInfo,
#endif
#ifdef CONFIG_COEX_WWAN_STATE
    .requestGetCoexWWANState = requestGetCoexWWANState,
#endif
};

#ifdef CONFIG_CELLINFO
static char *str_from_bcd_plmn (uint8 plmn[3])
{
    const char bcd_chars[] = "0123456789*#abc\0\0";
    static char str[12];
    int i;
    int j = 0;

    for (i = 0; i < 3; i++) {
        str[j] = bcd_chars[plmn[i]&0xF];
        if (str[j]) j++;
        str[j] = bcd_chars[plmn[i]>>4];
        if (str[j]) j++;
    }

    str[j++] = 0;

    return str;
}

typedef struct {
    UINT type;
    const char *name;
} ENUM_NAME_T;

#define enum_name(type) {type, #type}
#define N_ELEMENTS(arr)		(sizeof (arr) / sizeof ((arr)[0]))

static const ENUM_NAME_T QMI_NAS_ACTIVE_BAND_NAME[] = {
    enum_name(QMI_NAS_ACTIVE_BAND_BC_0),                  
    enum_name(QMI_NAS_ACTIVE_BAND_BC_1),                  
    enum_name(QMI_NAS_ACTIVE_BAND_BC_2),                  
    enum_name(QMI_NAS_ACTIVE_BAND_BC_3),                  
    enum_name(QMI_NAS_ACTIVE_BAND_BC_4),                  
    enum_name(QMI_NAS_ACTIVE_BAND_BC_5),                  
    enum_name(QMI_NAS_ACTIVE_BAND_BC_6),                  
    enum_name(QMI_NAS_ACTIVE_BAND_BC_7),                  
    enum_name(QMI_NAS_ACTIVE_BAND_BC_8),                  
    enum_name(QMI_NAS_ACTIVE_BAND_BC_9),                  
    enum_name(QMI_NAS_ACTIVE_BAND_BC_10),                 
    enum_name(QMI_NAS_ACTIVE_BAND_BC_11),                 
    enum_name(QMI_NAS_ACTIVE_BAND_BC_12),                 
    enum_name(QMI_NAS_ACTIVE_BAND_BC_13),                 
    enum_name(QMI_NAS_ACTIVE_BAND_BC_14),                 
    enum_name(QMI_NAS_ACTIVE_BAND_BC_15),                 
    enum_name(QMI_NAS_ACTIVE_BAND_BC_16),                 
    enum_name(QMI_NAS_ACTIVE_BAND_BC_17),                 
    enum_name(QMI_NAS_ACTIVE_BAND_BC_18),                 
    enum_name(QMI_NAS_ACTIVE_BAND_BC_19),                 
    enum_name(QMI_NAS_ACTIVE_BAND_GSM_450),               
    enum_name(QMI_NAS_ACTIVE_BAND_GSM_480),               
    enum_name(QMI_NAS_ACTIVE_BAND_GSM_750),               
    enum_name(QMI_NAS_ACTIVE_BAND_GSM_850),               
    enum_name(QMI_NAS_ACTIVE_BAND_GSM_900_EXTENDED),      
    enum_name(QMI_NAS_ACTIVE_BAND_GSM_900_PRIMARY),       
    enum_name(QMI_NAS_ACTIVE_BAND_GSM_900_RAILWAYS),      
    enum_name(QMI_NAS_ACTIVE_BAND_GSM_DCS_1800),          
    enum_name(QMI_NAS_ACTIVE_BAND_GSM_PCS_1900),          
    enum_name(QMI_NAS_ACTIVE_BAND_WCDMA_2100),            
    enum_name(QMI_NAS_ACTIVE_BAND_WCDMA_PCS_1900),        
    enum_name(QMI_NAS_ACTIVE_BAND_WCDMA_DCS_1800),        
    enum_name(QMI_NAS_ACTIVE_BAND_WCDMA_1700_US),         
    enum_name(QMI_NAS_ACTIVE_BAND_WCDMA_850),             
    enum_name(QMI_NAS_ACTIVE_BAND_WCDMA_800),             
    enum_name(QMI_NAS_ACTIVE_BAND_WCDMA_2600),            
    enum_name(QMI_NAS_ACTIVE_BAND_WCDMA_900),             
    enum_name(QMI_NAS_ACTIVE_BAND_WCDMA_1700_JAPAN),      
    enum_name(QMI_NAS_ACTIVE_BAND_WCDMA_1500_JAPAN),      
    enum_name(QMI_NAS_ACTIVE_BAND_WCDMA_850_JAPAN),       
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_1),              
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_2),              
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_3),              
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_4),              
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_5),              
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_6),              
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_7),              
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_8),              
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_9),              
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_10),             
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_11),             
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_12),             
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_13),             
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_14),             
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_17),             
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_18),             
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_19),             
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_20),             
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_21),             
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_23),             
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_24),             
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_25),             
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_26),             
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_27),             
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_28),             
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_29),             
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_30),             
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_31),             
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_32),             
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_33),             
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_34),             
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_35),             
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_36),             
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_37),             
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_38),             
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_39),             
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_40),             
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_41),             
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_42),             
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_43),             
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_46),             
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_47),             
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_48),             
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_66),             
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_71),             
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_125),            
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_126),            
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_127),            
    enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_250),            
    enum_name(QMI_NAS_ACTIVE_BAND_TDSCDMA_A),             
    enum_name(QMI_NAS_ACTIVE_BAND_TDSCDMA_B),             
    enum_name(QMI_NAS_ACTIVE_BAND_TDSCDMA_C),             
    enum_name(QMI_NAS_ACTIVE_BAND_TDSCDMA_D),             
    enum_name(QMI_NAS_ACTIVE_BAND_TDSCDMA_E),             
    enum_name(QMI_NAS_ACTIVE_BAND_TDSCDMA_F),             
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_1 ),          
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_2 ),          
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_3 ),          
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_5 ),          
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_7 ),          
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_8 ),          
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_20),          
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_28),          
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_38),          
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_41),          
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_50),          
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_51),          
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_66),          
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_70),          
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_71),          
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_74),          
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_75),          
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_76),          
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_77),          
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_78),          
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_79),          
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_80),          
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_81),          
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_82),          
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_83),          
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_84),          
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_85),          
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_257),         
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_258),         
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_259),         
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_260),         
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_261),         
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_12),          
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_25),          
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_34),          
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_39),          
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_40),          
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_65),          
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_86),          
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_48),          
    enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_14),          
};

static const char *qmi_nas_radio_interface_get_string(uint8 radio_if)
{
    const char *str = NULL;

    switch (radio_if) {
        case QMI_NAS_RADIO_INTERFACE_CDMA_1X: str = "cdma-1x"; break;
        case QMI_NAS_RADIO_INTERFACE_CDMA_1XEVDO: str = "cdma-1xevdo"; break;
        case QMI_NAS_RADIO_INTERFACE_AMPS: str = "amps"; break;
        case QMI_NAS_RADIO_INTERFACE_GSM: str = "gsm"; break;
        case QMI_NAS_RADIO_INTERFACE_UMTS: str = "umts"; break;
        case QMI_NAS_RADIO_INTERFACE_LTE: str = "lte"; break;
        case QMI_NAS_RADIO_INTERFACE_TD_SCDMA: str = "td-scdma"; break;
        case QMI_NAS_RADIO_INTERFACE_5GNR: str = "5gnr"; break;
        default: str = NULL; break;
    }

    return str ?  str : "unknown";
}

static const char *qmi_nas_active_band_get_string(uint32 active_band)
{
    size_t i;

    for (i = 0; i < N_ELEMENTS(QMI_NAS_ACTIVE_BAND_NAME); i++) {
        if (active_band == QMI_NAS_ACTIVE_BAND_NAME[i].type)
            return QMI_NAS_ACTIVE_BAND_NAME[i].name + strlen("QMI_NAS_ACTIVE_BAND_");
    }

    return "unknown";
}

typedef struct {
    uint16      min;
    uint16      max;
    const char *name;
} EarfcnRange;

/* http://niviuk.free.fr/lte_band.php */
static const EarfcnRange earfcn_ranges[] = {
    {     0,   599, "E-UTRA band 1: 2100"           },
    {   600,  1199, "E-UTRA band 2: 1900 PCS"       },
    {  1200,  1949, "E-UTRA band 3: 1800+"          },
    {  1950,  2399, "E-UTRA band 4: AWS-1"          },
    {  2400,  2649, "E-UTRA band 5: 850"            },
    {  2650,  2749, "E-UTRA band 6: UMTS only"      },
    {  2750,  3449, "E-UTRA band 7: 2600"           },
    {  3450,  3799, "E-UTRA band 8: 900"            },
    {  3800,  4149, "E-UTRA band 9: 1800"           },
    {  4150,  4749, "E-UTRA band 10: AWS-1+"        },
    {  4750,  4999, "E-UTRA band 11: 1500 Lower"    },
    {  5000,  5179, "E-UTRA band 12: 700 a"         },
    {  5180,  5279, "E-UTRA band 13: 700 c"         },
    {  5280,  5379, "E-UTRA band 14: 700 PS"        },
    {  5730,  5849, "E-UTRA band 17: 700 b"         },
    {  5850,  5999, "E-UTRA band 18: 800 Lower"     },
    {  6000,  6149, "E-UTRA band 19: 800 Upper"     },
    {  6150,  6449, "E-UTRA band 20: 800 DD"        },
    {  6450,  6599, "E-UTRA band 21: 1500 Upper"    },
    {  6600,  7399, "E-UTRA band 22: 3500"          },
    {  7500,  7699, "E-UTRA band 23: 2000 S-band"   },
    {  7700,  8039, "E-UTRA band 24: 1600 L-band"   },
    {  8040,  8689, "E-UTRA band 25: 1900+"         },
    {  8690,  9039, "E-UTRA band 26: 850+"          },
    {  9040,  9209, "E-UTRA band 27: 800 SMR"       },
    {  9210,  9659, "E-UTRA band 28: 700 APT"       },
    {  9660,  9769, "E-UTRA band 29: 700 d"         },
    {  9770,  9869, "E-UTRA band 30: 2300 WCS"      },
    {  9870,  9919, "E-UTRA band 31: 450"           },
    {  9920, 10359, "E-UTRA band 32: 1500 L-band"   },
    { 36000, 36199, "E-UTRA band 33: TD 1900"       },
    { 36200, 36349, "E-UTRA band 34: TD 2000"       },
    { 36350, 36949, "E-UTRA band 35: TD PCS Lower"  },
    { 36950, 37549, "E-UTRA band 36: TD PCS Upper"  },
    { 37550, 37749, "E-UTRA band 37: TD PCS Center" },
    { 37750, 38249, "E-UTRA band 38: TD 2600"       },
    { 38250, 38649, "E-UTRA band 39: TD 1900+"      },
    { 38650, 39649, "E-UTRA band 40: TD 2300"       },
    { 39650, 41589, "E-UTRA band 41: TD 2500"       },
    { 41590, 43589, "E-UTRA band 42: TD 3500"       },
    { 43590, 45589, "E-UTRA band 43: TD 3700"       },
    { 45590, 46589, "E-UTRA band 44: TD 700"        },
};

static const char * earfcn_to_eutra_band_string (uint16 earfcn)
{
    size_t i;

    for (i = 0; i < N_ELEMENTS (earfcn_ranges); i++) {
        if (earfcn <= earfcn_ranges[i].max && earfcn >= earfcn_ranges[i].min)
            return earfcn_ranges[i].name;
    }
    
    return "unknown";
}

static int nas_get_cell_location_info(void)
{
    PQCQMIMSG pRequest;
    PQCQMIMSG pResponse;
    PQMUX_MSG pMUXMsg;
    PQMI_TLV pV;
    int err;
    int i, j;

    pRequest = ComposeQMUXMsg(QMUX_TYPE_NAS, QMINAS_GET_CELL_LOCATION_INFO_REQ, NULL, NULL);
    err = QmiThreadSendQMI(pRequest, &pResponse);
    qmi_rsp_check_and_return();

    pV = (PQMI_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x2E);
    if (pV && pV->TLVLength) {
        printf ("5GNR ARFCN: '%u'\n", pV->u32);
    }

    {
        NasGetCellLocationNr5gServingCell *ptlv = (NasGetCellLocationNr5gServingCell *)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x2F);
        if (ptlv && ptlv->TLVLength)
        {
            printf ("5GNR cell information:\n"
                     "\tPLMN: '%s'\n"
                     "\tTracking Area Code: '%u'\n"
                     "\tGlobal Cell ID: '%" PRIu64 "'\n"
                     "\tPhysical Cell ID: '%u'\n"
                     "\tRSRQ: '%.1lf dB'\n"
                     "\tRSRP: '%.1lf dBm'\n"
                     "\tSNR: '%.1lf dB'\n",
                     str_from_bcd_plmn(ptlv->plmn),
                     ptlv->tac[0]<<16 | ptlv->tac[1]<<8 | ptlv->tac[2] ,
                     ptlv->global_cell_id,
                     ptlv->physical_cell_id,
                     (0.1) * ((double)ptlv->rsrq),
                     (0.1) * ((double)ptlv->rsrp),
                     (0.1) * ((double)ptlv->snr));
        }
    }

    {
        NasGetCellLocationLteInfoIntrafrequency *ptlv = (NasGetCellLocationLteInfoIntrafrequency *)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x13);
        if (ptlv && ptlv->TLVLength)
        {
            printf ("Intrafrequency LTE Info:\n"
                    "\tUE In Idle: '%s'\n"
                    "\tPLMN: '%s'\n"
                    "\tTracking Area Code: '%u'\n"
                    "\tGlobal Cell ID: '%u'\n"
                    "\tEUTRA Absolute RF Channel Number: '%u' (%s)\n"
                    "\tServing Cell ID: '%u'\n",
                    ptlv->ue_in_idle ? "yes" : "no",
                    str_from_bcd_plmn(ptlv->plmn),
                    ptlv->tracking_area_code,
                    ptlv->global_cell_id,
                    ptlv->absolute_rf_channel_number, earfcn_to_eutra_band_string(ptlv->absolute_rf_channel_number),
                    ptlv->serving_cell_id);

                if (ptlv->ue_in_idle)
                    printf ("\tCell Reselection Priority: '%u'\n"
                             "\tS Non Intra Search Threshold: '%u'\n"
                             "\tServing Cell Low Threshold: '%u'\n"
                             "\tS Intra Search Threshold: '%u'\n",
                             ptlv->cell_reselection_priority,
                             ptlv->s_non_intra_search_threshold,
                             ptlv->serving_cell_low_threshold,
                             ptlv->s_intra_search_threshold);


                for (i = 0; i < ptlv->cells_len; i++) {
                    NasGetCellLocationLteInfoCell *cell = &ptlv->cells_array[i];

                    printf ("\tCell [%u]:\n"
                             "\t\tPhysical Cell ID: '%u'\n"
                             "\t\tRSRQ: '%.1lf' dB\n"
                             "\t\tRSRP: '%.1lf' dBm\n"
                             "\t\tRSSI: '%.1lf' dBm\n",
                             i,
                             cell->physical_cell_id,
                             (double) cell->rsrq * 0.1,
                             (double) cell->rsrp * 0.1,
                             (double) cell->rssi * 0.1);

                    if (ptlv->ue_in_idle)
                        printf ("\t\tCell Selection RX Level: '%d'\n",
                                 cell->cell_selection_rx_level);
                }
        }
    }

    {
        NasGetCellLocationLteInfoInterfrequency *ptlv = (NasGetCellLocationLteInfoInterfrequency *)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x14);
        if (ptlv && ptlv->TLVLength)
        {
            int off = offsetof(NasGetCellLocationLteInfoInterfrequency, freqs[0]);
            printf ("Interfrequency LTE Info:\n"
                     "\tUE In Idle: '%s'\n", ptlv->ue_in_idle ? "yes" : "no");

                for (i = 0; i < ptlv->freqs_len; i++) {
                    NasGetCellLocationLteInfoInterfrequencyFrequencyElement *freq = (((void *)ptlv) + off);

                    off += sizeof(*freq);
                    printf ("\tFrequency [%u]:\n"
                             "\t\tEUTRA Absolute RF Channel Number: '%u' (%s)\n"
                             "\t\tSelection RX Level Low Threshold: '%u'\n"
                             "\t\tCell Selection RX Level High Threshold: '%u'\n",
                             i,
                             freq->eutra_absolute_rf_channel_number, earfcn_to_eutra_band_string(freq->eutra_absolute_rf_channel_number),
                             freq->cell_selection_rx_level_low_threshold,
                             freq->cell_selection_rx_level_high_threshold);
                    if (ptlv->ue_in_idle)
                        printf ("\t\tCell Reselection Priority: '%u'\n",
                                 freq->cell_reselection_priority);


                    for (j = 0; j < freq->cells_len; j++) {
                        NasGetCellLocationLteInfoCell *cell = &freq->cells_array[j];

                        off += sizeof(*cell);
                        printf ("\t\tCell [%u]:\n"
                                 "\t\t\tPhysical Cell ID: '%u'\n"
                                 "\t\t\tRSRQ: '%.1lf' dB\n"
                                 "\t\t\tRSRP: '%.1lf' dBm\n"
                                 "\t\t\tRSSI: '%.1lf' dBm\n"
                                 "\t\t\tCell Selection RX Level: '%u'\n",
                                 j,
                                 cell->physical_cell_id,
                                 (double) cell->rsrq * 0.1,
                                 (double) cell->rsrp * 0.1,
                                 (double) cell->rssi * 0.1,
                                 cell->cell_selection_rx_level);
                    }
                }
        }
    }

    pV = (PQMI_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x1E);
    if (pV && pV->TLVLength) {
        if (pV->u32 == 0xFFFFFFFF)
            printf ("LTE Timing Advance: 'unavailable'\n");
        else
            printf ("LTE Timing Advance: '%u'\n", pV->u32);
    }
    
    free(pResponse);
    return 0;
}

static int nas_get_rf_band_information(void)
{
    PQCQMIMSG pRequest;
    PQCQMIMSG pResponse;
    PQMUX_MSG pMUXMsg;
    int err;
    int i;

    pRequest = ComposeQMUXMsg(QMUX_TYPE_NAS, QMINAS_GET_RF_BAND_INFO_REQ, NULL, NULL);
    err = QmiThreadSendQMI(pRequest, &pResponse);
    qmi_rsp_check_and_return();

    {
        NasGetRfBandInfoList *ptlv = (NasGetRfBandInfoList *)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x01);
        if (ptlv && ptlv->TLVLength)
        {
            printf ("Band Information:\n");
            for (i = 0; i < ptlv->num_instances; i++) {
                NasGetRfBandInfo *band = &ptlv->bands_array[i];

                printf ("\tRadio Interface:   '%s'\n"
                         "\tActive Band Class: '%s'\n"
                         "\tActive Channel:    '%u'\n",
                         qmi_nas_radio_interface_get_string (band->radio_if),
                         qmi_nas_active_band_get_string (band->active_band),
                         band->active_channel);
            }
        }
    }

    {
        NasGetRfBandInfoExtendedList *ptlv = (NasGetRfBandInfoExtendedList *)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11);
        if (ptlv && ptlv->TLVLength)
        {
            printf ("Band Information (Extended):\n");
            for (i = 0; i < ptlv->num_instances; i++) {
                NasGetRfBandInfoExtended *band = &ptlv->bands_array[i];

                printf ("\tRadio Interface:   '%s'\n"
                         "\tActive Band Class: '%s'\n"
                         "\tActive Channel:    '%u'\n",
                         qmi_nas_radio_interface_get_string (band->radio_if),
                         qmi_nas_active_band_get_string (band->active_band),
                         band->active_channel);
            }
        }
    }

    {
        NasGetRfBandInfoBandWidthList *ptlv = (NasGetRfBandInfoBandWidthList *)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x12);
        if (ptlv && ptlv->TLVLength)
        {
            printf ("Bandwidth:\n");
            for (i = 0; i < ptlv->num_instances; i++) {
                NasGetRfBandInfoBandWidth *band = &ptlv->bands_array[i];

                printf ("\tRadio Interface:   '%s'\n"
                         "\tBandwidth:         '%u'\n",
                     qmi_nas_radio_interface_get_string (band->radio_if),
                     (band->bandwidth));
            }
        }
    }

    free(pResponse);
    return 0;
}
#endif
