• 高通与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 *** 
    ");
        (void) getTargetFromSysProperty();
    
        if ( !is_supported_qcci() )
        {
            if (!strncmp(ATFWD_DATA_TARGET_APQ, target,
                         strlen(target))) {
                printf("APQ baseband : Explicitly stopping ATFWD service....
    ");
                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
    ");
        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 
    ");
        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...
    ");
    
        //Get QMI service object
        printf("getting at svc obj for access terminal QMI svc
    ");
        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
    ", connectionResult);
            if (connectionResult < 0)
            {
                if ( !is_supported_qcci() )
                {
                    if (qmiHandle >= 0) {
                        qmi_release(qmiHandle);
                    }
                }
                else
                {
                    printf("Release qmi_client...
    ");
                    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
    ", connectionResult);
                    if (connectionResult < 0)
                    {
                        printf("Release qmi_client...
    ");
                        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
    ");
        int retryCnt = 1;
    
        for (; retryCnt <= ATFWD_MAX_RETRY_ATTEMPTS; retryCnt++) {
            LOGI("ATFWD :retryCnt <= ATFWD_MAX_RETRY_ATTEMPTS
    ");
            qmiErrorCode = 0;
            switch (type) {
                // 初始化QMI
                case INIT_QMI:
                    LOGI("ATFWD :Going to qmi_init(atfwdSysEventHandler)
    ");
                    qmiHandle = qmi_init(atfwdSysEventHandler, NULL);
                    *result = qmiHandle;
                    break;
                // 连接QMI(实现能接受到从modem下传上来的At命令)
                case INIT_QMI_SRVC:
                    LOGI("ATFWD :Going to qmi_connection_init(qmiPort, &qmiErrorCode)
    ");
                    *result = qmi_connection_init(qmiPort, &qmiErrorCode);
                    break;
                // 获取binder服务,以便能把相应的命令通过binder通讯方式传到相应的地方进行处理
                case INIT_ATFWD_SRVC:
                    LOGI("ATFWD :Going to initializeAtFwdService(case INIT_ATFWD_SRVC)
    ");
                    *result = initializeAtFwdService();
                    break;
                default:
                    LOGI("Invalid type %d", type);
                    return;
            }
            LOGI("ATFWD :result : %d 	 ,Init step :%d 	 ,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:
    ", nCommands);
            for (i = 0; i < nCommands ; i++) {
                printf("cmd%d: %s
    ", 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
    ",
                         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
    ", *handle);
                qmi_atcop_srvc_release_client(*handle, &qmiErrorCode);
                *handle = -1;
                return;
            }
        } else {
            printf("ATcop Service Init failed
    ");
            return;
        }
    
        printf("Registered AT Commands event handler
    ");
        return;
    }
    

    等待新命令

    此后,函数会循环在while(1)中,当modem下有传来需要处理的命令的时候,newRequest会置为1,走if(newRequest == 1){……}

    if (newRequest == 1) {
        LOGI("pthread notified for new request
    ");
        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通讯传来的数据
           }
        }
    

    最后就是针对接收到的具体的值进行处理。

    如果说我的文章对你有用,只不过是我站在巨人的肩膀上再继续努力罢了。
    若在页首无特别声明,本篇文章由 Schips 经过整理后发布。
    博客地址:https://www.cnblogs.com/schips/
  • 相关阅读:
    ASP.NET 2.0 中改进的缓存功能
    Python 一门神奇的语言
    showModalDialog()、showModelessDialog()方法使用详解
    在VS2005中 GridView导入Excel的两点小技巧附源码
    DVB码流中业务信息与电子节目指南
    js 日历控件
    js收藏
    什么是ECM,EMM,AU,EMU?
    精解PSISI(一)
    Oracle第四课(学习笔记)
  • 原文地址:https://www.cnblogs.com/schips/p/at_command_in_qualcomm_3_atfwd.html
Copyright © 2020-2023  润新知