wiki:802.11/wlan_exp/app_notes/tutorial_token_mac/CPU_LOW

Alterations to CPU_LOW

In this section, we will describe and discuss the changes needed to the low-level MAC code to realize the design. Here, we will not start off with the DCF code since the vast majority of that DCF behavior is irrelevant to TokenMAC. Instead, we will use the very simple NoMAC project as a starting point. Unaltered, this project acts as a straight passthrough connection between the high-level MAC and the PHY.

Furthermore, we will make some changes to the MAC Low Framework to handle new inter-processor communication (IPC) messages with CPU_HIGH.

Specific Changes

MAC Low Framework

Changes should be made to wlan_mac_low.c in the project SDK workspace zip.


In the CPU_HIGH alterations section, we created the TOKEN_NEW_RESERVATION and TOKEN_END_RESERVATION IPC messages. Now we need to alter the MAC Low Framework to deal with these messages and pass their contents to whatever CPU_LOW project uses the framework. First, we need to create some new global variables at the top of wlan_mac_low.c:

static function_ptr_t        new_reservation_callback;
static function_ptr_t        adjust_reservation_ts_callback;

volatile static u8           allow_new_mpdu_tx;
volatile static s8           pkt_buf_pending_tx;

We should give these values sane defaults in the wlan_mac_low_init() function:

    new_reservation_callback = (function_ptr_t)nullCallback;
    adjust_reservation_ts_callback = (function_ptr_t)nullCallback;
    allow_new_mpdu_tx        = 0;
    pkt_buf_pending_tx       = -1; // -1 is an invalid pkt_buf index

Finally, we should create some setters to allow the CPU_LOW application (e.g. NoMAC) to attach its function to these callbacks. Add the following functions to the MAC Low Framework:

inline void wlan_mac_low_set_new_reservation_callback(function_ptr_t callback){
    new_reservation_callback = callback;
}

inline void wlan_mac_low_set_adjust_reservation_ts_callback(function_ptr_t callback){
    adjust_reservation_ts_callback = callback;
}

The above updates are primarily bookkeeping. We'll explain the purpose of these additions in the coming sections.


When CPU_HIGH (or, more accurately, the AP project) passes CPU_LOW a TOKEN_NEW_RESERVATION message, we should pass the details of that message to the CPU_LOW application via the new_reservation_callback() we just created. The process_ipc_msg_from_high() function contains a large switch statement that covers each type of IPC message as an individual case. We should add a case for the TOKEN_NEW_RESERVATION message. First, we should declare a new local variable at the top of the function:

ipc_token_new_reservation* new_reservation;

Next, we'll assign that pointer to the IPC payload. This will let us access the payload of the IPC message with easy-to-read named structure elements rather than accessing arbitrary low-level bytes.

case IPC_MBOX_TOKEN_NEW_RESERVATION:
    new_reservation = (ipc_token_new_reservation*)msg->payload_ptr;
    new_reservation_callback(new_reservation);
break;

The most significant change to the MAC Low Framework is the handling of the IPC_MBOX_TX_MPDU_READY message. In the default codebase, that message will directly lead to calling the frame_tx_callback() callback function pointer. In TokenMAC, we need to be able to defer a transmission until later if it currently is not our token reservation period. So, instead of calling frame_tx_callback() directly within the IPC handling of IPC_MBOX_TX_MPDU_READY, we move everything out of that case statement into a new function:

// Note: the below function is a copy and paste of the code that was already present in the IPC_MBOX_TX_MPDU_READY case
void wlan_mac_low_proc_pkt_buf(u16 tx_pkt_buf){
    u32                      status;
    tx_frame_info          * tx_mpdu;
    mac_header_80211       * tx_80211_header;
    u8                       rate;
    u16                      ACK_N_DBPS;
    u32                      isLocked, owner;
    u32                      low_tx_details_size;
    wlan_ipc_msg             ipc_msg_to_high;

    if(lock_pkt_buf_tx(tx_pkt_buf) != PKT_BUF_MUTEX_SUCCESS){
        warp_printf(PL_ERROR, "Error: unable to lock TX pkt_buf %d\n", tx_pkt_buf);

        status_pkt_buf_tx(tx_pkt_buf, &isLocked, &owner);

        warp_printf(PL_ERROR, " TX pkt_buf %d status: isLocked = %d, owner = %d\n", tx_pkt_buf, isLocked, owner);

    } else {

        tx_mpdu = (tx_frame_info*)TX_PKT_BUF_TO_ADDR(tx_pkt_buf);

        tx_mpdu->delay_accept = (u32)(get_usec_timestamp() - tx_mpdu->timestamp_create);

        //Convert rate index into rate code used in PHY's SIGNAL field
        //ACK_N_DBPS is used to calculate duration of received ACKs.
        //The selection of ACK rates given DATA rates is specified in 9.7.6.5.2 of 802.11-2012
        rate = wlan_mac_mcs_to_phy_rate(tx_mpdu->params.phy.rate);
        ACK_N_DBPS = wlan_mac_mcs_to_n_dbps(wlan_mac_mcs_to_ctrl_resp_mcs(tx_mpdu->params.phy.rate));

        if((tx_mpdu->flags) & TX_MPDU_FLAGS_FILL_DURATION){
            //Get pointer to start of MAC header in packet buffer
            tx_80211_header = (mac_header_80211*)(TX_PKT_BUF_TO_ADDR(tx_pkt_buf)+PHY_TX_PKT_BUF_MPDU_OFFSET);

            //Compute and fill in the duration of any time-on-air following this packet's transmission
            // For DATA Tx, DURATION = T_SIFS + T_ACK, where T_ACK is function of the ACK Tx rate
            tx_80211_header->duration_id = wlan_ofdm_txtime(sizeof(mac_header_80211_ACK)+WLAN_PHY_FCS_NBYTES, ACK_N_DBPS) + T_SIFS;
        }

        if((tx_mpdu->flags) & TX_MPDU_FLAGS_FILL_TIMESTAMP){
            //Some management packets contain the node's local 64-bit microsecond timer value
            // The Tx hardware can insert this value into the outgoing byte stream automatically
            // This ensures the timestamp value is not skewed by any pre-Tx deferrals

            //The macros below set the first and last byte index where the Tx logic should insert
            // the 8-byte timestamp.
            //In the current implementation these indexes must span an 8-byte-aligned
            // region of the packet buffer (i.e. (start_ind % 8)==0 )
            wlan_phy_tx_timestamp_ins_start((24+PHY_TX_PKT_BUF_PHY_HDR_SIZE));
            wlan_phy_tx_timestamp_ins_end((31+PHY_TX_PKT_BUF_PHY_HDR_SIZE));

        } else {
            //When start>end, the Tx logic will not insert any timestamp
            wlan_phy_tx_timestamp_ins_start(1);
            wlan_phy_tx_timestamp_ins_end(0);
        }

        //Submit the MPDU for transmission - this callback will return only when the MPDU Tx is
        // complete (after all re-transmissions, ACK Rx, timeouts, etc.)

        status = frame_tx_callback(tx_pkt_buf, rate, tx_mpdu->length, low_tx_details);

        if((tx_mpdu->flags) & TX_MPDU_FLAGS_FILL_TIMESTAMP){
            //The Tx logic automatically inserted the timestamp at the time that the bytes
            //were being fed out to the Tx PHY. We can go back and re-insert this time into the
            //payload so that further processing (e.g. logging) sees the correct payload.

            //First, calculate what the value should be

            *((u64*)( (TX_PKT_BUF_TO_ADDR(tx_pkt_buf)+PHY_TX_PKT_BUF_MPDU_OFFSET + 24)) ) = (u64) ( (u64)get_tx_start_timestamp() + (s64)wlan_mac_get_timestamp_offset() );
        }

        //Record the total time this MPDU spent in the Tx state machine
        tx_mpdu->delay_done = (u32)(get_usec_timestamp() - (tx_mpdu->timestamp_create + (u64)(tx_mpdu->delay_accept)));

        low_tx_details_size = (tx_mpdu->num_tx_attempts)*sizeof(wlan_mac_low_tx_details);

        if(status == TX_MPDU_RESULT_SUCCESS){
            tx_mpdu->tx_result = TX_MPDU_RESULT_SUCCESS;
        } else {
            tx_mpdu->tx_result = TX_MPDU_RESULT_FAILURE;
        }

        //Revert the state of the packet buffer and return control to CPU High
        if(unlock_pkt_buf_tx(tx_pkt_buf) != PKT_BUF_MUTEX_SUCCESS){
            warp_printf(PL_ERROR, "Error: unable to unlock TX pkt_buf %d\n", tx_pkt_buf);
            wlan_mac_low_send_exception(EXC_MUTEX_TX_FAILURE);
        } else {
            ipc_msg_to_high.msg_id =  IPC_MBOX_MSG_ID(IPC_MBOX_TX_MPDU_DONE);

            //Add the per-Tx-event details to the IPC message so CPU High can add them to the log as TX_LOW entries
            if(low_tx_details != NULL){
                ipc_msg_to_high.payload_ptr = (u32*)low_tx_details;

                //Make sure we don't overfill the IPC mailbox with TX_LOW data; truncate the Tx details if necessary
                if(low_tx_details_size < (IPC_BUFFER_MAX_NUM_WORDS << 2)){
                    ipc_msg_to_high.num_payload_words = ( low_tx_details_size ) >> 2; // # of u32 words
                } else {
                    ipc_msg_to_high.num_payload_words = ( ((IPC_BUFFER_MAX_NUM_WORDS << 2)/sizeof(wlan_mac_low_tx_details)  )*sizeof(wlan_mac_low_tx_details) ) >> 2; // # of u32 words
                }
            } else {
                ipc_msg_to_high.num_payload_words = 0;
                ipc_msg_to_high.payload_ptr = NULL;
            }
            ipc_msg_to_high.arg0 = tx_pkt_buf;
            ipc_mailbox_write_msg(&ipc_msg_to_high);
        }
    }
}

Now we will change the IPC_MBOX_TX_MPDU_READY case to the following:

case IPC_MBOX_TX_MPDU_READY:
    if(allow_new_mpdu_tx){
        wlan_mac_low_proc_pkt_buf( msg->arg0 );
    } else {
        pkt_buf_pending_tx = msg->arg0;
    }           
break;

Basically, we will rely on the new allow_new_mpdu_tx global variable to tell us whether or not we are allowed to process a new MPDU for transmission. If we are, we will call the new wlan_mac_low_proc_pkt_buf function just like before. If we are not allowed to transmit, we will store the pending packet buffer to the new pkt_buf_pending_tx global variable and worry about calling wlan_mac_low_proc_pkt_buf later when we are allowed to transmit once again.


Next, we need to provide the ability for the CPU_LOW project to toggle the whether or not the framework is allowed to process new MPDU transmissions. Disallowing transmissions is easy. Add the following simple function to the MAC Low Framework:

void wlan_mac_low_disable_new_mpdu_tx(){
    allow_new_mpdu_tx = 0;
}

Allowing new MPDU transmissions is slightly more complex than setting allow_new_mpdu_tx to 1. If a IPC_MBOX_TX_MPDU_READY message was received from CPU_HIGH while new transmissions were disallowed, we stored the location of that MPDU in the pkt_buf_pending_tx global variable. When allowing new MPDU transmissions, we should see if this variable currently has anything for us to send and immediately do so if it does. Add the following function to the MAC Low Framework:

void wlan_mac_low_enable_new_mpdu_tx(){
    if(allow_new_mpdu_tx == 0){
        allow_new_mpdu_tx = 1;
        if(pkt_buf_pending_tx != -1){
            wlan_mac_low_proc_pkt_buf(pkt_buf_pending_tx);
            pkt_buf_pending_tx = -1;
        }
    }
}

Here, we are using -1 as a way of saying that there is no pending transmission. Any other value will be interpreted as an actual packet buffer.


Finally, we need to make a change that is not technically critical to the operation of TokenMAC, but will make our lives much easier when we actually need to use it. One complication that arises from using timestamps to schedule events is that a node is capable of altering its timestamp and redefining when "now" is. This happens explicitly when you use the set_time method of wlan_exp. It also happens implicitly whenever a STA overrides its own timestamp with the timestamp of a received beacon from its AP. Whatever the reason, we should provide a way to tell the CPU_LOW application when the timestamp is about to change and by how much that change will be. We already created the adjust_reservation_ts_callback callback in the first change above. Now we just need to call it. The wlan_mac_low_set_time() in the MAC Low Framework is the only code that changes the underlying timestamp, so it is the best place to calculate the difference between the new timestamp and whatever the old timestamp was. Add the following snippet of code to the very top of wlan_mac_low_set_time() before it has a chance to actually set the new timestamp:

    u64 curr_time = get_usec_timestamp();
    if(curr_time > new_time){
        adjust_reservation_ts_callback( -1*(s64)(curr_time - new_time) );
    } else if(new_time > curr_time){
        adjust_reservation_ts_callback( (new_time - curr_time) );
    }

Prior to changing the timestamp, we will notify the CPU_LOW project and provide an offset that is approximately equal to how much the timestamp is about to change.


NoMAC

Changes should be made to wlan_mac_nomac.c and wlan_mac_nomac.h in the project SDK workspace zip.

NoMAC is intended to be a near "blank slate" to make building totally custom MACs more straightforward. It does not have any of the complexities of the DCF. Transmissions are directly connected to the PHY. There is no carrier sensing, no acknowledgments, no retransmissions, no random backoffs, etc. NoMAC is the perfect place for us to add the low-level behavior of TokenMAC. We will construct TokenMAC in such a way that the same CPU_LOW project can be attached to either the AP or STA CPU_HIGH project to program a board. In other words, our modifications to NoMAC will have to work for either role of issuing tokens or accepting tokens.


First, we need to modify wlan_mac_nomac.h with a few new definitions.

//The 802.11 Reference Design has a total of 8 Tx packet buffers.
//Here, we will reserve the last packet buffer for our token offer and token
//response transmissions.
#define TX_PKT_BUF_TOKEN       7

#define MAC_HW_LASTBYTE_TOKEN (sizeof(mac_frame_custom_token)+3)

typedef struct{
    u8 frame_control_1;
    u8 frame_control_2;
    u16 duration_id;
    u8 address_ra[6];
    u8 address_ta[6];
    u32 res_duration_usec;
} mac_frame_custom_token;

We will use TX_PKT_BUF_TOKEN as a hardcoded packet buffer index that is dedicated to transmitting our new token frames. mac_frame_custom_token is a struct that is our new custom token frame. It's format is inspired by 802.11 frames in order to allow nearby commercial Wi-Fi devices to decode and ignore these frames. We'll set the frame_control_1 field to an invalid value to make ensure this.


Next, we will add two small functions to NoMAC that will act as packet constructors. They will create mac_frame_custom_token packets and fill the relevant values into the elements of that struct.

#define MAC_FRAME_CTRL1_SUBTYPE_TOKEN_OFFER     (MAC_FRAME_CTRL1_TYPE_CTRL | 0x10)
#define MAC_FRAME_CTRL1_SUBTYPE_TOKEN_RESPONSE  (MAC_FRAME_CTRL1_TYPE_CTRL | 0x20)

int wlan_create_token_offer_frame(void* pkt_buf_addr, u8* address_ra, u8* address_ta, u16 duration, u32 res_duration) {
    mac_frame_custom_token* token;
    token = (mac_frame_custom_token*)(pkt_buf_addr);

    token->frame_control_1 = MAC_FRAME_CTRL1_SUBTYPE_TOKEN_OFFER;
    token->frame_control_2 = 0;
    token->duration_id = duration;
    memcpy(token->address_ra, address_ra, 6);
    memcpy(token->address_ta, address_ta, 6);
    token->res_duration_usec = res_duration;

    //Include FCS in packet size (MAC accounts for FCS, even though the PHY calculates it)
    return (sizeof(mac_frame_custom_token)+WLAN_PHY_FCS_NBYTES);
}

int wlan_create_token_response_frame(void* pkt_buf_addr, u8* address_ra, u8* address_ta, u16 duration, u32 res_duration) {
    mac_frame_custom_token* token;
    token = (mac_frame_custom_token*)(pkt_buf_addr);

    token->frame_control_1 = MAC_FRAME_CTRL1_SUBTYPE_TOKEN_RESPONSE;
    token->frame_control_2 = 0;
    token->duration_id = duration;
    memcpy(token->address_ra, address_ra, 6);
    memcpy(token->address_ta, address_ta, 6);
    token->res_duration_usec = res_duration;

    //Include FCS in packet size (MAC accounts for FCS, even though the PHY calculates it)
    return (sizeof(mac_frame_custom_token)+WLAN_PHY_FCS_NBYTES);
}

Here we introduce the terminology of a "token offer" and a "token response." An offer is what an AP makes to a STA when it informs the STA it now can have the token and have access to the wireless medium. A response is what the STA responds to the offer with. This acts as a positive confirmation that the STA understands it has the token. Note that in both of these cases, the same mac_frame_custom_token struct is used. The distinction between these two types of frames is in the value of frame_control_1. The values of MAC_FRAME_CTRL1_SUBTYPE_TOKEN_OFFER and MAC_FRAME_CTRL1_SUBTYPE_TOKEN_RESPONSE are somewhat arbitrary. The definitions above are particularly useful because they are invalid in the 802.11 standard, so commercial Wi-Fi devices will likely just ignore them rather than falsely interpret them as some sort of standard 802.11 packet.


Next, we will perform a little bit of bookkeeping and declare two new global variables we will need at the top of wlan_mac_nomac.c:

u8 in_reservation;
u64 reservation_ts_end;

We will explain how we will use these new variables in the next sections. In main(), we also need to set some sane defaults:

    in_reservation = 0;
    wlan_mac_low_set_new_reservation_callback((void*)token_new_reservation);
    wlan_mac_low_set_adjust_reservation_ts_callback((void*)adjust_reservation_ts_end);

The safest place to add these lines is just before the while(1) clause at the end of the main() function. We have not created the token_new_reservation() or adjust_reservation_ts_end() functions yet. We will add those in the coming sections.


The first substantive addition we will make to the NoMAC project is a function to deal with the IPC from CPU_HIGH that says we are now entering a new token reservation period for some given address and for some given duration of time. By definition, if this code is executing, it means that the node is acting as an AP. We made no modifications to the CPU_HIGH STA project to issue this IPC message. The behavior we want to implement is the following:

  • Check the address inside the payload of the new token reservation IPC message
    • If it matches the address programmed in the EEPROM of this board, we can conclude that this reservation period is for us (the AP). As such, we can simply call wlan_mac_low_enable_new_mpdu_tx to enable our own MPDU transmissions. We can additionally set the reservation_ts_end global variable with the timestamp of when this reservation period is to be terminated.
    • If the address does not match our address in the EEPROM, we know this reservation period is for some other STA on the network. We need to communicate with them and inform them that they now have the token and need to transmit. Specifically, we want to send a "token offer" message and wait for a "token response" from the STA confirming that it now has the token. If we do not hear a response, we can time out and immediately terminate this reservation period. To implement this behavior, we can use the MAC Tx Controller A HW peripheral as it has built in support for post-transmission timeouts.
  • In either case, we should update the reservation_ts_end global variable to contain the timestamp of when this token reservation period is over.

Here is an implementation of that behavior as a function that can be added to NoMAC:

#define POLL_MAC_STATUS_TOKEN_OFFER_ACCEPTED 0x10000000
void token_new_reservation(ipc_token_new_reservation* new_reservation){

    u8 mac_cfg_rate;
    u16 mac_cfg_length;
    int curr_tx_pow;
    u32 mac_hw_status;
    u32 rx_status;

    wlan_ipc_msg       ipc_msg_to_high_start;
    ipc_token_new_reservation ipc_payload_start;
    wlan_ipc_msg       ipc_msg_to_high_end;
    ipc_token_end_reservation ipc_payload_end;


    ipc_msg_to_high_start.msg_id            = IPC_MBOX_MSG_ID(IPC_MBOX_TOKEN_NEW_RESERVATION);

    //The below chunk of code is just to make sure that we pad an extra u32 word if the
    //ipc_token_new_reservation struct is not 32-bit aligned.
    if( (sizeof(u32)*(sizeof(ipc_token_new_reservation)/sizeof(u32))) ==  sizeof(ipc_token_new_reservation) ){
        ipc_msg_to_high_start.num_payload_words = (sizeof(ipc_token_new_reservation)/sizeof(u32));
    } else {
        ipc_msg_to_high_start.num_payload_words = (sizeof(ipc_token_new_reservation)/sizeof(u32)) + 1;
    }

    ipc_msg_to_high_start.payload_ptr       = (u32*)(&ipc_payload_start);

    ipc_msg_to_high_end.msg_id            = IPC_MBOX_MSG_ID(IPC_MBOX_TOKEN_END_RESERVATION);

    //The below chunk of code is just to make sure that we pad an extra u32 word if the
    //ipc_token_end_reservation struct is not 32-bit aligned.
    if( (sizeof(u32)*(sizeof(ipc_token_end_reservation)/sizeof(u32))) ==  sizeof(ipc_token_end_reservation) ){
        ipc_msg_to_high_end.num_payload_words = (sizeof(ipc_token_end_reservation)/sizeof(u32));
    } else {
        ipc_msg_to_high_end.num_payload_words = (sizeof(ipc_token_end_reservation)/sizeof(u32)) + 1;
    }

    ipc_msg_to_high_end.payload_ptr       = (u32*)(&ipc_payload_end);

    mac_cfg_rate = WLAN_PHY_RATE_BPSK12;

    if(wlan_addr_eq(new_reservation->addr, eeprom_addr)){
        //This is my reservation
        in_reservation = 1;
        wlan_mac_low_enable_new_mpdu_tx();
        reservation_ts_end = get_usec_timestamp() + ((u64)new_reservation->res_duration);

        memcpy( ipc_payload_start.addr, new_reservation->addr, 6 );
        ipc_payload_start.res_duration = new_reservation->res_duration;
        ipc_mailbox_write_msg(&ipc_msg_to_high_start);

    } else {
        //This is someone else's reservation
        mac_cfg_length = wlan_create_token_offer_frame((void*)(TX_PKT_BUF_TO_ADDR(TX_PKT_BUF_TOKEN) + PHY_TX_PKT_BUF_MPDU_OFFSET),
                                           new_reservation->addr,
                                           eeprom_addr,
                                           0,
                                           new_reservation->res_duration);



        wlan_phy_set_tx_signal(TX_PKT_BUF_TOKEN, mac_cfg_rate, mac_cfg_length); // Write SIGNAL for RTS

        curr_tx_pow = wlan_mac_low_dbm_to_gain_target(15);
        wlan_mac_tx_ctrl_A_gains(curr_tx_pow, curr_tx_pow, curr_tx_pow, curr_tx_pow);

        //wlan_mac_tx_ctrl_A_params(pktBuf, antMask, preTx_backoff_slots, preWait_postRxTimer1, preWait_postTxTimer1, postWait_postTxTimer2)
        //postTxTimer2 is a timeout. We'll use that to wait for a token response
        wlan_mac_tx_ctrl_A_params(TX_PKT_BUF_TOKEN, 0x1, 0, 0, 0, 1);

        //Start the Tx state machine
        wlan_mac_tx_ctrl_A_start(1);
        wlan_mac_tx_ctrl_A_start(0);

        //Wait for the MPDU Tx to finish
        do { //while(tx_status & WLAN_MAC_STATUS_MASK_TX_A_PENDING)

            //Poll the DCF core status register
            mac_hw_status = wlan_mac_get_status();

            if( mac_hw_status & WLAN_MAC_STATUS_MASK_TX_A_DONE ) {
                //Transmission is complete

                memcpy( ipc_payload_start.addr, new_reservation->addr, 6 );
                ipc_payload_start.res_duration = new_reservation->res_duration;
                ipc_mailbox_write_msg(&ipc_msg_to_high_start);

                //Switch on the result of the transmission attempt
                switch( mac_hw_status & WLAN_MAC_STATUS_MASK_TX_A_RESULT ) {
                    case WLAN_MAC_STATUS_TX_A_RESULT_RX_STARTED:
                        //Transmission ended, followed by a new reception (hopefully a token response)

                        //Handle the new reception
                        rx_status = wlan_mac_low_poll_frame_rx();

                        //Check if the reception is an ACK addressed to this node, received with a valid checksum
                        if( (rx_status & POLL_MAC_STATUS_TOKEN_OFFER_ACCEPTED)) {

                            //We are now in a new reservation state for this user
                            in_reservation = 1;
                            reservation_ts_end = get_usec_timestamp() + ((u64)new_reservation->res_duration);
                        } else {
                            //Received a packet immediately after transmitting, but it wasn't the offer response we wanted
                            //This is equivalent to a timeout. Let CPU_HIGH know that this reservation period is over
                            in_reservation = 0;
                            ipc_payload_end.reason = TOKEN_TIMEOUT;
                            ipc_mailbox_write_msg(&ipc_msg_to_high_end);
                        }
                    break;
                    case WLAN_MAC_STATUS_TX_A_RESULT_TIMEOUT:
                        in_reservation = 0;
                        ipc_payload_end.reason = TOKEN_TIMEOUT;
                        ipc_mailbox_write_msg(&ipc_msg_to_high_end);
                    break;
                }
            } else { //else for if(mac_hw_status & WLAN_MAC_STATUS_MASK_TX_A_DONE)
                //Poll the MAC Rx state to check if a packet was received while our Tx was deferring

                if( mac_hw_status & (WLAN_MAC_STATUS_MASK_RX_PHY_ACTIVE | WLAN_MAC_STATUS_MASK_RX_PHY_BLOCKED_FCS_GOOD | WLAN_MAC_STATUS_MASK_RX_PHY_BLOCKED) ) {
                    rx_status = wlan_mac_low_poll_frame_rx();
                }
            }//END if(Tx A state machine done)
        } while( mac_hw_status & WLAN_MAC_STATUS_MASK_TX_A_PENDING );
    }
}

In the previous change, reservation_ts_end was updated with a timestamp designating the end of the current token reservation period. Now we need to check this variable against the current timestamp and, if the duration has expired, disallow any new MPDU transmissions and notify CPU_HIGH that the reservation period is over. Create a new function, poll_reservation_time, that does exactly that:

void poll_reservation_time(){
    wlan_ipc_msg       ipc_msg_to_high;
    ipc_token_end_reservation ipc_payload;

    ipc_msg_to_high.msg_id            = IPC_MBOX_MSG_ID(IPC_MBOX_TOKEN_END_RESERVATION);

    //The below chunk of code is just to make sure that we pad an extra u32 word if the
    //ipc_token_end_reservation struct is not 32-bit aligned.
    if( (sizeof(u32)*(sizeof(ipc_token_end_reservation)/sizeof(u32))) ==  sizeof(ipc_token_end_reservation) ){
        ipc_msg_to_high.num_payload_words = (sizeof(ipc_token_end_reservation)/sizeof(u32));
    } else {
        ipc_msg_to_high.num_payload_words = (sizeof(ipc_token_end_reservation)/sizeof(u32)) + 1;
    }

    ipc_msg_to_high.payload_ptr       = (u32*)(&ipc_payload);

    if(in_reservation && (get_usec_timestamp() >= reservation_ts_end)){
        in_reservation = 0;
        wlan_mac_low_disable_new_mpdu_tx();
        ipc_payload.reason = TOKEN_DURATION_COMPLETE;
        ipc_mailbox_write_msg(&ipc_msg_to_high);
    }
}

Now we need to call the new function whenever we can. In the primary while(1) loop of the main() function of NoMAC, simply add a call to poll_reservation_time().


Next, we need to change the frame_receive() function of the to deal with two potential kinds of receptions:

  1. If we are a STA, we could expect to receive a token offer from the AP. If we do, we should respond with a token response before the AP times out on the offer. After sending the token response, we know that we have entered a token reservation period so we should allow the transmission of new MPDUs and set the reservation_ts_end variable to represent the time when our token reservation period ends.
  2. If we are an AP, we could expect to receive a token response from other STAs. If we do, we need to let the token_new_reservation() context know that we did. Note in a previous change that token_new_reservation() actively looks for a POLL_MAC_STATUS_TOKEN_OFFER_ACCEPTED flag to be returned from the receive context. We should raise this flag.

The following function is a drop-in replacement to the existing frame_receive function that implements the above behavior:

u32 frame_receive(u8 rx_pkt_buf, phy_rx_details* phy_details){
    //This function is called after a good SIGNAL field is detected by either PHY (OFDM or DSSS)
    //It is the responsibility of this function to wait until a sufficient number of bytes have been received
    // before it can start to process those bytes. When this function is called the eventual checksum status is
    // unknown. The packet contents can be provisionally processed (e.g. prepare an ACK for fast transmission),
    // but post-reception actions must be conditioned on the eventual FCS status (good or bad).
    //
    // Note: The timing of this function is critical for correct operation of the 802.11 DCF. It is not
    // safe to add large delays to this function (e.g. xil_printf or usleep)
    //
    //Two primary job responsibilities of this function:
    // (1): Prepare outgoing ACK packets and instruct the MAC_DCF_HW core whether or not to send ACKs
    // (2): Pass up MPDUs (FCS valid or invalid) to CPU_HIGH

    u8 unicast_to_me;
    mac_header_80211* rx_header;
    mac_frame_custom_token* rx_token_frame;
    u8 ctrl_tx_gain;
    u32 tx_length;
    u32 return_value = 0;
    u32 mac_hw_status;
    wlan_ipc_msg       ipc_msg_to_high_start;
    ipc_token_new_reservation ipc_payload_start;

    ipc_msg_to_high_start.msg_id            = IPC_MBOX_MSG_ID(IPC_MBOX_TOKEN_NEW_RESERVATION);

    //The below chunk of code is just to make sure that we pad an extra u32 word if the
    //ipc_token_new_reservation struct is not 32-bit aligned.
    if( (sizeof(u32)*(sizeof(ipc_token_new_reservation)/sizeof(u32))) ==  sizeof(ipc_token_new_reservation) ){
        ipc_msg_to_high_start.num_payload_words = (sizeof(ipc_token_new_reservation)/sizeof(u32));
    } else {
        ipc_msg_to_high_start.num_payload_words = (sizeof(ipc_token_new_reservation)/sizeof(u32)) + 1;
    }

    ipc_msg_to_high_start.payload_ptr       = (u32*)(&ipc_payload_start);

    rx_frame_info* mpdu_info;
    void* pkt_buf_addr = (void *)RX_PKT_BUF_TO_ADDR(rx_pkt_buf);

    mpdu_info = (rx_frame_info*)pkt_buf_addr;

    rx_header = (mac_header_80211*)((void*)(pkt_buf_addr + PHY_RX_PKT_BUF_MPDU_OFFSET));

    //Wait until the PHY has written enough bytes so that the first address field can be processed
    while(wlan_mac_get_last_byte_index() < MAC_HW_LASTBYTE_TOKEN){
    };

    unicast_to_me = wlan_addr_eq(rx_header->address_1, eeprom_addr);

    if(unicast_to_me && (rx_header->frame_control_1 == MAC_FRAME_CTRL1_SUBTYPE_TOKEN_OFFER)){
        //Received a token offer
        rx_token_frame = (mac_frame_custom_token*)rx_header;

        //Set up a Token Response
        //wlan_mac_tx_ctrl_B_params(pktBuf, antMask, req_zeroNAV, preWait_postRxTimer1, preWait_postRxTimer2, postWait_postTxTimer1)
        wlan_mac_tx_ctrl_B_params(TX_PKT_BUF_TOKEN, 0x1, 0, 1, 0, 0);

        //ACKs are transmitted with a nominal Tx power used for all control packets
        ctrl_tx_gain = wlan_mac_low_dbm_to_gain_target(15);
        wlan_mac_tx_ctrl_B_gains(ctrl_tx_gain, ctrl_tx_gain, ctrl_tx_gain, ctrl_tx_gain);

        //Construct the token response frame in the dedicated Tx pkt buf
        tx_length = wlan_create_token_response_frame((void*)(TX_PKT_BUF_TO_ADDR(TX_PKT_BUF_TOKEN) + PHY_TX_PKT_BUF_MPDU_OFFSET),
                                                      rx_token_frame->address_ta,
                                                      eeprom_addr,
                                                      0,
                                                      rx_token_frame->res_duration_usec);


        //Write the SIGNAL field for the ACK
        wlan_phy_set_tx_signal(TX_PKT_BUF_TOKEN, WLAN_PHY_RATE_BPSK12, tx_length);

        mpdu_info->state = wlan_mac_dcf_hw_rx_finish();

        if(mpdu_info->state == RX_MPDU_STATE_FCS_GOOD){

            memcpy( ipc_payload_start.addr, rx_token_frame->address_ra, 6 );
            ipc_payload_start.res_duration = rx_token_frame->res_duration_usec;
            ipc_mailbox_write_msg(&ipc_msg_to_high_start);

            wlan_mac_tx_ctrl_B_start(1);
            wlan_mac_tx_ctrl_B_start(0);

            //Since this is our reservation period, we are now allowed to transmit
            in_reservation = 1;
            reservation_ts_end = get_usec_timestamp() + ((u64)rx_token_frame->res_duration_usec);
            wlan_mac_low_enable_new_mpdu_tx();
        }
    } else if(unicast_to_me && (rx_header->frame_control_1 == MAC_FRAME_CTRL1_SUBTYPE_TOKEN_RESPONSE)) {
        //Received a token offer
        rx_token_frame = (mac_frame_custom_token*)rx_header;

        if(rx_token_frame->res_duration_usec != 0){
            mpdu_info->state = wlan_mac_dcf_hw_rx_finish();
            if(mpdu_info->state == RX_MPDU_STATE_FCS_GOOD){
                return_value |= POLL_MAC_STATUS_TOKEN_OFFER_ACCEPTED;
            }
        }
    } else {
        mpdu_info->state = wlan_mac_dcf_hw_rx_finish(); //Blocks until reception is complete
    }

    mpdu_info->flags = 0;
    mpdu_info->phy_details = *phy_details;
    mpdu_info->channel = wlan_mac_low_get_active_channel();
    mpdu_info->timestamp = get_rx_start_timestamp();


    mpdu_info->ant_mode = wlan_phy_rx_get_active_rx_ant();

    mpdu_info->rf_gain = wlan_phy_rx_get_agc_RFG(mpdu_info->ant_mode);
    mpdu_info->bb_gain = wlan_phy_rx_get_agc_BBG(mpdu_info->ant_mode);
    mpdu_info->rx_power = wlan_mac_low_calculate_rx_power(wlan_phy_rx_get_pkt_rssi(mpdu_info->ant_mode), wlan_phy_rx_get_agc_RFG(mpdu_info->ant_mode));

    if(mpdu_info->state == RX_MPDU_STATE_FCS_GOOD){
        green_led_index = (green_led_index + 1) % NUM_LEDS;
        userio_write_leds_green(USERIO_BASEADDR, (1<<green_led_index));
    } else {
        red_led_index = (red_led_index + 1) % NUM_LEDS;
        userio_write_leds_red(USERIO_BASEADDR, (1<<red_led_index));
    }

    //Unlock the pkt buf mutex before passing the packet up
    // If this fails, something has gone horribly wrong
    if(unlock_pkt_buf_rx(rx_pkt_buf) != PKT_BUF_MUTEX_SUCCESS){
        xil_printf("Error: unable to unlock RX pkt_buf %d\n", rx_pkt_buf);
        wlan_mac_low_send_exception(EXC_MUTEX_RX_FAILURE);
    } else {
        wlan_mac_low_frame_ipc_send();
        //Find a free packet buffer and begin receiving packets there (blocks until free buf is found)
        wlan_mac_low_lock_empty_rx_pkt_buf();
    }

    //Unblock the PHY post-Rx (no harm calling this if the PHY isn't actually blocked)
    wlan_mac_dcf_hw_unblock_rx_phy();

    return return_value;

}

Finally, we need to deal with the fact that our timebase might change on the node. We set up a callback for reporting an upcoming timebase change earlier. We can adjust the reservation_ts_end timestamp with whatever the change will be. Create the following function in NoMAC:

void adjust_reservation_ts_end(s64 adjustment){
    reservation_ts_end += adjustment;
}

We assigned this function to the new callback in the MAC Low Framework in a previous change.

Last modified 8 years ago Last modified on Feb 4, 2016, 11:04:19 AM