diff options
author | Jiri Pirko <jpirko@redhat.com> | 2010-03-04 06:32:16 -0500 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2010-03-04 06:32:16 -0500 |
commit | 12c3400a84742f8bb0e4edc822e9ccba58781e0c (patch) | |
tree | 0d4d98975822024b6d7414739b6a88f83b553da4 | |
parent | 4c32531324b83672f100692354b680625bcd7fba (diff) |
rndis_wlan: correct multicast_list handling V3
My previous patch (655ffee284dfcf9a24ac0343f3e5ee6db85b85c5) added locking in
a bad way. Because rndis_set_oid can sleep, there is need to prepare multicast
addresses into local buffer under netif_addr_lock first, then call
rndis_set_oid outside. This caused reorganizing of the whole function.
Signed-off-by: Jiri Pirko <jpirko@redhat.com>
Reported-by: Jussi Kivilinna <jussi.kivilinna@mbnet.fi>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | drivers/net/wireless/rndis_wlan.c | 66 |
1 files changed, 41 insertions, 25 deletions
diff --git a/drivers/net/wireless/rndis_wlan.c b/drivers/net/wireless/rndis_wlan.c index 9f6d6bf06b8e..2887047069f2 100644 --- a/drivers/net/wireless/rndis_wlan.c +++ b/drivers/net/wireless/rndis_wlan.c | |||
@@ -1496,51 +1496,67 @@ static void set_multicast_list(struct usbnet *usbdev) | |||
1496 | { | 1496 | { |
1497 | struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); | 1497 | struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); |
1498 | struct dev_mc_list *mclist; | 1498 | struct dev_mc_list *mclist; |
1499 | __le32 filter; | 1499 | __le32 filter, basefilter; |
1500 | int ret, i, size; | 1500 | int ret; |
1501 | char *buf; | 1501 | char *mc_addrs = NULL; |
1502 | int mc_count; | ||
1502 | 1503 | ||
1503 | filter = RNDIS_PACKET_TYPE_DIRECTED | RNDIS_PACKET_TYPE_BROADCAST; | 1504 | basefilter = filter = RNDIS_PACKET_TYPE_DIRECTED | |
1505 | RNDIS_PACKET_TYPE_BROADCAST; | ||
1504 | 1506 | ||
1505 | netif_addr_lock_bh(usbdev->net); | ||
1506 | if (usbdev->net->flags & IFF_PROMISC) { | 1507 | if (usbdev->net->flags & IFF_PROMISC) { |
1507 | filter |= RNDIS_PACKET_TYPE_PROMISCUOUS | | 1508 | filter |= RNDIS_PACKET_TYPE_PROMISCUOUS | |
1508 | RNDIS_PACKET_TYPE_ALL_LOCAL; | 1509 | RNDIS_PACKET_TYPE_ALL_LOCAL; |
1509 | } else if (usbdev->net->flags & IFF_ALLMULTI || | 1510 | } else if (usbdev->net->flags & IFF_ALLMULTI) { |
1510 | netdev_mc_count(usbdev->net) > priv->multicast_size) { | 1511 | filter |= RNDIS_PACKET_TYPE_ALL_MULTICAST; |
1512 | } | ||
1513 | |||
1514 | if (filter != basefilter) | ||
1515 | goto set_filter; | ||
1516 | |||
1517 | /* | ||
1518 | * mc_list should be accessed holding the lock, so copy addresses to | ||
1519 | * local buffer first. | ||
1520 | */ | ||
1521 | netif_addr_lock_bh(usbdev->net); | ||
1522 | mc_count = netdev_mc_count(usbdev->net); | ||
1523 | if (mc_count > priv->multicast_size) { | ||
1511 | filter |= RNDIS_PACKET_TYPE_ALL_MULTICAST; | 1524 | filter |= RNDIS_PACKET_TYPE_ALL_MULTICAST; |
1512 | } else if (!netdev_mc_empty(usbdev->net)) { | 1525 | } else if (mc_count) { |
1513 | size = min(priv->multicast_size, netdev_mc_count(usbdev->net)); | 1526 | int i = 0; |
1514 | buf = kmalloc(size * ETH_ALEN, GFP_KERNEL); | 1527 | |
1515 | if (!buf) { | 1528 | mc_addrs = kmalloc(mc_count * ETH_ALEN, GFP_ATOMIC); |
1529 | if (!mc_addrs) { | ||
1516 | netdev_warn(usbdev->net, | 1530 | netdev_warn(usbdev->net, |
1517 | "couldn't alloc %d bytes of memory\n", | 1531 | "couldn't alloc %d bytes of memory\n", |
1518 | size * ETH_ALEN); | 1532 | mc_count * ETH_ALEN); |
1519 | netif_addr_unlock_bh(usbdev->net); | 1533 | netif_addr_unlock_bh(usbdev->net); |
1520 | return; | 1534 | return; |
1521 | } | 1535 | } |
1522 | 1536 | ||
1523 | i = 0; | 1537 | netdev_for_each_mc_addr(mclist, usbdev->net) |
1524 | netdev_for_each_mc_addr(mclist, usbdev->net) { | 1538 | memcpy(mc_addrs + i++ * ETH_ALEN, |
1525 | if (i == size) | 1539 | mclist->dmi_addr, ETH_ALEN); |
1526 | break; | 1540 | } |
1527 | memcpy(buf + i++ * ETH_ALEN, mclist->dmi_addr, ETH_ALEN); | 1541 | netif_addr_unlock_bh(usbdev->net); |
1528 | } | ||
1529 | 1542 | ||
1530 | ret = rndis_set_oid(usbdev, OID_802_3_MULTICAST_LIST, buf, | 1543 | if (filter != basefilter) |
1531 | i * ETH_ALEN); | 1544 | goto set_filter; |
1532 | if (ret == 0 && i > 0) | 1545 | |
1546 | if (mc_count) { | ||
1547 | ret = rndis_set_oid(usbdev, OID_802_3_MULTICAST_LIST, mc_addrs, | ||
1548 | mc_count * ETH_ALEN); | ||
1549 | kfree(mc_addrs); | ||
1550 | if (ret == 0) | ||
1533 | filter |= RNDIS_PACKET_TYPE_MULTICAST; | 1551 | filter |= RNDIS_PACKET_TYPE_MULTICAST; |
1534 | else | 1552 | else |
1535 | filter |= RNDIS_PACKET_TYPE_ALL_MULTICAST; | 1553 | filter |= RNDIS_PACKET_TYPE_ALL_MULTICAST; |
1536 | 1554 | ||
1537 | netdev_dbg(usbdev->net, "OID_802_3_MULTICAST_LIST(%d, max: %d) -> %d\n", | 1555 | netdev_dbg(usbdev->net, "OID_802_3_MULTICAST_LIST(%d, max: %d) -> %d\n", |
1538 | i, priv->multicast_size, ret); | 1556 | mc_count, priv->multicast_size, ret); |
1539 | |||
1540 | kfree(buf); | ||
1541 | } | 1557 | } |
1542 | netif_addr_unlock_bh(usbdev->net); | ||
1543 | 1558 | ||
1559 | set_filter: | ||
1544 | ret = rndis_set_oid(usbdev, OID_GEN_CURRENT_PACKET_FILTER, &filter, | 1560 | ret = rndis_set_oid(usbdev, OID_GEN_CURRENT_PACKET_FILTER, &filter, |
1545 | sizeof(filter)); | 1561 | sizeof(filter)); |
1546 | if (ret < 0) { | 1562 | if (ret < 0) { |