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) { |
