Merge ath-next from git://git.kernel.org/pub/scm/linux/kernel/git/kvalo/ath.git

ath.git patches for 4.10. Major changes:

ath9k

* add device tree bindings
* switch to use mac80211 intermediate software queues to reduce
  latency and fix bufferbloat
This commit is contained in:
Kalle Valo 2016-11-23 14:51:38 +02:00
commit e644b88e0a
17 changed files with 328 additions and 275 deletions

View File

@ -0,0 +1,48 @@
* Qualcomm Atheros ath9k wireless devices
This node provides properties for configuring the ath9k wireless device. The
node is expected to be specified as a child node of the PCI controller to
which the wireless chip is connected.
Required properties:
- compatible: For PCI and PCIe devices this should be an identifier following
the format as defined in "PCI Bus Binding to Open Firmware"
Revision 2.1. One of the possible formats is "pciVVVV,DDDD"
where VVVV is the PCI vendor ID and DDDD is PCI device ID.
Typically QCA's PCI vendor ID 168c is used while the PCI device
ID depends on the chipset - see the following (possibly
incomplete) list:
- 0023 for AR5416
- 0024 for AR5418
- 0027 for AR9160
- 0029 for AR9220 and AR9223
- 002a for AR9280 and AR9283
- 002b for AR9285
- 002c for AR2427
- 002d for AR9227
- 002e for AR9287
- 0030 for AR9380, AR9381 and AR9382
- 0032 for AR9485
- 0033 for AR9580 and AR9590
- 0034 for AR9462
- 0036 for AR9565
- 0037 for AR9485
- reg: Address and length of the register set for the device.
Optional properties:
- qca,no-eeprom: Indicates that there is no physical EEPROM connected to the
ath9k wireless chip (in this case the calibration /
EEPROM data will be loaded from userspace using the
kernel firmware loader).
- mac-address: See ethernet.txt in the parent directory
- local-mac-address: See ethernet.txt in the parent directory
In this example, the node is defined as child node of the PCI controller:
&pci0 {
wifi@168c,002d {
compatible = "pci168c,002d";
reg = <0x7000 0 0 0 0x1000>;
qca,no-eeprom;
};
};

View File

@ -327,4 +327,10 @@ static inline const char *ath_opmode_to_string(enum nl80211_iftype opmode)
}
#endif
extern const char *ath_bus_type_strings[];
static inline const char *ath_bus_type_to_string(enum ath_bus_type bustype)
{
return ath_bus_type_strings[bustype];
}
#endif /* ATH_H */

View File

@ -556,10 +556,8 @@ enum ath10k_fw_features {
*/
ATH10K_FW_FEATURE_BTCOEX_PARAM = 14,
/* Older firmware with HTT delivers incorrect tx status for null func
* frames to driver, but this fixed in 10.2 and 10.4 firmware versions.
* Also this workaround results in reporting of incorrect null func
* status for 10.4. This flag is used to skip the workaround.
/* Unused flag and proven to be not working, enable this if you want
* to experiment sending NULL func data frames in HTT TX
*/
ATH10K_FW_FEATURE_SKIP_NULL_FUNC_WAR = 15,

View File

@ -229,6 +229,32 @@ void ath10k_htt_tx_free_msdu_id(struct ath10k_htt *htt, u16 msdu_id)
idr_remove(&htt->pending_tx, msdu_id);
}
static void ath10k_htt_tx_free_cont_txbuf(struct ath10k_htt *htt)
{
struct ath10k *ar = htt->ar;
size_t size;
if (!htt->txbuf.vaddr)
return;
size = htt->max_num_pending_tx * sizeof(struct ath10k_htt_txbuf);
dma_free_coherent(ar->dev, size, htt->txbuf.vaddr, htt->txbuf.paddr);
}
static int ath10k_htt_tx_alloc_cont_txbuf(struct ath10k_htt *htt)
{
struct ath10k *ar = htt->ar;
size_t size;
size = htt->max_num_pending_tx * sizeof(struct ath10k_htt_txbuf);
htt->txbuf.vaddr = dma_alloc_coherent(ar->dev, size, &htt->txbuf.paddr,
GFP_KERNEL);
if (!htt->txbuf.vaddr)
return -ENOMEM;
return 0;
}
static void ath10k_htt_tx_free_cont_frag_desc(struct ath10k_htt *htt)
{
size_t size;
@ -256,10 +282,8 @@ static int ath10k_htt_tx_alloc_cont_frag_desc(struct ath10k_htt *htt)
htt->frag_desc.vaddr = dma_alloc_coherent(ar->dev, size,
&htt->frag_desc.paddr,
GFP_KERNEL);
if (!htt->frag_desc.vaddr) {
ath10k_err(ar, "failed to alloc fragment desc memory\n");
if (!htt->frag_desc.vaddr)
return -ENOMEM;
}
return 0;
}
@ -310,10 +334,26 @@ static int ath10k_htt_tx_alloc_txq(struct ath10k_htt *htt)
return 0;
}
static void ath10k_htt_tx_free_txdone_fifo(struct ath10k_htt *htt)
{
WARN_ON(!kfifo_is_empty(&htt->txdone_fifo));
kfifo_free(&htt->txdone_fifo);
}
static int ath10k_htt_tx_alloc_txdone_fifo(struct ath10k_htt *htt)
{
int ret;
size_t size;
size = roundup_pow_of_two(htt->max_num_pending_tx);
ret = kfifo_alloc(&htt->txdone_fifo, size, GFP_KERNEL);
return ret;
}
int ath10k_htt_tx_alloc(struct ath10k_htt *htt)
{
struct ath10k *ar = htt->ar;
int ret, size;
int ret;
ath10k_dbg(ar, ATH10K_DBG_BOOT, "htt tx max num pending tx %d\n",
htt->max_num_pending_tx);
@ -321,13 +361,9 @@ int ath10k_htt_tx_alloc(struct ath10k_htt *htt)
spin_lock_init(&htt->tx_lock);
idr_init(&htt->pending_tx);
size = htt->max_num_pending_tx * sizeof(struct ath10k_htt_txbuf);
htt->txbuf.vaddr = dma_alloc_coherent(ar->dev, size,
&htt->txbuf.paddr,
GFP_KERNEL);
if (!htt->txbuf.vaddr) {
ath10k_err(ar, "failed to alloc tx buffer\n");
ret = -ENOMEM;
ret = ath10k_htt_tx_alloc_cont_txbuf(htt);
if (ret) {
ath10k_err(ar, "failed to alloc cont tx buffer: %d\n", ret);
goto free_idr_pending_tx;
}
@ -343,8 +379,7 @@ int ath10k_htt_tx_alloc(struct ath10k_htt *htt)
goto free_frag_desc;
}
size = roundup_pow_of_two(htt->max_num_pending_tx);
ret = kfifo_alloc(&htt->txdone_fifo, size, GFP_KERNEL);
ret = ath10k_htt_tx_alloc_txdone_fifo(htt);
if (ret) {
ath10k_err(ar, "failed to alloc txdone fifo: %d\n", ret);
goto free_txq;
@ -359,10 +394,7 @@ free_frag_desc:
ath10k_htt_tx_free_cont_frag_desc(htt);
free_txbuf:
size = htt->max_num_pending_tx *
sizeof(struct ath10k_htt_txbuf);
dma_free_coherent(htt->ar->dev, size, htt->txbuf.vaddr,
htt->txbuf.paddr);
ath10k_htt_tx_free_cont_txbuf(htt);
free_idr_pending_tx:
idr_destroy(&htt->pending_tx);
@ -388,22 +420,13 @@ static int ath10k_htt_tx_clean_up_pending(int msdu_id, void *skb, void *ctx)
void ath10k_htt_tx_free(struct ath10k_htt *htt)
{
int size;
idr_for_each(&htt->pending_tx, ath10k_htt_tx_clean_up_pending, htt->ar);
idr_destroy(&htt->pending_tx);
if (htt->txbuf.vaddr) {
size = htt->max_num_pending_tx *
sizeof(struct ath10k_htt_txbuf);
dma_free_coherent(htt->ar->dev, size, htt->txbuf.vaddr,
htt->txbuf.paddr);
}
ath10k_htt_tx_free_cont_txbuf(htt);
ath10k_htt_tx_free_txq(htt);
ath10k_htt_tx_free_cont_frag_desc(htt);
WARN_ON(!kfifo_is_empty(&htt->txdone_fifo));
kfifo_free(&htt->txdone_fifo);
ath10k_htt_tx_free_txdone_fifo(htt);
}
void ath10k_htt_htc_tx_complete(struct ath10k *ar, struct sk_buff *skb)

View File

@ -3257,8 +3257,6 @@ ath10k_mac_tx_h_get_txmode(struct ath10k *ar,
if (ar->htt.target_version_major < 3 &&
(ieee80211_is_nullfunc(fc) || ieee80211_is_qos_nullfunc(fc)) &&
!test_bit(ATH10K_FW_FEATURE_HAS_WMI_MGMT_TX,
ar->running_fw->fw_file.fw_features) &&
!test_bit(ATH10K_FW_FEATURE_SKIP_NULL_FUNC_WAR,
ar->running_fw->fw_file.fw_features))
return ATH10K_HW_TXRX_MGMT;

View File

@ -338,7 +338,7 @@ static ssize_t write_file_spec_scan_ctl(struct file *file,
} else {
res = -EINVAL;
}
} else if (strncmp("background", buf, 9) == 0) {
} else if (strncmp("background", buf, 10) == 0) {
res = ath10k_spectral_scan_config(ar, SPECTRAL_BACKGROUND);
} else if (strncmp("manual", buf, 6) == 0) {
res = ath10k_spectral_scan_config(ar, SPECTRAL_MANUAL);

View File

@ -91,7 +91,6 @@ int ath_descdma_setup(struct ath_softc *sc, struct ath_descdma *dd,
#define ATH_RXBUF 512
#define ATH_TXBUF 512
#define ATH_TXBUF_RESERVE 5
#define ATH_MAX_QDEPTH (ATH_TXBUF / 4 - ATH_TXBUF_RESERVE)
#define ATH_TXMAXTRY 13
#define ATH_MAX_SW_RETRIES 30
@ -145,7 +144,7 @@ int ath_descdma_setup(struct ath_softc *sc, struct ath_descdma *dd,
#define BAW_WITHIN(_start, _bawsz, _seqno) \
((((_seqno) - (_start)) & 4095) < (_bawsz))
#define ATH_AN_2_TID(_an, _tidno) (&(_an)->tid[(_tidno)])
#define ATH_AN_2_TID(_an, _tidno) ath_node_to_tid(_an, _tidno)
#define IS_HT_RATE(rate) (rate & 0x80)
#define IS_CCK_RATE(rate) ((rate >= 0x18) && (rate <= 0x1e))
@ -164,7 +163,6 @@ struct ath_txq {
spinlock_t axq_lock;
u32 axq_depth;
u32 axq_ampdu_depth;
bool stopped;
bool axq_tx_inprogress;
struct list_head txq_fifo[ATH_TXFIFO_DEPTH];
u8 txq_headidx;
@ -232,7 +230,6 @@ struct ath_buf {
struct ath_atx_tid {
struct list_head list;
struct sk_buff_head buf_q;
struct sk_buff_head retry_q;
struct ath_node *an;
struct ath_txq *txq;
@ -247,13 +244,13 @@ struct ath_atx_tid {
s8 bar_index;
bool active;
bool clear_ps_filter;
bool has_queued;
};
struct ath_node {
struct ath_softc *sc;
struct ieee80211_sta *sta; /* station struct we're part of */
struct ieee80211_vif *vif; /* interface with which we're associated */
struct ath_atx_tid tid[IEEE80211_NUM_TIDS];
u16 maxampdu;
u8 mpdudensity;
@ -276,7 +273,6 @@ struct ath_tx_control {
struct ath_node *an;
struct ieee80211_sta *sta;
u8 paprd;
bool force_channel;
};
@ -293,7 +289,6 @@ struct ath_tx {
struct ath_descdma txdma;
struct ath_txq *txq_map[IEEE80211_NUM_ACS];
struct ath_txq *uapsdq;
u32 txq_max_pending[IEEE80211_NUM_ACS];
u16 max_aggr_framelen[IEEE80211_NUM_ACS][4][32];
};
@ -421,6 +416,22 @@ struct ath_offchannel {
int duration;
};
static inline struct ath_atx_tid *
ath_node_to_tid(struct ath_node *an, u8 tidno)
{
struct ieee80211_sta *sta = an->sta;
struct ieee80211_vif *vif = an->vif;
struct ieee80211_txq *txq;
BUG_ON(!vif);
if (sta)
txq = sta->txq[tidno % ARRAY_SIZE(sta->txq)];
else
txq = vif->txq;
return (struct ath_atx_tid *) txq->drv_priv;
}
#define case_rtn_string(val) case val: return #val
#define ath_for_each_chanctx(_sc, _ctx) \
@ -575,7 +586,6 @@ void ath_tx_edma_tasklet(struct ath_softc *sc);
int ath_tx_aggr_start(struct ath_softc *sc, struct ieee80211_sta *sta,
u16 tid, u16 *ssn);
void ath_tx_aggr_stop(struct ath_softc *sc, struct ieee80211_sta *sta, u16 tid);
void ath_tx_aggr_resume(struct ath_softc *sc, struct ieee80211_sta *sta, u16 tid);
void ath_tx_aggr_wakeup(struct ath_softc *sc, struct ath_node *an);
void ath_tx_aggr_sleep(struct ieee80211_sta *sta, struct ath_softc *sc,
@ -585,6 +595,7 @@ void ath9k_release_buffered_frames(struct ieee80211_hw *hw,
u16 tids, int nframes,
enum ieee80211_frame_release_type reason,
bool more_data);
void ath9k_wake_tx_queue(struct ieee80211_hw *hw, struct ieee80211_txq *queue);
/********/
/* VIFs */

View File

@ -1010,7 +1010,6 @@ static void ath_scan_send_probe(struct ath_softc *sc,
goto error;
txctl.txq = sc->tx.txq_map[IEEE80211_AC_VO];
txctl.force_channel = true;
if (ath_tx_start(sc->hw, skb, &txctl))
goto error;
@ -1133,7 +1132,6 @@ ath_chanctx_send_vif_ps_frame(struct ath_softc *sc, struct ath_vif *avp,
memset(&txctl, 0, sizeof(txctl));
txctl.txq = sc->tx.txq_map[IEEE80211_AC_VO];
txctl.sta = sta;
txctl.force_channel = true;
if (ath_tx_start(sc->hw, skb, &txctl)) {
ieee80211_free_txskb(sc->hw, skb);
return false;

View File

@ -600,7 +600,6 @@ static int read_file_xmit(struct seq_file *file, void *data)
PR("MPDUs XRetried: ", xretries);
PR("Aggregates: ", a_aggr);
PR("AMPDUs Queued HW:", a_queued_hw);
PR("AMPDUs Queued SW:", a_queued_sw);
PR("AMPDUs Completed:", a_completed);
PR("AMPDUs Retried: ", a_retries);
PR("AMPDUs XRetried: ", a_xretries);
@ -629,8 +628,7 @@ static void print_queue(struct ath_softc *sc, struct ath_txq *txq,
seq_printf(file, "%s: %d ", "qnum", txq->axq_qnum);
seq_printf(file, "%s: %2d ", "qdepth", txq->axq_depth);
seq_printf(file, "%s: %2d ", "ampdu-depth", txq->axq_ampdu_depth);
seq_printf(file, "%s: %3d ", "pending", txq->pending_frames);
seq_printf(file, "%s: %d\n", "stopped", txq->stopped);
seq_printf(file, "%s: %3d\n", "pending", txq->pending_frames);
ath_txq_unlock(sc, txq);
}
@ -1208,7 +1206,6 @@ static const char ath9k_gstrings_stats[][ETH_GSTRING_LEN] = {
AMKSTR(d_tx_mpdu_xretries),
AMKSTR(d_tx_aggregates),
AMKSTR(d_tx_ampdus_queued_hw),
AMKSTR(d_tx_ampdus_queued_sw),
AMKSTR(d_tx_ampdus_completed),
AMKSTR(d_tx_ampdu_retries),
AMKSTR(d_tx_ampdu_xretries),
@ -1288,7 +1285,6 @@ void ath9k_get_et_stats(struct ieee80211_hw *hw,
AWDATA(xretries);
AWDATA(a_aggr);
AWDATA(a_queued_hw);
AWDATA(a_queued_sw);
AWDATA(a_completed);
AWDATA(a_retries);
AWDATA(a_xretries);
@ -1346,14 +1342,6 @@ int ath9k_init_debug(struct ath_hw *ah)
read_file_xmit);
debugfs_create_devm_seqfile(sc->dev, "queues", sc->debug.debugfs_phy,
read_file_queues);
debugfs_create_u32("qlen_bk", S_IRUSR | S_IWUSR, sc->debug.debugfs_phy,
&sc->tx.txq_max_pending[IEEE80211_AC_BK]);
debugfs_create_u32("qlen_be", S_IRUSR | S_IWUSR, sc->debug.debugfs_phy,
&sc->tx.txq_max_pending[IEEE80211_AC_BE]);
debugfs_create_u32("qlen_vi", S_IRUSR | S_IWUSR, sc->debug.debugfs_phy,
&sc->tx.txq_max_pending[IEEE80211_AC_VI]);
debugfs_create_u32("qlen_vo", S_IRUSR | S_IWUSR, sc->debug.debugfs_phy,
&sc->tx.txq_max_pending[IEEE80211_AC_VO]);
debugfs_create_devm_seqfile(sc->dev, "misc", sc->debug.debugfs_phy,
read_file_misc);
debugfs_create_devm_seqfile(sc->dev, "reset", sc->debug.debugfs_phy,

View File

@ -147,7 +147,6 @@ struct ath_interrupt_stats {
* @completed: Total MPDUs (non-aggr) completed
* @a_aggr: Total no. of aggregates queued
* @a_queued_hw: Total AMPDUs queued to hardware
* @a_queued_sw: Total AMPDUs queued to software queues
* @a_completed: Total AMPDUs completed
* @a_retries: No. of AMPDUs retried (SW)
* @a_xretries: No. of AMPDUs dropped due to xretries
@ -174,7 +173,6 @@ struct ath_tx_stats {
u32 xretries;
u32 a_aggr;
u32 a_queued_hw;
u32 a_queued_sw;
u32 a_completed;
u32 a_retries;
u32 a_xretries;

View File

@ -52,8 +52,8 @@ static ssize_t read_file_node_aggr(struct file *file, char __user *user_buf,
"TID", "SEQ_START", "SEQ_NEXT", "BAW_SIZE",
"BAW_HEAD", "BAW_TAIL", "BAR_IDX", "SCHED", "PAUSED");
for (tidno = 0, tid = &an->tid[tidno];
tidno < IEEE80211_NUM_TIDS; tidno++, tid++) {
for (tidno = 0; tidno < IEEE80211_NUM_TIDS; tidno++) {
tid = ath_node_to_tid(an, tidno);
txq = tid->txq;
ath_txq_lock(sc, txq);
if (tid->active) {

View File

@ -244,8 +244,8 @@ int htc_connect_service(struct htc_target *target,
/* Find an available endpoint */
endpoint = get_next_avail_ep(target->endpoint);
if (!endpoint) {
dev_err(target->dev, "Endpoint is not available for"
"service %d\n", service_connreq->service_id);
dev_err(target->dev, "Endpoint is not available for service %d\n",
service_connreq->service_id);
return -EINVAL;
}
@ -382,7 +382,7 @@ static void ath9k_htc_fw_panic_report(struct htc_target *htc_handle,
break;
}
default:
dev_err(htc_handle->dev, "ath: uknown panic pattern!\n");
dev_err(htc_handle->dev, "ath: unknown panic pattern!\n");
break;
}
}

View File

@ -20,6 +20,8 @@
#include <linux/slab.h>
#include <linux/ath9k_platform.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_net.h>
#include <linux/relay.h>
#include <net/ieee80211_radiotap.h>
@ -358,7 +360,6 @@ static int ath9k_init_queues(struct ath_softc *sc)
for (i = 0; i < IEEE80211_NUM_ACS; i++) {
sc->tx.txq_map[i] = ath_txq_setup(sc, ATH9K_TX_QUEUE_DATA, i);
sc->tx.txq_map[i]->mac80211_qnum = i;
sc->tx.txq_max_pending[i] = ATH_MAX_QDEPTH;
}
return 0;
}
@ -555,6 +556,42 @@ static int ath9k_init_platform(struct ath_softc *sc)
return 0;
}
static int ath9k_of_init(struct ath_softc *sc)
{
struct device_node *np = sc->dev->of_node;
struct ath_hw *ah = sc->sc_ah;
struct ath_common *common = ath9k_hw_common(ah);
enum ath_bus_type bus_type = common->bus_ops->ath_bus_type;
const char *mac;
char eeprom_name[100];
int ret;
if (!of_device_is_available(np))
return 0;
ath_dbg(common, CONFIG, "parsing configuration from OF node\n");
if (of_property_read_bool(np, "qca,no-eeprom")) {
/* ath9k-eeprom-<bus>-<id>.bin */
scnprintf(eeprom_name, sizeof(eeprom_name),
"ath9k-eeprom-%s-%s.bin",
ath_bus_type_to_string(bus_type), dev_name(ah->dev));
ret = ath9k_eeprom_request(sc, eeprom_name);
if (ret)
return ret;
}
mac = of_get_mac_address(np);
if (mac)
ether_addr_copy(common->macaddr, mac);
ah->ah_flags &= ~AH_USE_EEPROM;
ah->ah_flags |= AH_NO_EEP_SWAP;
return 0;
}
static int ath9k_init_softc(u16 devid, struct ath_softc *sc,
const struct ath_bus_ops *bus_ops)
{
@ -611,6 +648,10 @@ static int ath9k_init_softc(u16 devid, struct ath_softc *sc,
if (ret)
return ret;
ret = ath9k_of_init(sc);
if (ret)
return ret;
if (ath9k_led_active_high != -1)
ah->config.led_active_high = ath9k_led_active_high == 1;
@ -877,6 +918,7 @@ static void ath9k_set_hw_capab(struct ath_softc *sc, struct ieee80211_hw *hw)
hw->max_rate_tries = 10;
hw->sta_data_size = sizeof(struct ath_node);
hw->vif_data_size = sizeof(struct ath_vif);
hw->txq_data_size = sizeof(struct ath_atx_tid);
hw->extra_tx_headroom = 4;
hw->wiphy->available_antennas_rx = BIT(ah->caps.max_rxchains) - 1;

View File

@ -1902,9 +1902,11 @@ static int ath9k_ampdu_action(struct ieee80211_hw *hw,
bool flush = false;
int ret = 0;
struct ieee80211_sta *sta = params->sta;
struct ath_node *an = (struct ath_node *)sta->drv_priv;
enum ieee80211_ampdu_mlme_action action = params->action;
u16 tid = params->tid;
u16 *ssn = &params->ssn;
struct ath_atx_tid *atid;
mutex_lock(&sc->mutex);
@ -1937,9 +1939,9 @@ static int ath9k_ampdu_action(struct ieee80211_hw *hw,
ath9k_ps_restore(sc);
break;
case IEEE80211_AMPDU_TX_OPERATIONAL:
ath9k_ps_wakeup(sc);
ath_tx_aggr_resume(sc, sta, tid);
ath9k_ps_restore(sc);
atid = ath_node_to_tid(an, tid);
atid->baw_size = IEEE80211_MIN_AMPDU_BUF <<
sta->ht_cap.ampdu_factor;
break;
default:
ath_err(ath9k_hw_common(sc->sc_ah), "Unknown AMPDU action\n");
@ -2701,4 +2703,5 @@ struct ieee80211_ops ath9k_ops = {
.sw_scan_start = ath9k_sw_scan_start,
.sw_scan_complete = ath9k_sw_scan_complete,
.get_txpower = ath9k_get_txpower,
.wake_tx_queue = ath9k_wake_tx_queue,
};

View File

@ -26,7 +26,6 @@ static const struct pci_device_id ath_pci_id_table[] = {
{ PCI_VDEVICE(ATHEROS, 0x0023) }, /* PCI */
{ PCI_VDEVICE(ATHEROS, 0x0024) }, /* PCI-E */
{ PCI_VDEVICE(ATHEROS, 0x0027) }, /* PCI */
{ PCI_VDEVICE(ATHEROS, 0x0029) }, /* PCI */
#ifdef CONFIG_ATH9K_PCOEM
/* Mini PCI AR9220 MB92 cards: Compex WLM200NX, Wistron DNMA-92 */
@ -37,7 +36,7 @@ static const struct pci_device_id ath_pci_id_table[] = {
.driver_data = ATH9K_PCI_LED_ACT_HI },
#endif
{ PCI_VDEVICE(ATHEROS, 0x002A) }, /* PCI-E */
{ PCI_VDEVICE(ATHEROS, 0x0029) }, /* PCI */
#ifdef CONFIG_ATH9K_PCOEM
{ PCI_DEVICE_SUB(PCI_VENDOR_ID_ATHEROS,
@ -85,7 +84,11 @@ static const struct pci_device_id ath_pci_id_table[] = {
0x10CF, /* Fujitsu */
0x1536),
.driver_data = ATH9K_PCI_D3_L1_WAR },
#endif
{ PCI_VDEVICE(ATHEROS, 0x002A) }, /* PCI-E */
#ifdef CONFIG_ATH9K_PCOEM
/* AR9285 card for Asus */
{ PCI_DEVICE_SUB(PCI_VENDOR_ID_ATHEROS,
0x002B,

View File

@ -67,6 +67,8 @@ static struct ath_buf *ath_tx_setup_buffer(struct ath_softc *sc,
struct ath_txq *txq,
struct ath_atx_tid *tid,
struct sk_buff *skb);
static int ath_tx_prepare(struct ieee80211_hw *hw, struct sk_buff *skb,
struct ath_tx_control *txctl);
enum {
MCS_HT20,
@ -137,6 +139,26 @@ static void ath_tx_queue_tid(struct ath_softc *sc, struct ath_txq *txq,
list_add_tail(&tid->list, list);
}
void ath9k_wake_tx_queue(struct ieee80211_hw *hw, struct ieee80211_txq *queue)
{
struct ath_softc *sc = hw->priv;
struct ath_common *common = ath9k_hw_common(sc->sc_ah);
struct ath_atx_tid *tid = (struct ath_atx_tid *) queue->drv_priv;
struct ath_txq *txq = tid->txq;
ath_dbg(common, QUEUE, "Waking TX queue: %pM (%d)\n",
queue->sta ? queue->sta->addr : queue->vif->addr,
tid->tidno);
ath_txq_lock(sc, txq);
tid->has_queued = true;
ath_tx_queue_tid(sc, txq, tid);
ath_txq_schedule(sc, txq);
ath_txq_unlock(sc, txq);
}
static struct ath_frame_info *get_frame_info(struct sk_buff *skb)
{
struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb);
@ -164,7 +186,6 @@ static void ath_set_rates(struct ieee80211_vif *vif, struct ieee80211_sta *sta,
static void ath_txq_skb_done(struct ath_softc *sc, struct ath_txq *txq,
struct sk_buff *skb)
{
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
struct ath_frame_info *fi = get_frame_info(skb);
int q = fi->txq;
@ -175,14 +196,6 @@ static void ath_txq_skb_done(struct ath_softc *sc, struct ath_txq *txq,
if (WARN_ON(--txq->pending_frames < 0))
txq->pending_frames = 0;
if (txq->stopped &&
txq->pending_frames < sc->tx.txq_max_pending[q]) {
if (ath9k_is_chanctx_enabled())
ieee80211_wake_queue(sc->hw, info->hw_queue);
else
ieee80211_wake_queue(sc->hw, q);
txq->stopped = false;
}
}
static struct ath_atx_tid *
@ -192,9 +205,48 @@ ath_get_skb_tid(struct ath_softc *sc, struct ath_node *an, struct sk_buff *skb)
return ATH_AN_2_TID(an, tidno);
}
static struct sk_buff *
ath_tid_pull(struct ath_atx_tid *tid)
{
struct ieee80211_txq *txq = container_of((void*)tid, struct ieee80211_txq, drv_priv);
struct ath_softc *sc = tid->an->sc;
struct ieee80211_hw *hw = sc->hw;
struct ath_tx_control txctl = {
.txq = tid->txq,
.sta = tid->an->sta,
};
struct sk_buff *skb;
struct ath_frame_info *fi;
int q;
if (!tid->has_queued)
return NULL;
skb = ieee80211_tx_dequeue(hw, txq);
if (!skb) {
tid->has_queued = false;
return NULL;
}
if (ath_tx_prepare(hw, skb, &txctl)) {
ieee80211_free_txskb(hw, skb);
return NULL;
}
q = skb_get_queue_mapping(skb);
if (tid->txq == sc->tx.txq_map[q]) {
fi = get_frame_info(skb);
fi->txq = q;
++tid->txq->pending_frames;
}
return skb;
}
static bool ath_tid_has_buffered(struct ath_atx_tid *tid)
{
return !skb_queue_empty(&tid->buf_q) || !skb_queue_empty(&tid->retry_q);
return !skb_queue_empty(&tid->retry_q) || tid->has_queued;
}
static struct sk_buff *ath_tid_dequeue(struct ath_atx_tid *tid)
@ -203,46 +255,11 @@ static struct sk_buff *ath_tid_dequeue(struct ath_atx_tid *tid)
skb = __skb_dequeue(&tid->retry_q);
if (!skb)
skb = __skb_dequeue(&tid->buf_q);
skb = ath_tid_pull(tid);
return skb;
}
/*
* ath_tx_tid_change_state:
* - clears a-mpdu flag of previous session
* - force sequence number allocation to fix next BlockAck Window
*/
static void
ath_tx_tid_change_state(struct ath_softc *sc, struct ath_atx_tid *tid)
{
struct ath_txq *txq = tid->txq;
struct ieee80211_tx_info *tx_info;
struct sk_buff *skb, *tskb;
struct ath_buf *bf;
struct ath_frame_info *fi;
skb_queue_walk_safe(&tid->buf_q, skb, tskb) {
fi = get_frame_info(skb);
bf = fi->bf;
tx_info = IEEE80211_SKB_CB(skb);
tx_info->flags &= ~IEEE80211_TX_CTL_AMPDU;
if (bf)
continue;
bf = ath_tx_setup_buffer(sc, txq, tid, skb);
if (!bf) {
__skb_unlink(skb, &tid->buf_q);
ath_txq_skb_done(sc, txq, skb);
ieee80211_free_txskb(sc->hw, skb);
continue;
}
}
}
static void ath_tx_flush_tid(struct ath_softc *sc, struct ath_atx_tid *tid)
{
struct ath_txq *txq = tid->txq;
@ -883,20 +900,16 @@ static int ath_compute_num_delims(struct ath_softc *sc, struct ath_atx_tid *tid,
static struct ath_buf *
ath_tx_get_tid_subframe(struct ath_softc *sc, struct ath_txq *txq,
struct ath_atx_tid *tid, struct sk_buff_head **q)
struct ath_atx_tid *tid)
{
struct ieee80211_tx_info *tx_info;
struct ath_frame_info *fi;
struct sk_buff *skb;
struct sk_buff *skb, *first_skb = NULL;
struct ath_buf *bf;
u16 seqno;
while (1) {
*q = &tid->retry_q;
if (skb_queue_empty(*q))
*q = &tid->buf_q;
skb = skb_peek(*q);
skb = ath_tid_dequeue(tid);
if (!skb)
break;
@ -908,7 +921,6 @@ ath_tx_get_tid_subframe(struct ath_softc *sc, struct ath_txq *txq,
bf->bf_state.stale = false;
if (!bf) {
__skb_unlink(skb, *q);
ath_txq_skb_done(sc, txq, skb);
ieee80211_free_txskb(sc->hw, skb);
continue;
@ -937,8 +949,20 @@ ath_tx_get_tid_subframe(struct ath_softc *sc, struct ath_txq *txq,
seqno = bf->bf_state.seqno;
/* do not step over block-ack window */
if (!BAW_WITHIN(tid->seq_start, tid->baw_size, seqno))
if (!BAW_WITHIN(tid->seq_start, tid->baw_size, seqno)) {
__skb_queue_tail(&tid->retry_q, skb);
/* If there are other skbs in the retry q, they are
* probably within the BAW, so loop immediately to get
* one of them. Otherwise the queue can get stuck. */
if (!skb_queue_is_first(&tid->retry_q, skb) &&
!WARN_ON(skb == first_skb)) {
if(!first_skb) /* infinite loop prevention */
first_skb = skb;
continue;
}
break;
}
if (tid->bar_index > ATH_BA_INDEX(tid->seq_start, seqno)) {
struct ath_tx_status ts = {};
@ -946,7 +970,6 @@ ath_tx_get_tid_subframe(struct ath_softc *sc, struct ath_txq *txq,
INIT_LIST_HEAD(&bf_head);
list_add(&bf->list, &bf_head);
__skb_unlink(skb, *q);
ath_tx_update_baw(sc, tid, seqno);
ath_tx_complete_buf(sc, bf, txq, &bf_head, NULL, &ts, 0);
continue;
@ -958,11 +981,10 @@ ath_tx_get_tid_subframe(struct ath_softc *sc, struct ath_txq *txq,
return NULL;
}
static bool
static int
ath_tx_form_aggr(struct ath_softc *sc, struct ath_txq *txq,
struct ath_atx_tid *tid, struct list_head *bf_q,
struct ath_buf *bf_first, struct sk_buff_head *tid_q,
int *aggr_len)
struct ath_buf *bf_first)
{
#define PADBYTES(_len) ((4 - ((_len) % 4)) % 4)
struct ath_buf *bf = bf_first, *bf_prev = NULL;
@ -972,12 +994,13 @@ ath_tx_form_aggr(struct ath_softc *sc, struct ath_txq *txq,
struct ieee80211_tx_info *tx_info;
struct ath_frame_info *fi;
struct sk_buff *skb;
bool closed = false;
bf = bf_first;
aggr_limit = ath_lookup_rate(sc, bf, tid);
do {
while (bf)
{
skb = bf->bf_mpdu;
fi = get_frame_info(skb);
@ -986,12 +1009,12 @@ ath_tx_form_aggr(struct ath_softc *sc, struct ath_txq *txq,
if (nframes) {
if (aggr_limit < al + bpad + al_delta ||
ath_lookup_legacy(bf) || nframes >= h_baw)
break;
goto stop;
tx_info = IEEE80211_SKB_CB(bf->bf_mpdu);
if ((tx_info->flags & IEEE80211_TX_CTL_RATE_CTRL_PROBE) ||
!(tx_info->flags & IEEE80211_TX_CTL_AMPDU))
break;
goto stop;
}
/* add padding for previous frame to aggregation length */
@ -1013,20 +1036,18 @@ ath_tx_form_aggr(struct ath_softc *sc, struct ath_txq *txq,
ath_tx_addto_baw(sc, tid, bf);
bf->bf_state.ndelim = ndelim;
__skb_unlink(skb, tid_q);
list_add_tail(&bf->list, bf_q);
if (bf_prev)
bf_prev->bf_next = bf;
bf_prev = bf;
bf = ath_tx_get_tid_subframe(sc, txq, tid, &tid_q);
if (!bf) {
closed = true;
break;
}
} while (ath_tid_has_buffered(tid));
bf = ath_tx_get_tid_subframe(sc, txq, tid);
}
goto finish;
stop:
__skb_queue_tail(&tid->retry_q, bf->bf_mpdu);
finish:
bf = bf_first;
bf->bf_lastbf = bf_prev;
@ -1037,9 +1058,7 @@ ath_tx_form_aggr(struct ath_softc *sc, struct ath_txq *txq,
TX_STAT_INC(txq->axq_qnum, a_aggr);
}
*aggr_len = al;
return closed;
return al;
#undef PADBYTES
}
@ -1416,18 +1435,15 @@ static void ath_tx_fill_desc(struct ath_softc *sc, struct ath_buf *bf,
static void
ath_tx_form_burst(struct ath_softc *sc, struct ath_txq *txq,
struct ath_atx_tid *tid, struct list_head *bf_q,
struct ath_buf *bf_first, struct sk_buff_head *tid_q)
struct ath_buf *bf_first)
{
struct ath_buf *bf = bf_first, *bf_prev = NULL;
struct sk_buff *skb;
int nframes = 0;
do {
struct ieee80211_tx_info *tx_info;
skb = bf->bf_mpdu;
nframes++;
__skb_unlink(skb, tid_q);
list_add_tail(&bf->list, bf_q);
if (bf_prev)
bf_prev->bf_next = bf;
@ -1436,13 +1452,15 @@ ath_tx_form_burst(struct ath_softc *sc, struct ath_txq *txq,
if (nframes >= 2)
break;
bf = ath_tx_get_tid_subframe(sc, txq, tid, &tid_q);
bf = ath_tx_get_tid_subframe(sc, txq, tid);
if (!bf)
break;
tx_info = IEEE80211_SKB_CB(bf->bf_mpdu);
if (tx_info->flags & IEEE80211_TX_CTL_AMPDU)
if (tx_info->flags & IEEE80211_TX_CTL_AMPDU) {
__skb_queue_tail(&tid->retry_q, bf->bf_mpdu);
break;
}
ath_set_rates(tid->an->vif, tid->an->sta, bf);
} while (1);
@ -1453,34 +1471,33 @@ static bool ath_tx_sched_aggr(struct ath_softc *sc, struct ath_txq *txq,
{
struct ath_buf *bf;
struct ieee80211_tx_info *tx_info;
struct sk_buff_head *tid_q;
struct list_head bf_q;
int aggr_len = 0;
bool aggr, last = true;
bool aggr;
if (!ath_tid_has_buffered(tid))
return false;
INIT_LIST_HEAD(&bf_q);
bf = ath_tx_get_tid_subframe(sc, txq, tid, &tid_q);
bf = ath_tx_get_tid_subframe(sc, txq, tid);
if (!bf)
return false;
tx_info = IEEE80211_SKB_CB(bf->bf_mpdu);
aggr = !!(tx_info->flags & IEEE80211_TX_CTL_AMPDU);
if ((aggr && txq->axq_ampdu_depth >= ATH_AGGR_MIN_QDEPTH) ||
(!aggr && txq->axq_depth >= ATH_NON_AGGR_MIN_QDEPTH)) {
(!aggr && txq->axq_depth >= ATH_NON_AGGR_MIN_QDEPTH)) {
__skb_queue_tail(&tid->retry_q, bf->bf_mpdu);
*stop = true;
return false;
}
ath_set_rates(tid->an->vif, tid->an->sta, bf);
if (aggr)
last = ath_tx_form_aggr(sc, txq, tid, &bf_q, bf,
tid_q, &aggr_len);
aggr_len = ath_tx_form_aggr(sc, txq, tid, &bf_q, bf);
else
ath_tx_form_burst(sc, txq, tid, &bf_q, bf, tid_q);
ath_tx_form_burst(sc, txq, tid, &bf_q, bf);
if (list_empty(&bf_q))
return false;
@ -1523,9 +1540,6 @@ int ath_tx_aggr_start(struct ath_softc *sc, struct ieee80211_sta *sta,
an->mpdudensity = density;
}
/* force sequence number allocation for pending frames */
ath_tx_tid_change_state(sc, txtid);
txtid->active = true;
*ssn = txtid->seq_start = txtid->seq_next;
txtid->bar_index = -1;
@ -1550,7 +1564,6 @@ void ath_tx_aggr_stop(struct ath_softc *sc, struct ieee80211_sta *sta, u16 tid)
ath_txq_lock(sc, txq);
txtid->active = false;
ath_tx_flush_tid(sc, txtid);
ath_tx_tid_change_state(sc, txtid);
ath_txq_unlock_complete(sc, txq);
}
@ -1560,14 +1573,12 @@ void ath_tx_aggr_sleep(struct ieee80211_sta *sta, struct ath_softc *sc,
struct ath_common *common = ath9k_hw_common(sc->sc_ah);
struct ath_atx_tid *tid;
struct ath_txq *txq;
bool buffered;
int tidno;
ath_dbg(common, XMIT, "%s called\n", __func__);
for (tidno = 0, tid = &an->tid[tidno];
tidno < IEEE80211_NUM_TIDS; tidno++, tid++) {
for (tidno = 0; tidno < IEEE80211_NUM_TIDS; tidno++) {
tid = ath_node_to_tid(an, tidno);
txq = tid->txq;
ath_txq_lock(sc, txq);
@ -1577,13 +1588,12 @@ void ath_tx_aggr_sleep(struct ieee80211_sta *sta, struct ath_softc *sc,
continue;
}
buffered = ath_tid_has_buffered(tid);
if (!skb_queue_empty(&tid->retry_q))
ieee80211_sta_set_buffered(sta, tid->tidno, true);
list_del_init(&tid->list);
ath_txq_unlock(sc, txq);
ieee80211_sta_set_buffered(sta, tidno, buffered);
}
}
@ -1596,49 +1606,20 @@ void ath_tx_aggr_wakeup(struct ath_softc *sc, struct ath_node *an)
ath_dbg(common, XMIT, "%s called\n", __func__);
for (tidno = 0, tid = &an->tid[tidno];
tidno < IEEE80211_NUM_TIDS; tidno++, tid++) {
for (tidno = 0; tidno < IEEE80211_NUM_TIDS; tidno++) {
tid = ath_node_to_tid(an, tidno);
txq = tid->txq;
ath_txq_lock(sc, txq);
tid->clear_ps_filter = true;
if (ath_tid_has_buffered(tid)) {
ath_tx_queue_tid(sc, txq, tid);
ath_txq_schedule(sc, txq);
}
ath_txq_unlock_complete(sc, txq);
}
}
void ath_tx_aggr_resume(struct ath_softc *sc, struct ieee80211_sta *sta,
u16 tidno)
{
struct ath_common *common = ath9k_hw_common(sc->sc_ah);
struct ath_atx_tid *tid;
struct ath_node *an;
struct ath_txq *txq;
ath_dbg(common, XMIT, "%s called\n", __func__);
an = (struct ath_node *)sta->drv_priv;
tid = ATH_AN_2_TID(an, tidno);
txq = tid->txq;
ath_txq_lock(sc, txq);
tid->baw_size = IEEE80211_MIN_AMPDU_BUF << sta->ht_cap.ampdu_factor;
if (ath_tid_has_buffered(tid)) {
ath_tx_queue_tid(sc, txq, tid);
ath_txq_schedule(sc, txq);
}
ath_txq_unlock_complete(sc, txq);
}
void ath9k_release_buffered_frames(struct ieee80211_hw *hw,
struct ieee80211_sta *sta,
u16 tids, int nframes,
@ -1651,7 +1632,6 @@ void ath9k_release_buffered_frames(struct ieee80211_hw *hw,
struct ieee80211_tx_info *info;
struct list_head bf_q;
struct ath_buf *bf_tail = NULL, *bf;
struct sk_buff_head *tid_q;
int sent = 0;
int i;
@ -1666,11 +1646,10 @@ void ath9k_release_buffered_frames(struct ieee80211_hw *hw,
ath_txq_lock(sc, tid->txq);
while (nframes > 0) {
bf = ath_tx_get_tid_subframe(sc, sc->tx.uapsdq, tid, &tid_q);
bf = ath_tx_get_tid_subframe(sc, sc->tx.uapsdq, tid);
if (!bf)
break;
__skb_unlink(bf->bf_mpdu, tid_q);
list_add_tail(&bf->list, &bf_q);
ath_set_rates(tid->an->vif, tid->an->sta, bf);
if (bf_isampdu(bf)) {
@ -1685,7 +1664,7 @@ void ath9k_release_buffered_frames(struct ieee80211_hw *hw,
sent++;
TX_STAT_INC(txq->axq_qnum, a_queued_hw);
if (an->sta && !ath_tid_has_buffered(tid))
if (an->sta && skb_queue_empty(&tid->retry_q))
ieee80211_sta_set_buffered(an->sta, i, false);
}
ath_txq_unlock_complete(sc, tid->txq);
@ -1914,13 +1893,7 @@ bool ath_drain_all_txq(struct ath_softc *sc)
if (!ATH_TXQ_SETUP(sc, i))
continue;
/*
* The caller will resume queues with ieee80211_wake_queues.
* Mark the queue as not stopped to prevent ath_tx_complete
* from waking the queue too early.
*/
txq = &sc->tx.txq[i];
txq->stopped = false;
ath_draintxq(sc, txq);
}
@ -2319,16 +2292,14 @@ int ath_tx_start(struct ieee80211_hw *hw, struct sk_buff *skb,
struct ath_softc *sc = hw->priv;
struct ath_txq *txq = txctl->txq;
struct ath_atx_tid *tid = NULL;
struct ath_node *an = NULL;
struct ath_buf *bf;
bool queue, skip_uapsd = false, ps_resp;
bool ps_resp;
int q, ret;
if (vif)
avp = (void *)vif->drv_priv;
if (info->flags & IEEE80211_TX_CTL_TX_OFFCHAN)
txctl->force_channel = true;
ps_resp = !!(info->control.flags & IEEE80211_TX_CTRL_PS_RESPONSE);
ret = ath_tx_prepare(hw, skb, txctl);
@ -2343,63 +2314,18 @@ int ath_tx_start(struct ieee80211_hw *hw, struct sk_buff *skb,
q = skb_get_queue_mapping(skb);
if (ps_resp)
txq = sc->tx.uapsdq;
if (txctl->sta) {
an = (struct ath_node *) sta->drv_priv;
tid = ath_get_skb_tid(sc, an, skb);
}
ath_txq_lock(sc, txq);
if (txq == sc->tx.txq_map[q]) {
fi->txq = q;
if (++txq->pending_frames > sc->tx.txq_max_pending[q] &&
!txq->stopped) {
if (ath9k_is_chanctx_enabled())
ieee80211_stop_queue(sc->hw, info->hw_queue);
else
ieee80211_stop_queue(sc->hw, q);
txq->stopped = true;
}
}
queue = ieee80211_is_data_present(hdr->frame_control);
/* If chanctx, queue all null frames while NOA could be there */
if (ath9k_is_chanctx_enabled() &&
ieee80211_is_nullfunc(hdr->frame_control) &&
!txctl->force_channel)
queue = true;
/* Force queueing of all frames that belong to a virtual interface on
* a different channel context, to ensure that they are sent on the
* correct channel.
*/
if (((avp && avp->chanctx != sc->cur_chan) ||
sc->cur_chan->stopped) && !txctl->force_channel) {
if (!txctl->an)
txctl->an = &avp->mcast_node;
queue = true;
skip_uapsd = true;
}
if (txctl->an && queue)
tid = ath_get_skb_tid(sc, txctl->an, skb);
if (!skip_uapsd && ps_resp) {
ath_txq_unlock(sc, txq);
txq = sc->tx.uapsdq;
ath_txq_lock(sc, txq);
} else if (txctl->an && queue) {
WARN_ON(tid->txq != txctl->txq);
if (info->flags & IEEE80211_TX_CTL_CLEAR_PS_FILT)
tid->clear_ps_filter = true;
/*
* Add this frame to software queue for scheduling later
* for aggregation.
*/
TX_STAT_INC(txq->axq_qnum, a_queued_sw);
__skb_queue_tail(&tid->buf_q, skb);
if (!txctl->an->sleeping)
ath_tx_queue_tid(sc, txq, tid);
ath_txq_schedule(sc, txq);
goto out;
++txq->pending_frames;
}
bf = ath_tx_setup_buffer(sc, txq, tid, skb);
@ -2892,9 +2818,8 @@ void ath_tx_node_init(struct ath_softc *sc, struct ath_node *an)
struct ath_atx_tid *tid;
int tidno, acno;
for (tidno = 0, tid = &an->tid[tidno];
tidno < IEEE80211_NUM_TIDS;
tidno++, tid++) {
for (tidno = 0; tidno < IEEE80211_NUM_TIDS; tidno++) {
tid = ath_node_to_tid(an, tidno);
tid->an = an;
tid->tidno = tidno;
tid->seq_start = tid->seq_next = 0;
@ -2902,11 +2827,14 @@ void ath_tx_node_init(struct ath_softc *sc, struct ath_node *an)
tid->baw_head = tid->baw_tail = 0;
tid->active = false;
tid->clear_ps_filter = true;
__skb_queue_head_init(&tid->buf_q);
tid->has_queued = false;
__skb_queue_head_init(&tid->retry_q);
INIT_LIST_HEAD(&tid->list);
acno = TID_TO_WME_AC(tidno);
tid->txq = sc->tx.txq_map[acno];
if (!an->sta)
break; /* just one multicast ath_atx_tid */
}
}
@ -2916,9 +2844,8 @@ void ath_tx_node_cleanup(struct ath_softc *sc, struct ath_node *an)
struct ath_txq *txq;
int tidno;
for (tidno = 0, tid = &an->tid[tidno];
tidno < IEEE80211_NUM_TIDS; tidno++, tid++) {
for (tidno = 0; tidno < IEEE80211_NUM_TIDS; tidno++) {
tid = ath_node_to_tid(an, tidno);
txq = tid->txq;
ath_txq_lock(sc, txq);
@ -2930,6 +2857,9 @@ void ath_tx_node_cleanup(struct ath_softc *sc, struct ath_node *an)
tid->active = false;
ath_txq_unlock(sc, txq);
if (!an->sta)
break; /* just one multicast ath_atx_tid */
}
}

View File

@ -90,3 +90,10 @@ void ath_printk(const char *level, const struct ath_common* common,
va_end(args);
}
EXPORT_SYMBOL(ath_printk);
const char *ath_bus_type_strings[] = {
[ATH_PCI] = "pci",
[ATH_AHB] = "ahb",
[ATH_USB] = "usb",
};
EXPORT_SYMBOL(ath_bus_type_strings);