V. A Complete Process
A complete process consists of the following four steps:
1. Eventloop receives RILJ requests and sends them to the reference library: RILJ -> Eventloop -> reference
2. Reference is responsible for converting commands into AT commands and sending them to Modem: reference -> Modem
3. reference receives a modem response through readerLoop and returns the data to Eventloop: Modem - > ReaderLoop
4. Eventloop returns the data to RILJ: Reader Loop - > LibRIL - > RILJ
Here we take RILJ's request for SimStatus as an example.
Let's start with a general flow chart:
5.1 RILJ –> Eventloop –> reference
The overview is as follows:
Step1: Monitor the request message sent by RILJ for EventLoop's fd_listen
Step2: Use fd_listen to create fdCommand handles and Event to listen for Socket flows
Step3: Processing the fdCommand stream to get request and token. And find the corresponding CommandInfo
Step4: Call the corresponding dispatchFunction encapsulation in CommandInfo to build RequestInfo
Step5: Initiate a request to Reference RIL by calling onRequest function in Reference RIL
5.1.2 Step1
RILJ sends a message to RILC to retrieve CardStatus
@Override
public void
getIccCardStatus(Message result) {
//Note: This RIL request has not been renamed to ICC,
// but this request is also valid for SIM and RUIM
RILRequest rr = RILRequest.obtain(RIL_REQUEST_GET_SIM_STATUS, result);
if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
send(rr);
}
5.1.3 Step2
As mentioned above, an Event containing a Socket handle is created to listen for information sent by RILJ. When the fd_Listen listens for new information, it triggers a callback function of Event in EventLoop, where the callback function of Event is listenCallback(), so let's see how to receive and process RILJ information.
static void listenCallback (int fd, short flags, void *param) {
/*
Getting s_fdCommand stream socket from s_fdListen listening socket
accept()Accept a client's connection request and return a new socket.
The so-called "new" means that this socket is not the same socket as the socket() that returns to listen for and receive connection requests from clients.
Communications with this accepted client are accomplished by sending and receiving data on this new socket.
s_fdListen can be understood as a listening port for the server, and fdCommand creates a flow of information for the port after listening for information.
*/
fdCommand = accept(fd, (sockaddr *) &peeraddr, &socklen);
//Gets the parameters of the current command
err = getsockopt(fdCommand, SOL_SOCKET, SO_PEERCRED, &creds, &szCreds);
//Set fdCommand to be non-blocking
ret = fcntl(fdCommand, F_SETFL, O_NONBLOCK);
//Place fdCommand in SocketListenParam
p_info->fdCommand = fdCommand;
//Build a RecordStream using fdCommand, which is used to read data
p_rs = record_stream_new(p_info->fdCommand, MAX_COMMAND_BYTES);
//Put it in SocketListenParam
p_info->p_rs = p_rs;
//Using fdCommand to build commands_event, the callback function is processCommands Callback in SocketListenParam
//Notice that the persist parameter here is true, so you need to manually delete Event.
ril_event_set (p_info->commands_event, p_info->fdCommand, 1,
p_info->processCommandsCallback, p_info);
//Add Event and activate EventLoop
rilEventAddWakeup (p_info->commands_event);
//Send a URC message to notify RILJ of a change in status
onNewCommandConnect(p_info->socket_id);
}
As can be seen from the above, there are two main things to do in listenCallback:
1. First, a stream socket (fdCommand) is created using fd_Listen to receive information, and a RecordStream and an Event are constructed using fdCommand. It's not hard to imagine that the Event callback function will read the message from the fdCommand, which will be read from the RecordStream. The Event callback function is processCommandsCallback(), which is analyzed in 4.3.2.
2. Call the onNewCommandConnect callback function and give RILJ notification feedback. Its main logic is as follows.
static void onNewCommandConnect(RIL_SOCKET_ID socket_id) {
//Send URC message to RILJ, RIL connection succeeded
RIL_UNSOL_RESPONSE(RIL_UNSOL_RIL_CONNECTED,
&rilVer, sizeof(rilVer), socket_id);
//Send a URC message to RILJ telling RILJ that Radio's status has changed
RIL_UNSOL_RESPONSE(RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED,
NULL, 0, socket_id);
}
5.1.2 Step3
In this function, start reading and processing messages sent by RILJ:
static void processCommandsCallback(int fd, short flags, void *param) {
RecordStream *p_rs;
void *p_record;
size_t recordlen;
int ret;
SocketListenParam *p_info = (SocketListenParam *)param;
//check
assert(fd == p_info->fdCommand);
//Here's the Reed Stream from the previous step
p_rs = p_info->p_rs;
for (;;) {
/* loop until EAGAIN/EINTR, end of stream, or other error */
//Read the data from RecordStream and put it into p_record. Successfully return 0
ret = record_stream_get_next(p_rs, &p_record, &recordlen);
if (ret == 0 && p_record == NULL) {//Finished reading
/* end-of-stream */
break;
} else if (ret < 0) {//read failure
break;
} else if (ret == 0) { /* && p_record != NULL *///Read successfully, not finished, need to continue reading
//Send RILJ layer data to Modem via AT
processCommandBuffer(p_record, recordlen, p_info->socket_id);
}
}
//Finishing after receiving
if (ret == 0 || !(errno == EAGAIN || errno == EINTR)) {
/* fatal error or end-of-stream */
if (ret != 0) {
RLOGE("error on reading command socket errno:%d\n", errno);
} else {
RLOGW("EOS. Closing command socket.");
}
//The command has been sent, closing the stream socket of the current command
close(fd);
p_info->fdCommand = -1;
s_fdCommand[p_info->socket_id] = -1;
//Delete the current Event (Note: The persist attribute of this Event is true and needs to be deleted manually)
ril_event_del(p_info->commands_event);
//Release RecordStream
record_stream_free(p_rs);
//Re-adding Socket Event between RILJ and RILC
//Re-add Listen Event to continue listening for the next connection
//Note: The persist attribute of s_listen_event is false, so it will be destroyed after each connection, and need to be added further.
/* start listening for new connections again */
rilEventAddWakeup(&s_listen_event[p_info->socket_id]);
//Callback when fdCommand closes
onCommandsSocketClosed(p_info->socket_id);
}
}
As you can see from the above code, there are two main things to do.
1. Get data from RecordStream and pass it to Modem
2. Re-add Socket to listen on Event
ProceCommandBuffer () function:
static int
processCommandBuffer(void *buffer, size_t buflen, RIL_SOCKET_ID socket_id) {
Parcel p;
status_t status;
int32_t request;
int32_t token;
RequestInfo *pRI;
int ret;
//Set the data to Parcel
p.setData((uint8_t *) buffer, buflen);
// status checked at end
//Read request code
status = p.readInt32(&request);
//Read token
status = p.readInt32 (&token);
//Initialize RequestInfo
pRI = (RequestInfo *)calloc(1, sizeof(RequestInfo));
//Set token
pRI->token = token;
//Setting CommandInfo
pRI->pCI = &(s_commands[request]);
//Setting SocketID
pRI->socket_id = socket_id;
//Set the next node, RequestInfo as linked list structure
pRI->p_next = *pendingRequestsHook;
//Calling CommandInfo's Distribution Function
pRI->pCI->dispatchFunction(p, pRI);
}
The above code flow is relatively simple, that is, to obtain the request code and token from the parameter data, then use them to construct RequestInfo, and finally call the dispatchFunction() function of CommandInfo in RequestInfo. The dispatchFunction function is a function pointer, and it has different implementations for different requests.
Then there are a few questions:
1. What is CommandInfo?
For example: {RIL_REQUEST_GET_SIM_STATUS, dispatch Void, responseSimStatus}
CommandInfo contains information about identifying RILJ request types, how requests are processed, and how requests are returned.
See 4.3.2 for details.
What is a request code?
From the s_commands[request], the request corresponds to the index value of the elements of the s_commands array. In RILJ and RILC, these two values should correspond to each other.What is a token?
The card can be regarded as the ID of the current request. When we get the data from Modem, we need to find the original request command according to different tokens, and then make the corresponding reply.What is RequestInfo?
The structure containing the request information is shown in 4.3.3.
5.1.2 Step4
The above function finally calls CommandInfo's distribution function to distribute the current Request.
static void
dispatchVoid (Parcel& p, RequestInfo *pRI) {
clearPrintBuf;
printRequest(pRI->token, pRI->pCI->requestNumber);//Print log
//Macro Definition: # define CALL_ONREQUEST(a, b, c, d, e) s_callbacks.onRequest((a), (b), (c), (d))
CALL_ONREQUEST(pRI->pCI->requestNumber, NULL, 0, pRI, pRI->socket_id);
}
In the distribution function, the onRequest function in the Reference RIL is finally called. Note that s_callbacks here are callbacks passed back when the Reference RIL is initialized.
For other types of distribution functions, such as dispatchString(), dispatchStrings(), dispatchInts(), etc., the onRequest function is ultimately called, but different distribution functions have done different processing of data before, and the final onRequest parameters are different.
5.1.2 Step5
//@ril_callbacks.c
static void onRequest(int request, void *data, size_t datalen, RIL_Token t, RIL_SOCKET_ID socket_id)
{
//Getting radioState
RIL_RadioState radioState = sState;
//If modem off, some requests are filtered
if (s_md_off && ...){
...
return;
}
//If Radio is not available, some requests are filtered
if (radioState == RADIO_STATE_UNAVAILABLE && ...){
...
return;
}
//If Radio shuts down, some requests are filtered
if (radioState == RADIO_STATE_OFF &&...){
...
return;
}
//Other filtering operations
...
//Here is the main processing logic.
//Different types of requests are handled by different functions, which are defined in different files for decoupling purposes.
//For example, the rilSimMain() function is defined in the mtk-ril/ril_sim.c file.
//Short-circuiting is used here, or if a function handles the request in front of it, it will jump out directly.
if (!(rilSimMain(request, data, datalen, t) ||
rilNwMain(request, data, datalen, t) ||
rilCcMain(request, data, datalen, t) ||
rilSsMain(request, data, datalen, t) ||
rilSmsMain(request, data, datalen, t) ||
rilStkMain(request, data, datalen, t) ||
rilOemMain(request, data, datalen, t) ||
rilDataMain(request, data, datalen, t)||
// MTK-START eMBMS feature.
rilEmbmsMain(request, data, datalen, t)||
rilPhbMain(request, data, datalen, t))) {
if (IMS_isRilRequestFromIms(t)) {
IMS_RIL_onRequestComplete(t, RIL_E_REQUEST_NOT_SUPPORTED, NULL, 0);
} else {
RIL_onRequestComplete(t, RIL_E_REQUEST_NOT_SUPPORTED, NULL, 0);
}
}
}
The above code shows that requests from different modules are handled by different functions, which are defined in different module C files. Specifically, you can refer to the mtk-ril directory.
Since we are concerned with RIL_REQUEST_GET_SIM_STATUS, it will be processed in rilSimMain ().
5.2 reference –> Modem
The overview is as follows:
Step6: Reference RIL calls processing functions for requests from different modules
Step7-9: Build AT commands sent to Modem based on the status of Radio and other information.
Step10-12: Send AT commands to Modem and wait for Modem to return information
5.2.1 Step6
//@mtk-ril/ril_sim.c
extern int rilSimMain(int request, void *data, size_t datalen, RIL_Token t)
{
switch (request)
{
case RIL_REQUEST_GET_SIM_STATUS:
requestGetSimStatus(data,datalen,t);
break;
...
default:
//If not processed, return 0
return 0; /* no match */
}
return 1; /* request find */
}
This method performs different functions for different requests, where requestGetSimStatus() is executed.
5.2.1 Step7-9
static void requestGetSimStatus(void *data, size_t datalen, RIL_Token t)
{
RIL_CardStatus_v6 *p_card_status = NULL;
char *p_buffer;
int buffer_size;
//Interacting with Modem, get SIM Status and populate it with p_card_status
int result = getCardStatus(&p_card_status,rid);
if (result == RIL_E_SUCCESS) {
//Processing the result RIL_CardStatus_v6
p_buffer = (char *)p_card_status;
buffer_size = sizeof(*p_card_status);
if (RIL_CARDSTATE_PRESENT == p_card_status->card_state && !s_md_off) {
//Get the remaining PINPUK attempts
getPINretryCount(&retryCounts, t, rid);
...
//Get AppStatus
iResult = getIccApplicationStatus(&p_card_status, rid, sessionId);
//Converting CardStatus to Char
p_buffer = (char *)p_card_status;
buffer_size = sizeof(*p_card_status);
}
}
//Return CardStatus response to Eventloop
RIL_onRequestComplete(t, result, p_buffer, buffer_size);
}
The above function first obtains CardStatus through the getCardStatus function, and then obtains the status of the card lock. If there is no lock, it obtains information such as AppStatus. Finally, the information is passed to LibRIL through callback function.
One of the two points to be concerned about above is that RIL_CardStatus_v6 contains the status information of the card. The second is the function implementation of getCardStatus, which is analyzed below.
RIL_CardStatus_v6 data structure:
typedef struct
{
//Card status
RIL_CardState card_state;
//Lock state
RIL_PinState universal_pin_state; /* applicable to USIM and CSIM: RIL_PINSTATE_xxx */
int gsm_umts_subscription_app_index; /* value < RIL_CARD_MAX_APPS, -1 if none */
int cdma_subscription_app_index; /* value < RIL_CARD_MAX_APPS, -1 if none */
int ims_subscription_app_index; /* value < RIL_CARD_MAX_APPS, -1 if none */
int num_applications; /* value <= RIL_CARD_MAX_APPS */
//Card application status
RIL_AppStatus applications[RIL_CARD_MAX_APPS];
} RIL_CardStatus_v6;
Friends familiar with RILJ will feel familiar. These RIL_Card State, RIL_Pin State, RIL_App Status have corresponding relationship with the UiccCard state of RILJ. RILC card status enum is defined in the ril.h file.
Then we analyze the function implementation of getCardStatus:
int getCardStatus(RIL_CardStatus_v6 **pp_card_status, RIL_SOCKET_ID rid) {
//This array defines all possible states of the card
static RIL_AppStatus app_status_array[] = {
// SIM_ABSENT = 0//card does not exist
{ RIL_APPTYPE_UNKNOWN, RIL_APPSTATE_UNKNOWN, RIL_PERSOSUBSTATE_READY,
NULL, NULL, 0, RIL_PINSTATE_UNKNOWN, RIL_PINSTATE_UNKNOWN },
// SIM_NOT_READY = 1//card not ready
{ RIL_APPTYPE_SIM, RIL_APPSTATE_DETECTED, RIL_PERSOSUBSTATE_READY,
NULL, NULL, 0, RIL_PINSTATE_UNKNOWN, RIL_PINSTATE_UNKNOWN },
// SIM_READY = 2//card ready
{ RIL_APPTYPE_SIM, RIL_APPSTATE_READY, RIL_PERSOSUBSTATE_READY,
NULL, NULL, 0, RIL_PINSTATE_UNKNOWN, RIL_PINSTATE_UNKNOWN },
// SIM_PIN = 3//PIN lock state
{ RIL_APPTYPE_SIM, RIL_APPSTATE_PIN, RIL_PERSOSUBSTATE_READY,
NULL, NULL, 0, RIL_PINSTATE_ENABLED_NOT_VERIFIED, RIL_PINSTATE_UNKNOWN },
...
}
//Getting sim status
sim_status = getSIMStatus(rid);
//Put state in variables
p_card_status->applications[0] = app_status_array[sim_status];
//Variables assigned to parameter pointers
*pp_card_status = p_card_status;
return RIL_E_SUCCESS;
}
It calls getSIMStatus to get the card status identifier from Modem, and then stores the corresponding state variables of the identifier in CardStatus.
getSIMStatus is implemented as follows:
SIM_Status getSIMStatus(RIL_SOCKET_ID rid)
{
//Variables encapsulating the results of AT commands
ATResponse *p_response = NULL;
char *cpinLine;
char *cpinResult;
//Send commands using AT channel and encapsulate the returned results in p_response
err = at_send_command_singleline("AT+CPIN?", "+CPIN:", &p_response, getChannelCtxbyProxy(rid));
//Get cpinLine from the result returned by AT
cpinLine = p_response->p_intermediates->line;
//Getting cpinResult from cpinLine
err = at_tok_nextstr(&cpinLine, &cpinResult);
//Get the card status from cpinResult, and see the SIM_Status enumeration class in ril_sim.h for the card status.
return ret;
}
The above getSIMStatus sends AT command directly to inquire SIM card information, and the return result is encapsulated in ATResponse. Get the result from ATResponse and get SIMStatus.
5.2.1 Step10-12
The at_send_command_singleline() function above will eventually call at_send_command_full_nolock() to write the request information to the AT channel.
static int at_send_command_full_nolock (const char *command, ATCommandType type,
const char *responsePrefix, const char *smspdu,
long long timeoutMsec, ATResponse **pp_outResponse)
{
//Write information to AT channel
err = writeline (command);
...
//Waiting until Modem returns information, if Modem returns information, it will be put into final Response
while (sp_response->finalResponse == NULL && s_readerClosed == 0) {
if (timeoutMsec != 0) {
err = pthread_cond_timedwait(&s_commandcond, &s_commandmutex, &ts);
} else {
err = pthread_cond_wait(&s_commandcond, &s_commandmutex);
}
if (err == ETIMEDOUT) {
err = AT_ERROR_TIMEOUT;
goto error;
}
}
...
}
5.3 Modem –> ReaderLoop
The overview is as follows:
Step13: ReadLooper retrieves data from Modem over AT channels
5.2.1 Step13
static void *readerLoop(void *arg)
{
for (;;) {
const char * line;
//Read a line from the AT channel file sentence fd and return NULL if timeout occurs
line = readline();
//If you don't read anything, break
if (line == NULL) {
break;
}
if(isSMSUnsolicited(line)) {
//For the processing of SMS UNSOL messages, note that this s_unsolHandler is a function parameter passed in at_open.
s_unsolHandler (line1, line2);
free(line1);
} else {
//Most of the messages are processed here.
processLine(line);
}
}
//Callback at the End of Reading AT Channel
onReaderClosed();
return NULL;
}
5.4 ReaderLoop –> LibRIL –> RILJ
The overview is as follows:
Step14: Processing upload data, distinguishing UNSOL from SOL
Step15: Processing SOL type upload data and storing the upload data in ATResponse will wake up Step11's waiting
Step16: Step7 function chain call returns
Step17: Call the callback function RIL_onRequestComplete() of LibRIL
Step18-20: Call the responseFunction specified by CommandInfo in Step3 to build the information to be returned to RILJ
Step21-23: Send the return information back to RILJ
5.4.1 Step14
static void processLine(const char *line)
{
//sp_response isNULL,Expressed as USOL news
//Further discussion on sp_response
if (sp_response == NULL) {
/* no command pending */
handleUnsolicited(line);
} else if (isFinalResponseSuccess(line)) {
//Processing of SOL messages
sp_response->success = 1;
handleFinalResponse(line);
}
...
pthread_mutex_unlock(&s_commandmutex);
}
5.4.1 Step15
static void handleFinalResponse(const char *line, RILChannelCtx *p_channel)
{
ATResponse *p_response = p_channel->p_response;
p_response->finalResponse = strdup(line);
//Write data to p_response
pthread_cond_signal(&p_channel->commandcond);
}
5.4.1 Step16
Get the desired information from Modem using the AT command. The result is passed up layer by layer and eventually back to the onRequest function that originated the request.
5.4.1 Step17
Here we begin with the result return function RIL_onRequestComplete() of the onRequest function:
Is RIL_onRequestComplete familiar with the callback function inside RIL_Env in LibRIL?
RIL_onRequestComplete(RIL_Token t, RIL_Errno e, void *response, size_t responselen) {
//This is the variable that encapsulates the request information earlier.
RequestInfo *pRI;
pRI = (RequestInfo *)t;
if (pRI->cancelled == 0) {
Parcel p;
//Write token
p.writeInt32 (pRI->token);
//Call the return function to package the data
ret = pRI->pCI->responseFunction(p, response, responselen);
}
//Send packaged data to RILJ
sendResponse(p, socket_id);
}
First, the returned data is written to Parcel. Different data is written by different functions, namely responseFunction.
Finally, sendResponse is called to send Parcel data to RILJ.
The following analysis:
Processing Command: {RIL_REQUEST_GET_SIM_STATUS, dispatchVoid, responseSimStatus} from the previous request
You can see that the final data returned is using the responseSimStatus function
5.4.1 Step18-20
static int responseSimStatus(Parcel &p, void *response, size_t responselen) {
responseSimStatusV6(p, response);
}
static void responseSimStatusV6(Parcel &p, void *response) {
RIL_CardStatus_v6 *p_cur = ((RIL_CardStatus_v6 *) response);
//Write data to Parcel
p.writeInt32(p_cur->card_state);
p.writeInt32(p_cur->universal_pin_state);
p.writeInt32(p_cur->gsm_umts_subscription_app_index);
p.writeInt32(p_cur->cdma_subscription_app_index);
p.writeInt32(p_cur->ims_subscription_app_index);
sendSimStatusAppInfo(p, p_cur->num_applications, p_cur->applications);
}
static void sendSimStatusAppInfo(Parcel &p, int num_apps, RIL_AppStatus appStatus[]) {
p.writeInt32(num_apps);
startResponse;
for (int i = 0; i < num_apps; i++) {
p.writeInt32(appStatus[i].app_type);
p.writeInt32(appStatus[i].app_state);
p.writeInt32(appStatus[i].perso_substate);
writeStringToParcel(p, (const char*)(appStatus[i].aid_ptr));
writeStringToParcel(p, (const char*)
(appStatus[i].app_label_ptr));
p.writeInt32(appStatus[i].pin1_replaced);
p.writeInt32(appStatus[i].pin1);
p.writeInt32(appStatus[i].pin2);
appendPrintBuf("%s[app_type=%d,app_state=%d,perso_substate=%d,\
aid_ptr=%s,app_label_ptr=%s,pin1_replaced=%d,pin1=%d,pin2=%d],",
printBuf,//static char printBuf[PRINTBUF_SIZE];LibRIL Cached information
appStatus[i].app_type,
appStatus[i].app_state,
appStatus[i].perso_substate,
appStatus[i].aid_ptr,
appStatus[i].app_label_ptr,
appStatus[i].pin1_replaced,
appStatus[i].pin1,
appStatus[i].pin2);
}
closeResponse;
}
All these calls do is write the returned data to Parcel.
5.4.1 Step21-23
static int
sendResponse (Parcel &p, RIL_SOCKET_ID socket_id) {
printResponse;
return sendResponseRaw(p.data(), p.dataSize(), socket_id);
}
static int
sendResponseRaw (const void *data, size_t dataSize, RIL_SOCKET_ID socket_id) {
//Here, fdCommand is the stream socket generated by fd_Listen as mentioned earlier.
int fd = s_ril_param_socket[socket_id].fdCommand;
//Write data to Socket and RILJ will receive it last time
ret = blockingWrite(fd, data, dataSize);
}
Call the stream socket to return the data to RILJ.
Since then, a request process for RILC has been completed.