Code Analysis of Event Kernel in RSL10 SDK (Part 2)
[Copy link]
First, let's review the key points in the previous article:
(1) The underlying Event Kernel is driven by messages. The receiver of the message is called a task, but it is not a task in RTOS. A task can be regarded as a collection of message processing functions (callback functions). Whenever the Kernel_Schedule() function is executed, the messages are processed one by one by passing them to the matching callback functions.
(2) A BLE application needs to create a task of type TASK_APP to exchange messages with the tasks inside the system to operate the BLE stack.
(3) The message ID is the basis for selecting which one from the default message processing function set of the receiving task. The message ID (including the task index and the message sequence number) is defined in the SDK. Users can define messages containing TASK_ID_APP.
(4) In addition to the message ID, the message processing function can obtain additional information from the sender task id and the receiver task id in the parameters. The length and format of the data contained in the message are free and are agreed upon by the sender and the receiver.
This article uses another example (peripheral_server) to see how BLE works.
In the BLE_Initialize() function, a GAPM_RESET_CMD message is sent :
/* Reset the stack */
cmd = KE_MSG_ALLOC(GAPM_RESET_CMD, TASK_GAPM, TASK_APP, gapm_reset_cmd);
cmd->operation = GAPM_RESET;
/* Send the message */
ke_msg_send(cmd);
This message is sent from TASK_APP to TASK_GAPM (task type) with data of struct gapm_reset_cmd structure type. The description of this command can be found in the manual:
Here, a GAPM_RESET_CMD message is sent to generate a soft reset. When completed, the GAPM task will respond with a GAPM_CMP_EVT message.
When ke_msg_send() is called in BLE_Initialize () , the message is only put into the queue. The message will not be processed until Kernel_Schedule() is executed later. Moreover, ke_create_task( TASK_APP ) has not been called at this time, so the message path cannot be established.
After Kernel_Schedule() in main() is executed, GAPM soft reset is executed. If GAPM_CMP_EVT message is responded (received by TASK_APP), what is the corresponding callback function?
Use GDB to check the table appm_default_state[] and it will be clear:
(gdb) print appm_default_state
$1 = {{id = 65535, func = 0x1003d1 <Msg_Handler>}
{id = 3328, func = 0x100d31 <GAPM_CmpEvt>}
{id = 3356, func = 0x100ced <GAPM_ProfileAddedInd>}
{id = 3585, func = 0x100fb1 <GAPC_ConnectionReqInd>}
{id = 3584, func = 0x100e51 <GAPC_CmpEvt>}
{id = 3587, func = 0x100f41 <GAPC_DisconnectInd>}
{id = 3594, func = 0x100dd9 <GAPC_GetDevInfoReqInd>}
{id = 3601, func = 0x100e55 <GAPC_ParamUpdatedInd>}
{id = 3599, func = 0x100e81 <GAPC_ParamUpdateReqInd>}
{id = 9217, func = 0x100605 <Batt_EnableRsp_Server>}
{id = 9220, func = 0x1005e5 <Batt_LevelNtfCfgInd>}
{id = 3091, func = 0x100715 <GATTC_ReadReqInd>}
{id = 3093, func = 0x100861 <GATTC_WriteReqInd>}
{id = 2817, func = 0x1006e5 <GATTM_AddSvcRsp>}
{id = 3072, func = 0x1009a9 <GATTC_CmpEvt>}
{id = 3095, func = 0x1009e1 <GATTC_AttInfoReqInd>}
{id = 3841, func = 0x1003d5 <APP_Timer>}
{id = 3842, func = 0x100469 <LED_Timer>}}
Find the value of GAPM_CMP_EVT from the definition and calculate: 3328
enum gapm_msg_id
{
/* Default event */
/// Command Complete event
GAPM_CMP_EVT = TASK_FIRST_MSG(TASK_ID_GAPM),
Therefore, when the GAPM_CMP_EVT message is responded to TASK_APP, the GAPM_CmpEvt() function will be called.
int GAPM_CmpEvt(ke_msg_id_t const msg_id,
struct gapm_cmp_evt const *param,
ke_task_id_t const dest_id, ke_task_id_t const src_id)
{
struct gapm_set_dev_config_cmd *cmd;
switch (param->operation)
{
/* A reset has occurred, configure the device and
* start a kernel timer for the application */
case (GAPM_RESET):
{
if (param->status == GAP_ERR_NO_ERROR)
{
/* Set the device configuration */
cmd = KE_MSG_ALLOC(GAPM_SET_DEV_CONFIG_CMD, TASK_GAPM, TASK_APP,
gapm_set_dev_config_cmd);
memcpy(cmd, gapmConfigCmd,
sizeof(struct gapm_set_dev_config_cmd));
free(gapmConfigCmd);
/* Send message */
ke_msg_send(cmd);
/* Start a timer to be used as a periodic tick timer for
* application */
ke_timer_set(APP_TEST_TIMER, TASK_APP, TIMER_200MS_SETTING);
/* Start LED timer */
ke_timer_set(LED_TIMER, TASK_APP, TIMER_200MS_SETTING);
}
}
break;
/* Device configuration updated */
case (GAPM_SET_DEV_CONFIG):
{
/* Start creating the GATT database */
ble_env[0].state = APPM_CREATE_DB;
/* Add the first required service in the database */
if (!Service_Add())
{
for (unsigned int i = 0; i < NUM_MASTERS; i++)
{
/* If there are no more services to add, go to the ready
* state */
ble_env[i].state = APPM_READY;
}
/* Start advertising since there are no services to add
* to the attribute database */
Advertising_Start();
}
}
break;
default:
{
/* No action required for other operations */
}
break;
}
return (KE_MSG_CONSUMED);
}
It makes a selection based on the data in the message, so take a look at the manual 's description of GAPM_CMP_EVT
This message is a response to the completion of an operation. The operation field in the message data indicates what operation was completed. Because the previous operation was a reset, the GAPM_RESET branch should be taken this time.
The program checks the status again. If there is no error, it sends the GAPM_SET_DEV_CONFIG_CMD message to configure GAPM. This message contains a lot of data in the previously initialized gapmConfigCmd global variable.
Next, the program sets two timers, each of which will generate the message APP_TEST_TIMER and LED_TIMER after 200ms. Using timers is a standard practice for generating delayed operations in the Event Kernel environment.
According to the manual, when the GAPM_SET_DEV_CONFIG_CMD message is processed, it will also respond to the GAPM_CMP_EVT message. At this time, another conditional branch in the above GAPM_CmpEvt() function is executed: the Service_Add() function is called.
bool Service_Add(void)
{
/* Check if another should be added in the database */
if (appm_add_svc_func_list[ble_env[0].next_svc] != NULL)
{
/* Call the function used to add the required service */
appm_add_svc_func_list[ble_env[0].next_svc] ();
/* Select the next service to add */
ble_env[0].next_svc++;
return (true);
}
return (false);
}
The writing of this Service_Add() function is quite special. After analysis, it will call Batt_ServiceAdd_Server() function when it is called for the first time . The code is listed as follows with some details omitted:
void Batt_ServiceAdd_Server(void)
{
struct bass_db_cfg *db_cfg;
struct gapm_profile_task_add_cmd *req =
KE_MSG_ALLOC_DYN(GAPM_PROFILE_TASK_ADD_CMD,
TASK_GAPM,
TASK_APP,
gapm_profile_task_add_cmd,
sizeof(struct bass_db_cfg));
/* Fill message */
......
/* Send the message */
ke_msg_send(req);
}
It can be seen that adding Battery Service is achieved by sending GAPM_PROFILE_TASK_ADD_CMD message.
This message will generate two responses. The first response will cause the callback function GAPM_ProfileAddedInd() .
int GAPM_ProfileAddedInd(ke_msg_id_t const msg_id,
struct gapm_profile_added_ind const *param,
ke_task_id_t const dest_id,
ke_task_id_t const src_id)
{
/* If the application is creating its attribute database, continue to add
* services; otherwise do nothing. */
if (ble_env[0].state == APPM_CREATE_DB)
{
PRINTF("__GAPM_PROFILE_ADDED_IND\n");
/* Add the next requested service */
if (!Service_Add())
{
for (unsigned int i = 0; i < NUM_MASTERS; i++)
{
/* If there are no more services to add, go to the ready state
* */
ble_env[i].state = APPM_READY;
}
/* No more services to add, start advertising */
Advertising_Start();
}
}
return (KE_MSG_CONSUMED);
}
The Service_Add() function is called again , this is the second time, which will cause the CustomService_ServiceAdd() function to be called. In this function, a custom service is added by sending a GATTM_ADD_SVC_REQ message to TASK_GATTM.
After GATTM processes the message, it will respond with GATTM_ADD_SVC_RSP , which is then received by the GATTC_AddSvcRsp() function.
int GATTM_AddSvcRsp(ke_msg_id_t const msg_id,
struct gattm_add_svc_rsp const *param,
ke_task_id_t const dest_id,
ke_task_id_t const src_id)
{
for (unsigned int i = 0; i < NUM_MASTERS; i++)
{
cs_env[i].start_hdl = param->start_hdl;
}
PRINTF("__CUSTOM SERVICE ADDED\n");
/* Add the next requested service */
if (!Service_Add())
{
/* All services have been added, go to the ready state
* and start advertising */
for (unsigned int i = 0; i < NUM_MASTERS; i++)
{
ble_env[i].state = APPM_READY;
}
Advertising_Start();
}
return (KE_MSG_CONSUMED);
}
When Service_Add() is called for the third time , there are no more services to add, so the Advertising_Start() function is executed.
You can see the implementation of Advertising_Start() , which sends the GAPM_START_ADVERTISE_CMD message to GAPM.
In this way, BLE enters the advertising state and wireless broadcasting begins. From the application's point of view, this is done in the "background". The callback function will only be executed when a specific event generates a message. For example, when a BLE central device initiates a connection, GAPM will respond with a GAPC_CONNECTIEN_REQ_IND message, which is processed by the GAPC_ConnectionReqInd() function.
int GAPC_ConnectionReqInd(ke_msg_id_t const msg_id,
struct gapc_connection_req_ind const *param,
ke_task_id_t const dest_id,
ke_task_id_t const src_id)
{
if (Connected_Peer_Num() < NUM_MASTERS)
{
uint8_t device_indx;
/* Search for the first disconnected link */
for (device_indx = 0; device_indx < NUM_MASTERS; device_indx++)
{
if (ble_env[device_indx].state != APPM_CONNECTED)
{
break;
}
}
PRINTF("__GAPC_CONNECTION_REQ_IND\n");
/* Set connection index */
ble_env[device_indx].conidx = KE_IDX_GET(src_id);
/* Check if the received connection handle was valid */
if (ble_env[device_indx].conidx != GAP_INVALID_CONIDX)
{
/* Change peer state in the app environment structure */
ble_env[device_indx].state = APPM_CONNECTED;
/* Retrieve the connection info from the parameters */
ble_env[device_indx].conhdl = param->conhdl;
Send_Connection_Confirmation(device_indx);
BLE_SetServiceState(true, device_indx);
}
}
return (KE_MSG_CONSUMED);
}
In the Send_Connection_Confirmation() function called therein , the establishment of the BLE connection is confirmed by sending a GAPC_CONNECTION_CFM message.
void Send_Connection_Confirmation(uint8_t device_indx)
{
struct gapc_connection_cfm *cfm;
/* Allocate connection confirmation message */
cfm = KE_MSG_ALLOC(GAPC_CONNECTION_CFM,
KE_BUILD_ID(TASK_GAPC, ble_env[device_indx].conidx),
KE_BUILD_ID(TASK_APP, device_indx), gapc_connection_cfm);
cfm->ltk_present = false;
cfm->svc_changed_ind_enable = 0;
cfm->pairing_lvl = GAP_PAIRING_BOND_UNAUTH;
/* Send the message */
ke_msg_send(cfm);
}
There are many other BLE service accesses that are similar, implemented by sending command messages and receiving response messages, which I will not list here.
In this way, the steps are separated into separate callback functions. Do you think it is confusing?
|