高通與At指令:ATFWD解析


背景

本章的內容是適用於AP側AT指令開發調試的有關人員。

主要是介紹高通實現的ATFWD框架。在這需要說明一下的是,或許你對AT Command很了解了,但是卻貌似都不知道ATFWD,這很正常,嚴格來說,ATFWD都不算屬於AT Command框架的一部分,只是高通對擴展的at命令做的一個擴展實現。

我們之前說到,ATCommands以處理方式可以分有兩類,一類是直接在modem下進行處理的,還有一部分是在AP側進行處理更加方便有效的。

而對於APSide的AT命令,高通也提供了一套框架進行實現,在這我們就這一塊做詳細的學習。

同樣的,AT命令通過ATCoP從串口傳來並被解析,而對於APSide的AT命令我們會通過allow_list[]數組注冊,這個時候modem會判斷傳來的命令是不是AP相關的,如果是,通過qmi通訊將AT命令傳到AP側進行處理,而在AP側的流程便是通過ATFWD框架實現的。

因此,只要有ATCoP,那么有關的指令就需要注冊到ATCoP中

在某個新基線上移植AT指令,發現有問題,因此收集了這個系列的 文章 作為 這方面的知識補充。

原文作者:laozhuxinlu,本文有刪改。

AT指令在產線中是一類比較重要的問題, 一天沒來得及解決,則會拖延生產的有關進度。

ATFWD 主要是與 含有 modem 的有關異構處理器有關的。

如果 對應的高通平台 沒有 modem 處理器(例如 SDM845),則使用 port-bridge 的方式進行 AT 實現,后續我們會講到。

代碼解析

在vendor下,一般是在vendor/qcom/proprietary/telephony-apps/ATFWD-daemon目錄下,我們能看到ATFWD的具體實現:

有時候也可能存在於vendor/qcom/proprietary/data/ATFWD-daemon

  • Android.mk:編譯一個主進程(ATFWD-daemon)到system/bin下面做實時監聽從modem下傳來的AT命令。
  • atfwd_daemon.c:主進程的main函數定義,做AT命令的注冊已經QMI(Qualcom Message Interface 高通信息接口 )初始化,並循環監聽傳來的AT命令並處理返回。
  • sendcmd.cpp:初始化獲取binder服務,以此實現將AT命令傳到實際處理的地方。
  • IAtCmdFwd.cpp:對binder服務的定義。

main

ATFWD-daemon進程的入口是main()函數

/*=========================================================================
  FUNCTION:  main

===========================================================================*/
/*!
@brief
  Initialize the QMI connection and register the ATFWD event listener.
  argv[1] if provided, gives the name of the qmi port to open.
  Default is "rmnet_sdio0".

*/
/*=========================================================================*/
int main (int argc, char **argv)
{
    AtCmdResponse *response;
    int i, connectionResult, initType;

    userHandle = userHandleSMD = -1;
    i = connectionResult = 0;

    printf("*** Starting ATFWD-daemon *** \n");
    (void) getTargetFromSysProperty();

    if ( !is_supported_qcci() )
    {
        if (!strncmp(ATFWD_DATA_TARGET_APQ, target,
                     strlen(target))) {
            printf("APQ baseband : Explicitly stopping ATFWD service....\n");
            stopSelf();
            return -1;
        }

        if (argc >= 2) {
            qmiPort = argv[1];
        } else {
            qmiPort = getDefaultPort();
            if( NULL == qmiPort ) {
                qmiPort = DEFAULT_QMI_PORT;
            }
        }

        if (argc >= 3) {
            secondaryPort = argv[2];
        } else if (!strncmp(ATFWD_DATA_TARGET_SVLTE2A, target, strlen(target))) {
            /* For SVLTE type II targets, Modem currently exposes two ATCOP ports.
            * One bridged from USB to SDIO, directly talking to 9k modem
            * Another bridged from USB to SMD, directly talking to 8k
            * Therefore given this modem architecture, ATFWD-daemon needs to
            * listen to both the modems( 8k & 9K).
            * Register with 8k modem
            */
            secondaryPort = DEFAULT_SMD_PORT;
        } else if (!strncmp(ATFWD_DATA_TARGET_SGLTE, target, strlen(target))) {
            // For SGLTE targets, Register with the SMUX port.
            secondaryPort = QMI_PORT_RMNET_SMUX_0;
        }
    }

    printf("init all signals\n");
    signalInit();

    pthread_mutexattr_t attr;
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init(&ctrMutex, &attr);
    pthread_cond_init(&ctrCond, NULL);

    printf("Explicitly disbling qmux \n");
    qmi_cci_qmux_xport_unregister(QMI_CLIENT_QMUX_RMNET_INSTANCE_0);
    qmi_cci_qmux_xport_unregister(QMI_CLIENT_QMUX_RMNET_USB_INSTANCE_0);
    qmi_cci_qmux_xport_unregister(QMI_CLIENT_QMUX_RMNET_SMUX_INSTANCE_0);
    qmi_cci_qmux_xport_unregister(QMI_CLIENT_QMUX_RMNET_MHI_INSTANCE_0);
    printf("Disabling QMUX complete...\n");

    //Get QMI service object
    printf("getting at svc obj for access terminal QMI svc\n");
    qmi_at_svc_obj = at_get_service_object_v01();

    for (initType = INIT_QMI; initType != INIT_MAX; initType++) {
        connectionResult = 0;
        tryInit (initType, &connectionResult);
        printf(" tryinit complete with connectresult: %d\n", connectionResult);
        if (connectionResult < 0)
        {
            if ( !is_supported_qcci() )
            {
                if (qmiHandle >= 0) {
                    qmi_release(qmiHandle);
                }
            }
            else
            {
                printf("Release qmi_client...\n");
                qmi_client_release(qmi_at_svc_client);
                qmi_at_svc_client = NULL;
            }

            stopSelf();
            return -1;
        }
    }
    else
    {
        if (!registerATCommands())
        {
            stopSelf();
            return -1;
        }
    }

    while (1) {
        pthread_mutex_lock(&ctrMutex);
        while (!isNewCommandAvailable()) {
            printf("Waiting for ctrCond");
            pthread_cond_wait(&ctrCond, &ctrMutex);
            printf("Recieved ctrCond: p: %d, S:%d, nr: %d",regForPrimaryPort, regForSecondaryPort, newRequest );
        }

        if ( !is_supported_qcci() )
        {
            if (regForPrimaryPort == 1) {
                if (qmiPort) {
                    printf("Rcvd pthread notification for primary QMI port registration");
                    initAtcopServiceAndRegisterCommands(qmiPort, &userHandle);
                } else {
                    printf("Notification for primary QMI port registration when NOT valid, ignore...");
                }
                regForPrimaryPort = 0;
            }

            if (regForSecondaryPort == 1) {
                if (secondaryPort) {
                    printf("Rcvd pthread notification for secondary QMI port registration");
                    initAtcopServiceAndRegisterCommands(secondaryPort, &userHandleSMD);
                } else {
                    printf("Notification for secondary QMI port registration when NOT valid, ignore...");
                }
                regForSecondaryPort = 0;
            }
            if(userHandle < 0 && userHandleSMD < 0)
            {
                printf("userhandle(s) for both 8k and 9k modems NOT valid -- bail out");
                if (qmiHandle >= 0)
                {
                    qmi_release(qmiHandle);
                }
                stopSelf();
                return -1;
            }
        }
        else
        {
            if ( regForPrimaryPort == 1)
            {
                printf("Registering for primary port (QCCI).");
                connectionResult = 0;
                tryInit (INIT_QMI_SRVC, &connectionResult);
                printf(" init result: %d\n", connectionResult);
                if (connectionResult < 0)
                {
                    printf("Release qmi_client...\n");
                    qmi_client_release(qmi_at_svc_client);
                    qmi_at_svc_client = NULL;
                    stopSelf();
                    return -1;
                }
                if (!registerATCommands())
                {
                    printf("Register for primary port (QCCI) failed.");
                    stopSelf();
                    return -1;
                }
                regForPrimaryPort=0;
            }

            if ( regForServiceUp == 1 )
            {
                regForServiceUpEvent();
            }
        }

        if (newRequest == 1) {
            printf("pthread notified for new request; sending response.");
            response = sendit(&fwdcmd);
            if (response == NULL) {
                printf("Response processing complete、Invalid cmd resp.");
                sendInvalidCommandResponse();
                printf("Invalid response sending complete.");
            } else {
                printf("Response processing complete、Sending response.");
                sendResponse(response);
                printf("Send response complete.");
            }

            if (fwdcmd.name) free(fwdcmd.name);
            if (fwdcmd.tokens) {
                for (i = 0; i < fwdcmd.ntokens; i++) {
                    free(fwdcmd.tokens[i]);
                }
                free(fwdcmd.tokens);
            }
            freeAtCmdResponse(response);
            newRequest = 0;
            printf("New request processing complete.");
        }

        pthread_mutex_unlock(&ctrMutex);
    }

    return 0;
}

tryInit

在main中會調用tryInit():實現三塊的初始化:

  • 首先是QMI的初始化;其次是進行QMI連接的初始化:這兩塊主要是實現能接受到從modem下傳上來的At命令。
  • 最后是對binder服務的獲取初始化:以便能把相應的命令通過binder通訊方式傳到相應的地方進行處理。
void tryInit (atfwd_init_type_t type, int *result) {
    LOGI("ATFWD :Going to tryInit ATFWD daemon\n");
    int retryCnt = 1;

    for (; retryCnt <= ATFWD_MAX_RETRY_ATTEMPTS; retryCnt++) {
        LOGI("ATFWD :retryCnt <= ATFWD_MAX_RETRY_ATTEMPTS\n");
        qmiErrorCode = 0;
        switch (type) {
            // 初始化QMI
            case INIT_QMI:
                LOGI("ATFWD :Going to qmi_init(atfwdSysEventHandler)\n");
                qmiHandle = qmi_init(atfwdSysEventHandler, NULL);
                *result = qmiHandle;
                break;
            // 連接QMI(實現能接受到從modem下傳上來的At命令)
            case INIT_QMI_SRVC:
                LOGI("ATFWD :Going to qmi_connection_init(qmiPort, &qmiErrorCode)\n");
                *result = qmi_connection_init(qmiPort, &qmiErrorCode);
                break;
            // 獲取binder服務,以便能把相應的命令通過binder通訊方式傳到相應的地方進行處理
            case INIT_ATFWD_SRVC:
                LOGI("ATFWD :Going to initializeAtFwdService(case INIT_ATFWD_SRVC)\n");
                *result = initializeAtFwdService();
                break;
            default:
                LOGI("Invalid type %d", type);
                return;
        }
        LOGI("ATFWD :result : %d \t ,Init step :%d \t ,qmiErrorCode: %d", *result, type, qmiErrorCode);
        if (*result >= 0 && qmiErrorCode == 0) {
            break;
        }
        sleep(retryCnt * ATFWD_RETRY_DELAY);
    }

    return;
}

initAtcopServiceAndRegisterCommands

初始化並注冊所有其添加的At命令;

// 對應的命令
qmi_atcop_at_cmd_fwd_req_type atCmdFwdReqType[] = {

    { //AT command fwd type
        1, // Number of commands
        {
            { QMI_ATCOP_AT_CMD_NOT_ABORTABLE, "+CKPD"},
        }
    },
    { //AT command fwd type
        1, // Number of commands
        {
            { QMI_ATCOP_AT_CMD_NOT_ABORTABLE, "+CTSA"},
        }
    },
    { //AT command fwd type
        1, // Number of commands
        {
            { QMI_ATCOP_AT_CMD_NOT_ABORTABLE, "+CFUN"},
        }
    },
    // ...
};

void initAtcopServiceAndRegisterCommands(const char *port, int *handle) {
    int i, nErrorCnt, nCommands;
    i = nErrorCnt = 0;

    initAtCopServiceByPort(port, handle);

    if (*handle > 0) {
        nCommands = sizeof(atCmdFwdReqType) / sizeof(atCmdFwdReqType[0]);
        printf("Trying to register %d commands:\n", nCommands);
        for (i = 0; i < nCommands ; i++) {
            printf("cmd%d: %s\n", i, atCmdFwdReqType[i].qmi_atcop_at_cmd_fwd_req_type[0].at_cmd_name);

            qmiErrorCode = 0;
            int registrationStatus = qmi_atcop_reg_at_command_fwd_req(*handle, \
                                              &atCmdFwdReqType[i], &qmiErrorCode);
            printf("qmi_atcop_reg_at_command_fwd_req: %d", qmiErrorCode);
            if (registrationStatus < 0 || qmiErrorCode != 0) {
                printf("Could not register AT command : %s with the QMI Interface - Err code:%d\n",
                     atCmdFwdReqType[i].qmi_atcop_at_cmd_fwd_req_type[0].at_cmd_name, qmiErrorCode);
                nErrorCnt++;
                qmiErrorCode = 0;
            }
        }

        if(nErrorCnt == nCommands) {
            printf("AT commands registration failure..、Release client handle: %d\n", *handle);
            qmi_atcop_srvc_release_client(*handle, &qmiErrorCode);
            *handle = -1;
            return;
        }
    } else {
        printf("ATcop Service Init failed\n");
        return;
    }

    printf("Registered AT Commands event handler\n");
    return;
}

等待新命令

此后,函數會循環在while(1)中,當modem下有傳來需要處理的命令的時候,newRequest會置為1,走if(newRequest == 1){……}

if (newRequest == 1) {
    LOGI("pthread notified for new request\n");
    response = sendit(&fwdcmd);
    if (response == NULL) {
        sendInvalidCommandResponse();
    } else {
        sendResponse(response);
    }

    if (fwdcmd.name) free(fwdcmd.name);
    if (fwdcmd.tokens) {
        for (i = 0; i < fwdcmd.ntokens; i++) {
            free(fwdcmd.tokens[i]);
        }
        free(fwdcmd.tokens);
    }
    freeAtCmdResponse(response);
    newRequest = 0;
}

main()函數下調用sendit(&fwdcmd)函數將命令傳遞出去,並將返回的結果給response數據結構;

Sendit

typedef struct {
  int opcode;
  char *name; // 指令名稱, AT+abc --> abc
  int ntokens; // 有多少個參數
  char **tokens; // 參數數組
} AtCmd;

typedef struct {
  int result;
  char *response;
} AtCmdResponse;

extern "C" AtCmdResponse *sendit(const AtCmd *cmd)
{
    AtCmdResponse *result = NULL;
    result = new AtCmdResponse;
    result->response = NULL;
    LOGI("sendit");

    LOGE("%s:%d peeta", __func__, __LINE__);
    if(strcasecmp(cmd->name, "+QFCT")==0){
        LOGI("ATFWD AtCmdFwd QFCT");
        if(NULL != cmd->tokens) {
            LOGI("ATFWD AtCmdFwd Tokens Not NULL ntokens=%d",cmd->ntokens);
            if(cmd->ntokens == 0 || cmd->tokens[0] == NULL){
                LOGI("ATFWD AtCmdFwd Tokens[0] is NULL");
                quec_qfct_handle(result);
            }else if(0 == strncmp("wifi-kill",cmd->tokens[0],strlen("wifi-kill"))){
                //  char *args[5] = { PTT_SOCKET_BIN, "-f", "-d", "-v", NULL };
                LOGI("ATFWD AtCmdFwd:%s",cmd->tokens[0]);
                property_set("wifi.ptt_socket_app", "false");
                property_set("wifi.p_socket_app", "true");
                //...
        }else{
            LOGI("ATFWD AtCmdFwd Tokens is NULL");
            quec_qfct_handle(result);
        }
    }else if(strcasecmp(cmd->name, "+QGMR")==0)
    {
        quec_qgmr_handle(cmd,result);
    }

    return result;
}

如果使用到了binder,還可以這樣:sendit()函數調用processCommand()函數,processCommand是繼承於BpInterface類實現的,我們通過Parcel數據將數據寫入data下,然后通過調用喚起RPC進行binder數據通訊:remote()->transact(processAtCmd,data, &reply);

extern "C" AtCmdResponse *sendit(const AtCmd *cmd)
{
    AtCmdResponse *result;

    if (!cmd) return NULL;

    result = gAtCmdFwdService->processCommand(*cmd);

    return result;
}

以上主要就是一個ATCommand在ATFWD下的大致流程了。

總結

簡單的說,那就是一個進程,進行數據中轉的進程:數據從modem下傳上來先通過venderril,再從venderril下傳到framework(或者別的什么地方)下進行處理,ATFWD便是venderril下的一個中轉站。其中與modem的通訊方式采用QMI,與framework采用bingerserver方式通訊。

ATFWD調試技巧

如果在AT指令的實現中遇到了某些問題,可以按照下面的流程進行分析。

0、確保ATFWD進程正常執行,如果沒有,則根據log確定 是 中途退出(沒走完流程)還是 Android系統的權限問題。

AT Command流程分析之具體實現

主要是介紹作為一個AT Command的開發者,具體如何參與到代碼的開發。當然,這里主要是介紹一些基本的開發工作……

想必從前面的學習,你已經了解到AT命令執行的大致流程,基於這個流程,AT Command的功能開發也主要是包括在兩個方面:

  • BP Side類型的AT命令開發
  • AP Side類型的AT命令開發

BP側

首先是BP Side類型的AT命令開發,或者說如何在ATCoP上去擴展實現實現一個AT命令。

我們知道AT命令分有以下幾種類型,在這我們以最常見的擴展AT命令為例,命名:”+CLAY”。

  • 基本 AT 命令(basic_table)
  • 寄存器 AT 命令(sreg_table)
  • 擴展 AT 命令(extended_table)
  • 廠商 AT 命令(vendor_table)

定義指針變量

在dsati.h下的dsatetsi_ext_action_index_enum_type枚舉數組中添加一個指針變量如下:

DSATETSI_EXT_ACT_CLAY_ETSI_IDX = 14084

建立AT命令和處理函數的映射

在dsatetsictab.c下的dsatetsi_ext_action_table_ex []數據下添加映射:

//...
{DSATETSI_EXT_ACT_CLAY_ETSI_IDX,  dsatetsime_exec_clay_cmd  }
// ...

定義AT命令

如果想要定義一個at命令,需要首先確定它的命令表項,也就是name、屬性、參數情況、處理函數指針等……

下面我們增加的是一個最簡單的命令,name是”+CLAY”,屬性是無參數。

在dsatetsictab_ex.c下的dsatetsi_ext_action_table []數組中添加:

{ "+CLAY", READ_ONLY | COMMON_CMD,
 SPECIAL_NONE, 0,DSATETSI_EXT_ACT_CLAY_ETSI_IDX 
}

具體的含義請參見 AT Command流程分析之AtCop解析模塊。

聲明處理函數

上面完成以后就能定義其實際的處理函數了,在定義之前,我們先要聲明一下,在dsatetsime.h下添加:

dsat_result_enum_type  dsatetsime_exec_clay_cmd (

  dsat_mode_enum_typemode, /*AT command mode: */

  constdsati_cmd_type *parse_table, /*Ptr to cmd in parse table */

  consttokens_struct_type *tok_ptr, /*Command tokens from parser */

  dsm_item_type*res_buff_ptr /* Place to put response */

);

定義處理函數

dsat_result_enum_type dsatetsime_exec_clay_cmd (
    dsat_mode_enum_typemode, /*AT command mode: */
    constdsati_cmd_type *parse_table, /*Ptr to cmd in parse table */
    consttokens_struct_type *tok_ptr, /*Command tokens from parser */
    dsm_item_type*res_buff_ptr /* Place to put response */
){

    dsat_result_enum_type result= DSAT_OK;

    if(tok_ptr->op == NA){
        res_buff_ptr->used =(word) snprintf ((char*)res_buff_ptr->data_ptr,
                                             res_buff_ptr->size,
                                             "%s: %s,%s",
                                             "+CLAY",
                                             "hello",
                                             "world");
    }

    else if(tok_ptr->op ==(NA|EQ|QU)){} //針對其他的語法格式進行處理
    else if(tok_ptr->op ==(NA|QU)){}    //針對其他的語法格式進行處理
    else if(tok_ptr->op ==(NA|EQ|AR)){} //針對其他的語法格式進行處理
    else{ result= DSAT_ERROR;}          //針對錯誤的語法格式進行處理

    return result;
}

至此,一個BP Site的自定義AT 命令便開發完成了,這里需要注意的是,這邊只是舉例實現,而且對AT+CLAY的命令類型作為擴展命令開發的,就所以流程僅供參考……

AP側

那么,要想擴展添加一個AP Site的AT命令又該如何呢?

首先要確認ATFWD在設備中已添加注冊並正常運行(可在system/bin下去查看)

同樣的,這里就AT+CLAY舉例實現……

1、在Modem側添加自定義的AT Command的注冊。

*/amss_8909/modem_proc/datamodem/interface/atcop/src/dsatclient_ex.c下的LOCAL byte allowed_list[][MAX_CMD_SIZE]數組中添加定義:

  LOCAL byte allowed_list[][MAX_CMD_SIZE]={……,"+CLAY",""};

2、 AP側的Vendor下添加AT Command的注冊。

*/vendor/qcom/proprietary/data/ATFWD-daemon/atfwd_daemon.c下的qmi_atcop_at_cmd_fwd_req_type atCmdFwdReqType[]數組中添加定義:

    { //AT command fwd type
        1, // Number of commands
        {
            { QMI_ATCOP_AT_CMD_NOT_ABORTABLE, "+CLAY"},
        }
    },

3、 添加在framework側的實際處理並返回處理結果。之前我們說過,AP Site的AT命令在串口經過modem通過QMI通訊方式將命令傳到Vendor Ril下,但是實際上是需要將該命令傳到framework下去處理的,這個時候需要用到binder通訊方式將命令傳到framework下去實際處理。

在Vendor下的binder通訊發送在/vendor/qcom/proprietary/data/ATFWD-daemon/IAtCmdFwd.cpp下去實現的:

virtual AtCmdResponse *processCommand(const AtCmd &cmd)
{
    // ...

    data.writeInterfaceToken(IAtCmdFwdService::getInterfaceDescriptor());
    data.writeInt32(1);                     //specify there is an input parameter
    data.writeInt32(cmd.opcode);            //opcode
    String16 cmdname(cmd.name);
    s16 = strdup8to16(cmd.name, &len);
    data.writeString16(s16, len);            //command name
    free(s16);
    data.writeInt32(cmd.ntokens);
    for (int i=0; i < cmd.ntokens; i++) {
        s16 = strdup8to16(cmd.tokens[i], &len);
        data.writeString16(s16,len);
        free(s16);
    }

    status_t status = remote()->transact(processAtCmd, data, &reply);//RPC call
    LOGI("Status: %d",status);
    if (status != NO_ERROR) {
        LOGE("Error in RPC Call to AtCdmFwd Service (Exception occurred?)");
        return NULL;
    }
    // ...
}

實際就是通過

status_t status = remote()->transact(processAtCmd, data, &reply);//RPC call

實現了發送,最后返回的處理結果在status下。

4、在framework側的處理實現:

status_t BnAtCmdFwdService::onTransact(uint32_t code, const Parcel& data,
Parcel* reply, uint32_t flags) {
    case processAtCmd: {
             // ...……   //接受到binder通訊傳來的數據
       }
    }

最后就是針對接收到的具體的值進行處理。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM