diff options
Diffstat (limited to 'drivers/net/wireless/rndis_wlan.c')
-rw-r--r-- | drivers/net/wireless/rndis_wlan.c | 67 |
1 files changed, 42 insertions, 25 deletions
diff --git a/drivers/net/wireless/rndis_wlan.c b/drivers/net/wireless/rndis_wlan.c index 9f6d6bf06b8e..1de5b22d3efe 100644 --- a/drivers/net/wireless/rndis_wlan.c +++ b/drivers/net/wireless/rndis_wlan.c | |||
@@ -41,6 +41,7 @@ | |||
41 | #include <linux/if_arp.h> | 41 | #include <linux/if_arp.h> |
42 | #include <linux/ctype.h> | 42 | #include <linux/ctype.h> |
43 | #include <linux/spinlock.h> | 43 | #include <linux/spinlock.h> |
44 | #include <linux/slab.h> | ||
44 | #include <net/iw_handler.h> | 45 | #include <net/iw_handler.h> |
45 | #include <net/cfg80211.h> | 46 | #include <net/cfg80211.h> |
46 | #include <linux/usb/usbnet.h> | 47 | #include <linux/usb/usbnet.h> |
@@ -1496,51 +1497,67 @@ static void set_multicast_list(struct usbnet *usbdev) | |||
1496 | { | 1497 | { |
1497 | struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); | 1498 | struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); |
1498 | struct dev_mc_list *mclist; | 1499 | struct dev_mc_list *mclist; |
1499 | __le32 filter; | 1500 | __le32 filter, basefilter; |
1500 | int ret, i, size; | 1501 | int ret; |
1501 | char *buf; | 1502 | char *mc_addrs = NULL; |
1503 | int mc_count; | ||
1502 | 1504 | ||
1503 | filter = RNDIS_PACKET_TYPE_DIRECTED | RNDIS_PACKET_TYPE_BROADCAST; | 1505 | basefilter = filter = RNDIS_PACKET_TYPE_DIRECTED | |
1506 | RNDIS_PACKET_TYPE_BROADCAST; | ||
1504 | 1507 | ||
1505 | netif_addr_lock_bh(usbdev->net); | ||
1506 | if (usbdev->net->flags & IFF_PROMISC) { | 1508 | if (usbdev->net->flags & IFF_PROMISC) { |
1507 | filter |= RNDIS_PACKET_TYPE_PROMISCUOUS | | 1509 | filter |= RNDIS_PACKET_TYPE_PROMISCUOUS | |
1508 | RNDIS_PACKET_TYPE_ALL_LOCAL; | 1510 | RNDIS_PACKET_TYPE_ALL_LOCAL; |
1509 | } else if (usbdev->net->flags & IFF_ALLMULTI || | 1511 | } else if (usbdev->net->flags & IFF_ALLMULTI) { |
1510 | netdev_mc_count(usbdev->net) > priv->multicast_size) { | 1512 | filter |= RNDIS_PACKET_TYPE_ALL_MULTICAST; |
1513 | } | ||
1514 | |||
1515 | if (filter != basefilter) | ||
1516 | goto set_filter; | ||
1517 | |||
1518 | /* | ||
1519 | * mc_list should be accessed holding the lock, so copy addresses to | ||
1520 | * local buffer first. | ||
1521 | */ | ||
1522 | netif_addr_lock_bh(usbdev->net); | ||
1523 | mc_count = netdev_mc_count(usbdev->net); | ||
1524 | if (mc_count > priv->multicast_size) { | ||
1511 | filter |= RNDIS_PACKET_TYPE_ALL_MULTICAST; | 1525 | filter |= RNDIS_PACKET_TYPE_ALL_MULTICAST; |
1512 | } else if (!netdev_mc_empty(usbdev->net)) { | 1526 | } else if (mc_count) { |
1513 | size = min(priv->multicast_size, netdev_mc_count(usbdev->net)); | 1527 | int i = 0; |
1514 | buf = kmalloc(size * ETH_ALEN, GFP_KERNEL); | 1528 | |
1515 | if (!buf) { | 1529 | mc_addrs = kmalloc(mc_count * ETH_ALEN, GFP_ATOMIC); |
1530 | if (!mc_addrs) { | ||
1516 | netdev_warn(usbdev->net, | 1531 | netdev_warn(usbdev->net, |
1517 | "couldn't alloc %d bytes of memory\n", | 1532 | "couldn't alloc %d bytes of memory\n", |
1518 | size * ETH_ALEN); | 1533 | mc_count * ETH_ALEN); |
1519 | netif_addr_unlock_bh(usbdev->net); | 1534 | netif_addr_unlock_bh(usbdev->net); |
1520 | return; | 1535 | return; |
1521 | } | 1536 | } |
1522 | 1537 | ||
1523 | i = 0; | 1538 | netdev_for_each_mc_addr(mclist, usbdev->net) |
1524 | netdev_for_each_mc_addr(mclist, usbdev->net) { | 1539 | memcpy(mc_addrs + i++ * ETH_ALEN, |
1525 | if (i == size) | 1540 | mclist->dmi_addr, ETH_ALEN); |
1526 | break; | 1541 | } |
1527 | memcpy(buf + i++ * ETH_ALEN, mclist->dmi_addr, ETH_ALEN); | 1542 | netif_addr_unlock_bh(usbdev->net); |
1528 | } | ||
1529 | 1543 | ||
1530 | ret = rndis_set_oid(usbdev, OID_802_3_MULTICAST_LIST, buf, | 1544 | if (filter != basefilter) |
1531 | i * ETH_ALEN); | 1545 | goto set_filter; |
1532 | if (ret == 0 && i > 0) | 1546 | |
1547 | if (mc_count) { | ||
1548 | ret = rndis_set_oid(usbdev, OID_802_3_MULTICAST_LIST, mc_addrs, | ||
1549 | mc_count * ETH_ALEN); | ||
1550 | kfree(mc_addrs); | ||
1551 | if (ret == 0) | ||
1533 | filter |= RNDIS_PACKET_TYPE_MULTICAST; | 1552 | filter |= RNDIS_PACKET_TYPE_MULTICAST; |
1534 | else | 1553 | else |
1535 | filter |= RNDIS_PACKET_TYPE_ALL_MULTICAST; | 1554 | filter |= RNDIS_PACKET_TYPE_ALL_MULTICAST; |
1536 | 1555 | ||
1537 | netdev_dbg(usbdev->net, "OID_802_3_MULTICAST_LIST(%d, max: %d) -> %d\n", | 1556 | netdev_dbg(usbdev->net, "OID_802_3_MULTICAST_LIST(%d, max: %d) -> %d\n", |
1538 | i, priv->multicast_size, ret); | 1557 | mc_count, priv->multicast_size, ret); |
1539 | |||
1540 | kfree(buf); | ||
1541 | } | 1558 | } |
1542 | netif_addr_unlock_bh(usbdev->net); | ||
1543 | 1559 | ||
1560 | set_filter: | ||
1544 | ret = rndis_set_oid(usbdev, OID_GEN_CURRENT_PACKET_FILTER, &filter, | 1561 | ret = rndis_set_oid(usbdev, OID_GEN_CURRENT_PACKET_FILTER, &filter, |
1545 | sizeof(filter)); | 1562 | sizeof(filter)); |
1546 | if (ret < 0) { | 1563 | if (ret < 0) { |