高通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通信原理圖:
兩個特點:
1.單一的物理鏈接總線,必須被多個邏輯設備所復用。
2.不同的邏輯設備要求獨立的控制信道和數據信道。
QMI終端原理圖如下:
從圖中可看出,整個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消息的格式
整個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)的格式。
TLV格式的數據存放在QMI Service Message里面的Value中。
Control Flags:表示消息是請求、響應還是指示。Datasheet見文檔。
TLV結構圖:
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 ];
初始化流程圖如下:
初始化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中的入口方法。調用的流程如如下:
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層消息處理
調用流程圖如下:
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消息處理流程圖如下:
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里面。調用流程圖如下:
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方法開始從共享內存接收數據。調用流程如如下:
(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等。
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()發送出去。
調用流程圖如下:
/* 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發送的流程相似,不詳細介紹。