linux/drivers/net/wireless/libertas_tf/main.c

766 lines
19 KiB
C
Raw Normal View History

/*
* Copyright (C) 2008, cozybit Inc.
* Copyright (C) 2003-2006, Marvell International Ltd.
*
* 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.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h percpu.h is included by sched.h and module.h and thus ends up being included when building most .c files. percpu.h includes slab.h which in turn includes gfp.h making everything defined by the two files universally available and complicating inclusion dependencies. percpu.h -> slab.h dependency is about to be removed. Prepare for this change by updating users of gfp and slab facilities include those headers directly instead of assuming availability. As this conversion needs to touch large number of source files, the following script is used as the basis of conversion. http://userweb.kernel.org/~tj/misc/slabh-sweep.py The script does the followings. * Scan files for gfp and slab usages and update includes such that only the necessary includes are there. ie. if only gfp is used, gfp.h, if slab is used, slab.h. * When the script inserts a new include, it looks at the include blocks and try to put the new include such that its order conforms to its surrounding. It's put in the include block which contains core kernel includes, in the same order that the rest are ordered - alphabetical, Christmas tree, rev-Xmas-tree or at the end if there doesn't seem to be any matching order. * If the script can't find a place to put a new include (mostly because the file doesn't have fitting include block), it prints out an error message indicating which .h file needs to be added to the file. The conversion was done in the following steps. 1. The initial automatic conversion of all .c files updated slightly over 4000 files, deleting around 700 includes and adding ~480 gfp.h and ~3000 slab.h inclusions. The script emitted errors for ~400 files. 2. Each error was manually checked. Some didn't need the inclusion, some needed manual addition while adding it to implementation .h or embedding .c file was more appropriate for others. This step added inclusions to around 150 files. 3. The script was run again and the output was compared to the edits from #2 to make sure no file was left behind. 4. Several build tests were done and a couple of problems were fixed. e.g. lib/decompress_*.c used malloc/free() wrappers around slab APIs requiring slab.h to be added manually. 5. The script was run on all .h files but without automatically editing them as sprinkling gfp.h and slab.h inclusions around .h files could easily lead to inclusion dependency hell. Most gfp.h inclusion directives were ignored as stuff from gfp.h was usually wildly available and often used in preprocessor macros. Each slab.h inclusion directive was examined and added manually as necessary. 6. percpu.h was updated not to include slab.h. 7. Build test were done on the following configurations and failures were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my distributed build env didn't work with gcov compiles) and a few more options had to be turned off depending on archs to make things build (like ipr on powerpc/64 which failed due to missing writeq). * x86 and x86_64 UP and SMP allmodconfig and a custom test config. * powerpc and powerpc64 SMP allmodconfig * sparc and sparc64 SMP allmodconfig * ia64 SMP allmodconfig * s390 SMP allmodconfig * alpha SMP allmodconfig * um on x86_64 SMP allmodconfig 8. percpu.h modifications were reverted so that it could be applied as a separate patch and serve as bisection point. Given the fact that I had only a couple of failures from tests on step 6, I'm fairly confident about the coverage of this conversion patch. If there is a breakage, it's likely to be something in one of the arch headers which should be easily discoverable easily on most builds of the specific arch. Signed-off-by: Tejun Heo <tj@kernel.org> Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org> Cc: Ingo Molnar <mingo@redhat.com> Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 16:04:11 +08:00
#include <linux/slab.h>
#include <linux/etherdevice.h>
#include "libertas_tf.h"
#define DRIVER_RELEASE_VERSION "004.p0"
/* thinfirm version: 5.132.X.pX */
#define LBTF_FW_VER_MIN 0x05840300
#define LBTF_FW_VER_MAX 0x0584ffff
#define QOS_CONTROL_LEN 2
/* Module parameters */
unsigned int lbtf_debug;
EXPORT_SYMBOL_GPL(lbtf_debug);
module_param_named(libertas_tf_debug, lbtf_debug, int, 0644);
static const char lbtf_driver_version[] = "THINFIRM-USB8388-" DRIVER_RELEASE_VERSION
#ifdef DEBUG
"-dbg"
#endif
"";
struct workqueue_struct *lbtf_wq;
static const struct ieee80211_channel lbtf_channels[] = {
{ .center_freq = 2412, .hw_value = 1 },
{ .center_freq = 2417, .hw_value = 2 },
{ .center_freq = 2422, .hw_value = 3 },
{ .center_freq = 2427, .hw_value = 4 },
{ .center_freq = 2432, .hw_value = 5 },
{ .center_freq = 2437, .hw_value = 6 },
{ .center_freq = 2442, .hw_value = 7 },
{ .center_freq = 2447, .hw_value = 8 },
{ .center_freq = 2452, .hw_value = 9 },
{ .center_freq = 2457, .hw_value = 10 },
{ .center_freq = 2462, .hw_value = 11 },
{ .center_freq = 2467, .hw_value = 12 },
{ .center_freq = 2472, .hw_value = 13 },
{ .center_freq = 2484, .hw_value = 14 },
};
/* This table contains the hardware specific values for the modulation rates. */
static const struct ieee80211_rate lbtf_rates[] = {
{ .bitrate = 10,
.hw_value = 0, },
{ .bitrate = 20,
.hw_value = 1,
.flags = IEEE80211_RATE_SHORT_PREAMBLE },
{ .bitrate = 55,
.hw_value = 2,
.flags = IEEE80211_RATE_SHORT_PREAMBLE },
{ .bitrate = 110,
.hw_value = 3,
.flags = IEEE80211_RATE_SHORT_PREAMBLE },
{ .bitrate = 60,
.hw_value = 5,
.flags = 0 },
{ .bitrate = 90,
.hw_value = 6,
.flags = 0 },
{ .bitrate = 120,
.hw_value = 7,
.flags = 0 },
{ .bitrate = 180,
.hw_value = 8,
.flags = 0 },
{ .bitrate = 240,
.hw_value = 9,
.flags = 0 },
{ .bitrate = 360,
.hw_value = 10,
.flags = 0 },
{ .bitrate = 480,
.hw_value = 11,
.flags = 0 },
{ .bitrate = 540,
.hw_value = 12,
.flags = 0 },
};
static void lbtf_cmd_work(struct work_struct *work)
{
struct lbtf_private *priv = container_of(work, struct lbtf_private,
cmd_work);
lbtf_deb_enter(LBTF_DEB_CMD);
spin_lock_irq(&priv->driver_lock);
/* command response? */
if (priv->cmd_response_rxed) {
priv->cmd_response_rxed = 0;
spin_unlock_irq(&priv->driver_lock);
lbtf_process_rx_command(priv);
spin_lock_irq(&priv->driver_lock);
}
if (priv->cmd_timed_out && priv->cur_cmd) {
struct cmd_ctrl_node *cmdnode = priv->cur_cmd;
if (++priv->nr_retries > 10) {
lbtf_complete_command(priv, cmdnode,
-ETIMEDOUT);
priv->nr_retries = 0;
} else {
priv->cur_cmd = NULL;
/* Stick it back at the _top_ of the pending
* queue for immediate resubmission */
list_add(&cmdnode->list, &priv->cmdpendingq);
}
}
priv->cmd_timed_out = 0;
spin_unlock_irq(&priv->driver_lock);
if (!priv->fw_ready) {
lbtf_deb_leave_args(LBTF_DEB_CMD, "fw not ready");
return;
}
/* Execute the next command */
if (!priv->cur_cmd)
lbtf_execute_next_command(priv);
lbtf_deb_leave(LBTF_DEB_CMD);
}
/**
* lbtf_setup_firmware: initialize firmware.
*
* @priv A pointer to struct lbtf_private structure
*
* Returns: 0 on success.
*/
static int lbtf_setup_firmware(struct lbtf_private *priv)
{
int ret = -1;
lbtf_deb_enter(LBTF_DEB_FW);
/*
* Read priv address from HW
*/
memset(priv->current_addr, 0xff, ETH_ALEN);
ret = lbtf_update_hw_spec(priv);
if (ret) {
ret = -1;
goto done;
}
lbtf_set_mac_control(priv);
lbtf_set_radio_control(priv);
ret = 0;
done:
lbtf_deb_leave_args(LBTF_DEB_FW, "ret: %d", ret);
return ret;
}
/**
* This function handles the timeout of command sending.
* It will re-send the same command again.
*/
static void command_timer_fn(unsigned long data)
{
struct lbtf_private *priv = (struct lbtf_private *)data;
unsigned long flags;
lbtf_deb_enter(LBTF_DEB_CMD);
spin_lock_irqsave(&priv->driver_lock, flags);
if (!priv->cur_cmd) {
printk(KERN_DEBUG "libertastf: command timer expired; "
"no pending command\n");
goto out;
}
printk(KERN_DEBUG "libertas: command %x timed out\n",
le16_to_cpu(priv->cur_cmd->cmdbuf->command));
priv->cmd_timed_out = 1;
queue_work(lbtf_wq, &priv->cmd_work);
out:
spin_unlock_irqrestore(&priv->driver_lock, flags);
lbtf_deb_leave(LBTF_DEB_CMD);
}
static int lbtf_init_adapter(struct lbtf_private *priv)
{
lbtf_deb_enter(LBTF_DEB_MAIN);
memset(priv->current_addr, 0xff, ETH_ALEN);
mutex_init(&priv->lock);
priv->vif = NULL;
setup_timer(&priv->command_timer, command_timer_fn,
(unsigned long)priv);
INIT_LIST_HEAD(&priv->cmdfreeq);
INIT_LIST_HEAD(&priv->cmdpendingq);
spin_lock_init(&priv->driver_lock);
/* Allocate the command buffers */
if (lbtf_allocate_cmd_buffer(priv))
return -1;
lbtf_deb_leave(LBTF_DEB_MAIN);
return 0;
}
static void lbtf_free_adapter(struct lbtf_private *priv)
{
lbtf_deb_enter(LBTF_DEB_MAIN);
lbtf_free_cmd_buffer(priv);
del_timer(&priv->command_timer);
lbtf_deb_leave(LBTF_DEB_MAIN);
}
static void lbtf_op_tx(struct ieee80211_hw *hw, struct sk_buff *skb)
{
struct lbtf_private *priv = hw->priv;
priv->skb_to_tx = skb;
queue_work(lbtf_wq, &priv->tx_work);
/*
* queue will be restarted when we receive transmission feedback if
* there are no buffered multicast frames to send
*/
ieee80211_stop_queues(priv->hw);
}
static void lbtf_tx_work(struct work_struct *work)
{
struct lbtf_private *priv = container_of(work, struct lbtf_private,
tx_work);
unsigned int len;
struct ieee80211_tx_info *info;
struct txpd *txpd;
struct sk_buff *skb = NULL;
int err;
lbtf_deb_enter(LBTF_DEB_MACOPS | LBTF_DEB_TX);
if ((priv->vif->type == NL80211_IFTYPE_AP) &&
(!skb_queue_empty(&priv->bc_ps_buf)))
skb = skb_dequeue(&priv->bc_ps_buf);
else if (priv->skb_to_tx) {
skb = priv->skb_to_tx;
priv->skb_to_tx = NULL;
} else {
lbtf_deb_leave(LBTF_DEB_MACOPS | LBTF_DEB_TX);
return;
}
len = skb->len;
info = IEEE80211_SKB_CB(skb);
txpd = (struct txpd *) skb_push(skb, sizeof(struct txpd));
if (priv->surpriseremoved) {
dev_kfree_skb_any(skb);
lbtf_deb_leave(LBTF_DEB_MACOPS | LBTF_DEB_TX);
return;
}
memset(txpd, 0, sizeof(struct txpd));
/* Activate per-packet rate selection */
txpd->tx_control |= cpu_to_le32(MRVL_PER_PACKET_RATE |
ieee80211_get_tx_rate(priv->hw, info)->hw_value);
/* copy destination address from 802.11 header */
memcpy(txpd->tx_dest_addr_high, skb->data + sizeof(struct txpd) + 4,
ETH_ALEN);
txpd->tx_packet_length = cpu_to_le16(len);
txpd->tx_packet_location = cpu_to_le32(sizeof(struct txpd));
lbtf_deb_hex(LBTF_DEB_TX, "TX Data", skb->data, min_t(unsigned int, skb->len, 100));
BUG_ON(priv->tx_skb);
spin_lock_irq(&priv->driver_lock);
priv->tx_skb = skb;
err = priv->hw_host_to_card(priv, MVMS_DAT, skb->data, skb->len);
spin_unlock_irq(&priv->driver_lock);
if (err) {
dev_kfree_skb_any(skb);
priv->tx_skb = NULL;
pr_err("TX error: %d", err);
}
lbtf_deb_leave(LBTF_DEB_MACOPS | LBTF_DEB_TX);
}
static int lbtf_op_start(struct ieee80211_hw *hw)
{
struct lbtf_private *priv = hw->priv;
void *card = priv->card;
int ret = -1;
lbtf_deb_enter(LBTF_DEB_MACOPS);
if (!priv->fw_ready)
/* Upload firmware */
if (priv->hw_prog_firmware(card))
goto err_prog_firmware;
/* poke the firmware */
priv->capability = WLAN_CAPABILITY_SHORT_PREAMBLE;
priv->radioon = RADIO_ON;
priv->mac_control = CMD_ACT_MAC_RX_ON | CMD_ACT_MAC_TX_ON;
ret = lbtf_setup_firmware(priv);
if (ret)
goto err_prog_firmware;
if ((priv->fwrelease < LBTF_FW_VER_MIN) ||
(priv->fwrelease > LBTF_FW_VER_MAX)) {
ret = -1;
goto err_prog_firmware;
}
printk(KERN_INFO "libertastf: Marvell WLAN 802.11 thinfirm adapter\n");
lbtf_deb_leave(LBTF_DEB_MACOPS);
return 0;
err_prog_firmware:
priv->hw_reset_device(card);
lbtf_deb_leave_args(LBTF_DEB_MACOPS, "error programing fw; ret=%d", ret);
return ret;
}
static void lbtf_op_stop(struct ieee80211_hw *hw)
{
struct lbtf_private *priv = hw->priv;
unsigned long flags;
struct sk_buff *skb;
struct cmd_ctrl_node *cmdnode;
lbtf_deb_enter(LBTF_DEB_MACOPS);
/* Flush pending command nodes */
spin_lock_irqsave(&priv->driver_lock, flags);
list_for_each_entry(cmdnode, &priv->cmdpendingq, list) {
cmdnode->result = -ENOENT;
cmdnode->cmdwaitqwoken = 1;
wake_up_interruptible(&cmdnode->cmdwait_q);
}
spin_unlock_irqrestore(&priv->driver_lock, flags);
cancel_work_sync(&priv->cmd_work);
cancel_work_sync(&priv->tx_work);
while ((skb = skb_dequeue(&priv->bc_ps_buf)))
dev_kfree_skb_any(skb);
priv->radioon = RADIO_OFF;
lbtf_set_radio_control(priv);
lbtf_deb_leave(LBTF_DEB_MACOPS);
}
static int lbtf_op_add_interface(struct ieee80211_hw *hw,
struct ieee80211_vif *vif)
{
struct lbtf_private *priv = hw->priv;
lbtf_deb_enter(LBTF_DEB_MACOPS);
if (priv->vif != NULL)
return -EOPNOTSUPP;
priv->vif = vif;
switch (vif->type) {
case NL80211_IFTYPE_MESH_POINT:
case NL80211_IFTYPE_AP:
lbtf_set_mode(priv, LBTF_AP_MODE);
break;
case NL80211_IFTYPE_STATION:
lbtf_set_mode(priv, LBTF_STA_MODE);
break;
default:
priv->vif = NULL;
return -EOPNOTSUPP;
}
lbtf_set_mac_address(priv, (u8 *) vif->addr);
lbtf_deb_leave(LBTF_DEB_MACOPS);
return 0;
}
static void lbtf_op_remove_interface(struct ieee80211_hw *hw,
struct ieee80211_vif *vif)
{
struct lbtf_private *priv = hw->priv;
lbtf_deb_enter(LBTF_DEB_MACOPS);
if (priv->vif->type == NL80211_IFTYPE_AP ||
priv->vif->type == NL80211_IFTYPE_MESH_POINT)
lbtf_beacon_ctrl(priv, 0, 0);
lbtf_set_mode(priv, LBTF_PASSIVE_MODE);
lbtf_set_bssid(priv, 0, NULL);
priv->vif = NULL;
lbtf_deb_leave(LBTF_DEB_MACOPS);
}
static int lbtf_op_config(struct ieee80211_hw *hw, u32 changed)
{
struct lbtf_private *priv = hw->priv;
struct ieee80211_conf *conf = &hw->conf;
lbtf_deb_enter(LBTF_DEB_MACOPS);
if (conf->channel->center_freq != priv->cur_freq) {
priv->cur_freq = conf->channel->center_freq;
lbtf_set_channel(priv, conf->channel->hw_value);
}
lbtf_deb_leave(LBTF_DEB_MACOPS);
return 0;
}
static u64 lbtf_op_prepare_multicast(struct ieee80211_hw *hw,
struct netdev_hw_addr_list *mc_list)
{
struct lbtf_private *priv = hw->priv;
int i;
struct netdev_hw_addr *ha;
int mc_count = netdev_hw_addr_list_count(mc_list);
if (!mc_count || mc_count > MRVDRV_MAX_MULTICAST_LIST_SIZE)
return mc_count;
priv->nr_of_multicastmacaddr = mc_count;
i = 0;
netdev_hw_addr_list_for_each(ha, mc_list)
memcpy(&priv->multicastlist[i++], ha->addr, ETH_ALEN);
return mc_count;
}
#define SUPPORTED_FIF_FLAGS (FIF_PROMISC_IN_BSS | FIF_ALLMULTI)
static void lbtf_op_configure_filter(struct ieee80211_hw *hw,
unsigned int changed_flags,
unsigned int *new_flags,
u64 multicast)
{
struct lbtf_private *priv = hw->priv;
int old_mac_control = priv->mac_control;
lbtf_deb_enter(LBTF_DEB_MACOPS);
changed_flags &= SUPPORTED_FIF_FLAGS;
*new_flags &= SUPPORTED_FIF_FLAGS;
if (!changed_flags) {
lbtf_deb_leave(LBTF_DEB_MACOPS);
return;
}
if (*new_flags & (FIF_PROMISC_IN_BSS))
priv->mac_control |= CMD_ACT_MAC_PROMISCUOUS_ENABLE;
else
priv->mac_control &= ~CMD_ACT_MAC_PROMISCUOUS_ENABLE;
if (*new_flags & (FIF_ALLMULTI) ||
multicast > MRVDRV_MAX_MULTICAST_LIST_SIZE) {
priv->mac_control |= CMD_ACT_MAC_ALL_MULTICAST_ENABLE;
priv->mac_control &= ~CMD_ACT_MAC_MULTICAST_ENABLE;
} else if (multicast) {
priv->mac_control |= CMD_ACT_MAC_MULTICAST_ENABLE;
priv->mac_control &= ~CMD_ACT_MAC_ALL_MULTICAST_ENABLE;
lbtf_cmd_set_mac_multicast_addr(priv);
} else {
priv->mac_control &= ~(CMD_ACT_MAC_MULTICAST_ENABLE |
CMD_ACT_MAC_ALL_MULTICAST_ENABLE);
if (priv->nr_of_multicastmacaddr) {
priv->nr_of_multicastmacaddr = 0;
lbtf_cmd_set_mac_multicast_addr(priv);
}
}
if (priv->mac_control != old_mac_control)
lbtf_set_mac_control(priv);
lbtf_deb_leave(LBTF_DEB_MACOPS);
}
static void lbtf_op_bss_info_changed(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
struct ieee80211_bss_conf *bss_conf,
u32 changes)
{
struct lbtf_private *priv = hw->priv;
struct sk_buff *beacon;
lbtf_deb_enter(LBTF_DEB_MACOPS);
if (changes & (BSS_CHANGED_BEACON | BSS_CHANGED_BEACON_INT)) {
switch (priv->vif->type) {
case NL80211_IFTYPE_AP:
case NL80211_IFTYPE_MESH_POINT:
beacon = ieee80211_beacon_get(hw, vif);
if (beacon) {
lbtf_beacon_set(priv, beacon);
kfree_skb(beacon);
lbtf_beacon_ctrl(priv, 1,
bss_conf->beacon_int);
}
break;
default:
break;
}
}
if (changes & BSS_CHANGED_BSSID) {
bool activate = !is_zero_ether_addr(bss_conf->bssid);
lbtf_set_bssid(priv, activate, bss_conf->bssid);
}
if (changes & BSS_CHANGED_ERP_PREAMBLE) {
if (bss_conf->use_short_preamble)
priv->preamble = CMD_TYPE_SHORT_PREAMBLE;
else
priv->preamble = CMD_TYPE_LONG_PREAMBLE;
lbtf_set_radio_control(priv);
}
lbtf_deb_leave(LBTF_DEB_MACOPS);
}
static int lbtf_op_get_survey(struct ieee80211_hw *hw, int idx,
struct survey_info *survey)
{
struct lbtf_private *priv = hw->priv;
struct ieee80211_conf *conf = &hw->conf;
if (idx != 0)
return -ENOENT;
survey->channel = conf->channel;
survey->filled = SURVEY_INFO_NOISE_DBM;
survey->noise = priv->noise;
return 0;
}
static const struct ieee80211_ops lbtf_ops = {
.tx = lbtf_op_tx,
.start = lbtf_op_start,
.stop = lbtf_op_stop,
.add_interface = lbtf_op_add_interface,
.remove_interface = lbtf_op_remove_interface,
.config = lbtf_op_config,
.prepare_multicast = lbtf_op_prepare_multicast,
.configure_filter = lbtf_op_configure_filter,
.bss_info_changed = lbtf_op_bss_info_changed,
.get_survey = lbtf_op_get_survey,
};
int lbtf_rx(struct lbtf_private *priv, struct sk_buff *skb)
{
struct ieee80211_rx_status stats;
struct rxpd *prxpd;
int need_padding;
unsigned int flags;
struct ieee80211_hdr *hdr;
lbtf_deb_enter(LBTF_DEB_RX);
prxpd = (struct rxpd *) skb->data;
memset(&stats, 0, sizeof(stats));
if (!(prxpd->status & cpu_to_le16(MRVDRV_RXPD_STATUS_OK)))
stats.flag |= RX_FLAG_FAILED_FCS_CRC;
stats.freq = priv->cur_freq;
stats.band = IEEE80211_BAND_2GHZ;
stats.signal = prxpd->snr;
priv->noise = prxpd->nf;
/* Marvell rate index has a hole at value 4 */
if (prxpd->rx_rate > 4)
--prxpd->rx_rate;
stats.rate_idx = prxpd->rx_rate;
skb_pull(skb, sizeof(struct rxpd));
hdr = (struct ieee80211_hdr *)skb->data;
flags = le32_to_cpu(*(__le32 *)(skb->data + 4));
need_padding = ieee80211_is_data_qos(hdr->frame_control);
need_padding ^= ieee80211_has_a4(hdr->frame_control);
need_padding ^= ieee80211_is_data_qos(hdr->frame_control) &&
(*ieee80211_get_qos_ctl(hdr) &
IEEE80211_QOS_CONTROL_A_MSDU_PRESENT);
if (need_padding) {
memmove(skb->data + 2, skb->data, skb->len);
skb_reserve(skb, 2);
}
memcpy(IEEE80211_SKB_RXCB(skb), &stats, sizeof(stats));
lbtf_deb_rx("rx data: skb->len-sizeof(RxPd) = %d-%zd = %zd\n",
skb->len, sizeof(struct rxpd), skb->len - sizeof(struct rxpd));
lbtf_deb_hex(LBTF_DEB_RX, "RX Data", skb->data,
min_t(unsigned int, skb->len, 100));
ieee80211_rx_irqsafe(priv->hw, skb);
lbtf_deb_leave(LBTF_DEB_RX);
return 0;
}
EXPORT_SYMBOL_GPL(lbtf_rx);
/**
* lbtf_add_card: Add and initialize the card, no fw upload yet.
*
* @card A pointer to card
*
* Returns: pointer to struct lbtf_priv.
*/
struct lbtf_private *lbtf_add_card(void *card, struct device *dmdev)
{
struct ieee80211_hw *hw;
struct lbtf_private *priv = NULL;
lbtf_deb_enter(LBTF_DEB_MAIN);
hw = ieee80211_alloc_hw(sizeof(struct lbtf_private), &lbtf_ops);
if (!hw)
goto done;
priv = hw->priv;
if (lbtf_init_adapter(priv))
goto err_init_adapter;
priv->hw = hw;
priv->card = card;
priv->tx_skb = NULL;
hw->queues = 1;
hw->flags = IEEE80211_HW_HOST_BROADCAST_PS_BUFFERING;
hw->extra_tx_headroom = sizeof(struct txpd);
memcpy(priv->channels, lbtf_channels, sizeof(lbtf_channels));
memcpy(priv->rates, lbtf_rates, sizeof(lbtf_rates));
priv->band.n_bitrates = ARRAY_SIZE(lbtf_rates);
priv->band.bitrates = priv->rates;
priv->band.n_channels = ARRAY_SIZE(lbtf_channels);
priv->band.channels = priv->channels;
hw->wiphy->bands[IEEE80211_BAND_2GHZ] = &priv->band;
hw->wiphy->interface_modes =
BIT(NL80211_IFTYPE_STATION) |
BIT(NL80211_IFTYPE_ADHOC);
skb_queue_head_init(&priv->bc_ps_buf);
SET_IEEE80211_DEV(hw, dmdev);
INIT_WORK(&priv->cmd_work, lbtf_cmd_work);
INIT_WORK(&priv->tx_work, lbtf_tx_work);
if (ieee80211_register_hw(hw))
goto err_init_adapter;
goto done;
err_init_adapter:
lbtf_free_adapter(priv);
ieee80211_free_hw(hw);
priv = NULL;
done:
lbtf_deb_leave_args(LBTF_DEB_MAIN, "priv %p", priv);
return priv;
}
EXPORT_SYMBOL_GPL(lbtf_add_card);
int lbtf_remove_card(struct lbtf_private *priv)
{
struct ieee80211_hw *hw = priv->hw;
lbtf_deb_enter(LBTF_DEB_MAIN);
priv->surpriseremoved = 1;
del_timer(&priv->command_timer);
lbtf_free_adapter(priv);
priv->hw = NULL;
ieee80211_unregister_hw(hw);
ieee80211_free_hw(hw);
lbtf_deb_leave(LBTF_DEB_MAIN);
return 0;
}
EXPORT_SYMBOL_GPL(lbtf_remove_card);
void lbtf_send_tx_feedback(struct lbtf_private *priv, u8 retrycnt, u8 fail)
{
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(priv->tx_skb);
ieee80211_tx_info_clear_status(info);
/*
* Commented out, otherwise we never go beyond 1Mbit/s using mac80211
* default pid rc algorithm.
*
* info->status.retry_count = MRVL_DEFAULT_RETRIES - retrycnt;
*/
if (!(info->flags & IEEE80211_TX_CTL_NO_ACK) && !fail)
info->flags |= IEEE80211_TX_STAT_ACK;
skb_pull(priv->tx_skb, sizeof(struct txpd));
ieee80211_tx_status_irqsafe(priv->hw, priv->tx_skb);
priv->tx_skb = NULL;
if (!priv->skb_to_tx && skb_queue_empty(&priv->bc_ps_buf))
ieee80211_wake_queues(priv->hw);
else
queue_work(lbtf_wq, &priv->tx_work);
}
EXPORT_SYMBOL_GPL(lbtf_send_tx_feedback);
void lbtf_bcn_sent(struct lbtf_private *priv)
{
struct sk_buff *skb = NULL;
if (priv->vif->type != NL80211_IFTYPE_AP)
return;
if (skb_queue_empty(&priv->bc_ps_buf)) {
bool tx_buff_bc = 0;
while ((skb = ieee80211_get_buffered_bc(priv->hw, priv->vif))) {
skb_queue_tail(&priv->bc_ps_buf, skb);
tx_buff_bc = 1;
}
if (tx_buff_bc) {
ieee80211_stop_queues(priv->hw);
queue_work(lbtf_wq, &priv->tx_work);
}
}
skb = ieee80211_beacon_get(priv->hw, priv->vif);
if (skb) {
lbtf_beacon_set(priv, skb);
kfree_skb(skb);
}
}
EXPORT_SYMBOL_GPL(lbtf_bcn_sent);
static int __init lbtf_init_module(void)
{
lbtf_deb_enter(LBTF_DEB_MAIN);
lbtf_wq = create_workqueue("libertastf");
if (lbtf_wq == NULL) {
printk(KERN_ERR "libertastf: couldn't create workqueue\n");
return -ENOMEM;
}
lbtf_deb_leave(LBTF_DEB_MAIN);
return 0;
}
static void __exit lbtf_exit_module(void)
{
lbtf_deb_enter(LBTF_DEB_MAIN);
destroy_workqueue(lbtf_wq);
lbtf_deb_leave(LBTF_DEB_MAIN);
}
module_init(lbtf_init_module);
module_exit(lbtf_exit_module);
MODULE_DESCRIPTION("Libertas WLAN Thinfirm Driver Library");
MODULE_AUTHOR("Cozybit Inc.");
MODULE_LICENSE("GPL");