Reverse Engineering WiFi on RISC-V BL602

📝 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…

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…

(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

Ghidra configuration for BL602

§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)…

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!

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…

(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…

(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…

(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…

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…

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

Source code is available for most of the Non-WiFi Functions.

(Just click the links above)

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!

Today we’ve done some Reverse Engineering for the purpose of Education… Just to understand how BL602 sends and receives WiFi Packets.

And we now understand how to sniff around the Decompiled WiFi Firmware to uncover every WiFi Hardware Register.

I hope someone will continue the Reverse Engineering work… Maybe create an Open Source WiFi Driver for BL602!

(Perhaps we should revive the Pine64 BL602 Nutcracker Project)

In the next article we shall check out the BL706 Audio Video Board

Stay tuned for more articles on BL602, BL604 and BL706!

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…

  3. More about BL602 RF IP and Hardware Registers:

  4. There’s an interesting discussion about the licensing of the WiFi Supplicant, which looks identical to the Linux version on Rockchip RK3399…

    Licensing of the WiFi Supplicant

    (Source)

  5. ESP32 uses CEVA’s Bluetooth IP but not CEVA’s WiFi IP, according to SpritesMods on Twitter