wiki:802.11/wlan_exp/Extending

Version 10 (modified by murphpo, 7 years ago) (diff)

--

Extending the Experiments Framework

The 802.11 Reference Design Experiments Framework provides a variety of commands to control and observe WARP v3 nodes in real time. However many experiments will require additional commands, especially when users extend the reference code with new behaviors. Starting in 802.11 Reference Design v1.4 we have greatly simplified the process of implementing extensions to the experiments framework.

There are two kinds of framework extensions:

  • User Commands: commands interact with the MAC High Framework and the upper-level MAC running in CPU High
  • Low Params: a write-only path for setting parameters in the MAC Low Framework and the lower-level MAC running in CPU Low

User Commands

The User Command flow provides a simple way to send custom commands and data from your Python script to the WARP v3 nodes in your experimental network. This flow presents a simple pipe for exchanging arbitrary commands and responses between a Python script and the upper-level MAC in a WARP node running the 802.11 Reference Design.

Python

In Python user commands are constructed as:

  • Command ID: an arbitrary 24-bit integer
  • Command arguments: an optional list of u32 arguments supplied by the Python code that is passed to the C code handler

The command ID is used by the C code to select which code block should process the command payload. The Python and C code must use matching lists of command IDs. All user command IDs must be 24-bit integers (i.e. in ~[0, 224 -1]). The framework does not reserve any command ID values.

The command arguments are an optional payload supplied by the Python script. The wlan_exp framework sends this payload to the node with no modifications. The arguments must be constructed as a list of unsigned 32-bit integers (u32 values). The Python and C code must implement complementary argument construction/parsing functions. The argument list may be omitted if a command requires no payload.

The WARP node must respond to every user command. The reference C code implements this response by default. Each command response may contain a payload supplied by the user C code. If the command handler supplies a response payload, this payload is conveyed back from the node to the Python code automatically. When present the response payload is always a list of u32 values. Your Python and C code must implement complementary response payload generation/processing functions.

User commands are passed to the wlan_exp framework using the send_user_command(cmd_id, args) method. For example:

#No command arguments, no response payload
my_node.send_user_command(CMDID_USR_MYCMD, args=None)

#3 arguments, no response payload
my_node.send_user_command(CMDID_USR_MYCMD, args=[ARG0 ARG1 ARG2])

#2 arguments, with response payload
cmd_resp = my_node.send_user_command(CMDID_USR_MYCMD, args=[ARG0 ARG1])

For simple commands your Python script can call send_user_command directly. For more complicated commands it is suggested you implement your custom command functionality in a separate method. This will simplify debugging your code and reusing your custom commands across multiple experiments. The sample code below adopts the approach of a custom method for sending a user command.

MAC C Code

The 802.11 MAC code running on the WARP node handles user commands at two levels.

If the command behavior is common to all MAC applications, the command is handled in the process_user_cmd() function in wlan_exp_user.c. You should modify process_user_cmd() to handle any new framework-level commands required for your application. Any command IDs not handled in process_user_cmd() will be passed through to the MAC application.

If a command behavior depends on the upper-level MAC application, the command is handled in the wlan_exp_process_user_cmd() callback function in the top-level MAC. You should modify the wlan_exp_process_user_cmd() function in the wlan_mac_ap.c, wlan_mac_sta.c and wlan_mac_ibss.c files to implement any MAC-specific command behaviors.

The implementations of command handlers are similar in wlan_exp_user.c (for common commands) and in top-level MACs (for MAC-specific behaviors). The command processing flow is:

  • Examine the command ID and deterine which code block (case) shoudl handle the command
  • Implement the required command behavior
  • If a response payload is required, populate the response values into the response->args array
  • Update the response header fields for response length (resp_hdr->length) and number of response arguments (resp_hdr->num_args)

The wlan_exp framework will encapsulate the command response payload in an Ethernet fame and return it to the calling Python script for processing.

Interrupt Safety
The process_user_cmd() and wlan_exp_process_user_cmd() functions executed outside the context of an interrupt service routine. As a result these functions may be interrupted at any time. Almost all processing in CPU High occurs in ISR contexts, including all processing of wireless and Ethernet Tx/Rx. If your command handler accesses or modifies data structures that might be modified in one of the upper-level MAC's interrupt service routines, you must take precautions to avoid bad states.

The easiest way to protect a command handler from bad states due to unexpected interrupts is to disable interrupts during execution of the handler. However disabling interrupts will have performance implications. When interrupts are disabled all packet processing is disabled in CPU High. This means no new Ethernet receptions can be processed, no new MPDUs can be submitted to CPU Low for transmission, and no wireless receptions will be processed. It also means all scheduled events will be postponed, including all LTG callbacks and beacon transmissions. You should always minimize the length of time interrupts are disabled.

The MAC High Framework provides helper functions for disabling and restoring the interrupt controller state:

interrupt_state_t curr_interrupt_state;

//Record the current interrupt enable state, then disable all interrupts
curr_interrupt_state = wlan_mac_high_interrupt_stop();

/*
* Do any interrupt-sensitive processing here
*/

//Restore the interrupt enable state
wlan_mac_high_interrupt_restore_state(curr_interrupt_state);

TODO:

  • Endianness

Example Command: Reading Tx Queue Status

This example shows how to implement a custom wlan_exp command, including the required Python and C code. The example command retrieves the status of the Tx queues from the WARP node. The upper-level MAC responds to the command with the number of packets currently enqueued in each Tx queue. Because each upper-level MAC (AP, STA, IBSS) handles Tx queues differently the command handler is implemented in the wlan_exp_user_ap_process_cmd() function of the top-level MAC.

Python Code

The first step is to implement a Python method to send the command and process the results. This method is shown below.

#Define a command ID (must be 24-bit integer)
CMDID_USER_TX_QUEUE_STATUS = 100

#Define a user command ID
USER_CMDID_TX_QUEUE_STATUS = 100

#Define method for sending command and processing the response
def get_tx_queue_status(node, queue_id=None):

    if(queue_id is not None):
        #Arguments must be a list of ints - listify/cast the scaler queue_id
        args = [int(queue_id)]
    else:
        args = None

    resp = node.send_user_command(USER_CMDID_TX_QUEUE_STATUS, args)

    #Response should be list of ints with 2 values per queue
    # Each pair of response values is (queue_id, queue_occupancy)
    # Sanity check the response, then convert to a list of 2-tuples and return
    if(len(resp) % 2 != 0):
        print('ERROR: Tx Queue Status command response lenth ({0} values) was not even!'.format(len(resp)))
        return
    else:
        #Group each pair of response values into 2-tuple and return list of 2-tuples
        ret = zip(*[iter(resp)]*2)

    return ret

This method takes one node object as its argument, sends the USER_CMDID_TX_QUEUE_STATUS command to the node, then parses the response. If the response is valid it returns the list of queue id/occupancy values.

You can implement this method wherever it is callable from your Python script. The easiest place is to include the method at the top of your script, then call it directly during the experiment.

C Code

Each upper-level MAC application implements its own callback function for handling application-specific user commands. By default these functions are empty. You must modify the C code to implement the behaviors required for your custom command.

Modify the wlan_exp_user_ap_process_cmd() function in wlan_mac_ap.c. Start by defining some local variables at the top of the function:

#define USER_CMDID_TX_QUEUE_STATUS 100
int i;
u32 req_queue_id;
u32 q_id, q_occ;
dl_entry* curr_station_info_entry;
station_info* curr_station_info;

Next, add a new case for the your command ID to the switch(cmd_id) statement:

    case USER_CMDID_TX_QUEUE_STATUS:
        xil_printf("Got Tx queue status cmd\n");

        if(command->header->num_args > 0) {
            req_queue_id = Xil_Ntohl(cmd_args_32[0]);
            xil_printf("Queue ID %d requested\n", req_queue_id);

            resp_args_32[resp_index++] = Xil_Htonl(req_queue_id);
            resp_args_32[resp_index++] = Xil_Htonl(queue_num_queued(req_queue_id));

        } else {
            xil_printf("No queue ID requested - returning status of all queues\n");

            //Gather status of all Tx queues:
            // Multicast queue (MCAST_QID)
            // Managment queue (MANAGEMENT_QID)
            // Queue per AID
            resp_args_32[resp_index++] = Xil_Htonl(MCAST_QID);
            resp_args_32[resp_index++] = Xil_Htonl(queue_num_queued(MCAST_QID));
            resp_args_32[resp_index++] = Xil_Htonl(MANAGEMENT_QID);
            resp_args_32[resp_index++] = Xil_Htonl(queue_num_queued(MANAGEMENT_QID));

            if(my_bss_info->associated_stations.length > 0) {
                curr_station_info_entry = NULL;

                //Iterating over the dl_list is potentially dangerous, as the list itself might change
                // if this function is interrupted. We protect against this by iterating over (at most)
                // the number of entries in the list at the time this loop starts. The iteration variable i
                // is *not* used for indexing the list - we still traverse entries with entry.next
                for(i=0; i<my_bss_info->associated_stations.length; i++) {

                    if(curr_station_info_entry == NULL) {
                        //First iteration - get the head of the dl_list
                        curr_station_info_entry = my_bss_info->associated_stations.first;
                    } else {
                        //Subsequent iteration - get the next entry in the dl_list
                        curr_station_info_entry = dl_entry_next(curr_station_info_entry);
                    }

                    //Get the station info pointer from the list entry
                    curr_station_info = (station_info*)(curr_station_info_entry->data);

                    //Get the queue ID and queue occupancy
                    q_id = AID_TO_QID(curr_station_info->AID);
                    q_occ = queue_num_queued(q_id);
                    xil_printf("Q: %2d %3d\n", q_id, q_occ);


                    //Add the queue info to the response payload
                    resp_args_32[resp_index++] = Xil_Htonl(q_id);
                    resp_args_32[resp_index++] = Xil_Htonl(q_occ);

                    //Break out of the for loop on the last station info entry or
                    // if our response is already reached the max size allowed by the framework
                    if( (curr_station_info_entry == my_bss_info->associated_stations.last) ||
                        (dl_entry_next(curr_station_info_entry) == NULL) ||
                        (resp_index >= max_resp_len) ) {
                        break;
                    }
                }
            }
        }

        //All done - resp_args_32 now contains queue status, res_index is length of response arguments array
        // Copy these values into the response header struct
        resp_hdr->length  += (resp_index * sizeof(resp_args_32[0]));
        resp_hdr->num_args = resp_index;

        break;

Finally, to utilize this new command from your Python script:

#Assume n0 is a wlan_exp node object that has already been initialized
q_stat = get_tx_queue_status(n0)

#Print the queue status response
if(q_stat):
  print('Tx Queue Status for node {0}'.format(n0.sn_str))
  print('ID Occupancy')

  for q_id,q_occ in q_stat:
      print('{0:2d} {1:3d}'.format(q_id, q_occ))
else:
  print('No Tx Queue Status received for {0}'.format(n0_str))

Low Params

The User Command flow described above is can configure and reading state in CPU High. Some applications will also require interacting with CPU Low.