Move the A-MSDU handling code from mac80211 to cfg80211 so that more
drivers can use it. The new created function ieee80211_amsdu_to_8023s
converts an A-MSDU frame to a list of 802.3 frames.
Cc: Johannes Berg <[email protected]>
Signed-off-by: Zhu Yi <[email protected]>
---
V2: use __ version of sk_buff_head operations and mark addr const
include/net/cfg80211.h | 21 ++++++++-
net/mac80211/rx.c | 107 ++++++++++-------------------------------------
net/wireless/util.c | 96 ++++++++++++++++++++++++++++++++++++++++++-
3 files changed, 136 insertions(+), 88 deletions(-)
diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 0884b9a..f1353d1 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -1578,7 +1578,7 @@ unsigned int ieee80211_hdrlen(__le16 fc);
* @addr: the device MAC address
* @iftype: the virtual interface type
*/
-int ieee80211_data_to_8023(struct sk_buff *skb, u8 *addr,
+int ieee80211_data_to_8023(struct sk_buff *skb, const u8 *addr,
enum nl80211_iftype iftype);
/**
@@ -1589,10 +1589,27 @@ int ieee80211_data_to_8023(struct sk_buff *skb, u8 *addr,
* @bssid: the network bssid (used only for iftype STATION and ADHOC)
* @qos: build 802.11 QoS data frame
*/
-int ieee80211_data_from_8023(struct sk_buff *skb, u8 *addr,
+int ieee80211_data_from_8023(struct sk_buff *skb, const u8 *addr,
enum nl80211_iftype iftype, u8 *bssid, bool qos);
/**
+ * ieee80211_amsdu_to_8023s - decode an IEEE 802.11n A-MSDU frame
+ *
+ * Decode an IEEE 802.11n A-MSDU frame and convert it to a list of
+ * 802.3 frames. This function returns 0 on succeess. In this case,
+ * we optimize the code to reuse the @skb to hold the last 802.3
+ * frame in the @list.
+ *
+ * @skb: The input IEEE 802.11n A-MSDU frame.
+ * @list: The output list of 802.3 frames. It must be allocated and
+ * initialized by by the caller.
+ * @addr: The device MAC address.
+ * @iftype: The device interface type.
+ */
+int ieee80211_amsdu_to_8023s(struct sk_buff *skb, struct sk_buff_head *list,
+ const u8 *addr, enum nl80211_iftype iftype);
+
+/**
* cfg80211_classify8021d - determine the 802.1p/1d tag for a data frame
* @skb: the data frame
*/
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index 57b8a0a..539c790 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -1556,16 +1556,11 @@ static ieee80211_rx_result debug_noinline
ieee80211_rx_h_amsdu(struct ieee80211_rx_data *rx)
{
struct net_device *dev = rx->sdata->dev;
- struct ieee80211_local *local = rx->local;
- u16 ethertype;
- u8 *payload;
- struct sk_buff *skb = rx->skb, *frame = NULL;
+ struct sk_buff *skb = rx->skb;
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
__le16 fc = hdr->frame_control;
- const struct ethhdr *eth;
- int remaining, err;
- u8 dst[ETH_ALEN];
- u8 src[ETH_ALEN];
+ int err;
+ struct sk_buff_head frame_list;
if (unlikely(!ieee80211_is_data(fc)))
return RX_CONTINUE;
@@ -1576,92 +1571,36 @@ ieee80211_rx_h_amsdu(struct ieee80211_rx_data *rx)
if (!(rx->flags & IEEE80211_RX_AMSDU))
return RX_CONTINUE;
- err = __ieee80211_data_to_8023(rx);
- if (unlikely(err))
+ if (ieee80211_has_a4(hdr->frame_control) &&
+ rx->sdata->vif.type == NL80211_IFTYPE_AP_VLAN &&
+ !rx->sdata->u.vlan.sta)
return RX_DROP_UNUSABLE;
- skb->dev = dev;
-
- dev->stats.rx_packets++;
- dev->stats.rx_bytes += skb->len;
-
- /* skip the wrapping header */
- eth = (struct ethhdr *) skb_pull(skb, sizeof(struct ethhdr));
- if (!eth)
+ if (is_multicast_ether_addr(hdr->addr1) &&
+ ((rx->sdata->vif.type == NL80211_IFTYPE_AP_VLAN &&
+ rx->sdata->u.vlan.sta) ||
+ (rx->sdata->vif.type == NL80211_IFTYPE_STATION &&
+ rx->sdata->u.mgd.use_4addr)))
return RX_DROP_UNUSABLE;
- while (skb != frame) {
- u8 padding;
- __be16 len = eth->h_proto;
- unsigned int subframe_len = sizeof(struct ethhdr) + ntohs(len);
-
- remaining = skb->len;
- memcpy(dst, eth->h_dest, ETH_ALEN);
- memcpy(src, eth->h_source, ETH_ALEN);
-
- padding = ((4 - subframe_len) & 0x3);
- /* the last MSDU has no padding */
- if (subframe_len > remaining)
- return RX_DROP_UNUSABLE;
-
- skb_pull(skb, sizeof(struct ethhdr));
- /* if last subframe reuse skb */
- if (remaining <= subframe_len + padding)
- frame = skb;
- else {
- /*
- * Allocate and reserve two bytes more for payload
- * alignment since sizeof(struct ethhdr) is 14.
- */
- frame = dev_alloc_skb(
- ALIGN(local->hw.extra_tx_headroom, 4) +
- subframe_len + 2);
-
- if (frame == NULL)
- return RX_DROP_UNUSABLE;
+ skb->dev = dev;
+ __skb_queue_head_init(&frame_list);
- skb_reserve(frame,
- ALIGN(local->hw.extra_tx_headroom, 4) +
- sizeof(struct ethhdr) + 2);
- memcpy(skb_put(frame, ntohs(len)), skb->data,
- ntohs(len));
+ err = ieee80211_amsdu_to_8023s(skb, &frame_list, dev->dev_addr,
+ rx->sdata->vif.type);
+ if (err)
+ return RX_DROP_UNUSABLE;
- eth = (struct ethhdr *) skb_pull(skb, ntohs(len) +
- padding);
- if (!eth) {
- dev_kfree_skb(frame);
- return RX_DROP_UNUSABLE;
- }
- }
+ dev->stats.rx_packets++;
+ dev->stats.rx_bytes += skb->len;
- skb_reset_network_header(frame);
- frame->dev = dev;
- frame->priority = skb->priority;
- rx->skb = frame;
-
- payload = frame->data;
- ethertype = (payload[6] << 8) | payload[7];
-
- if (likely((compare_ether_addr(payload, rfc1042_header) == 0 &&
- ethertype != ETH_P_AARP && ethertype != ETH_P_IPX) ||
- compare_ether_addr(payload,
- bridge_tunnel_header) == 0)) {
- /* remove RFC1042 or Bridge-Tunnel
- * encapsulation and replace EtherType */
- skb_pull(frame, 6);
- memcpy(skb_push(frame, ETH_ALEN), src, ETH_ALEN);
- memcpy(skb_push(frame, ETH_ALEN), dst, ETH_ALEN);
- } else {
- memcpy(skb_push(frame, sizeof(__be16)),
- &len, sizeof(__be16));
- memcpy(skb_push(frame, ETH_ALEN), src, ETH_ALEN);
- memcpy(skb_push(frame, ETH_ALEN), dst, ETH_ALEN);
- }
+ while (!skb_queue_empty(&frame_list)) {
+ rx->skb = __skb_dequeue(&frame_list);
if (!ieee80211_frame_allowed(rx, fc)) {
- if (skb == frame) /* last frame */
+ if (skb == rx->skb) /* last frame */
return RX_DROP_UNUSABLE;
- dev_kfree_skb(frame);
+ dev_kfree_skb(rx->skb);
continue;
}
diff --git a/net/wireless/util.c b/net/wireless/util.c
index 59361fd..63a3d72 100644
--- a/net/wireless/util.c
+++ b/net/wireless/util.c
@@ -285,7 +285,7 @@ static int ieee80211_get_mesh_hdrlen(struct ieee80211s_hdr *meshhdr)
}
}
-int ieee80211_data_to_8023(struct sk_buff *skb, u8 *addr,
+int ieee80211_data_to_8023(struct sk_buff *skb, const u8 *addr,
enum nl80211_iftype iftype)
{
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data;
@@ -383,7 +383,7 @@ int ieee80211_data_to_8023(struct sk_buff *skb, u8 *addr,
}
EXPORT_SYMBOL(ieee80211_data_to_8023);
-int ieee80211_data_from_8023(struct sk_buff *skb, u8 *addr,
+int ieee80211_data_from_8023(struct sk_buff *skb, const u8 *addr,
enum nl80211_iftype iftype, u8 *bssid, bool qos)
{
struct ieee80211_hdr hdr;
@@ -497,6 +497,98 @@ int ieee80211_data_from_8023(struct sk_buff *skb, u8 *addr,
}
EXPORT_SYMBOL(ieee80211_data_from_8023);
+
+int ieee80211_amsdu_to_8023s(struct sk_buff *skb, struct sk_buff_head *list,
+ const u8 *addr, enum nl80211_iftype iftype)
+{
+ struct sk_buff *frame = NULL;
+ u16 ethertype;
+ u8 *payload;
+ const struct ethhdr *eth;
+ int remaining, err;
+ u8 dst[ETH_ALEN], src[ETH_ALEN];
+
+ err = ieee80211_data_to_8023(skb, addr, iftype);
+ if (err)
+ return err;
+
+ /* skip the wrapping header */
+ eth = (struct ethhdr *) skb_pull(skb, sizeof(struct ethhdr));
+ if (!eth)
+ return -1;
+
+ while (skb != frame) {
+ u8 padding;
+ __be16 len = eth->h_proto;
+ unsigned int subframe_len = sizeof(struct ethhdr) + ntohs(len);
+
+ remaining = skb->len;
+ memcpy(dst, eth->h_dest, ETH_ALEN);
+ memcpy(src, eth->h_source, ETH_ALEN);
+
+ padding = (4 - subframe_len) & 0x3;
+ /* the last MSDU has no padding */
+ if (subframe_len > remaining)
+ goto purge;
+
+ skb_pull(skb, sizeof(struct ethhdr));
+ /* reuse skb for the last subframe */
+ if (remaining <= subframe_len + padding)
+ frame = skb;
+ else {
+ /*
+ * Allocate and reserve two bytes more for payload
+ * alignment since sizeof(struct ethhdr) is 14.
+ */
+ frame = dev_alloc_skb(subframe_len + 2);
+ if (!frame)
+ goto purge;
+
+ skb_reserve(frame, sizeof(struct ethhdr) + 2);
+ memcpy(skb_put(frame, ntohs(len)), skb->data,
+ ntohs(len));
+
+ eth = (struct ethhdr *)skb_pull(skb, ntohs(len) +
+ padding);
+ if (!eth) {
+ dev_kfree_skb(frame);
+ goto purge;
+ }
+ }
+
+ skb_reset_network_header(frame);
+ frame->dev = skb->dev;
+ frame->priority = skb->priority;
+
+ payload = frame->data;
+ ethertype = (payload[6] << 8) | payload[7];
+
+ if (likely((compare_ether_addr(payload, rfc1042_header) == 0 &&
+ ethertype != ETH_P_AARP && ethertype != ETH_P_IPX) ||
+ compare_ether_addr(payload,
+ bridge_tunnel_header) == 0)) {
+ /* remove RFC1042 or Bridge-Tunnel
+ * encapsulation and replace EtherType */
+ skb_pull(frame, 6);
+ memcpy(skb_push(frame, ETH_ALEN), src, ETH_ALEN);
+ memcpy(skb_push(frame, ETH_ALEN), dst, ETH_ALEN);
+ } else {
+ memcpy(skb_push(frame, sizeof(__be16)), &len,
+ sizeof(__be16));
+ memcpy(skb_push(frame, ETH_ALEN), src, ETH_ALEN);
+ memcpy(skb_push(frame, ETH_ALEN), dst, ETH_ALEN);
+ }
+ __skb_queue_tail(list, frame);
+ }
+
+ return 0;
+
+ purge:
+ __skb_queue_purge(list);
+ return -1;
+}
+EXPORT_SYMBOL(ieee80211_amsdu_to_8023s);
+
/* Given a data frame determine the 802.1p/1d tag to use. */
unsigned int cfg80211_classify8021d(struct sk_buff *skb)
{
--
1.6.0.4
When the device receives an A-MSDU frame (indicated by flag
IWM_RX_TICKET_AMSDU_MSK), use ieee80211_amsdu_to_8023s to convert
it to a list of 802.3 frames and handled them to upper layer.
Cc: Johannes Berg <[email protected]>
Signed-off-by: Zhu Yi <[email protected]>
---
V2: use __ version of sk_buff_head operations
drivers/net/wireless/iwmc3200wifi/rx.c | 55 +++++++++++++++++++++++++++-----
1 files changed, 47 insertions(+), 8 deletions(-)
diff --git a/drivers/net/wireless/iwmc3200wifi/rx.c b/drivers/net/wireless/iwmc3200wifi/rx.c
index 72c27a3..29e3d1d 100644
--- a/drivers/net/wireless/iwmc3200wifi/rx.c
+++ b/drivers/net/wireless/iwmc3200wifi/rx.c
@@ -1534,6 +1534,34 @@ static void classify8023(struct sk_buff *skb)
}
}
+static int iwm_rx_process_amsdu(struct iwm_priv *iwm, struct sk_buff *skb)
+{
+ struct wireless_dev *wdev = iwm_to_wdev(iwm);
+ struct net_device *ndev = iwm_to_ndev(iwm);
+ struct sk_buff_head list;
+ struct sk_buff *frame;
+ int ret;
+
+ IWM_HEXDUMP(iwm, DBG, RX, "A-MSDU: ", skb->data, skb->len);
+
+ __skb_queue_head_init(&list);
+ ret = ieee80211_amsdu_to_8023s(skb, &list, ndev->dev_addr,
+ wdev->iftype);
+ if (ret) {
+ IWM_ERR(iwm, "decode A-MSDU frame failed\n");
+ return -EINVAL;
+ }
+
+ while ((frame = __skb_dequeue(&list))) {
+ if (netif_rx_ni(frame) == NET_RX_DROP) {
+ IWM_ERR(iwm, "Packet dropped\n");
+ ndev->stats.rx_dropped++;
+ }
+ }
+
+ return 0;
+}
+
static void iwm_rx_process_packet(struct iwm_priv *iwm,
struct iwm_rx_packet *packet,
struct iwm_rx_ticket_node *ticket_node)
@@ -1548,25 +1576,36 @@ static void iwm_rx_process_packet(struct iwm_priv *iwm,
switch (le16_to_cpu(ticket_node->ticket->action)) {
case IWM_RX_TICKET_RELEASE:
IWM_DBG_RX(iwm, DBG, "RELEASE packet\n");
+
+ skb->dev = iwm_to_ndev(iwm);
+ skb->protocol = eth_type_trans(skb, ndev);
+ skb->ip_summed = CHECKSUM_NONE;
+ memset(skb->cb, 0, sizeof(skb->cb));
+
+ ndev->stats.rx_packets++;
+ ndev->stats.rx_bytes += skb->len;
+
classify8023(skb);
iwm_rx_adjust_packet(iwm, packet, ticket_node);
+
+ if (le16_to_cpu(ticket_node->ticket->flags) &
+ IWM_RX_TICKET_AMSDU_MSK) {
+ ret = iwm_rx_process_amsdu(iwm, skb);
+ if (ret < 0)
+ kfree_skb(packet->skb);
+ break;
+ }
+
ret = ieee80211_data_to_8023(skb, ndev->dev_addr, wdev->iftype);
if (ret < 0) {
IWM_DBG_RX(iwm, DBG, "Couldn't convert 802.11 header - "
"%d\n", ret);
+ kfree_skb(packet->skb);
break;
}
IWM_HEXDUMP(iwm, DBG, RX, "802.3: ", skb->data, skb->len);
- skb->dev = iwm_to_ndev(iwm);
- skb->protocol = eth_type_trans(skb, ndev);
- skb->ip_summed = CHECKSUM_NONE;
- memset(skb->cb, 0, sizeof(skb->cb));
-
- ndev->stats.rx_packets++;
- ndev->stats.rx_bytes += skb->len;
-
if (netif_rx_ni(skb) == NET_RX_DROP) {
IWM_ERR(iwm, "Packet dropped\n");
ndev->stats.rx_dropped++;
--
1.6.0.4
On Fri, 2009-11-27 at 17:40 +0800, Johannes Berg wrote:
> You did the right thing in mac80211, but not here -- if it returns an
> error it hasn't freed 'skb', only modified it.
Indeed.
> I think that we should have ieee80211_amsdu_to_8023s() consume it in
> all cases, just not fill the list (and return an error I guess) when
> it can't be decoded properly.
Yeah, since I didn't even use it correctly myself, I couldn't expect
others do. Besides, the nontransparent reuse of skb for the last item in
the list (skb == rx->skb) looks dirty. I'll use RX_QUEUED in v3.
Thanks,
-yi
On Mon, 2009-11-30 at 09:11 +0800, Zhu Yi wrote:
> > You did the right thing in mac80211, but not here -- if it returns
> an error it hasn't freed 'skb', only modified it.
>
> Indeed.
Speak too soon. I freed it in the calling function. But I'll still use
your suggestion to consume the skb here, for the reason I mentioned
early.
Thanks,
-yi
On Fri, 2009-11-27 at 11:18 +0800, Zhu Yi wrote:
> + __skb_queue_head_init(&list);
> + ret = ieee80211_amsdu_to_8023s(skb, &list, ndev->dev_addr,
> + wdev->iftype);
> + if (ret) {
> + IWM_ERR(iwm, "decode A-MSDU frame failed\n");
> + return -EINVAL;
> + }
You did the right thing in mac80211, but not here -- if it returns an
error it hasn't freed 'skb', only modified it.
I think that we should have ieee80211_amsdu_to_8023s() consume it in all
cases, just not fill the list (and return an error I guess) when it
can't be decoded properly.
johannes