高通android QMI機制


高通android QMI機制

原文(有刪改):https://blog.csdn.net/u012439416/category_7004974

概論

Qualcomm MSM Interface,作用用於AP和BP側的交互,通俗說法就是讓設備終端TE(可以是手機,PDA,計算機)

對高通BP側的AMSS系統進行操作,如調用函數,讀取數據,設置其中的NV項等。

QMI的核心稱之為QMI框架(QMI Framework),其主要功能包括以下3點:

  • 連接MSM模塊和設備終端,提供一個正交的控制和數據通道。在QMI的消息用有兩種定義,一種是QMIControl Message;另一種是QMI DataMessage,支持這兩種消息並發,不會互相干擾導致出錯。

  • 列舉一系列的枚舉邏輯設備,提供給連接使用。QMI機制類似於一個服務器機制,有相應的client端和services端,對應於QMI的control point和service。在AP向BP發送請求時,AP作為client端,當AP接收BP側返回的響應時,AP作為services端。QMI包含了一系列的QMI Service,例如nas,voice,wds等,這些不同的services相當於不同邏輯設備,給不同的app調用。

  • QMI有相應的消息和消息的協議,設備終端就是通過這些消息來訪問AMSS。對於不同的qmi消息,消息長度不一樣,可自己定義消息長度,不同的qmi消息,消息格式是相同的。

上圖是QMIFramework的一個軟件結構圖。

從圖中可以看出,上層控制點打包對應類型的QMI消息或通過其他操作系統的框架,將要發出的數據傳到AP側底層的邏輯設備,最后邏輯設備通過內聯的總線接口,傳到BP側的AMSS。在代碼中可以找到從控制點發送到邏輯設備的函數。

rrno_enum_type qcril_qmi_client_send_msg_sync {
    qcril_qmi_client_e_type svctype,
    unsigned long msg id,
    *void req_c struct,
    int reg_c_struct_len,
    void *resp_c_struct,
    int resp_c_struct_ len
}

這個是控制點向BP側發送同步消息的函數,參數包括走的QMI_Service類型,Service里面消息的名稱,請求消息的初始地址,長度,返回相應的初始地址和長度。邏輯設備和BP側內聯的總線也可以分很多種:

USB,SDIO,共享內存,無線協議802.11等都可以作為總線連接AP和BP。

咱們現在開發的MSM平台用的是共享內存。代碼中qmi_port_defs.h中的枚舉qmi_connection_id_type定義了AP側QMI和BP側的連接通道,包括集成modem的MSM平台和獨立modem的MDM。

QMI_CONN_ID_RMNET_O = QMI_CONN_ID_FIRST,//Correspond
QMI_cONN_ID_RMNET_1,  //corresponds to SMD DAT,
QMI_cONN_ID_RMNET_2;  //Corresponds to SMD DAT,
QMI_cONN_ID_RMNET_3,
QMI_cONN_IDLRMNET_4,
QMI_cONN_ID_RMNET_6,
QMI_cONN_ID__RMNET_7,

在代碼中的vendor\qcom\proprietary\qmi\platform目錄,linux_qmi_qmux_if_client.c,定義了和BP側通信的邏輯設備種類。

static linux_qmi_qmux_if_conn_sock_info_t linux_qmi_qmux_if_client_conn_socks[]=
#ifdef FEATURE_QMI_ANDROID
/*Radio Group Client Process */
{ "radio", QMI_QMUX_IF_RADIO_CLIENT_SOCKET_PATH, QMIL_QMUx_IF_RADIO_CONN_SOCKET_PATH },
/* Audio Group Client Process */
{ "audio", QMI_QMUX_IF_AUD1O_CLIENT_SOCKET_PATH, QMI_QMUX_IF_AUD1O_CONN_SOCKET_PATH },
/* Bluetooth Group Client Process */
{ "bluetooth",QMil_QMUX_IF_BLLETOOTH_CLIENT_SOCKET_PATH,QMIl_QMUX_IF_BLETOOTH_CON_SOCKET_PATH },
/*GPS Group Client Process */
{ "gps", QMI_QMUX_IF_GPS_CLIENT_SOCKET_PATH, QMI_QMUX_IF_GPS_CONN_SOCKET_PATH }
#endif

目前我們QMI支持的邏輯設備有圖中四種,電話系統,音頻,藍牙,GPS。

TE和MSM通信原理圖:

img

兩個特點:

1.單一的物理鏈接總線,必須被多個邏輯設備所復用。

2.不同的邏輯設備要求獨立的控制信道和數據信道。

QMI終端原理圖如下:

img

從圖中可看出,整個QMI架構中,主要是通過QMUX層完成軟件上的TE和MSM的交互。

  • 1,一個服務可以對應多個控制點,一個控制點只能對應一個服務。
  • 2,控制點與服務的關系就好比C/S模型中的客戶端與服務器關系。
  • 3,如果某程序使用幾種QMI服務,那么它就要為每種服務構建一個控制點。

可以看出QMI並不是一個簡單的一對一傳輸通信方式,而是一個服務可以同時接受幾個控制點發出的消息,

其實現的原理也是對傳輸信道的復用。

復用協議QMUX

QMI Multiplexing Protocol(QMUX):QMI的復用協議

消息從控制點經過類似socket的線程傳到QMI接口后,QMI負責對數據進行封裝,加上QMUX消息的頭,發送到QMUX層,再通過QMUX層傳到共享內存到BP側。

QMUX消息的格式

img

整個QMUX控制信道的結構如上圖,

  • I/FType:QMI將控制點數據封裝后,發送到QMUX前,加的消息頭,長度為一個byte,值通常為0x01,表示這個消息為QMUX消息,如果是其他值,則為其他消息。
  • Length: QMUX消息的長度,不包括I/F Type。
  • ControlFlags:控制位,表示消息傳輸的方向。長度為1個byte,只有第7個bit是標志位,其他位為0,bit7=1說明QMUX消息由服務端發送,bit7=0由控制點發送。
  • Clien ID: 控制點的標識,在控制點和服務端都需要賦值,當在服務端發出的消息Client ID的值為0xFF,表示該消息為廣播消息,由服務端主動發出,被所有控制點搜到。

QMUX SDU和TLV結構:

在整個控制信道的消息中,出去消息標識頭I/F Type,和QMUX消息頭,數據傳輸在QMUXSDU中完成,QMUX SDU里面的數據需要支持Type Length Value(TLV)的格式。

img

TLV格式的數據存放在QMI Service Message里面的Value中。

Control Flags:表示消息是請求、響應還是指示。Datasheet見文檔。

TLV結構圖:

img

QMUX消息類型

1、請求:請求消息用於設置參數、查詢參數值或配置指示的產生。請求消息由控制點產生,一個有效的請求通常會產生來自服務的應答。

2、響應:響應由服務產生,為回應接收到的請求。每個響應至少包含指示請求成功或失敗的結果參數以及錯誤狀態。

3、指示:指示由服務端主動發出,為了讓控制點知道底層狀態的變更,類似於信號強度,掉網Out of Service都是服務端主動發出給控制點的指示。

Qcril初始化流程

rild守護進程的rild.c文件中main方法有關加載動態庫代碼如下:

dlHandle = dlopen(rilLibPath, RTLD_NOW);//加載庫
// ...
funcs = rilInit(&s_rilEnv, argc, rilArgv);//初始化   實際調用的是RIL_Init方法

s_rilEnv結構體定義如下:也就是qcril.c可以回調ril的方法:

static struct RIL_Env s_rilEnv = {
    RIL_onRequestComplete,
    RIL_onUnsolicitedResponse,
    RIL_requestTimedCallback
};

Android平台不同廠商的AP側可以相同,但是Modem側肯定會有很大的差異,RIL層要解決一個問題:就是適配不同廠商的Modem,為了達到兼容性要求,android在AP與Modem之間搭建了RILC的框架,由不同的Modem廠商將自己的協議連接到AP側。

對於高通平台來說,RILC就是QCRIL。

qcril.c的RIL_Init方法主要步驟如下:

//設置線程的名字
qmi_ril_set_thread_name( pthread_self() , QMI_RIL_QMI_RILD_THREAD_NAME);
//初始化接收Modem消息的EventLoop
qcril_event_init();
// 初始化QCRIL各個模塊
qcril_init(c_argc, c_argv);
// 啟動線程
qcril_event_start();
//其他初始化
qmi_ril_initiate_bootup();
//返回接口函數
return  &qcril_request_api[ QCRIL_DEFAULT_INSTANCE_ID ];

初始化流程圖如下:

img

初始化EventLoop過程

在Qcril中搭建了EventLoop循環用於檢測Modem上報的消息,而EventLoop機制的初始化工作是在 qcril_event_init()中完成的。

qcril_event.c的qcril_event_init方法主要部分如下:

//創建線程,入口方法為qcril_event_main
ret = pthread_create(&qcril_event.tid, &attr, qcril_event_main, NULL);
// ...
//設置線程的名字為event
qmi_ril_set_thread_name(qcril_event.tid, QMI_RIL_EVENT_THREAD_NAME);

在初始化過程中,通過pthread_create()函數創建了EventLoop線程,並且指出該線程的入口方法為qcril_event_main(),主要邏輯如下:

qcril_event_init_list(&qcril_event.list); //初始化qcril_event.list鏈表  
// ...
ret = pipe(filedes); //創建管道  
// ...
while (qcril_event.started < 2) //阻塞等待qcril初始化
  {
    QCRIL_LOG_VERBOSE("Event thread waiting for started == 2 (%d)", qcril_event.started );
    pthread_cond_wait(&qcril_event_startupCond, &qcril_event.startup_mutex);
  }
// ...
for (;;) // for循環讀取qmi底層發送的請求
  {
// ...
//阻塞等待接收內容  
n = select(qcril_event.fdWakeupRead + 1, &rfds, NULL, NULL, NULL);
// ...
do //讀取qmi內容
 {
    ret = read(qcril_event.fdWakeupRead, &buff, sizeof(buff));
// ...
//處理qmi發送的請求
err_no = qcril_process_event( ev->instance_id, ev->modem_id, ev->event_id,
 ev->data, ev->datalen, ev->t );
// ...

在以上過程中,完成qcril_event.list鏈表的初始化,然后通過pthread_cond_wait進入阻塞狀態,當被解鎖后以及進入EventLoop循環,檢測到事件后,通過qcril_process_event處理。

初始化qcril各個模塊

Qcril在接到RILC的請求后,需要根據請求的類型將消息派發給不同的負責模塊,而qcril_init()就是完成各個模塊的初始化工作。

qcril_init方法部分代碼如下:

qcril_arb_init();  
qcril_init_state();  
qmi_ril_oem_hook_init();  
qcril_db_init();  
qcril_init_hash_table();//初始化Event table  
qcril_reqlist_init();  
// ...

在這里對qcril的各個模塊進行初始化。其中完成了很重要的一步就是將qcril_event_table表拷貝給qcril_hash_table,用於onRequest時對各種請求進行處理, qcril_init_hash_table方法如下:

for (reg_index = 0; reg_index < QCRIL_ARR_SIZE( qcril_event_table ); reg_index++)
 // ...
qcril_hash_table[hash_index] = &qcril_event_table[reg_index]; 
// ...

qcril_event_table是一個靜態表單, 里面保存了所有RILC中下發請求的ID以及相應的處理函數,表單部分內容如下:

{ QCRIL_REG_ALL_STATES( QCRIL_EVT_UIM_QMI_COMMAND_CALLBACK,
 qcril_uim_process_qmi_callback ) },

里面每一項都包含兩個元素:事件ID和處理函數,在處理這些消息時將會根據事件的ID查找並執行相應的處理函數。

​ 比如,對於得到當前SIM卡狀態這個請求,對應的ID為RIL_REQUEST_GET_SIM_STATUS,而其處理方法為qcril_uim_request_get_sim_status。

啟動EventLoop線程

初始化EventLoop時,在完成其鏈表的初始化過程后,通過pthread_cond_wait()將其阻塞,而現在要做的就是取消其阻塞狀態,使其進入消息檢測循環。

這是在qcril_event_start方法中完成的:

void qcril_event_start( void )
{
  QCRIL_MUTEX_LOCK( &qcril_event.startup_mutex, "[Main Thread] 
qcril_event.startup_mutex" );
qcril_event.started = 2; //更新狀態  
pthread_cond_broadcast(&qcril_event_startupCond);
//釋放EventLoop鎖
QCRIL_MUTEX_UNLOCK( &qcril_event.startup_mutex, "[Main Thread] 
qcril_event.startup_mutex" );
}

由於EventLoop被初始化后一直處於阻塞狀態,所以在這里將started狀態置為2后,對qcril_event_startupCond進行解鎖,從而使EventLoop進入循環。

3.4其他初始化過程

在qmi_ril_initiate_bootup方法如下:

qcril_setup_timed_callback( QCRIL_DEFAULT_INSTANCE_ID,
QCRIL_DEFAULT_MODEM_ID, qmi_ril_bootup_perform_core_or_start_polling, NULL,
NULL );

qmi_ril_bootup_perform_core_or_start_polling方法部分代碼如下:

init_res = qmi_ril_core_init();//qmi初始化

qmi_ril_core_init方法調用qcril_qmi_client_init方法完成qcril客戶端的初始化。

res = qcril_qmi_client_init();

將回調函數注冊給RILC

在Qcril的初始化完畢后,將自己的函數列表返回給RilC,也就是qcril_request_api:

static const RIL_RadioFunctions qcril_request_api[] = {
{ RIL_VERSION, onRequest_rid, currentState_rid, onSupports_rid, onCancel_rid, getVersion_rid }
};

這樣的話,在RIL中調用的接口就會進入該函數列表中進行處理。

這樣准備工作就完成了

QCRIL消息發送

當ril有請求過來時,就會調用ril庫的onRequest()方法,此時就會根據當前Qcril注冊的函數列表進入到qcril_request_api的onRequest_rid方法中,因此, onRequest_rid方法是QCRIL中的入口方法。調用的流程如如下:

img

qcril_execute_event首先調用qcril_hash_table_lookup方法從表中查找當前的Event,如果沒有找到當前的Request,就認為非法,找到之后,進入qcril_dispatch_event()中派發該Event

(entry_ptr->handler)(params_ptr, &ret);

ret是返回的結果,通過entry_ptr->handler調用當前Event的處理函數。這里的handler對應qcril_hash_table中的某一項。第一章中將qcril_event_table表中的數據拷貝給了qcril_hash_table,所以這里的handler可以理解為qcril_event_table中的某一項。

之后的流程就會進入到某個具體請求的處理函數中,比如打電話是對應的請求是RIL_REQUEST_DIAL,其處理函數為:qcril_qmi_voice_request_dial;掛斷電話對應的請求是RIL_REQUEST_HANGUP, 其處理函數為qcril_qmi_voice_request_hangup;

qcril_qmi_voice_request_hangup方法進一步調用qcril_qmi_client_send_msg_async發送到QMI層。

當然, qcril_qmi_client_send_msg_async是異步處理的, qcril_qmi_client_send_msg_sync是同步處理的。

最后,這2個方法都會調用qmi_client_send_msg_sync完成發送。

一些其他的處理方法或者會調用這兩個方法發送到QMI層,或者直接調用qmi_client_send_msg_sync發送。

2.1 QMI層消息處理

調用流程圖如下:

img

ril_err = qcril_qmi_client_send_msg_async ( QCRIL_QMI_CLIENT_VOICE,
                                          QMI_VOICE_ANSWER_CALL_REQ_V02,
                                          &ans_call_req_msg,
                                          sizeof(ans_call_req_msg),
                                          ans_call_resp_msg_ptr,
                                          sizeof(*ans_call_resp_msg_ptr),
                                          (void*)(uintptr_t)user_data);

這里選擇的qmi_service是voice。之所以選擇voice作為發送通道,是因為第二個參數QMI_VOICE_GET_CONFIG_REQ_V02。

qcril_qmi_client_send_msg_async方法主要邏輯如下:

if (NULL != client_info.qmi_svc_clients[svc_type])
{
  qmi_error =  qmi_client_send_msg_async_with_shm(client_info.qmi_svc_clients[svc_type],
   													// ...
}

這個函數首先會去判斷我們調用的這個voice的服務類型是否存在於QMI定義的服務列表中,如果不存在,還需要自己添加該service,如果存在,執行QMI client端發送消息的接口函數qmi_client_send_msg_async_with_shm。

該方法直接調用qmi_client_send_msg_sync方法,

rc = qmi_client_send_msg_sync(user_handle,
                                  msg_id,
                                  req_c_struct,
                                  req_c_struct_len,
                                  resp_c_struct,
                                  resp_c_struct_len,
                                  timeout_msecs);

同步消息在QMUX層發送到BP側后,會一直等待BP的響應所以函數的最后一個參數是一個timeout,而異步消息不需要。

qmi_client_send_msg_sync方法首先計算請求消息的長度和設定返回消息的最大長度,然后對請求消息按QMUX格式進行編碼通過函數qmi_service_send_msg_sync_millisec()發送出去,最后等待BP側返回的響應,將返回的response進行解碼。

2.2 QMUX層消息處理

QMUX消息處理流程圖如下:

img

qmi_service_send_msg_sync_millisec方法首先去獲取一些QMUX層所需要的消息,發送通道的conn_id。

QMUX消息格式用到的client_id

conn_id = QMI_SRVC_CLIENT_HANDLE_TO_CONN_ID (user_handle);
client_id = QMI_SRVC_CLIENT_HANDLE_TO_CLIENT_ID (user_handle);

然后打包到一個傳輸消息的結構體 qmi_service_txn_info_type *txn,這個txn在稍后跟進代碼中會發現,就是QMUX消息中的tranciationID

/* Initialize Id fields */
  txn->conn_id    = conn_id;
  txn->service_id = service_id;
  txn->client_id = client_id;
  txn->msg_id = msg_id;
  txn->api_flag = api_flag;
  /* Initialize fields */
  txn->srvc_txn_info.txn_type = QMI_TXN_SYNC;
  txn->srvc_txn_info.sync_async.sync.user_reply_buf = NULL;
  txn->srvc_txn_info.sync_async.sync.user_reply_buf_size = 0;
  txn->srvc_txn_info.sync_async.sync.rsp_rc = QMI_NO_ERR;
  txn->srvc_txn_info.sync_async.sync.qmi_err_code = QMI_SERVICE_ERR_NONE;

這些完成之后,調用qmi_service_send_msg方法發送,該方法會判斷服務ID和通道的conn_id是否有效:

if ((int)conn_id >= (int)QMI_MAX_CONN_IDS)
{
    return QMI_INTERNAL_ERR;
}
if ( qmi_qcci_internal_public_service_id_to_bookkeeping_service_id ( service_id ) >= 
    QMI_MAX_SERVICES)
{
    return QMI_INTERNAL_ERR;
}

首先調用qmi_service_write_std_srvc_msg_hdr,方法給發送過來的消息加上message ID和Length,

if (qmi_service_write_std_srvc_msg_hdr (&msg_buf,
                                          &msg_buf_size,
                                          msg_id,
                                          msg_buf_size) < 0)

所以不難猜測,在QMI發送端的接口函數qmi_client_send_msg_sync()中編解碼的原理是按照QMUX消息中的TLV格式進行編解碼。

再走到這步加上message ID和Length,整個QMUX SDU中的QMI service message已完成。

qmi_service_write_std_srvc_msg_hdr方法如下:

static
int qmi_service_write_std_srvc_msg_hdr (unsigned char **msg_buf,
int  *msg_buf_size,   unsigned long msg_id,  int  length)
{
  unsigned char *tmp_msg_buf;
  /* Back pointer up by 4 bytes */
  *msg_buf -= QMI_SRVC_STD_MSG_HDR_SIZE;
  *msg_buf_size += QMI_SRVC_STD_MSG_HDR_SIZE;
  tmp_msg_buf = *msg_buf;
  /* Write the message ID field (16 bits) */
  WRITE_16_BIT_VAL (tmp_msg_buf,msg_id);
  /* Write the length field */
  WRITE_16_BIT_VAL (tmp_msg_buf,length);
  return 0;
}

加message ID和Length的方法:消息指針的偏移。指針向前偏移,消息長度增加。

隨后用類似方法,通過函數qmi_service_write_std_txn_hdr_and_inc_txn_id ()將control flags和tranciation ID加上,完成整個QMUX SDU。

最后通過函數qmi_qmux_if_send_qmi_msg()發送到QMUX層,到這里,整個QMI interface的流程走完。

  • 主要作用:獲取上層發送的請求,選擇相應的serviceid,conn_id,client id,對消息完成整個QMUX SDU的封裝,發送到QMUX。

函數qmi_qmux_if_send_qmi_msg()的工作是:

  • 對上層發過來打包好的QMUX SDU完成加QMUX HEADER的工作。添加QMUX header的方法同樣是指針偏移。

qmi_qmux_if_send_qmi_msg方法直接調用qmi_qmux_if_send_to_qmux方法進行處理,首先將QMUX header里面的控制位,QMI服務ID,QMI客戶端ID打包到結構體hdr,再通過Memcpy完成:

/* Set up message for sending */
  memset(&hdr, 0, sizeof(qmi_qmux_if_msg_hdr_type));
  hdr.msg_id = msg_id;
  hdr.qmux_client_id = qmux_client_id;
  hdr.qmux_txn_id = qmux_txn_id;
  hdr.qmi_conn_id = qmi_conn_id;
  hdr.qmi_service_id = qmi_service_id;
  hdr.qmi_client_id = qmi_client_id;
  hdr.control_flags = 0; // Unused for TX to QMUX, only valid for RX
  /* Decrement msg pointer and increment msg_len */
  msg -= QMI_QMUX_IF_HDR_SIZE;
  msg_len += (int)QMI_QMUX_IF_HDR_SIZE;
  /* Copy header into message buffer */
  memcpy ((void *)msg, (void *)&hdr, QMI_QMUX_IF_HDR_SIZE);

到這里整個QMUXMessage完成封裝。然后會把封裝好的QMUXmessage發到下層,通過調用宏QMI_QMUX_IF_PLATFORM_TX_MSG()。

這個宏定義了函數linux_qmi_qmux_if_client_tx_msg()。

qmi_platform_qmux_if.h中宏QMI_QMUX_IF_PLATFORM_TX_MSG定義如下:

#define QMI_QMUX_IF_PLATFORM_TX_MSG(client,msg,msg_len) \
     linux_qmi_qmux_if_client_tx_msg (client,msg,msg_len)

因此,調用宏QMI_QMUX_IF_PLATFORM_TX_MSG就是調用linux_qmi_qmux_if_client_tx_msg方法,該方法通過socket,將消息發到linux_qmi_qmux_if_server的接口。

  if ((rc = send (client_fd,
                  (void *) msg,
                  (size_t) msg_len,
                  MSG_DONTWAIT | MSG_NOSIGNAL)) < 0)

底層消息發送

在linux_qmi_qmux_if_server.c文件的入口main()函數,通過一個select來監聽所有從linux_qmi_client端發出的socket,通過for循環調用linux_qmi_qmux_if_server_process_client_msg()處理這些監聽的消息。進入到函數linux_qmi_qmux_if_server_process_client_msg()后,

通過recv函數將監聽的socket的消息寫入buf_size這個buffer里面。調用流程圖如下:

img

if ((buf_size = recv (fd, (void*)&platform_msg_hdr,
  QMI_QMUX_IF_PLATFORM_SPECIFIC_HDR_SIZE,0))<= 0)

recv方法在接收socket時,並沒有全部接收。而且只接收了platform_msg_hdr。這個platform_msg_hdr是在linux_qmi_qmux_client里面定義的,server接收到后,首先會判斷從client端發過來的這個消息是否正常,包括client_id和消息長度。

remaining_bytes = (size_t) platform_msg_hdr.total_msg_size - QMI_QMUX_IF_PLATFORM_SPECIFIC_HDR_SIZE;
if ((buf_size = recv (fd, (void *)linux_qmi_qmux_if_rx_buf, remaining_bytes, 0)) <= 0)

如果client_id匹配不上 或時消息長度溢出,都會將消息丟棄,不會發送。如果判斷消息沒問題,就會將其余的消息(除去platform_msg_hdr)再次通過recv()函數從socket中接受,放到remaining_bytes這個buffer中,用qmi_qmux_tx_msg方法繼續處理。

在qmi_qmux_tx_msg()函數中,又會對之前打包好的QMUX消息進行去頭,拆分。

判斷QMUX header中的message_id,如果是QMI_MSG,則會調用函數qmi_qmux_tx_to_modem(),將拆分后的service_id,client_id等發送到下層。

{
    rc = qmi_qmux_tx_to_modem( msg_hdr.qmi_conn_id,
                              msg_hdr.qmi_service_id,
                              msg_hdr.qmi_client_id,
                              msg,
                              msg_len );
}

接下來在函數qmi_qmux_tx_to_modem(),對QMUX整個控制信道消息的頭進行一個重組,包括I/F Type。

完成QMI整個control channel message的構建。

/* I/F type is 1 for a QMUX message */
  WRITE_8_BIT_VAL (tmp_msg_ptr, 1);
 
  /* Length is length of message to send which includes the QMUX header, but since
  ** QMI_QMUX_HDR_SIZE includes the I/F byte and the length field in a QMUX
  ** message doesn't include this, we need to subtract 1
  */
  WRITE_16_BIT_VAL (tmp_msg_ptr, (msg_len - 1));
 
  /* Control flags byte should be set to 0 for control point */
  WRITE_8_BIT_VAL (tmp_msg_ptr, 0);
 
  /* Now put in service type and client ID */
  WRITE_8_BIT_VAL (tmp_msg_ptr, service_id);
  WRITE_8_BIT_VAL (tmp_msg_ptr, client_id);

然后通過宏QMI_QMUX_IO_PLATFORM_SEND_QMI_MSG調用ARM側進入共享內存和BP側交互的IO口函數linux_qmi_qmux_io_send_qmi_msg(),

qmi_platform_qmux_io.h中的宏定義如下:

#define QMI_QMUX_IO_PLATFORM_SEND_QMI_MSG(conn_id,msg_buf,len) \
  linux_qmi_qmux_io_send_qmi_msg (conn_id,msg_buf,len)

在這個linux_qmi_qmux_io_send_qmi_msg讀寫函數中,判斷如果當前AP側和BP側的連接通道是激活狀態的話,就通過write函數,將打包好的QMI消息,寫入連接通道信息里面的f_desc參數,

ret = write(conn_info->f_desc, (void*) msg_ptr, (size_t)msg_len);

到此,整個ARM流程結束。以上主要介紹AP側要發送一個請求到BP側,QMI是怎么對請求進行編碼成QMUX消息,怎么將編碼后的QMUX消息加頭組合成一種AP和BP可共同識別的消息格式,最后是怎么發送到BP側的。

Modem消息接收

消息初始化

初始化:qmi_modem_taskàqmii_init()àqmux_init()。qmux_init方法完成對控制通道的初始化后,

通過函數qmuxi_process_rx_sig方法開始從共享內存接收數據。調用流程如如下:

img

(void)qmi_set_sig_handler(QMI_QMUX_RX_SIGNAL, qmuxi_process_rx_sig, NULL);

在qmuxi_process_rx_sig方法中,首先通過dsm_dequeue()讀取在隊列中等待的QMUX消息,賦值給指針qmux_pdu表示QMUX消息的首地址。

qmux_pdu = dsm_dequeue( &qmux_s->io.rx_wm );

然后調用qmuxi_process_msg方法開始對AP側發過來的QMUX消息解包。

qmuxi_process_msg( qmux_s, qmux_pdu );

qmuxi_process_msg方法首先拆分IF Type,通過函數dsm_pull8()進行解包。然后判斷IF Type類型,然后把QMUX Message通過qmuxi_input()繼續處理。

在qmuxi_input()中,會拆分QMUX的消息頭,將消息頭大卸八塊,包括length,control_flags,client_id,service_id,然后調用qmi_framework_svc_recv方法將剩下的QMUX SDU從拆分出來的QMUX消息頭中,找到BP側相對應的service處理。

if((svci == NULL) || (!(svci->registered)))
  {
    status =  qmi_framework_svc_recv( qmi_instance_by_qmux_state(qmux_s),
        (qmux_service_e_type) qmux_hdr.svc_type, qmux_hdr.clid, *qmux_pdu );
    *qmux_pdu = NULL;
    return status;
  }

在qmi_framework_svc_recv方法中,並不會馬上根據service_id等對QMUX消息進行處理,這里也是一個接口,將消息打包成BP側的QMI_framework的一個command,發送出去。

qmi_framework_msg_hdr_type       msg_hdr;
qmi_framework_svc_info_type *    svc_info;
// ...
svc_info = qmi_framework_svc_state[service];

定義一個qmi_framework_svc_info_type的指針變量,對svc_info的操作就等於對qmi_framework_svc_state的操作。

打包到msg_hdr

 msg_hdr.common_hdr.service = service;
  msg_hdr.common_hdr.client_id = client_id;
  msg_hdr.common_hdr.qmi_instance = (int32)qmi_instance;
  msg_hdr.common_hdr.transaction_id = msg_x_id;
  msg_hdr.msg_ctl_flag =  msg_ctl;
  msg_hdr.msg_len = remaining_bytes;

最后

svc_info->cfg.cbs.cmd_hdlr(&msg_hdr,&sdu_in);

這個機制是怎么樣呢?以phone相關的qmi_voice.c為例。

消息分類處理

qmi_voice.c在qmi_voice_init方法進行初始化時,

qmi_mmode_set_cmd_handler(QMI_MMODE_CMD_VOICE_FW_CB, qmi_voice_handle_fw_cmd);
// ...
qmi_voicei_cfg.cmn_svc_cfg.fw_cfg.cbs.alloc_clid  = qmi_voice_fw_alloc_clid_cback;
qmi_voicei_cfg.cmn_svc_cfg.fw_cfg.cbs.dealloc_clid = qmi_voice_fw_dealloc_clid_cback;
qmi_voicei_cfg.cmn_svc_cfg.fw_cfg.cbs.init_cback  = qmi_voice_fw_init_cback;
qmi_voicei_cfg.cmn_svc_cfg.fw_cfg.cbs.cmd_hdlr  = qmi_voice_fw_req_cback;
  
qmi_voicei_cfg.cmn_svc_cfg.cmd_hdlr_array    = qmi_voicei_cmd_callbacks;
qmi_voicei_cfg.cmn_svc_cfg.cmd_num_entries   = VOICEI_CMD_MAX;
qmi_voicei_cfg.cmn_svc_cfg.service_id = QMUX_SERVICE_VOICE;
  
  /*-----------------------------------------------------------------------
  step 2:  calling QMI Framework API to register the service.
  ----------------------------------------------------------------------*/
  errval= qmi_framework_reg_service(QMUX_SERVICE_VOICE, 
&qmi_voicei_cfg.cmn_svc_cfg.fw_cfg);

初始化的時候,用結構體指針&qmi_voicei _cfg,到qmi_framework里面注冊服務,獲取AP測發送過來的qmux,可以關注給cmd_hdlr賦值的函數qmi_voice_fw_req_cback,這是從qmi_framework返回的回調函數。

因此,對於phone消息, qmi_framework_svc_recv都是回調qmi_voice_fw_req_cback進行處理,qmi_voice_fw_req_cback方法讀取QMUX消息的頭指針,和QMUX SDU的首地址。發送到voice服務里面專門的command_handle。

qmi_mmode_send_cmd(QMI_MMODE_CMD_VOICE_FW_CB, cmd_ptr);

在voice初始化可找到QMI_MMODE_CMD_VOICE_FW_CB對應的方法為qmi_voice_handle_fw_cmd。

qmi_mmode_send_cmd方法會調用qmi_voice_handle_fw_cmd方法,該方法根據不同的消息類型調用不同的方法進行處理,

switch(qmi_info->id)
  {
    case QMI_MMODE_FW_INIT_CB:
      qmi_voicei_fw_init_cback_hdlr(qmi_info->data.qmi_fw_info.init_cb.num_instances);
      break;
    case QMI_MMODE_FW_ALLOC_CLID_CB:
      qmi_voicei_fw_alloc_clid_hdlr(&qmi_info->data.qmi_fw_info.alloc_clid.msg_hdr);
      break;
    case QMI_MMODE_FW_DEALLOC_CLID_CB:
      qmi_voicei_fw_dealloc_clid_hdlr(&qmi_info->data.qmi_fw_info.dealloc_clid.msg_hdr);
      break;
    case QMI_MMODE_FW_REQ_CB:
      qmi_voicei_fw_req_hdlr(&qmi_info->data.qmi_fw_info.req_cb.msg_hdr,
                               qmi_info->data.qmi_fw_info.req_cb.sdu_in);
      break;
    default:
      QM_MSG_ERROR("Unsupported qmi-voice fw cmd");
      break;
  }

qmi_voicei_fw_req_hdlr並不會馬上對獲取的消息進行處理,而是判斷頭消息中的client_id是否符合,符合則通過函數qmi_mmode_svc_req_hdlr發送到request_handler中,

if( msg_hdr->common_hdr.client_id > 0 )
  {
    cl_sp = (qmi_voicei_client_state_type *) 
               qmi_voice_state.client[msg_hdr->common_hdr.client_id - 1];
    ASSERT(cl_sp);
   /*-------------------------------------------------------------------------
     Invoke the common svc request handler
    -------------------------------------------------------------------------*/
   qmi_mmode_svc_req_hdlr(&qmi_voicei_cfg.cmn_svc_cfg, msg_hdr, &cl_sp->common, 
                          sdu_in);

到此,幾乎所有的消息類型最后都會調用qmi_mmode_svc_req_hdlr方法發送到request_handler中。

消息分發

在qmi_mmode_svc_req_hdlr函數中,真正對QMUX消息進行處理。首先拆分頭消息中的service_id,control_flag等。

img

svc_id = svc_cfg->service_id;

然后通過一個while循環開始對QMUX SDU進行解包。

while (sdu_in)
  {
    /*-----------------------------------------------------------------------
      Extract service message header
    -----------------------------------------------------------------------*/
    temp = dsm_pull16( &sdu_in );
}

解包函數dsm_pull16()作用,按照一定的大小,從&sdu_in這個地址中獲取數據。獲取完后,跳出while循環,調用qmi_mmode_svci_dispatch_transaction方法進行處理。

qmi_mmode_svci_dispatch_transaction(&msg_hdr->common_hdr,svc_cfg,x_p);

其中參數x_p是解包后的qmux_sdu存放的buffer。

qmi_mmode_svci_input方法會通過request_handler對消息進行處理分發。

response_ptr = cmd_hdlr->request_hdlr( svc_cfg->svc_sp, cmd_buf_p, cl_sp, sdu_in );

request_hdlr方法是被封裝了的,沒法繼續跟進,BP側的接收處理到此結束。

modem消息發送

一般BP側處理完請求后,都會回應一個響應給AP,一般是用宏QMI_SVC_PKT_PUSH將要作為響應的消息發送出去。qmi_svc_utils.h中QMI_SVC_PKT_PUSH定義如下:

#define QMI_SVC_PKT_PUSH(pkt,val,len)  ( len == dsm_pushdown_packed(pkt,\
      val,\ len,\ DSM_DS_SMALL_ITEM_POOL ) )

將val里面的數據,傳len個長度到pkt的buffer里面,然后發送出去。Val和len是我們要作為響應的數據和數據包大小。QMI_SVC_PKT_PUSH()只能傳單一的參數,根據上文說明的QMUX消息中TLV格式的原理,可以用多個QMI_SVC_PKT_PUSH連着用,這樣可以將多個數據或參數打包到同一條消息中發送。

例如,在voice服務中,用多個QMI_SVC_PKT_PUSH回傳消息。

QMI_SVC_PKT_PUSH(&response, (void*)&info->call_id, sizeof(info->call_id))) )
// ...
QMI_SVC_PKT_PUSH(&response, (void *)&tag, VOICEI_TLV_TAG_SIZE)
// ...

最后還要記得寫上TLV的總長度,和標志位。標志位必須和AP側發出的請求相對應,如AP側發出請求為0x21,返回的標志位rec_tag也必須為0x21,方便返回AP后查表處理。將TLV消息放入response這個buffer后,會繼續打包QMUX的消息頭,然后通過函數qmi_mmode_svc_send_response()發送出去。

調用流程圖如下:

img

/* Send the response */
  status = qmi_mmode_svc_send_response( &common_hdr,cmd_buf_p, msg_ptr);

回傳的消息也是需要嚴格按照QMUX消息的格式的, qmi_mmode_svc_send_response方法中按照格式對QMUX消息進行重組,

msg_hdr.common_hdr.client_id      = common_hdr->client_id; 
msg_hdr.common_hdr.qmi_instance   = common_hdr->qmi_instance;  
msg_hdr.common_hdr.service        = common_hdr->service;  
msg_hdr.common_hdr.transaction_id = x_p->x_id;
msg_hdr.msg_ctl_flag = QMI_FLAG_MSGTYPE_RESP;
if( x_p->n_cmds > 1 )
{
  msg_hdr.msg_ctl_flag |= QMI_FLAG_MASK_COMPOUND;
}
msg_hdr.msg_len  = (uint16) dsm_length_packet(msg_ptr);
qmi_framework_svc_send_response( &msg_hdr, msg_ptr );

指針msg_ptr讀取x_p->resp_list[]中的響應消息,msg_hdr為qmux消息的頭。

通過函數qmi_framework_svc_send_respnse()發到qmi_framework,這只是qmi_framework的一個接口,會調用qmi_frameworki_svc_send方法,直接調用qmi_frameworki_svc_send方法,

status = qmi_frameworki_svc_send( msg_hdr, 
                               QMI_CMD_FRAMEWORK_SEND_RESPONSE,
                               msg_ptr );

qmi_frameworki_svc_send方法首先對接收到QMUX消息進行處理,將消息頭和內容放到結構體qmi_framework_msg_buf的成員變量中,

memscpy( &qmi_framework_msg_buf->msg_hdr, sizeof (*msg_hdr), msg_hdr, 
          sizeof (*msg_hdr) );
qmi_framework_msg_buf->dsm_item = msg_ptr;

然后調用qmi_framework_process_svc_send方法,

return qmi_framework_process_svc_send((void *)qmi_framework_msg_buf, qmi_cmd );

qmi_framework_process_svc_send方法首先校驗QMI服務給qmi_framework發出的command,判斷這次從BP側發出的消息是一個響應AP側請求的response還是主動發給AP的一個indicated。

if((qmi_cmd_id_e_type)qmi_cmd == QMI_CMD_FRAMEWORK_SEND_RESPONSE )
  {
    msg_type = QMI_FLAG_MSGTYPE_RESP;
  }
  else if((qmi_cmd_id_e_type)qmi_cmd == QMI_CMD_FRAMEWORK_SEND_IND)
  {
    msg_type = QMI_FLAG_MSGTYPE_IND;

然后判斷消息頭的有效性,最后通過函數qmi_frameworki_qmux_send()發送到BP側QMI的接口,

if (FALSE == qmi_frameworki_validate_msg_hdr(msg_hdr, msg_type ))
{
  LOG_MSG_ERROR_1 ("Msg header validation failed - unable to send Response for cmd %d",
                   msg_hdr->msg_ctl_flag);
    goto func_end;
  }
  return qmi_frameworki_qmux_send(msg_buf, msg_type, ind_cmd_id, msg_ptr);

qmi_frameworki_qmux_send方法首先會對QMUX消息進行一個過濾,如果消息異常,則丟棄。

if ( qmi_svc_filter_message( (qmi_instance_e_type) 
msg_buf->msg_hdr.common_hdr.qmi_instance, msg_buf->msg_hdr.common_hdr.service,
            msg_type, int_cmd_id) )
{
// ...

然后調用QMUX層的接口qmux_sio_send方法進行處理,

qmux_sio_send ( (qmi_instance_e_type)msg_buf->msg_hdr.common_hdr.qmi_instance, 
               msg_buf->msg_hdr.common_hdr.service, 
               msg_buf->msg_hdr.common_hdr.client_id, 
               msg_ptr);

qmux_sio_send方法首先調用dsm_length_packet方法對消息進行組裝,

if( dsm_length_packet(qmux_sdu) == 0)

然后組合上IF Type完成整個消息重組,

iftype = (byte)qmux_state[ qmi_instance ].io.port_info.frame_mode;

最后通過IO口的傳輸函數發送到AP側,

if( QMUX_DEVSTREAM_CONTROL != qmux_state[qmi_instance].io.port_info.qmi_stream )
{
    sio_transmit( qmux_state[qmi_instance].io.sio_handle, qmux_sdu );
}
else
{
    sio_control_transmit( qmux_state[qmi_instance].io.sio_handle, qmux_sdu );
}

這樣,消息就從modem側發出來了。

8 QCRLC消息接收

在qmi_qmux.c的qmi_qmux_pwr_up_init方法中,

QMI_QMUX_IO_PLATFORM_PWR_UP_INIT(qmi_qmux_rx_msg,qmi_qmux_event_cb);

qmi_qmux_rx_msg會接收處理BP側回發的消息, 主要是對從BP側接收到的消息進行解包,判斷BP側的消息類型是響應還是指示,轉發給上層client端處理。根據QMUX消息結構的長度,選擇是用1個字節的方式讀取還是2個字節的方式讀取,

/* Read the I/F byte, make sure it is a 1 */
READ_8_BIT_VAL(msg_ptr,i_f_byte);
 
if (i_f_byte != 1)
{
    QMI_ERR_MSG_1 ("qmi_qmux: Received invalid I/F byte = %d\n",i_f_byte);
     return;
}
/* Read the message length */
READ_16_BIT_VAL(msg_ptr, length);

判斷消息類型是BP側群發的廣播,還是專門針對某個client端的響應。

if (client_id == QMI_QMUX_INVALID_QMI_CLIENT_ID)
{
      qmi_qmux_rx_client_broadcast (conn_id, service_id, client_id, control_flags,
                   msg_ptr, msg_len);
}
else
{
   qmi_qmux_rx_client_msg (conn_id, service_id, client_id, control_flags,
                     msg_ptr, msg_len);
    }

接下去的流程,和之前AP發送的流程相似,不詳細介紹。


免責聲明!

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



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