linux_dsm_epyc7002/drivers/net/wireless/intersil/hostap/hostap_main.c
David S. Miller cf124db566 net: Fix inconsistent teardown and release of private netdev state.
Network devices can allocate reasources and private memory using
netdev_ops->ndo_init().  However, the release of these resources
can occur in one of two different places.

Either netdev_ops->ndo_uninit() or netdev->destructor().

The decision of which operation frees the resources depends upon
whether it is necessary for all netdev refs to be released before it
is safe to perform the freeing.

netdev_ops->ndo_uninit() presumably can occur right after the
NETDEV_UNREGISTER notifier completes and the unicast and multicast
address lists are flushed.

netdev->destructor(), on the other hand, does not run until the
netdev references all go away.

Further complicating the situation is that netdev->destructor()
almost universally does also a free_netdev().

This creates a problem for the logic in register_netdevice().
Because all callers of register_netdevice() manage the freeing
of the netdev, and invoke free_netdev(dev) if register_netdevice()
fails.

If netdev_ops->ndo_init() succeeds, but something else fails inside
of register_netdevice(), it does call ndo_ops->ndo_uninit().  But
it is not able to invoke netdev->destructor().

This is because netdev->destructor() will do a free_netdev() and
then the caller of register_netdevice() will do the same.

However, this means that the resources that would normally be released
by netdev->destructor() will not be.

Over the years drivers have added local hacks to deal with this, by
invoking their destructor parts by hand when register_netdevice()
fails.

Many drivers do not try to deal with this, and instead we have leaks.

Let's close this hole by formalizing the distinction between what
private things need to be freed up by netdev->destructor() and whether
the driver needs unregister_netdevice() to perform the free_netdev().

netdev->priv_destructor() performs all actions to free up the private
resources that used to be freed by netdev->destructor(), except for
free_netdev().

netdev->needs_free_netdev is a boolean that indicates whether
free_netdev() should be done at the end of unregister_netdevice().

Now, register_netdevice() can sanely release all resources after
ndo_ops->ndo_init() succeeds, by invoking both ndo_ops->ndo_uninit()
and netdev->priv_destructor().

And at the end of unregister_netdevice(), we invoke
netdev->priv_destructor() and optionally call free_netdev().

Signed-off-by: David S. Miller <davem@davemloft.net>
2017-06-07 15:53:24 -04:00

1130 lines
28 KiB
C

/*
* Host AP (software wireless LAN access point) driver for
* Intersil Prism2/2.5/3 - hostap.o module, common routines
*
* Copyright (c) 2001-2002, SSH Communications Security Corp and Jouni Malinen
* <j@w1.fi>
* Copyright (c) 2002-2005, Jouni Malinen <j@w1.fi>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation. See README and COPYING for
* more details.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/proc_fs.h>
#include <linux/if_arp.h>
#include <linux/delay.h>
#include <linux/random.h>
#include <linux/workqueue.h>
#include <linux/kmod.h>
#include <linux/rtnetlink.h>
#include <linux/wireless.h>
#include <linux/etherdevice.h>
#include <net/net_namespace.h>
#include <net/iw_handler.h>
#include <net/lib80211.h>
#include <linux/uaccess.h>
#include "hostap_wlan.h"
#include "hostap_80211.h"
#include "hostap_ap.h"
#include "hostap.h"
MODULE_AUTHOR("Jouni Malinen");
MODULE_DESCRIPTION("Host AP common routines");
MODULE_LICENSE("GPL");
#define TX_TIMEOUT (2 * HZ)
#define PRISM2_MAX_FRAME_SIZE 2304
#define PRISM2_MIN_MTU 256
/* FIX: */
#define PRISM2_MAX_MTU (PRISM2_MAX_FRAME_SIZE - (6 /* LLC */ + 8 /* WEP */))
struct net_device * hostap_add_interface(struct local_info *local,
int type, int rtnl_locked,
const char *prefix,
const char *name)
{
struct net_device *dev, *mdev;
struct hostap_interface *iface;
int ret;
dev = alloc_etherdev(sizeof(struct hostap_interface));
if (dev == NULL)
return NULL;
iface = netdev_priv(dev);
iface->dev = dev;
iface->local = local;
iface->type = type;
list_add(&iface->list, &local->hostap_interfaces);
mdev = local->dev;
eth_hw_addr_inherit(dev, mdev);
dev->base_addr = mdev->base_addr;
dev->irq = mdev->irq;
dev->mem_start = mdev->mem_start;
dev->mem_end = mdev->mem_end;
hostap_setup_dev(dev, local, type);
dev->needs_free_netdev = true;
sprintf(dev->name, "%s%s", prefix, name);
if (!rtnl_locked)
rtnl_lock();
SET_NETDEV_DEV(dev, mdev->dev.parent);
ret = register_netdevice(dev);
if (!rtnl_locked)
rtnl_unlock();
if (ret < 0) {
printk(KERN_WARNING "%s: failed to add new netdevice!\n",
dev->name);
free_netdev(dev);
return NULL;
}
printk(KERN_DEBUG "%s: registered netdevice %s\n",
mdev->name, dev->name);
return dev;
}
void hostap_remove_interface(struct net_device *dev, int rtnl_locked,
int remove_from_list)
{
struct hostap_interface *iface;
if (!dev)
return;
iface = netdev_priv(dev);
if (remove_from_list) {
list_del(&iface->list);
}
if (dev == iface->local->ddev)
iface->local->ddev = NULL;
else if (dev == iface->local->apdev)
iface->local->apdev = NULL;
else if (dev == iface->local->stadev)
iface->local->stadev = NULL;
if (rtnl_locked)
unregister_netdevice(dev);
else
unregister_netdev(dev);
/* dev->destructor = free_netdev() will free the device data, including
* private data, when removing the device */
}
static inline int prism2_wds_special_addr(u8 *addr)
{
if (addr[0] || addr[1] || addr[2] || addr[3] || addr[4] || addr[5])
return 0;
return 1;
}
int prism2_wds_add(local_info_t *local, u8 *remote_addr,
int rtnl_locked)
{
struct net_device *dev;
struct list_head *ptr;
struct hostap_interface *iface, *empty, *match;
empty = match = NULL;
read_lock_bh(&local->iface_lock);
list_for_each(ptr, &local->hostap_interfaces) {
iface = list_entry(ptr, struct hostap_interface, list);
if (iface->type != HOSTAP_INTERFACE_WDS)
continue;
if (prism2_wds_special_addr(iface->u.wds.remote_addr))
empty = iface;
else if (ether_addr_equal(iface->u.wds.remote_addr, remote_addr)) {
match = iface;
break;
}
}
if (!match && empty && !prism2_wds_special_addr(remote_addr)) {
/* take pre-allocated entry into use */
memcpy(empty->u.wds.remote_addr, remote_addr, ETH_ALEN);
read_unlock_bh(&local->iface_lock);
printk(KERN_DEBUG "%s: using pre-allocated WDS netdevice %s\n",
local->dev->name, empty->dev->name);
return 0;
}
read_unlock_bh(&local->iface_lock);
if (!prism2_wds_special_addr(remote_addr)) {
if (match)
return -EEXIST;
hostap_add_sta(local->ap, remote_addr);
}
if (local->wds_connections >= local->wds_max_connections)
return -ENOBUFS;
/* verify that there is room for wds# postfix in the interface name */
if (strlen(local->dev->name) >= IFNAMSIZ - 5) {
printk(KERN_DEBUG "'%s' too long base device name\n",
local->dev->name);
return -EINVAL;
}
dev = hostap_add_interface(local, HOSTAP_INTERFACE_WDS, rtnl_locked,
local->ddev->name, "wds%d");
if (dev == NULL)
return -ENOMEM;
iface = netdev_priv(dev);
memcpy(iface->u.wds.remote_addr, remote_addr, ETH_ALEN);
local->wds_connections++;
return 0;
}
int prism2_wds_del(local_info_t *local, u8 *remote_addr,
int rtnl_locked, int do_not_remove)
{
unsigned long flags;
struct list_head *ptr;
struct hostap_interface *iface, *selected = NULL;
write_lock_irqsave(&local->iface_lock, flags);
list_for_each(ptr, &local->hostap_interfaces) {
iface = list_entry(ptr, struct hostap_interface, list);
if (iface->type != HOSTAP_INTERFACE_WDS)
continue;
if (ether_addr_equal(iface->u.wds.remote_addr, remote_addr)) {
selected = iface;
break;
}
}
if (selected && !do_not_remove)
list_del(&selected->list);
write_unlock_irqrestore(&local->iface_lock, flags);
if (selected) {
if (do_not_remove)
eth_zero_addr(selected->u.wds.remote_addr);
else {
hostap_remove_interface(selected->dev, rtnl_locked, 0);
local->wds_connections--;
}
}
return selected ? 0 : -ENODEV;
}
u16 hostap_tx_callback_register(local_info_t *local,
void (*func)(struct sk_buff *, int ok, void *),
void *data)
{
unsigned long flags;
struct hostap_tx_callback_info *entry;
entry = kmalloc(sizeof(*entry), GFP_KERNEL);
if (entry == NULL)
return 0;
entry->func = func;
entry->data = data;
spin_lock_irqsave(&local->lock, flags);
entry->idx = local->tx_callback ? local->tx_callback->idx + 1 : 1;
entry->next = local->tx_callback;
local->tx_callback = entry;
spin_unlock_irqrestore(&local->lock, flags);
return entry->idx;
}
int hostap_tx_callback_unregister(local_info_t *local, u16 idx)
{
unsigned long flags;
struct hostap_tx_callback_info *cb, *prev = NULL;
spin_lock_irqsave(&local->lock, flags);
cb = local->tx_callback;
while (cb != NULL && cb->idx != idx) {
prev = cb;
cb = cb->next;
}
if (cb) {
if (prev == NULL)
local->tx_callback = cb->next;
else
prev->next = cb->next;
kfree(cb);
}
spin_unlock_irqrestore(&local->lock, flags);
return cb ? 0 : -1;
}
/* val is in host byte order */
int hostap_set_word(struct net_device *dev, int rid, u16 val)
{
struct hostap_interface *iface;
__le16 tmp = cpu_to_le16(val);
iface = netdev_priv(dev);
return iface->local->func->set_rid(dev, rid, &tmp, 2);
}
int hostap_set_string(struct net_device *dev, int rid, const char *val)
{
struct hostap_interface *iface;
char buf[MAX_SSID_LEN + 2];
int len;
iface = netdev_priv(dev);
len = strlen(val);
if (len > MAX_SSID_LEN)
return -1;
memset(buf, 0, sizeof(buf));
buf[0] = len; /* little endian 16 bit word */
memcpy(buf + 2, val, len);
return iface->local->func->set_rid(dev, rid, &buf, MAX_SSID_LEN + 2);
}
u16 hostap_get_porttype(local_info_t *local)
{
if (local->iw_mode == IW_MODE_ADHOC && local->pseudo_adhoc)
return HFA384X_PORTTYPE_PSEUDO_IBSS;
if (local->iw_mode == IW_MODE_ADHOC)
return HFA384X_PORTTYPE_IBSS;
if (local->iw_mode == IW_MODE_INFRA)
return HFA384X_PORTTYPE_BSS;
if (local->iw_mode == IW_MODE_REPEAT)
return HFA384X_PORTTYPE_WDS;
if (local->iw_mode == IW_MODE_MONITOR)
return HFA384X_PORTTYPE_PSEUDO_IBSS;
return HFA384X_PORTTYPE_HOSTAP;
}
int hostap_set_encryption(local_info_t *local)
{
u16 val, old_val;
int i, keylen, len, idx;
char keybuf[WEP_KEY_LEN + 1];
enum { NONE, WEP, OTHER } encrypt_type;
idx = local->crypt_info.tx_keyidx;
if (local->crypt_info.crypt[idx] == NULL ||
local->crypt_info.crypt[idx]->ops == NULL)
encrypt_type = NONE;
else if (strcmp(local->crypt_info.crypt[idx]->ops->name, "WEP") == 0)
encrypt_type = WEP;
else
encrypt_type = OTHER;
if (local->func->get_rid(local->dev, HFA384X_RID_CNFWEPFLAGS, &val, 2,
1) < 0) {
printk(KERN_DEBUG "Could not read current WEP flags.\n");
goto fail;
}
le16_to_cpus(&val);
old_val = val;
if (encrypt_type != NONE || local->privacy_invoked)
val |= HFA384X_WEPFLAGS_PRIVACYINVOKED;
else
val &= ~HFA384X_WEPFLAGS_PRIVACYINVOKED;
if (local->open_wep || encrypt_type == NONE ||
((local->ieee_802_1x || local->wpa) && local->host_decrypt))
val &= ~HFA384X_WEPFLAGS_EXCLUDEUNENCRYPTED;
else
val |= HFA384X_WEPFLAGS_EXCLUDEUNENCRYPTED;
if ((encrypt_type != NONE || local->privacy_invoked) &&
(encrypt_type == OTHER || local->host_encrypt))
val |= HFA384X_WEPFLAGS_HOSTENCRYPT;
else
val &= ~HFA384X_WEPFLAGS_HOSTENCRYPT;
if ((encrypt_type != NONE || local->privacy_invoked) &&
(encrypt_type == OTHER || local->host_decrypt))
val |= HFA384X_WEPFLAGS_HOSTDECRYPT;
else
val &= ~HFA384X_WEPFLAGS_HOSTDECRYPT;
if (val != old_val &&
hostap_set_word(local->dev, HFA384X_RID_CNFWEPFLAGS, val)) {
printk(KERN_DEBUG "Could not write new WEP flags (0x%x)\n",
val);
goto fail;
}
if (encrypt_type != WEP)
return 0;
/* 104-bit support seems to require that all the keys are set to the
* same keylen */
keylen = 6; /* first 5 octets */
len = local->crypt_info.crypt[idx]->ops->get_key(keybuf, sizeof(keybuf), NULL,
local->crypt_info.crypt[idx]->priv);
if (idx >= 0 && idx < WEP_KEYS && len > 5)
keylen = WEP_KEY_LEN + 1; /* first 13 octets */
for (i = 0; i < WEP_KEYS; i++) {
memset(keybuf, 0, sizeof(keybuf));
if (local->crypt_info.crypt[i]) {
(void) local->crypt_info.crypt[i]->ops->get_key(
keybuf, sizeof(keybuf),
NULL, local->crypt_info.crypt[i]->priv);
}
if (local->func->set_rid(local->dev,
HFA384X_RID_CNFDEFAULTKEY0 + i,
keybuf, keylen)) {
printk(KERN_DEBUG "Could not set key %d (len=%d)\n",
i, keylen);
goto fail;
}
}
if (hostap_set_word(local->dev, HFA384X_RID_CNFWEPDEFAULTKEYID, idx)) {
printk(KERN_DEBUG "Could not set default keyid %d\n", idx);
goto fail;
}
return 0;
fail:
printk(KERN_DEBUG "%s: encryption setup failed\n", local->dev->name);
return -1;
}
int hostap_set_antsel(local_info_t *local)
{
u16 val;
int ret = 0;
if (local->antsel_tx != HOSTAP_ANTSEL_DO_NOT_TOUCH &&
local->func->cmd(local->dev, HFA384X_CMDCODE_READMIF,
HFA386X_CR_TX_CONFIGURE,
NULL, &val) == 0) {
val &= ~(BIT(2) | BIT(1));
switch (local->antsel_tx) {
case HOSTAP_ANTSEL_DIVERSITY:
val |= BIT(1);
break;
case HOSTAP_ANTSEL_LOW:
break;
case HOSTAP_ANTSEL_HIGH:
val |= BIT(2);
break;
}
if (local->func->cmd(local->dev, HFA384X_CMDCODE_WRITEMIF,
HFA386X_CR_TX_CONFIGURE, &val, NULL)) {
printk(KERN_INFO "%s: setting TX AntSel failed\n",
local->dev->name);
ret = -1;
}
}
if (local->antsel_rx != HOSTAP_ANTSEL_DO_NOT_TOUCH &&
local->func->cmd(local->dev, HFA384X_CMDCODE_READMIF,
HFA386X_CR_RX_CONFIGURE,
NULL, &val) == 0) {
val &= ~(BIT(1) | BIT(0));
switch (local->antsel_rx) {
case HOSTAP_ANTSEL_DIVERSITY:
break;
case HOSTAP_ANTSEL_LOW:
val |= BIT(0);
break;
case HOSTAP_ANTSEL_HIGH:
val |= BIT(0) | BIT(1);
break;
}
if (local->func->cmd(local->dev, HFA384X_CMDCODE_WRITEMIF,
HFA386X_CR_RX_CONFIGURE, &val, NULL)) {
printk(KERN_INFO "%s: setting RX AntSel failed\n",
local->dev->name);
ret = -1;
}
}
return ret;
}
int hostap_set_roaming(local_info_t *local)
{
u16 val;
switch (local->host_roaming) {
case 1:
val = HFA384X_ROAMING_HOST;
break;
case 2:
val = HFA384X_ROAMING_DISABLED;
break;
case 0:
default:
val = HFA384X_ROAMING_FIRMWARE;
break;
}
return hostap_set_word(local->dev, HFA384X_RID_CNFROAMINGMODE, val);
}
int hostap_set_auth_algs(local_info_t *local)
{
int val = local->auth_algs;
/* At least STA f/w v0.6.2 seems to have issues with cnfAuthentication
* set to include both Open and Shared Key flags. It tries to use
* Shared Key authentication in that case even if WEP keys are not
* configured.. STA f/w v0.7.6 is able to handle such configuration,
* but it is unknown when this was fixed between 0.6.2 .. 0.7.6. */
if (local->sta_fw_ver < PRISM2_FW_VER(0,7,0) &&
val != PRISM2_AUTH_OPEN && val != PRISM2_AUTH_SHARED_KEY)
val = PRISM2_AUTH_OPEN;
if (hostap_set_word(local->dev, HFA384X_RID_CNFAUTHENTICATION, val)) {
printk(KERN_INFO "%s: cnfAuthentication setting to 0x%x "
"failed\n", local->dev->name, local->auth_algs);
return -EINVAL;
}
return 0;
}
void hostap_dump_rx_header(const char *name, const struct hfa384x_rx_frame *rx)
{
u16 status, fc;
status = __le16_to_cpu(rx->status);
printk(KERN_DEBUG "%s: RX status=0x%04x (port=%d, type=%d, "
"fcserr=%d) silence=%d signal=%d rate=%d rxflow=%d; "
"jiffies=%ld\n",
name, status, (status >> 8) & 0x07, status >> 13, status & 1,
rx->silence, rx->signal, rx->rate, rx->rxflow, jiffies);
fc = __le16_to_cpu(rx->frame_control);
printk(KERN_DEBUG " FC=0x%04x (type=%d:%d) dur=0x%04x seq=0x%04x "
"data_len=%d%s%s\n",
fc, (fc & IEEE80211_FCTL_FTYPE) >> 2,
(fc & IEEE80211_FCTL_STYPE) >> 4,
__le16_to_cpu(rx->duration_id), __le16_to_cpu(rx->seq_ctrl),
__le16_to_cpu(rx->data_len),
fc & IEEE80211_FCTL_TODS ? " [ToDS]" : "",
fc & IEEE80211_FCTL_FROMDS ? " [FromDS]" : "");
printk(KERN_DEBUG " A1=%pM A2=%pM A3=%pM A4=%pM\n",
rx->addr1, rx->addr2, rx->addr3, rx->addr4);
printk(KERN_DEBUG " dst=%pM src=%pM len=%d\n",
rx->dst_addr, rx->src_addr,
__be16_to_cpu(rx->len));
}
void hostap_dump_tx_header(const char *name, const struct hfa384x_tx_frame *tx)
{
u16 fc;
printk(KERN_DEBUG "%s: TX status=0x%04x retry_count=%d tx_rate=%d "
"tx_control=0x%04x; jiffies=%ld\n",
name, __le16_to_cpu(tx->status), tx->retry_count, tx->tx_rate,
__le16_to_cpu(tx->tx_control), jiffies);
fc = __le16_to_cpu(tx->frame_control);
printk(KERN_DEBUG " FC=0x%04x (type=%d:%d) dur=0x%04x seq=0x%04x "
"data_len=%d%s%s\n",
fc, (fc & IEEE80211_FCTL_FTYPE) >> 2,
(fc & IEEE80211_FCTL_STYPE) >> 4,
__le16_to_cpu(tx->duration_id), __le16_to_cpu(tx->seq_ctrl),
__le16_to_cpu(tx->data_len),
fc & IEEE80211_FCTL_TODS ? " [ToDS]" : "",
fc & IEEE80211_FCTL_FROMDS ? " [FromDS]" : "");
printk(KERN_DEBUG " A1=%pM A2=%pM A3=%pM A4=%pM\n",
tx->addr1, tx->addr2, tx->addr3, tx->addr4);
printk(KERN_DEBUG " dst=%pM src=%pM len=%d\n",
tx->dst_addr, tx->src_addr,
__be16_to_cpu(tx->len));
}
static int hostap_80211_header_parse(const struct sk_buff *skb,
unsigned char *haddr)
{
memcpy(haddr, skb_mac_header(skb) + 10, ETH_ALEN); /* addr2 */
return ETH_ALEN;
}
int hostap_80211_get_hdrlen(__le16 fc)
{
if (ieee80211_is_data(fc) && ieee80211_has_a4 (fc))
return 30; /* Addr4 */
else if (ieee80211_is_cts(fc) || ieee80211_is_ack(fc))
return 10;
else if (ieee80211_is_ctl(fc))
return 16;
return 24;
}
static int prism2_close(struct net_device *dev)
{
struct hostap_interface *iface;
local_info_t *local;
PDEBUG(DEBUG_FLOW, "%s: prism2_close\n", dev->name);
iface = netdev_priv(dev);
local = iface->local;
if (dev == local->ddev) {
prism2_sta_deauth(local, WLAN_REASON_DEAUTH_LEAVING);
}
#ifndef PRISM2_NO_KERNEL_IEEE80211_MGMT
if (!local->hostapd && dev == local->dev &&
(!local->func->card_present || local->func->card_present(local)) &&
local->hw_ready && local->ap && local->iw_mode == IW_MODE_MASTER)
hostap_deauth_all_stas(dev, local->ap, 1);
#endif /* PRISM2_NO_KERNEL_IEEE80211_MGMT */
if (dev == local->dev) {
local->func->hw_shutdown(dev, HOSTAP_HW_ENABLE_CMDCOMPL);
}
if (netif_running(dev)) {
netif_stop_queue(dev);
netif_device_detach(dev);
}
cancel_work_sync(&local->reset_queue);
cancel_work_sync(&local->set_multicast_list_queue);
cancel_work_sync(&local->set_tim_queue);
#ifndef PRISM2_NO_STATION_MODES
cancel_work_sync(&local->info_queue);
#endif
cancel_work_sync(&local->comms_qual_update);
module_put(local->hw_module);
local->num_dev_open--;
if (dev != local->dev && local->dev->flags & IFF_UP &&
local->master_dev_auto_open && local->num_dev_open == 1) {
/* Close master radio interface automatically if it was also
* opened automatically and we are now closing the last
* remaining non-master device. */
dev_close(local->dev);
}
return 0;
}
static int prism2_open(struct net_device *dev)
{
struct hostap_interface *iface;
local_info_t *local;
PDEBUG(DEBUG_FLOW, "%s: prism2_open\n", dev->name);
iface = netdev_priv(dev);
local = iface->local;
if (local->no_pri) {
printk(KERN_DEBUG "%s: could not set interface UP - no PRI "
"f/w\n", dev->name);
return -ENODEV;
}
if ((local->func->card_present && !local->func->card_present(local)) ||
local->hw_downloading)
return -ENODEV;
if (!try_module_get(local->hw_module))
return -ENODEV;
local->num_dev_open++;
if (!local->dev_enabled && local->func->hw_enable(dev, 1)) {
printk(KERN_WARNING "%s: could not enable MAC port\n",
dev->name);
prism2_close(dev);
return -ENODEV;
}
if (!local->dev_enabled)
prism2_callback(local, PRISM2_CALLBACK_ENABLE);
local->dev_enabled = 1;
if (dev != local->dev && !(local->dev->flags & IFF_UP)) {
/* Master radio interface is needed for all operation, so open
* it automatically when any virtual net_device is opened. */
local->master_dev_auto_open = 1;
dev_open(local->dev);
}
netif_device_attach(dev);
netif_start_queue(dev);
return 0;
}
static int prism2_set_mac_address(struct net_device *dev, void *p)
{
struct hostap_interface *iface;
local_info_t *local;
struct list_head *ptr;
struct sockaddr *addr = p;
iface = netdev_priv(dev);
local = iface->local;
if (local->func->set_rid(dev, HFA384X_RID_CNFOWNMACADDR, addr->sa_data,
ETH_ALEN) < 0 || local->func->reset_port(dev))
return -EINVAL;
read_lock_bh(&local->iface_lock);
list_for_each(ptr, &local->hostap_interfaces) {
iface = list_entry(ptr, struct hostap_interface, list);
memcpy(iface->dev->dev_addr, addr->sa_data, ETH_ALEN);
}
memcpy(local->dev->dev_addr, addr->sa_data, ETH_ALEN);
read_unlock_bh(&local->iface_lock);
return 0;
}
/* TODO: to be further implemented as soon as Prism2 fully supports
* GroupAddresses and correct documentation is available */
void hostap_set_multicast_list_queue(struct work_struct *work)
{
local_info_t *local =
container_of(work, local_info_t, set_multicast_list_queue);
struct net_device *dev = local->dev;
if (hostap_set_word(dev, HFA384X_RID_PROMISCUOUSMODE,
local->is_promisc)) {
printk(KERN_INFO "%s: %sabling promiscuous mode failed\n",
dev->name, local->is_promisc ? "en" : "dis");
}
}
static void hostap_set_multicast_list(struct net_device *dev)
{
#if 0
/* FIX: promiscuous mode seems to be causing a lot of problems with
* some station firmware versions (FCSErr frames, invalid MACPort, etc.
* corrupted incoming frames). This code is now commented out while the
* problems are investigated. */
struct hostap_interface *iface;
local_info_t *local;
iface = netdev_priv(dev);
local = iface->local;
if ((dev->flags & IFF_ALLMULTI) || (dev->flags & IFF_PROMISC)) {
local->is_promisc = 1;
} else {
local->is_promisc = 0;
}
schedule_work(&local->set_multicast_list_queue);
#endif
}
static void prism2_tx_timeout(struct net_device *dev)
{
struct hostap_interface *iface;
local_info_t *local;
struct hfa384x_regs regs;
iface = netdev_priv(dev);
local = iface->local;
printk(KERN_WARNING "%s Tx timed out! Resetting card\n", dev->name);
netif_stop_queue(local->dev);
local->func->read_regs(dev, &regs);
printk(KERN_DEBUG "%s: CMD=%04x EVSTAT=%04x "
"OFFSET0=%04x OFFSET1=%04x SWSUPPORT0=%04x\n",
dev->name, regs.cmd, regs.evstat, regs.offset0, regs.offset1,
regs.swsupport0);
local->func->schedule_reset(local);
}
const struct header_ops hostap_80211_ops = {
.create = eth_header,
.cache = eth_header_cache,
.cache_update = eth_header_cache_update,
.parse = hostap_80211_header_parse,
};
EXPORT_SYMBOL(hostap_80211_ops);
static const struct net_device_ops hostap_netdev_ops = {
.ndo_start_xmit = hostap_data_start_xmit,
.ndo_open = prism2_open,
.ndo_stop = prism2_close,
.ndo_do_ioctl = hostap_ioctl,
.ndo_set_mac_address = prism2_set_mac_address,
.ndo_set_rx_mode = hostap_set_multicast_list,
.ndo_tx_timeout = prism2_tx_timeout,
.ndo_validate_addr = eth_validate_addr,
};
static const struct net_device_ops hostap_mgmt_netdev_ops = {
.ndo_start_xmit = hostap_mgmt_start_xmit,
.ndo_open = prism2_open,
.ndo_stop = prism2_close,
.ndo_do_ioctl = hostap_ioctl,
.ndo_set_mac_address = prism2_set_mac_address,
.ndo_set_rx_mode = hostap_set_multicast_list,
.ndo_tx_timeout = prism2_tx_timeout,
.ndo_validate_addr = eth_validate_addr,
};
static const struct net_device_ops hostap_master_ops = {
.ndo_start_xmit = hostap_master_start_xmit,
.ndo_open = prism2_open,
.ndo_stop = prism2_close,
.ndo_do_ioctl = hostap_ioctl,
.ndo_set_mac_address = prism2_set_mac_address,
.ndo_set_rx_mode = hostap_set_multicast_list,
.ndo_tx_timeout = prism2_tx_timeout,
.ndo_validate_addr = eth_validate_addr,
};
void hostap_setup_dev(struct net_device *dev, local_info_t *local,
int type)
{
struct hostap_interface *iface;
iface = netdev_priv(dev);
ether_setup(dev);
dev->min_mtu = PRISM2_MIN_MTU;
dev->max_mtu = PRISM2_MAX_MTU;
dev->priv_flags &= ~IFF_TX_SKB_SHARING;
/* kernel callbacks */
if (iface) {
/* Currently, we point to the proper spy_data only on
* the main_dev. This could be fixed. Jean II */
iface->wireless_data.spy_data = &iface->spy_data;
dev->wireless_data = &iface->wireless_data;
}
dev->wireless_handlers = &hostap_iw_handler_def;
dev->watchdog_timeo = TX_TIMEOUT;
switch(type) {
case HOSTAP_INTERFACE_AP:
dev->priv_flags |= IFF_NO_QUEUE; /* use main radio device queue */
dev->netdev_ops = &hostap_mgmt_netdev_ops;
dev->type = ARPHRD_IEEE80211;
dev->header_ops = &hostap_80211_ops;
break;
case HOSTAP_INTERFACE_MASTER:
dev->netdev_ops = &hostap_master_ops;
break;
default:
dev->priv_flags |= IFF_NO_QUEUE; /* use main radio device queue */
dev->netdev_ops = &hostap_netdev_ops;
}
dev->mtu = local->mtu;
dev->ethtool_ops = &prism2_ethtool_ops;
}
static int hostap_enable_hostapd(local_info_t *local, int rtnl_locked)
{
struct net_device *dev = local->dev;
if (local->apdev)
return -EEXIST;
printk(KERN_DEBUG "%s: enabling hostapd mode\n", dev->name);
local->apdev = hostap_add_interface(local, HOSTAP_INTERFACE_AP,
rtnl_locked, local->ddev->name,
"ap");
if (local->apdev == NULL)
return -ENOMEM;
return 0;
}
static int hostap_disable_hostapd(local_info_t *local, int rtnl_locked)
{
struct net_device *dev = local->dev;
printk(KERN_DEBUG "%s: disabling hostapd mode\n", dev->name);
hostap_remove_interface(local->apdev, rtnl_locked, 1);
local->apdev = NULL;
return 0;
}
static int hostap_enable_hostapd_sta(local_info_t *local, int rtnl_locked)
{
struct net_device *dev = local->dev;
if (local->stadev)
return -EEXIST;
printk(KERN_DEBUG "%s: enabling hostapd STA mode\n", dev->name);
local->stadev = hostap_add_interface(local, HOSTAP_INTERFACE_STA,
rtnl_locked, local->ddev->name,
"sta");
if (local->stadev == NULL)
return -ENOMEM;
return 0;
}
static int hostap_disable_hostapd_sta(local_info_t *local, int rtnl_locked)
{
struct net_device *dev = local->dev;
printk(KERN_DEBUG "%s: disabling hostapd mode\n", dev->name);
hostap_remove_interface(local->stadev, rtnl_locked, 1);
local->stadev = NULL;
return 0;
}
int hostap_set_hostapd(local_info_t *local, int val, int rtnl_locked)
{
int ret;
if (val < 0 || val > 1)
return -EINVAL;
if (local->hostapd == val)
return 0;
if (val) {
ret = hostap_enable_hostapd(local, rtnl_locked);
if (ret == 0)
local->hostapd = 1;
} else {
local->hostapd = 0;
ret = hostap_disable_hostapd(local, rtnl_locked);
if (ret != 0)
local->hostapd = 1;
}
return ret;
}
int hostap_set_hostapd_sta(local_info_t *local, int val, int rtnl_locked)
{
int ret;
if (val < 0 || val > 1)
return -EINVAL;
if (local->hostapd_sta == val)
return 0;
if (val) {
ret = hostap_enable_hostapd_sta(local, rtnl_locked);
if (ret == 0)
local->hostapd_sta = 1;
} else {
local->hostapd_sta = 0;
ret = hostap_disable_hostapd_sta(local, rtnl_locked);
if (ret != 0)
local->hostapd_sta = 1;
}
return ret;
}
int prism2_update_comms_qual(struct net_device *dev)
{
struct hostap_interface *iface;
local_info_t *local;
int ret = 0;
struct hfa384x_comms_quality sq;
iface = netdev_priv(dev);
local = iface->local;
if (!local->sta_fw_ver)
ret = -1;
else if (local->sta_fw_ver >= PRISM2_FW_VER(1,3,1)) {
if (local->func->get_rid(local->dev,
HFA384X_RID_DBMCOMMSQUALITY,
&sq, sizeof(sq), 1) >= 0) {
local->comms_qual = (s16) le16_to_cpu(sq.comm_qual);
local->avg_signal = (s16) le16_to_cpu(sq.signal_level);
local->avg_noise = (s16) le16_to_cpu(sq.noise_level);
local->last_comms_qual_update = jiffies;
} else
ret = -1;
} else {
if (local->func->get_rid(local->dev, HFA384X_RID_COMMSQUALITY,
&sq, sizeof(sq), 1) >= 0) {
local->comms_qual = le16_to_cpu(sq.comm_qual);
local->avg_signal = HFA384X_LEVEL_TO_dBm(
le16_to_cpu(sq.signal_level));
local->avg_noise = HFA384X_LEVEL_TO_dBm(
le16_to_cpu(sq.noise_level));
local->last_comms_qual_update = jiffies;
} else
ret = -1;
}
return ret;
}
int prism2_sta_send_mgmt(local_info_t *local, u8 *dst, u16 stype,
u8 *body, size_t bodylen)
{
struct sk_buff *skb;
struct hostap_ieee80211_mgmt *mgmt;
struct hostap_skb_tx_data *meta;
struct net_device *dev = local->dev;
skb = dev_alloc_skb(IEEE80211_MGMT_HDR_LEN + bodylen);
if (skb == NULL)
return -ENOMEM;
mgmt = (struct hostap_ieee80211_mgmt *)
skb_put(skb, IEEE80211_MGMT_HDR_LEN);
memset(mgmt, 0, IEEE80211_MGMT_HDR_LEN);
mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | stype);
memcpy(mgmt->da, dst, ETH_ALEN);
memcpy(mgmt->sa, dev->dev_addr, ETH_ALEN);
memcpy(mgmt->bssid, dst, ETH_ALEN);
if (body)
memcpy(skb_put(skb, bodylen), body, bodylen);
meta = (struct hostap_skb_tx_data *) skb->cb;
memset(meta, 0, sizeof(*meta));
meta->magic = HOSTAP_SKB_TX_DATA_MAGIC;
meta->iface = netdev_priv(dev);
skb->dev = dev;
skb_reset_mac_header(skb);
skb_reset_network_header(skb);
dev_queue_xmit(skb);
return 0;
}
int prism2_sta_deauth(local_info_t *local, u16 reason)
{
union iwreq_data wrqu;
int ret;
__le16 val = cpu_to_le16(reason);
if (local->iw_mode != IW_MODE_INFRA ||
is_zero_ether_addr(local->bssid) ||
ether_addr_equal(local->bssid, "\x44\x44\x44\x44\x44\x44"))
return 0;
ret = prism2_sta_send_mgmt(local, local->bssid, IEEE80211_STYPE_DEAUTH,
(u8 *) &val, 2);
eth_zero_addr(wrqu.ap_addr.sa_data);
wireless_send_event(local->dev, SIOCGIWAP, &wrqu, NULL);
return ret;
}
struct proc_dir_entry *hostap_proc;
static int __init hostap_init(void)
{
if (init_net.proc_net != NULL) {
hostap_proc = proc_mkdir("hostap", init_net.proc_net);
if (!hostap_proc)
printk(KERN_WARNING "Failed to mkdir "
"/proc/net/hostap\n");
} else
hostap_proc = NULL;
return 0;
}
static void __exit hostap_exit(void)
{
if (hostap_proc != NULL) {
hostap_proc = NULL;
remove_proc_entry("hostap", init_net.proc_net);
}
}
EXPORT_SYMBOL(hostap_set_word);
EXPORT_SYMBOL(hostap_set_string);
EXPORT_SYMBOL(hostap_get_porttype);
EXPORT_SYMBOL(hostap_set_encryption);
EXPORT_SYMBOL(hostap_set_antsel);
EXPORT_SYMBOL(hostap_set_roaming);
EXPORT_SYMBOL(hostap_set_auth_algs);
EXPORT_SYMBOL(hostap_dump_rx_header);
EXPORT_SYMBOL(hostap_dump_tx_header);
EXPORT_SYMBOL(hostap_80211_get_hdrlen);
EXPORT_SYMBOL(hostap_setup_dev);
EXPORT_SYMBOL(hostap_set_multicast_list_queue);
EXPORT_SYMBOL(hostap_set_hostapd);
EXPORT_SYMBOL(hostap_set_hostapd_sta);
EXPORT_SYMBOL(hostap_add_interface);
EXPORT_SYMBOL(hostap_remove_interface);
EXPORT_SYMBOL(prism2_update_comms_qual);
module_init(hostap_init);
module_exit(hostap_exit);