https://lupyuen.github.io/articles/wifi Pick another theme! Reverse Engineering WiFi on RISC-V BL602 * 1 BL602 WiFi Demo Firmware + 1.1 Register WiFi Event Handler + 1.2 Start WiFi Firmware Task + 1.3 Start WiFi Manager Task + 1.4 Connect to WiFi Network + 1.5 Send HTTP Request * 2 Connect to WiFi Access Point + 2.1 Send request to WiFi Manager Task + 2.2 WiFi Manager State Machine + 2.3 Send request to LMAC + 2.4 Trigger LMAC Interrupt * 3 Decompiled WiFi Demo Firmware + 3.1 Linking to decompiled code * 4 WiFi Firmware Task + 4.1 Start Firmware Task + 4.2 Schedule Kernel Events + 4.3 Handle Transmit Payload + 4.4 Another Transmit Payload * 5 CEVA RivieraWaves + 5.1 Upper Medium Access Control + 5.2 Lower Medium Access Control * 6 WiFi Supplicant * 7 WiFi Physical Layer * 8 Quantitative Analysis + 8.1 Extract the decompiled functions + 8.2 Load functions into spreadsheet + 8.3 Classify the decompiled functions + 8.4 Match the decompiled functions + 8.5 Count the lines of code * 9 Other Modules * 10 GitHub Search Is Our Best Friend! * 11 What's Next * 12 Notes 7 Jul 2021 Today we shall Reverse Engineer the WiFi Driver on the BL602 RISC-V + WiFi SoC and learn what happens inside... Guided by the (incomplete) source code that we found for the driver. Why Reverse Engineer the BL602 WiFi Driver? 1. Education: To learn how WiFi Packets are transmitted and received on BL602. 2. Troubleshooting: If the WiFi Driver doesn't work right, we should be able to track down the problem. (Maybe fix it too!) 3. Auditing: To be sure that WiFi Packets are transmitted / received correctly and securely. (See this non-BL602 example) 4. Replacement: Perhaps one day we might replace the Closed-Source WiFi Driver by an Open Source driver. (Maybe Openwifi?) Read on and join me on the Reverse Engineering journey! Quantitative Analysis of Decompiled BL602 WiFi Firmware 1 BL602 WiFi Demo Firmware Let's study the source code of the BL602 WiFi Demo Firmware from the BL602 IoT SDK: bl602_demo_wifi In the demo firmware we shall... 1. Register the WiFi Event Handler that will handle WiFi Events 2. Start the WiFi Firmware Task that will control the BL602 WiFi Firmware 3. Start the WiFi Manager Task that will manage the WiFi Connection State 4. Connect to a WiFi Access Point 5. Send a HTTP Request 1.1 Register WiFi Event Handler When the firmware starts, we register a Callback Function that will handle WiFi Events: main.c // Called at startup to init drivers and run event loop static void aos_loop_proc(void *pvParameters) { // Omitted: Init the drivers ... // Register Callback Function for WiFi Events aos_register_event_filter( EV_WIFI, // Event Type event_cb_wifi_event, // Event Callback Function NULL); // Event Callback Argument // Start WiFi Networking Stack cmd_stack_wifi(NULL, 0, 0, NULL); // Run event loop aos_loop_run(); } (We'll see event_cb_wifi_event in a while) This startup code calls cmd_stack_wifi to start the WiFi Networking Stack. Let's look inside... 1.2 Start WiFi Firmware Task In cmd_stack_wifi we start the WiFi Firmware Task like so: main.c // Start WiFi Networking Stack static void cmd_stack_wifi(char *buf, int len, int argc, char **argv) { // Check whether WiFi Networking is already started static uint8_t stack_wifi_init = 0; if (1 == stack_wifi_init) { return; } // Already started stack_wifi_init = 1; // Start WiFi Firmware Task (FreeRTOS) hal_wifi_start_firmware_task(); // Post a WiFi Event to start WiFi Manager Task aos_post_event( EV_WIFI, // Event Type CODE_WIFI_ON_INIT_DONE, // Event Code 0); // Event Argument } (We'll cover hal_wifi_start_firmware_task later in this article) After starting the task, we post the WiFi Event CODE_WIFI_ON_INIT_DONE to start the WiFi Manager Task. Let's look inside the WiFi Event Handler... 1.3 Start WiFi Manager Task Here's how we handle WiFi Events: main.c // Callback Function for WiFi Events static void event_cb_wifi_event(input_event_t *event, void *private_data) { // Handle the WiFi Event switch (event->code) { // Posted by cmd_stack_wifi to start Wi-Fi Manager Task case CODE_WIFI_ON_INIT_DONE: // Start the WiFi Manager Task (FreeRTOS) wifi_mgmr_start_background(&conf); break; // Omitted: Handle other WiFi Events When we receive the WiFi Event CODE_WIFI_ON_INIT_DONE, we start the WiFi Manager Task (in FreeRTOS) by calling wifi_mgmr_start_background. wifi_mgmr_start_background comes from the BL602 WiFi Driver. (See the source code) 1.4 Connect to WiFi Network Now that we have started both WiFi Background Tasks (WiFi Firmware Task and WiFi Manager Task), let's connect to a WiFi Network! The demo firmware lets us enter this command to connect to a WiFi Access Point... # wifi_sta_connect YOUR_WIFI_SSID YOUR_WIFI_PASSWORD Here's how the wifi_sta_connect command is implemented: main.c // Connect to WiFi Access Point static void wifi_sta_connect(char *ssid, char *password) { // Enable WiFi Client wifi_interface_t wifi_interface = wifi_mgmr_sta_enable(); // Connect to WiFi Access Point wifi_mgmr_sta_connect( wifi_interface, // WiFi Interface ssid, // SSID password, // Password NULL, // PMK NULL, // MAC Address 0, // Band 0); // Frequency } We call wifi_mgmr_sta_enable from the BL602 WiFi Driver to enable the WiFi Client. ("STA" refers to "WiFi Station", which means WiFi Client) Then we call wifi_mgmr_sta_connect (also from the BL602 WiFi Driver) to connect to the WiFi Access Point. (We'll study the internals of wifi_mgmr_sta_connect in the next chapter) 1.5 Send HTTP Request Now we enter this command to send a HTTP Request over WiFi... # httpc Here's the implementation of the httpc command: main.c // Send a HTTP GET Request with LWIP static void cmd_httpc_test(char *buf, int len, int argc, char **argv) { // Check whether a HTTP Request is already running static httpc_connection_t settings; static httpc_state_t *req; if (req) { return; } // Request already running // Init the LWIP HTTP Settings memset(&settings, 0, sizeof(settings)); settings.use_proxy = 0; settings.result_fn = cb_httpc_result; settings.headers_done_fn = cb_httpc_headers_done_fn; // Send a HTTP GET Request with LWIP httpc_get_file_dns( "nf.cr.dandanman.com", // Host 80, // Port "/ddm/ContentResource/music/204.mp3", // URI &settings, // Settings cb_altcp_recv_fn, // Callback Function &req, // Callback Argument &req); // Request } On BL602 we use LWIP, the Lightweight IP Stack to do IP, UDP, TCP and HTTP Networking. httpc_get_file_dns is documented here For more details on the BL602 WiFi Demo Firmware, check out the docs... * BL602 WiFi Demo Firmware Docs Let's reverse engineer the BL602 WiFi Demo Firmware... And learn what happens inside! Connecting to WiFi Access Point 2 Connect to WiFi Access Point What really happens when BL602 connects to a WiFi Access Point? To understand how BL602 connects to a WiFi Access Point, let's read the Source Code from the BL602 WiFi Driver. Watch what happens as we... 1. Send the Connect Request to the WiFi Manager Task 2. Process the Connect Request with WiFi Manager's State Machine 3. Forward the Connect Request to the WiFi Hardware (LMAC) 4. Trigger an LMAC Interrupt to perform the request 2.1 Send request to WiFi Manager Task Earlier we called wifi_mgmr_sta_connect to connect to the WiFi Access Point. Here's what happens inside: wifi_mgmr_ext.c // Connect to WiFi Access Point int wifi_mgmr_sta_connect(wifi_interface_t *wifi_interface, char *ssid, char *psk, char *pmk, uint8_t *mac, uint8_t band, uint16_t freq) { // Set WiFi SSID and PSK wifi_mgmr_sta_ssid_set(ssid); wifi_mgmr_sta_psk_set(psk); // Connect to WiFi Access Point return wifi_mgmr_api_connect(ssid, psk, pmk, mac, band, freq); } We set the WiFi SSID and PSK. Then we call wifi_mgmr_api_connect to connect to the access point. wifi_mgmr_api_connect does this: wifi_mgmr_api.c // Connect to WiFi Access Point int wifi_mgmr_api_connect(char *ssid, char *psk, char *pmk, uint8_t *mac, uint8_t band, uint16_t freq) { // Omitted: Copy PSK, PMK, MAC Address, Band and Frequency ... // Send Connect Request to WiFi Manager Task wifi_mgmr_event_notify(msg); return 0; } wifi_mgmr_api_connect Here we call wifi_mgmr_event_notify to send the Connect Request to the WiFi Manager Task. wifi_mgmr_event_notify is defined in wifi_mgmr.c ... // Send request to WiFi Manager Task int wifi_mgmr_event_notify(wifi_mgmr_msg_t *msg) { // Omitted: Wait for WiFi Manager to start ... // Send request to WiFi Manager via Message Queue if (os_mq_send( &(wifiMgmr.mq), // Message Queue msg, // Request Message msg->len)) { // Message Length // Failed to send request return -1; } return 0; } How does os_mq_send send the request to the WiFi Manager Task? os_mq_send calls FreeRTOS to deliver the Request Message to WiFi Manager's Message Queue: os_hal.h #define os_mq_send(mq, msg, len) \ (xMessageBufferSend(mq, msg, len, portMAX_DELAY) > 0 ? 0 : 1) wifi_mgmr_event_notify 2.2 WiFi Manager State Machine The WiFi Manager runs a State Machine in its Background Task (FreeRTOS) to manage the state of each WiFi Connection. What happens when WiFi Manager receives our request to connect to a WiFi Access Point? Let's find out in wifi_mgmr.c ... // Called when WiFi Manager receives Connect Request static void stateIdleAction_connect( void *oldStateData, struct event *event, void *newStateData) { // Set the WiFi Profile for the Connect Request wifi_mgmr_msg_t *msg = event->data; wifi_mgmr_profile_msg_t *profile_msg = (wifi_mgmr_profile_msg_t*) msg->data; profile_msg->ssid_tail[0] = '\0'; profile_msg->psk_tail[0] = '\0'; // Remember the WiFi Profile in the WiFi Manager wifi_mgmr_profile_add(&wifiMgmr, profile_msg, -1); // Connect to the WiFi Profile. TODO: Other security support bl_main_connect( (const uint8_t *) profile_msg->ssid, profile_msg->ssid_len, (const uint8_t *) profile_msg->psk, profile_msg->psk_len, (const uint8_t *) profile_msg->pmk, profile_msg->pmk_len, (const uint8_t *) profile_msg->mac, (const uint8_t) profile_msg->band, (const uint16_t) profile_msg->freq); } stateIdleAction_connect Here we set the WiFi Profile and call bl_main_connect to connect to the profile. In bl_main_connect we set the Connection Parameters for the 802.11 WiFi Protocol: bl_main.c // Connect to the WiFi Profile int bl_main_connect(const uint8_t* ssid, int ssid_len, const uint8_t *psk, int psk_len, const uint8_t *pmk, int pmk_len, const uint8_t *mac, const uint8_t band, const uint16_t freq) { // Connection Parameters for 802.11 WiFi Protocol struct cfg80211_connect_params sme; // Omitted: Set the 802.11 Connection Parameters ... // Connect to WiFi Network with the 802.11 Connection Parameters bl_cfg80211_connect(&wifi_hw, &sme); return 0; } The Connection Parameters are passed to bl_cfg80211_connect, defined in bl_main.c ... // Connect to WiFi Network with the 802.11 Connection Parameters int bl_cfg80211_connect(struct bl_hw *bl_hw, struct cfg80211_connect_params *sme) { // Will be populated with the connection result struct sm_connect_cfm sm_connect_cfm; // Forward the Connection Parameters to the LMAC int error = bl_send_sm_connect_req(bl_hw, sme, &sm_connect_cfm); // Omitted: Check connection result Which calls bl_send_sm_connect_req to send the Connection Parameters to the WiFi Hardware (LMAC). Let's dig in and find out how... bl_send_sm_connect_req 2.3 Send request to LMAC What is LMAC? Lower Medium Access Control (LMAC) is the firmware that runs inside the BL602 WiFi Radio Hardware and executes the WiFi Radio functions. (Like sending and receiving WiFi Packets) To connect to a WiFi Access Point, we pass the Connection Parameters to LMAC by calling bl_send_sm_connect_req, defined in bl_msg_tx.c ... // Forward the Connection Parameters to the LMAC int bl_send_sm_connect_req(struct bl_hw *bl_hw, struct cfg80211_connect_params *sme, struct sm_connect_cfm *cfm) { // Build the SM_CONNECT_REQ message struct sm_connect_req *req = bl_msg_zalloc(SM_CONNECT_REQ, TASK_SM, DRV_TASK_ID, sizeof(struct sm_connect_req)); // Omitted: Set parameters for the SM_CONNECT_REQ message ... // Send the SM_CONNECT_REQ message to LMAC Firmware return bl_send_msg(bl_hw, req, 1, SM_CONNECT_CFM, cfm); } Here we compose an SM_CONNECT_REQ message that contains the Connection Parameters. ("SM" refers to the LMAC State Machine by RivieraWaves) Then we call bl_send_msg to send the message to LMAC: bl_msg_tx.c // Send message to LMAC Firmware static int bl_send_msg(struct bl_hw *bl_hw, const void *msg_params, int reqcfm, lmac_msg_id_t reqid, void *cfm) { // Omitted: Allocate a buffer for the message ... // Omitted: Copy the message to the buffer ... // Add the message to the LMAC Message Queue int ret = bl_hw->cmd_mgr.queue(&bl_hw->cmd_mgr, cmd); bl_send_msg The code above calls ipc_host_msg_push to add the message to the LMAC Message Queue: ipc_host.c // Add the message to the LMAC Message Queue. // IPC = Interprocess Communication int ipc_host_msg_push(struct ipc_host_env_tag *env, void *msg_buf, uint16_t len) { // Get the address of the IPC message buffer in Shared RAM uint32_t *src = (uint32_t*) ((struct bl_cmd *) msg_buf)->a2e_msg; uint32_t *dst = (uint32_t*) &(env->shared->msg_a2e_buf.msg); // Copy the message to the IPC message buffer for (int i = 0; i < len; i += 4) { *dst++ = *src++; } env->msga2e_hostid = msg_buf; // Trigger an LMAC Interrupt to send the message to EMB // IPC_IRQ_A2E_MSG is 2 ipc_app2emb_trigger_set(IPC_IRQ_A2E_MSG); ipc_host_msg_push After copying the message to the LMAC Message Queue (in Shared RAM), we call ipc_app2emb_trigger_set to trigger an LMAC Interrupt. LMAC (and the BL602 Radio Hardware) will then transmit the proper WiFi Packets to establish a network connection with the WiFi Access Point. And that's how BL602 connects to a WiFi Access Point! ipc_app2emb_trigger_set 2.4 Trigger LMAC Interrupt But how do we trigger an LMAC Interrupt? // Trigger an LMAC Interrupt to send the message to EMB // IPC_IRQ_A2E_MSG is 2 ipc_app2emb_trigger_set(IPC_IRQ_A2E_MSG); Let's look inside ipc_app2emb_trigger_set and learn how it triggers an LMAC Interrupt: reg_ipc_app.h // WiFi Hardware Register Base Address #define REG_WIFI_REG_BASE 0x44000000 // IPC Hardware Register Base Address #define IPC_REG_BASE_ADDR 0x00800000 // APP2EMB_TRIGGER Register Definition // Bits Field Name Reset Value // ----- ------------------ ----------- // 31:00 APP2EMB_TRIGGER 0x0 #define IPC_APP2EMB_TRIGGER_ADDR 0x12000000 #define IPC_APP2EMB_TRIGGER_OFFSET 0x00000000 #define IPC_APP2EMB_TRIGGER_INDEX 0x00000000 #define IPC_APP2EMB_TRIGGER_RESET 0x00000000 // Write to IPC Register #define REG_IPC_APP_WR(env, INDEX, value) \ (*(volatile u32 *) ((u8 *) env + IPC_REG_BASE_ADDR + 4*(INDEX)) \ = value) // Trigger LMAC Interrupt static inline void ipc_app2emb_trigger_set(u32 value) { // Write to WiFi IPC Register at address 0x4480 0000 REG_IPC_APP_WR( REG_WIFI_REG_BASE, IPC_APP2EMB_TRIGGER_INDEX, value); } This code triggers an LMAC Interrupt by writing to the WiFi Hardware Register (for Interprocess Communication) at... REG_WIFI_REG_BASE + IPC_REG_BASE_ADDR + 4 * IPC_APP2EMB_TRIGGER_INDEX Which gives us address 0x4480 0000 Wait... Is address 0x4480 0000 documented anywhere? Nope it's not documented in the BL602 Reference Manual... Undocumented WiFi Hardware Registers In fact the entire region of WiFi Hardware Registers at 0x4400 0000 is undocumented. We've just uncovered a BL602 WiFi Secret! 3 Decompiled WiFi Demo Firmware Are we really Reverse Engineering the BL602 WiFi Driver? Not quite. So far we've been reading the published source code for the BL602 WiFi Driver. Can we do some serious Reverse Engineering now? Most certainly! A big chunk of the BL602 WiFi Driver doesn't come with any source code. (Like the functions for WiFi WPA Authentication) But BraveHeartFLOSSDev did an excellent job decompiling into C (with Ghidra) the BL602 WiFi Demo Firmware... * BraveHeartFLOSSDev/bl602nutcracker1 (We'll use this fork) We shall now study this Decompiled C Code... And do some serious Reverse Engineering of the BL602 WiFi Driver! More about Ghidra 3.1 Linking to decompiled code Sadly GitHub won't show our huge Decompiled C Files in the web browser. So deep-linking to specific lines of code will be a problem. Here's the workaround for deep-linking... 1. Download the repo of Decompiled C Files... git clone --recursive https://github.com/lupyuen/bl602nutcracker1 2. When we see a link like this... This is the decompiled code: bl602_demo_wifi.c Right-click (or long press) the link. Select Copy Link Address 3. Paste the address into a text editor. We will see this... https://github.com/lupyuen/bl602nutcracker1/blob/main/bl602_demo_wifi.c#L38512-L38609 4. Note that the address ends with... bl602_demo_wifi.c#L38512-L38609 5. This means that we should... Open the downloaded source file bl602_demo_wifi.c in our code editor (like VSCode) And jump to line number 38512 (use Ctrl-G) 6. And we shall see the referenced Decompiled C Code... Assertions 4 WiFi Firmware Task The BL602 WiFi Driver operates with two Background Tasks (FreeRTOS)... * WiFi Manager Task: Manages the WiFi Connection State * WiFi Firmware Task: Controls the WiFi Firmware We've covered the WiFi Manager Task. (Remember the State Machine?) Now we dive into the WiFi Firmware Task. Watch what happens as we... 1. Start the WiFi Firmware Task 2. Schedule Kernel Events to handle WiFi Packets 3. Handle the transmission of WiFi Packets Starting the WiFi Firmware Task 4.1 Start Firmware Task Earlier we saw cmd_stack_wifi calling hal_wifi_start_firmware_task to start the Firmware Task. Let's look inside hal_wifi_start_firmware_task now: hal_wifi.c // Start WiFi Firmware Task (FreeRTOS) int hal_wifi_start_firmware_task(void) { // Stack space for the WiFi Firmware Task static StackType_t wifi_fw_stack[WIFI_STACK_SIZE]; // Task Handle for the WiFi Firmware Task static StaticTask_t wifi_fw_task; // Create a FreeRTOS Background Task xTaskCreateStatic( wifi_main, // Task will run this function (char *) "fw", // Task Name WIFI_STACK_SIZE, // Task Stack Size NULL, // Task Parameters TASK_PRIORITY_FW, // Task Priority wifi_fw_stack, // Task Stack &wifi_fw_task); // Task Handle return 0; } This creates a FreeRTOS Background Task that runs the function wifi_main forever. What's inside wifi_main? We don't have the source code for wifi_main. But thanks to BraveHeartFLOSSDev we have the C code decompiled from the BL602 WiFi Firmware! Here's wifi_main from the Decompiled C Code: bl602_demo_wifi.c // WiFi Firmware Task runs this forever void wifi_main(void *param) { ... // Init the LMAC and UMAC rfc_init(40000000); mpif_clk_init(); sysctrl_init(); intc_init(); ipc_emb_init(); bl_init(); ... // Loop forever handling WiFi Kernel Events do { ... // Wait for something (?) if (ke_env.evt_field == 0) { ipc_emb_wait(); } ... // Schedule a WiFi Kernel Event and handle it ke_evt_schedule(); // Sleep for a while iVar1 = bl_sleep(); // (Whaaat?) coex_wifi_pta_forece_enable((uint) (iVar1 == 0)); } while( true ); } The actual Decompiled C Code for wifi_main looks a lot noisier... wifi_main So we picked the highlights for our Reverse Engineering. (And we added some annotations too) wifi_main loops forever handling WiFi Kernel Events, to transmit and receive WiFi Packets. ("ke" refers to the WiFi Kernel, the heart of the WiFi Driver) wifi_main calls ke_evt_schedule to handle WiFi Kernel Events. Let's lookup ke_evt_schedule in our Decompiled C Code: bl602_demo_wifi.c // Schedule a WiFi Kernel Event and handle it void ke_evt_schedule(void) { int iVar1; evt_ptr_t *peVar2; while (ke_env.evt_field != 0) { iVar1 = __clzsi2(ke_env.evt_field); peVar2 = ke_evt_hdlr[iVar1].func; if ((0x1a < iVar1) || (peVar2 == (evt_ptr_t *)0x0)) { assert_err("(event < KE_EVT_MAX) && ke_evt_hdlr[event].func","module",0xdd); } (*peVar2)(ke_evt_hdlr[iVar1].param); } // This line is probably incorrectly decompiled gp = (code *)((int)SFlash_Cache_Hit_Count_Get + 6); return; } This decompiled code does... something something something. Thankfully This One Weird Trick will help us understand this cryptic decompiled code... GitHub Search! Searching for ke_evt_schedule 4.2 Schedule Kernel Events Is it possible that ke_evt_schedule wasn't invented for BL602? Maybe ke_evt_schedule was created for something else? Bingo! Let's do a GitHub Search for ke_evt_schedule! * GitHub Code Search for ke_evt_schedule (Search results are sorted by recently indexed) We'll see this ke_evt_schedule code from AliOS Things (the embedded OS) and RivieraWaves (explained next chapter): ke_event.c // Event scheduler entry point. This primitive has to be // called in the background loop in order to execute the // event handlers for the event that are set. void ke_evt_schedule(void) { uint32_t field, event; field = ke_env.evt_field; while (field) { // Compiler is assumed to optimize with loop inversion // Find highest priority event set event = co_clz(field); // Sanity check ASSERT_ERR((event < KE_EVT_MAX) && ke_evt_hdlr[event].func); // Execute corresponding handler (ke_evt_hdlr[event].func)(ke_evt_hdlr[event].param); // Update the volatile value field = ke_env.evt_field; } } Compare this with our decompiled version of ke_evt_schedule... It's a perfect match! Right down to the Assertion Check! (event < KE_EVT_MAX) && ke_evt_hdlr[event].func Since the two versions of ke_evt_schedule are functionally identical, let's read the AliOS / RivieraWaves version of ke_evt_schedule. ke_evt_schedule from AliOS / RivieraWaves We see that ke_evt_schedule handles WiFi Kernel Events by calling the Event Handlers in ke_evt_hdlr. Here are the ke_evt_hdlr Event Handlers from the AliOS / RivieraWaves code: ke_event.c // Event Handlers called by ke_evt_schedule static const struct ke_evt_tag ke_evt_hdlr[32] = { {&rwnxl_reset_evt, 0}, // [KE_EVT_RESET] {&ke_timer_schedule, 0}, // [KE_EVT_KE_TIMER] {&txl_payload_handle, AC_VO}, // [KE_EVT_IPC_EMB_TXDESC_AC3] // This Event Handler looks interesting {&txl_payload_handle, AC_VI}, // [KE_EVT_IPC_EMB_TXDESC_AC2] {&txl_payload_handle, AC_BE}, // [KE_EVT_IPC_EMB_TXDESC_AC1] {&txl_payload_handle, AC_BK}, // [KE_EVT_IPC_EMB_TXDESC_AC0] {&ke_task_schedule, 0}, // [KE_EVT_KE_MESSAGE] {&mm_hw_idle_evt, 0}, // [KE_EVT_HW_IDLE] ... txl_payload_handle Event Handler txl_payload_handle is the Event Handler that handles the transmission of WiFi Payloads. Let's look inside and learn how it transmits WiFi Packets. txl_payload_handle doesn't do much 4.3 Handle Transmit Payload What is txl_payload_handle? Thanks to the AliOS / RivieraWaves source code, we have a meaningful description of the txl_payload_handle function: txl_cntrl.h // Perform operations on payloads that have been // transfered from host memory. This primitive is // called by the interrupt controller ISR. It // performs LLC translation and MIC computing if required. // LLC = Logical Link Control, MIC = Message Integrity Code void txl_payload_handle(int access_category); This suggests that txl_payload_handle is called to transmit WiFi Packets... After the packet payload has been copied from BL602 to the Radio Hardware. (Via the Shared RAM Buffer) Searching our decompiled code for txl_payload_handle shows this: bl602_demo_wifi.c // Handle transmit payload void txl_payload_handle(void) { while ((_DAT_44a00024 & 0x1f) != 0) { int iVar1 = __clzsi2(_DAT_44a00024 & 0x1f); // Write to WiFi Register at 0x44A0 0020 _DAT_44a00020 = 1 << (0x1fU - iVar1 & 0x1f); } } It doesn't seem to do much payload processing. But it writes to the undocumented WiFi Register at 0x44A0 0020. Which will trigger the LMAC Firmware to transmit the WiFi Packet perhaps? But hold up! We have something that might explain what's inside txl_payload_handle... txl_payload_handle_backup 4.4 Another Transmit Payload Right after txl_payload_handle in our decompiled code, there's a function txl_payload_handle_backup. Based on the name, txl_payload_handle_backup is probably another function that handles payload transmission. Here are the highlights of the decompiled txl_payload_handle_backup function: bl602_demo_wifi.c // Another transmit payload handler. // Probably works the same way as txl_payload_handle, // but runs on BL602 instead of LMAC Firmware. void txl_payload_handle_backup(void) { ... // Iterate through a list of packet buffers (?) while (ptVar4 = ptVar10->list[0].first, ptVar4 == (txl_buffer_tag *)0x0) { LAB_230059f6: uVar3 = uVar3 + 1; ptVar10 = (txl_buffer_env_tag *)&ptVar10->buf_idx[0].free_size; ptVar11 = (txl_cntrl_env_tag *)(ptVar11->txlist + 1); } txl_payload_handle_backup starts by iterating through a list of packet buffers to be transmitted. (Probably) Then it calls some RXU, TXL and TXU functions from RivieraWaves... // Loop (until when?) do { // Call some RXU, TXL and TXU functions rxu_cntrl_monitor_pm((mac_addr *)&ptVar4[1].lenheader); ... txl_machdr_format((uint32_t)(ptVar4 + 1)); ... txu_cntrl_tkip_mic_append(txdesc,(uint8_t)uVar2); (More about RivieraWaves in the next chapter) Next we write to some undocumented WiFi Registers: 0x44B0 8180, 0x44B0 8198, 0x44B0 81A4 and 0x44B0 81A8 ... // Write to WiFi Registers _DAT_44b08180 = 0x800; _DAT_44b081a4 = ptVar9; ... _DAT_44b08180 = 0x1000; _DAT_44b081a8 = ptVar9; ... _DAT_44b08180 = 0x100; _DAT_44b08198 = ptVar9; (They don't appear in this list either) The function performs some Assertion Checks. The Assertion Failure Messages may be helpful for deciphering the decompiled code... // Assertion Checks line = 0x23c; condition = "blmac_tx_ac_2_state_getf() != 2"; ... line = 0x236; condition = "blmac_tx_ac_3_state_getf() != 2"; ... line = 0x22f; condition = "blmac_tx_bcn_state_getf() != 2"; ... line = 0x242; condition = "blmac_tx_ac_1_state_getf() != 2"; ... line = 0x248; condition = "blmac_tx_ac_0_state_getf() != 2"; ... assert_rec(condition, "module", line); The function ends by setting a timer... // Set a timer blmac_abs_timer_set(uVar6, (uint32_t)(puVar8 + _DAT_44b00120)); // Continue looping } while( true ); } Is the original source code for txl_payload_handle_backup really so long? Likely not. The C Compiler optimises the firmware code by inlining some functions. When we decompile the firmware, the inlined code appears embedded inside the calling functions. (That's why we see so much repetition in the decompiled code) Let's talk about RivieraWaves... RivieraWaves in AliOS 5 CEVA RivieraWaves When we searched GitHub for the WiFi Event Scheduler ke_evt_schedule, we found this source code... * mclown/AliOS-Things (We'll use this fork) This appears to be the source code for the AliOS Things embedded OS. (Ported to the Beken BK7231U WiFi SoC) But at the top of the source file we see... Copyright (C) RivieraWaves 2011-2016 This means that the WiFi source code originates from CEVA RivieraWaves, not AliOS! CEVA RivieraWaves (Source) What is CEVA RivieraWaves? RivieraWaves is the Software / Firmware that implements the 802.11 Wireless Protocol on WiFi SoCs (like BL602). On BL602 there are two layers of RivieraWaves Firmware... 1. Upper Medium Access Control (UMAC) Runs on the BL602 RISC-V CPU. Some of the code we've seen earlier comes from UMAC. (Like the Kernel Event Scheduler) 2. Lower Medium Access Control (LMAC) Runs inside the BL602 Radio Hardware. We don't have any LMAC code to study since it's hidden inside the Radio Hardware. (But we can see the LMAC Interfaces exposed by the WiFi Registers) More about WiFi Medium Access Control More about RivieraWaves Is RivieraWaves used elsewhere? Yes, RivieraWaves is used in many popular WiFi SoCs. This article hints at the WiFi SoCs that might be using RivieraWaves (or similar code by CEVA)... Customers of RivieraWaves (Source) 5.1 Upper Medium Access Control Recall that UMAC (Upper Medium Access Control) is the RivieraWaves code that runs on the BL602 RISC-V CPU. When we match the decompiled BL602 WiFi Firmware with the AliOS / RivieraWaves code, we discover the Source Code for the UMAC Modules (and Common Modules) that are used in BL602... 1. CO Module (Common) 2. KE Module (Kernel) 3. ME Module (Message?) 4. RC Module (Rate Control) 5. RXU Module (Receive UMAC) 6. SCANU Module (Scan SSID UMAC) 7. SM Module (State Machine) 8. TXU Module (Transmit UMAC) These modules are mostly identical across BL602 and AliOS / RivieraWaves. (Except RXU, which looks different) (More about UMAC Matching when we discuss Quantitative Analysis) Compare BL602 with RivieraWaves 5.2 Lower Medium Access Control Remember that LMAC (Lower Medium Access Control) is the RivieraWaves code that runs inside the BL602 Radio Hardware. By matching the decompiled BL602 WiFi Firmware with the AliOS / RivieraWaves code, we discover the LMAC Interfaces that are exposed by the BL602 Radio Hardware... 1. APM Interface (Missing from AliOS) 2. CFG Interface (Missing from AliOS) 3. CHAN Interface (MAC Channel Mgmt) 4. HAL Interface (Hardware Abstraction Layer) 5. MM Interface (MAC Mgmt) 6. RXL Interface (Receive LMAC) 7. STA Interface (Station Mgmt) 8. TXL Interface (Transmit LMAC) The LMAC Interfaces linked above are for reference only... The BL602 implementation of LMAC is very different from the Beken BK7231U implementation above. These LMAC Modules seem to be mostly identical across BL602 and AliOS / RivieraWaves... 1. PS Module (Power Save) 2. SCAN Module (Scan SSID) 3. TD Module (Traffic Detection) 4. VIF Module (Virtual Interface) (More about LMAC Matching when we discuss Quantitative Analysis) WiFi Supplicant: Rockchip RK3399 vs BL602 6 WiFi Supplicant What's the WiFi Supplicant? WiFi Supplicant is the code that handles WiFi Authentication. (Like for WPA and WPA2) So WiFi Supplicant comes from RivieraWaves right? Nope. Based on the decompiled code, BL602 implements its own WiFi Supplicant with functions like supplicantInit, allocSupplicantData and keyMgmtGetKeySize. (See this) Maybe the WiFi Supplicant code came from another project? When we search GitHub for the function names, we discover this matching source code... * karthirockz/rk3399-kernel (We'll use this fork) That's actually the WiFi Supplicant for Rockchip RK3399, based on Linux! Are they really the same code? We compared the decompiled BL602 WiFi Supplicant code with the Rockchip RK3399 source code... They are nearly 100% identical! (See this) This is awesome because we have just uncovered the secret origin of (roughly) 2,500 Lines of Code from the decompiled BL602 firmware! (This data comes from the Quantitative Analysis, which we'll discuss in a while) WiFi Supplicant: Lines of code 7 WiFi Physical Layer What's the WiFi Physical Layer? WiFi Physical layer is the wireless protocol that controls the airwaves and dictates how WiFi Packets should be transmitted and received. (It operates underneath the Medium Access Control Layer) More about WiFi Physical Layer Lemme guess... BL602 doesn't use RivieraWaves for the WiFi Physical Layer? Nope we don't think the BL602 Physical Layer comes from RivieraWaves. The origin of BL602's Physical Layer is a little murky... How so? Here's a snippet of BL602 Physical Layer from the decompiled code: bl602_demo_wifi.c // From BL602 Decompiled Code: Init Physical Layer void phy_init(phy_cfg_tag *config) { mdm_reset(); ... mdm_txcbwmax_setf((byte)(_DAT_44c00000 >> 0x18) & 3); _Var2 = phy_vht_supported(); agc_config(); ... // Init transmitter rate power control trpc_init(); // Init phy adaptive features pa_init(); phy_tcal_reset(); phy_tcal_start(); } When we search GitHub for phy_init and phy_hw_set_channel (another BL602 function), we get one meaningful result... * jixinintelligence/bl602-604 (We'll use this fork) Which implements phy_init like so: phy_bl602.c ... // From GitHub Search: Init Physical Layer void phy_init(const struct phy_cfg_tag *config) { const struct phy_bl602_cfg_tag *cfg = (const struct phy_bl602_cfg_tag *)&config->parameters; phy_hw_init(cfg); phy_env->cfg = *cfg; phy_env->band = PHY_BAND_2G4; phy_env->chnl_type = PHY_CHNL_BW_OTHER; phy_env->chnl_prim20_freq = PHY_UNUSED; phy_env->chnl_center1_freq = PHY_UNUSED; phy_env->chnl_center2_freq = PHY_UNUSED; // Init transmitter rate power control trpc_init(); // Init phy adaptive features pa_init(); } BL602 Physical Layer Comparing the BL602 decompiled code with the GitHub Search Result... The BL602 code seems to be doing a lot more? (Where are the calls to mdm_reset, phy_tcal_reset and phy_tcal_start?) Thus we don't have a 100% match for the BL602 Physical Layer. (Maybe 50%) Nonetheless this is a helpful discovery for our Reverse Engineering! Extracting the function names from the decompiled firmware 8 Quantitative Analysis How many Lines of Decompiled Code do we actually need to decipher? How much of the BL602 WiFi Source Code is already available elsewhere? To answer these questions, let's do a Quantitative Analysis of the Decompiled BL602 Firmware Code. (Yep that's the fancy term for data crunching with a spreadsheet) We shall... 1. Extract the function names from the decompiled BL602 WiFi Demo Firmware 2. Load the decompiled function names into a spreadsheet for analysis 3. Classify the decompiled function names by module 4. Match the decompiled function code with the source code we've discovered through GitHub Search 5. Count the number of lines of decompiled code that don't have any matching source code 8.1 Extract the decompiled functions Our BL602 WiFi Demo Firmware bl602_demo_wifi has been decompiled into one giant C file... * Decompiled WiFi Demo Firmware bl602_demo_wifi.c We run this command to extract the Function Names and their Line Numbers (for counting the Lines of Code)... # Extract the function names (and line numbers) # from the decompiled firmware. The line must # begin with an underscore or a letter, # without indentation. grep --line-number \ "^[_a-zA-Z]" \ bl602_demo_wifi.c \ | grep -v LAB_ \ >bl602_demo_wifi.txt This produces bl602_demo_wifi.txt, a long list of Decompiled Function Names and their Line Numbers. (Here's a snippet) (Plus Function Parameters and Type Definitions... We'll scrub them away soon) But this list includes EVERYTHING... Including the non-WiFi functions no? Yes. But it's fun to comb through Every Single Function in the Decompiled Firmware (128,000 Lines of Code)... Just to see what makes it tick. Why not just decompile and analyse the BL602 WiFi Library: libbl602_wifi.a ? The BL602 WiFi Library libbl602_wifi.a might contain some extra WiFi Functions that won't get linked into the WiFi Firmware. Hence we're decompiling and analysing the actual WiFi Functions called by the WiFi Firmware. (BTW: Our counting of Lines of Code will include Blank Lines and Comment Lines) Loading decompiled function names into a spreadsheet 8.2 Load functions into spreadsheet We load bl602_demo_wifi.txt (the list of Decompiled Function Names and Line Numbers) into a spreadsheet for analysis. (See pic above) Here's our spreadsheet for Quantitative Analysis in various formats... * Google Sheets * LibreOffice / OpenOffice Format * Excel Format * CSV Format (raw data only) We scrub the data to remove the Type Definitions, Function Return Types and the Function Parameters. Based on the Line Numbers, we compute the Lines of Code for each function (including Blank Lines and Comment Lines). And apply Conditional Formatting to highlight the Decompiled Functions with the most Lines of Code. (Which are also the Most Complex Functions) These are the functions we should pay more attention during the analysis. Classify the decompiled functions 8.3 Classify the decompiled functions Next we classify each Decompiled Function by Module. The pic above shows that we've classified the "rxl_" functions as "???RivieraWaves RXL". (RXL means Receive LMAC) We use "???" to mark the Modules that we couldn't find any source code. Doing this for all 3,000 Decompiled Functions sounds tedious...? Fortunately the Decompiled Functions belonging to a Module are clustered together. So it's easy to copy and fill the Module Name for a batch of functions. Remember our red highlighting for Complex Functions? It's OK to skip the classification of the Less Complex Functions (if we're not sure how to classify them). In our spreadsheet we've classified over 97,000 Decompiled Lines of Code. That's 86% of all Decompiled Lines of Code. Good enough for our analysis! Matching the decompiled function code 8.4 Match the decompiled functions Remember the source code we've discovered earlier through GitHub Search? (For RivieraWaves, WiFi Supplicant and Physical Layer) Now we dive into the Discovered Source Code and see how closely they match the Decompiled Functions. How do we record our findings? Inside our spreadsheet is a column that records the Source Code URL (from GitHub Search) that we've matched with our Decompiled Functions. (See pic above) We've also added a comment that says how closely they match. ("BL602 version is different") If the Discovered Source Code doesn't match the Decompiled Function, we flag the Module Name with "???". Do we need to match every Decompiled Function? To simplify the matching, we picked one or two of the Most Complex Functions from each Module. (Yep the red highlighting really helps!) Thus our matching is not 100% thorough and accurate... But it's reasonably accurate. Counting the decompiled lines of code in BL602 WiFi Firmware 8.5 Count the lines of code Finally we add a Pivot Table to count the Lines of Code that are matched (or unmatched) with GitHub Search. In the second tab of our spreadsheet, we see the Pivot Table that summarises the results of our Quantitative Analysis... 1. Lines of Code to be Reverse Engineered: 10,500 Not found on GitHub Search: LMAC Interface, and some parts of WiFi Supplicant. Decompiled lines of code to be reverse engineered 2. Lines of Code for Partial Reverse Engineering: 3,500 We found partial matches for the Physical Layer on GitHub Search. Decompiled lines of code for partial reverse enginnering (We'll talk about BL602 HAL and Standard Driver in the next chapter) 3. Lines of Code Already Found Elsewhere: 11,300 (Wow!) Found on GitHub Search: UMAC and most of WiFi Supplicant Decompiled lines of code already found elsewhere 4. We also have 7,500 Lines of Code from parts of the BL602 WiFi Driver whose source code may be found in the BL602 IoT SDK. (See this) Includes the WiFi Manager that we've seen earlier. Lines of code for BL602 WiFi Driver Conclusion: We have plenty of source code to guide us for the Reverse Engineering of BL602 WiFi! 9 Other Modules WiFi Functions make up 29% of the total Lines of Code in our Decompiled WiFi Firmware. What's inside the other 71% of the Decompiled Code? Let's run through the Non-WiFi Functions in our Decompiled Firmware... (Complex modules are highlighted in red) Decompiled lines of code * AliOS: Embedded framework for multitasking and device drivers * AWS IoT, AWS MQTT: Demo Firmware talks to AWS Cloud for IoT and MQTT (Message Queue) Services * BL602 Hardware Abstraction Layer (HAL): Functions for Bootloader, DMA, GPIO, Flash Memory, Interrupts, Real Time Clock, Security (Encryption), UART, ... * BL602 Standard Driver: Called by the BL602 Hardware Abstraction Layer to access the BL602 Hardware Registers * C Standard Library: Because our firmware is compiled with the GCC Compiler * EasyFlash: Embedded database * FreeRTOS: Embedded OS that runs underneath AliOS * Lightweight IP (LWIP): For IP, UDP, TCP and HTTP Networking * Mbed TLS: Implements Transport Layer Security, needed by AWS IoT and MQTT Source code is available for most of the Non-WiFi Functions. GitHub Code Search 10 GitHub Search Is Our Best Friend! Today we've learnt a valuable lesson... GitHub Search is our Best Friend for Reverse Engineering! Here's what we have discovered through GitHub Search... 1. Source Code for UMAC and LMAC GitHub Code Search for ke_evt_schedule 2. Source Code for WiFi Supplicant GitHub Code Search for supplicantInit, allocSupplicantData and keyMgmtGetKeySize 3. Source Code for Physical Layer GitHub Code Search for phy_init and phy_hw_set_channel Remember to check GitHub Search when doing any Reverse Engineering! 11 What's Next This has been a thrilling journey... Many Thanks to the contributors of the Pine64 BL602 Reverse Engineering Project for inspiring this article! In the next article we shall check out the BL706 Audio Video Board. Stay tuned! * Sponsor me a coffee * Discuss this article on Reddit * Read "The RISC-V BL602 Book" * Check out my articles * RSS Feed Got a question, comment or suggestion? Create an Issue or submit a Pull Request here... lupyuen.github.io/src/wifi.md BL706 Audio Video Board BL706 Audio Video Board 12 Notes 1. This article is the expanded version of this Twitter Thread 2. According to madushan1000 on Twitter, the BL602 WiFi RTL may be found here... + fengmaoqiao/my_logic_code + fengmaoqiao/workplace Also check out these tips on BL602 WiFi and Bluetooth LE 3. More about BL602 RF IP: Hardware Notes: RF IP 4. More about BL602 Physical Layer: Hardware Notes: PHY Registers 5. ESP32 uses CEVA's Bluetooth IP but not WiFi IP, according to SpritesMods on Twitter