int is_tx_buffer_empty(){
	tx_frame_info* tx_mpdu = (tx_frame_info*) TX_PKT_BUF_TO_ADDR(tx_pkt_buf);

	if( ( tx_mpdu->state == TX_MPDU_STATE_TX_PENDING ) && ( is_cpu_low_ready() ) ){
		return 1;
	} else {
		return 0;
	}
}
inline void wlan_phy_set_tx_signal(u8 pkt_buf, u8 rate, u16 length) {
	Xil_Out32((u32*)(TX_PKT_BUF_TO_ADDR(pkt_buf) + PHY_TX_PKT_BUF_PHY_HDR_OFFSET), WLAN_TX_SIGNAL_CALC(rate, length));
	return;
}
/**
 * @brief Handles transmission of a wireless packet
 *
 * This function is called to transmit a new packet via the PHY. While the code does utilize the wlan_mac_dcf_hw core,
 * it bypasses any of the DCF-specific state in order to directly transmit the frame. This function should be called once per packet and will return 
 * immediately following that transmission. It will not perform any DCF-like retransmissions.
 *
 * This function is called once per IPC_MBOX_TX_MPDU_READY message from CPU High. The IPC_MBOX_TX_MPDU_DONE message will be sent
 * back to CPU High when this function returns.
 *
 * @param u8 rx_pkt_buf
 *  -Index of the Tx packet buffer containing the packet to transmit
 * @param u8 rate
 *  -Index of PHY rate at which packet will be transmitted
 * @param u16 length
 *  -Number of bytes in packet, including MAC header and FCS
 * @param wlan_mac_low_tx_details* low_tx_details
 *  -Pointer to array of metadata entries to be created for each PHY transmission of this packet (eventually leading to TX_LOW log entries)
 * @return
 *  -Transmission result
 */
int frame_transmit(u8 pkt_buf, u8 rate, u16 length, wlan_mac_low_tx_details* low_tx_details) {
	//This function manages the MAC_DCF_HW core.

	//return 0; //disable all Tx

	tx_frame_info* mpdu_info = (tx_frame_info*) (TX_PKT_BUF_TO_ADDR(pkt_buf));
	u64 last_tx_timestamp;
	int curr_tx_pow;
	last_tx_timestamp = (u64)(mpdu_info->delay_accept) + (u64)(mpdu_info->timestamp_create);

	u32 mac_hw_status;

	//Write the SIGNAL field (interpreted by the PHY during Tx waveform generation)
	wlan_phy_set_tx_signal(pkt_buf, rate, length);

	unsigned char mpdu_tx_ant_mask = 0;
	switch(mpdu_info->params.phy.antenna_mode) {
		case TX_ANTMODE_SISO_ANTA:
			mpdu_tx_ant_mask |= 0x1;
		break;
		case TX_ANTMODE_SISO_ANTB:
			mpdu_tx_ant_mask |= 0x2;
		break;
		case TX_ANTMODE_SISO_ANTC:
			mpdu_tx_ant_mask |= 0x4;
		break;
		case TX_ANTMODE_SISO_ANTD:
			mpdu_tx_ant_mask |= 0x8;
		break;
		default:
			mpdu_tx_ant_mask = 0x1;
		break;
	}

	mpdu_info->num_tx_attempts = 1;

	curr_tx_pow = wlan_mac_low_dbm_to_gain_target(mpdu_info->params.phy.power);

	//wlan_mac_tx_ctrl_A_params(pktBuf, antMask, preTx_backoff_slots, preWait_postRxTimer1, preWait_postTxTimer1, postWait_postTxTimer2)
	wlan_mac_tx_ctrl_A_params(pkt_buf, mpdu_tx_ant_mask, 0, 0, 0, 0);

	//Set Tx Gains
	wlan_mac_tx_ctrl_A_gains(curr_tx_pow, curr_tx_pow, curr_tx_pow, curr_tx_pow);

	//Before we mess with any PHY state, we need to make sure it isn't actively
	//transmitting. For example, it may be sending an ACK when we get to this part of the code
	while(wlan_mac_get_status() & WLAN_MAC_STATUS_MASK_TX_PHY_ACTIVE){}

	//Submit the MPDU for transmission - this starts the MAC hardware's MPDU 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{
		if(low_tx_details != NULL){
			low_tx_details[0].mpdu_phy_params.rate = mpdu_info->params.phy.rate;
			low_tx_details[0].mpdu_phy_params.power = mpdu_info->params.phy.power;
			low_tx_details[0].mpdu_phy_params.antenna_mode = mpdu_info->params.phy.antenna_mode;
			low_tx_details[0].chan_num = wlan_mac_low_get_active_channel();
			low_tx_details[0].num_slots = 0;
			low_tx_details[0].cw = 0;
		}
		mac_hw_status = wlan_mac_get_status();

		if( mac_hw_status & WLAN_MAC_STATUS_MASK_TX_A_DONE) {
			if( low_tx_details != NULL ){
				low_tx_details[0].tx_start_delta = (u32)(get_tx_start_timestamp() - last_tx_timestamp);
				last_tx_timestamp = get_tx_start_timestamp();
			}

			switch( mac_hw_status & WLAN_MAC_STATUS_MASK_TX_A_RESULT ){
				case WLAN_MAC_STATUS_TX_A_RESULT_NONE:
					return 0;
				break;
			}
		}
	} while( mac_hw_status & WLAN_MAC_STATUS_MASK_TX_A_PENDING );

	return -1;
}
void wlan_mac_util_init( u32 type, u32 eth_dev_num ){
	int            Status;
    u32            i;
	u32            gpio_read;
	u32            queue_len;
	u64            timestamp;
	u32            log_size;
	tx_frame_info* tx_mpdu;

    // Initialize callbacks
	eth_rx_callback         = (function_ptr_t)nullCallback;
	mpdu_rx_callback        = (function_ptr_t)nullCallback;
	fcs_bad_rx_callback     = (function_ptr_t)nullCallback;
	mpdu_tx_done_callback   = (function_ptr_t)nullCallback;
	pb_u_callback           = (function_ptr_t)nullCallback;
	pb_m_callback           = (function_ptr_t)nullCallback;
	pb_d_callback           = (function_ptr_t)nullCallback;
	uart_callback           = (function_ptr_t)nullCallback;
	ipc_rx_callback         = (function_ptr_t)nullCallback;
	check_queue_callback    = (function_ptr_t)nullCallback;
	mpdu_tx_accept_callback = (function_ptr_t)nullCallback;

	wlan_mac_ipc_init();

	for(i=0;i < NUM_TX_PKT_BUFS; i++){
		tx_mpdu = (tx_frame_info*)TX_PKT_BUF_TO_ADDR(i);
		tx_mpdu->state = TX_MPDU_STATE_EMPTY;
	}

	tx_pkt_buf = 0;

#ifdef _DEBUG_
	xil_printf("locking tx_pkt_buf = %d\n", tx_pkt_buf);
#endif

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

	tx_mpdu = (tx_frame_info*)TX_PKT_BUF_TO_ADDR(tx_pkt_buf);
	tx_mpdu->state = TX_MPDU_STATE_TX_PENDING;

	//Initialize the central DMA (CDMA) driver
	XAxiCdma_Config *cdma_cfg_ptr;
	cdma_cfg_ptr = XAxiCdma_LookupConfig(XPAR_AXI_CDMA_0_DEVICE_ID);
	Status = XAxiCdma_CfgInitialize(&cdma_inst, cdma_cfg_ptr, cdma_cfg_ptr->BaseAddress);
	if (Status != XST_SUCCESS) {
		warp_printf(PL_ERROR,"Error initializing CDMA: %d\n", Status);
	}
	XAxiCdma_IntrDisable(&cdma_inst, XAXICDMA_XR_IRQ_ALL_MASK);


	Status = XGpio_Initialize(&Gpio, GPIO_DEVICE_ID);
	gpio_timestamp_initialize();

	if (Status != XST_SUCCESS) {
		warp_printf(PL_ERROR, "Error initializing GPIO\n");
		return;
	}

	Status = XUartLite_Initialize(&UartLite, UARTLITE_DEVICE_ID);
	if (Status != XST_SUCCESS) {
		warp_printf(PL_ERROR, "Error initializing XUartLite\n");
		return;
	}


	gpio_read = XGpio_DiscreteRead(&Gpio, GPIO_INPUT_CHANNEL);
	if(gpio_read&GPIO_MASK_DRAM_INIT_DONE){
		xil_printf("DRAM SODIMM Detected\n");
		if(memory_test()==0){
			queue_dram_present(1);
			dram_present = 1;
		} else {
			queue_dram_present(0);
			dram_present = 0;
		}
	} else {
		queue_dram_present(0);
		dram_present = 0;
		timestamp = get_usec_timestamp();

		while((get_usec_timestamp() - timestamp) < 100000){
			if((XGpio_DiscreteRead(&Gpio, GPIO_INPUT_CHANNEL)&GPIO_MASK_DRAM_INIT_DONE)){
				xil_printf("DRAM SODIMM Detected\n");
				if(memory_test()==0){
					queue_dram_present(1);
					dram_present = 1;
				} else {
					queue_dram_present(0);
					dram_present = 0;
				}
				break;
			}
		}
	}

	queue_len = queue_init();

	if( dram_present ) {
		//The event_list lives in DRAM immediately following the queue payloads.
		if(MAX_EVENT_LOG == -1){
			log_size = (DDR3_SIZE - queue_len);
		} else {
			log_size = min( (DDR3_SIZE - queue_len), MAX_EVENT_LOG );
		}

		event_log_init( (void*)(DDR3_BASEADDR + queue_len), log_size );

	} else {
		log_size = 0;
	}

#ifdef USE_WARPNET_WLAN_EXP
	// Communicate the log size to WARPNet
	node_info_set_event_log_size( log_size );
#endif

	wlan_eth_init();

	//Set direction of GPIO channels
	XGpio_SetDataDirection(&Gpio, GPIO_INPUT_CHANNEL, 0xFFFFFFFF);
	XGpio_SetDataDirection(&Gpio, GPIO_OUTPUT_CHANNEL, 0);


	Status = XTmrCtr_Initialize(&TimerCounterInst, TMRCTR_DEVICE_ID);
	if (Status != XST_SUCCESS) {
		xil_printf("XTmrCtr failed to initialize\n");
		return;
	}

	//Set the handler for Timer
	XTmrCtr_SetHandler(&TimerCounterInst, timer_handler, &TimerCounterInst);

	//Enable interrupt of timer and auto-reload so it continues repeatedly
	XTmrCtr_SetOptions(&TimerCounterInst, TIMER_CNTR_FAST, XTC_DOWN_COUNT_OPTION | XTC_INT_MODE_OPTION);
	XTmrCtr_SetOptions(&TimerCounterInst, TIMER_CNTR_SLOW, XTC_DOWN_COUNT_OPTION | XTC_INT_MODE_OPTION);

	timer_running[TIMER_CNTR_FAST] = 0;
	timer_running[TIMER_CNTR_SLOW] = 0;
    
    // Get the type of node from the input parameter
    hw_info.type              = type;
    hw_info.wn_exp_eth_device = eth_dev_num;
    
#ifdef USE_WARPNET_WLAN_EXP
    // We cannot initialize WARPNet until after the lower CPU sends all the HW information to us through the IPC call
    warpnet_initialized = 0;
#endif
    
    wlan_mac_ltg_sched_init();

}