rndis_wlan: correct multicast_list handling V3

My previous patch (655ffee284) 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>
This commit is contained in:
Jiri Pirko 2010-03-04 03:32:16 -08:00 committed by David S. Miller
parent 4c32531324
commit 12c3400a84
1 changed files with 41 additions and 25 deletions

View File

@ -1496,51 +1496,67 @@ static void set_multicast_list(struct usbnet *usbdev)
{
struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev);
struct dev_mc_list *mclist;
__le32 filter;
int ret, i, size;
char *buf;
__le32 filter, basefilter;
int ret;
char *mc_addrs = NULL;
int mc_count;
filter = RNDIS_PACKET_TYPE_DIRECTED | RNDIS_PACKET_TYPE_BROADCAST;
basefilter = filter = RNDIS_PACKET_TYPE_DIRECTED |
RNDIS_PACKET_TYPE_BROADCAST;
netif_addr_lock_bh(usbdev->net);
if (usbdev->net->flags & IFF_PROMISC) {
filter |= RNDIS_PACKET_TYPE_PROMISCUOUS |
RNDIS_PACKET_TYPE_ALL_LOCAL;
} else if (usbdev->net->flags & IFF_ALLMULTI ||
netdev_mc_count(usbdev->net) > priv->multicast_size) {
} else if (usbdev->net->flags & IFF_ALLMULTI) {
filter |= RNDIS_PACKET_TYPE_ALL_MULTICAST;
} else if (!netdev_mc_empty(usbdev->net)) {
size = min(priv->multicast_size, netdev_mc_count(usbdev->net));
buf = kmalloc(size * ETH_ALEN, GFP_KERNEL);
if (!buf) {
}
if (filter != basefilter)
goto set_filter;
/*
* mc_list should be accessed holding the lock, so copy addresses to
* local buffer first.
*/
netif_addr_lock_bh(usbdev->net);
mc_count = netdev_mc_count(usbdev->net);
if (mc_count > priv->multicast_size) {
filter |= RNDIS_PACKET_TYPE_ALL_MULTICAST;
} else if (mc_count) {
int i = 0;
mc_addrs = kmalloc(mc_count * ETH_ALEN, GFP_ATOMIC);
if (!mc_addrs) {
netdev_warn(usbdev->net,
"couldn't alloc %d bytes of memory\n",
size * ETH_ALEN);
mc_count * ETH_ALEN);
netif_addr_unlock_bh(usbdev->net);
return;
}
i = 0;
netdev_for_each_mc_addr(mclist, usbdev->net) {
if (i == size)
break;
memcpy(buf + i++ * ETH_ALEN, mclist->dmi_addr, ETH_ALEN);
}
netdev_for_each_mc_addr(mclist, usbdev->net)
memcpy(mc_addrs + i++ * ETH_ALEN,
mclist->dmi_addr, ETH_ALEN);
}
netif_addr_unlock_bh(usbdev->net);
ret = rndis_set_oid(usbdev, OID_802_3_MULTICAST_LIST, buf,
i * ETH_ALEN);
if (ret == 0 && i > 0)
if (filter != basefilter)
goto set_filter;
if (mc_count) {
ret = rndis_set_oid(usbdev, OID_802_3_MULTICAST_LIST, mc_addrs,
mc_count * ETH_ALEN);
kfree(mc_addrs);
if (ret == 0)
filter |= RNDIS_PACKET_TYPE_MULTICAST;
else
filter |= RNDIS_PACKET_TYPE_ALL_MULTICAST;
netdev_dbg(usbdev->net, "OID_802_3_MULTICAST_LIST(%d, max: %d) -> %d\n",
i, priv->multicast_size, ret);
kfree(buf);
mc_count, priv->multicast_size, ret);
}
netif_addr_unlock_bh(usbdev->net);
set_filter:
ret = rndis_set_oid(usbdev, OID_GEN_CURRENT_PACKET_FILTER, &filter,
sizeof(filter));
if (ret < 0) {