Commit 12c3400a authored by Jiri Pirko's avatar Jiri Pirko Committed by David S. Miller

rndis_wlan: correct multicast_list handling V3

My previous patch (655ffee2) 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: default avatarJiri Pirko <jpirko@redhat.com>
Reported-by: default avatarJussi Kivilinna <jussi.kivilinna@mbnet.fi>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 4c325313
...@@ -1496,51 +1496,67 @@ static void set_multicast_list(struct usbnet *usbdev) ...@@ -1496,51 +1496,67 @@ static void set_multicast_list(struct usbnet *usbdev)
{ {
struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev);
struct dev_mc_list *mclist; struct dev_mc_list *mclist;
__le32 filter; __le32 filter, basefilter;
int ret, i, size; int ret;
char *buf; 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) { if (usbdev->net->flags & IFF_PROMISC) {
filter |= RNDIS_PACKET_TYPE_PROMISCUOUS | filter |= RNDIS_PACKET_TYPE_PROMISCUOUS |
RNDIS_PACKET_TYPE_ALL_LOCAL; RNDIS_PACKET_TYPE_ALL_LOCAL;
} else if (usbdev->net->flags & IFF_ALLMULTI || } else if (usbdev->net->flags & IFF_ALLMULTI) {
netdev_mc_count(usbdev->net) > priv->multicast_size) { filter |= RNDIS_PACKET_TYPE_ALL_MULTICAST;
}
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; filter |= RNDIS_PACKET_TYPE_ALL_MULTICAST;
} else if (!netdev_mc_empty(usbdev->net)) { } else if (mc_count) {
size = min(priv->multicast_size, netdev_mc_count(usbdev->net)); int i = 0;
buf = kmalloc(size * ETH_ALEN, GFP_KERNEL);
if (!buf) { mc_addrs = kmalloc(mc_count * ETH_ALEN, GFP_ATOMIC);
if (!mc_addrs) {
netdev_warn(usbdev->net, netdev_warn(usbdev->net,
"couldn't alloc %d bytes of memory\n", "couldn't alloc %d bytes of memory\n",
size * ETH_ALEN); mc_count * ETH_ALEN);
netif_addr_unlock_bh(usbdev->net); netif_addr_unlock_bh(usbdev->net);
return; return;
} }
i = 0; netdev_for_each_mc_addr(mclist, usbdev->net)
netdev_for_each_mc_addr(mclist, usbdev->net) { memcpy(mc_addrs + i++ * ETH_ALEN,
if (i == size) mclist->dmi_addr, ETH_ALEN);
break; }
memcpy(buf + 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, if (filter != basefilter)
i * ETH_ALEN); goto set_filter;
if (ret == 0 && i > 0)
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; filter |= RNDIS_PACKET_TYPE_MULTICAST;
else else
filter |= RNDIS_PACKET_TYPE_ALL_MULTICAST; filter |= RNDIS_PACKET_TYPE_ALL_MULTICAST;
netdev_dbg(usbdev->net, "OID_802_3_MULTICAST_LIST(%d, max: %d) -> %d\n", netdev_dbg(usbdev->net, "OID_802_3_MULTICAST_LIST(%d, max: %d) -> %d\n",
i, priv->multicast_size, ret); mc_count, priv->multicast_size, ret);
kfree(buf);
} }
netif_addr_unlock_bh(usbdev->net);
set_filter:
ret = rndis_set_oid(usbdev, OID_GEN_CURRENT_PACKET_FILTER, &filter, ret = rndis_set_oid(usbdev, OID_GEN_CURRENT_PACKET_FILTER, &filter,
sizeof(filter)); sizeof(filter));
if (ret < 0) { if (ret < 0) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment