Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

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

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
Loading
Loading
Loading
Loading
+41 −25
Original line number Diff line number Diff line
@@ -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);

	if (filter != basefilter)
		goto set_filter;

		ret = rndis_set_oid(usbdev, OID_802_3_MULTICAST_LIST, buf,
								i * ETH_ALEN);
		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;
		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) {