alistair23-linux/drivers/staging/wlan-ng/cfg80211.c
Johannes Berg 80279fb7ba cfg80211: properly send NL80211_ATTR_DISCONNECTED_BY_AP in disconnect
When we disconnect from the AP, drivers call cfg80211_disconnect().
This doesn't know whether the disconnection was initiated locally
or by the AP though, which can cause problems with the supplicant,
for example with WPS. This issue obviously doesn't show up with any
mac80211 based driver since mac80211 doesn't call this function.

Fix this by requiring drivers to indicate whether the disconnect is
locally generated or not. I've tried to update the drivers, but may
not have gotten the values correct, and some drivers may currently
not be able to report correct values. In case of doubt I left it at
false, which is the current behaviour.

For libertas, make adjustments as indicated by Dan Williams.

Reported-by: Matthieu Mauger <matthieux.mauger@intel.com>
Tested-by: Matthieu Mauger <matthieux.mauger@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
2015-05-26 15:21:27 +02:00

797 lines
18 KiB
C

/* cfg80211 Interface for prism2_usb module */
#include "hfa384x.h"
#include "prism2mgmt.h"
/* Prism2 channel/frequency/bitrate declarations */
static const struct ieee80211_channel prism2_channels[] = {
{ .center_freq = 2412 },
{ .center_freq = 2417 },
{ .center_freq = 2422 },
{ .center_freq = 2427 },
{ .center_freq = 2432 },
{ .center_freq = 2437 },
{ .center_freq = 2442 },
{ .center_freq = 2447 },
{ .center_freq = 2452 },
{ .center_freq = 2457 },
{ .center_freq = 2462 },
{ .center_freq = 2467 },
{ .center_freq = 2472 },
{ .center_freq = 2484 },
};
static const struct ieee80211_rate prism2_rates[] = {
{ .bitrate = 10 },
{ .bitrate = 20 },
{ .bitrate = 55 },
{ .bitrate = 110 }
};
#define PRISM2_NUM_CIPHER_SUITES 2
static const u32 prism2_cipher_suites[PRISM2_NUM_CIPHER_SUITES] = {
WLAN_CIPHER_SUITE_WEP40,
WLAN_CIPHER_SUITE_WEP104
};
/* prism2 device private data */
struct prism2_wiphy_private {
wlandevice_t *wlandev;
struct ieee80211_supported_band band;
struct ieee80211_channel channels[ARRAY_SIZE(prism2_channels)];
struct ieee80211_rate rates[ARRAY_SIZE(prism2_rates)];
struct cfg80211_scan_request *scan_request;
};
static const void * const prism2_wiphy_privid = &prism2_wiphy_privid;
/* Helper Functions */
static int prism2_result2err(int prism2_result)
{
int err = 0;
switch (prism2_result) {
case P80211ENUM_resultcode_invalid_parameters:
err = -EINVAL;
break;
case P80211ENUM_resultcode_implementation_failure:
err = -EIO;
break;
case P80211ENUM_resultcode_not_supported:
err = -EOPNOTSUPP;
break;
default:
err = 0;
break;
}
return err;
}
static int prism2_domibset_uint32(wlandevice_t *wlandev, u32 did, u32 data)
{
struct p80211msg_dot11req_mibset msg;
p80211item_uint32_t *mibitem =
(p80211item_uint32_t *) &msg.mibattribute.data;
msg.msgcode = DIDmsg_dot11req_mibset;
mibitem->did = did;
mibitem->data = data;
return p80211req_dorequest(wlandev, (u8 *) &msg);
}
static int prism2_domibset_pstr32(wlandevice_t *wlandev,
u32 did, u8 len, const u8 *data)
{
struct p80211msg_dot11req_mibset msg;
p80211item_pstr32_t *mibitem =
(p80211item_pstr32_t *) &msg.mibattribute.data;
msg.msgcode = DIDmsg_dot11req_mibset;
mibitem->did = did;
mibitem->data.len = len;
memcpy(mibitem->data.data, data, len);
return p80211req_dorequest(wlandev, (u8 *) &msg);
}
/* The interface functions, called by the cfg80211 layer */
static int prism2_change_virtual_intf(struct wiphy *wiphy,
struct net_device *dev,
enum nl80211_iftype type, u32 *flags,
struct vif_params *params)
{
wlandevice_t *wlandev = dev->ml_priv;
u32 data;
int result;
int err = 0;
switch (type) {
case NL80211_IFTYPE_ADHOC:
if (wlandev->macmode == WLAN_MACMODE_IBSS_STA)
goto exit;
wlandev->macmode = WLAN_MACMODE_IBSS_STA;
data = 0;
break;
case NL80211_IFTYPE_STATION:
if (wlandev->macmode == WLAN_MACMODE_ESS_STA)
goto exit;
wlandev->macmode = WLAN_MACMODE_ESS_STA;
data = 1;
break;
default:
netdev_warn(dev, "Operation mode: %d not support\n", type);
return -EOPNOTSUPP;
}
/* Set Operation mode to the PORT TYPE RID */
result = prism2_domibset_uint32(wlandev,
DIDmib_p2_p2Static_p2CnfPortType,
data);
if (result)
err = -EFAULT;
dev->ieee80211_ptr->iftype = type;
exit:
return err;
}
static int prism2_add_key(struct wiphy *wiphy, struct net_device *dev,
u8 key_index, bool pairwise, const u8 *mac_addr,
struct key_params *params)
{
wlandevice_t *wlandev = dev->ml_priv;
u32 did;
int err = 0;
int result = 0;
switch (params->cipher) {
case WLAN_CIPHER_SUITE_WEP40:
case WLAN_CIPHER_SUITE_WEP104:
result = prism2_domibset_uint32(wlandev,
DIDmib_dot11smt_dot11PrivacyTable_dot11WEPDefaultKeyID,
key_index);
if (result)
goto exit;
/* send key to driver */
switch (key_index) {
case 0:
did = DIDmib_dot11smt_dot11WEPDefaultKeysTable_dot11WEPDefaultKey0;
break;
case 1:
did = DIDmib_dot11smt_dot11WEPDefaultKeysTable_dot11WEPDefaultKey1;
break;
case 2:
did = DIDmib_dot11smt_dot11WEPDefaultKeysTable_dot11WEPDefaultKey2;
break;
case 3:
did = DIDmib_dot11smt_dot11WEPDefaultKeysTable_dot11WEPDefaultKey3;
break;
default:
err = -EINVAL;
goto exit;
}
result = prism2_domibset_pstr32(wlandev, did,
params->key_len, params->key);
if (result)
goto exit;
break;
default:
pr_debug("Unsupported cipher suite\n");
result = 1;
}
exit:
if (result)
err = -EFAULT;
return err;
}
static int prism2_get_key(struct wiphy *wiphy, struct net_device *dev,
u8 key_index, bool pairwise,
const u8 *mac_addr, void *cookie,
void (*callback)(void *cookie, struct key_params*))
{
wlandevice_t *wlandev = dev->ml_priv;
struct key_params params;
int len;
if (key_index >= NUM_WEPKEYS)
return -EINVAL;
len = wlandev->wep_keylens[key_index];
memset(&params, 0, sizeof(params));
if (len == 13)
params.cipher = WLAN_CIPHER_SUITE_WEP104;
else if (len == 5)
params.cipher = WLAN_CIPHER_SUITE_WEP104;
else
return -ENOENT;
params.key_len = len;
params.key = wlandev->wep_keys[key_index];
params.seq_len = 0;
callback(cookie, &params);
return 0;
}
static int prism2_del_key(struct wiphy *wiphy, struct net_device *dev,
u8 key_index, bool pairwise, const u8 *mac_addr)
{
wlandevice_t *wlandev = dev->ml_priv;
u32 did;
int err = 0;
int result = 0;
/* There is no direct way in the hardware (AFAIK) of removing
a key, so we will cheat by setting the key to a bogus value */
/* send key to driver */
switch (key_index) {
case 0:
did =
DIDmib_dot11smt_dot11WEPDefaultKeysTable_dot11WEPDefaultKey0;
break;
case 1:
did =
DIDmib_dot11smt_dot11WEPDefaultKeysTable_dot11WEPDefaultKey1;
break;
case 2:
did =
DIDmib_dot11smt_dot11WEPDefaultKeysTable_dot11WEPDefaultKey2;
break;
case 3:
did =
DIDmib_dot11smt_dot11WEPDefaultKeysTable_dot11WEPDefaultKey3;
break;
default:
err = -EINVAL;
goto exit;
}
result = prism2_domibset_pstr32(wlandev, did, 13, "0000000000000");
exit:
if (result)
err = -EFAULT;
return err;
}
static int prism2_set_default_key(struct wiphy *wiphy, struct net_device *dev,
u8 key_index, bool unicast, bool multicast)
{
wlandevice_t *wlandev = dev->ml_priv;
int err = 0;
int result = 0;
result = prism2_domibset_uint32(wlandev,
DIDmib_dot11smt_dot11PrivacyTable_dot11WEPDefaultKeyID,
key_index);
if (result)
err = -EFAULT;
return err;
}
static int prism2_get_station(struct wiphy *wiphy, struct net_device *dev,
const u8 *mac, struct station_info *sinfo)
{
wlandevice_t *wlandev = dev->ml_priv;
struct p80211msg_lnxreq_commsquality quality;
int result;
memset(sinfo, 0, sizeof(*sinfo));
if ((wlandev == NULL) || (wlandev->msdstate != WLAN_MSD_RUNNING))
return -EOPNOTSUPP;
/* build request message */
quality.msgcode = DIDmsg_lnxreq_commsquality;
quality.dbm.data = P80211ENUM_truth_true;
quality.dbm.status = P80211ENUM_msgitem_status_data_ok;
/* send message to nsd */
if (wlandev->mlmerequest == NULL)
return -EOPNOTSUPP;
result = wlandev->mlmerequest(wlandev, (struct p80211msg *) &quality);
if (result == 0) {
sinfo->txrate.legacy = quality.txrate.data;
sinfo->filled |= BIT(NL80211_STA_INFO_TX_BITRATE);
sinfo->signal = quality.level.data;
sinfo->filled |= BIT(NL80211_STA_INFO_SIGNAL);
}
return result;
}
static int prism2_scan(struct wiphy *wiphy,
struct cfg80211_scan_request *request)
{
struct net_device *dev;
struct prism2_wiphy_private *priv = wiphy_priv(wiphy);
wlandevice_t *wlandev;
struct p80211msg_dot11req_scan msg1;
struct p80211msg_dot11req_scan_results msg2;
struct cfg80211_bss *bss;
int result;
int err = 0;
int numbss = 0;
int i = 0;
u8 ie_buf[46];
int ie_len;
if (!request)
return -EINVAL;
dev = request->wdev->netdev;
wlandev = dev->ml_priv;
if (priv->scan_request && priv->scan_request != request)
return -EBUSY;
if (wlandev->macmode == WLAN_MACMODE_ESS_AP) {
netdev_err(dev, "Can't scan in AP mode\n");
return -EOPNOTSUPP;
}
priv->scan_request = request;
memset(&msg1, 0x00, sizeof(struct p80211msg_dot11req_scan));
msg1.msgcode = DIDmsg_dot11req_scan;
msg1.bsstype.data = P80211ENUM_bsstype_any;
memset(&msg1.bssid.data.data, 0xFF, sizeof(msg1.bssid.data.data));
msg1.bssid.data.len = 6;
if (request->n_ssids > 0) {
msg1.scantype.data = P80211ENUM_scantype_active;
msg1.ssid.data.len = request->ssids->ssid_len;
memcpy(msg1.ssid.data.data,
request->ssids->ssid, request->ssids->ssid_len);
} else {
msg1.scantype.data = 0;
}
msg1.probedelay.data = 0;
for (i = 0;
(i < request->n_channels) && i < ARRAY_SIZE(prism2_channels);
i++)
msg1.channellist.data.data[i] =
ieee80211_frequency_to_channel(
request->channels[i]->center_freq);
msg1.channellist.data.len = request->n_channels;
msg1.maxchanneltime.data = 250;
msg1.minchanneltime.data = 200;
result = p80211req_dorequest(wlandev, (u8 *) &msg1);
if (result) {
err = prism2_result2err(msg1.resultcode.data);
goto exit;
}
/* Now retrieve scan results */
numbss = msg1.numbss.data;
for (i = 0; i < numbss; i++) {
int freq;
memset(&msg2, 0, sizeof(msg2));
msg2.msgcode = DIDmsg_dot11req_scan_results;
msg2.bssindex.data = i;
result = p80211req_dorequest(wlandev, (u8 *) &msg2);
if ((result != 0) ||
(msg2.resultcode.data != P80211ENUM_resultcode_success)) {
break;
}
ie_buf[0] = WLAN_EID_SSID;
ie_buf[1] = msg2.ssid.data.len;
ie_len = ie_buf[1] + 2;
memcpy(&ie_buf[2], &(msg2.ssid.data.data), msg2.ssid.data.len);
freq = ieee80211_channel_to_frequency(msg2.dschannel.data,
IEEE80211_BAND_2GHZ);
bss = cfg80211_inform_bss(wiphy,
ieee80211_get_channel(wiphy, freq),
CFG80211_BSS_FTYPE_UNKNOWN,
(const u8 *) &(msg2.bssid.data.data),
msg2.timestamp.data, msg2.capinfo.data,
msg2.beaconperiod.data,
ie_buf,
ie_len,
(msg2.signal.data - 65536) * 100, /* Conversion to signed type */
GFP_KERNEL
);
if (!bss) {
err = -ENOMEM;
goto exit;
}
cfg80211_put_bss(wiphy, bss);
}
if (result)
err = prism2_result2err(msg2.resultcode.data);
exit:
cfg80211_scan_done(request, err ? 1 : 0);
priv->scan_request = NULL;
return err;
}
static int prism2_set_wiphy_params(struct wiphy *wiphy, u32 changed)
{
struct prism2_wiphy_private *priv = wiphy_priv(wiphy);
wlandevice_t *wlandev = priv->wlandev;
u32 data;
int result;
int err = 0;
if (changed & WIPHY_PARAM_RTS_THRESHOLD) {
if (wiphy->rts_threshold == -1)
data = 2347;
else
data = wiphy->rts_threshold;
result = prism2_domibset_uint32(wlandev,
DIDmib_dot11mac_dot11OperationTable_dot11RTSThreshold,
data);
if (result) {
err = -EFAULT;
goto exit;
}
}
if (changed & WIPHY_PARAM_FRAG_THRESHOLD) {
if (wiphy->frag_threshold == -1)
data = 2346;
else
data = wiphy->frag_threshold;
result = prism2_domibset_uint32(wlandev,
DIDmib_dot11mac_dot11OperationTable_dot11FragmentationThreshold,
data);
if (result) {
err = -EFAULT;
goto exit;
}
}
exit:
return err;
}
static int prism2_connect(struct wiphy *wiphy, struct net_device *dev,
struct cfg80211_connect_params *sme)
{
wlandevice_t *wlandev = dev->ml_priv;
struct ieee80211_channel *channel = sme->channel;
struct p80211msg_lnxreq_autojoin msg_join;
u32 did;
int length = sme->ssid_len;
int chan = -1;
int is_wep = (sme->crypto.cipher_group == WLAN_CIPHER_SUITE_WEP40) ||
(sme->crypto.cipher_group == WLAN_CIPHER_SUITE_WEP104);
int result;
int err = 0;
/* Set the channel */
if (channel) {
chan = ieee80211_frequency_to_channel(channel->center_freq);
result = prism2_domibset_uint32(wlandev,
DIDmib_dot11phy_dot11PhyDSSSTable_dot11CurrentChannel,
chan);
if (result)
goto exit;
}
/* Set the authorization */
if ((sme->auth_type == NL80211_AUTHTYPE_OPEN_SYSTEM) ||
((sme->auth_type == NL80211_AUTHTYPE_AUTOMATIC) && !is_wep))
msg_join.authtype.data = P80211ENUM_authalg_opensystem;
else if ((sme->auth_type == NL80211_AUTHTYPE_SHARED_KEY) ||
((sme->auth_type == NL80211_AUTHTYPE_AUTOMATIC) && is_wep))
msg_join.authtype.data = P80211ENUM_authalg_sharedkey;
else
netdev_warn(dev,
"Unhandled authorisation type for connect (%d)\n",
sme->auth_type);
/* Set the encryption - we only support wep */
if (is_wep) {
if (sme->key) {
result = prism2_domibset_uint32(wlandev,
DIDmib_dot11smt_dot11PrivacyTable_dot11WEPDefaultKeyID,
sme->key_idx);
if (result)
goto exit;
/* send key to driver */
switch (sme->key_idx) {
case 0:
did = DIDmib_dot11smt_dot11WEPDefaultKeysTable_dot11WEPDefaultKey0;
break;
case 1:
did = DIDmib_dot11smt_dot11WEPDefaultKeysTable_dot11WEPDefaultKey1;
break;
case 2:
did = DIDmib_dot11smt_dot11WEPDefaultKeysTable_dot11WEPDefaultKey2;
break;
case 3:
did = DIDmib_dot11smt_dot11WEPDefaultKeysTable_dot11WEPDefaultKey3;
break;
default:
err = -EINVAL;
goto exit;
}
result = prism2_domibset_pstr32(wlandev,
did, sme->key_len,
(u8 *)sme->key);
if (result)
goto exit;
}
/* Assume we should set privacy invoked and exclude unencrypted
We could possibly use sme->privacy here, but the assumption
seems reasonable anyway */
result = prism2_domibset_uint32(wlandev,
DIDmib_dot11smt_dot11PrivacyTable_dot11PrivacyInvoked,
P80211ENUM_truth_true);
if (result)
goto exit;
result = prism2_domibset_uint32(wlandev,
DIDmib_dot11smt_dot11PrivacyTable_dot11ExcludeUnencrypted,
P80211ENUM_truth_true);
if (result)
goto exit;
} else {
/* Assume we should unset privacy invoked
and exclude unencrypted */
result = prism2_domibset_uint32(wlandev,
DIDmib_dot11smt_dot11PrivacyTable_dot11PrivacyInvoked,
P80211ENUM_truth_false);
if (result)
goto exit;
result = prism2_domibset_uint32(wlandev,
DIDmib_dot11smt_dot11PrivacyTable_dot11ExcludeUnencrypted,
P80211ENUM_truth_false);
if (result)
goto exit;
}
/* Now do the actual join. Note there is no way that I can
see to request a specific bssid */
msg_join.msgcode = DIDmsg_lnxreq_autojoin;
memcpy(msg_join.ssid.data.data, sme->ssid, length);
msg_join.ssid.data.len = length;
result = p80211req_dorequest(wlandev, (u8 *) &msg_join);
exit:
if (result)
err = -EFAULT;
return err;
}
static int prism2_disconnect(struct wiphy *wiphy, struct net_device *dev,
u16 reason_code)
{
wlandevice_t *wlandev = dev->ml_priv;
struct p80211msg_lnxreq_autojoin msg_join;
int result;
int err = 0;
/* Do a join, with a bogus ssid. Thats the only way I can think of */
msg_join.msgcode = DIDmsg_lnxreq_autojoin;
memcpy(msg_join.ssid.data.data, "---", 3);
msg_join.ssid.data.len = 3;
result = p80211req_dorequest(wlandev, (u8 *) &msg_join);
if (result)
err = -EFAULT;
return err;
}
static int prism2_join_ibss(struct wiphy *wiphy, struct net_device *dev,
struct cfg80211_ibss_params *params)
{
return -EOPNOTSUPP;
}
static int prism2_leave_ibss(struct wiphy *wiphy, struct net_device *dev)
{
return -EOPNOTSUPP;
}
static int prism2_set_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev,
enum nl80211_tx_power_setting type, int mbm)
{
struct prism2_wiphy_private *priv = wiphy_priv(wiphy);
wlandevice_t *wlandev = priv->wlandev;
u32 data;
int result;
int err = 0;
if (type == NL80211_TX_POWER_AUTOMATIC)
data = 30;
else
data = MBM_TO_DBM(mbm);
result = prism2_domibset_uint32(wlandev,
DIDmib_dot11phy_dot11PhyTxPowerTable_dot11CurrentTxPowerLevel,
data);
if (result) {
err = -EFAULT;
goto exit;
}
exit:
return err;
}
static int prism2_get_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev,
int *dbm)
{
struct prism2_wiphy_private *priv = wiphy_priv(wiphy);
wlandevice_t *wlandev = priv->wlandev;
struct p80211msg_dot11req_mibget msg;
p80211item_uint32_t *mibitem;
int result;
int err = 0;
mibitem = (p80211item_uint32_t *) &msg.mibattribute.data;
msg.msgcode = DIDmsg_dot11req_mibget;
mibitem->did =
DIDmib_dot11phy_dot11PhyTxPowerTable_dot11CurrentTxPowerLevel;
result = p80211req_dorequest(wlandev, (u8 *) &msg);
if (result) {
err = -EFAULT;
goto exit;
}
*dbm = mibitem->data;
exit:
return err;
}
/* Interface callback functions, passing data back up to the cfg80211 layer */
void prism2_connect_result(wlandevice_t *wlandev, u8 failed)
{
u16 status = failed ?
WLAN_STATUS_UNSPECIFIED_FAILURE : WLAN_STATUS_SUCCESS;
cfg80211_connect_result(wlandev->netdev, wlandev->bssid,
NULL, 0, NULL, 0, status, GFP_KERNEL);
}
void prism2_disconnected(wlandevice_t *wlandev)
{
cfg80211_disconnected(wlandev->netdev, 0, NULL,
0, false, GFP_KERNEL);
}
void prism2_roamed(wlandevice_t *wlandev)
{
cfg80211_roamed(wlandev->netdev, NULL, wlandev->bssid,
NULL, 0, NULL, 0, GFP_KERNEL);
}
/* Structures for declaring wiphy interface */
static const struct cfg80211_ops prism2_usb_cfg_ops = {
.change_virtual_intf = prism2_change_virtual_intf,
.add_key = prism2_add_key,
.get_key = prism2_get_key,
.del_key = prism2_del_key,
.set_default_key = prism2_set_default_key,
.get_station = prism2_get_station,
.scan = prism2_scan,
.set_wiphy_params = prism2_set_wiphy_params,
.connect = prism2_connect,
.disconnect = prism2_disconnect,
.join_ibss = prism2_join_ibss,
.leave_ibss = prism2_leave_ibss,
.set_tx_power = prism2_set_tx_power,
.get_tx_power = prism2_get_tx_power,
};
/* Functions to create/free wiphy interface */
static struct wiphy *wlan_create_wiphy(struct device *dev, wlandevice_t *wlandev)
{
struct wiphy *wiphy;
struct prism2_wiphy_private *priv;
wiphy = wiphy_new(&prism2_usb_cfg_ops, sizeof(*priv));
if (!wiphy)
return NULL;
priv = wiphy_priv(wiphy);
priv->wlandev = wlandev;
memcpy(priv->channels, prism2_channels, sizeof(prism2_channels));
memcpy(priv->rates, prism2_rates, sizeof(prism2_rates));
priv->band.channels = priv->channels;
priv->band.n_channels = ARRAY_SIZE(prism2_channels);
priv->band.bitrates = priv->rates;
priv->band.n_bitrates = ARRAY_SIZE(prism2_rates);
priv->band.band = IEEE80211_BAND_2GHZ;
priv->band.ht_cap.ht_supported = false;
wiphy->bands[IEEE80211_BAND_2GHZ] = &priv->band;
set_wiphy_dev(wiphy, dev);
wiphy->privid = prism2_wiphy_privid;
wiphy->max_scan_ssids = 1;
wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION)
| BIT(NL80211_IFTYPE_ADHOC);
wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM;
wiphy->n_cipher_suites = PRISM2_NUM_CIPHER_SUITES;
wiphy->cipher_suites = prism2_cipher_suites;
if (wiphy_register(wiphy) < 0)
return NULL;
return wiphy;
}
static void wlan_free_wiphy(struct wiphy *wiphy)
{
wiphy_unregister(wiphy);
wiphy_free(wiphy);
}