Version 5 (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.


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 hanlded 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.

A user command consists of the following elements:

Example Command: Reading Tx Queue Status

my_node is an instance of a wlan_exp node object that has already been initialized.

#Define a command ID (must be integer in [0, 65535])

#Define a user command ID

#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)]
        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)))
        #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.

To use this function:

#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
  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))
  print('No Tx Queue Status received for {0}'.format(n0_str))

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.

In this example the custom command will query the lengths of Tx queues. Modify the wlan_exp_user_ap_process_cmd() function in wlan_mac_ap.c.

    int i;
    u32 req_queue_id;
    u32 q_id, q_occ;
    dl_entry* curr_station_info_entry;
    station_info* curr_station_info;

Add a new case for the new command ID to the switch(cmd_id) statement:

        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
                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) ) {

        //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;


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.