@@ -196,8 +196,252 @@ static int orinoco_set_channel(struct wiphy *wiphy,
return err;
}
+/* Helper to ensure all keys are valid for the current encoding
+ algorithm */
+static void orinoco_set_encoding(struct orinoco_private *priv,
+ enum orinoco_alg encoding)
+{
+ if (priv->encode_alg &&
+ priv->encode_alg != encoding) {
+ int i;
+
+ for (i = 0; i < ORINOCO_MAX_KEYS; i++) {
+ kfree(priv->keys[i].key);
+ kfree(priv->keys[i].seq);
+ priv->keys[i].key = NULL;
+ priv->keys[i].seq = NULL;
+ priv->keys[i].key_len = 0;
+ priv->keys[i].seq_len = 0;
+ priv->keys[i].cipher = 0;
+ }
+
+ if (priv->encode_alg == ORINOCO_ALG_TKIP &&
+ priv->has_wpa) {
+ (void) orinoco_clear_tkip_key(priv, i);
+ (void) orinoco_clear_tkip_key(priv, i);
+ (void) orinoco_clear_tkip_key(priv, i);
+ (void) orinoco_clear_tkip_key(priv, i);
+ } else if (priv->encode_alg == ORINOCO_ALG_WEP)
+ __orinoco_hw_setup_wepkeys(priv);
+ }
+ priv->encode_alg = encoding;
+}
+
+/* Helper routine to record keys
+ * Do not call from interrupt context */
+static int orinoco_set_key(struct orinoco_private *priv, int index,
+ enum orinoco_alg alg, const u8 *key, int key_len,
+ const u8 *seq, int seq_len)
+{
+ kzfree(priv->keys[index].key);
+ kzfree(priv->keys[index].seq);
+
+ if (key_len) {
+ priv->keys[index].key = kzalloc(key_len, GFP_KERNEL);
+ if (!priv->keys[index].key)
+ goto nomem;
+ } else
+ priv->keys[index].key = NULL;
+
+ if (seq_len) {
+ priv->keys[index].seq = kzalloc(seq_len, GFP_KERNEL);
+ if (!priv->keys[index].seq)
+ goto free_key;
+ } else
+ priv->keys[index].seq = NULL;
+
+ priv->keys[index].key_len = key_len;
+ priv->keys[index].seq_len = seq_len;
+
+ if (key_len)
+ memcpy(priv->keys[index].key, key, key_len);
+ if (seq_len)
+ memcpy(priv->keys[index].seq, seq, seq_len);
+
+
+ switch (alg) {
+ case ORINOCO_ALG_TKIP:
+ priv->keys[index].cipher = WLAN_CIPHER_SUITE_TKIP;
+ break;
+
+ case ORINOCO_ALG_WEP:
+ priv->keys[index].cipher = (key_len > SMALL_KEY_SIZE) ?
+ WLAN_CIPHER_SUITE_WEP104 : WLAN_CIPHER_SUITE_WEP40;
+ break;
+
+ case ORINOCO_ALG_NONE:
+ default:
+ priv->keys[index].cipher = 0;
+ break;
+ }
+
+ return 0;
+
+free_key:
+ kfree(priv->keys[index].key);
+ priv->keys[index].key = NULL;
+
+nomem:
+ priv->keys[index].key_len = 0;
+ priv->keys[index].seq_len = 0;
+ priv->keys[index].cipher = 0;
+
+ return -ENOMEM;
+}
+
+/* Setup channel, SSID and BSSID */
+static int __orinoco_connect(struct wiphy *wiphy,
+ struct ieee80211_channel *channel,
+ const u8 *bssid, const u8 *ssid,
+ u8 ssid_len)
+{
+ struct orinoco_private *priv = wiphy_priv(wiphy);
+
+ if (channel) {
+ int chan;
+
+ chan = ieee80211_freq_to_dsss_chan(channel->center_freq);
+
+ if ((chan > 0) && (chan < NUM_CHANNELS) &&
+ (priv->channel_mask & (1 << chan)))
+ priv->channel = chan;
+ }
+
+ memset(priv->desired_essid, 0, sizeof(priv->desired_essid));
+ if (ssid)
+ memcpy(priv->desired_essid, ssid, ssid_len);
+
+ if (bssid) {
+ /* Intersil firmware hangs if you try to roam manually
+ * without an SSID set. We should always get one in
+ * this call, but just check.
+ */
+ BUG_ON(ssid_len == 0);
+
+ memcpy(priv->desired_bssid, bssid, ETH_ALEN);
+ priv->bssid_fixed = 1;
+ } else {
+ memset(priv->desired_bssid, 0, ETH_ALEN);
+ priv->bssid_fixed = 0;
+ }
+
+ return 0;
+}
+
+static int orinoco_connect(struct wiphy *wiphy, struct net_device *dev,
+ struct cfg80211_connect_params *sme)
+{
+ struct orinoco_private *priv = wiphy_priv(wiphy);
+ enum orinoco_alg encode_alg;
+ unsigned long lock;
+ int err;
+
+ if (orinoco_lock(priv, &lock) != 0)
+ return -EBUSY;
+
+ /* Setup the requested parameters in priv. If the card is not
+ * capable, then the driver will just ignore the settings that
+ * it can't do. */
+ err = __orinoco_connect(wiphy, sme->channel, sme->bssid,
+ sme->ssid, sme->ssid_len);
+ if (err)
+ goto out;
+
+ switch (sme->auth_type) {
+ case NL80211_AUTHTYPE_OPEN_SYSTEM:
+ priv->wep_restrict = 0;
+ break;
+ case NL80211_AUTHTYPE_SHARED_KEY:
+ priv->wep_restrict = 1;
+ break;
+ default:
+ /* Can't handle anything else */
+ err = -EINVAL;
+ goto out;
+ }
+
+ switch (sme->crypto.cipher_group) {
+ case WLAN_CIPHER_SUITE_TKIP:
+ encode_alg = ORINOCO_ALG_TKIP;
+ priv->wpa_enabled = 1;
+ break;
+
+ case WLAN_CIPHER_SUITE_WEP40:
+ case WLAN_CIPHER_SUITE_WEP104:
+ encode_alg = ORINOCO_ALG_WEP;
+ priv->wpa_enabled = 0;
+ break;
+
+ default:
+ encode_alg = ORINOCO_ALG_NONE;
+ priv->wpa_enabled = 0;
+ }
+
+ orinoco_set_encoding(priv, encode_alg);
+
+ /* What are we supposed to do with multiple AKM suites? */
+ if (sme->crypto.n_akm_suites > 0)
+ priv->key_mgmt = sme->crypto.akm_suites[0] & 7;
+
+ /* Finally, set WEP key */
+ if (encode_alg == ORINOCO_ALG_WEP) {
+ err = orinoco_set_key(priv, sme->key_idx, encode_alg,
+ &sme->key[0], sme->key_len, NULL, 0);
+ if (err)
+ goto out;
+
+ priv->tx_key = sme->key_idx;
+ }
+
+ /* Enable cfg80211 notification */
+ priv->connect_commanded = 1;
+
+ err = orinoco_commit(priv);
+
+out:
+ orinoco_unlock(priv, &lock);
+
+ return err;
+}
+
+static int orinoco_disconnect(struct wiphy *wiphy, struct net_device *dev,
+ u16 reason_code)
+{
+ struct orinoco_private *priv = wiphy_priv(wiphy);
+ unsigned long lock;
+ u8 addr[ETH_ALEN];
+ int err;
+
+ if (orinoco_lock(priv, &lock) != 0)
+ return -EBUSY;
+
+ memset(priv->desired_bssid, 0, ETH_ALEN);
+ memset(priv->desired_essid, 0, sizeof(priv->desired_essid));
+
+ err = orinoco_hw_get_current_bssid(priv, &addr[0]);
+
+ if (!err) {
+ err = orinoco_hw_disassociate(priv, &addr[0],
+ reason_code);
+ }
+
+ priv->wpa_enabled = 0;
+
+ /* Disable cfg80211 notification */
+ priv->connect_commanded = 0;
+
+ if (err)
+ err = orinoco_commit(priv);
+
+ orinoco_unlock(priv, &lock);
+
+ return err;
+}
+
const struct cfg80211_ops orinoco_cfg_ops = {
.change_virtual_intf = orinoco_change_vif,
.set_channel = orinoco_set_channel,
.scan = orinoco_scan,
+ .connect = orinoco_connect,
+ .disconnect = orinoco_disconnect,
};
@@ -1188,98 +1188,109 @@ static void orinoco_join_ap(struct work_struct *work)
kfree(buf);
}
-/* Send new BSSID to userspace */
-static void orinoco_send_bssid_wevent(struct orinoco_private *priv)
-{
- struct net_device *dev = priv->ndev;
- struct hermes *hw = &priv->hw;
- union iwreq_data wrqu;
- int err;
-
- err = hermes_read_ltv(hw, USER_BAP, HERMES_RID_CURRENTBSSID,
- ETH_ALEN, NULL, wrqu.ap_addr.sa_data);
- if (err != 0)
- return;
-
- wrqu.ap_addr.sa_family = ARPHRD_ETHER;
-
- /* Send event to user space */
- wireless_send_event(dev, SIOCGIWAP, &wrqu, NULL);
-}
-
-static void orinoco_send_assocreqie_wevent(struct orinoco_private *priv)
+static void orinoco_send_wevents(struct work_struct *work)
{
- struct net_device *dev = priv->ndev;
- struct hermes *hw = &priv->hw;
- union iwreq_data wrqu;
+ struct orinoco_private *priv =
+ container_of(work, struct orinoco_private, wevent_work);
+ hermes_t *hw = &priv->hw;
+ unsigned long flags;
+ u8 req_buf[88];
+ u8 resp_buf[88];
+ u8 bssid[ETH_ALEN];
+ size_t req_len = 0;
+ size_t resp_len = 0;
+ u8 *req_ie = NULL;
+ u8 *resp_ie = NULL;
+ enum nl80211_iftype iw_mode;
+ u16 linkstatus;
int err;
- u8 buf[88];
- u8 *ie;
- if (!priv->has_wpa)
+ if (orinoco_lock(priv, &flags) != 0)
return;
- err = hermes_read_ltv(hw, USER_BAP, HERMES_RID_CURRENT_ASSOC_REQ_INFO,
- sizeof(buf), NULL, &buf);
- if (err != 0)
- return;
+ err = hermes_read_ltv(hw, USER_BAP, HERMES_RID_CURRENTBSSID,
+ ETH_ALEN, NULL, &bssid[0]);
+ if (err)
+ goto out;
- ie = orinoco_get_wpa_ie(buf, sizeof(buf));
- if (ie) {
- int rem = sizeof(buf) - (ie - &buf[0]);
- wrqu.data.length = ie[1] + 2;
- if (wrqu.data.length > rem)
- wrqu.data.length = rem;
+ if (priv->has_wpa) {
+ err = hermes_read_ltv(hw, USER_BAP,
+ HERMES_RID_CURRENT_ASSOC_REQ_INFO,
+ sizeof(req_buf), NULL, &req_buf);
+ if (!err) {
+ req_ie = orinoco_get_wpa_ie(req_buf,
+ sizeof(req_buf));
+ if (req_ie) {
+ int rem = sizeof(req_buf) -
+ (req_ie - &req_buf[0]);
+ req_len = req_ie[1] + 2;
+ if (req_len > rem)
+ req_len = rem;
+ }
+ }
- if (wrqu.data.length)
- /* Send event to user space */
- wireless_send_event(dev, IWEVASSOCREQIE, &wrqu, ie);
+ err = hermes_read_ltv(hw, USER_BAP,
+ HERMES_RID_CURRENT_ASSOC_RESP_INFO,
+ sizeof(resp_buf), NULL, &resp_buf);
+ if (!err) {
+ resp_ie = orinoco_get_wpa_ie(resp_buf,
+ sizeof(resp_buf));
+ if (resp_ie) {
+ int rem = sizeof(resp_buf) -
+ (resp_ie - &resp_buf[0]);
+ resp_len = resp_ie[1] + 2;
+ if (resp_len > rem)
+ resp_len = rem;
+ }
+ }
}
-}
-
-static void orinoco_send_assocrespie_wevent(struct orinoco_private *priv)
-{
- struct net_device *dev = priv->ndev;
- struct hermes *hw = &priv->hw;
- union iwreq_data wrqu;
- int err;
- u8 buf[88]; /* TODO: verify max size or IW_GENERIC_IE_MAX */
- u8 *ie;
- if (!priv->has_wpa)
- return;
+ iw_mode = priv->iw_mode;
+ linkstatus = priv->last_linkstatus;
- err = hermes_read_ltv(hw, USER_BAP, HERMES_RID_CURRENT_ASSOC_RESP_INFO,
- sizeof(buf), NULL, &buf);
- if (err != 0)
- return;
+ if (!priv->connect_commanded)
+ goto out;
- ie = orinoco_get_wpa_ie(buf, sizeof(buf));
- if (ie) {
- int rem = sizeof(buf) - (ie - &buf[0]);
- wrqu.data.length = ie[1] + 2;
- if (wrqu.data.length > rem)
- wrqu.data.length = rem;
+ orinoco_unlock(priv, &flags);
- if (wrqu.data.length)
- /* Send event to user space */
- wireless_send_event(dev, IWEVASSOCRESPIE, &wrqu, ie);
- }
-}
+ switch (iw_mode) {
+ case NL80211_IFTYPE_STATION:
-static void orinoco_send_wevents(struct work_struct *work)
-{
- struct orinoco_private *priv =
- container_of(work, struct orinoco_private, wevent_work);
- unsigned long flags;
+ switch (linkstatus) {
+ case HERMES_LINKSTATUS_CONNECTED:
+ case HERMES_LINKSTATUS_AP_IN_RANGE:
+ cfg80211_connect_result(priv->ndev, bssid,
+ req_ie, req_len,
+ resp_ie, resp_len,
+ WLAN_STATUS_SUCCESS,
+ GFP_KERNEL);
+ break;
- if (orinoco_lock(priv, &flags) != 0)
- return;
+ case HERMES_LINKSTATUS_DISCONNECTED:
+ case HERMES_LINKSTATUS_NOT_CONNECTED:
+ case HERMES_LINKSTATUS_AP_OUT_OF_RANGE:
+ case HERMES_LINKSTATUS_AP_CHANGE:
+ cfg80211_roamed(priv->ndev, bssid, req_ie, req_len,
+ resp_ie, resp_len, GFP_KERNEL);
+ break;
- orinoco_send_assocreqie_wevent(priv);
- orinoco_send_assocrespie_wevent(priv);
- orinoco_send_bssid_wevent(priv);
+ case HERMES_LINKSTATUS_ASSOC_FAILED:
+ cfg80211_connect_result(priv->ndev, bssid,
+ req_ie, req_len,
+ resp_ie, resp_len,
+ WLAN_STATUS_ASSOC_DENIED_UNSPEC,
+ GFP_KERNEL);
+ break;
+ }
+ break;
+ case NL80211_IFTYPE_ADHOC:
+ case NL80211_IFTYPE_MONITOR:
+ default:
+ break;
+ }
+ return;
+ out:
orinoco_unlock(priv, &flags);
}
@@ -2050,6 +2061,7 @@ int orinoco_init(struct orinoco_private *priv)
set_port_type(priv);
priv->channel = 0; /* use firmware default */
+ priv->connect_commanded = 0;
priv->promiscuous = 0;
priv->encode_alg = ORINOCO_ALG_NONE;
priv->tx_key = 0;
@@ -78,6 +78,7 @@ struct orinoco_private {
/* driver state */
int open;
+ int connect_commanded;
u16 last_linkstatus;
struct work_struct join_work;
struct work_struct wevent_work;
Basic station mode support. Signed-off-by: David Kilroy <kilroyd@googlemail.com> --- drivers/net/wireless/orinoco/cfg.c | 244 ++++++++++++++++++++++++++++++++ drivers/net/wireless/orinoco/main.c | 164 ++++++++++++---------- drivers/net/wireless/orinoco/orinoco.h | 1 + 3 files changed, 333 insertions(+), 76 deletions(-)