mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-28 11:18:45 +07:00
cf124db566
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>
1130 lines
28 KiB
C
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, ®s);
|
|
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);
|