wiki:802.11/wlan_exp/app_notes/tutorial_token_mac/CPU_LOW

Version 4 (modified by chunter, 9 years ago) (diff)

--

Alterations to CPU_LOW

In this section, we will describe and sicuss 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 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;

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:

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.