From 1487cd5e76337555737cbc55d7d83f41460d198f Mon Sep 17 00:00:00 2001 From: Jussi Kivilinna Date: Thu, 30 Jul 2009 19:41:20 +0300 Subject: usbnet: allow "minidriver" to prevent urb unlinking on usbnet_stop rndis_wlan devices freeze after running usbnet_stop several times. It appears that firmware freezes in state where it does not respond to any RNDIS commands and device have to be physically unplugged/replugged. This patch lets minidrivers to disable unlink_urbs on usbnet_stop through new info flag. Signed-off-by: Jussi Kivilinna Cc: David Brownell Signed-off-by: John W. Linville --- include/linux/usb/usbnet.h | 1 + 1 file changed, 1 insertion(+) (limited to 'include/linux') diff --git a/include/linux/usb/usbnet.h b/include/linux/usb/usbnet.h index 7c17b2efba86..c642f78dd9cf 100644 --- a/include/linux/usb/usbnet.h +++ b/include/linux/usb/usbnet.h @@ -86,6 +86,7 @@ struct driver_info { #define FLAG_FRAMING_AX 0x0040 /* AX88772/178 packets */ #define FLAG_WLAN 0x0080 /* use "wlan%d" names */ +#define FLAG_AVOID_UNLINK_URBS 0x0100 /* don't unlink urbs at usbnet_stop() */ /* init device ... can sleep, or cause probe() failure */ -- cgit v1.2.3 From 2a4901bcbe9c122bd56e1f6c337fcb4ad75fafb7 Mon Sep 17 00:00:00 2001 From: Jussi Kivilinna Date: Thu, 30 Jul 2009 19:41:52 +0300 Subject: rndis_host: allow rndis_wlan to see all indications Allow rndis_wlan to see all indications. Currently rndis_host lets rndis_wlan to know about link state changes only, but there is whole set of other 802.11-specific indications that rndis_wlan should handle properly. So rename link_change() to indication() and convert rndis_wlan to use it. Signed-off-by: Jussi Kivilinna Cc: David Brownell Signed-off-by: John W. Linville --- drivers/net/usb/rndis_host.c | 50 +++++++++++++++++++++++---------------- drivers/net/wireless/rndis_wlan.c | 31 +++++++++++++++++++----- include/linux/usb/usbnet.h | 5 ++-- 3 files changed, 56 insertions(+), 30 deletions(-) (limited to 'include/linux') diff --git a/drivers/net/usb/rndis_host.c b/drivers/net/usb/rndis_host.c index 2232232b7989..d032bba9bc4c 100644 --- a/drivers/net/usb/rndis_host.c +++ b/drivers/net/usb/rndis_host.c @@ -64,6 +64,32 @@ void rndis_status(struct usbnet *dev, struct urb *urb) } EXPORT_SYMBOL_GPL(rndis_status); +/* + * RNDIS indicate messages. + */ +static void rndis_msg_indicate(struct usbnet *dev, struct rndis_indicate *msg, + int buflen) +{ + struct cdc_state *info = (void *)&dev->data; + struct device *udev = &info->control->dev; + + if (dev->driver_info->indication) { + dev->driver_info->indication(dev, msg, buflen); + } else { + switch (msg->status) { + case RNDIS_STATUS_MEDIA_CONNECT: + dev_info(udev, "rndis media connect\n"); + break; + case RNDIS_STATUS_MEDIA_DISCONNECT: + dev_info(udev, "rndis media disconnect\n"); + break; + default: + dev_info(udev, "rndis indication: 0x%08x\n", + le32_to_cpu(msg->status)); + } + } +} + /* * RPC done RNDIS-style. Caller guarantees: * - message is properly byteswapped @@ -143,27 +169,9 @@ int rndis_command(struct usbnet *dev, struct rndis_msg_hdr *buf, int buflen) request_id, xid); /* then likely retry */ } else switch (buf->msg_type) { - case RNDIS_MSG_INDICATE: { /* fault/event */ - struct rndis_indicate *msg = (void *)buf; - int state = 0; - - switch (msg->status) { - case RNDIS_STATUS_MEDIA_CONNECT: - state = 1; - case RNDIS_STATUS_MEDIA_DISCONNECT: - dev_info(&info->control->dev, - "rndis media %sconnect\n", - !state?"dis":""); - if (dev->driver_info->link_change) - dev->driver_info->link_change( - dev, state); - break; - default: - dev_info(&info->control->dev, - "rndis indication: 0x%08x\n", - le32_to_cpu(msg->status)); - } - } + case RNDIS_MSG_INDICATE: /* fault/event */ + rndis_msg_indicate(dev, (void *)buf, buflen); + break; case RNDIS_MSG_KEEPALIVE: { /* ping */ struct rndis_keepalive_c *msg = (void *)buf; diff --git a/drivers/net/wireless/rndis_wlan.c b/drivers/net/wireless/rndis_wlan.c index f6dcbb168b78..6b6452b0e8c4 100644 --- a/drivers/net/wireless/rndis_wlan.c +++ b/drivers/net/wireless/rndis_wlan.c @@ -2211,13 +2211,32 @@ static void rndis_wlan_set_multicast_list(struct net_device *dev) queue_work(priv->workqueue, &priv->work); } -static void rndis_wlan_link_change(struct usbnet *usbdev, int state) +static void rndis_wlan_indication(struct usbnet *usbdev, void *ind, int buflen) { struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); + struct rndis_indicate *msg = ind; /* queue work to avoid recursive calls into rndis_command */ - set_bit(state ? WORK_LINK_UP : WORK_LINK_DOWN, &priv->work_pending); - queue_work(priv->workqueue, &priv->work); + switch (msg->status) { + case RNDIS_STATUS_MEDIA_CONNECT: + devinfo(usbdev, "media connect"); + + set_bit(WORK_LINK_UP, &priv->work_pending); + queue_work(priv->workqueue, &priv->work); + break; + + case RNDIS_STATUS_MEDIA_DISCONNECT: + devinfo(usbdev, "media disconnect"); + + set_bit(WORK_LINK_DOWN, &priv->work_pending); + queue_work(priv->workqueue, &priv->work); + break; + + default: + devinfo(usbdev, "indication: 0x%08x", + le32_to_cpu(msg->status)); + break; + } } @@ -2666,7 +2685,7 @@ static const struct driver_info bcm4320b_info = { .reset = rndis_wlan_reset, .stop = rndis_wlan_stop, .early_init = bcm4320b_early_init, - .link_change = rndis_wlan_link_change, + .indication = rndis_wlan_indication, }; static const struct driver_info bcm4320a_info = { @@ -2681,7 +2700,7 @@ static const struct driver_info bcm4320a_info = { .reset = rndis_wlan_reset, .stop = rndis_wlan_stop, .early_init = bcm4320a_early_init, - .link_change = rndis_wlan_link_change, + .indication = rndis_wlan_indication, }; static const struct driver_info rndis_wlan_info = { @@ -2696,7 +2715,7 @@ static const struct driver_info rndis_wlan_info = { .reset = rndis_wlan_reset, .stop = rndis_wlan_stop, .early_init = bcm4320a_early_init, - .link_change = rndis_wlan_link_change, + .indication = rndis_wlan_indication, }; /*-------------------------------------------------------------------------*/ diff --git a/include/linux/usb/usbnet.h b/include/linux/usb/usbnet.h index c642f78dd9cf..de8b4b18961b 100644 --- a/include/linux/usb/usbnet.h +++ b/include/linux/usb/usbnet.h @@ -122,9 +122,8 @@ struct driver_info { * right after minidriver have initialized hardware. */ int (*early_init)(struct usbnet *dev); - /* called by minidriver when link state changes, state: 0=disconnect, - * 1=connect */ - void (*link_change)(struct usbnet *dev, int state); + /* called by minidriver when receiving indication */ + void (*indication)(struct usbnet *dev, void *ind, int indlen); /* for new devices, use the descriptor-reading code instead */ int in; /* rx endpoint */ -- cgit v1.2.3 From 030645aceb3d9f10b1c3d2231c50f5a8bb3a9667 Mon Sep 17 00:00:00 2001 From: Jussi Kivilinna Date: Thu, 30 Jul 2009 19:41:58 +0300 Subject: rndis_wlan: handle 802.11 indications from device Add handling for 802.11 specific rndis indications. Signed-off-by: Jussi Kivilinna Signed-off-by: John W. Linville --- drivers/net/wireless/rndis_wlan.c | 233 +++++++++++++++++++++++++++++++++++++- include/linux/usb/rndis_host.h | 13 ++- 2 files changed, 239 insertions(+), 7 deletions(-) (limited to 'include/linux') diff --git a/drivers/net/wireless/rndis_wlan.c b/drivers/net/wireless/rndis_wlan.c index 6b6452b0e8c4..7a50cfa18843 100644 --- a/drivers/net/wireless/rndis_wlan.c +++ b/drivers/net/wireless/rndis_wlan.c @@ -201,6 +201,24 @@ enum ndis_80211_priv_filter { NDIS_80211_PRIV_8021X_WEP }; +enum ndis_80211_status_type { + NDIS_80211_STATUSTYPE_AUTHENTICATION, + NDIS_80211_STATUSTYPE_MEDIASTREAMMODE, + NDIS_80211_STATUSTYPE_PMKID_CANDIDATELIST, + NDIS_80211_STATUSTYPE_RADIOSTATE, +}; + +enum ndis_80211_media_stream_mode { + NDIS_80211_MEDIA_STREAM_OFF, + NDIS_80211_MEDIA_STREAM_ON +}; + +enum ndis_80211_radio_status { + NDIS_80211_RADIO_STATUS_ON, + NDIS_80211_RADIO_STATUS_HARDWARE_OFF, + NDIS_80211_RADIO_STATUS_SOFTWARE_OFF, +}; + enum ndis_80211_addkey_bits { NDIS_80211_ADDKEY_8021X_AUTH = cpu_to_le32(1 << 28), NDIS_80211_ADDKEY_SET_INIT_RECV_SEQ = cpu_to_le32(1 << 29), @@ -213,6 +231,35 @@ enum ndis_80211_addwep_bits { NDIS_80211_ADDWEP_TRANSMIT_KEY = cpu_to_le32(1 << 31) }; +struct ndis_80211_auth_request { + __le32 length; + u8 bssid[6]; + u8 padding[2]; + __le32 flags; +} __attribute__((packed)); + +struct ndis_80211_pmkid_candidate { + u8 bssid[6]; + u8 padding[2]; + __le32 flags; +} __attribute__((packed)); + +struct ndis_80211_pmkid_cand_list { + __le32 version; + __le32 num_candidates; + struct ndis_80211_pmkid_candidate candidate_list[0]; +} __attribute__((packed)); + +struct ndis_80211_status_indication { + __le32 status_type; + union { + enum ndis_80211_media_stream_mode media_stream_mode; + enum ndis_80211_radio_status radio_status; + struct ndis_80211_auth_request auth_request[0]; + struct ndis_80211_pmkid_cand_list cand_list; + } u; +} __attribute__((packed)); + struct ndis_80211_ssid { __le32 length; u8 essid[NDIS_802_11_LENGTH_SSID]; @@ -2211,16 +2258,195 @@ static void rndis_wlan_set_multicast_list(struct net_device *dev) queue_work(priv->workqueue, &priv->work); } + +static void rndis_wlan_auth_indication(struct usbnet *usbdev, + struct ndis_80211_status_indication *indication, + int len) +{ + u8 *buf; + const char *type; + int flags, buflen; + bool pairwise_error, group_error; + struct ndis_80211_auth_request *auth_req; + + /* must have at least one array entry */ + if (len < offsetof(struct ndis_80211_status_indication, u) + + sizeof(struct ndis_80211_auth_request)) { + devinfo(usbdev, "authentication indication: " + "too short message (%i)", len); + return; + } + + buf = (void *)&indication->u.auth_request[0]; + buflen = len - offsetof(struct ndis_80211_status_indication, u); + + while (buflen >= sizeof(*auth_req)) { + auth_req = (void *)buf; + type = "unknown"; + flags = le32_to_cpu(auth_req->flags); + pairwise_error = false; + group_error = false; + + if (flags & 0x1) + type = "reauth request"; + if (flags & 0x2) + type = "key update request"; + if (flags & 0x6) { + pairwise_error = true; + type = "pairwise_error"; + } + if (flags & 0xe) { + group_error = true; + type = "group_error"; + } + + devinfo(usbdev, "authentication indication: %s (0x%08x)", type, + le32_to_cpu(auth_req->flags)); + + if (pairwise_error || group_error) { + union iwreq_data wrqu; + struct iw_michaelmicfailure micfailure; + + memset(&micfailure, 0, sizeof(micfailure)); + if (pairwise_error) + micfailure.flags |= IW_MICFAILURE_PAIRWISE; + if (group_error) + micfailure.flags |= IW_MICFAILURE_GROUP; + + memcpy(micfailure.src_addr.sa_data, auth_req->bssid, + ETH_ALEN); + + memset(&wrqu, 0, sizeof(wrqu)); + wrqu.data.length = sizeof(micfailure); + wireless_send_event(usbdev->net, IWEVMICHAELMICFAILURE, + &wrqu, (u8 *)&micfailure); + } + + buflen -= le32_to_cpu(auth_req->length); + buf += le32_to_cpu(auth_req->length); + } +} + +static void rndis_wlan_pmkid_cand_list_indication(struct usbnet *usbdev, + struct ndis_80211_status_indication *indication, + int len) +{ + struct ndis_80211_pmkid_cand_list *cand_list; + int list_len, expected_len, i; + + if (len < offsetof(struct ndis_80211_status_indication, u) + + sizeof(struct ndis_80211_pmkid_cand_list)) { + devinfo(usbdev, "pmkid candidate list indication: " + "too short message (%i)", len); + return; + } + + list_len = le32_to_cpu(indication->u.cand_list.num_candidates) * + sizeof(struct ndis_80211_pmkid_candidate); + expected_len = sizeof(struct ndis_80211_pmkid_cand_list) + list_len + + offsetof(struct ndis_80211_status_indication, u); + + if (len < expected_len) { + devinfo(usbdev, "pmkid candidate list indication: " + "list larger than buffer (%i < %i)", + len, expected_len); + return; + } + + cand_list = &indication->u.cand_list; + + devinfo(usbdev, "pmkid candidate list indication: " + "version %i, candidates %i", + le32_to_cpu(cand_list->version), + le32_to_cpu(cand_list->num_candidates)); + + if (le32_to_cpu(cand_list->version) != 1) + return; + + for (i = 0; i < le32_to_cpu(cand_list->num_candidates); i++) { + struct iw_pmkid_cand pcand; + union iwreq_data wrqu; + struct ndis_80211_pmkid_candidate *cand = + &cand_list->candidate_list[i]; + + devdbg(usbdev, "cand[%i]: flags: 0x%08x, bssid: %pM", + i, le32_to_cpu(cand->flags), cand->bssid); + + memset(&pcand, 0, sizeof(pcand)); + if (le32_to_cpu(cand->flags) & 0x01) + pcand.flags |= IW_PMKID_CAND_PREAUTH; + pcand.index = i; + memcpy(pcand.bssid.sa_data, cand->bssid, ETH_ALEN); + + memset(&wrqu, 0, sizeof(wrqu)); + wrqu.data.length = sizeof(pcand); + wireless_send_event(usbdev->net, IWEVPMKIDCAND, &wrqu, + (u8 *)&pcand); + } +} + +static void rndis_wlan_media_specific_indication(struct usbnet *usbdev, + struct rndis_indicate *msg, int buflen) +{ + struct ndis_80211_status_indication *indication; + int len, offset; + + offset = offsetof(struct rndis_indicate, status) + + le32_to_cpu(msg->offset); + len = le32_to_cpu(msg->length); + + if (len < 8) { + devinfo(usbdev, "media specific indication, " + "ignore too short message (%i < 8)", len); + return; + } + + if (offset + len > buflen) { + devinfo(usbdev, "media specific indication, " + "too large to fit to buffer (%i > %i)", + offset + len, buflen); + return; + } + + indication = (void *)((u8 *)msg + offset); + + switch (le32_to_cpu(indication->status_type)) { + case NDIS_80211_STATUSTYPE_RADIOSTATE: + devinfo(usbdev, "radio state indication: %i", + le32_to_cpu(indication->u.radio_status)); + return; + + case NDIS_80211_STATUSTYPE_MEDIASTREAMMODE: + devinfo(usbdev, "media stream mode indication: %i", + le32_to_cpu(indication->u.media_stream_mode)); + return; + + case NDIS_80211_STATUSTYPE_AUTHENTICATION: + rndis_wlan_auth_indication(usbdev, indication, len); + return; + + case NDIS_80211_STATUSTYPE_PMKID_CANDIDATELIST: + rndis_wlan_pmkid_cand_list_indication(usbdev, indication, len); + return; + + default: + devinfo(usbdev, "media specific indication: " + "unknown status type 0x%08x", + le32_to_cpu(indication->status_type)); + } +} + + static void rndis_wlan_indication(struct usbnet *usbdev, void *ind, int buflen) { struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); struct rndis_indicate *msg = ind; - /* queue work to avoid recursive calls into rndis_command */ switch (msg->status) { case RNDIS_STATUS_MEDIA_CONNECT: devinfo(usbdev, "media connect"); + /* queue work to avoid recursive calls into rndis_command */ set_bit(WORK_LINK_UP, &priv->work_pending); queue_work(priv->workqueue, &priv->work); break; @@ -2228,10 +2454,15 @@ static void rndis_wlan_indication(struct usbnet *usbdev, void *ind, int buflen) case RNDIS_STATUS_MEDIA_DISCONNECT: devinfo(usbdev, "media disconnect"); + /* queue work to avoid recursive calls into rndis_command */ set_bit(WORK_LINK_DOWN, &priv->work_pending); queue_work(priv->workqueue, &priv->work); break; + case RNDIS_STATUS_MEDIA_SPECIFIC_INDICATION: + rndis_wlan_media_specific_indication(usbdev, msg, buflen); + break; + default: devinfo(usbdev, "indication: 0x%08x", le32_to_cpu(msg->status)); diff --git a/include/linux/usb/rndis_host.h b/include/linux/usb/rndis_host.h index 37836b937d97..1ef1ebc2b04f 100644 --- a/include/linux/usb/rndis_host.h +++ b/include/linux/usb/rndis_host.h @@ -70,12 +70,13 @@ struct rndis_msg_hdr { #define RNDIS_MSG_KEEPALIVE_C (RNDIS_MSG_KEEPALIVE|RNDIS_MSG_COMPLETION) /* codes for "status" field of completion messages */ -#define RNDIS_STATUS_SUCCESS cpu_to_le32(0x00000000) -#define RNDIS_STATUS_FAILURE cpu_to_le32(0xc0000001) -#define RNDIS_STATUS_INVALID_DATA cpu_to_le32(0xc0010015) -#define RNDIS_STATUS_NOT_SUPPORTED cpu_to_le32(0xc00000bb) -#define RNDIS_STATUS_MEDIA_CONNECT cpu_to_le32(0x4001000b) -#define RNDIS_STATUS_MEDIA_DISCONNECT cpu_to_le32(0x4001000c) +#define RNDIS_STATUS_SUCCESS cpu_to_le32(0x00000000) +#define RNDIS_STATUS_FAILURE cpu_to_le32(0xc0000001) +#define RNDIS_STATUS_INVALID_DATA cpu_to_le32(0xc0010015) +#define RNDIS_STATUS_NOT_SUPPORTED cpu_to_le32(0xc00000bb) +#define RNDIS_STATUS_MEDIA_CONNECT cpu_to_le32(0x4001000b) +#define RNDIS_STATUS_MEDIA_DISCONNECT cpu_to_le32(0x4001000c) +#define RNDIS_STATUS_MEDIA_SPECIFIC_INDICATION cpu_to_le32(0x40010012) /* codes for OID_GEN_PHYSICAL_MEDIUM */ #define RNDIS_PHYSICAL_MEDIUM_UNSPECIFIED cpu_to_le32(0x00000000) -- cgit v1.2.3 From 3ad201496badddd8e1cda87ee6d29e8b3b8e1279 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Sun, 2 Aug 2009 02:36:49 +0300 Subject: rfkill: add the GPS radio type Althoug GPS is a technology w/o transmitting radio and thus not a primary candidate for rfkill switch, rfkill gives unified interface point for devices with wireless technology. The input key is not supplied as it is too be deprecated. Cc: johannes@sipsolutions.net Signed-off-by: Tomas Winkler Acked-by: Marcel Holtmann Signed-off-by: John W. Linville --- include/linux/rfkill.h | 1 + net/rfkill/core.c | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'include/linux') diff --git a/include/linux/rfkill.h b/include/linux/rfkill.h index 10202903141a..21ca51bf4dd2 100644 --- a/include/linux/rfkill.h +++ b/include/linux/rfkill.h @@ -47,6 +47,7 @@ enum rfkill_type { RFKILL_TYPE_UWB, RFKILL_TYPE_WIMAX, RFKILL_TYPE_WWAN, + RFKILL_TYPE_GPS, NUM_RFKILL_TYPES, }; diff --git a/net/rfkill/core.c b/net/rfkill/core.c index 044de1c6af3d..dbeaf2983822 100644 --- a/net/rfkill/core.c +++ b/net/rfkill/core.c @@ -589,11 +589,13 @@ static const char *rfkill_get_type_str(enum rfkill_type type) return "wimax"; case RFKILL_TYPE_WWAN: return "wwan"; + case RFKILL_TYPE_GPS: + return "gps"; default: BUG(); } - BUILD_BUG_ON(NUM_RFKILL_TYPES != RFKILL_TYPE_WWAN + 1); + BUILD_BUG_ON(NUM_RFKILL_TYPES != RFKILL_TYPE_GPS + 1); } static ssize_t rfkill_type_show(struct device *dev, -- cgit v1.2.3