/**************************************************************************************************
  Filename:       mac_tx.c
  Revised:        $Date: 2007-10-15 14:56:36 -0700 (Mon, 15 Oct 2007) $
  Revision:       $Revision: 15687 $

  Description:    Describe the purpose and contents of the file.


  Copyright 2006-2007 Texas Instruments Incorporated. All rights reserved.

  IMPORTANT: Your use of this Software is limited to those specific rights
  granted under the terms of a software license agreement between the user
  who downloaded the software, his/her employer (which must be your employer)
  and Texas Instruments Incorporated (the "License").  You may not use this
  Software unless you agree to abide by the terms of the License. The License
  limits your use, and you acknowledge, that the Software may not be modified,
  copied or distributed unless embedded on a Texas Instruments microcontroller
  or used solely and exclusively in conjunction with a Texas Instruments radio
  frequency transceiver, which is integrated into your product.  Other than for
  the foregoing purpose, you may not use, reproduce, copy, prepare derivative
  works of, modify, distribute, perform, display or sell this Software and/or
  its documentation for any purpose.

  YOU FURTHER ACKNOWLEDGE AND AGREE THAT THE SOFTWARE AND DOCUMENTATION ARE
  PROVIDED “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED,
  INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY, TITLE,
  NON-INFRINGEMENT AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL
  TEXAS INSTRUMENTS OR ITS LICENSORS BE LIABLE OR OBLIGATED UNDER CONTRACT,
  NEGLIGENCE, STRICT LIABILITY, CONTRIBUTION, BREACH OF WARRANTY, OR OTHER
  LEGAL EQUITABLE THEORY ANY DIRECT OR INDIRECT DAMAGES OR EXPENSES
  INCLUDING BUT NOT LIMITED TO ANY INCIDENTAL, SPECIAL, INDIRECT, PUNITIVE
  OR CONSEQUENTIAL DAMAGES, LOST PROFITS OR LOST DATA, COST OF PROCUREMENT
  OF SUBSTITUTE GOODS, TECHNOLOGY, SERVICES, OR ANY CLAIMS BY THIRD PARTIES
  (INCLUDING BUT NOT LIMITED TO ANY DEFENSE THEREOF), OR OTHER SIMILAR COSTS.

  Should you have any questions regarding your right to use this Software,
  contact Texas Instruments Incorporated at www.TI.com.
**************************************************************************************************/

/* ------------------------------------------------------------------------------------------------
 *                                          Includes
 * ------------------------------------------------------------------------------------------------
 */

/* hal */
#include "hal_types.h"
#include "hal_defs.h"
#include "hal_mcu.h"
#include "hal_mac_cfg.h"

/* high-level */
#include "mac_spec.h"
#include "mac_pib.h"

/* exported low-level */
#include "mac_low_level.h"

/* low-level specific */
#include "mac_tx.h"
#include "mac_backoff_timer.h"
#include "mac_rx.h"
#include "mac_rx_onoff.h"
#include "mac_radio.h"
#include "mac_sleep.h"

/* target specific */
#include "mac_radio_defs.h"

/* debug */
#include "mac_assert.h"


/* ------------------------------------------------------------------------------------------------
 *                                            Defines
 * ------------------------------------------------------------------------------------------------
 */
#define MFR_LEN                   MAC_FCS_FIELD_LEN
#define PREPENDED_BYTE_LEN        1


/* ------------------------------------------------------------------------------------------------
 *                                         Global Constants
 * ------------------------------------------------------------------------------------------------
 */

/*
 *  This is the time, in backoffs, required to set up a slotted transmit.
 *  It is exported to high level so that code can schedule enough time
 *  for slotted transmits.
 *
 *  A default is provided if a value is not specified.  If the default
 *  is not appropriate, a #define should be added within hal_mac_cfg.h.
 */
#ifndef HAL_MAC_TX_SLOTTED_DELAY
#define HAL_MAC_TX_SLOTTED_DELAY    3
#endif
uint8 const macTxSlottedDelay = HAL_MAC_TX_SLOTTED_DELAY;


/* ------------------------------------------------------------------------------------------------
 *                                         Global Variables
 * ------------------------------------------------------------------------------------------------
 */
uint8 macTxActive;
uint8 macTxType;
uint8 macTxBe;
uint8 macTxCsmaBackoffDelay;


/* ------------------------------------------------------------------------------------------------
 *                                         Local Variables
 * ------------------------------------------------------------------------------------------------
 */
static uint8 nb;
static uint8 txSeqn;
static uint8 txAckReq;
static uint8 txRetransmitFlag;


/* ------------------------------------------------------------------------------------------------
 *                                         Local Prototypes
 * ------------------------------------------------------------------------------------------------
 */
static void txCsmaPrep(void);
static void txGo(void);
static void txCsmaGo(void);
static void txComplete(uint8 status);


/**************************************************************************************************
 * @fn          macTxInit
 *
 * @brief       Initialize variables for tx module.
 *
 * @param       none
 *
 * @return      none
 **************************************************************************************************
 */
void macTxInit(void)
{
  macTxActive      = MAC_TX_ACTIVE_NO_ACTIVITY;
  txRetransmitFlag = 0;
}


/**************************************************************************************************
 * @fn          macTxHaltCleanup
 *
 * @brief       -
 *
 * @param       none
 *
 * @return      none
 **************************************************************************************************
 */
void macTxHaltCleanup(void)
{
  MAC_RADIO_TX_RESET();
  macTxInit();
}


/**************************************************************************************************
 * @fn          macTxFrame
 *
 * @brief       Transmit the frame pointed to by pMacDataTx with the specified type.
 *              NOTE! It is not legal to call this function from interrupt context.
 *
 * @param       txType - type of transmit
 *
 * @return      none
 **************************************************************************************************
 */
void macTxFrame(uint8 txType)
{
  MAC_ASSERT(!macTxActive);            /* transmit on top of transmit */

  /* mark transmit as active */
  macTxActive = MAC_TX_ACTIVE_INITIALIZE;

  /*
   *  The MAC will not enter sleep mode if there is an active transmit.  However, if macSleep() is
   *  ever called from interrupt context, it possible to enter sleep state after a transmit is
   *  intiated but before macTxActive is set.  To recover from this, the transmit must be aborted
   *  and proper notificiation given to high-level.
   */
  if (macSleepState != MAC_SLEEP_STATE_AWAKE)
  {
    /* notify high-level that transmit had to be aborted */
    txComplete(MAC_TX_ABORTED);

    /* exit from transmit logic */
    return;
  }

  /* save transmit type */
  macTxType = txType;

  /*-------------------------------------------------------------------------------
   *  Prepare for transmit.
   */
  if (macTxType == MAC_TX_TYPE_SLOTTED)
  {
    MAC_RADIO_TX_PREP_SLOTTED();
  }
  else
  {
    MAC_ASSERT((macTxType == MAC_TX_TYPE_SLOTTED_CSMA) || (macTxType == MAC_TX_TYPE_UNSLOTTED_CSMA));

    nb = 0;
    macTxBe = (pMacDataTx->internal.txOptions & MAC_TXOPTION_ALT_BE) ? macPib.altBe : macPib.minBe;

    if ((macTxType == MAC_TX_TYPE_SLOTTED_CSMA) && (macPib.battLifeExt))
    {
      macTxBe = MIN(2, macTxBe);
    }

    txCsmaPrep();
  }

  /*-------------------------------------------------------------------------------
   *  Load transmit FIFO unless this is a retransmit.  No need to write
   *  the FIFO again in that case.
   */
  if (!txRetransmitFlag)
  {
    uint8 * p;
    uint8   lenMhrMsdu;

    MAC_ASSERT(pMacDataTx != NULL); /* must have data to transmit */

    /* save needed parameters */
    txAckReq = MAC_ACK_REQUEST(pMacDataTx->msdu.p);
    txSeqn   = MAC_SEQ_NUMBER(pMacDataTx->msdu.p);

    /* set length of frame (note: use of term msdu is a misnomer, here it's actually mhr + msdu) */
    lenMhrMsdu = pMacDataTx->msdu.len;

    /* calling code guarantees an unused prepended byte  */
    p = pMacDataTx->msdu.p - PREPENDED_BYTE_LEN;

    /* first byte of buffer is length of MPDU */
    *p = lenMhrMsdu + MFR_LEN;

    /*
     *  Flush the TX FIFO.  This is necessary in case the previous transmit was never
     *  actually sent (e.g. CSMA failed without strobing TXON).  If bytes are written to
     *  the FIFO but not transmitted, they remain in the FIFO to be transmitted whenever
     *  a strobe of TXON does happen.
     */
    MAC_RADIO_FLUSH_TX_FIFO();

    /* write bytes to FIFO, prepended byte is included, MFR is not (it's generated by hardware) */
    MAC_RADIO_WRITE_TX_FIFO(p, PREPENDED_BYTE_LEN + lenMhrMsdu);
  }

  /*-------------------------------------------------------------------------------
   *  If not receiving, start the transmit.  If receive is active
   *  queue up the transmit.
   *
   *  Critical sections around the state change prevents any sort of race condition
   *  with  macTxStartQueuedFrame().  This guarantees function txGo() will only be
   *  called once.
   */
  {
    halIntState_t  s;

    HAL_ENTER_CRITICAL_SECTION(s);
    if (!macRxActive && !macRxOutgoingAckFlag)
    {
      macTxActive = MAC_TX_ACTIVE_GO;
      HAL_EXIT_CRITICAL_SECTION(s);
      txGo();
    }
    else
    {
      macTxActive = MAC_TX_ACTIVE_QUEUED;
      HAL_EXIT_CRITICAL_SECTION(s);
    }
  }
}


/*=================================================================================================
 * @fn          txCsmaPrep
 *
 * @brief       Prepare/initialize for a CSMA transmit.
 *
 * @param       none
 *
 * @return      none
 *=================================================================================================
 */
static void txCsmaPrep(void)
{
  macTxCsmaBackoffDelay = macRadioRandomByte() & ((1 << macTxBe) - 1);

  if (macTxType == MAC_TX_TYPE_SLOTTED_CSMA)
  {
    MAC_RADIO_TX_PREP_CSMA_SLOTTED();
  }
  else
  {
    MAC_RADIO_TX_PREP_CSMA_UNSLOTTED();
  }
}


/*=================================================================================================
 * @fn          txGo
 *
 * @brief       Start a transmit going.
 *
 * @param       none
 *
 * @return      none
 *=================================================================================================
 */
static void txGo(void)
{
  /*
   *  If execution has reached this point, any transmitted ACK has long since completed.  It is
   *  possible though that there is still a pending callback.  If so, it is irrelevant and needs to
   *  be canceled at this point.
   */
  MAC_RADIO_CANCEL_ACK_TX_DONE_CALLBACK();
  macRxOutgoingAckFlag = 0;

  /* based on type of transmit, call the correct "go" functionality */
  if (macTxType == MAC_TX_TYPE_SLOTTED)
  {
    MAC_RADIO_TX_GO_SLOTTED();
  }
  else
  {
    txCsmaGo();
  }
}


/*=================================================================================================
 * @fn          txCsmaGo
 *
 * @brief       Start a CSMA transmit going.
 *
 * @param       none
 *
 * @return      none
 *=================================================================================================
 */
static void txCsmaGo(void)
{
  if (macTxType == MAC_TX_TYPE_SLOTTED_CSMA)
  {
    if (macTxCsmaBackoffDelay >= macDataTxTimeAvailable())
    {
      txComplete(MAC_NO_TIME);
      return;
    }
    MAC_RADIO_TX_GO_SLOTTED_CSMA();
  }
  else
  {
    MAC_RADIO_TX_GO_CSMA();
  }
}


/**************************************************************************************************
 * @fn          macTxFrameRetransmit
 *
 * @brief       Retransmit the last frame.
 *
 * @param       none
 *
 * @return      none
 **************************************************************************************************
 */
void macTxFrameRetransmit(void)
{
  txRetransmitFlag = 1;
  macTxFrame(macTxType);
}


/**************************************************************************************************
 * @fn          macTxStartQueuedFrame
 *
 * @brief       See if there is a queued frame waiting to transmit.  If so, initiate
 *              the transmit now.
 *
 * @param       none
 *
 * @return      none
 **************************************************************************************************
 */
void macTxStartQueuedFrame(void)
{
  halIntState_t  s;

  MAC_ASSERT(!macRxActive && !macRxOutgoingAckFlag); /* queued frames should not transmit in middle of a receive */

  /*
   *  Critical sections around the state change prevents any sort of race condition
   *  with macTxFrame().  This guarantees function txGo() will only be be called once.
   */
  HAL_ENTER_CRITICAL_SECTION(s);
  if (macTxActive == MAC_TX_ACTIVE_QUEUED)
  {
    macTxActive = MAC_TX_ACTIVE_GO;
    HAL_EXIT_CRITICAL_SECTION(s);
    txGo();
  }
  else
  {
    HAL_EXIT_CRITICAL_SECTION(s);
  }
}


/**************************************************************************************************
 * @fn          macTxChannelBusyCallback
 *
 * @brief       This callback is executed if a CSMA transmit was attempted but the channel
 *              was busy.
 *
 * @param       none
 *
 * @return      none
 **************************************************************************************************
 */
void macTxChannelBusyCallback(void)
{
  MAC_ASSERT((macTxType == MAC_TX_TYPE_SLOTTED_CSMA) || (macTxType == MAC_TX_TYPE_UNSLOTTED_CSMA));

  /* turn off receiver if allowed */
  macTxActive = MAC_TX_ACTIVE_CHANNEL_BUSY;
  macRxOffRequest();

  /*  clear channel assement failed, follow through with CSMA algorithm */
  nb++;
  if (nb > macPib.maxCsmaBackoffs)
  {
    txComplete(MAC_CHANNEL_ACCESS_FAILURE);
  }
  else
  {
    macTxBe = MIN(macTxBe+1, macPib.maxBe);
    txCsmaPrep();
    macTxActive = MAC_TX_ACTIVE_GO;
    txCsmaGo();
  }
}


/**************************************************************************************************
 * @fn          macTxDoneCallback
 *
 * @brief       This callback is executed when transmit completes.
 *
 * @param       none
 *
 * @return      none
 **************************************************************************************************
 */
void macTxDoneCallback(void)
{
  halIntState_t  s;

  /*
   *  There is a small chance this function could be called twice for a single transmit.
   *  To prevent logic from executing twice, the state variable macTxActive is used as
   *  a gating mechanism to guarantee single time execution.
   */
  HAL_ENTER_CRITICAL_SECTION(s);
  if (macTxActive == MAC_TX_ACTIVE_GO)
  {
    /* see if ACK was requested */
    if (!txAckReq)
    {
      macTxActive = MAC_TX_ACTIVE_DONE;
      HAL_EXIT_CRITICAL_SECTION(s);

      /* ACK was not requested, transmit is complete */
      txComplete(MAC_SUCCESS);
    }
    else
    {
      /*
       *  ACK was requested - must wait to receive it.  A timer is set
       *  to expire after the timeout duration for waiting for an ACK.
       *  If an ACK is received, the function macTxAckReceived() is called.
       *  If an ACK is not received within the timeout period,
       *  the function macTxAckTimeoutCallback() is called.
       */
      macTxActive = MAC_TX_ACTIVE_LISTEN_FOR_ACK;
      MAC_RADIO_TX_REQUEST_ACK_TIMEOUT_CALLBACK();
      HAL_EXIT_CRITICAL_SECTION(s);
    }
  }
  else
  {
    HAL_EXIT_CRITICAL_SECTION(s);
  }
}


/**************************************************************************************************
 * @fn          macTxAckReceivedCallback
 *
 * @brief       This function is called by the receive logic when an ACK is received and
 *              transmit logic is listening for an ACK.
 *
 * @param       seqn        - sequence number of received ACK
 * @param       pendingFlag - set if pending flag of ACK is set, cleared otherwise
 *
 * @return      none
 **************************************************************************************************
 */
void macTxAckReceivedCallback(uint8 seqn, uint8 pendingFlag)
{
  halIntState_t  s;

  /* only process if listening for an ACK; critical section prevents race condition problems */
  HAL_ENTER_CRITICAL_SECTION(s);
  if (macTxActive == MAC_TX_ACTIVE_LISTEN_FOR_ACK)
  {
    macTxActive = MAC_TX_ACTIVE_POST_ACK;
    MAC_RADIO_TX_CANCEL_ACK_TIMEOUT_CALLBACK();
    HAL_EXIT_CRITICAL_SECTION(s);

    /* see if the sequence number of received ACK matches sequence number of packet just sent */
    if (seqn == txSeqn)
    {
      /*
       *  Sequence numbers match so transmit is successful.  Return appropriate
       *  status based on the pending flag of the received ACK.
       */
      if (pendingFlag)
      {
        txComplete(MAC_ACK_PENDING);
      }
      else
      {
        txComplete(MAC_SUCCESS);
      }
    }
    else
    {
      /* sequence number did not match; per spec, transmit failed at this point */
      txComplete(MAC_NO_ACK);
    }
  }
  else
  {
    HAL_EXIT_CRITICAL_SECTION(s);
  }
}


/**************************************************************************************************
 * @fn          macTxAckNotReceivedCallback
 *
 * @brief       This function is called by the receive logic when transmit is listening
 *              for an ACK but something else is received.  It is also called if the
 *              listen-for-ACK timeout is reached.
 *
 * @brief
 *
 * @param       none
 *
 * @return      none
 **************************************************************************************************
 */
void macTxAckNotReceivedCallback(void)
{
  halIntState_t  s;

  /* only process if listening for an ACK; critical section prevents race condition problems */
  HAL_ENTER_CRITICAL_SECTION(s);
  if (macTxActive == MAC_TX_ACTIVE_LISTEN_FOR_ACK)
  {
    macTxActive = MAC_TX_ACTIVE_POST_ACK;
    MAC_RADIO_TX_CANCEL_ACK_TIMEOUT_CALLBACK();
    HAL_EXIT_CRITICAL_SECTION(s);

    /* a non-ACK was received when expecting an ACK, per spec transmit is over at this point */
    txComplete(MAC_NO_ACK);
  }
  else
  {
    HAL_EXIT_CRITICAL_SECTION(s);
  }
}


/*=================================================================================================
 * @fn          txComplete
 *
 * @brief       Transmit has completed.  Perform needed maintenance and return status of
 *              the transmit via callback function.
 *
 * @param       status - status of the transmit that just went out
 *
 * @return      none
 *=================================================================================================
 */
static void txComplete(uint8 status)
{
  /* reset the retransmit flag */
  txRetransmitFlag = 0;

  /* update tx state; turn off receiver if nothing is keeping it on */
  macTxActive = MAC_TX_ACTIVE_NO_ACTIVITY;

  /* turn off receive if allowed */
  macRxOffRequest();

  /* update transmit power in case there was a change */
  macRadioUpdateTxPower();

  /*
   *  Channel cannot change during transmit so update it here.  (Channel *can* change during
   *  a receive.  The update function resets receive logic and any partially received
   *  frame is purged.)
   */
  macRadioUpdateChannel();

  /* return status of transmit via callback function */
  macTxCompleteCallback(status);
}


/**************************************************************************************************
 * @fn          macTxTimestampCallback
 *
 * @brief       This callback function records the timestamp into the receive data structure.
 *              It should be called as soon as possible after there is a valid timestamp.
 *
 * @param       none
 *
 * @return      none
 **************************************************************************************************
 */
void macTxTimestampCallback(void)
{
  MAC_ASSERT(pMacDataTx != NULL); /* transmit structure must be there */

  pMacDataTx->internal.timestamp  = macBackoffTimerCapture();
  pMacDataTx->internal.timestamp2 = MAC_RADIO_TIMER_CAPTURE();
}


/**************************************************************************************************
 * @fn          macTxCollisionWithRxCallback
 *
 * @brief       Function called if transmit strobed on top of a receive.
 *
 * @param       none
 *
 * @return      none
 **************************************************************************************************
 */
void macTxCollisionWithRxCallback(void)
{
  macRxHaltCleanup();
}



/**************************************************************************************************
 *                                  Compile Time Integrity Checks
 **************************************************************************************************
 */
#if (MAC_TX_ACTIVE_NO_ACTIVITY != 0x00)
#error "ERROR! Zero is reserved value of macTxActive. Allows boolean operations, e.g !macTxActive."
#endif

/**************************************************************************************************
*/
