| /* |
| Copyright (C) 2004 - 2007 rt2x00 SourceForge Project |
| <http://rt2x00.serialmonkey.com> |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program; if not, write to the |
| Free Software Foundation, Inc., |
| 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
| */ |
| |
| /* |
| Module: rt2x00lib |
| Abstract: rt2x00 generic device routines. |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| |
| #include "rt2x00.h" |
| #include "rt2x00lib.h" |
| #include "rt2x00dump.h" |
| |
| /* |
| * Ring handler. |
| */ |
| struct data_ring *rt2x00lib_get_ring(struct rt2x00_dev *rt2x00dev, |
| const unsigned int queue) |
| { |
| int beacon = test_bit(DRIVER_REQUIRE_BEACON_RING, &rt2x00dev->flags); |
| |
| /* |
| * Check if we are requesting a reqular TX ring, |
| * or if we are requesting a Beacon or Atim ring. |
| * For Atim rings, we should check if it is supported. |
| */ |
| if (queue < rt2x00dev->hw->queues && rt2x00dev->tx) |
| return &rt2x00dev->tx[queue]; |
| |
| if (!rt2x00dev->bcn || !beacon) |
| return NULL; |
| |
| if (queue == IEEE80211_TX_QUEUE_BEACON) |
| return &rt2x00dev->bcn[0]; |
| else if (queue == IEEE80211_TX_QUEUE_AFTER_BEACON) |
| return &rt2x00dev->bcn[1]; |
| |
| return NULL; |
| } |
| EXPORT_SYMBOL_GPL(rt2x00lib_get_ring); |
| |
| /* |
| * Link tuning handlers |
| */ |
| void rt2x00lib_reset_link_tuner(struct rt2x00_dev *rt2x00dev) |
| { |
| if (!test_bit(DEVICE_ENABLED_RADIO, &rt2x00dev->flags)) |
| return; |
| |
| /* |
| * Reset link information. |
| * Both the currently active vgc level as well as |
| * the link tuner counter should be reset. Resetting |
| * the counter is important for devices where the |
| * device should only perform link tuning during the |
| * first minute after being enabled. |
| */ |
| rt2x00dev->link.count = 0; |
| rt2x00dev->link.vgc_level = 0; |
| |
| /* |
| * Reset the link tuner. |
| */ |
| rt2x00dev->ops->lib->reset_tuner(rt2x00dev); |
| } |
| |
| static void rt2x00lib_start_link_tuner(struct rt2x00_dev *rt2x00dev) |
| { |
| /* |
| * Clear all (possibly) pre-existing quality statistics. |
| */ |
| memset(&rt2x00dev->link.qual, 0, sizeof(rt2x00dev->link.qual)); |
| |
| /* |
| * The RX and TX percentage should start at 50% |
| * this will assure we will get at least get some |
| * decent value when the link tuner starts. |
| * The value will be dropped and overwritten with |
| * the correct (measured )value anyway during the |
| * first run of the link tuner. |
| */ |
| rt2x00dev->link.qual.rx_percentage = 50; |
| rt2x00dev->link.qual.tx_percentage = 50; |
| |
| rt2x00lib_reset_link_tuner(rt2x00dev); |
| |
| queue_delayed_work(rt2x00dev->hw->workqueue, |
| &rt2x00dev->link.work, LINK_TUNE_INTERVAL); |
| } |
| |
| static void rt2x00lib_stop_link_tuner(struct rt2x00_dev *rt2x00dev) |
| { |
| cancel_delayed_work_sync(&rt2x00dev->link.work); |
| } |
| |
| /* |
| * Ring initialization |
| */ |
| static void rt2x00lib_init_rxrings(struct rt2x00_dev *rt2x00dev) |
| { |
| struct data_ring *ring = rt2x00dev->rx; |
| unsigned int i; |
| |
| if (!rt2x00dev->ops->lib->init_rxentry) |
| return; |
| |
| if (ring->data_addr) |
| memset(ring->data_addr, 0, rt2x00_get_ring_size(ring)); |
| |
| for (i = 0; i < ring->stats.limit; i++) |
| rt2x00dev->ops->lib->init_rxentry(rt2x00dev, &ring->entry[i]); |
| |
| rt2x00_ring_index_clear(ring); |
| } |
| |
| static void rt2x00lib_init_txrings(struct rt2x00_dev *rt2x00dev) |
| { |
| struct data_ring *ring; |
| unsigned int i; |
| |
| if (!rt2x00dev->ops->lib->init_txentry) |
| return; |
| |
| txringall_for_each(rt2x00dev, ring) { |
| if (ring->data_addr) |
| memset(ring->data_addr, 0, rt2x00_get_ring_size(ring)); |
| |
| for (i = 0; i < ring->stats.limit; i++) |
| rt2x00dev->ops->lib->init_txentry(rt2x00dev, |
| &ring->entry[i]); |
| |
| rt2x00_ring_index_clear(ring); |
| } |
| } |
| |
| /* |
| * Radio control handlers. |
| */ |
| int rt2x00lib_enable_radio(struct rt2x00_dev *rt2x00dev) |
| { |
| int status; |
| |
| /* |
| * Don't enable the radio twice. |
| * And check if the hardware button has been disabled. |
| */ |
| if (test_bit(DEVICE_ENABLED_RADIO, &rt2x00dev->flags) || |
| test_bit(DEVICE_DISABLED_RADIO_HW, &rt2x00dev->flags)) |
| return 0; |
| |
| /* |
| * Initialize all data rings. |
| */ |
| rt2x00lib_init_rxrings(rt2x00dev); |
| rt2x00lib_init_txrings(rt2x00dev); |
| |
| /* |
| * Enable radio. |
| */ |
| status = rt2x00dev->ops->lib->set_device_state(rt2x00dev, |
| STATE_RADIO_ON); |
| if (status) |
| return status; |
| |
| __set_bit(DEVICE_ENABLED_RADIO, &rt2x00dev->flags); |
| |
| /* |
| * Enable RX. |
| */ |
| rt2x00lib_toggle_rx(rt2x00dev, STATE_RADIO_RX_ON); |
| |
| /* |
| * Start the TX queues. |
| */ |
| ieee80211_start_queues(rt2x00dev->hw); |
| |
| return 0; |
| } |
| |
| void rt2x00lib_disable_radio(struct rt2x00_dev *rt2x00dev) |
| { |
| if (!__test_and_clear_bit(DEVICE_ENABLED_RADIO, &rt2x00dev->flags)) |
| return; |
| |
| /* |
| * Stop all scheduled work. |
| */ |
| if (work_pending(&rt2x00dev->beacon_work)) |
| cancel_work_sync(&rt2x00dev->beacon_work); |
| if (work_pending(&rt2x00dev->filter_work)) |
| cancel_work_sync(&rt2x00dev->filter_work); |
| if (work_pending(&rt2x00dev->config_work)) |
| cancel_work_sync(&rt2x00dev->config_work); |
| |
| /* |
| * Stop the TX queues. |
| */ |
| ieee80211_stop_queues(rt2x00dev->hw); |
| |
| /* |
| * Disable RX. |
| */ |
| rt2x00lib_toggle_rx(rt2x00dev, STATE_RADIO_RX_OFF); |
| |
| /* |
| * Disable radio. |
| */ |
| rt2x00dev->ops->lib->set_device_state(rt2x00dev, STATE_RADIO_OFF); |
| } |
| |
| void rt2x00lib_toggle_rx(struct rt2x00_dev *rt2x00dev, enum dev_state state) |
| { |
| /* |
| * When we are disabling the RX, we should also stop the link tuner. |
| */ |
| if (state == STATE_RADIO_RX_OFF) |
| rt2x00lib_stop_link_tuner(rt2x00dev); |
| |
| rt2x00dev->ops->lib->set_device_state(rt2x00dev, state); |
| |
| /* |
| * When we are enabling the RX, we should also start the link tuner. |
| */ |
| if (state == STATE_RADIO_RX_ON && |
| is_interface_present(&rt2x00dev->interface)) |
| rt2x00lib_start_link_tuner(rt2x00dev); |
| } |
| |
| static void rt2x00lib_evaluate_antenna_sample(struct rt2x00_dev *rt2x00dev) |
| { |
| enum antenna rx = rt2x00dev->link.ant.active.rx; |
| enum antenna tx = rt2x00dev->link.ant.active.tx; |
| int sample_a = |
| rt2x00_get_link_ant_rssi_history(&rt2x00dev->link, ANTENNA_A); |
| int sample_b = |
| rt2x00_get_link_ant_rssi_history(&rt2x00dev->link, ANTENNA_B); |
| |
| /* |
| * We are done sampling. Now we should evaluate the results. |
| */ |
| rt2x00dev->link.ant.flags &= ~ANTENNA_MODE_SAMPLE; |
| |
| /* |
| * During the last period we have sampled the RSSI |
| * from both antenna's. It now is time to determine |
| * which antenna demonstrated the best performance. |
| * When we are already on the antenna with the best |
| * performance, then there really is nothing for us |
| * left to do. |
| */ |
| if (sample_a == sample_b) |
| return; |
| |
| if (rt2x00dev->link.ant.flags & ANTENNA_RX_DIVERSITY) |
| rx = (sample_a > sample_b) ? ANTENNA_A : ANTENNA_B; |
| |
| if (rt2x00dev->link.ant.flags & ANTENNA_TX_DIVERSITY) |
| tx = (sample_a > sample_b) ? ANTENNA_A : ANTENNA_B; |
| |
| rt2x00lib_config_antenna(rt2x00dev, rx, tx); |
| } |
| |
| static void rt2x00lib_evaluate_antenna_eval(struct rt2x00_dev *rt2x00dev) |
| { |
| enum antenna rx = rt2x00dev->link.ant.active.rx; |
| enum antenna tx = rt2x00dev->link.ant.active.tx; |
| int rssi_curr = rt2x00_get_link_ant_rssi(&rt2x00dev->link); |
| int rssi_old = rt2x00_update_ant_rssi(&rt2x00dev->link, rssi_curr); |
| |
| /* |
| * Legacy driver indicates that we should swap antenna's |
| * when the difference in RSSI is greater that 5. This |
| * also should be done when the RSSI was actually better |
| * then the previous sample. |
| * When the difference exceeds the threshold we should |
| * sample the rssi from the other antenna to make a valid |
| * comparison between the 2 antennas. |
| */ |
| if (abs(rssi_curr - rssi_old) < 5) |
| return; |
| |
| rt2x00dev->link.ant.flags |= ANTENNA_MODE_SAMPLE; |
| |
| if (rt2x00dev->link.ant.flags & ANTENNA_RX_DIVERSITY) |
| rx = (rx == ANTENNA_A) ? ANTENNA_B : ANTENNA_A; |
| |
| if (rt2x00dev->link.ant.flags & ANTENNA_TX_DIVERSITY) |
| tx = (tx == ANTENNA_A) ? ANTENNA_B : ANTENNA_A; |
| |
| rt2x00lib_config_antenna(rt2x00dev, rx, tx); |
| } |
| |
| static void rt2x00lib_evaluate_antenna(struct rt2x00_dev *rt2x00dev) |
| { |
| /* |
| * Determine if software diversity is enabled for |
| * either the TX or RX antenna (or both). |
| * Always perform this check since within the link |
| * tuner interval the configuration might have changed. |
| */ |
| rt2x00dev->link.ant.flags &= ~ANTENNA_RX_DIVERSITY; |
| rt2x00dev->link.ant.flags &= ~ANTENNA_TX_DIVERSITY; |
| |
| if (rt2x00dev->hw->conf.antenna_sel_rx == 0 && |
| rt2x00dev->default_ant.rx == ANTENNA_SW_DIVERSITY) |
| rt2x00dev->link.ant.flags |= ANTENNA_RX_DIVERSITY; |
| if (rt2x00dev->hw->conf.antenna_sel_tx == 0 && |
| rt2x00dev->default_ant.tx == ANTENNA_SW_DIVERSITY) |
| rt2x00dev->link.ant.flags |= ANTENNA_TX_DIVERSITY; |
| |
| if (!(rt2x00dev->link.ant.flags & ANTENNA_RX_DIVERSITY) && |
| !(rt2x00dev->link.ant.flags & ANTENNA_TX_DIVERSITY)) { |
| rt2x00dev->link.ant.flags = 0; |
| return; |
| } |
| |
| /* |
| * If we have only sampled the data over the last period |
| * we should now harvest the data. Otherwise just evaluate |
| * the data. The latter should only be performed once |
| * every 2 seconds. |
| */ |
| if (rt2x00dev->link.ant.flags & ANTENNA_MODE_SAMPLE) |
| rt2x00lib_evaluate_antenna_sample(rt2x00dev); |
| else if (rt2x00dev->link.count & 1) |
| rt2x00lib_evaluate_antenna_eval(rt2x00dev); |
| } |
| |
| static void rt2x00lib_update_link_stats(struct link *link, int rssi) |
| { |
| int avg_rssi = rssi; |
| |
| /* |
| * Update global RSSI |
| */ |
| if (link->qual.avg_rssi) |
| avg_rssi = MOVING_AVERAGE(link->qual.avg_rssi, rssi, 8); |
| link->qual.avg_rssi = avg_rssi; |
| |
| /* |
| * Update antenna RSSI |
| */ |
| if (link->ant.rssi_ant) |
| rssi = MOVING_AVERAGE(link->ant.rssi_ant, rssi, 8); |
| link->ant.rssi_ant = rssi; |
| } |
| |
| static void rt2x00lib_precalculate_link_signal(struct link_qual *qual) |
| { |
| if (qual->rx_failed || qual->rx_success) |
| qual->rx_percentage = |
| (qual->rx_success * 100) / |
| (qual->rx_failed + qual->rx_success); |
| else |
| qual->rx_percentage = 50; |
| |
| if (qual->tx_failed || qual->tx_success) |
| qual->tx_percentage = |
| (qual->tx_success * 100) / |
| (qual->tx_failed + qual->tx_success); |
| else |
| qual->tx_percentage = 50; |
| |
| qual->rx_success = 0; |
| qual->rx_failed = 0; |
| qual->tx_success = 0; |
| qual->tx_failed = 0; |
| } |
| |
| static int rt2x00lib_calculate_link_signal(struct rt2x00_dev *rt2x00dev, |
| int rssi) |
| { |
| int rssi_percentage = 0; |
| int signal; |
| |
| /* |
| * We need a positive value for the RSSI. |
| */ |
| if (rssi < 0) |
| rssi += rt2x00dev->rssi_offset; |
| |
| /* |
| * Calculate the different percentages, |
| * which will be used for the signal. |
| */ |
| if (rt2x00dev->rssi_offset) |
| rssi_percentage = (rssi * 100) / rt2x00dev->rssi_offset; |
| |
| /* |
| * Add the individual percentages and use the WEIGHT |
| * defines to calculate the current link signal. |
| */ |
| signal = ((WEIGHT_RSSI * rssi_percentage) + |
| (WEIGHT_TX * rt2x00dev->link.qual.tx_percentage) + |
| (WEIGHT_RX * rt2x00dev->link.qual.rx_percentage)) / 100; |
| |
| return (signal > 100) ? 100 : signal; |
| } |
| |
| static void rt2x00lib_link_tuner(struct work_struct *work) |
| { |
| struct rt2x00_dev *rt2x00dev = |
| container_of(work, struct rt2x00_dev, link.work.work); |
| |
| /* |
| * When the radio is shutting down we should |
| * immediately cease all link tuning. |
| */ |
| if (!test_bit(DEVICE_ENABLED_RADIO, &rt2x00dev->flags)) |
| return; |
| |
| /* |
| * Update statistics. |
| */ |
| rt2x00dev->ops->lib->link_stats(rt2x00dev, &rt2x00dev->link.qual); |
| rt2x00dev->low_level_stats.dot11FCSErrorCount += |
| rt2x00dev->link.qual.rx_failed; |
| |
| /* |
| * Only perform the link tuning when Link tuning |
| * has been enabled (This could have been disabled from the EEPROM). |
| */ |
| if (!test_bit(CONFIG_DISABLE_LINK_TUNING, &rt2x00dev->flags)) |
| rt2x00dev->ops->lib->link_tuner(rt2x00dev); |
| |
| /* |
| * Precalculate a portion of the link signal which is |
| * in based on the tx/rx success/failure counters. |
| */ |
| rt2x00lib_precalculate_link_signal(&rt2x00dev->link.qual); |
| |
| /* |
| * Evaluate antenna setup, make this the last step since this could |
| * possibly reset some statistics. |
| */ |
| rt2x00lib_evaluate_antenna(rt2x00dev); |
| |
| /* |
| * Increase tuner counter, and reschedule the next link tuner run. |
| */ |
| rt2x00dev->link.count++; |
| queue_delayed_work(rt2x00dev->hw->workqueue, &rt2x00dev->link.work, |
| LINK_TUNE_INTERVAL); |
| } |
| |
| static void rt2x00lib_packetfilter_scheduled(struct work_struct *work) |
| { |
| struct rt2x00_dev *rt2x00dev = |
| container_of(work, struct rt2x00_dev, filter_work); |
| unsigned int filter = rt2x00dev->packet_filter; |
| |
| /* |
| * Since we had stored the filter inside interface.filter, |
| * we should now clear that field. Otherwise the driver will |
| * assume nothing has changed (*total_flags will be compared |
| * to interface.filter to determine if any action is required). |
| */ |
| rt2x00dev->packet_filter = 0; |
| |
| rt2x00dev->ops->hw->configure_filter(rt2x00dev->hw, |
| filter, &filter, 0, NULL); |
| } |
| |
| static void rt2x00lib_configuration_scheduled(struct work_struct *work) |
| { |
| struct rt2x00_dev *rt2x00dev = |
| container_of(work, struct rt2x00_dev, config_work); |
| struct ieee80211_bss_conf bss_conf; |
| |
| bss_conf.use_short_preamble = |
| test_bit(CONFIG_SHORT_PREAMBLE, &rt2x00dev->flags); |
| |
| /* |
| * FIXME: shouldn't invoke it this way because all other contents |
| * of bss_conf is invalid. |
| */ |
| rt2x00mac_bss_info_changed(rt2x00dev->hw, rt2x00dev->interface.id, |
| &bss_conf, BSS_CHANGED_ERP_PREAMBLE); |
| } |
| |
| /* |
| * Interrupt context handlers. |
| */ |
| static void rt2x00lib_beacondone_scheduled(struct work_struct *work) |
| { |
| struct rt2x00_dev *rt2x00dev = |
| container_of(work, struct rt2x00_dev, beacon_work); |
| struct data_ring *ring = |
| rt2x00lib_get_ring(rt2x00dev, IEEE80211_TX_QUEUE_BEACON); |
| struct data_entry *entry = rt2x00_get_data_entry(ring); |
| struct sk_buff *skb; |
| |
| skb = ieee80211_beacon_get(rt2x00dev->hw, |
| rt2x00dev->interface.id, |
| &entry->tx_status.control); |
| if (!skb) |
| return; |
| |
| rt2x00dev->ops->hw->beacon_update(rt2x00dev->hw, skb, |
| &entry->tx_status.control); |
| |
| dev_kfree_skb(skb); |
| } |
| |
| void rt2x00lib_beacondone(struct rt2x00_dev *rt2x00dev) |
| { |
| if (!test_bit(DEVICE_ENABLED_RADIO, &rt2x00dev->flags)) |
| return; |
| |
| queue_work(rt2x00dev->hw->workqueue, &rt2x00dev->beacon_work); |
| } |
| EXPORT_SYMBOL_GPL(rt2x00lib_beacondone); |
| |
| void rt2x00lib_txdone(struct data_entry *entry, |
| const int status, const int retry) |
| { |
| struct rt2x00_dev *rt2x00dev = entry->ring->rt2x00dev; |
| struct ieee80211_tx_status *tx_status = &entry->tx_status; |
| struct ieee80211_low_level_stats *stats = &rt2x00dev->low_level_stats; |
| int success = !!(status == TX_SUCCESS || status == TX_SUCCESS_RETRY); |
| int fail = !!(status == TX_FAIL_RETRY || status == TX_FAIL_INVALID || |
| status == TX_FAIL_OTHER); |
| |
| /* |
| * Update TX statistics. |
| */ |
| tx_status->flags = 0; |
| tx_status->ack_signal = 0; |
| tx_status->excessive_retries = (status == TX_FAIL_RETRY); |
| tx_status->retry_count = retry; |
| rt2x00dev->link.qual.tx_success += success; |
| rt2x00dev->link.qual.tx_failed += retry + fail; |
| |
| if (!(tx_status->control.flags & IEEE80211_TXCTL_NO_ACK)) { |
| if (success) |
| tx_status->flags |= IEEE80211_TX_STATUS_ACK; |
| else |
| stats->dot11ACKFailureCount++; |
| } |
| |
| tx_status->queue_length = entry->ring->stats.limit; |
| tx_status->queue_number = tx_status->control.queue; |
| |
| if (tx_status->control.flags & IEEE80211_TXCTL_USE_RTS_CTS) { |
| if (success) |
| stats->dot11RTSSuccessCount++; |
| else |
| stats->dot11RTSFailureCount++; |
| } |
| |
| /* |
| * Send the tx_status to mac80211 & debugfs. |
| * mac80211 will clean up the skb structure. |
| */ |
| get_skb_desc(entry->skb)->frame_type = DUMP_FRAME_TXDONE; |
| rt2x00debug_dump_frame(rt2x00dev, entry->skb); |
| ieee80211_tx_status_irqsafe(rt2x00dev->hw, entry->skb, tx_status); |
| entry->skb = NULL; |
| } |
| EXPORT_SYMBOL_GPL(rt2x00lib_txdone); |
| |
| void rt2x00lib_rxdone(struct data_entry *entry, struct sk_buff *skb, |
| struct rxdata_entry_desc *desc) |
| { |
| struct rt2x00_dev *rt2x00dev = entry->ring->rt2x00dev; |
| struct ieee80211_rx_status *rx_status = &rt2x00dev->rx_status; |
| struct ieee80211_hw_mode *mode; |
| struct ieee80211_rate *rate; |
| struct ieee80211_hdr *hdr; |
| unsigned int i; |
| int val = 0; |
| u16 fc; |
| |
| /* |
| * Update RX statistics. |
| */ |
| mode = &rt2x00dev->hwmodes[rt2x00dev->curr_hwmode]; |
| for (i = 0; i < mode->num_rates; i++) { |
| rate = &mode->rates[i]; |
| |
| /* |
| * When frame was received with an OFDM bitrate, |
| * the signal is the PLCP value. If it was received with |
| * a CCK bitrate the signal is the rate in 0.5kbit/s. |
| */ |
| if (!desc->ofdm) |
| val = DEVICE_GET_RATE_FIELD(rate->val, RATE); |
| else |
| val = DEVICE_GET_RATE_FIELD(rate->val, PLCP); |
| |
| if (val == desc->signal) { |
| val = rate->val; |
| break; |
| } |
| } |
| |
| /* |
| * Only update link status if this is a beacon frame carrying our bssid. |
| */ |
| hdr = (struct ieee80211_hdr*)skb->data; |
| fc = le16_to_cpu(hdr->frame_control); |
| if (is_beacon(fc) && desc->my_bss) |
| rt2x00lib_update_link_stats(&rt2x00dev->link, desc->rssi); |
| |
| rt2x00dev->link.qual.rx_success++; |
| |
| rx_status->rate = val; |
| rx_status->signal = |
| rt2x00lib_calculate_link_signal(rt2x00dev, desc->rssi); |
| rx_status->ssi = desc->rssi; |
| rx_status->flag = desc->flags; |
| rx_status->antenna = rt2x00dev->link.ant.active.rx; |
| |
| /* |
| * Send frame to mac80211 & debugfs |
| */ |
| get_skb_desc(skb)->frame_type = DUMP_FRAME_RXDONE; |
| rt2x00debug_dump_frame(rt2x00dev, skb); |
| ieee80211_rx_irqsafe(rt2x00dev->hw, skb, rx_status); |
| } |
| EXPORT_SYMBOL_GPL(rt2x00lib_rxdone); |
| |
| /* |
| * TX descriptor initializer |
| */ |
| void rt2x00lib_write_tx_desc(struct rt2x00_dev *rt2x00dev, |
| struct sk_buff *skb, |
| struct ieee80211_tx_control *control) |
| { |
| struct txdata_entry_desc desc; |
| struct skb_desc *skbdesc = get_skb_desc(skb); |
| struct ieee80211_hdr *ieee80211hdr = skbdesc->data; |
| int tx_rate; |
| int bitrate; |
| int length; |
| int duration; |
| int residual; |
| u16 frame_control; |
| u16 seq_ctrl; |
| |
| memset(&desc, 0, sizeof(desc)); |
| |
| desc.cw_min = skbdesc->ring->tx_params.cw_min; |
| desc.cw_max = skbdesc->ring->tx_params.cw_max; |
| desc.aifs = skbdesc->ring->tx_params.aifs; |
| |
| /* |
| * Identify queue |
| */ |
| if (control->queue < rt2x00dev->hw->queues) |
| desc.queue = control->queue; |
| else if (control->queue == IEEE80211_TX_QUEUE_BEACON || |
| control->queue == IEEE80211_TX_QUEUE_AFTER_BEACON) |
| desc.queue = QUEUE_MGMT; |
| else |
| desc.queue = QUEUE_OTHER; |
| |
| /* |
| * Read required fields from ieee80211 header. |
| */ |
| frame_control = le16_to_cpu(ieee80211hdr->frame_control); |
| seq_ctrl = le16_to_cpu(ieee80211hdr->seq_ctrl); |
| |
| tx_rate = control->tx_rate; |
| |
| /* |
| * Check whether this frame is to be acked |
| */ |
| if (!(control->flags & IEEE80211_TXCTL_NO_ACK)) |
| __set_bit(ENTRY_TXD_ACK, &desc.flags); |
| |
| /* |
| * Check if this is a RTS/CTS frame |
| */ |
| if (is_rts_frame(frame_control) || is_cts_frame(frame_control)) { |
| __set_bit(ENTRY_TXD_BURST, &desc.flags); |
| if (is_rts_frame(frame_control)) { |
| __set_bit(ENTRY_TXD_RTS_FRAME, &desc.flags); |
| __set_bit(ENTRY_TXD_ACK, &desc.flags); |
| } else |
| __clear_bit(ENTRY_TXD_ACK, &desc.flags); |
| if (control->rts_cts_rate) |
| tx_rate = control->rts_cts_rate; |
| } |
| |
| /* |
| * Check for OFDM |
| */ |
| if (DEVICE_GET_RATE_FIELD(tx_rate, RATEMASK) & DEV_OFDM_RATEMASK) |
| __set_bit(ENTRY_TXD_OFDM_RATE, &desc.flags); |
| |
| /* |
| * Check if more fragments are pending |
| */ |
| if (ieee80211_get_morefrag(ieee80211hdr)) { |
| __set_bit(ENTRY_TXD_BURST, &desc.flags); |
| __set_bit(ENTRY_TXD_MORE_FRAG, &desc.flags); |
| } |
| |
| /* |
| * Beacons and probe responses require the tsf timestamp |
| * to be inserted into the frame. |
| */ |
| if (control->queue == IEEE80211_TX_QUEUE_BEACON || |
| is_probe_resp(frame_control)) |
| __set_bit(ENTRY_TXD_REQ_TIMESTAMP, &desc.flags); |
| |
| /* |
| * Determine with what IFS priority this frame should be send. |
| * Set ifs to IFS_SIFS when the this is not the first fragment, |
| * or this fragment came after RTS/CTS. |
| */ |
| if ((seq_ctrl & IEEE80211_SCTL_FRAG) > 0 || |
| test_bit(ENTRY_TXD_RTS_FRAME, &desc.flags)) |
| desc.ifs = IFS_SIFS; |
| else |
| desc.ifs = IFS_BACKOFF; |
| |
| /* |
| * PLCP setup |
| * Length calculation depends on OFDM/CCK rate. |
| */ |
| desc.signal = DEVICE_GET_RATE_FIELD(tx_rate, PLCP); |
| desc.service = 0x04; |
| |
| length = skbdesc->data_len + FCS_LEN; |
| if (test_bit(ENTRY_TXD_OFDM_RATE, &desc.flags)) { |
| desc.length_high = (length >> 6) & 0x3f; |
| desc.length_low = length & 0x3f; |
| } else { |
| bitrate = DEVICE_GET_RATE_FIELD(tx_rate, RATE); |
| |
| /* |
| * Convert length to microseconds. |
| */ |
| residual = get_duration_res(length, bitrate); |
| duration = get_duration(length, bitrate); |
| |
| if (residual != 0) { |
| duration++; |
| |
| /* |
| * Check if we need to set the Length Extension |
| */ |
| if (bitrate == 110 && residual <= 30) |
| desc.service |= 0x80; |
| } |
| |
| desc.length_high = (duration >> 8) & 0xff; |
| desc.length_low = duration & 0xff; |
| |
| /* |
| * When preamble is enabled we should set the |
| * preamble bit for the signal. |
| */ |
| if (DEVICE_GET_RATE_FIELD(tx_rate, PREAMBLE)) |
| desc.signal |= 0x08; |
| } |
| |
| rt2x00dev->ops->lib->write_tx_desc(rt2x00dev, skb, &desc, control); |
| |
| /* |
| * Update ring entry. |
| */ |
| skbdesc->entry->skb = skb; |
| memcpy(&skbdesc->entry->tx_status.control, control, sizeof(*control)); |
| |
| /* |
| * The frame has been completely initialized and ready |
| * for sending to the device. The caller will push the |
| * frame to the device, but we are going to push the |
| * frame to debugfs here. |
| */ |
| skbdesc->frame_type = DUMP_FRAME_TX; |
| rt2x00debug_dump_frame(rt2x00dev, skb); |
| } |
| EXPORT_SYMBOL_GPL(rt2x00lib_write_tx_desc); |
| |
| /* |
| * Driver initialization handlers. |
| */ |
| static void rt2x00lib_channel(struct ieee80211_channel *entry, |
| const int channel, const int tx_power, |
| const int value) |
| { |
| entry->chan = channel; |
| if (channel <= 14) |
| entry->freq = 2407 + (5 * channel); |
| else |
| entry->freq = 5000 + (5 * channel); |
| entry->val = value; |
| entry->flag = |
| IEEE80211_CHAN_W_IBSS | |
| IEEE80211_CHAN_W_ACTIVE_SCAN | |
| IEEE80211_CHAN_W_SCAN; |
| entry->power_level = tx_power; |
| entry->antenna_max = 0xff; |
| } |
| |
| static void rt2x00lib_rate(struct ieee80211_rate *entry, |
| const int rate, const int mask, |
| const int plcp, const int flags) |
| { |
| entry->rate = rate; |
| entry->val = |
| DEVICE_SET_RATE_FIELD(rate, RATE) | |
| DEVICE_SET_RATE_FIELD(mask, RATEMASK) | |
| DEVICE_SET_RATE_FIELD(plcp, PLCP); |
| entry->flags = flags; |
| entry->val2 = entry->val; |
| if (entry->flags & IEEE80211_RATE_PREAMBLE2) |
| entry->val2 |= DEVICE_SET_RATE_FIELD(1, PREAMBLE); |
| entry->min_rssi_ack = 0; |
| entry->min_rssi_ack_delta = 0; |
| } |
| |
| static int rt2x00lib_probe_hw_modes(struct rt2x00_dev *rt2x00dev, |
| struct hw_mode_spec *spec) |
| { |
| struct ieee80211_hw *hw = rt2x00dev->hw; |
| struct ieee80211_hw_mode *hwmodes; |
| struct ieee80211_channel *channels; |
| struct ieee80211_rate *rates; |
| unsigned int i; |
| unsigned char tx_power; |
| |
| hwmodes = kzalloc(sizeof(*hwmodes) * spec->num_modes, GFP_KERNEL); |
| if (!hwmodes) |
| goto exit; |
| |
| channels = kzalloc(sizeof(*channels) * spec->num_channels, GFP_KERNEL); |
| if (!channels) |
| goto exit_free_modes; |
| |
| rates = kzalloc(sizeof(*rates) * spec->num_rates, GFP_KERNEL); |
| if (!rates) |
| goto exit_free_channels; |
| |
| /* |
| * Initialize Rate list. |
| */ |
| rt2x00lib_rate(&rates[0], 10, DEV_RATEMASK_1MB, |
| 0x00, IEEE80211_RATE_CCK); |
| rt2x00lib_rate(&rates[1], 20, DEV_RATEMASK_2MB, |
| 0x01, IEEE80211_RATE_CCK_2); |
| rt2x00lib_rate(&rates[2], 55, DEV_RATEMASK_5_5MB, |
| 0x02, IEEE80211_RATE_CCK_2); |
| rt2x00lib_rate(&rates[3], 110, DEV_RATEMASK_11MB, |
| 0x03, IEEE80211_RATE_CCK_2); |
| |
| if (spec->num_rates > 4) { |
| rt2x00lib_rate(&rates[4], 60, DEV_RATEMASK_6MB, |
| 0x0b, IEEE80211_RATE_OFDM); |
| rt2x00lib_rate(&rates[5], 90, DEV_RATEMASK_9MB, |
| 0x0f, IEEE80211_RATE_OFDM); |
| rt2x00lib_rate(&rates[6], 120, DEV_RATEMASK_12MB, |
| 0x0a, IEEE80211_RATE_OFDM); |
| rt2x00lib_rate(&rates[7], 180, DEV_RATEMASK_18MB, |
| 0x0e, IEEE80211_RATE_OFDM); |
| rt2x00lib_rate(&rates[8], 240, DEV_RATEMASK_24MB, |
| 0x09, IEEE80211_RATE_OFDM); |
| rt2x00lib_rate(&rates[9], 360, DEV_RATEMASK_36MB, |
| 0x0d, IEEE80211_RATE_OFDM); |
| rt2x00lib_rate(&rates[10], 480, DEV_RATEMASK_48MB, |
| 0x08, IEEE80211_RATE_OFDM); |
| rt2x00lib_rate(&rates[11], 540, DEV_RATEMASK_54MB, |
| 0x0c, IEEE80211_RATE_OFDM); |
| } |
| |
| /* |
| * Initialize Channel list. |
| */ |
| for (i = 0; i < spec->num_channels; i++) { |
| if (spec->channels[i].channel <= 14) |
| tx_power = spec->tx_power_bg[i]; |
| else if (spec->tx_power_a) |
| tx_power = spec->tx_power_a[i]; |
| else |
| tx_power = spec->tx_power_default; |
| |
| rt2x00lib_channel(&channels[i], |
| spec->channels[i].channel, tx_power, i); |
| } |
| |
| /* |
| * Intitialize 802.11b |
| * Rates: CCK. |
| * Channels: OFDM. |
| */ |
| if (spec->num_modes > HWMODE_B) { |
| hwmodes[HWMODE_B].mode = MODE_IEEE80211B; |
| hwmodes[HWMODE_B].num_channels = 14; |
| hwmodes[HWMODE_B].num_rates = 4; |
| hwmodes[HWMODE_B].channels = channels; |
| hwmodes[HWMODE_B].rates = rates; |
| } |
| |
| /* |
| * Intitialize 802.11g |
| * Rates: CCK, OFDM. |
| * Channels: OFDM. |
| */ |
| if (spec->num_modes > HWMODE_G) { |
| hwmodes[HWMODE_G].mode = MODE_IEEE80211G; |
| hwmodes[HWMODE_G].num_channels = 14; |
| hwmodes[HWMODE_G].num_rates = spec->num_rates; |
| hwmodes[HWMODE_G].channels = channels; |
| hwmodes[HWMODE_G].rates = rates; |
| } |
| |
| /* |
| * Intitialize 802.11a |
| * Rates: OFDM. |
| * Channels: OFDM, UNII, HiperLAN2. |
| */ |
| if (spec->num_modes > HWMODE_A) { |
| hwmodes[HWMODE_A].mode = MODE_IEEE80211A; |
| hwmodes[HWMODE_A].num_channels = spec->num_channels - 14; |
| hwmodes[HWMODE_A].num_rates = spec->num_rates - 4; |
| hwmodes[HWMODE_A].channels = &channels[14]; |
| hwmodes[HWMODE_A].rates = &rates[4]; |
| } |
| |
| if (spec->num_modes > HWMODE_G && |
| ieee80211_register_hwmode(hw, &hwmodes[HWMODE_G])) |
| goto exit_free_rates; |
| |
| if (spec->num_modes > HWMODE_B && |
| ieee80211_register_hwmode(hw, &hwmodes[HWMODE_B])) |
| goto exit_free_rates; |
| |
| if (spec->num_modes > HWMODE_A && |
| ieee80211_register_hwmode(hw, &hwmodes[HWMODE_A])) |
| goto exit_free_rates; |
| |
| rt2x00dev->hwmodes = hwmodes; |
| |
| return 0; |
| |
| exit_free_rates: |
| kfree(rates); |
| |
| exit_free_channels: |
| kfree(channels); |
| |
| exit_free_modes: |
| kfree(hwmodes); |
| |
| exit: |
| ERROR(rt2x00dev, "Allocation ieee80211 modes failed.\n"); |
| return -ENOMEM; |
| } |
| |
| static void rt2x00lib_remove_hw(struct rt2x00_dev *rt2x00dev) |
| { |
| if (test_bit(DEVICE_REGISTERED_HW, &rt2x00dev->flags)) |
| ieee80211_unregister_hw(rt2x00dev->hw); |
| |
| if (likely(rt2x00dev->hwmodes)) { |
| kfree(rt2x00dev->hwmodes->channels); |
| kfree(rt2x00dev->hwmodes->rates); |
| kfree(rt2x00dev->hwmodes); |
| rt2x00dev->hwmodes = NULL; |
| } |
| } |
| |
| static int rt2x00lib_probe_hw(struct rt2x00_dev *rt2x00dev) |
| { |
| struct hw_mode_spec *spec = &rt2x00dev->spec; |
| int status; |
| |
| /* |
| * Initialize HW modes. |
| */ |
| status = rt2x00lib_probe_hw_modes(rt2x00dev, spec); |
| if (status) |
| return status; |
| |
| /* |
| * Register HW. |
| */ |
| status = ieee80211_register_hw(rt2x00dev->hw); |
| if (status) { |
| rt2x00lib_remove_hw(rt2x00dev); |
| return status; |
| } |
| |
| __set_bit(DEVICE_REGISTERED_HW, &rt2x00dev->flags); |
| |
| return 0; |
| } |
| |
| /* |
| * Initialization/uninitialization handlers. |
| */ |
| static int rt2x00lib_alloc_entries(struct data_ring *ring, |
| const u16 max_entries, const u16 data_size, |
| const u16 desc_size) |
| { |
| struct data_entry *entry; |
| unsigned int i; |
| |
| ring->stats.limit = max_entries; |
| ring->data_size = data_size; |
| ring->desc_size = desc_size; |
| |
| /* |
| * Allocate all ring entries. |
| */ |
| entry = kzalloc(ring->stats.limit * sizeof(*entry), GFP_KERNEL); |
| if (!entry) |
| return -ENOMEM; |
| |
| for (i = 0; i < ring->stats.limit; i++) { |
| entry[i].flags = 0; |
| entry[i].ring = ring; |
| entry[i].skb = NULL; |
| entry[i].entry_idx = i; |
| } |
| |
| ring->entry = entry; |
| |
| return 0; |
| } |
| |
| static int rt2x00lib_alloc_ring_entries(struct rt2x00_dev *rt2x00dev) |
| { |
| struct data_ring *ring; |
| |
| /* |
| * Allocate the RX ring. |
| */ |
| if (rt2x00lib_alloc_entries(rt2x00dev->rx, RX_ENTRIES, DATA_FRAME_SIZE, |
| rt2x00dev->ops->rxd_size)) |
| return -ENOMEM; |
| |
| /* |
| * First allocate the TX rings. |
| */ |
| txring_for_each(rt2x00dev, ring) { |
| if (rt2x00lib_alloc_entries(ring, TX_ENTRIES, DATA_FRAME_SIZE, |
| rt2x00dev->ops->txd_size)) |
| return -ENOMEM; |
| } |
| |
| if (!test_bit(DRIVER_REQUIRE_BEACON_RING, &rt2x00dev->flags)) |
| return 0; |
| |
| /* |
| * Allocate the BEACON ring. |
| */ |
| if (rt2x00lib_alloc_entries(&rt2x00dev->bcn[0], BEACON_ENTRIES, |
| MGMT_FRAME_SIZE, rt2x00dev->ops->txd_size)) |
| return -ENOMEM; |
| |
| /* |
| * Allocate the Atim ring. |
| */ |
| if (rt2x00lib_alloc_entries(&rt2x00dev->bcn[1], ATIM_ENTRIES, |
| DATA_FRAME_SIZE, rt2x00dev->ops->txd_size)) |
| return -ENOMEM; |
| |
| return 0; |
| } |
| |
| static void rt2x00lib_free_ring_entries(struct rt2x00_dev *rt2x00dev) |
| { |
| struct data_ring *ring; |
| |
| ring_for_each(rt2x00dev, ring) { |
| kfree(ring->entry); |
| ring->entry = NULL; |
| } |
| } |
| |
| static void rt2x00lib_uninitialize(struct rt2x00_dev *rt2x00dev) |
| { |
| if (!__test_and_clear_bit(DEVICE_INITIALIZED, &rt2x00dev->flags)) |
| return; |
| |
| /* |
| * Unregister extra components. |
| */ |
| rt2x00rfkill_unregister(rt2x00dev); |
| |
| /* |
| * Allow the HW to uninitialize. |
| */ |
| rt2x00dev->ops->lib->uninitialize(rt2x00dev); |
| |
| /* |
| * Free allocated ring entries. |
| */ |
| rt2x00lib_free_ring_entries(rt2x00dev); |
| } |
| |
| static int rt2x00lib_initialize(struct rt2x00_dev *rt2x00dev) |
| { |
| int status; |
| |
| if (test_bit(DEVICE_INITIALIZED, &rt2x00dev->flags)) |
| return 0; |
| |
| /* |
| * Allocate all ring entries. |
| */ |
| status = rt2x00lib_alloc_ring_entries(rt2x00dev); |
| if (status) { |
| ERROR(rt2x00dev, "Ring entries allocation failed.\n"); |
| return status; |
| } |
| |
| /* |
| * Initialize the device. |
| */ |
| status = rt2x00dev->ops->lib->initialize(rt2x00dev); |
| if (status) |
| goto exit; |
| |
| __set_bit(DEVICE_INITIALIZED, &rt2x00dev->flags); |
| |
| /* |
| * Register the extra components. |
| */ |
| rt2x00rfkill_register(rt2x00dev); |
| |
| return 0; |
| |
| exit: |
| rt2x00lib_free_ring_entries(rt2x00dev); |
| |
| return status; |
| } |
| |
| int rt2x00lib_start(struct rt2x00_dev *rt2x00dev) |
| { |
| int retval; |
| |
| if (test_bit(DEVICE_STARTED, &rt2x00dev->flags)) |
| return 0; |
| |
| /* |
| * If this is the first interface which is added, |
| * we should load the firmware now. |
| */ |
| if (test_bit(DRIVER_REQUIRE_FIRMWARE, &rt2x00dev->flags)) { |
| retval = rt2x00lib_load_firmware(rt2x00dev); |
| if (retval) |
| return retval; |
| } |
| |
| /* |
| * Initialize the device. |
| */ |
| retval = rt2x00lib_initialize(rt2x00dev); |
| if (retval) |
| return retval; |
| |
| /* |
| * Enable radio. |
| */ |
| retval = rt2x00lib_enable_radio(rt2x00dev); |
| if (retval) { |
| rt2x00lib_uninitialize(rt2x00dev); |
| return retval; |
| } |
| |
| __set_bit(DEVICE_STARTED, &rt2x00dev->flags); |
| |
| return 0; |
| } |
| |
| void rt2x00lib_stop(struct rt2x00_dev *rt2x00dev) |
| { |
| if (!test_bit(DEVICE_STARTED, &rt2x00dev->flags)) |
| return; |
| |
| /* |
| * Perhaps we can add something smarter here, |
| * but for now just disabling the radio should do. |
| */ |
| rt2x00lib_disable_radio(rt2x00dev); |
| |
| __clear_bit(DEVICE_STARTED, &rt2x00dev->flags); |
| } |
| |
| /* |
| * driver allocation handlers. |
| */ |
| static int rt2x00lib_alloc_rings(struct rt2x00_dev *rt2x00dev) |
| { |
| struct data_ring *ring; |
| unsigned int index; |
| |
| /* |
| * We need the following rings: |
| * RX: 1 |
| * TX: hw->queues |
| * Beacon: 1 (if required) |
| * Atim: 1 (if required) |
| */ |
| rt2x00dev->data_rings = 1 + rt2x00dev->hw->queues + |
| (2 * test_bit(DRIVER_REQUIRE_BEACON_RING, &rt2x00dev->flags)); |
| |
| ring = kzalloc(rt2x00dev->data_rings * sizeof(*ring), GFP_KERNEL); |
| if (!ring) { |
| ERROR(rt2x00dev, "Ring allocation failed.\n"); |
| return -ENOMEM; |
| } |
| |
| /* |
| * Initialize pointers |
| */ |
| rt2x00dev->rx = ring; |
| rt2x00dev->tx = &rt2x00dev->rx[1]; |
| if (test_bit(DRIVER_REQUIRE_BEACON_RING, &rt2x00dev->flags)) |
| rt2x00dev->bcn = &rt2x00dev->tx[rt2x00dev->hw->queues]; |
| |
| /* |
| * Initialize ring parameters. |
| * RX: queue_idx = 0 |
| * TX: queue_idx = IEEE80211_TX_QUEUE_DATA0 + index |
| * TX: cw_min: 2^5 = 32. |
| * TX: cw_max: 2^10 = 1024. |
| */ |
| rt2x00dev->rx->rt2x00dev = rt2x00dev; |
| rt2x00dev->rx->queue_idx = 0; |
| |
| index = IEEE80211_TX_QUEUE_DATA0; |
| txring_for_each(rt2x00dev, ring) { |
| ring->rt2x00dev = rt2x00dev; |
| ring->queue_idx = index++; |
| ring->tx_params.aifs = 2; |
| ring->tx_params.cw_min = 5; |
| ring->tx_params.cw_max = 10; |
| } |
| |
| return 0; |
| } |
| |
| static void rt2x00lib_free_rings(struct rt2x00_dev *rt2x00dev) |
| { |
| kfree(rt2x00dev->rx); |
| rt2x00dev->rx = NULL; |
| rt2x00dev->tx = NULL; |
| rt2x00dev->bcn = NULL; |
| } |
| |
| int rt2x00lib_probe_dev(struct rt2x00_dev *rt2x00dev) |
| { |
| int retval = -ENOMEM; |
| |
| /* |
| * Let the driver probe the device to detect the capabilities. |
| */ |
| retval = rt2x00dev->ops->lib->probe_hw(rt2x00dev); |
| if (retval) { |
| ERROR(rt2x00dev, "Failed to allocate device.\n"); |
| goto exit; |
| } |
| |
| /* |
| * Initialize configuration work. |
| */ |
| INIT_WORK(&rt2x00dev->beacon_work, rt2x00lib_beacondone_scheduled); |
| INIT_WORK(&rt2x00dev->filter_work, rt2x00lib_packetfilter_scheduled); |
| INIT_WORK(&rt2x00dev->config_work, rt2x00lib_configuration_scheduled); |
| INIT_DELAYED_WORK(&rt2x00dev->link.work, rt2x00lib_link_tuner); |
| |
| /* |
| * Reset current working type. |
| */ |
| rt2x00dev->interface.type = IEEE80211_IF_TYPE_INVALID; |
| |
| /* |
| * Allocate ring array. |
| */ |
| retval = rt2x00lib_alloc_rings(rt2x00dev); |
| if (retval) |
| goto exit; |
| |
| /* |
| * Initialize ieee80211 structure. |
| */ |
| retval = rt2x00lib_probe_hw(rt2x00dev); |
| if (retval) { |
| ERROR(rt2x00dev, "Failed to initialize hw.\n"); |
| goto exit; |
| } |
| |
| /* |
| * Register extra components. |
| */ |
| rt2x00rfkill_allocate(rt2x00dev); |
| rt2x00debug_register(rt2x00dev); |
| |
| __set_bit(DEVICE_PRESENT, &rt2x00dev->flags); |
| |
| return 0; |
| |
| exit: |
| rt2x00lib_remove_dev(rt2x00dev); |
| |
| return retval; |
| } |
| EXPORT_SYMBOL_GPL(rt2x00lib_probe_dev); |
| |
| void rt2x00lib_remove_dev(struct rt2x00_dev *rt2x00dev) |
| { |
| __clear_bit(DEVICE_PRESENT, &rt2x00dev->flags); |
| |
| /* |
| * Disable radio. |
| */ |
| rt2x00lib_disable_radio(rt2x00dev); |
| |
| /* |
| * Uninitialize device. |
| */ |
| rt2x00lib_uninitialize(rt2x00dev); |
| |
| /* |
| * Free extra components |
| */ |
| rt2x00debug_deregister(rt2x00dev); |
| rt2x00rfkill_free(rt2x00dev); |
| |
| /* |
| * Free ieee80211_hw memory. |
| */ |
| rt2x00lib_remove_hw(rt2x00dev); |
| |
| /* |
| * Free firmware image. |
| */ |
| rt2x00lib_free_firmware(rt2x00dev); |
| |
| /* |
| * Free ring structures. |
| */ |
| rt2x00lib_free_rings(rt2x00dev); |
| } |
| EXPORT_SYMBOL_GPL(rt2x00lib_remove_dev); |
| |
| /* |
| * Device state handlers |
| */ |
| #ifdef CONFIG_PM |
| int rt2x00lib_suspend(struct rt2x00_dev *rt2x00dev, pm_message_t state) |
| { |
| int retval; |
| |
| NOTICE(rt2x00dev, "Going to sleep.\n"); |
| __clear_bit(DEVICE_PRESENT, &rt2x00dev->flags); |
| |
| /* |
| * Only continue if mac80211 has open interfaces. |
| */ |
| if (!test_bit(DEVICE_STARTED, &rt2x00dev->flags)) |
| goto exit; |
| __set_bit(DEVICE_STARTED_SUSPEND, &rt2x00dev->flags); |
| |
| /* |
| * Disable radio. |
| */ |
| rt2x00lib_stop(rt2x00dev); |
| rt2x00lib_uninitialize(rt2x00dev); |
| |
| /* |
| * Suspend/disable extra components. |
| */ |
| rt2x00rfkill_suspend(rt2x00dev); |
| rt2x00debug_deregister(rt2x00dev); |
| |
| exit: |
| /* |
| * Set device mode to sleep for power management, |
| * on some hardware this call seems to consistently fail. |
| * From the specifications it is hard to tell why it fails, |
| * and if this is a "bad thing". |
| * Overall it is safe to just ignore the failure and |
| * continue suspending. The only downside is that the |
| * device will not be in optimal power save mode, but with |
| * the radio and the other components already disabled the |
| * device is as good as disabled. |
| */ |
| retval = rt2x00dev->ops->lib->set_device_state(rt2x00dev, STATE_SLEEP); |
| if (retval) |
| WARNING(rt2x00dev, "Device failed to enter sleep state, " |
| "continue suspending.\n"); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(rt2x00lib_suspend); |
| |
| int rt2x00lib_resume(struct rt2x00_dev *rt2x00dev) |
| { |
| struct interface *intf = &rt2x00dev->interface; |
| int retval; |
| |
| NOTICE(rt2x00dev, "Waking up.\n"); |
| |
| /* |
| * Restore/enable extra components. |
| */ |
| rt2x00debug_register(rt2x00dev); |
| rt2x00rfkill_resume(rt2x00dev); |
| |
| /* |
| * Only continue if mac80211 had open interfaces. |
| */ |
| if (!__test_and_clear_bit(DEVICE_STARTED_SUSPEND, &rt2x00dev->flags)) |
| return 0; |
| |
| /* |
| * Reinitialize device and all active interfaces. |
| */ |
| retval = rt2x00lib_start(rt2x00dev); |
| if (retval) |
| goto exit; |
| |
| /* |
| * Reconfigure device. |
| */ |
| rt2x00lib_config(rt2x00dev, &rt2x00dev->hw->conf, 1); |
| if (!rt2x00dev->hw->conf.radio_enabled) |
| rt2x00lib_disable_radio(rt2x00dev); |
| |
| rt2x00lib_config_mac_addr(rt2x00dev, intf->mac); |
| rt2x00lib_config_bssid(rt2x00dev, intf->bssid); |
| rt2x00lib_config_type(rt2x00dev, intf->type); |
| |
| /* |
| * We are ready again to receive requests from mac80211. |
| */ |
| __set_bit(DEVICE_PRESENT, &rt2x00dev->flags); |
| |
| /* |
| * It is possible that during that mac80211 has attempted |
| * to send frames while we were suspending or resuming. |
| * In that case we have disabled the TX queue and should |
| * now enable it again |
| */ |
| ieee80211_start_queues(rt2x00dev->hw); |
| |
| /* |
| * When in Master or Ad-hoc mode, |
| * restart Beacon transmitting by faking a beacondone event. |
| */ |
| if (intf->type == IEEE80211_IF_TYPE_AP || |
| intf->type == IEEE80211_IF_TYPE_IBSS) |
| rt2x00lib_beacondone(rt2x00dev); |
| |
| return 0; |
| |
| exit: |
| rt2x00lib_disable_radio(rt2x00dev); |
| rt2x00lib_uninitialize(rt2x00dev); |
| rt2x00debug_deregister(rt2x00dev); |
| |
| return retval; |
| } |
| EXPORT_SYMBOL_GPL(rt2x00lib_resume); |
| #endif /* CONFIG_PM */ |
| |
| /* |
| * rt2x00lib module information. |
| */ |
| MODULE_AUTHOR(DRV_PROJECT); |
| MODULE_VERSION(DRV_VERSION); |
| MODULE_DESCRIPTION("rt2x00 library"); |
| MODULE_LICENSE("GPL"); |