From nobody Thu Sep 24 17:56:23 2026 Received: from rcdn-iport-5.cisco.com (rcdn-iport-5.cisco.com [173.37.86.76]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 25F9D3D88F5; Mon, 21 Sep 2026 20:01:29 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=173.37.86.76 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790020891; cv=none; b=polN/J6df6TbPoWZ3bUJLGaNSzsmA4Gc+KNY0em7SyVQVnMsvTgMuFIylULWLcKUESSuZ7xeMcxR/0vaNuwBXyLAx4w5aIELnteZlcwRw8o+8xMTNSEpi/fffAwX/95U2ZDE8K7jh8AAMSg5qVyEfllaMUjZvgXKEbEafBbSdrI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790020891; c=relaxed/simple; bh=1IbCnn5ExMs1u3paqE8e5LFnVmYSitrHG+7sl4Bq+3E=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=MpRBixnGrZLh0ZG7MI99goTahqTdYKyUCvj0W4jaoq+wSWTtGiAiFJtrlOFgFmSy4exVsOtmlGgOcEcOppA3xTigWxtYxJx7YMJeUrPvBiZTMOy9PU3LodzedrG83vZU8YSu5FS4liTRq0FLLGt8u6XNR5wwNdQdhQ6gJaO5zAs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=cisco.com; spf=pass smtp.mailfrom=cisco.com; dkim=pass (2048-bit key) header.d=cisco.com header.i=@cisco.com header.b=b8w/w7uk; arc=none smtp.client-ip=173.37.86.76 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=cisco.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=cisco.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=cisco.com header.i=@cisco.com header.b="b8w/w7uk" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cisco.com; i=@cisco.com; l=2408; q=dns/txt; s=iport01; t=1790020890; x=1791230490; h=from:date:subject:mime-version:content-transfer-encoding: message-id:references:in-reply-to:to:cc; bh=J8PIC4Sls3ICIho+s3ExQIvvAHAm1YM1y599LQa8IOM=; b=b8w/w7ukpSMM2Mg/DSjEgYILribwA5cSBK5vCaZqesTtKGXn/HsVMLI7 e/26h6sd7dmiyycabn69kcFZu2AvIEpTglDvxBoYpBe3WWeYlBgWK53Dk 3lw9ccuA5lRQKGllMmpHO69dE86Wil0W4eUspGIDDCBQ3YA0r2HuQF1uw sCVd8QXnQ4cm12pDdVqIIDAo76j+1lWMbxdRwfHJKEvmICcaJ5L2SjBo/ woU+93nm69Yqn+KDlSmboCrIvMeoim0Ctp2jLflSIKcxn1OPCmH2mKADX LoAYKNvJL7kpIrZRH5Ao5nyc4we1OZC/iKkSnAhFNFx10D7Bb40aQ/eMp Q==; X-CSE-ConnectionGUID: ZPVFOrqYRziyUxsXGou5Rw== X-CSE-MsgGUID: F8oFZYD7RYCIAnMbFh33Hg== X-IPAS-Result: =?us-ascii?q?A0BGAgCvi7Fq/47/Ja1aglmCGD+BVUNJhFePUoIhmECFX?= =?us-ascii?q?hSBag8BAQEPUQQBAYUFAo4HAiY0CQ4BAgQDAgMBAQEBAQEBAQEBAQEKAQEFA?= =?us-ascii?q?QEBAgEHBYEOE4ZckBMCAQMjVhAlAiYCAkYQBgESgwKCdQPFeHqBMoEBhH7ZS?= =?us-ascii?q?oFoAQsUAYEKLoVxgm0BgXCECDuEQScbgUlEgRWDaYQqg3SCagSDHBKUWgk/g?= =?us-ascii?q?QIcA1ksAVUTDQoLBwVhUjMDIAoLEhIYFQIUHRIPBBYyHXAMJxIsFy9YGwYFg?= =?us-ascii?q?R18FAQTghojGTZ6gQlegSspYAEQF4EHgggCglSBfwIBSUMOB0VTCSdBChJHK?= =?us-ascii?q?SIIEgkBExowC4EnGA1IESw3Bg8ZBD0BbgePXh+CSgIQFnklWoEUHTiTHSWDW?= =?us-ascii?q?Y1fa4E1n1qEKIFmoBAzhASTY5MGLodmkHQjgjaiAYRpgWg8gVkzGggbFYMiU?= =?us-ascii?q?xkPji4WgRQBAZIHAbx7JzI+AQcCBw8CgXORfgEB?= IronPort-Data: A9a23:8wrAL6BfFTKcTRVW//viw5YqxClBgxIJ4kV8jS/XYbTApDtxgjJUx 2MfXG2Pb/qJM2H2fdtxPNm+o01VvMCEm9BrOVdlrnsFo1CmBibm6XV1Cm+qYkt+++WaFBoPA /02M4eGcYZsCCCM/n9BC5C5xVFkz6aEW7HgP+DNPyF1VGdMRTwo4f5Zs7ZRbrVA357jXGthh fuo+5eBYA7/gmYuWo4pw/vrRC1H7ayaVAww5jTSVdgT1HfCmn8cCo4oJK3ZBxPQXolOE+emc P3Ixbe/83mx109F5gSNy+uTnuUiG9Y+DCDW4pZkc/HKbitq+kTe5p0G2M80Mi+7vdkmc+dZk 72hvbToIesg0zaldO41C3G0GAkmVUFKFSOuzXWX6aSuI0P6n3TE8c1QImYsJLwi6OdmPHFVx dMfNW03cUXW7w626OrTpuhEnM8vKozveYgYoHwllWyfBvc9SpeFSKLPjTNa9G5v3YYVQrCEO pdfMGY1BPjDS0Un1lM/E5sinfyohXnXeDxDo1XTrq0yi4TW5FErjuGyb4uPJrRmQ+17zkG2l FPqoF7IKTAzMvmV5zreqVKj07qncSTTHdh6+KeD3vJjnlCW7mAaFhATUVy1vb+/h1LWc99TN kkd6Ccyhac180OvQ5/2WBjQiH6DpBsHc9ldCes37EeK0KW8ywWEDGEsTTNbbtEi8sgsSlQC3 1mFhd72RjpirLGYV1qZ67GS6ziyUQANJGUPYy4sVwYJ49D/5oo0i3rnStdlDb7wjdDvHzz06 y6FoTJ4hLgJi8MPkaKh8jjvhT+wqpXXZhA66x+RXW+/6A59Iom/aOSVBUPz9/1MKsOdC1KGp nVBw5XY5+EVBpbLnyuIKAkQIIyUCz++GGW0qTZS81MJrlxBJ1bLkVhs3QxD IronPort-HdrOrdr: A9a23:drM86K+IjT8L6CPw1Yhuk+AuI+orL9Y04lQ7vn2ZhyYlFvBw8P rOoB1773HJYVkqNU3I9errBEDEewK/yXcX2+Ys1MmZPDUO0VHAROpfBMnZsl7d8kbFmtK1u5 0QFpRWOZncAUd3iMDi7BL9MdoxqeP3iZyAtKPyw2hgSAdyZ7ol1Qd2DQ6HDlZ7LTM2Y6bRbK DsgfZvln6HZWkdaNi9Cz0uWujOoMCOqbfdCCR2fyLOLGK1/EmVAHmQKWni4isj X-Talos-CUID: 9a23:MQZRam2Qef3QEi2OPszOCbxfKPgbXFrH1UbqMU6pNHZyeJKSVAKq5/Yx X-Talos-MUID: 9a23:RrdaNQqyb7M+W/XLh2cezx8yM+la0ojxMnEmsLwAnsOqNXcrGzjI2Q== X-IronPort-Anti-Spam-Filtered: true X-IronPort-AV: E=Sophos;i="6.27,115,1787011200"; d="scan'208";a="526177924" Received: from rcdn-l-core-05.cisco.com ([173.37.255.142]) by rcdn-iport-5.cisco.com with ESMTP/TLS/TLS_AES_256_GCM_SHA384; 21 Sep 2026 20:00:22 +0000 Received: from [127.0.0.1] (unknown [10.25.128.208]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by rcdn-l-core-05.cisco.com (Postfix) with ESMTPS id 76FC11800035F; Mon, 21 Sep 2026 20:00:21 +0000 (GMT) From: Satish Kharat Date: Mon, 21 Sep 2026 12:59:44 -0700 Subject: [PATCH net-next 1/6] net: add netif_rx_mode_schedule_fresh() Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260921-b4-enic-sriov-v2-vf-receive-control-v1-1-67a0a6e08d43@cisco.com> References: <20260921-b4-enic-sriov-v2-vf-receive-control-v1-0-67a0a6e08d43@cisco.com> In-Reply-To: <20260921-b4-enic-sriov-v2-vf-receive-control-v1-0-67a0a6e08d43@cisco.com> To: Andrew Lunn , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, Satish Kharat , sebaddel@cisco.com X-Mailer: b4 0.15.2 X-Developer-Signature: v=1; a=ed25519-sha256; t=1790020820; l=2414; i=satishkh@cisco.com; h=from:subject:message-id; bh=1IbCnn5ExMs1u3paqE8e5LFnVmYSitrHG+7sl4Bq+3E=; b=Zsix0xTJfOEQYoG315riWlX/NJUX1udCnr14VobgzN6SamnTdKThKS72mVMdlR9GkpchBIiU0 1x1rPmfqTB8DJsUdS13NLZmGF9K/lGkCkJ/DLijj+ZlbCjbF5egogk3 X-Developer-Key: i=satishkh@cisco.com; a=ed25519; pk=4BxbLoCGvHeIMH/PbpiNVx8/2IcTjFvqcr0AD8K6WLY= X-Outbound-Client-TLS: ANONYMOUS;unknown [10.25.128.208];TLSv1.3;TLS_AES_256_GCM_SHA384;256 X-Outbound-SMTP-Client: 10.25.128.208, [10.25.128.208] X-Outbound-Node: rcdn-l-core-05.cisco.com Add a receive-mode scheduling helper for callers that need to replay the complete configuration after a state transition. Cancel a pending retry before queueing the immediate update so stale retry timers cannot consume the new operation's retry budget. Resetting the backoff also gives the fresh replay its full bounded retry sequence. Assisted-by: LLM Signed-off-by: Satish Kharat --- include/linux/netdevice.h | 1 + net/core/dev_addr_lists.c | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 5d16737167ee..2246be72d811 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -5197,6 +5197,7 @@ static inline void __dev_mc_unsync(struct net_device = *dev, =20 /* Functions used for secondary unicast and multicast support */ void dev_set_rx_mode(struct net_device *dev); +void netif_rx_mode_schedule_fresh(struct net_device *dev); void netif_rx_mode_schedule_retry(struct net_device *dev); int netif_set_promiscuity(struct net_device *dev, int inc); int dev_set_promiscuity(struct net_device *dev, int inc); diff --git a/net/core/dev_addr_lists.c b/net/core/dev_addr_lists.c index 08528ca0a8b3..4ae0510ce91f 100644 --- a/net/core/dev_addr_lists.c +++ b/net/core/dev_addr_lists.c @@ -1337,6 +1337,27 @@ static void netif_rx_mode_queue(struct net_device *d= ev) __netdev_work_core_sched(dev, NETDEV_WORK_RX_MODE); } =20 +/** + * netif_rx_mode_schedule_fresh() - schedule a fresh receive-mode update + * @dev: network device + * + * Cancel any pending retry and reset its backoff budget before scheduling= an + * immediate receive-mode update. Callers use this after a state transition + * which requires the complete receive configuration to be replayed, rather + * than after a failed update (which uses netif_rx_mode_schedule_retry()). + * + * Context: sleepable. The caller must hold the device operations lock, or + * RTNL for a device which still uses RTNL-compatible operations. + */ +void netif_rx_mode_schedule_fresh(struct net_device *dev) +{ + might_sleep(); + netdev_assert_locked_ops_compat(dev); + netif_rx_mode_cancel_retry(dev); + netif_rx_mode_queue(dev); +} +EXPORT_SYMBOL_GPL(netif_rx_mode_schedule_fresh); + static void netif_rx_mode_retry(struct timer_list *t) { struct net_device *dev =3D --=20 2.43.0 From nobody Thu Sep 24 17:56:23 2026 Received: from rcdn-iport-2.cisco.com (rcdn-iport-2.cisco.com [173.37.86.73]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 30C4F3E3D90; Mon, 21 Sep 2026 20:01:31 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=173.37.86.73 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790020894; cv=none; b=dQpa21pcSf3h5cPxRp8Xv7ogL1BjyK5C+P7nNUcB2aVlyvehos+T//5opFoNn8sv9LrYVqmz9CiUaaRTf/+zIvruDpDmlE6VJFvIr/v31LVxfaZPhXzdzsFCkdMcfTMIajyBjeqxPuLEvukOthO+WYrZM+5Cbvf+8DD6Yh8k69Y= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790020894; c=relaxed/simple; bh=sFh0aQamfTAjvNlnNEhb6wt7vj2SCLsQOvGzBgc1wfU=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=iOekSfaPnDedkh2MqPiIxdp/mKTgMX1nga27FwB69dxAWlgOPL8wU+cBCJOvNUe0l7xg8/7u84ZkPdpzYch3PKneDUILXhGs/z9Fyyxlt8DSFo10IyItG0YNp2dih42I+xNn/IUKwLz6QQiGHrjg6mWDg3TSPMF4ggcCaBt8hvE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=cisco.com; spf=pass smtp.mailfrom=cisco.com; dkim=pass (2048-bit key) header.d=cisco.com header.i=@cisco.com header.b=PyhmwhHJ; arc=none smtp.client-ip=173.37.86.73 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=cisco.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=cisco.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=cisco.com header.i=@cisco.com header.b="PyhmwhHJ" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cisco.com; i=@cisco.com; l=4854; q=dns/txt; s=iport01; t=1790020892; x=1791230492; h=from:date:subject:mime-version:content-transfer-encoding: message-id:references:in-reply-to:to:cc; bh=SMC6SgLhwngWg/peGv5/1lQgClaqRttLnJ85Qw8EPrU=; b=PyhmwhHJ8W8jOxPt1kM3ax6RK8t9FfqGfXoIf1BOZosr/MWp7zc3nyV3 pe+hYYKcUkat5t8VVzO3hc/7Whb2Lxq6TmFC4I1puFtBa4f6mXyE6XOjg RmBdRCmfQzrn5naskZBk6V7gTjGhvqzxZ3KV85AEF1ABNpGI5zEOgEP0K VWNEaGPx97UeKJZFzhEA3gRIByaIe94DFTZeLRYeWXvyqA/N/Yz/MJuT0 6EuVTLGDLHmctkrXJBBXRT3cIixky4nRqgcVzUSSMI5td1JtMbZ2jCGXZ DkHcIZeKBA+16BwtcXK96BpWFGSrPB9pqUOKUbh4DmXVf08gwH5uxZALW A==; X-CSE-ConnectionGUID: iwYid21+Qy+PBPty+d1JgQ== X-CSE-MsgGUID: dXnpGvi8SjCoMvlR4+JdJQ== X-IPAS-Result: =?us-ascii?q?A0AnAAAnjLFq/47/Ja1aHQEBAQEJARIBBQUBgXwIAQsBg?= =?us-ascii?q?laBVUNJhFeIG4c3giGeHoF+DwEBAQ9RBAEBhQUCjgcCJjQJDgECBAMCAwEBA?= =?us-ascii?q?QEBAQEBAQEBAQoBAQUBAQECAQcFgQ4ThlyQEwIBAyMEUhAlAiYCAkUBEAYBE?= =?us-ascii?q?oMCgnUDxWx6fzOBAYR+2UqBaAELFAGBCi4BhXCCbQGBcIQIO4RBJxuBSUSBF?= =?us-ascii?q?YNpgQUBgxmDf4JqBIMcEpRaCT+BAhwDWSwBVRMNCgsHBWFSMwMgCgsSEhgVA?= =?us-ascii?q?hQdEg8EFjIdcAwnEiwXL1gbBgWBHXwUBBOCGiMZNnqBCV6BKylgARAXgQeCC?= =?us-ascii?q?AKCVIF/AgFJQw4HRVMJJ0EKEkcpIggSCQETGjALgScYDUgRLBQjBg8ZBD0Bb?= =?us-ascii?q?gePXh+CXIEECyUmYQGBADCXJo1fa6EPhCiBZqAQM5dnkwaZCCOjZ1CEaYFoP?= =?us-ascii?q?IFZMxoIGxWDIlMZD44uCwuTHQG8eycyPQEBBwIHDgMLgWiQEYFtAQE?= IronPort-Data: A9a23:Su6s6q0f/1VaQuofjPbD5QFwkn2cJEfYwER7XKvMYLTBsI5bpzYFx 2seXz/VPPeCa2ugL9kiPo/i8EgOuJCEnIA3SAFo3Hw8FHgiRegpqji6wuYcGwvIc6UvmWo+t 512huHodZ5yEzmE4Ej9atANlFEkvYmQXL3wFeXYDS54QA5gWU8JhAlq8wIDqtYAbeORXUXX5 rsen+WFYAX7g28uazpOg06+gEoHUMra6WtwUmMWPZinjHeG/1EJAZQWI72GLneQauF8Au6gS u/f+6qy92Xf8g1FIovNfmHTKxBirhb6ZGBiu1IOM0SQqkEqSh8ajs7XAMEhhXJ/0F1lqTzeJ OJl7vRcQS9xVkHFdX90vxNwS0mSNoUekFPLzOTWXcG7lyX7n3XQL/pGDV4HPIFCo6VOECJ23 8QEOXMBNzKMvrfjqF67YrEEasULNsLnOsYb/3pn1zycVatgSpHYSKKM7thdtNsyrpkRRrCFO IxDNGcpNUiaC/FMEg9/5JYWh+60h2L/cD1wo1OOrq1x6G/WpOB0+OW0aYSEIYTbFa25mG6Ul mTe0Hr4UioYH9u61z6/rjGOis32yHaTtIU6UefQGuRRqFGa2GEWIBEfS1a+pf63lgi4Vs43A 0EY5y8nv6Uu3EOsSdb5Uluzp3vslh4bQd9LO+438geAzuzT+QnxLmsZRDdpa9E8ssIyAzsw2 TehltXlHzFw87uYVX6Q6J+QsDW0fyMVRUcabCUJSwot+dTvoIgvyBnIS75LFK+zk82wGjzqx T2OhDYxiq9VjsMR0ai/u1fdjFqEoJXVQgMrzhvYU3jj7Q5jYoOhIYuy5jDmAe1oNo2VSByF+ XMDgcXbtL1IBpCWnyvLS+IIdF2028u43PTnqQYHN/EcG/6FohZPoag4DOlCGXpU IronPort-HdrOrdr: A9a23:K3fxH6gkRVxat1Eh99mz8RiMZXBQXiwji2hC6mlwRA09TyVXrb HLoB19737JYVcqNk3I9ursBEDtexnhHNtOkOss1NSZMzUOxlHJEGgK1+KLqAEIWReOldK1vp 0NT0ERMqyUMXFKyev3/wW8Fc8t252k/LDAv5am815dCSxndK1k6R50EUKgEkNwTBRbHpZRLu v72iM+nUvGRV0nKuKmG3ICQ+/P4/fPlJ7gfFo6IiRP0nj3sdttg4SKaSRxGXwlIkhy/Ys= X-Talos-CUID: =?us-ascii?q?9a23=3ATpe8y2lwmfUyvcBJ+MB6EPNdXQvXOXzC/XbceRb?= =?us-ascii?q?7MHcqcJetVU7T9PpGnPM7zg=3D=3D?= X-Talos-MUID: 9a23:6rMEjAandgh1r+BTuw/qnB58NZdT4Y+pLV4Jq8lFmcWmDHkl X-IronPort-Anti-Spam-Filtered: true X-IronPort-AV: E=Sophos;i="6.27,115,1787011200"; d="scan'208";a="511693098" Received: from rcdn-l-core-05.cisco.com ([173.37.255.142]) by rcdn-iport-2.cisco.com with ESMTP/TLS/TLS_AES_256_GCM_SHA384; 21 Sep 2026 20:00:23 +0000 Received: from [127.0.0.1] (unknown [10.25.128.208]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by rcdn-l-core-05.cisco.com (Postfix) with ESMTPS id 3921C18000353; Mon, 21 Sep 2026 20:00:22 +0000 (GMT) From: Satish Kharat Date: Mon, 21 Sep 2026 12:59:45 -0700 Subject: [PATCH net-next 2/6] enic: serialize V2 VF mailbox requests Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260921-b4-enic-sriov-v2-vf-receive-control-v1-2-67a0a6e08d43@cisco.com> References: <20260921-b4-enic-sriov-v2-vf-receive-control-v1-0-67a0a6e08d43@cisco.com> In-Reply-To: <20260921-b4-enic-sriov-v2-vf-receive-control-v1-0-67a0a6e08d43@cisco.com> To: Andrew Lunn , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, Satish Kharat , sebaddel@cisco.com X-Mailer: b4 0.15.2 X-Developer-Signature: v=1; a=ed25519-sha256; t=1790020820; l=4981; i=satishkh@cisco.com; h=from:subject:message-id; bh=sFh0aQamfTAjvNlnNEhb6wt7vj2SCLsQOvGzBgc1wfU=; b=Uw6mZWTvaSZRZFk5/kNI3TN7V6NTBiHnXnWZHjli5SvjZ4PjlcoOOzWcrZ1dTLkh045/T+CAd 2t+eHUQxsnZD2kkhQZVsyoxhlVDagiwZmKAD/mr7w24wg5XOlWdOEYn X-Developer-Key: i=satishkh@cisco.com; a=ed25519; pk=4BxbLoCGvHeIMH/PbpiNVx8/2IcTjFvqcr0AD8K6WLY= X-Outbound-Client-TLS: ANONYMOUS;unknown [10.25.128.208];TLSv1.3;TLS_AES_256_GCM_SHA384;256 X-Outbound-SMTP-Client: 10.25.128.208, [10.25.128.208] X-Outbound-Node: rcdn-l-core-05.cisco.com The VF stores one expected reply and uses one completion for mailbox requests. If two VF control paths issue requests at the same time, the second request can replace the reply state for the first. Add a request mutex used only by the VF. Hold it from before a request is armed until its reply or timeout has been consumed. Recheck VF registration after taking the mutex and clear pending state on send failure so a later request cannot inherit it. PF-side request processing is unchanged. Unsolicited PF notifications and their acknowledgments remain asynchronous. Assisted-by: LLM Signed-off-by: Satish Kharat --- drivers/net/ethernet/cisco/enic/enic.h | 1 + drivers/net/ethernet/cisco/enic/enic_mbox.c | 49 +++++++++++++++++++++++++= ++-- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/cisco/enic/enic.h b/drivers/net/ethernet/= cisco/enic/enic.h index 7a509a056990..3945fe28f199 100644 --- a/drivers/net/ethernet/cisco/enic/enic.h +++ b/drivers/net/ethernet/cisco/enic/enic.h @@ -335,6 +335,7 @@ struct enic { * the requester. */ struct completion mbox_comp; + struct mutex vf_mbox_request_lock; /* serializes VF request lifetimes */ spinlock_t mbox_state_lock; /* protects expected reply state */ u64 mbox_expected_msg_num; u8 mbox_expected_reply; diff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.c b/drivers/net/ethe= rnet/cisco/enic/enic_mbox.c index 5c93ca49552a..b8a18d9682b2 100644 --- a/drivers/net/ethernet/cisco/enic/enic_mbox.c +++ b/drivers/net/ethernet/cisco/enic/enic_mbox.c @@ -218,6 +218,32 @@ static int enic_mbox_wait_reply(struct enic *enic, uns= igned long timeout_ms) return err; } =20 +static void enic_mbox_vf_request_start(struct enic *enic) +{ + mutex_lock(&enic->vf_mbox_request_lock); + reinit_completion(&enic->mbox_comp); + spin_lock_bh(&enic->mbox_state_lock); + enic->mbox_expected_msg_num =3D 0; + enic->mbox_expected_reply =3D 0; + spin_unlock_bh(&enic->mbox_state_lock); +} + +static void enic_mbox_vf_request_abort(struct enic *enic) +{ + lockdep_assert_held(&enic->vf_mbox_request_lock); + spin_lock_bh(&enic->mbox_state_lock); + enic->mbox_expected_reply =3D 0; + enic->mbox_expected_msg_num =3D 0; + spin_unlock_bh(&enic->mbox_state_lock); + mutex_unlock(&enic->vf_mbox_request_lock); +} + +static void enic_mbox_vf_request_finish(struct enic *enic) +{ + lockdep_assert_held(&enic->vf_mbox_request_lock); + mutex_unlock(&enic->vf_mbox_request_lock); +} + int enic_mbox_send_link_state(struct enic *enic, u16 vf_id, u32 link_state) { struct enic_mbox_pf_link_state_notif_msg notif =3D {}; @@ -623,6 +649,7 @@ int enic_mbox_vf_capability_check(struct enic *enic) u32 version; int err; =20 + enic_mbox_vf_request_start(enic); WRITE_ONCE(enic->pf_cap_version, 0); req.version =3D cpu_to_le32(ENIC_MBOX_CAP_VERSION_1); =20 @@ -630,11 +657,14 @@ int enic_mbox_vf_capability_check(struct enic *enic) ENIC_MBOX_VF_CAPABILITY_REQUEST, ENIC_MBOX_VF_CAPABILITY_REPLY, &req, sizeof(req)); - if (err) + if (err) { + enic_mbox_vf_request_abort(enic); return err; + } =20 err =3D enic_mbox_wait_reply(enic, 3000); version =3D READ_ONCE(enic->pf_cap_version); + enic_mbox_vf_request_finish(enic); if (err) { netdev_warn(enic->netdev, "MBOX: no capability reply from PF\n"); @@ -656,15 +686,19 @@ int enic_mbox_vf_register(struct enic *enic) bool registered; int err; =20 + enic_mbox_vf_request_start(enic); WRITE_ONCE(enic->vf_registered, false); =20 err =3D enic_mbox_vf_send_request(enic, ENIC_MBOX_VF_REGISTER_REQUEST, ENIC_MBOX_VF_REGISTER_REPLY, NULL, 0); - if (err) + if (err) { + enic_mbox_vf_request_abort(enic); return err; + } =20 err =3D enic_mbox_wait_reply(enic, 3000); registered =3D READ_ONCE(enic->vf_registered); + enic_mbox_vf_request_finish(enic); if (err) { netdev_warn(enic->netdev, "MBOX: VF registration with PF timed out\n"); @@ -684,16 +718,24 @@ int enic_mbox_vf_unregister(struct enic *enic) =20 if (!READ_ONCE(enic->vf_registered)) return 0; + enic_mbox_vf_request_start(enic); + if (!READ_ONCE(enic->vf_registered)) { + enic_mbox_vf_request_finish(enic); + return 0; + } =20 err =3D enic_mbox_vf_send_request(enic, ENIC_MBOX_VF_UNREGISTER_REQUEST, ENIC_MBOX_VF_UNREGISTER_REPLY, NULL, 0); - if (err) + if (err) { + enic_mbox_vf_request_abort(enic); return err; + } =20 err =3D enic_mbox_wait_reply(enic, 3000); registered =3D READ_ONCE(enic->vf_registered); + enic_mbox_vf_request_finish(enic); if (err) return err; if (registered) @@ -711,6 +753,7 @@ void enic_mbox_init(struct enic *enic) */ if (!reinit) { mutex_init(&enic->mbox_lock); + mutex_init(&enic->vf_mbox_request_lock); init_completion(&enic->mbox_comp); spin_lock_init(&enic->mbox_state_lock); enic->mbox_msg_num =3D 0; --=20 2.43.0 From nobody Thu Sep 24 17:56:23 2026 Received: from rcdn-iport-4.cisco.com (rcdn-iport-4.cisco.com [173.37.86.75]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3107F443C30; Mon, 21 Sep 2026 20:01:32 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=173.37.86.75 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790020896; cv=none; b=ZBe28MRVtvytguip58oVmhWR8ZaRRZuZc8VfemTyNltWnVAVcoBH3CxCS+FLBhwFRRg/M8aUVeTide0V9l22TiV5nj52KdBCXDXADFmHyAjcBPv7YDdzK6coEzACH6N4O2/yEZZD+eQUw1rjMtK0zW5AUoKv8i2UdExHl4p6KjI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790020896; c=relaxed/simple; bh=QkZwVj5suBrZgkE0RrWLgjO4TXrkNcLwYiH5Lqfhn0g=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=t8IzRe/AsJvUWbXr+gLkZ5ImpXmAfpr+JWe8qLLu5dFPCcf+Xm9jOP0JUQV1Iew6Txp94LNbhGXypjrUf3jLuVbDZeWi5WP+rQlzO7st2KVFfS85XCbwBzJXBbAeOuKWZxxGxL01kt6XU1lxqbZSKkkXdgse4/NbTvwBPtJqsG0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=cisco.com; spf=pass smtp.mailfrom=cisco.com; dkim=pass (2048-bit key) header.d=cisco.com header.i=@cisco.com header.b=Y9loUF/H; arc=none smtp.client-ip=173.37.86.75 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=cisco.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=cisco.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=cisco.com header.i=@cisco.com header.b="Y9loUF/H" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cisco.com; i=@cisco.com; l=29686; q=dns/txt; s=iport01; t=1790020892; x=1791230492; h=from:date:subject:mime-version:content-transfer-encoding: message-id:references:in-reply-to:to:cc; bh=qzmf9x2iSPClodO3s3BJBheIgEFou2YsvV4QaaDbBYI=; b=Y9loUF/Hz+uMbpDo3IFGg12fMNSsZPLMWVjkREvWjJProG94R4pCUYXD WfyF7SlpDXh1RxBqp4zRf6sx9WFQykPklONawjXEasXRqZrQ9iaB+/3Z4 u1zaoPI4PNBz2UJHo2268mNhoDQC6VXB6yxFM3yP5xY1uotnEx6+kyyb8 shjCwyyKCx2f+SzHwKbT7fKhmMF73A4gP7gxOR04b4FyU5KUh58ZrIHGs mU3Stx4WA7hCO8k94Xitd9D2cWkpt4e3uB+L+hQkezsGN5i73wgQuEBZ5 Xc/RpTS4VT+pUbqvbW/cLvPUGqTg8jKx+q7967MnWgJp44OmOdzq156eR w==; X-CSE-ConnectionGUID: hYqsXAL0R3KYcdhyRSKong== X-CSE-MsgGUID: uwaEr0luR2y1TiUcvdUO1g== X-IPAS-Result: =?us-ascii?q?A0AAAwCvi7Fq/47/Ja1aHgEBCxIMggULghg/gVVDSYRXj?= =?us-ascii?q?1KCIZFNjFEUgWoPAQEBD1EEAQGFBQKOBwImNgcOAQIEAwIDAQEBAQEBAQEBA?= =?us-ascii?q?QEBCgEBBQEBAQIBBwWBDhOGXJATAgEDGgkECwE0EhAlAiYCAkUBEAYBEoMCg?= =?us-ascii?q?nUDxXh6fzOBAYR+2UqBaAELFAGBCi6FcYJtAYFwhAg7g0Z7JxuBSUSBFYJYI?= =?us-ascii?q?m+EHwuDdIJqBIMcEoJmhzOKQQk/gQIcA1ksAVUTDQoLBwVhUjMDIAoLEhIYF?= =?us-ascii?q?QIUHRIPBBYyHXAMJxIsFy9YGwYFgR18FAQTghojGTZ6gQlegSspYAEQF4EHg?= =?us-ascii?q?ggCglSBfwIBSUMOB0VTCSdBChJHKSIIEgkBExowC4EnGA1IESw3Bg8ZBD0Bb?= =?us-ascii?q?gePXh+BZAYBUQ4CCQcBDQgXAg4cATUTCGAELRAJMxsaCSdFA5JgCoN0jkqBN?= =?us-ascii?q?Z9ahCiBZqAQM5dnkwaZCCOjSQYPCQFPhGmBbgE1gVkzGggbFTuCZ1MZD44uF?= =?us-ascii?q?oEUAQKSBgG8eycyPgEHAgcPAoFzkAAEDRd2YAEB?= IronPort-Data: A9a23:wEOWm6pR3uaSoq0GnhPNT0K7lxdeBmLKZBIvgKrLsJaIsI4StFCzt garIBnSO/beYjCne4x0bY+y90MBv5WHzINrSgI6/nxnRXkX9OPIVI+TRqvS04x+DSFioGZPt Zh2hgzodZhsJpPkjk7zdOWn9T8jhfngqoPUUIbsIjp2SRJvVBAvgBdin/9RqoNziLBVOSvV0 T/Ji5OZYgDNNwJcaDpOtfrS8ko35pwehRtB1rAATaET1LPhvyF94KI3fcmZM3b+S49IKe+2L 86r5K255G7Q4yA2AdqjlLvhGmVSKlIFFVHT4pb+c/HKbilq/kTe4I5iXBYvQRs/ZwGyojxE4 I4lWapc5useFvakdOw1C3G0GszlVEFM0OevzXOX6aR/w6BaGpfh660GMa04AWEX0uZGGHp+3 NhAEmsuXxuPh/OM+5G8TOY506zPLOGzVG8ekmtrwTecCbMtRorOBv2Xo9RZxzw3wMtJGJ4yZ eJANmEpN0uGOUASfA5LUvrSn8/w7pX7WyZRtFOOpak3y2PS1wd2lrPqNbI5f/TXH5gEwhbC9 zquE2LRHw4TGcbHzSe/qGP1uu7ylj33ZaVLG+jtnhJtqBjJroAJMzUaXEW2pNG1g1CzXtZYJ VBS/CcyxYA/+FGuR8vwQzW3p3mLuhNaUN1Ve8U67xuI0YLX7hyfC2xCSSROAPQlqcU/bT8nz FmEm5XuHzMHmLSTRWiQ6fSSoC++NDY9KXIEY2kPTWMt+9DprYcypgjCQtZqDOi+ididMTXxx S2a6SsznbMeieYV2Kihu1PKmTShot7OVAFdzgPaQm6o8Ctna4O/IY+l817W6bBHNonxc7Wal GIPl87b6KUFCouA0XTQBu4MB7quof2CNVUwnGJSInXozBz1k1bLQGyayGsWyJtBWircRQLUX Q== IronPort-HdrOrdr: A9a23:vP4FIKzJRAd5/GetdEteKrPw2r1zdoMgy1knxilNoNJuA6ilfq eV/cjztCWbtN9/YhwdcLy7UpVoBEmsl6KdgrN/AV7dZmnbUQKTRekI0WKh+UyCJ8SUzIJgPM lbAs1DIey1IFRkgcP9+QWiV/Qt0LC8gdmVrNab4ntzQQRtcq16qz14BAGSD1FsSGB9dPwE/e Knl616jgvlX2gLZcKmAXRAde3Cq9rX0K/CW3c9dmYaAM3kt0LT1FY8eCLopSsjbw== X-Talos-CUID: 9a23:5ODSWWNTIzjVC+5DWBh+7349PpgfKV7sx3vQMnWDKmFvR+jA X-Talos-MUID: 9a23:xtw04wVIsfTafF/q/CLzjRx4bMN12f6/UXwMrJ4Yt/SjbCMlbg== X-IronPort-Anti-Spam-Filtered: true X-IronPort-AV: E=Sophos;i="6.27,115,1787011200"; d="scan'208";a="525948290" Received: from rcdn-l-core-05.cisco.com ([173.37.255.142]) by rcdn-iport-4.cisco.com with ESMTP/TLS/TLS_AES_256_GCM_SHA384; 21 Sep 2026 20:00:23 +0000 Received: from [127.0.0.1] (unknown [10.25.128.208]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by rcdn-l-core-05.cisco.com (Postfix) with ESMTPS id F3CE818000236; Mon, 21 Sep 2026 20:00:22 +0000 (GMT) From: Satish Kharat Date: Mon, 21 Sep 2026 12:59:46 -0700 Subject: [PATCH net-next 3/6] enic: recover V2 VF mailbox when PF state is unknown Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260921-b4-enic-sriov-v2-vf-receive-control-v1-3-67a0a6e08d43@cisco.com> References: <20260921-b4-enic-sriov-v2-vf-receive-control-v1-0-67a0a6e08d43@cisco.com> In-Reply-To: <20260921-b4-enic-sriov-v2-vf-receive-control-v1-0-67a0a6e08d43@cisco.com> To: Andrew Lunn , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, Satish Kharat , sebaddel@cisco.com X-Mailer: b4 0.15.2 X-Developer-Signature: v=1; a=ed25519-sha256; t=1790020820; l=30660; i=satishkh@cisco.com; h=from:subject:message-id; bh=QkZwVj5suBrZgkE0RrWLgjO4TXrkNcLwYiH5Lqfhn0g=; b=JsUkwbCagJsJtVJXme/JN/zh1cQKleejtnfdnrdvJ3/7mXQwz7Iqa6Q1Z6mGswCMKkJ1BWO7+ J+ojYVFEzuCCRPe+opCYJ8jztXs5nYRZVr/XjsqgREGqEaZUxXPyBVQ X-Developer-Key: i=satishkh@cisco.com; a=ed25519; pk=4BxbLoCGvHeIMH/PbpiNVx8/2IcTjFvqcr0AD8K6WLY= X-Outbound-Client-TLS: ANONYMOUS;unknown [10.25.128.208];TLSv1.3;TLS_AES_256_GCM_SHA384;256 X-Outbound-SMTP-Client: 10.25.128.208, [10.25.128.208] X-Outbound-Node: rcdn-l-core-05.cisco.com A mailbox send can complete and still lose its reply. In that case the VF cannot know whether the PF changed its address or receive-filter state. A send-completion timeout is different because the admin-WQ descriptor may still belong to the device. Reconnect the V2 VF mailbox after the first case. After a hardware send timeout, stop using the channel and leave the timed-out DMA mapping for admin-channel teardown to reclaim safely. Stop V2 VF receive traffic when the VF can no longer trust that its state matches the PF. Rebuild and register the admin channel again at a safe open/reset boundary, then restore receive traffic after the station address and filters have been replayed. Defer notification acknowledgments so receive dispatch cannot block behind a VF request. Reinitialize non-dynamic vNICs after a successful soft reset before programming queue resources. This restores the initialization sequence used during probe and ensures that reset recovery rebuilds the vNIC before reopening the datapath. Also use this recovery after malformed or dropped admin receive traffic, and track whether the V2 datapath is open so a failed internal reset cannot stop it twice. Assisted-by: LLM Signed-off-by: Satish Kharat --- drivers/net/ethernet/cisco/enic/enic.h | 23 ++- drivers/net/ethernet/cisco/enic/enic_admin.c | 27 +++- drivers/net/ethernet/cisco/enic/enic_main.c | 208 ++++++++++++++++++++++-= ---- drivers/net/ethernet/cisco/enic/enic_mbox.c | 171 ++++++++++++++++++++-- drivers/net/ethernet/cisco/enic/enic_mbox.h | 2 + drivers/net/ethernet/cisco/enic/enic_rq.c | 11 +- 6 files changed, 392 insertions(+), 50 deletions(-) diff --git a/drivers/net/ethernet/cisco/enic/enic.h b/drivers/net/ethernet/= cisco/enic/enic.h index 3945fe28f199..782b8b1843ab 100644 --- a/drivers/net/ethernet/cisco/enic/enic.h +++ b/drivers/net/ethernet/cisco/enic/enic.h @@ -303,8 +303,21 @@ struct enic { * left the resources freed. */ bool admin_chan_up; - /* set on send timeout; cleared on channel re-open */ + /* Blocks sends while the channel is closed or awaiting recovery. */ bool mbox_send_disabled; + /* A send timeout leaves a descriptor hardware-owned. Do not reopen the + * channel during this device lifetime until reset/DMA fencing is proven. + */ + bool mbox_tx_poisoned; + /* After a lost or inconsistent reply, the VF cannot know whether the PF + * applied the request. Reconnect during the next open or reset. + */ + bool vf_mbox_reconnect_required; + u32 vf_mbox_fault_generation; + /* One slow-path-owned predicate keeps the RX hot path fail-closed while + * VF registration is lost or receive state may not match the PF. + */ + bool vf_rx_quarantined; struct vnic_wq admin_wq; struct vnic_rq admin_rq; struct vnic_cq admin_cq[2]; @@ -325,6 +338,10 @@ struct enic { spinlock_t vf_link_state_lock; enum enic_vf_link_state vf_link_state; bool vf_link_running; + /* Tracks a completely opened V2 VF datapath. An internal reset can stop + * it while netif_running() remains true, then fail before reopen. + */ + bool vf_datapath_open; =20 /* MBOX protocol state =E2=80=94 mbox_lock serializes admin WQ sends */ struct mutex mbox_lock; @@ -337,6 +354,10 @@ struct enic { struct completion mbox_comp; struct mutex vf_mbox_request_lock; /* serializes VF request lifetimes */ spinlock_t mbox_state_lock; /* protects expected reply state */ + spinlock_t vf_ack_lock; /* protects vf_ack_list */ + struct list_head vf_ack_list; + struct work_struct vf_ack_work; + unsigned int vf_ack_count; u64 mbox_expected_msg_num; u8 mbox_expected_reply; bool mbox_initialized; diff --git a/drivers/net/ethernet/cisco/enic/enic_admin.c b/drivers/net/eth= ernet/cisco/enic/enic_admin.c index 61c82b48044d..30c0a5c89a2d 100644 --- a/drivers/net/ethernet/cisco/enic/enic_admin.c +++ b/drivers/net/ethernet/cisco/enic/enic_admin.c @@ -132,14 +132,22 @@ unsigned int enic_admin_wq_cq_service(struct enic *en= ic) */ #define ENIC_ADMIN_MSG_MAX 256 =20 +static void enic_admin_rx_lost(struct enic *enic) +{ + if (enic_is_sriov_vf_v2(enic)) + enic_mbox_vf_require_reconnect(enic); +} + static void enic_admin_msg_enqueue(struct enic *enic, void *buf, unsigned int len) { struct enic_admin_msg *msg; =20 msg =3D kmalloc_flex(*msg, data, len); - if (!msg) + if (!msg) { + enic_admin_rx_lost(enic); return; + } =20 msg->len =3D len; memcpy(msg->data, buf, len); @@ -152,6 +160,7 @@ static void enic_admin_msg_enqueue(struct enic *enic, v= oid *buf, netdev_warn(enic->netdev, "admin msg backlog full (%u); dropping\n", ENIC_ADMIN_MSG_MAX); + enic_admin_rx_lost(enic); return; } list_add_tail(&msg->list, &enic->admin_msg_list); @@ -194,8 +203,10 @@ unsigned int enic_admin_rq_cq_service(struct enic *eni= c) rq_desc =3D desc; bwf =3D le16_to_cpu(rq_desc->bytes_written_flags); bytes_written =3D bwf & CQ_ENET_RQ_DESC_BYTES_WRITTEN_MASK; - if (bytes_written > buf->len) + if (bytes_written > buf->len) { + enic_admin_rx_lost(enic); goto next_desc; + } =20 dma_sync_single_for_cpu(&enic->pdev->dev, buf->dma_addr, buf->len, @@ -210,11 +221,13 @@ unsigned int enic_admin_rq_cq_service(struct enic *en= ic) if (bwf & CQ_ENET_RQ_DESC_FLAGS_TRUNCATED) { netdev_warn_once(enic->netdev, "admin RQ: truncated message dropped\n"); + enic_admin_rx_lost(enic); goto next_desc; } if (!(rq_desc->flags & CQ_ENET_RQ_DESC_FLAGS_FCS_OK)) { netdev_warn_once(enic->netdev, "admin RQ: bad FCS, dropping message\n"); + enic_admin_rx_lost(enic); goto next_desc; } =20 @@ -534,6 +547,11 @@ int enic_admin_channel_open(struct enic *enic) =20 if (!enic->has_admin_channel) return -ENODEV; + if (READ_ONCE(enic->mbox_tx_poisoned)) { + netdev_err(enic->netdev, + "Refusing to reopen admin channel after send timeout\n"); + return -EIO; + } =20 /* Keep MBOX sends disabled for the entire open sequence. It is * cleared only after every resource is allocated and enabled below, @@ -641,6 +659,11 @@ void enic_admin_channel_close(struct enic *enic) enic_admin_teardown_intr(enic); cancel_work_sync(&enic->link_notify_work); cancel_work_sync(&enic->admin_msg_work); + /* admin_msg_work is the sole VF ACK producer. Drain it before the ACK + * worker so an enqueue cannot race the final cancel and queue purge. + */ + if (enic_is_sriov_vf_v2(enic)) + enic_mbox_vf_ack_cancel(enic); enic_admin_msg_drain(enic); =20 enic_admin_qp_type_set(enic, QP_DISABLE); diff --git a/drivers/net/ethernet/cisco/enic/enic_main.c b/drivers/net/ethe= rnet/cisco/enic/enic_main.c index 9086e6dd558a..1c60e7a666ae 100644 --- a/drivers/net/ethernet/cisco/enic/enic_main.c +++ b/drivers/net/ethernet/cisco/enic/enic_main.c @@ -71,6 +71,8 @@ #define PCI_DEVICE_ID_CISCO_VIC_ENET_VF_V2 0x02b7 /* enet SRIOV V2 VF */ #define PCI_DEVICE_ID_CISCO_VIC_ENET_VF_USNIC 0x00cf /* enet USNIC VF */ =20 +static int __enic_stop(struct net_device *netdev, bool remove_vf_station); + /* Supported devices */ static const struct pci_device_id enic_id_table[] =3D { { PCI_VDEVICE(CISCO, PCI_DEVICE_ID_CISCO_VIC_ENET) }, @@ -1718,6 +1720,8 @@ static void enic_notify_timer_start(struct enic *enic) } } =20 +static int enic_admin_chan_reopen(struct enic *enic); + /* rtnl lock is held, process context */ static int enic_open(struct net_device *netdev) { @@ -1736,6 +1740,30 @@ static int enic_open(struct net_device *netdev) .flags =3D PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV, }; =20 + /* A reply timeout invalidates the current request generation. Rebuild + * and re-register the channel before allocating datapath resources so a + * later userspace down/up can recover a failed open or reset handshake. + * A send timeout is intentionally not recoverable here because its WQ + * descriptor may still be hardware-owned. + */ + if (enic_is_sriov_vf_v2(enic) && + READ_ONCE(enic->mbox_tx_poisoned)) + return -EIO; + if (enic_is_sriov_vf_v2(enic) && + (!enic->admin_chan_up || !READ_ONCE(enic->vf_registered) || + READ_ONCE(enic->vf_mbox_reconnect_required))) { + /* Re-registration makes the PF discard the old VF-requested + * filters. Clear the netdev-core synchronization state so the + * receive-mode callback replays the current address lists. + */ + enic_reset_addr_lists(enic); + if (enic->admin_chan_up) + enic_admin_channel_close(enic); + err =3D enic_admin_chan_reopen(enic); + if (err) + return err; + } + err =3D enic_request_intr(enic); if (err) { netdev_err(netdev, "Unable to request irq.\n"); @@ -1794,17 +1822,41 @@ static int enic_open(struct net_device *netdev) netdev_err(netdev, "Failed to enable device: %d\n", err); goto err_out_dev_enable; } + if (enic_is_sriov_vf_v2(enic)) { + /* Commit the replay only if no mailbox fault arrived while station + * and receive policy were being programmed. Keep the state lock + * through the carrier transition so a later fault necessarily wins + * and turns carrier back off. + */ + spin_lock_bh(&enic->mbox_state_lock); + if (!READ_ONCE(enic->vf_registered) || + READ_ONCE(enic->mbox_send_disabled) || + READ_ONCE(enic->mbox_tx_poisoned) || + READ_ONCE(enic->vf_mbox_reconnect_required)) { + err =3D -EIO; + } else { + WRITE_ONCE(enic->vf_rx_quarantined, false); + enic_mbox_vf_link_state_set_running(enic, true); + } + spin_unlock_bh(&enic->mbox_state_lock); + if (err) { + netdev_err(netdev, + "MBOX state changed during VF datapath open\n"); + goto err_out_dev_disable; + } + } =20 for (i =3D 0; i < enic->intr_count; i++) vnic_intr_unmask(&enic->intr[i]); - enic_notify_timer_start(enic); enic_rfs_timer_start(enic); if (enic_is_sriov_vf_v2(enic)) - enic_mbox_vf_link_state_set_running(enic, true); + enic->vf_datapath_open =3D true; =20 return 0; =20 +err_out_dev_disable: + enic_dev_disable(enic); err_out_dev_enable: for (i =3D 0; i < enic->rq_count; i++) napi_disable(&enic->napi[i]); @@ -1834,12 +1886,20 @@ static int enic_open(struct net_device *netdev) } =20 /* rtnl lock is held, process context */ -static int enic_stop(struct net_device *netdev) +static int __enic_stop(struct net_device *netdev, bool remove_vf_station) { struct enic *enic =3D netdev_priv(netdev); unsigned int i; int err; =20 + /* Internal reset leaves netif_running() set while the datapath is down. + * If re-registration or reopen then fails, a later administrative close + * must not disable NAPI a second time. + */ + if (enic_is_sriov_vf_v2(enic) && !enic->vf_datapath_open) + return 0; + (void)remove_vf_station; + for (i =3D 0; i < enic->intr_count; i++) { vnic_intr_mask(&enic->intr[i]); (void)vnic_intr_masked(&enic->intr[i]); /* flush write */ @@ -1893,10 +1953,17 @@ static int enic_stop(struct net_device *netdev) vnic_cq_clean(&enic->cq[i]); for (i =3D 0; i < enic->intr_count; i++) vnic_intr_clean(&enic->intr[i]); + if (enic_is_sriov_vf_v2(enic)) + enic->vf_datapath_open =3D false; =20 return 0; } =20 +static int enic_stop(struct net_device *netdev) +{ + return __enic_stop(netdev, true); +} + static int _enic_change_mtu(struct net_device *netdev, int new_mtu) { bool running =3D netif_running(netdev); @@ -2196,14 +2263,15 @@ static bool enic_has_admin_chan(struct enic *enic) (enic_sriov_enabled(enic) && enic->vf_type =3D=3D ENIC_VF_TYPE_V2); } =20 -/* Re-establish the admin/MBOX channel after a reset has re-created the da= ta - * path. Mirrors the relevant part of the probe / SR-IOV-enable sequence: +/* Re-establish the admin/MBOX channel after a reset has re-created the vN= IC + * resources. Mirrors the relevant part of the probe / SR-IOV-enable sequ= ence: * reinitialise MBOX and reopen the channel, then for a VF re-run the PF * handshake (the reset wiped the VF's admin QP, so the VF must register * again), or for a PF re-push the current link state to registered VFs. */ -static void enic_admin_chan_reopen(struct enic *enic) +static int enic_admin_chan_reopen(struct enic *enic) { + u32 recovery_generation =3D 0; int err; =20 /* Install the MBOX receive handler and clear pending reply state before @@ -2222,12 +2290,17 @@ static void enic_admin_chan_reopen(struct enic *eni= c) */ if (enic_is_sriov_vf_v2(enic)) WRITE_ONCE(enic->vf_registered, false); + if (enic_is_sriov_vf_v2(enic)) { + spin_lock_bh(&enic->mbox_state_lock); + recovery_generation =3D enic->vf_mbox_fault_generation; + spin_unlock_bh(&enic->mbox_state_lock); + } =20 err =3D enic_admin_channel_open(enic); if (err) { netdev_err(enic->netdev, "admin channel reopen after reset failed: %d\n", err); - return; + return err; } =20 if (enic_is_sriov_vf_v2(enic)) { @@ -2237,7 +2310,7 @@ static void enic_admin_chan_reopen(struct enic *enic) "MBOX capability check after reset failed: %d\n", err); enic_admin_channel_close(enic); - return; + return err; } err =3D enic_mbox_vf_register(enic); if (err) { @@ -2245,6 +2318,26 @@ static void enic_admin_chan_reopen(struct enic *enic) "MBOX VF re-registration after reset failed: %d\n", err); enic_admin_channel_close(enic); + return err; + } + enic_reset_addr_lists(enic); + /* Capability negotiation and registration establish a new protocol + * generation. RX remains quarantined until enic_open() replays the + * station and receive policy. + */ + spin_lock_bh(&enic->mbox_state_lock); + if (enic->vf_mbox_fault_generation !=3D recovery_generation || + READ_ONCE(enic->mbox_tx_poisoned)) { + err =3D -EAGAIN; + } else { + WRITE_ONCE(enic->vf_mbox_reconnect_required, false); + } + spin_unlock_bh(&enic->mbox_state_lock); + if (err) { + netdev_warn(enic->netdev, + "MBOX state changed during VF re-registration\n"); + enic_admin_channel_close(enic); + return err; } } else { /* The link came back up during enic_open() above while MBOX @@ -2253,79 +2346,119 @@ static void enic_admin_chan_reopen(struct enic *en= ic) */ schedule_work(&enic->link_notify_work); } + + return 0; } =20 static void enic_reset(struct work_struct *work) { struct enic *enic =3D container_of(work, struct enic, reset); + int err; =20 if (!netif_running(enic->netdev)) return; =20 rtnl_lock(); + /* V2 protocol recovery can be queued immediately before ndo_stop() + * acquires RTNL. Recheck under RTNL so that new recovery path cannot + * reopen a device userspace just closed. Preserve the existing reset + * behavior for every other ENIC device. + */ + if (enic_is_sriov_vf_v2(enic) && !netif_running(enic->netdev)) + goto unlock; =20 /* Stop any activity from infiniband */ enic_set_api_busy(enic, true); =20 - /* Fully tear down the V2 admin/MBOX channel before the soft reset. - * The reset wipes all hardware queues including the admin WQ/RQ; - * closing first tells firmware to stop the admin QP (so it no longer - * DMAs from the about-to-be-reset rings) and frees the admin resources - * so they are cleanly re-allocated afterwards. + /* Stop the datapath and existing admin/MBOX channel before the soft + * reset. Do not send DEL_MAC from this path: a timeout would poison the + * channel while reset and fresh registration already discard the old + * VF-requested protocol state before the station address is replayed. + * Reopen allocates fresh admin resources after reset recreates the vNIC. */ + __enic_stop(enic->netdev, false); if (enic_has_admin_chan(enic)) enic_admin_channel_close(enic); =20 - enic_stop(enic->netdev); if (enic_is_sriov_vf_v2(enic)) enic_mbox_vf_link_state_reset(enic); + err =3D enic_dev_soft_reset(enic); + if (err) + goto reset_out; + + if (!enic_is_dynamic(enic)) { + err =3D vnic_dev_init(enic->vdev, 0); + if (err) { + netdev_err(enic->netdev, + "vNIC init after soft reset failed: %d\n", + err); + goto reset_out; + } + } =20 - enic_dev_soft_reset(enic); enic_reset_addr_lists(enic); enic_init_vnic_resources(enic); enic_set_rss_nic_cfg(enic); enic_dev_set_ig_vlan_rewrite_mode(enic); enic_ext_cq(enic); =20 - enic_open(enic->netdev); + /* A V2 VF needs PF registration before enic_open() can install its + * station address. A V2 PF reopens afterwards and replays carrier. + */ + if (enic_is_sriov_vf_v2(enic)) { + err =3D enic_admin_chan_reopen(enic); + if (err) + goto reset_out; + } =20 - /* Re-establish the admin/MBOX channel after the data path is back up. - * It was fully torn down by enic_admin_channel_close() above; - * enic_admin_chan_reopen() reopens it and, for a PF re-pushes link - * state, or for a VF re-runs the probe-time PF handshake. + err =3D enic_open(enic->netdev); + if (err) + netdev_err(enic->netdev, + "Failed to reopen datapath after reset: %d\n", err); + + /* A PF reopens its admin channel after the datapath and re-pushes link + * state. The VF handshake, which open depends on, completed above. */ - if (enic_has_admin_chan(enic)) + if (enic_has_admin_chan(enic) && !enic_is_sriov_vf_v2(enic)) enic_admin_chan_reopen(enic); =20 +reset_out: /* Allow infiniband to fiddle with the device again */ enic_set_api_busy(enic, false); =20 call_netdevice_notifiers(NETDEV_REBOOT, enic->netdev); =20 +unlock: rtnl_unlock(); } =20 static void enic_tx_hang_reset(struct work_struct *work) { struct enic *enic =3D container_of(work, struct enic, tx_hang_reset); + int err; =20 rtnl_lock(); + /* The V2 changes below add admin-channel recovery to this worker. Do not + * let that new path reopen a VF after userspace completed ndo_stop(); + * leave the existing behavior for other ENIC devices unchanged. + */ + if (enic_is_sriov_vf_v2(enic) && !netif_running(enic->netdev)) + goto unlock; =20 /* Stop any activity from infiniband */ enic_set_api_busy(enic, true); =20 - /* Fully tear down the V2 admin/MBOX channel before the hang reset, for - * the same reason as the soft reset path: stop the admin QP and free - * the admin resources before the hardware queues are wiped. + /* Preserve the firmware hang-notification contract by reporting the hung + * queue before stopping and cleaning it. As in the soft-reset path, skip + * DEL_MAC because reset and fresh registration are the cleanup boundary. */ + enic_dev_hang_notify(enic); + __enic_stop(enic->netdev, false); if (enic_has_admin_chan(enic)) enic_admin_channel_close(enic); =20 - enic_dev_hang_notify(enic); - enic_stop(enic->netdev); if (enic_is_sriov_vf_v2(enic)) enic_mbox_vf_link_state_reset(enic); - enic_dev_hang_reset(enic); enic_reset_addr_lists(enic); enic_init_vnic_resources(enic); @@ -2333,21 +2466,30 @@ static void enic_tx_hang_reset(struct work_struct *= work) enic_dev_set_ig_vlan_rewrite_mode(enic); enic_ext_cq(enic); =20 - enic_open(enic->netdev); + if (enic_is_sriov_vf_v2(enic)) { + err =3D enic_admin_chan_reopen(enic); + if (err) + goto hang_reset_out; + } =20 - /* Re-establish the admin/MBOX channel after the data path is back up. - * It was fully torn down by enic_admin_channel_close() above; - * enic_admin_chan_reopen() reopens it and, for a PF re-pushes link - * state, or for a VF re-runs the probe-time PF handshake. + err =3D enic_open(enic->netdev); + if (err) + netdev_err(enic->netdev, + "Failed to reopen datapath after hang reset: %d\n", err); + + /* A PF reopens its admin channel after the datapath and re-pushes link + * state. The VF handshake, which open depends on, completed above. */ - if (enic_has_admin_chan(enic)) + if (enic_has_admin_chan(enic) && !enic_is_sriov_vf_v2(enic)) enic_admin_chan_reopen(enic); =20 +hang_reset_out: /* Allow infiniband to fiddle with the device again */ enic_set_api_busy(enic, false); =20 call_netdevice_notifiers(NETDEV_REBOOT, enic->netdev); =20 +unlock: rtnl_unlock(); } =20 diff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.c b/drivers/net/ethe= rnet/cisco/enic/enic_mbox.c index b8a18d9682b2..2d77d41577a3 100644 --- a/drivers/net/ethernet/cisco/enic/enic_mbox.c +++ b/drivers/net/ethernet/cisco/enic/enic_mbox.c @@ -149,7 +149,20 @@ static int enic_mbox_send_msg_id(struct enic *enic, u8= msg_type, * or free the buffer: the device may still DMA from dma_addr. * Mark the channel unusable so no further sends are attempted. */ + spin_lock_bh(&enic->mbox_state_lock); WRITE_ONCE(enic->mbox_send_disabled, true); + WRITE_ONCE(enic->mbox_tx_poisoned, true); + if (enic_is_sriov_vf_v2(enic)) { + /* The request may have changed PF receive policy even though + * local descriptor ownership is still uncertain. Fail the VF + * closed and do not turn this into an ordinary protocol + * reconnect; the admin-channel lifecycle owns final reclamation. + */ + WRITE_ONCE(enic->vf_rx_quarantined, true); + } + spin_unlock_bh(&enic->mbox_state_lock); + if (enic_is_sriov_vf_v2(enic)) + enic_mbox_vf_link_state_set_running(enic, false); } =20 netdev_dbg(enic->netdev, @@ -184,6 +197,105 @@ static int enic_mbox_send_reply(struct enic *enic, u8= msg_type, payload_len, msg_num, true, 0); } =20 +struct enic_mbox_vf_ack { + struct list_head list; + u64 msg_num; + u16 ret_major; + u8 msg_type; +}; + +static void enic_mbox_vf_ack_work(struct work_struct *work) +{ + struct enic *enic =3D container_of(work, struct enic, vf_ack_work); + struct enic_mbox_vf_ack *pending; + + for (;;) { + struct enic_mbox_generic_reply ack =3D {}; + u8 msg_type; + int err; + + spin_lock_bh(&enic->vf_ack_lock); + if (list_empty(&enic->vf_ack_list)) { + spin_unlock_bh(&enic->vf_ack_lock); + break; + } + pending =3D list_first_entry(&enic->vf_ack_list, + struct enic_mbox_vf_ack, list); + list_del(&pending->list); + enic->vf_ack_count--; + spin_unlock_bh(&enic->vf_ack_lock); + + if (READ_ONCE(enic->mbox_send_disabled)) { + kfree(pending); + continue; + } + + msg_type =3D pending->msg_type; + ack.ret_major =3D cpu_to_le16(pending->ret_major); + err =3D enic_mbox_send_reply(enic, msg_type, ENIC_MBOX_DST_PF, + &ack, sizeof(ack), pending->msg_num); + kfree(pending); + if (err && net_ratelimit()) + netdev_warn(enic->netdev, + "MBOX: failed to send ACK type %u: %d\n", + msg_type, err); + } +} + +static void enic_mbox_vf_queue_ack(struct enic *enic, u8 msg_type, + u64 msg_num, u16 ret_major) +{ + struct enic_mbox_vf_ack *pending; + + if (READ_ONCE(enic->mbox_send_disabled)) + return; + pending =3D kmalloc_obj(*pending, GFP_ATOMIC); + if (!pending) { + if (net_ratelimit()) + netdev_warn(enic->netdev, + "MBOX: dropping ACK type %u: no memory\n", + msg_type); + return; + } + pending->msg_num =3D msg_num; + pending->ret_major =3D ret_major; + pending->msg_type =3D msg_type; + + spin_lock_bh(&enic->vf_ack_lock); + if (READ_ONCE(enic->mbox_send_disabled) || + enic->vf_ack_count >=3D ENIC_ADMIN_DESC_COUNT) { + spin_unlock_bh(&enic->vf_ack_lock); + kfree(pending); + if (net_ratelimit()) + netdev_warn(enic->netdev, + "MBOX: dropping ACK type %u: queue unavailable\n", + msg_type); + return; + } + list_add_tail(&pending->list, &enic->vf_ack_list); + enic->vf_ack_count++; + spin_unlock_bh(&enic->vf_ack_lock); + schedule_work(&enic->vf_ack_work); +} + +void enic_mbox_vf_ack_cancel(struct enic *enic) +{ + struct enic_mbox_vf_ack *pending, *tmp; + LIST_HEAD(discard); + + if (!enic->mbox_initialized) + return; + cancel_work_sync(&enic->vf_ack_work); + spin_lock_bh(&enic->vf_ack_lock); + list_splice_init(&enic->vf_ack_list, &discard); + enic->vf_ack_count =3D 0; + spin_unlock_bh(&enic->vf_ack_lock); + list_for_each_entry_safe(pending, tmp, &discard, list) { + list_del(&pending->list); + kfree(pending); + } +} + static int enic_mbox_vf_send_request(struct enic *enic, u8 request_type, u8 expected_reply, void *payload, u16 payload_len) @@ -193,6 +305,26 @@ static int enic_mbox_vf_send_request(struct enic *enic= , u8 request_type, expected_reply); } =20 +static void enic_mbox_vf_mark_reconnect_locked(struct enic *enic, + bool registration_lost) +{ + lockdep_assert_held(&enic->mbox_state_lock); + + if (registration_lost) + WRITE_ONCE(enic->vf_registered, false); + enic->vf_mbox_fault_generation++; + WRITE_ONCE(enic->vf_mbox_reconnect_required, true); + WRITE_ONCE(enic->mbox_send_disabled, true); + WRITE_ONCE(enic->vf_rx_quarantined, true); +} + +static void enic_mbox_vf_kick_recovery(struct enic *enic) +{ + enic_mbox_vf_link_state_set_running(enic, false); + if (netif_running(enic->netdev)) + schedule_work(&enic->reset); +} + static int enic_mbox_wait_reply(struct enic *enic, unsigned long timeout_m= s) { unsigned long left; @@ -203,9 +335,9 @@ static int enic_mbox_wait_reply(struct enic *enic, unsi= gned long timeout_ms) if (left) return 0; =20 - /* Invalidate a request that the handler has not already accepted. A - * delayed reply cannot match a later request because message numbers are - * monotonic across channel reopen. + /* Invalidate a request that the handler has not already accepted. Whether + * losing the reply invalidates the current protocol generation is an + * operation-specific decision made by the caller. */ spin_lock_bh(&enic->mbox_state_lock); if (enic->mbox_expected_reply) { @@ -468,9 +600,8 @@ static void enic_mbox_vf_handle_link_state(struct enic = *enic, void *payload, u64 msg_num) { struct enic_mbox_pf_link_state_notif_msg *notif =3D payload; - struct enic_mbox_pf_link_state_ack_msg ack =3D {}; u32 link_state =3D le32_to_cpu(notif->link_state); - int err; + u16 ret_major =3D 0; =20 spin_lock_bh(&enic->vf_link_state_lock); switch (link_state) { @@ -491,16 +622,16 @@ static void enic_mbox_vf_handle_link_state(struct eni= c *enic, void *payload, default: netdev_warn(enic->netdev, "MBOX: unknown link state %u\n", link_state); - ack.ack.ret_major =3D cpu_to_le16(ENIC_MBOX_ERR_GENERIC); + ret_major =3D ENIC_MBOX_ERR_GENERIC; break; } spin_unlock_bh(&enic->vf_link_state_lock); =20 - err =3D enic_mbox_send_reply(enic, ENIC_MBOX_PF_LINK_STATE_ACK, - ENIC_MBOX_DST_PF, &ack, sizeof(ack), msg_num); - if (err && net_ratelimit()) - netdev_warn(enic->netdev, - "MBOX: failed to send link state ACK: %d\n", err); + /* Notification dispatch must not wait behind a synchronous request send: + * its matching reply may be queued behind this notification. + */ + enic_mbox_vf_queue_ack(enic, ENIC_MBOX_PF_LINK_STATE_ACK, msg_num, + ret_major); } =20 void enic_mbox_vf_link_state_reset(struct enic *enic) @@ -525,6 +656,17 @@ void enic_mbox_vf_link_state_set_running(struct enic *= enic, bool running) spin_unlock_bh(&enic->vf_link_state_lock); } =20 +void enic_mbox_vf_require_reconnect(struct enic *enic) +{ + /* A fresh REGISTER transaction lets the PF discard any VF-requested + * configuration whose final state became uncertain. + */ + spin_lock_bh(&enic->mbox_state_lock); + enic_mbox_vf_mark_reconnect_locked(enic, false); + spin_unlock_bh(&enic->mbox_state_lock); + enic_mbox_vf_kick_recovery(enic); +} + static bool enic_mbox_vf_payload_ok(struct enic *enic, u8 msg_type, u16 payload_len, size_t min_len) { @@ -600,6 +742,8 @@ static void enic_mbox_recv_handler(struct enic *enic, v= oid *buf, netdev_warn(enic->netdev, "MBOX: truncated message (len %u < %zu)\n", len, sizeof(*hdr)); + if (!enic->vf_state) + enic_mbox_vf_require_reconnect(enic); return; } =20 @@ -756,7 +900,12 @@ void enic_mbox_init(struct enic *enic) mutex_init(&enic->vf_mbox_request_lock); init_completion(&enic->mbox_comp); spin_lock_init(&enic->mbox_state_lock); + spin_lock_init(&enic->vf_ack_lock); + INIT_LIST_HEAD(&enic->vf_ack_list); + INIT_WORK(&enic->vf_ack_work, enic_mbox_vf_ack_work); enic->mbox_msg_num =3D 0; + if (enic_is_sriov_vf_v2(enic)) + WRITE_ONCE(enic->vf_rx_quarantined, true); enic->mbox_initialized =3D true; } else { reinit_completion(&enic->mbox_comp); diff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.h b/drivers/net/ethe= rnet/cisco/enic/enic_mbox.h index 60409bad2f28..37bc41a900f4 100644 --- a/drivers/net/ethernet/cisco/enic/enic_mbox.h +++ b/drivers/net/ethernet/cisco/enic/enic_mbox.h @@ -90,6 +90,8 @@ int enic_mbox_send_msg(struct enic *enic, u8 msg_type, u1= 6 dst_vnic_id, int enic_mbox_send_link_state(struct enic *enic, u16 vf_id, u32 link_state= ); void enic_mbox_vf_link_state_reset(struct enic *enic); void enic_mbox_vf_link_state_set_running(struct enic *enic, bool running); +void enic_mbox_vf_ack_cancel(struct enic *enic); +void enic_mbox_vf_require_reconnect(struct enic *enic); int enic_mbox_vf_capability_check(struct enic *enic); int enic_mbox_vf_register(struct enic *enic); int enic_mbox_vf_unregister(struct enic *enic); diff --git a/drivers/net/ethernet/cisco/enic/enic_rq.c b/drivers/net/ethern= et/cisco/enic/enic_rq.c index ccbf5c9a21d0..80fe7e819c37 100644 --- a/drivers/net/ethernet/cisco/enic/enic_rq.c +++ b/drivers/net/ethernet/cisco/enic/enic_rq.c @@ -330,8 +330,6 @@ static void enic_rq_indicate_buf(struct enic *enic, str= uct vnic_rq *rq, u16 bytes_written, vlan_tci, checksum; u32 rss_hash; =20 - rqstats->packets++; - cq_enet_rq_desc_dec((struct cq_enet_rq_desc *)cq_desc, &ingress_port, &fcoe, &eop, &sop, &rss_type, &csum_not_calc, &rss_hash, &bytes_written, &packet_error, @@ -340,8 +338,15 @@ static void enic_rq_indicate_buf(struct enic *enic, st= ruct vnic_rq *rq, &tcp_udp_csum_ok, &udp, &tcp, &ipv4_csum_ok, &ipv6, &ipv4, &ipv4_fragment, &fcs_ok); =20 - if (enic_rq_pkt_error(rq, packet_error, fcs_ok, bytes_written)) + if (enic_rq_pkt_error(rq, packet_error, fcs_ok, bytes_written)) { + rqstats->packets++; + return; + } + if (unlikely(READ_ONCE(enic->vf_rx_quarantined))) { + dev_core_stats_rx_dropped_inc(enic->netdev); return; + } + rqstats->packets++; =20 if (eop && bytes_written > 0) { /* Good receive --=20 2.43.0 From nobody Thu Sep 24 17:56:23 2026 Received: from rcdn-iport-3.cisco.com (rcdn-iport-3.cisco.com [173.37.86.74]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CEFAD446853; Mon, 21 Sep 2026 20:01:34 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=173.37.86.74 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790020899; cv=none; b=U6RUtNngZfpXgnM/oPwRZ1BIj4tCExiXg7m09kYbn/+pVHb7i/AUUNgK3yyve84b3eBLJK1Qm2jI0DUyCJ8dkBk5OVprj5LWABzifv7lW1yuAhi8xFqUaWrGJvmhBBHpIQqoAi95ajvv9fDkTHSiiLk6Rc/4RO91btGrD5/yezQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790020899; c=relaxed/simple; bh=yl4XoV0665vlNHq6lpOJ+AQRgE/loyqwM/3vx3YkqHQ=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=XGwIs446wOHbD9GpPTOJ0Tb+h4gne9RjTNA5akXopjTYQO87TkV41v2Dbl+moOLH7SqbIfJF2sy6pILHhytGOVLGAuTKSiPjSl3Y3IbtVKzbr2FA4bWGVpNv9KjQjuenYX8jlgFr+mnIGUagL1e9wnz0wAlpgGW8XJb0LoE70ms= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=cisco.com; spf=pass smtp.mailfrom=cisco.com; dkim=pass (2048-bit key) header.d=cisco.com header.i=@cisco.com header.b=ds8przJL; arc=none smtp.client-ip=173.37.86.74 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=cisco.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=cisco.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=cisco.com header.i=@cisco.com header.b="ds8przJL" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cisco.com; i=@cisco.com; l=26995; q=dns/txt; s=iport01; t=1790020895; x=1791230495; h=from:date:subject:mime-version:content-transfer-encoding: message-id:references:in-reply-to:to:cc; bh=p500mn7UZLNyZQo01CgXTZQuRAXhdjLcX/kQ3vZ5y2Y=; b=ds8przJLaUAMfhtWFD9uQ5k0BESbe129VzuGvtSHYCwpQVNnZvofKcld 3EQ/d+r+C2jjtlXjvbTanKnEyT0wrV3qNMXYJyFLSbnf/fTTfRXA0I8cH tHu7JaJk2ifb4Gs5quRcsO8kodtp1y2SHfzykjMhNTA9tvRAx5zIoDavU JJFVbmkGZdTaRMHa81uYpzOm6jrklOUX0ihJDfuktwbj3FcBLvDm70IQc RoHKCGcStEZEcExXNyh64Vq+6fB29ZKDC25M83SXaMq3eXZtgqtcjvHMR gZjVXZbNgepA9PV9bsvjv+xMtFDmqh/4IIBwmFNMaZELTzsHguSgSjpI5 g==; X-CSE-ConnectionGUID: QS23j9CmQ9q8SnRXo7V4dA== X-CSE-MsgGUID: PVNqrqcrS1ygMhgtyJRmaA== X-IPAS-Result: =?us-ascii?q?A0BHAgAnjLFq/47/Ja1aHgEBCxIMggULgleBVUNJhFePU?= =?us-ascii?q?oIhkU2MURSBag8BAQEPUQQBAYISgnMCjgcCJjQJDgECBAMCAwEBAQEBAQEBA?= =?us-ascii?q?QEBAQoBAQUBAQECAQcFgQ4ThlyQEwIBAxoJBFIQJQImAgJGEAYBEoMCgnUDx?= =?us-ascii?q?Wx6fzOBAYR+2UqBaAELFAGBCi6FcYJtAYFwhAg7hEEnG4FJRIEVg2mEHwqDd?= =?us-ascii?q?YJqBIMcEoMwkSoJP4ECHANZLAFVEw0KCwcFYVIzAyAKCxISGBUCFB0SDwQWM?= =?us-ascii?q?h1wDCcSLBcvWBsGBYEdfBQEE4IaIxk2eoEJXoErKWABEBeBB4IIAoJUgX8CA?= =?us-ascii?q?UlDDgdFUwknQQoSRykiCBIJARMaMAuBJxgNSBEsNwYPGQQ9AW4Hj14fgWpgC?= =?us-ascii?q?wYBBw9uC0I9AyoBZhoBAUqSaisBOIM8jV9rgTWfWoQogWaILJdkM5dnkwaZC?= =?us-ascii?q?COjSQYYUIRpgWg8gVkzGggbFYMiUxkPji4Wkx0BvHsnMj4BBwIHDwKBc5ACD?= =?us-ascii?q?xcHgU8BAQ?= IronPort-Data: A9a23:2Bwr4a2chx3mqmNOxvbD5QFwkn2cJEfYwER7XKvMYLTBsI5bp2ZRy 2pNXGmGM/vcNmPxLdx/bIq2px4P7MCEytdlHQU63Hw8FHgiRegpqji6wuYcGwvIc6UvmWo+t 512huHodZ5yEzmE4Ej9atANlFEkvYmQXL3wFeXYDS54QA5gWU8JhAlq8wIDqtYAbeORXUXX5 rsen+WFYAX7g28uazpOg06+gEoHUMra6WtwUmMWPZinjHeG/1EJAZQWI72GLneQauF8Au6gS u/f+6qy92Xf8g1FIovNfmHTKxBirhb6ZGBiu1IOM0SQqkEqSh8ajs7XAMEhhXJ/0F1lqTzeJ OJl7vRcQS9xVkHFdX90vxNwS0mSNoUekFPLzOTWXcG7lyX7n3XQL/pGNXpsZo9J0eNOUHgez voiGDdSUEyPrrfjqF67YrEEasULNsLnOsYb/3pn1zycVatgSpHYSKKM7thdtNsyrpkRRrCFO IxDNGcpNUiaC/FMEg9/5JYWh+60h2L/cD1wo1OOrq1x6G/WpOB0+OW3boGEJYfWFa25mG6gm ETf5X3yGCpDJdO6yTWZ41CWg+bmyHaTtIU6UefQGuRRqFGa2GEWIBEfS1a+pf63lgi4Vs43A 0EY5y8nv6Uu3EOsSdb5Uluzp3vslh4bQd9LO+438geAzuzT+QnxLmsZRDdpa9E8ssIyAzsw2 TehltXlHzFw87uYVX6Q6J+QsDW0fyMVRUcabCUJSwot+dTvoIgvyBnIS75LFK+zk82wGjzqx T2OhDYxiq9VjsMR0ai/u1fdjFqEoJXVQgMrzhvYU3jj7Q5jYoOhIYuy5jDmAe1oNo2VSByF+ XMDgcXbtL1IBpCWnyvLS+IIdF2028u43PTnqQYHN/EcG/6FohZPoag4DOlCGXpU IronPort-HdrOrdr: A9a23:RZNWsKhi+MbgNrbiWXg8NP7wp3BQXiwji2hC6mlwRA09TyVXrb HLoB19737JYVcqNk3I9ursBEDtexnhHNtOkOss1NSZMzUOxlHJEGgK1+KLqAEIWReOldK1vp 0NT0ERMqyUMXFKyev3/wW8Fc8t252k/LDAv5am815dCSxndK1k6R50EUKgEkNwTBRbHpZRLu v72iM+nUvGRV0nKuKmG3ICQ+/P4/fPlJ7gfFo6IiRP0nj3sdttg4SKaSRxGXwlIkhy/Ys= X-Talos-CUID: =?us-ascii?q?9a23=3ASGtYj2r/saqhs6zMc7BSpo/mUfkeKCbfnVzVGh6?= =?us-ascii?q?hWV1vco2SFmHM9Ioxxg=3D=3D?= X-Talos-MUID: =?us-ascii?q?9a23=3A/59hwgxasdmWyTCE0euGS6SceOKaqJyQLlkXiJ4?= =?us-ascii?q?cgJCVOglwNCuMrg6yS6Zyfw=3D=3D?= X-IronPort-Anti-Spam-Filtered: true X-IronPort-AV: E=Sophos;i="6.27,115,1787011200"; d="scan'208";a="526397348" Received: from rcdn-l-core-05.cisco.com ([173.37.255.142]) by rcdn-iport-3.cisco.com with ESMTP/TLS/TLS_AES_256_GCM_SHA384; 21 Sep 2026 20:00:24 +0000 Received: from [127.0.0.1] (unknown [10.25.128.208]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by rcdn-l-core-05.cisco.com (Postfix) with ESMTPS id CE93D1800035F; Mon, 21 Sep 2026 20:00:23 +0000 (GMT) From: Satish Kharat Date: Mon, 21 Sep 2026 12:59:47 -0700 Subject: [PATCH net-next 4/6] enic: validate V2 VF configuration replies Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260921-b4-enic-sriov-v2-vf-receive-control-v1-4-67a0a6e08d43@cisco.com> References: <20260921-b4-enic-sriov-v2-vf-receive-control-v1-0-67a0a6e08d43@cisco.com> In-Reply-To: <20260921-b4-enic-sriov-v2-vf-receive-control-v1-0-67a0a6e08d43@cisco.com> To: Andrew Lunn , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, Satish Kharat , sebaddel@cisco.com X-Mailer: b4 0.15.2 X-Developer-Signature: v=1; a=ed25519-sha256; t=1790020820; l=27980; i=satishkh@cisco.com; h=from:subject:message-id; bh=yl4XoV0665vlNHq6lpOJ+AQRgE/loyqwM/3vx3YkqHQ=; b=ukfsAXZER+YiAOGLXCOCfETcg4jVQOgYyT5yJcnMPS2wdaLJwdceuAqf7hUW178jP0Jr1cnfC 82hIXX1Y5m3DU6yDvA/ZQQrKlZIqk++uQBBOypWFBhiwmGydjK0UEcz X-Developer-Key: i=satishkh@cisco.com; a=ed25519; pk=4BxbLoCGvHeIMH/PbpiNVx8/2IcTjFvqcr0AD8K6WLY= X-Outbound-Client-TLS: ANONYMOUS;unknown [10.25.128.208];TLSv1.3;TLS_AES_256_GCM_SHA384;256 X-Outbound-SMTP-Client: 10.25.128.208, [10.25.128.208] X-Outbound-Node: rcdn-l-core-05.cisco.com Add the established V2 mailbox operations for MAC filters, administrative-MAC notifications, and packet-filter settings. Keep request buffers alive until the VF request completes so detailed replies can be checked entry by entry. Validate message framing, echoed operations, result counts, operation-specific idempotent results, and applied packet-filter flags before publishing a reply. If a reply is malformed or contradictory, or the PF reports that the VF is no longer registered, the VF can no longer trust that its state matches the PF. Require a new VF registration before accepting traffic again. The protocol's ret_minor field counts non-SKIPPED per-entry result codes, including the idempotent DUPLICATE and NOT_FOUND outcomes. SKIPPED remains an operation-specific policy result but is not part of that aggregate count. Return stable policy errors to callers while retaining retry semantics for operations that made no state change. This patch adds only the VF side of these operations. The in-tree V2 PF enable path remains dormant because enic_driver does not yet register .sriov_configure. A future PF activation series must implement PF-side handling for the MAC-address and packet-filter mailbox requests, including the VF policy checks, before wiring that callback. Assisted-by: LLM Signed-off-by: Satish Kharat --- drivers/net/ethernet/cisco/enic/enic.h | 9 + drivers/net/ethernet/cisco/enic/enic_mbox.c | 492 ++++++++++++++++++++++++= +++- drivers/net/ethernet/cisco/enic/enic_mbox.h | 82 +++++ 3 files changed, 565 insertions(+), 18 deletions(-) diff --git a/drivers/net/ethernet/cisco/enic/enic.h b/drivers/net/ethernet/= cisco/enic/enic.h index 782b8b1843ab..45992b355501 100644 --- a/drivers/net/ethernet/cisco/enic/enic.h +++ b/drivers/net/ethernet/cisco/enic/enic.h @@ -239,6 +239,8 @@ enum enic_vf_type { }; =20 /* Per-instance private data structure */ +struct enic_mac_addr; + struct enic { struct net_device *netdev; struct pci_dev *pdev; @@ -360,6 +362,13 @@ struct enic { unsigned int vf_ack_count; u64 mbox_expected_msg_num; u8 mbox_expected_reply; + int mbox_reply_status; + u16 mbox_reply_filter_flags; + /* The request mutex keeps this caller-owned reply array alive until the + * matching reply handler has copied all per-address result flags. + */ + struct enic_mac_addr *mbox_reply_mac_addrs; + u16 mbox_reply_mac_count; bool mbox_initialized; =20 /* PF: per-VF MBOX state, allocated when SRIOV V2 is enabled */ diff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.c b/drivers/net/ethe= rnet/cisco/enic/enic_mbox.c index 2d77d41577a3..3f73072170b9 100644 --- a/drivers/net/ethernet/cisco/enic/enic_mbox.c +++ b/drivers/net/ethernet/cisco/enic/enic_mbox.c @@ -6,6 +6,7 @@ #include #include #include +#include =20 #include "vnic_dev.h" #include "vnic_wq.h" @@ -376,6 +377,82 @@ static void enic_mbox_vf_request_finish(struct enic *e= nic) mutex_unlock(&enic->vf_mbox_request_lock); } =20 +/* Return with mbox_state_lock held when this handler owns the reply. */ +static bool enic_mbox_vf_reply_claim(struct enic *enic, u8 reply_type, + u64 msg_num, u8 *expected) +{ + spin_lock_bh(&enic->mbox_state_lock); + *expected =3D enic->mbox_expected_reply; + if (*expected =3D=3D reply_type && + enic->mbox_expected_msg_num =3D=3D msg_num) + return true; + spin_unlock_bh(&enic->mbox_state_lock); + + return false; +} + +enum enic_mbox_vf_reply_recovery { + ENIC_MBOX_VF_REPLY_OK, + ENIC_MBOX_VF_REPLY_RECONNECT, + ENIC_MBOX_VF_REPLY_REGISTRATION_LOST, +}; + +static int +enic_mbox_vf_classify_reply(bool malformed, u16 ret_major, + enum enic_mbox_vf_reply_recovery *recovery) +{ + *recovery =3D ENIC_MBOX_VF_REPLY_OK; + if (malformed) { + *recovery =3D ENIC_MBOX_VF_REPLY_RECONNECT; + return -EIO; + } + /* Some deployed peers return a negative errno in this 16-bit field. + * Interpret protocol bits only when no unknown bits are present; otherwi= se + * an errno such as -EINVAL could accidentally look like registration los= s. + */ + if (!(ret_major & ~ENIC_MBOX_ERR_MASK) && + (ret_major & ENIC_MBOX_ERR_VF_NOT_REGISTERED)) { + *recovery =3D ENIC_MBOX_VF_REPLY_REGISTRATION_LOST; + return -ENODEV; + } + if (!(ret_major & ~ENIC_MBOX_ERR_MASK) && + (ret_major & ENIC_MBOX_ERR_MSG_NOT_SUPPORTED)) + return -EOPNOTSUPP; + if (ret_major) + return -EIO; + + return 0; +} + +static void +enic_mbox_vf_recover_reply_locked(struct enic *enic, + enum enic_mbox_vf_reply_recovery recovery) +{ + bool registration_lost; + + lockdep_assert_held(&enic->mbox_state_lock); + + if (recovery !=3D ENIC_MBOX_VF_REPLY_OK) { + registration_lost =3D + recovery =3D=3D ENIC_MBOX_VF_REPLY_REGISTRATION_LOST; + enic_mbox_vf_mark_reconnect_locked(enic, registration_lost); + } +} + +static void enic_mbox_vf_reply_complete(struct enic *enic) +{ + lockdep_assert_held(&enic->mbox_state_lock); + enic->mbox_expected_reply =3D 0; + enic->mbox_expected_msg_num =3D 0; + /* Publish completion before releasing the state lock. A waiter that + * hit the timeout boundary may otherwise see the claimed state, finish t= he + * request, and let a new request reinitialize this completion before the + * old handler signals it. + */ + complete(&enic->mbox_comp); + spin_unlock_bh(&enic->mbox_state_lock); +} + int enic_mbox_send_link_state(struct enic *enic, u16 vf_id, u32 link_state) { struct enic_mbox_pf_link_state_notif_msg notif =3D {}; @@ -551,23 +628,21 @@ static void enic_mbox_vf_handle_reply(struct enic *en= ic, u8 reply_type, void *payload, u64 msg_num) { struct enic_mbox_generic_reply *reply =3D payload; + enum enic_mbox_vf_reply_recovery recovery; u16 ret_major =3D le16_to_cpu(reply->ret_major); - u64 expected_msg_num; - u8 expected_type; + u8 expected; + int status; =20 - spin_lock_bh(&enic->mbox_state_lock); - expected_type =3D enic->mbox_expected_reply; - expected_msg_num =3D enic->mbox_expected_msg_num; - if (expected_type !=3D reply_type || expected_msg_num !=3D msg_num) { - spin_unlock_bh(&enic->mbox_state_lock); + status =3D enic_mbox_vf_classify_reply(false, ret_major, &recovery); + if (!enic_mbox_vf_reply_claim(enic, reply_type, msg_num, &expected)) { netdev_warn(enic->netdev, - "MBOX: stale reply %u/%llu (expected %u/%llu), drop\n", + "MBOX: stale reply %u/%llu (expected %u), drop\n", reply_type, (unsigned long long)msg_num, - expected_type, (unsigned long long)expected_msg_num); + expected); return; } =20 - if (!ret_major) { + if (!status) { switch (reply_type) { case ENIC_MBOX_VF_CAPABILITY_REPLY: { struct enic_mbox_vf_capability_reply_msg *cap =3D payload; @@ -584,16 +659,168 @@ static void enic_mbox_vf_handle_reply(struct enic *e= nic, u8 reply_type, break; } } - enic->mbox_expected_reply =3D 0; - enic->mbox_expected_msg_num =3D 0; - complete(&enic->mbox_comp); - spin_unlock_bh(&enic->mbox_state_lock); + enic_mbox_vf_recover_reply_locked(enic, recovery); + WRITE_ONCE(enic->mbox_reply_status, status); + enic_mbox_vf_reply_complete(enic); =20 if (ret_major) netdev_warn(enic->netdev, "MBOX: PF rejected reply type %u: %u/%u\n", reply_type, ret_major, le16_to_cpu(reply->ret_minor)); + if (recovery !=3D ENIC_MBOX_VF_REPLY_OK) + enic_mbox_vf_kick_recovery(enic); +} + +static bool enic_mbox_vf_mac_reply_matches(const struct enic_mac_addr *req= uest, + const struct enic_mac_addr *reply) +{ + u16 request_flags =3D le16_to_cpu(request->flags); + u16 reply_flags =3D le16_to_cpu(reply->flags); + u16 idempotent_result =3D reply_flags & + (ENIC_MAC_ADDR_FLAG_DUPLICATE | + ENIC_MAC_ADDR_FLAG_NOT_FOUND); + u16 result =3D reply_flags & ENIC_MAC_ADDR_FLAG_REPLY_MASK; + u16 expected_result; + + if (!ether_addr_equal(request->addr, reply->addr) || + (request_flags & ENIC_MAC_ADDR_FLAG_REQUEST_MASK) !=3D + (reply_flags & ENIC_MAC_ADDR_FLAG_REQUEST_MASK)) + return false; + if (hweight16(result) > 1) + return false; + + /* DUPLICATE is a successful ADD result and NOT_FOUND is a successful + * DELETE result. Neither is valid for the opposite operation, and a + * reply cannot report both outcomes for one entry. + */ + expected_result =3D request_flags & ENIC_MAC_ADDR_FLAG_ADD ? + ENIC_MAC_ADDR_FLAG_DUPLICATE : + ENIC_MAC_ADDR_FLAG_NOT_FOUND; + + return !idempotent_result || idempotent_result =3D=3D expected_result; +} + +static void enic_mbox_vf_handle_add_del_mac_reply(struct enic *enic, + void *payload, u16 msg_len, + u64 msg_num) +{ + struct enic_mbox_vf_add_del_mac_reply_msg *reply =3D payload; + enum enic_mbox_vf_reply_recovery recovery; + u16 reported_errors =3D 0; + u16 num_addrs =3D 0; + u16 ret_minor =3D 0; + u16 ret_major =3D 0; + u8 expected; + unsigned int i; + int status; + + if (msg_len < sizeof(*reply)) { + status =3D enic_mbox_vf_classify_reply(true, 0, &recovery); + } else { + ret_major =3D le16_to_cpu(reply->reply.ret_major); + ret_minor =3D le16_to_cpu(reply->reply.ret_minor); + status =3D enic_mbox_vf_classify_reply(false, ret_major, + &recovery); + if (status =3D=3D -EIO) + recovery =3D ENIC_MBOX_VF_REPLY_RECONNECT; + } + if (status) + goto claim; + + num_addrs =3D le16_to_cpu(reply->num_addrs); + if (!num_addrs || num_addrs > ENIC_MBOX_MAX_MAC_OPS || + struct_size(reply, mac_addr, num_addrs) > msg_len) { + status =3D enic_mbox_vf_classify_reply(true, 0, &recovery); + goto claim; + } + +claim: + if (!enic_mbox_vf_reply_claim(enic, ENIC_MBOX_VF_ADD_DEL_MAC_REPLY, + msg_num, &expected)) + return; + if (!status && + (num_addrs !=3D enic->mbox_reply_mac_count || + !enic->mbox_reply_mac_addrs)) { + status =3D enic_mbox_vf_classify_reply(true, 0, &recovery); + } else if (!status) { + /* A detailed reply corresponds entry-for-entry with the request. + * Validate the echoed request fields and operation-specific results + * before exposing result flags to the waiting caller. + */ + for (i =3D 0; i < num_addrs; i++) { + struct enic_mac_addr *request =3D + &enic->mbox_reply_mac_addrs[i]; + u16 flags =3D le16_to_cpu(reply->mac_addr[i].flags); + + if (!enic_mbox_vf_mac_reply_matches(request, + &reply->mac_addr[i])) { + status =3D enic_mbox_vf_classify_reply(true, 0, + &recovery); + break; + } + if (flags & ENIC_MAC_ADDR_FLAG_INDETERMINATE_MASK) { + status =3D -EIO; + recovery =3D ENIC_MBOX_VF_REPLY_RECONNECT; + break; + } + if ((flags & ENIC_MAC_ADDR_FLAG_REPLY_MASK) && + !(flags & ENIC_MAC_ADDR_FLAG_SKIPPED)) + reported_errors++; + } + if (!status && reported_errors !=3D ret_minor) + status =3D enic_mbox_vf_classify_reply(true, 0, + &recovery); + + if (!status) + for (i =3D 0; i < num_addrs; i++) + enic->mbox_reply_mac_addrs[i].flags =3D + reply->mac_addr[i].flags; + } + /* After a malformed reply, the VF cannot trust that its state matches the + * PF. VF_NOT_REGISTERED means the PF removed all VF state. Both require a + * new registration; an ordinary policy rejection does not. + */ + enic_mbox_vf_recover_reply_locked(enic, recovery); + WRITE_ONCE(enic->mbox_reply_status, status); + enic_mbox_vf_reply_complete(enic); + if (recovery !=3D ENIC_MBOX_VF_REPLY_OK) + enic_mbox_vf_kick_recovery(enic); +} + +static void enic_mbox_vf_handle_set_pkt_filter_reply(struct enic *enic, + void *payload, u16 msg_len, + u64 msg_num) +{ + struct enic_mbox_vf_set_pkt_filter_reply_msg *reply =3D payload; + enum enic_mbox_vf_reply_recovery recovery; + u16 applied =3D 0; + u16 ret_major =3D 0; + u8 expected; + int status; + + if (msg_len < sizeof(*reply)) { + status =3D enic_mbox_vf_classify_reply(true, 0, &recovery); + } else { + ret_major =3D le16_to_cpu(reply->reply.ret_major); + status =3D enic_mbox_vf_classify_reply(false, ret_major, + &recovery); + if (status && (ret_major & ~ENIC_MBOX_ERR_MASK)) + recovery =3D ENIC_MBOX_VF_REPLY_RECONNECT; + } + if (!status) + applied =3D le16_to_cpu(reply->reply.ret_minor); + + if (!enic_mbox_vf_reply_claim(enic, + ENIC_MBOX_VF_SET_PKT_FILTER_REPLY, + msg_num, &expected)) + return; + enic_mbox_vf_recover_reply_locked(enic, recovery); + WRITE_ONCE(enic->mbox_reply_status, status); + WRITE_ONCE(enic->mbox_reply_filter_flags, applied); + enic_mbox_vf_reply_complete(enic); + if (recovery !=3D ENIC_MBOX_VF_REPLY_OK) + enic_mbox_vf_kick_recovery(enic); } =20 static void enic_mbox_vf_handle_link_state(struct enic *enic, void *payloa= d, @@ -679,6 +906,34 @@ static bool enic_mbox_vf_payload_ok(struct enic *enic,= u8 msg_type, return true; } =20 +static void enic_mbox_vf_malformed_msg(struct enic *enic, u8 msg_type, + u64 msg_num) +{ + u8 expected; + + switch (msg_type) { + case ENIC_MBOX_PF_LINK_STATE_NOTIF: + case ENIC_MBOX_PF_SET_ADMIN_MAC_NOTIF: + enic_mbox_vf_require_reconnect(enic); + return; + case ENIC_MBOX_VF_CAPABILITY_REPLY: + case ENIC_MBOX_VF_REGISTER_REPLY: + case ENIC_MBOX_VF_UNREGISTER_REPLY: + case ENIC_MBOX_VF_ADD_DEL_MAC_REPLY: + case ENIC_MBOX_VF_SET_PKT_FILTER_REPLY: + break; + default: + return; + } + + if (!enic_mbox_vf_reply_claim(enic, msg_type, msg_num, &expected)) + return; + enic_mbox_vf_mark_reconnect_locked(enic, false); + WRITE_ONCE(enic->mbox_reply_status, -EIO); + enic_mbox_vf_reply_complete(enic); + enic_mbox_vf_kick_recovery(enic); +} + static void enic_mbox_vf_process_msg(struct enic *enic, struct enic_mbox_hdr *hdr, void *payload, u16 payload_len) @@ -690,8 +945,10 @@ static void enic_mbox_vf_process_msg(struct enic *enic, size_t exp =3D sizeof(struct enic_mbox_vf_capability_reply_msg); =20 if (!enic_mbox_vf_payload_ok(enic, hdr->msg_type, - payload_len, exp)) + payload_len, exp)) { + enic_mbox_vf_malformed_msg(enic, hdr->msg_type, msg_num); return; + } enic_mbox_vf_handle_reply(enic, hdr->msg_type, payload, msg_num); break; } @@ -699,8 +956,10 @@ static void enic_mbox_vf_process_msg(struct enic *enic, size_t exp =3D sizeof(struct enic_mbox_vf_register_reply_msg); =20 if (!enic_mbox_vf_payload_ok(enic, hdr->msg_type, - payload_len, exp)) + payload_len, exp)) { + enic_mbox_vf_malformed_msg(enic, hdr->msg_type, msg_num); return; + } enic_mbox_vf_handle_reply(enic, hdr->msg_type, payload, msg_num); break; } @@ -708,8 +967,10 @@ static void enic_mbox_vf_process_msg(struct enic *enic, size_t exp =3D sizeof(struct enic_mbox_vf_register_reply_msg); =20 if (!enic_mbox_vf_payload_ok(enic, hdr->msg_type, - payload_len, exp)) + payload_len, exp)) { + enic_mbox_vf_malformed_msg(enic, hdr->msg_type, msg_num); return; + } enic_mbox_vf_handle_reply(enic, hdr->msg_type, payload, msg_num); break; } @@ -717,11 +978,21 @@ static void enic_mbox_vf_process_msg(struct enic *eni= c, size_t exp =3D sizeof(struct enic_mbox_pf_link_state_notif_msg); =20 if (!enic_mbox_vf_payload_ok(enic, hdr->msg_type, - payload_len, exp)) + payload_len, exp)) { + enic_mbox_vf_malformed_msg(enic, hdr->msg_type, msg_num); return; + } enic_mbox_vf_handle_link_state(enic, payload, msg_num); break; } + case ENIC_MBOX_VF_ADD_DEL_MAC_REPLY: + enic_mbox_vf_handle_add_del_mac_reply(enic, payload, + payload_len, msg_num); + break; + case ENIC_MBOX_VF_SET_PKT_FILTER_REPLY: + enic_mbox_vf_handle_set_pkt_filter_reply(enic, payload, + payload_len, msg_num); + break; default: netdev_dbg(enic->netdev, "MBOX: VF unhandled msg type %u\n", @@ -761,6 +1032,10 @@ static void enic_mbox_recv_handler(struct enic *enic,= void *buf, netdev_warn(enic->netdev, "MBOX: invalid msg_len %u (buf len %u)\n", msg_len, len); + if (!enic->vf_state && + le16_to_cpu(hdr->src_vnic_id) =3D=3D ENIC_MBOX_DST_PF) + enic_mbox_vf_malformed_msg(enic, hdr->msg_type, + le64_to_cpu(hdr->msg_num)); return; } =20 @@ -791,10 +1066,12 @@ int enic_mbox_vf_capability_check(struct enic *enic) { struct enic_mbox_vf_capability_msg req =3D {}; u32 version; + int status; int err; =20 enic_mbox_vf_request_start(enic); WRITE_ONCE(enic->pf_cap_version, 0); + WRITE_ONCE(enic->mbox_reply_status, 0); req.version =3D cpu_to_le32(ENIC_MBOX_CAP_VERSION_1); =20 err =3D enic_mbox_vf_send_request(enic, @@ -808,12 +1085,15 @@ int enic_mbox_vf_capability_check(struct enic *enic) =20 err =3D enic_mbox_wait_reply(enic, 3000); version =3D READ_ONCE(enic->pf_cap_version); + status =3D READ_ONCE(enic->mbox_reply_status); enic_mbox_vf_request_finish(enic); if (err) { netdev_warn(enic->netdev, "MBOX: no capability reply from PF\n"); return err; } + if (status) + return status; =20 if (version < ENIC_MBOX_CAP_VERSION_1) { netdev_warn(enic->netdev, @@ -828,10 +1108,12 @@ int enic_mbox_vf_capability_check(struct enic *enic) int enic_mbox_vf_register(struct enic *enic) { bool registered; + int status; int err; =20 enic_mbox_vf_request_start(enic); WRITE_ONCE(enic->vf_registered, false); + WRITE_ONCE(enic->mbox_reply_status, 0); =20 err =3D enic_mbox_vf_send_request(enic, ENIC_MBOX_VF_REGISTER_REQUEST, ENIC_MBOX_VF_REGISTER_REPLY, NULL, 0); @@ -842,12 +1124,15 @@ int enic_mbox_vf_register(struct enic *enic) =20 err =3D enic_mbox_wait_reply(enic, 3000); registered =3D READ_ONCE(enic->vf_registered); + status =3D READ_ONCE(enic->mbox_reply_status); enic_mbox_vf_request_finish(enic); if (err) { netdev_warn(enic->netdev, "MBOX: VF registration with PF timed out\n"); return err; } + if (status) + return status; =20 if (!registered) return -ENODEV; @@ -858,15 +1143,18 @@ int enic_mbox_vf_register(struct enic *enic) int enic_mbox_vf_unregister(struct enic *enic) { bool registered; + int status; int err; =20 if (!READ_ONCE(enic->vf_registered)) return 0; + enic_mbox_vf_request_start(enic); if (!READ_ONCE(enic->vf_registered)) { enic_mbox_vf_request_finish(enic); return 0; } + WRITE_ONCE(enic->mbox_reply_status, 0); =20 err =3D enic_mbox_vf_send_request(enic, ENIC_MBOX_VF_UNREGISTER_REQUEST, @@ -879,14 +1167,182 @@ int enic_mbox_vf_unregister(struct enic *enic) =20 err =3D enic_mbox_wait_reply(enic, 3000); registered =3D READ_ONCE(enic->vf_registered); + status =3D READ_ONCE(enic->mbox_reply_status); enic_mbox_vf_request_finish(enic); if (err) return err; + if (status) + return status; if (registered) return -EACCES; return 0; } =20 +int enic_mbox_vf_add_del_macs(struct enic *enic, + struct enic_mac_addr *macs, u16 num_macs) +{ + struct enic_mbox_vf_add_del_mac_msg *req; + unsigned int i; + int status; + int err; + + if (!READ_ONCE(enic->vf_registered) || !enic->has_admin_channel) + return -ENODEV; + if (!num_macs || num_macs > ENIC_MBOX_MAX_MAC_OPS) + return -EINVAL; + + req =3D kzalloc_flex(*req, mac_addr, num_macs); + if (!req) + return -ENOMEM; + + req->num_addrs =3D cpu_to_le16(num_macs); + for (i =3D 0; i < num_macs; i++) + req->mac_addr[i] =3D macs[i]; + + enic_mbox_vf_request_start(enic); + if (!READ_ONCE(enic->vf_registered) || !enic->has_admin_channel) { + err =3D -ENODEV; + } else { + spin_lock_bh(&enic->mbox_state_lock); + enic->mbox_reply_mac_addrs =3D macs; + enic->mbox_reply_mac_count =3D num_macs; + spin_unlock_bh(&enic->mbox_state_lock); + WRITE_ONCE(enic->mbox_reply_status, 0); + err =3D enic_mbox_vf_send_request(enic, + ENIC_MBOX_VF_ADD_DEL_MAC_REQUEST, + ENIC_MBOX_VF_ADD_DEL_MAC_REPLY, + req, + struct_size(req, mac_addr, + num_macs)); + } + kfree(req); + if (err) { + spin_lock_bh(&enic->mbox_state_lock); + enic->mbox_reply_mac_addrs =3D NULL; + enic->mbox_reply_mac_count =3D 0; + spin_unlock_bh(&enic->mbox_state_lock); + enic_mbox_vf_request_abort(enic); + return err; + } + + err =3D enic_mbox_wait_reply(enic, 3000); + status =3D READ_ONCE(enic->mbox_reply_status); + spin_lock_bh(&enic->mbox_state_lock); + enic->mbox_reply_mac_addrs =3D NULL; + enic->mbox_reply_mac_count =3D 0; + spin_unlock_bh(&enic->mbox_state_lock); + if (err) { + /* The PF may have updated its software ledger before a hardware + * failure whose reply was lost. A repeated idempotent operation could + * then appear converged while hardware state is stale, so replay from + * a fresh registration generation. + */ + enic_mbox_vf_require_reconnect(enic); + enic_mbox_vf_request_finish(enic); + return err; + } + enic_mbox_vf_request_finish(enic); + + return status; +} + +int enic_mbox_vf_add_del_mac(struct enic *enic, const u8 *addr, bool add, + bool station) +{ + struct enic_mac_addr mac =3D {}; + u16 flags =3D 0; + int err; + + ether_addr_copy(mac.addr, addr); + if (add) + flags |=3D ENIC_MAC_ADDR_FLAG_ADD; + if (station) + flags |=3D ENIC_MAC_ADDR_FLAG_STATION; + mac.flags =3D cpu_to_le16(flags); + + err =3D enic_mbox_vf_add_del_macs(enic, &mac, 1); + if (err) + return err; + if (le16_to_cpu(mac.flags) & ENIC_MAC_ADDR_FLAG_ERROR_MASK) + return -EACCES; + + return 0; +} + +int enic_mbox_vf_set_pkt_filter(struct enic *enic, int directed, + int multicast, int broadcast, + int promisc, int allmulti, u16 *applied_flags) +{ + struct enic_mbox_vf_set_pkt_filter_msg req =3D {}; + u16 applied; + u16 flags =3D 0; + u16 required; + int status; + int err; + + if (!READ_ONCE(enic->vf_registered) || !enic->has_admin_channel) + return -ENODEV; + + if (directed) + flags |=3D CMD_PFILTER_DIRECTED; + if (multicast) + flags |=3D CMD_PFILTER_MULTICAST; + if (broadcast) + flags |=3D CMD_PFILTER_BROADCAST; + if (promisc) + flags |=3D CMD_PFILTER_PROMISCUOUS; + if (allmulti) + flags |=3D CMD_PFILTER_ALL_MULTICAST; + req.flags =3D cpu_to_le16(flags); + + enic_mbox_vf_request_start(enic); + if (!READ_ONCE(enic->vf_registered) || !enic->has_admin_channel) { + enic_mbox_vf_request_abort(enic); + return -ENODEV; + } + WRITE_ONCE(enic->mbox_reply_status, 0); + WRITE_ONCE(enic->mbox_reply_filter_flags, 0); + + err =3D enic_mbox_vf_send_request(enic, + ENIC_MBOX_VF_SET_PKT_FILTER_REQUEST, + ENIC_MBOX_VF_SET_PKT_FILTER_REPLY, + &req, sizeof(req)); + if (err) { + enic_mbox_vf_request_abort(enic); + return err; + } + + err =3D enic_mbox_wait_reply(enic, 3000); + status =3D READ_ONCE(enic->mbox_reply_status); + if (!err && !status) { + applied =3D READ_ONCE(enic->mbox_reply_filter_flags); + /* Directed, multicast, and broadcast are not policy-gated. The PF + * may only withhold the two broad receive modes, and may add directed + * reception because it is mandatory for a usable VF. + */ + required =3D (flags | CMD_PFILTER_DIRECTED) & + ~(CMD_PFILTER_PROMISCUOUS | + CMD_PFILTER_ALL_MULTICAST); + if ((applied & ~(flags | CMD_PFILTER_DIRECTED)) || + (applied & required) !=3D required) { + netdev_warn(enic->netdev, + "MBOX: invalid packet filter reply %#x for request %#x\n", + applied, flags); + enic_mbox_vf_require_reconnect(enic); + status =3D -EIO; + } else if (applied_flags) { + *applied_flags =3D applied; + } + } + if (err) + enic_mbox_vf_require_reconnect(enic); + enic_mbox_vf_request_finish(enic); + if (err) + return err; + + return status; +} + void enic_mbox_init(struct enic *enic) { bool reinit =3D enic->mbox_initialized; diff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.h b/drivers/net/ethe= rnet/cisco/enic/enic_mbox.h index 37bc41a900f4..5eca3a25671e 100644 --- a/drivers/net/ethernet/cisco/enic/enic_mbox.h +++ b/drivers/net/ethernet/cisco/enic/enic_mbox.h @@ -5,6 +5,7 @@ #define _ENIC_MBOX_H_ =20 #include +#include #include =20 /* @@ -22,6 +23,12 @@ enum enic_mbox_msg_type { ENIC_MBOX_VF_UNREGISTER_REPLY =3D 5, ENIC_MBOX_PF_LINK_STATE_NOTIF =3D 6, ENIC_MBOX_PF_LINK_STATE_ACK =3D 7, + ENIC_MBOX_VF_ADD_DEL_MAC_REQUEST =3D 10, + ENIC_MBOX_VF_ADD_DEL_MAC_REPLY =3D 11, + ENIC_MBOX_PF_SET_ADMIN_MAC_NOTIF =3D 12, + ENIC_MBOX_PF_SET_ADMIN_MAC_ACK =3D 13, + ENIC_MBOX_VF_SET_PKT_FILTER_REQUEST =3D 14, + ENIC_MBOX_VF_SET_PKT_FILTER_REPLY =3D 15, ENIC_MBOX_MAX }; =20 @@ -42,6 +49,9 @@ struct enic_mbox_generic_reply { #define ENIC_MBOX_ERR_GENERIC BIT(0) #define ENIC_MBOX_ERR_VF_NOT_REGISTERED BIT(1) #define ENIC_MBOX_ERR_MSG_NOT_SUPPORTED BIT(2) +#define ENIC_MBOX_ERR_MASK (ENIC_MBOX_ERR_GENERIC | \ + ENIC_MBOX_ERR_VF_NOT_REGISTERED | \ + ENIC_MBOX_ERR_MSG_NOT_SUPPORTED) =20 /* ENIC_MBOX_VF_CAPABILITY_REQUEST / _REPLY */ #define ENIC_MBOX_CAP_VERSION_0 0 @@ -80,6 +90,71 @@ struct enic_mbox_pf_link_state_ack_msg { struct enic_mbox_generic_reply ack; }; =20 +/* ENIC_MBOX_PF_SET_ADMIN_MAC_NOTIF / _ACK */ +struct enic_mbox_pf_set_admin_mac_notif_msg { + u8 mac_addr[ETH_ALEN]; + __le16 pad; +}; + +/* ENIC_MBOX_VF_ADD_DEL_MAC_REQUEST / _REPLY */ +#define ENIC_MAC_ADDR_FLAG_ADD BIT(0) +#define ENIC_MAC_ADDR_FLAG_STATION BIT(1) +#define ENIC_MAC_ADDR_FLAG_OVERFLOW BIT(8) +#define ENIC_MAC_ADDR_FLAG_DUPLICATE BIT(9) +#define ENIC_MAC_ADDR_FLAG_FAILED BIT(10) +#define ENIC_MAC_ADDR_FLAG_NOT_FOUND BIT(11) +#define ENIC_MAC_ADDR_FLAG_ERROR BIT(12) +#define ENIC_MAC_ADDR_FLAG_NOT_PERMITTED BIT(13) +#define ENIC_MAC_ADDR_FLAG_INVALID BIT(14) +#define ENIC_MAC_ADDR_FLAG_SKIPPED BIT(15) + +#define ENIC_MAC_ADDR_FLAG_REQUEST_MASK GENMASK(7, 0) +#define ENIC_MAC_ADDR_FLAG_REPLY_MASK GENMASK(15, 8) +#define ENIC_MAC_ADDR_FLAG_INDETERMINATE_MASK \ + (ENIC_MAC_ADDR_FLAG_FAILED | ENIC_MAC_ADDR_FLAG_ERROR) +#define ENIC_MAC_ADDR_FLAG_PERMANENT_MASK \ + (ENIC_MAC_ADDR_FLAG_OVERFLOW | ENIC_MAC_ADDR_FLAG_NOT_PERMITTED | \ + ENIC_MAC_ADDR_FLAG_INVALID) +#define ENIC_MAC_ADDR_FLAG_ERROR_MASK (ENIC_MAC_ADDR_FLAG_OVERFLOW | \ + ENIC_MAC_ADDR_FLAG_FAILED | \ + ENIC_MAC_ADDR_FLAG_ERROR | \ + ENIC_MAC_ADDR_FLAG_NOT_PERMITTED | \ + ENIC_MAC_ADDR_FLAG_INVALID | \ + ENIC_MAC_ADDR_FLAG_SKIPPED) + +/* The protocol permits replacing all perfect filters and the station addr= ess + * in one request: one delete and one add operation for each address. + */ +#define ENIC_MBOX_MAX_MAC_OPS 130 + +struct enic_mac_addr { + u8 addr[ETH_ALEN]; + __le16 flags; +}; + +struct enic_mbox_vf_add_del_mac_msg { + __le16 num_addrs; + __le16 pad; + struct enic_mac_addr mac_addr[]; +}; + +struct enic_mbox_vf_add_del_mac_reply_msg { + struct enic_mbox_generic_reply reply; + __le16 num_addrs; + __le16 pad; + struct enic_mac_addr mac_addr[]; +}; + +/* ENIC_MBOX_VF_SET_PKT_FILTER_REQUEST / _REPLY */ +struct enic_mbox_vf_set_pkt_filter_msg { + __le16 flags; + __le16 pad; +}; + +struct enic_mbox_vf_set_pkt_filter_reply_msg { + struct enic_mbox_generic_reply reply; +}; + #define ENIC_MBOX_DST_PF 0xFFFF =20 struct enic; @@ -95,5 +170,12 @@ void enic_mbox_vf_require_reconnect(struct enic *enic); int enic_mbox_vf_capability_check(struct enic *enic); int enic_mbox_vf_register(struct enic *enic); int enic_mbox_vf_unregister(struct enic *enic); +int enic_mbox_vf_add_del_macs(struct enic *enic, + struct enic_mac_addr *macs, u16 num_macs); +int enic_mbox_vf_add_del_mac(struct enic *enic, const u8 *addr, bool add, + bool station); +int enic_mbox_vf_set_pkt_filter(struct enic *enic, int directed, int multi= cast, + int broadcast, int promisc, int allmulti, + u16 *applied_flags); =20 #endif /* _ENIC_MBOX_H_ */ --=20 2.43.0 From nobody Thu Sep 24 17:56:23 2026 Received: from rcdn-iport-9.cisco.com (rcdn-iport-9.cisco.com [173.37.86.80]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1D06A4477FF; Mon, 21 Sep 2026 20:01:34 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=173.37.86.80 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790020899; cv=none; b=rZRIsVY+LsuATwh/kVB2CPClndirkZdr6XT+QdMMRTSS9ZvSav4pUHJbtzsKp+bEim3kaci637goUTiiVCku4G9IR5KPN94beEl8yRrVYErqtUD6teOI4ra3I3LOzdrB/D2djvfYoJl1UFI6/LEuMmcvrxGXBZzDuX2ZnVwUZhQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790020899; c=relaxed/simple; bh=Jb1do3JYCWW6dOhSWyttHtcQ9snXh6edI00u2g5OGV0=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=BRkpfgCTrwkD6A5ZprEC+WsGchv7ebV39Q8S1e3dq1ivLhNzvZOiqaoCs0bgcTPjXUAX93/ie5l8GTYpkMgmGcGNaCoqIOcK8mipU0p3pG4EE/lp1TgZqh7j7Le6PFkVWsnZa54EgKXwXCRw4VaCyjV45FcH8X7Y1o5t4nxJ9yY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=cisco.com; spf=pass smtp.mailfrom=cisco.com; dkim=pass (2048-bit key) header.d=cisco.com header.i=@cisco.com header.b=O6sjZCqP; arc=none smtp.client-ip=173.37.86.80 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=cisco.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=cisco.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=cisco.com header.i=@cisco.com header.b="O6sjZCqP" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cisco.com; i=@cisco.com; l=28994; q=dns/txt; s=iport01; t=1790020895; x=1791230495; h=from:date:subject:mime-version:content-transfer-encoding: message-id:references:in-reply-to:to:cc; bh=7fgm8mlyKbByieaaB19j5DNu3nAUW+sCEDTX+6LqSRo=; b=O6sjZCqP/BnUHs7sl/E9FUZPNN6idD5vlgTqs5CUFQmd2j/SsOo6/x40 rtnKVeZUWNacXYn2BrzSx7e2sVG/Uhinnfrbxq6/3GgF2W0w+SemaJCOg FRARhxcdankfwZPN/N0IQ6ovTDQIzUBBBD89MSHN2ozrsZ7JWhsMtPR4s g1YVcJhEqRApiN2AEk8aOZzfDGBPz68j+lLye6UWU+zos8siy4hoMmJL+ 0e7ZPkOmXj6r2NDsSnwpr9eBp8ZbZ/t+UZ+mHZt1A4Rr7AB7gBrYpXzA1 qmrp6+vKP+/fEoe+63IU1AutWU1IVMIJyFud3tMsVmRVH0R+r60rHIswt w==; X-CSE-ConnectionGUID: aldp8VxISOKb2F3g4XgwTQ== X-CSE-MsgGUID: K71Z3TgpSJ+NY9kl2V9v4g== X-IPAS-Result: =?us-ascii?q?A0BYAACvi7Fq/47/Ja1aHQEBAQEJARIBBQUBgX4GAQsBg?= =?us-ascii?q?laBVUNJhFePUoIhkU2MURSBag8BAQEPUQQBAYISgnMCjgcCJjYHDgECBAMCA?= =?us-ascii?q?wEBAQEBAQEBAQEBAQoBAQUBAQECAQcFgQ4ThlyQEwIBAxoJBFIQJQImAgJGE?= =?us-ascii?q?AYBEoMCgnUDxXh6fzOBAYR+2UqBaAELFAGBCi4BhXCCbQGBcIQIO4RBJxuBS?= =?us-ascii?q?USBFYNpgRmDBguDdIJqBIMcEoENiQyKQQk/gQIcA1ksAVUTDQoLBwVhUjMDI?= =?us-ascii?q?AoLEhIYFQIUHRIPBBYyHXAMJxIsFy9YGwYFgR18FAQTghojGTZ6gQlegSspY?= =?us-ascii?q?AEQF4EHgggCglSBfwIBSUMOB0VTCSdBChJHKSIIEgkBExowC4EnGA1IESw3B?= =?us-ascii?q?g8ZBD0BbgePXh+BagFRGQYBDggXEB01exAhAQ8IKgoHFAQRBi4XL5JjCQE4g?= =?us-ascii?q?zyNX2uBNZ9ahCiBZqAQM4QEk2M0klKZCCOCNqEZDwkBAk2EaYFvCiuBWTMaC?= =?us-ascii?q?BsVgyJTGQ+OLhaTHQG8eycyPgEHAgcPAoFzkAAEDReBVgEB?= IronPort-Data: A9a23:I/MlMKjSVEb+f5vo+21Vx8fQX161shEKZh0ujC45NGQN5FlHY01je htvXmGPM6zea2Snftt0aIq+oR8H6p6Ey9BkQAFsrygwQi9jpJueD7x1DKtf0wB+jyHnZBg6h ynLQoCYdKjYdleF+FHwdOCn8ikkvU2xbuKUIPbePSxsThNTRi4kiBZy88Y0mYcAbeKRW2thg vus5ZeAULOZ82QsaDxMuvja8EkHUMna4Vv0gHRvPZing3eG/5UlJMp3Db28KXL+Xr5VEoaSL 87fzKu093/u5BwkDNWoiN7TKiXmlZaLYGBiIlIPM0STqkAqSh4ai87XB9JAAatjsAhlqvgqo Dl7WTNcfi9yVkHEsLx1vxC1iEiSN4UekFPMCSDXXcB+UyQqflO0q8iCAn3aMqUR/upvUV9e1 Mc/CzpXQEuM29K5346CH7wEasQLdKEHPasFsX1miDWcBvE8TNWbHuPB5MRT23E7gcUm8fT2P pVCL2EwKk6dPlsWZg1/5JEWxI9EglHkeiFZs1KWpYI84nPYy0p6172F3N/9J4bXHZkNwx3Dz o7A10emWyNBCNOH8hjG1EP9heuVo3LlBI1HQdVU8dYv2jV/3Fc7BBQIWF6TrfCnh0u6XNxDb UoZ5kIGoKQv8UW5Q8XVUBq/r3qJ+BUbXrJ4E+og7RqlyafO5QudQG8eQVZpa8Esvec1SCYs2 1vPmMnmbRRmtrGPRG3e8LqIoT6sESwIK2lEbi9sZRMM6dTloakpgx7PR8olG6mw5vXxGDft0 3WJoTI4irE7k8EGzeO48ErBjjbqoYLGJiYz6xnbU3yN8Ax0fsimapau5Fyd6uxPRLt1VXGbt 3QC3szb5+cUANTVyWqGQf4GG/ei4PPt3CDgvGOD1qIJr1yFk0NPt6gKiN2iDC+F6vo5RAI= IronPort-HdrOrdr: A9a23:iHR84ax/7YgIDsyfiX8EKrPw2r1zdoMgy1knxilNoNJuA6ilfq eV/cjztCWbtN9/YhwdcLy7UpVoBEmsl6KdgrN/AV7dZmnbUQKTRekI0WKh+UyCJ8SUzIJgPM lbAs1DIey1IFRkgcP9+QWiV/Qt0LC8gdmVrNab4ntzQQRtcq16qz14BAGSD1FsSGB9dPwE/e Knl616jgvlX2gLZcKmAXRAde3Cq9rX0K/CW3c9dmYaAM3kt0LT1FY8eCLopSsjbw== X-Talos-CUID: 9a23:jZByc2CHHT/6Kl/6EyBj+FZEJNAESVr+xi3+eEDkOWl2dITAHA== X-Talos-MUID: 9a23:nXdSrgs9Kl6cCsuD1M2np247BpxU7LyVKgMroMQFv5O6ZDIsNGLI X-IronPort-Anti-Spam-Filtered: true X-IronPort-AV: E=Sophos;i="6.27,115,1787011200"; d="scan'208";a="525040533" Received: from rcdn-l-core-05.cisco.com ([173.37.255.142]) by rcdn-iport-9.cisco.com with ESMTP/TLS/TLS_AES_256_GCM_SHA384; 21 Sep 2026 20:00:25 +0000 Received: from [127.0.0.1] (unknown [10.25.128.208]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by rcdn-l-core-05.cisco.com (Postfix) with ESMTPS id A675A18000353; Mon, 21 Sep 2026 20:00:24 +0000 (GMT) From: Satish Kharat Date: Mon, 21 Sep 2026 12:59:48 -0700 Subject: [PATCH net-next 5/6] enic: manage V2 VF station and administrative MAC Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260921-b4-enic-sriov-v2-vf-receive-control-v1-5-67a0a6e08d43@cisco.com> References: <20260921-b4-enic-sriov-v2-vf-receive-control-v1-0-67a0a6e08d43@cisco.com> In-Reply-To: <20260921-b4-enic-sriov-v2-vf-receive-control-v1-0-67a0a6e08d43@cisco.com> To: Andrew Lunn , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, Satish Kharat , sebaddel@cisco.com X-Mailer: b4 0.15.2 X-Developer-Signature: v=1; a=ed25519-sha256; t=1790020820; l=29838; i=satishkh@cisco.com; h=from:subject:message-id; bh=Jb1do3JYCWW6dOhSWyttHtcQ9snXh6edI00u2g5OGV0=; b=Sg7Yw2t9VAUtGu4Loe3YQxfEaR9G5qutYf2egmesxWCncUOx67ab9G3Xm55vGjqZaxwminXcA W2o4TLKIakiDlv9CDhWqTJVFmixKoJc6B05p/Mk7+Niuo4J0dDF3lRp X-Developer-Key: i=satishkh@cisco.com; a=ed25519; pk=4BxbLoCGvHeIMH/PbpiNVx8/2IcTjFvqcr0AD8K6WLY= X-Outbound-Client-TLS: ANONYMOUS;unknown [10.25.128.208];TLSv1.3;TLS_AES_256_GCM_SHA384;256 X-Outbound-SMTP-Client: 10.25.128.208, [10.25.128.208] X-Outbound-Node: rcdn-l-core-05.cisco.com Use the V2 mailbox MAC operation for the VF station address and keep that address separate from netdev secondary-unicast synchronization. Replace a station address with one mailbox request containing a delete followed by an add. If the PF reports that only part of the replacement was applied, require the VF to register again before accepting traffic. Treat the PF administrative MAC as authoritative. Process notifications outside receive dispatch, preserve a delegated VF-selected address, refresh the policy after every registration, and protect VF worker shutdown and restart against teardown and recovery. Install the station address during open and remove it during an ordinary stop. At reset and removal boundaries, rely on VF_UNREGISTER or the next VF registration to clear the old state. Assisted-by: LLM Signed-off-by: Satish Kharat --- drivers/net/ethernet/cisco/enic/enic.h | 17 + drivers/net/ethernet/cisco/enic/enic_admin.c | 2 + drivers/net/ethernet/cisco/enic/enic_dev.c | 11 + drivers/net/ethernet/cisco/enic/enic_dev.h | 1 + drivers/net/ethernet/cisco/enic/enic_main.c | 566 +++++++++++++++++++++++= +++- drivers/net/ethernet/cisco/enic/enic_mbox.c | 30 ++ 6 files changed, 623 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/cisco/enic/enic.h b/drivers/net/ethernet/= cisco/enic/enic.h index 45992b355501..0f0ef68130f2 100644 --- a/drivers/net/ethernet/cisco/enic/enic.h +++ b/drivers/net/ethernet/cisco/enic/enic.h @@ -340,6 +340,9 @@ struct enic { spinlock_t vf_link_state_lock; enum enic_vf_link_state vf_link_state; bool vf_link_running; + /* Last station address which may still be installed at the PF. */ + u8 vf_station_addr[ETH_ALEN] __aligned(2); + bool vf_station_addr_valid; /* Tracks a completely opened V2 VF datapath. An internal reset can stop * it while netif_running() remains true, then fail before reopen. */ @@ -360,6 +363,16 @@ struct enic { struct list_head vf_ack_list; struct work_struct vf_ack_work; unsigned int vf_ack_count; + struct delayed_work vf_admin_mac_work; + spinlock_t vf_admin_mac_lock; /* protects pending admin MAC */ + u8 vf_admin_mac[ETH_ALEN]; + u8 vf_admin_mac_random_addr[ETH_ALEN]; + bool vf_admin_mac_pending; + bool vf_admin_mac_random_valid; + bool vf_admin_mac_work_enabled; + bool vf_admin_mac_recovery_attempted; + u32 vf_admin_mac_generation; + u8 vf_admin_mac_retries; u64 mbox_expected_msg_num; u8 mbox_expected_reply; int mbox_reply_status; @@ -493,6 +506,10 @@ static inline int enic_dma_map_check(struct enic *enic= , dma_addr_t dma_addr) } =20 void enic_reset_addr_lists(struct enic *enic); +void enic_vf_admin_mac_notify(struct enic *enic, const u8 *addr); +void enic_vf_admin_mac_quiesce(struct enic *enic); +void enic_vf_admin_mac_rearm(struct enic *enic); +void enic_vf_admin_mac_purge(struct enic *enic); int enic_sriov_enabled(struct enic *enic); int enic_is_valid_vf(struct enic *enic, int vf); int enic_is_dynamic(struct enic *enic); diff --git a/drivers/net/ethernet/cisco/enic/enic_admin.c b/drivers/net/eth= ernet/cisco/enic/enic_admin.c index 30c0a5c89a2d..5e52d4da8d3c 100644 --- a/drivers/net/ethernet/cisco/enic/enic_admin.c +++ b/drivers/net/ethernet/cisco/enic/enic_admin.c @@ -641,6 +641,8 @@ void enic_admin_channel_close(struct enic *enic) { int err; =20 + if (enic_is_sriov_vf_v2(enic)) + enic_vf_admin_mac_quiesce(enic); if (!enic->has_admin_channel) return; =20 diff --git a/drivers/net/ethernet/cisco/enic/enic_dev.c b/drivers/net/ether= net/cisco/enic/enic_dev.c index 659787f73cf1..48c1ca9b40fb 100644 --- a/drivers/net/ethernet/cisco/enic/enic_dev.c +++ b/drivers/net/ethernet/cisco/enic/enic_dev.c @@ -32,6 +32,17 @@ int enic_dev_stats_dump(struct enic *enic, struct vnic_s= tats **vstats) return err; } =20 +int enic_dev_get_mac_addr(struct enic *enic, u8 *mac_addr) +{ + int err; + + spin_lock_bh(&enic->devcmd_lock); + err =3D vnic_dev_get_mac_addr(enic->vdev, mac_addr); + spin_unlock_bh(&enic->devcmd_lock); + + return err; +} + int enic_dev_add_station_addr(struct enic *enic) { int err; diff --git a/drivers/net/ethernet/cisco/enic/enic_dev.h b/drivers/net/ether= net/cisco/enic/enic_dev.h index 698d0cb02064..66d0e51681f5 100644 --- a/drivers/net/ethernet/cisco/enic/enic_dev.h +++ b/drivers/net/ethernet/cisco/enic/enic_dev.h @@ -26,6 +26,7 @@ =20 int enic_dev_fw_info(struct enic *enic, struct vnic_devcmd_fw_info **fw_in= fo); int enic_dev_stats_dump(struct enic *enic, struct vnic_stats **vstats); +int enic_dev_get_mac_addr(struct enic *enic, u8 *mac_addr); int enic_dev_add_station_addr(struct enic *enic); int enic_dev_del_station_addr(struct enic *enic); int enic_dev_packet_filter(struct enic *enic, int directed, int multicast, diff --git a/drivers/net/ethernet/cisco/enic/enic_main.c b/drivers/net/ethe= rnet/cisco/enic/enic_main.c index 1c60e7a666ae..16412c29e932 100644 --- a/drivers/net/ethernet/cisco/enic/enic_main.c +++ b/drivers/net/ethernet/cisco/enic/enic_main.c @@ -89,6 +89,9 @@ MODULE_DEVICE_TABLE(pci, enic_id_table); =20 #define ENIC_LARGE_PKT_THRESHOLD 1000 #define ENIC_MAX_COALESCE_TIMERS 10 +#define ENIC_VF_ADMIN_MAC_MAX_RETRIES 4 +#define ENIC_VF_ADMIN_MAC_RETRY_MS 100 +#define ENIC_VF_ADMIN_MAC_REFRESH_RETRIES 3 /* Interrupt moderation table, which will be used to decide the * coalescing timer values * {rx_rate in Mbps, mapping percentage of the range} @@ -1040,8 +1043,10 @@ void enic_reset_addr_lists(struct enic *enic) { struct net_device *netdev =3D enic->netdev; =20 + netif_addr_lock_bh(netdev); __dev_uc_unsync(netdev, NULL); __dev_mc_unsync(netdev, NULL); + netif_addr_unlock_bh(netdev); =20 enic->mc_count =3D 0; enic->uc_count =3D 0; @@ -1065,6 +1070,456 @@ static int enic_set_mac_addr(struct net_device *net= dev, char *addr) return 0; } =20 +static void enic_vf_station_recovery_required(struct enic *enic) +{ + enic_mbox_vf_require_reconnect(enic); +} + +static void enic_vf_station_addr_set(struct enic *enic, const u8 *addr) +{ + ether_addr_copy(enic->vf_station_addr, addr); + enic->vf_station_addr_valid =3D true; +} + +static int enic_vf_keep_nonstation_sync(struct net_device *netdev, + const u8 *addr) +{ + struct enic *enic =3D netdev_priv(netdev); + + if (!ether_addr_equal(addr, enic->vf_station_addr)) + return -ENOENT; + if (WARN_ON_ONCE(!enic->uc_count)) + return 0; + enic->uc_count--; + + return 0; +} + +static void enic_vf_station_sync_reset(struct enic *enic) +{ + if (!enic->vf_station_addr_valid) + return; + + /* The PF keys its MAC ledger by address. A station entry is also the + * receive filter for that address and must not retain a second core + * synchronization reference which could later delete the station. + */ + netif_addr_lock_bh(enic->netdev); + __dev_uc_unsync(enic->netdev, enic_vf_keep_nonstation_sync); + netif_addr_unlock_bh(enic->netdev); +} + +static int enic_vf_station_addr_del(struct enic *enic) +{ + int err; + + if (!enic->vf_station_addr_valid) + return 0; + + err =3D enic_mbox_vf_add_del_mac(enic, enic->vf_station_addr, + false, true); + if (!err) { + enic->vf_station_addr_valid =3D false; + } else if (READ_ONCE(enic->mbox_tx_poisoned)) { + enic_mbox_vf_link_state_set_running(enic, false); + } else if (err =3D=3D -EACCES) { + /* A definitive rejected DELETE leaves an unrequested station + * filter installed. Fresh registration is the cleanup boundary. + */ + enic_vf_station_recovery_required(enic); + } + + return err; +} + +/* End every replacement with an equal-address DEL+ADD pair. A bare ADD co= uld + * report DUPLICATE for a secondary exact filter without applying the curr= ent + * station-address policy. If another station is tracked, delete it first = in + * the same compound request. + */ +static int enic_vf_station_addr_replace(struct enic *enic, const u8 *addr) +{ + struct enic_mac_addr macs[3] =3D {}; + bool all_deletes_skipped =3D true; + bool deletes_converged =3D true; + u16 num_macs =3D 0; + unsigned int i; + u16 result; + int err; + + if (enic->vf_station_addr_valid && + !ether_addr_equal(enic->vf_station_addr, addr)) { + ether_addr_copy(macs[num_macs].addr, enic->vf_station_addr); + macs[num_macs++].flags =3D + cpu_to_le16(ENIC_MAC_ADDR_FLAG_STATION); + } + ether_addr_copy(macs[num_macs].addr, addr); + macs[num_macs++].flags =3D cpu_to_le16(ENIC_MAC_ADDR_FLAG_STATION); + ether_addr_copy(macs[num_macs].addr, addr); + macs[num_macs++].flags =3D cpu_to_le16(ENIC_MAC_ADDR_FLAG_ADD | + ENIC_MAC_ADDR_FLAG_STATION); + + err =3D enic_mbox_vf_add_del_macs(enic, macs, num_macs); + if (err) { + if (READ_ONCE(enic->mbox_tx_poisoned)) + enic_mbox_vf_link_state_set_running(enic, false); + return err; + } + + for (i =3D 0; i < num_macs - 1; i++) { + result =3D le16_to_cpu(macs[i].flags) & + ENIC_MAC_ADDR_FLAG_REPLY_MASK; + if (result !=3D ENIC_MAC_ADDR_FLAG_SKIPPED) + all_deletes_skipped =3D false; + if (result && result !=3D ENIC_MAC_ADDR_FLAG_NOT_FOUND) + deletes_converged =3D false; + } + result =3D le16_to_cpu(macs[num_macs - 1].flags) & + ENIC_MAC_ADDR_FLAG_REPLY_MASK; + if (deletes_converged && !result) + return 0; + + /* A policy-rejected ADD can be reported with all preceding DELETEs + * skipped. No operation changed state in that coherent result tuple. + */ + if (all_deletes_skipped && result) { + if (result & (ENIC_MAC_ADDR_FLAG_DUPLICATE | + ENIC_MAC_ADDR_FLAG_PERMANENT_MASK)) + return -EACCES; + return -EIO; + } + + /* After any other partial or contradictory result, the VF does not know + * which station address the PF kept. Re-register before accepting traffi= c. + */ + enic_vf_station_recovery_required(enic); + for (i =3D 0; i < num_macs; i++) + if (le16_to_cpu(macs[i].flags) & + ENIC_MAC_ADDR_FLAG_PERMANENT_MASK) + return -EACCES; + + return -EIO; +} + +static void enic_vf_admin_mac_cache_selected(struct enic *enic, + const u8 *addr) +{ + /* A zero administrative policy delegates the operational address to + * the VF. Preserve a successful user selection across reconnects. + */ + spin_lock_bh(&enic->vf_admin_mac_lock); + if (is_zero_ether_addr(enic->vf_admin_mac)) { + ether_addr_copy(enic->vf_admin_mac_random_addr, addr); + enic->vf_admin_mac_random_valid =3D true; + } + spin_unlock_bh(&enic->vf_admin_mac_lock); +} + +static void enic_vf_admin_mac_work(struct work_struct *work) +{ + struct enic *enic =3D container_of(to_delayed_work(work), struct enic, + vf_admin_mac_work); + u8 policy[ETH_ALEN]; + u8 selected[ETH_ALEN]; + unsigned long delay =3D 0; + u32 generation =3D 0; + bool changed; + bool mutated =3D false; + bool reschedule =3D false; + bool station_installed =3D false; + bool stale =3D false; + bool zero_policy; + bool pending; + int err =3D 0; + + spin_lock_bh(&enic->vf_admin_mac_lock); + pending =3D enic->vf_admin_mac_pending && + enic->vf_admin_mac_work_enabled; + spin_unlock_bh(&enic->vf_admin_mac_lock); + if (!pending) + return; + + /* Teardown owns RTNL while synchronously cancelling this work. Do not + * block that owner. + */ + if (!rtnl_trylock()) { + mod_delayed_work(system_wq, &enic->vf_admin_mac_work, + msecs_to_jiffies(10)); + return; + } + + spin_lock_bh(&enic->vf_admin_mac_lock); + pending =3D enic->vf_admin_mac_pending && + enic->vf_admin_mac_work_enabled; + if (pending) { + ether_addr_copy(policy, enic->vf_admin_mac); + generation =3D enic->vf_admin_mac_generation; + zero_policy =3D is_zero_ether_addr(policy); + if (zero_policy && enic->vf_admin_mac_random_valid) + ether_addr_copy(selected, + enic->vf_admin_mac_random_addr); + } + spin_unlock_bh(&enic->vf_admin_mac_lock); + if (!pending) + goto unlock; + + if (zero_policy && !READ_ONCE(enic->vf_admin_mac_random_valid)) { + eth_random_addr(selected); + spin_lock_bh(&enic->vf_admin_mac_lock); + if (!enic->vf_admin_mac_pending || + !enic->vf_admin_mac_work_enabled || + enic->vf_admin_mac_generation !=3D generation || + !is_zero_ether_addr(enic->vf_admin_mac)) { + stale =3D true; + } else if (enic->vf_admin_mac_random_valid) { + ether_addr_copy(selected, + enic->vf_admin_mac_random_addr); + } else { + ether_addr_copy(enic->vf_admin_mac_random_addr, selected); + enic->vf_admin_mac_random_valid =3D true; + } + spin_unlock_bh(&enic->vf_admin_mac_lock); + if (stale) + goto unlock; + } else if (!zero_policy) { + ether_addr_copy(selected, policy); + } + + if (!netif_device_present(enic->netdev) || + !READ_ONCE(enic->vf_registered)) { + err =3D -EAGAIN; + goto unlock; + } + + /* A nonzero notification describes a station address the PF has already + * installed. Zero delegates selection to the VF, which registers the + * selected random address through the policy-safe replacement request. + */ + if (!READ_ONCE(enic->vf_datapath_open)) { + station_installed =3D !zero_policy && + (!enic->vf_station_addr_valid || + ether_addr_equal(enic->vf_station_addr, selected)); + } else if (zero_policy) { + mutated =3D true; + err =3D enic_vf_station_addr_replace(enic, selected); + station_installed =3D !err; + } else if (enic->vf_station_addr_valid && + !ether_addr_equal(enic->vf_station_addr, selected)) { + mutated =3D true; + err =3D enic_vf_station_addr_del(enic); + station_installed =3D !err; + } else { + station_installed =3D true; + } + if (err) + goto unlock; + + spin_lock_bh(&enic->vf_admin_mac_lock); + if (!enic->vf_admin_mac_pending || + !enic->vf_admin_mac_work_enabled || + enic->vf_admin_mac_generation !=3D generation) { + stale =3D true; + /* If policy changed while the request was running, the completed + * station change may no longer match it. Record recovery while holding + * vf_admin_mac_lock, which also protects VF worker shutdown. Teardown + * therefore either suppresses the update or sees it first, and already + * uses unregister/register to clear old state. + */ + if (mutated && enic->vf_admin_mac_work_enabled) + enic_vf_station_recovery_required(enic); + } else { + enic->vf_admin_mac_pending =3D false; + enic->vf_admin_mac_retries =3D 0; + enic->vf_admin_mac_recovery_attempted =3D false; + if (!zero_policy) + enic->vf_admin_mac_random_valid =3D false; + } + spin_unlock_bh(&enic->vf_admin_mac_lock); + if (stale) + goto unlock; + + changed =3D !ether_addr_equal(enic->netdev->dev_addr, selected); + enic_vf_station_sync_reset(enic); + eth_hw_addr_set(enic->netdev, selected); + if (!zero_policy || changed) + enic->netdev->addr_assign_type =3D zero_policy ? + NET_ADDR_RANDOM : NET_ADDR_SET; + ether_addr_copy(enic->mac_addr, selected); + if (station_installed) + enic_vf_station_addr_set(enic, selected); + enic_vf_station_sync_reset(enic); + if (enic->netdev->reg_state =3D=3D NETREG_REGISTERED && changed) + call_netdevice_notifiers(NETDEV_CHANGEADDR, enic->netdev); + netdev_info(enic->netdev, "MBOX: admin MAC set to %pM\n", selected); + +unlock: + rtnl_unlock(); + + spin_lock_bh(&enic->vf_admin_mac_lock); + if (err && enic->vf_admin_mac_pending && + enic->vf_admin_mac_work_enabled && + generation =3D=3D enic->vf_admin_mac_generation && + !READ_ONCE(enic->mbox_send_disabled)) { + if (enic->vf_admin_mac_retries < + ENIC_VF_ADMIN_MAC_MAX_RETRIES) { + enic->vf_admin_mac_retries++; + reschedule =3D true; + delay =3D msecs_to_jiffies(ENIC_VF_ADMIN_MAC_RETRY_MS); + } else if (!enic->vf_admin_mac_recovery_attempted) { + enic->vf_admin_mac_recovery_attempted =3D true; + /* vf_admin_mac_lock also protects VF worker shutdown. */ + enic_vf_station_recovery_required(enic); + } + } + if (enic->vf_admin_mac_pending && + enic->vf_admin_mac_work_enabled && + generation !=3D enic->vf_admin_mac_generation) { + reschedule =3D true; + delay =3D 0; + } + pending =3D enic->vf_admin_mac_pending; + spin_unlock_bh(&enic->vf_admin_mac_lock); + if (pending && reschedule) + mod_delayed_work(system_wq, &enic->vf_admin_mac_work, delay); +} + +void enic_vf_admin_mac_notify(struct enic *enic, const u8 *addr) +{ + bool new_policy; + + spin_lock_bh(&enic->vf_admin_mac_lock); + new_policy =3D !enic->vf_admin_mac_pending || + !ether_addr_equal(enic->vf_admin_mac, addr); + if (new_policy) { + ether_addr_copy(enic->vf_admin_mac, addr); + if (!is_zero_ether_addr(addr)) + enic->vf_admin_mac_random_valid =3D false; + enic->vf_admin_mac_pending =3D true; + enic->vf_admin_mac_generation++; + enic->vf_admin_mac_retries =3D 0; + enic->vf_admin_mac_recovery_attempted =3D false; + } + if (new_policy && enic->vf_admin_mac_work_enabled) + mod_delayed_work(system_wq, &enic->vf_admin_mac_work, 0); + spin_unlock_bh(&enic->vf_admin_mac_lock); +} + +void enic_vf_admin_mac_quiesce(struct enic *enic) +{ + spin_lock_bh(&enic->vf_admin_mac_lock); + enic->vf_admin_mac_work_enabled =3D false; + enic->vf_admin_mac_generation++; + spin_unlock_bh(&enic->vf_admin_mac_lock); + cancel_delayed_work_sync(&enic->vf_admin_mac_work); +} + +void enic_vf_admin_mac_rearm(struct enic *enic) +{ + spin_lock_bh(&enic->vf_admin_mac_lock); + enic->vf_admin_mac_work_enabled =3D true; + if (enic->vf_admin_mac_pending) + mod_delayed_work(system_wq, &enic->vf_admin_mac_work, 0); + spin_unlock_bh(&enic->vf_admin_mac_lock); +} + +void enic_vf_admin_mac_purge(struct enic *enic) +{ + enic_vf_admin_mac_quiesce(enic); + spin_lock_bh(&enic->vf_admin_mac_lock); + enic->vf_admin_mac_pending =3D false; + enic->vf_admin_mac_random_valid =3D false; + enic->vf_admin_mac_recovery_attempted =3D false; + enic->vf_admin_mac_retries =3D 0; + enic->vf_admin_mac_generation++; + spin_unlock_bh(&enic->vf_admin_mac_lock); +} + +/* CMD_GET_MAC_ADDR is the PF-owned policy store. Re-read it after REGISTE= R so + * a lost notification cannot leave the VF using stale administrative poli= cy. + */ +static int enic_vf_admin_mac_refresh(struct enic *enic) +{ + u8 previous_policy[ETH_ALEN]; + u8 random_addr[ETH_ALEN]; + u8 selected[ETH_ALEN]; + u8 policy[ETH_ALEN]; + u32 generation; + bool apply_policy; + bool changed; + bool pending; + bool policy_changed; + bool zero_policy; + unsigned int attempt; + int err; + + for (attempt =3D 0; attempt < ENIC_VF_ADMIN_MAC_REFRESH_RETRIES; + attempt++) { + spin_lock_bh(&enic->vf_admin_mac_lock); + generation =3D enic->vf_admin_mac_generation; + pending =3D enic->vf_admin_mac_pending; + ether_addr_copy(previous_policy, enic->vf_admin_mac); + spin_unlock_bh(&enic->vf_admin_mac_lock); + + err =3D enic_dev_get_mac_addr(enic, policy); + if (err) + return err; + if (is_multicast_ether_addr(policy)) + return -EADDRNOTAVAIL; + + zero_policy =3D is_zero_ether_addr(policy); + if (zero_policy) + eth_random_addr(random_addr); + + spin_lock_bh(&enic->vf_admin_mac_lock); + if (enic->vf_admin_mac_generation !=3D generation) { + spin_unlock_bh(&enic->vf_admin_mac_lock); + continue; + } + + policy_changed =3D !ether_addr_equal(previous_policy, policy); + apply_policy =3D pending || policy_changed || + !is_valid_ether_addr(enic->netdev->dev_addr); + if (!apply_policy) { + ether_addr_copy(selected, enic->netdev->dev_addr); + if (zero_policy) { + ether_addr_copy(enic->vf_admin_mac_random_addr, + selected); + enic->vf_admin_mac_random_valid =3D true; + } + } else if (zero_policy && enic->vf_admin_mac_random_valid) { + ether_addr_copy(selected, + enic->vf_admin_mac_random_addr); + } else if (zero_policy) { + ether_addr_copy(selected, random_addr); + ether_addr_copy(enic->vf_admin_mac_random_addr, selected); + enic->vf_admin_mac_random_valid =3D true; + } else { + ether_addr_copy(selected, policy); + enic->vf_admin_mac_random_valid =3D false; + } + ether_addr_copy(enic->vf_admin_mac, policy); + enic->vf_admin_mac_pending =3D false; + enic->vf_admin_mac_recovery_attempted =3D false; + enic->vf_admin_mac_retries =3D 0; + enic->vf_admin_mac_generation++; + spin_unlock_bh(&enic->vf_admin_mac_lock); + + changed =3D !ether_addr_equal(enic->netdev->dev_addr, selected); + eth_hw_addr_set(enic->netdev, selected); + if (!zero_policy || changed) + enic->netdev->addr_assign_type =3D zero_policy ? + NET_ADDR_RANDOM : NET_ADDR_SET; + ether_addr_copy(enic->mac_addr, selected); + if (changed && enic->netdev->reg_state =3D=3D NETREG_REGISTERED) + call_netdevice_notifiers(NETDEV_CHANGEADDR, + enic->netdev); + + return 0; + } + + return -EAGAIN; +} + static int enic_set_mac_address_dynamic(struct net_device *netdev, void *p) { struct enic *enic =3D netdev_priv(netdev); @@ -1072,6 +1527,45 @@ static int enic_set_mac_address_dynamic(struct net_d= evice *netdev, void *p) char *addr =3D saddr->sa_data; int err; =20 + if (enic_is_sriov_vf_v2(enic)) { + if (!is_valid_ether_addr(addr)) + return -EADDRNOTAVAIL; + if (ether_addr_equal(addr, netdev->dev_addr)) + return 0; + + if (enic->vf_datapath_open && + !READ_ONCE(enic->vf_registered)) + return -ENODEV; + + /* An internal reset keeps IFF_UP set while its failed reopen leaves + * the datapath closed. Cache the requested address in that state; the + * next successful open installs it together with the datapath. + */ + if (!enic->vf_datapath_open) { + err =3D enic_set_mac_addr(netdev, addr); + if (!err) + enic_vf_admin_mac_cache_selected(enic, addr); + return err; + } + + /* Keep the old software address visible until the complete station + * replacement proves convergence. + */ + err =3D enic_vf_station_addr_replace(enic, addr); + if (err) + return err; + + enic_vf_station_sync_reset(enic); + err =3D enic_set_mac_addr(netdev, addr); + if (!err) { + enic_vf_station_addr_set(enic, addr); + enic_vf_station_sync_reset(enic); + enic_vf_admin_mac_cache_selected(enic, addr); + } + + return err; + } + if (netif_running(enic->netdev)) { err =3D enic_dev_del_station_addr(enic); if (err) @@ -1726,6 +2220,7 @@ static int enic_admin_chan_reopen(struct enic *enic); static int enic_open(struct net_device *netdev) { struct enic *enic =3D netdev_priv(netdev); + bool vf_mac_added =3D false; unsigned int i; int err, ret; unsigned int max_pkt_len =3D netdev->mtu + VLAN_ETH_HLEN; @@ -1807,6 +2302,25 @@ static int enic_open(struct net_device *netdev) if (!enic_is_dynamic(enic) && !enic_is_sriov_vf(enic)) enic_dev_add_station_addr(enic); =20 + if (enic_is_sriov_vf_v2(enic)) { + if (!READ_ONCE(enic->vf_registered)) { + netdev_err(netdev, "VF is not registered with its PF\n"); + err =3D -ENODEV; + goto err_out_disable_wq; + } + + err =3D enic_vf_station_addr_replace(enic, netdev->dev_addr); + if (err) { + netdev_err(netdev, + "Failed to register VF station address: %d\n", + err); + goto err_out_disable_wq; + } + enic_vf_station_addr_set(enic, netdev->dev_addr); + enic_vf_station_sync_reset(enic); + vf_mac_added =3D true; + } + enic_set_rx_mode(netdev); =20 netif_tx_wake_all_queues(netdev); @@ -1864,6 +2378,15 @@ static int enic_open(struct net_device *netdev) for (i =3D 0; i < enic->wq_count; i++) napi_disable(&enic->napi[enic_cq_wq(enic, i)]); netif_tx_disable(netdev); +err_out_disable_wq: + if (vf_mac_added) { + ret =3D enic_vf_station_addr_del(enic); + if (ret) { + netdev_warn(netdev, + "Failed to remove VF station address during open rollback: %d\n", + ret); + } + } if (!enic_is_dynamic(enic) && !enic_is_sriov_vf(enic)) enic_dev_del_station_addr(enic); for (i =3D 0; i < enic->wq_count; i++) @@ -1898,7 +2421,6 @@ static int __enic_stop(struct net_device *netdev, boo= l remove_vf_station) */ if (enic_is_sriov_vf_v2(enic) && !enic->vf_datapath_open) return 0; - (void)remove_vf_station; =20 for (i =3D 0; i < enic->intr_count; i++) { vnic_intr_mask(&enic->intr[i]); @@ -1923,6 +2445,15 @@ static int __enic_stop(struct net_device *netdev, bo= ol remove_vf_station) for (i =3D 0; i < enic->wq_count; i++) napi_disable(&enic->napi[enic_cq_wq(enic, i)]); netif_tx_disable(netdev); + if (remove_vf_station && enic_is_sriov_vf_v2(enic) && + READ_ONCE(enic->vf_registered)) { + err =3D enic_vf_station_addr_del(enic); + if (err) { + netdev_warn(netdev, + "Failed to remove VF station address: %d\n", + err); + } + } =20 if (!enic_is_dynamic(enic) && !enic_is_sriov_vf(enic)) enic_dev_del_station_addr(enic); @@ -2321,9 +2852,18 @@ static int enic_admin_chan_reopen(struct enic *enic) return err; } enic_reset_addr_lists(enic); + enic->vf_station_addr_valid =3D false; + err =3D enic_vf_admin_mac_refresh(enic); + if (err) { + netdev_err(enic->netdev, + "Failed to refresh VF admin MAC after reset: %d\n", + err); + enic_admin_channel_close(enic); + return err; + } /* Capability negotiation and registration establish a new protocol - * generation. RX remains quarantined until enic_open() replays the - * station and receive policy. + * generation after authoritative MAC policy is refreshed. RX remains + * quarantined until enic_open() replays station and receive policy. */ spin_lock_bh(&enic->mbox_state_lock); if (enic->vf_mbox_fault_generation !=3D recovery_generation || @@ -2339,6 +2879,7 @@ static int enic_admin_chan_reopen(struct enic *enic) enic_admin_channel_close(enic); return err; } + enic_vf_admin_mac_rearm(enic); } else { /* The link came back up during enic_open() above while MBOX * sends were still disabled (channel not yet reopened), so that @@ -3322,6 +3863,9 @@ static int enic_probe(struct pci_dev *pdev, const str= uct pci_device_id *ent) * cancel_work_sync()) would otherwise act on an uninitialised work. */ INIT_WORK(&enic->link_notify_work, enic_link_notify_work_handler); + spin_lock_init(&enic->vf_admin_mac_lock); + INIT_DELAYED_WORK(&enic->vf_admin_mac_work, + enic_vf_admin_mac_work); =20 /* V2 VF: open admin channel and register with PF. * Must happen before register_netdev so the VF is fully @@ -3351,6 +3895,12 @@ static int enic_probe(struct pci_dev *pdev, const st= ruct pci_device_id *ent) "MBOX VF registration failed: %d\n", err); goto err_out_admin_close; } + err =3D enic_vf_admin_mac_refresh(enic); + if (err) { + dev_err(dev, + "MBOX VF admin MAC refresh failed: %d\n", err); + goto err_out_admin_close; + } } =20 netif_set_real_num_tx_queues(netdev, enic->wq_count); @@ -3476,6 +4026,8 @@ static int enic_probe(struct pci_dev *pdev, const str= uct pci_device_id *ent) dev_err(dev, "Cannot register net device, aborting\n"); goto err_out_admin_close; } + if (enic_is_sriov_vf_v2(enic)) + enic_vf_admin_mac_rearm(enic); =20 return 0; =20 @@ -3531,9 +4083,13 @@ static void enic_remove(struct pci_dev *pdev) =20 /* Close the admin channel and unregister from the PF before * unregister_netdev() to prevent a late PF notification from - * touching a netdev that is being torn down. + * touching a netdev that is being torn down. VF_UNREGISTER is the + * protocol teardown operation: the PF removes all VF-requested + * configuration, including the station address, before replying. */ if (enic_is_sriov_vf_v2(enic)) { + enic_vf_admin_mac_quiesce(enic); + if (READ_ONCE(enic->vf_registered)) { int unreg_err =3D enic_mbox_vf_unregister(enic); =20 @@ -3552,6 +4108,8 @@ static void enic_remove(struct pci_dev *pdev) * enic_link_check() scheduled it just as SR-IOV was disabled. */ cancel_work_sync(&enic->link_notify_work); + if (enic_is_sriov_vf_v2(enic)) + enic_vf_admin_mac_purge(enic); #ifdef CONFIG_PCI_IOV if (enic_sriov_enabled(enic)) { if (enic->vf_type =3D=3D ENIC_VF_TYPE_V2) diff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.c b/drivers/net/ethe= rnet/cisco/enic/enic_mbox.c index 3f73072170b9..1c1d087fa966 100644 --- a/drivers/net/ethernet/cisco/enic/enic_mbox.c +++ b/drivers/net/ethernet/cisco/enic/enic_mbox.c @@ -861,6 +861,25 @@ static void enic_mbox_vf_handle_link_state(struct enic= *enic, void *payload, ret_major); } =20 +static void enic_mbox_vf_handle_admin_mac(struct enic *enic, void *payload, + u64 msg_num) +{ + struct enic_mbox_pf_set_admin_mac_notif_msg *notif =3D payload; + u16 ret_major =3D 0; + + if (is_multicast_ether_addr(notif->mac_addr)) { + netdev_warn(enic->netdev, + "MBOX: rejecting multicast admin MAC %pM\n", + notif->mac_addr); + ret_major =3D ENIC_MBOX_ERR_GENERIC; + } else { + enic_vf_admin_mac_notify(enic, notif->mac_addr); + } + + enic_mbox_vf_queue_ack(enic, ENIC_MBOX_PF_SET_ADMIN_MAC_ACK, + msg_num, ret_major); +} + void enic_mbox_vf_link_state_reset(struct enic *enic) { spin_lock_bh(&enic->vf_link_state_lock); @@ -985,6 +1004,17 @@ static void enic_mbox_vf_process_msg(struct enic *eni= c, enic_mbox_vf_handle_link_state(enic, payload, msg_num); break; } + case ENIC_MBOX_PF_SET_ADMIN_MAC_NOTIF: { + size_t exp =3D sizeof(struct enic_mbox_pf_set_admin_mac_notif_msg); + + if (!enic_mbox_vf_payload_ok(enic, hdr->msg_type, + payload_len, exp)) { + enic_mbox_vf_malformed_msg(enic, hdr->msg_type, msg_num); + return; + } + enic_mbox_vf_handle_admin_mac(enic, payload, msg_num); + break; + } case ENIC_MBOX_VF_ADD_DEL_MAC_REPLY: enic_mbox_vf_handle_add_del_mac_reply(enic, payload, payload_len, msg_num); --=20 2.43.0 From nobody Thu Sep 24 17:56:23 2026 Received: from rcdn-iport-2.cisco.com (rcdn-iport-2.cisco.com [173.37.86.73]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0784E4477E5; Mon, 21 Sep 2026 20:01:34 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=173.37.86.73 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790020899; cv=none; b=jLrtiCQfWcTcygAk1/UNSASMSpPlFCYtRk6FIsus0JUcnFLL3Xyrlm+qOpwWPfSN0nAVyTGvrmXam7Aqdg/oYfZNNuxM2k3i/ulwv8h/B2noK06UNBY9B3mhf1IkGYP1z5IVY43/Vp1LPH6LmbCuDGppvHc30+cLfOr0PChPIH0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790020899; c=relaxed/simple; bh=97AQflgcrISq9kq6KIsEFL3PJhF/AZZXOndehlbI310=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=aDzV0A8Ku8LZ62N2yf8h9bgLfGf9brWUr3nD0BBYTrDgZ+KcGF9tBLyUQa2zUpJLZlxLav5w+HW3w52zKq4A+Z1rcTplSm14uOpElJNNQnhHSSD39dPqXxM8b9CieZL3bLj+3Wh/89vFd0z8meh8EUNH3ljFTL9ZmpKn3ZLhNlo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=cisco.com; spf=pass smtp.mailfrom=cisco.com; dkim=pass (2048-bit key) header.d=cisco.com header.i=@cisco.com header.b=RPeIxWpk; arc=none smtp.client-ip=173.37.86.73 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=cisco.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=cisco.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=cisco.com header.i=@cisco.com header.b="RPeIxWpk" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cisco.com; i=@cisco.com; l=21904; q=dns/txt; s=iport01; t=1790020895; x=1791230495; h=from:date:subject:mime-version:content-transfer-encoding: message-id:references:in-reply-to:to:cc; bh=4bJqR62WhhlN4A4Mw0qPqB+p2WmSAOX2OVh36VPQe8A=; b=RPeIxWpkv18VkWr9Bl1rzg3kx0NPdNaPVJ7lF61GFjKpvDap1oKvmc0X fqBiePuTeusgvk0R40jwGeumF6WJ4b/9QCcO2DcYQGKQHfgyTnl7Mky1y jtfFRvirV0nLERVCbo+upwuRlRp1ql5eCCVx2ywlizhieZ3wpPYsV3wTF PBC8NK3PV9MwiFva+LevQRO8h1g13Vr2RGQF6dap3ik3cFUCeGN3RZEod ksziQREoBsmh2gBRhBSXAZaSFbyg0XmrwX4Bnww5n7EfooynvltgO59/0 VijFQfD/ZgtobYZG4M5uWioeRweoKFLRNefgjUXQn6KLQA1UM8KXE2F2Y g==; X-CSE-ConnectionGUID: 6eRiGjFbTVS0cJq5R4KoTw== X-CSE-MsgGUID: 2MbnUD+4RQ6K9LwvdkwxRA== X-IPAS-Result: =?us-ascii?q?A0A9AAAnjLFq/47/Ja1aHQEBAQEJARIBBQUBgX0HAQsBg?= =?us-ascii?q?laBVUNJhFePUoIhkU2MURSBag8BAQEPUQQBAYUFAo4HAiY1CA4BAgQDAgMBA?= =?us-ascii?q?QEBAQEBAQEBAQEKAQEFAQEBAgEHBYEOE4ZckBMCAQMaCQRAEhAlAiYCAkYQB?= =?us-ascii?q?gESgwKCdQPFbHp/M4EBhH7ZSoFoAQsUAYEKLgGFcIJtAYFwhAg7hEEnG4FJR?= =?us-ascii?q?IEVg2mEGwQLg3SCagSDHBKCZoczikEJP4ECHANZLAFVEw0KCwcFYVIzAyAKC?= =?us-ascii?q?xISGBUCFB0SDwQWMh1wDCcSLBcvWBsGBYEdfBQEE4IaIxk2eoEJXoErKWABE?= =?us-ascii?q?BeBB4IIAoJUgX8CAUlDDgdFUwknQQoSRykiCBIJARMaMAuBJxgNSBEsNwYPG?= =?us-ascii?q?QQ9AW4Hj14fgWoBUR8BAQ0fLRsPC3tBHRUOFxUFAQEtAUWSVwwdJYM8jV9rg?= =?us-ascii?q?TWfWoQogWagEDOEBJNjkwaZCCOCNqETBg8JAQJNhGmBagE5gVkzGggbFTuCZ?= =?us-ascii?q?1MZD44uFpMdAbx7JzI9AQEHAgcOAwuBaJAAAQQMF4FWAQE?= IronPort-Data: A9a23:BtUcRari4ThduIyGt2OuH24XJd5eBmLKZBIvgKrLsJaIsI4StFCzt garIBmBPKzZZDfzLowgaYW28hkB75XXm9ViSwJp+Hw8Qi5A+OPIVI+TRqvS04x+DSFioGZPt Zh2hgzodZhsJpPkjk7zdOWn9T8jhfngqoPUUIbsIjp2SRJvVBAvgBdin/9RqoNziLBVOSvV0 T/Ji5OZYgDNNwJcaDpOtfrS8ko35pwehRtB1rAATaET1LPhvyF94KI3fcmZM3b+S49IKe+2L 86r5K255G7Q4yA2AdqjlLvhGmVSKlIFFVHT4pb+c/HKbilq/kTe4I5iXBYvQRs/ZwGyojxE4 I4lWapc5useFvakdOw1C3G0GszlVEFM0OevzXOX6aR/w6BaGpfh660GMa04AWEX0rh6O2Bh9 9ISFA42Nhuhnr+MnJG8dvY506zPLOGzVG8ekmtrwTecCbMtRorOBv2Xo9RZxzw3wMtJGJ4yZ eJANmEpN0uGOUASfA5LUvrSn8/w7pX7WyZRtFOOpak3y2PS1wd2lrPqNbI5f/TXHJwLzx3B/ TquE2LRWDQQMuGzima/yyyP18vQgy/4R5IoPejtnhJtqBjJroAJMzUaXEW2pNG1g1CzXtZYJ VBS/CcyxYA/+FGuR8vwQzW3p3mLuhNaUN1Ve8U67xuI0YLX7hyfC2xCSSROAPQlqcU/bT8nz FmEm5XuHzMHmLSTRWiQ6fSSoC++NDY9KXIEY2kPTWMt+9DprYcypgjCQtZqDOi+ididMTXxx S2a6SsznbMeieYV2Kihu1PKmTShot7OVAFdzgPaQm6o8Ctna4O/IY+l817W6bBHNonxc7Wal GIPl87b6KUFCouA0XTQBu4MB7quof2CNVUwnGJSInXozBz1k1bLQGyayGsWyJtBWircRQLUX Q== IronPort-HdrOrdr: A9a23:iWt0Y66y8Yn/ZfCucwPXwD7XdLJyesId70hD6qm+c20wTiX+ra GTdZUguyMc5wx9ZJhNo7q90ey7MBHhHP1OjrX5Q43DYOCOggLBR72Kr7GSoAEIcBeTygcy79 YET4FOTPD9ElR+i9/3+02bH8ZI+qj/zImYwcrT0HtpSxhncOVb7wl/AhuGCUEefnghOXNALu v62uN34xy9ZH8QcsO3QlMBX+TFupn3saiOW29/O/bigzP+9w9BL9XBYmOl4is= X-Talos-CUID: 9a23:IWqeVWz4wtcD/Gd0pXaRBgVKF/okXX/zxkvuGBezK380cuG2TmOPrfY= X-Talos-MUID: 9a23:DfhvKQoR359KdX0+6Ukez2tHPsFVz+fyMnlTssQipNeINjNgAR7I2Q== X-IronPort-Anti-Spam-Filtered: true X-IronPort-AV: E=Sophos;i="6.27,115,1787011200"; d="scan'208";a="511693130" Received: from rcdn-l-core-05.cisco.com ([173.37.255.142]) by rcdn-iport-2.cisco.com with ESMTP/TLS/TLS_AES_256_GCM_SHA384; 21 Sep 2026 20:00:26 +0000 Received: from [127.0.0.1] (unknown [10.25.128.208]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by rcdn-l-core-05.cisco.com (Postfix) with ESMTPS id 7B3421800035F; Mon, 21 Sep 2026 20:00:25 +0000 (GMT) From: Satish Kharat Date: Mon, 21 Sep 2026 12:59:49 -0700 Subject: [PATCH net-next 6/6] enic: configure V2 VF receive mode over mailbox Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260921-b4-enic-sriov-v2-vf-receive-control-v1-6-67a0a6e08d43@cisco.com> References: <20260921-b4-enic-sriov-v2-vf-receive-control-v1-0-67a0a6e08d43@cisco.com> In-Reply-To: <20260921-b4-enic-sriov-v2-vf-receive-control-v1-0-67a0a6e08d43@cisco.com> To: Andrew Lunn , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, Satish Kharat , sebaddel@cisco.com X-Mailer: b4 0.15.2 X-Developer-Signature: v=1; a=ed25519-sha256; t=1790020820; l=22763; i=satishkh@cisco.com; h=from:subject:message-id; bh=97AQflgcrISq9kq6KIsEFL3PJhF/AZZXOndehlbI310=; b=A94ebD6OsA/IJG7Zk310tz0ubS469eyk/XJ9EsGYDeLxnzQE+8qq7YKXzFyPKrxjSkDJB368k tq4beHfEiCmAwZSFz/oyiIecKy3zbRLMypWDywp6v/dkR36vHoTLCWL X-Developer-Key: i=satishkh@cisco.com; a=ed25519; pk=4BxbLoCGvHeIMH/PbpiNVx8/2IcTjFvqcr0AD8K6WLY= X-Outbound-Client-TLS: ANONYMOUS;unknown [10.25.128.208];TLSv1.3;TLS_AES_256_GCM_SHA384;256 X-Outbound-SMTP-Client: 10.25.128.208, [10.25.128.208] X-Outbound-Node: rcdn-l-core-05.cisco.com V2 VFs cannot program receive filters directly. Add an asynchronous receive-mode callback that sends individual unicast and multicast address changes and packet-filter settings through the VF's one-request-at-a-time mailbox path. Keep each callback to at most one VF mailbox request. Enable newly required broad receive coverage before changing individual-address filters, remove stale broad coverage before replaying a finite address list, and schedule a fresh callback for the second half of a successful two-step transition. Use the netdev address-list synchronization state to batch operations: sync_cnt =3D=3D 0 identifies a new entry, refcount > sync_cnt identifies an address that is still requested, and refcount =3D=3D sync_cnt identifies an installed entry which has become stale. The asynchronous netdev core reconciles the resulting sync_cnt changes from the snapshot back to the live list. Track detailed per-address results so idempotent outcomes converge, stable policy denials do not consume the retry budget, and failed deletes or contradictory replies require a new VF registration. Keep the station address out of the secondary-unicast list. Select the asynchronous netdev operations only for V2 VFs and detach the device before mailbox teardown so no receive-mode callback can race removal. Assisted-by: LLM Signed-off-by: Satish Kharat --- drivers/net/ethernet/cisco/enic/enic.h | 4 + drivers/net/ethernet/cisco/enic/enic_main.c | 499 ++++++++++++++++++++++++= +++- 2 files changed, 490 insertions(+), 13 deletions(-) diff --git a/drivers/net/ethernet/cisco/enic/enic.h b/drivers/net/ethernet/= cisco/enic/enic.h index 0f0ef68130f2..a20ca8fa7927 100644 --- a/drivers/net/ethernet/cisco/enic/enic.h +++ b/drivers/net/ethernet/cisco/enic/enic.h @@ -382,6 +382,10 @@ struct enic { */ struct enic_mac_addr *mbox_reply_mac_addrs; u16 mbox_reply_mac_count; + u16 vf_pkt_filter_requested; + u16 vf_pkt_filter_applied; + bool vf_pkt_filter_valid; + bool vf_pkt_filter_update_pending; bool mbox_initialized; =20 /* PF: per-VF MBOX state, allocated when SRIOV V2 is enabled */ diff --git a/drivers/net/ethernet/cisco/enic/enic_main.c b/drivers/net/ethe= rnet/cisco/enic/enic_main.c index 16412c29e932..1cc1935ee5c2 100644 --- a/drivers/net/ethernet/cisco/enic/enic_main.c +++ b/drivers/net/ethernet/cisco/enic/enic_main.c @@ -1051,6 +1051,8 @@ void enic_reset_addr_lists(struct enic *enic) enic->mc_count =3D 0; enic->uc_count =3D 0; enic->flags =3D 0; + enic->vf_pkt_filter_valid =3D false; + enic->vf_pkt_filter_update_pending =3D false; } =20 static int enic_set_mac_addr(struct net_device *netdev, char *addr) @@ -1348,8 +1350,12 @@ static void enic_vf_admin_mac_work(struct work_struc= t *work) if (station_installed) enic_vf_station_addr_set(enic, selected); enic_vf_station_sync_reset(enic); - if (enic->netdev->reg_state =3D=3D NETREG_REGISTERED && changed) - call_netdevice_notifiers(NETDEV_CHANGEADDR, enic->netdev); + if (enic->netdev->reg_state =3D=3D NETREG_REGISTERED) { + if (changed) + call_netdevice_notifiers(NETDEV_CHANGEADDR, + enic->netdev); + netif_rx_mode_schedule_fresh(enic->netdev); + } netdev_info(enic->netdev, "MBOX: admin MAC set to %pM\n", selected); =20 unlock: @@ -1561,6 +1567,7 @@ static int enic_set_mac_address_dynamic(struct net_de= vice *netdev, void *p) enic_vf_station_addr_set(enic, addr); enic_vf_station_sync_reset(enic); enic_vf_admin_mac_cache_selected(enic, addr); + netif_rx_mode_schedule_fresh(netdev); } =20 return err; @@ -1603,17 +1610,412 @@ static int enic_set_mac_address(struct net_device = *netdev, void *p) return enic_dev_add_station_addr(enic); } =20 +static u16 enic_rx_mode_to_pkt_filter(struct net_device *netdev, + unsigned int uc_count, + unsigned int mc_count) +{ + u16 flags =3D CMD_PFILTER_DIRECTED; + + if (netdev->flags & IFF_MULTICAST) + flags |=3D CMD_PFILTER_MULTICAST; + if (netdev->flags & IFF_BROADCAST) + flags |=3D CMD_PFILTER_BROADCAST; + if ((netdev->flags & IFF_PROMISC) || + uc_count > ENIC_UNICAST_PERFECT_FILTERS) + flags |=3D CMD_PFILTER_PROMISCUOUS; + if ((netdev->flags & IFF_ALLMULTI) || + mc_count > ENIC_MULTICAST_PERFECT_FILTERS) + flags |=3D CMD_PFILTER_ALL_MULTICAST; + + return flags; +} + +static int enic_vf_set_pkt_filter(struct enic *enic, u16 flags, + u16 *applied_flags) +{ + u16 applied; + int err; + + /* The PF can change trust policy independently and thereby withdraw + * broad modes. Always refresh those requests so the returned applied + * flags remain authoritative. The other modes are not trust-gated and + * can be reused for ordinary address-list churn. + */ + if (!(flags & (CMD_PFILTER_PROMISCUOUS | + CMD_PFILTER_ALL_MULTICAST)) && + enic->vf_pkt_filter_valid && + enic->vf_pkt_filter_requested =3D=3D flags) { + if (applied_flags) + *applied_flags =3D enic->vf_pkt_filter_applied; + return 0; + } + + err =3D enic_mbox_vf_set_pkt_filter(enic, + !!(flags & CMD_PFILTER_DIRECTED), + !!(flags & CMD_PFILTER_MULTICAST), + !!(flags & CMD_PFILTER_BROADCAST), + !!(flags & CMD_PFILTER_PROMISCUOUS), + !!(flags & CMD_PFILTER_ALL_MULTICAST), + &applied); + if (err) + return err; + + enic->vf_pkt_filter_requested =3D flags; + enic->vf_pkt_filter_applied =3D applied; + enic->vf_pkt_filter_valid =3D true; + if (applied_flags) + *applied_flags =3D applied; + + return 0; +} + +static void enic_vf_report_pkt_filter_denial(struct net_device *netdev, + u16 requested, u16 applied) +{ + if ((requested & CMD_PFILTER_PROMISCUOUS) && + !(applied & CMD_PFILTER_PROMISCUOUS)) + netdev_dbg(netdev, "PF policy denied promiscuous receive mode\n"); + if ((requested & CMD_PFILTER_ALL_MULTICAST) && + !(applied & CMD_PFILTER_ALL_MULTICAST)) + netdev_dbg(netdev, + "PF policy denied all-multicast receive mode\n"); +} + +struct enic_vf_mac_op { + struct netdev_hw_addr *ha; + unsigned int *filter_count; +}; + +static unsigned int +enic_vf_addr_list_count(const struct enic *enic, + const struct netdev_hw_addr_list *list, + bool unicast) +{ + const struct netdev_hw_addr *ha; + unsigned int count =3D 0; + + netdev_hw_addr_list_for_each(ha, list) + if (ha->refcount > ha->sync_cnt && + !(unicast && enic->vf_station_addr_valid && + ether_addr_equal(ha->addr, enic->vf_station_addr))) + count++; + + return count; +} + +static int enic_vf_collect_mac_ops(struct enic *enic, + struct netdev_hw_addr_list *list, + bool unicast, + bool install_new, + unsigned int *filter_count, + struct enic_mac_addr *macs, + struct enic_vf_mac_op *ops, + u16 *num_ops) +{ + struct netdev_hw_addr *ha; + u16 pos =3D *num_ops; + + /* Deletes precede adds so a full perfect-filter table has room for a + * replacement address in the same transaction. + */ + netdev_hw_addr_list_for_each(ha, list) { + if (unicast && enic->vf_station_addr_valid && + ether_addr_equal(ha->addr, enic->vf_station_addr)) + continue; + if (!ha->sync_cnt || + ha->refcount !=3D ha->sync_cnt) + continue; + if (pos =3D=3D ENIC_MBOX_MAX_MAC_OPS) + return -E2BIG; + + ether_addr_copy(macs[pos].addr, ha->addr); + macs[pos].flags =3D 0; + ops[pos].ha =3D ha; + ops[pos].filter_count =3D filter_count; + pos++; + } + + if (!install_new) + goto done; + + netdev_hw_addr_list_for_each(ha, list) { + if (unicast && enic->vf_station_addr_valid && + ether_addr_equal(ha->addr, enic->vf_station_addr)) + continue; + if (ha->sync_cnt) + continue; + if (pos =3D=3D ENIC_MBOX_MAX_MAC_OPS) + return -E2BIG; + + ether_addr_copy(macs[pos].addr, ha->addr); + macs[pos].flags =3D cpu_to_le16(ENIC_MAC_ADDR_FLAG_ADD); + ops[pos].ha =3D ha; + ops[pos].filter_count =3D filter_count; + pos++; + } + +done: + *num_ops =3D pos; + return 0; +} + +static int enic_vf_sync_mac_filters(struct enic *enic, + struct netdev_hw_addr_list *uc, + struct netdev_hw_addr_list *mc, + bool install_uc, bool install_mc, + bool *sent) +{ + struct enic_vf_mac_op *ops; + struct enic_mac_addr *macs; + bool permanent_add =3D false; + bool reconnect =3D false; + bool retryable_add =3D false; + u16 num_ops =3D 0; + unsigned int i; + int err =3D 0; + + /* A class can contribute at most its installed perfect filters as + * deletes and its finite perfect-filter limit as adds. Keep the wire + * batch large enough for both classes so an update always fits in one + * transaction and -E2BIG remains only a defensive state-corruption + * check. + */ + BUILD_BUG_ON(ENIC_MBOX_MAX_MAC_OPS < + 2 * (ENIC_UNICAST_PERFECT_FILTERS + + ENIC_MULTICAST_PERFECT_FILTERS)); + + *sent =3D false; + macs =3D kcalloc(ENIC_MBOX_MAX_MAC_OPS, sizeof(*macs), GFP_KERNEL); + if (!macs) + return -ENOMEM; + ops =3D kcalloc(ENIC_MBOX_MAX_MAC_OPS, sizeof(*ops), GFP_KERNEL); + if (!ops) { + err =3D -ENOMEM; + goto free_macs; + } + + err =3D enic_vf_collect_mac_ops(enic, uc, true, install_uc, + &enic->uc_count, + macs, ops, &num_ops); + if (err) + goto free_ops; + err =3D enic_vf_collect_mac_ops(enic, mc, false, install_mc, + &enic->mc_count, + macs, ops, &num_ops); + if (err || !num_ops) + goto free_ops; + + /* The protocol carries all changed unicast and multicast addresses in + * one request. Besides matching the native ABI, this bounds time spent + * in the RTNL-held asynchronous receive-mode callback to one MAC reply. + */ + err =3D enic_mbox_vf_add_del_macs(enic, macs, num_ops); + if (err) + goto free_ops; + *sent =3D true; + + for (i =3D 0; i < num_ops; i++) { + u16 flags =3D le16_to_cpu(macs[i].flags); + bool add =3D flags & ENIC_MAC_ADDR_FLAG_ADD; + + if (flags & ENIC_MAC_ADDR_FLAG_SKIPPED) { + if (add) + retryable_add =3D true; + else + reconnect =3D true; + continue; + } + if (flags & ENIC_MAC_ADDR_FLAG_PERMANENT_MASK) { + if (add) + permanent_add =3D true; + else + reconnect =3D true; + continue; + } + + if (add) { + ops[i].ha->sync_cnt++; + ops[i].ha->refcount++; + (*ops[i].filter_count)++; + } else { + ops[i].ha->sync_cnt--; + ops[i].ha->refcount--; + if (WARN_ON_ONCE(!*ops[i].filter_count)) + err =3D -EIO; + else + (*ops[i].filter_count)--; + } + } + + /* A failed DELETE can leave hardware accepting an address no longer in + * the requested list. Registration is the fail-closed cleanup boundary. + * A skipped ADD changes no acceptance state and can use the bounded core + * retry path. Permanent ADD denials remain stable policy results. + */ + if (reconnect) { + enic_mbox_vf_require_reconnect(enic); + err =3D -EIO; + } else if (retryable_add) { + err =3D -EAGAIN; + } else if (permanent_add) { + err =3D -EACCES; + } + +free_ops: + kfree(ops); +free_macs: + kfree(macs); + return err; +} + +static int enic_set_vf_rx_mode(struct net_device *netdev, + struct netdev_hw_addr_list *uc, + struct netdev_hw_addr_list *mc) +{ + struct enic *enic =3D netdev_priv(netdev); + unsigned int uc_count =3D enic_vf_addr_list_count(enic, uc, true); + unsigned int mc_count =3D enic_vf_addr_list_count(enic, mc, false); + u16 flags =3D enic_rx_mode_to_pkt_filter(netdev, uc_count, mc_count); + bool uc_overflow =3D uc_count > ENIC_UNICAST_PERFECT_FILTERS; + bool mc_overflow =3D mc_count > ENIC_MULTICAST_PERFECT_FILTERS; + u16 broad_modes =3D CMD_PFILTER_PROMISCUOUS | + CMD_PFILTER_ALL_MULTICAST; + bool broad_enable_needed; + bool broad_withdrawal; + bool filter_needed; + bool sent; + u16 prefilter_flags; + u16 applied_flags; + int err; + + if (!READ_ONCE(enic->vf_registered)) + return -ENODEV; + + filter_needed =3D !enic->vf_pkt_filter_valid || + enic->vf_pkt_filter_requested !=3D flags || + (flags & broad_modes); + broad_enable_needed =3D + (!enic->vf_pkt_filter_valid && (flags & broad_modes)) || + (flags & broad_modes & ~enic->vf_pkt_filter_requested); + broad_withdrawal =3D enic->vf_pkt_filter_valid && + (enic->vf_pkt_filter_applied & broad_modes & ~flags); + if (broad_enable_needed) { + /* Establish newly required broad coverage before an independent + * exact-address rejection can block it. Retain any broad mode that + * is currently applied until its finite replacement is installed. + */ + prefilter_flags =3D flags; + if (enic->vf_pkt_filter_valid) + prefilter_flags |=3D enic->vf_pkt_filter_applied & broad_modes; + err =3D enic_vf_set_pkt_filter(enic, prefilter_flags, + &applied_flags); + if (err) + return err; + enic->vf_pkt_filter_update_pending =3D false; + enic_vf_report_pkt_filter_denial(netdev, flags, applied_flags); + netif_rx_mode_schedule_fresh(netdev); + return 0; + } + + /* A permanent exact-address rejection can leave an independent packet + * filter update pending. Give that update the next mailbox transaction, + * unless it would withdraw broad coverage before finite replacements are + * installed. A later receive-mode change can retry the rejected address. + */ + if (enic->vf_pkt_filter_update_pending) { + if (!filter_needed || broad_withdrawal) { + enic->vf_pkt_filter_update_pending =3D false; + } else { + err =3D enic_vf_set_pkt_filter(enic, flags, &applied_flags); + if (err) + return err; + enic->vf_pkt_filter_update_pending =3D false; + enic_vf_report_pkt_filter_denial(netdev, flags, applied_flags); + return 0; + } + } + + if (broad_withdrawal) { + /* Withdraw stale broad acceptance before installing its finite + * replacement. Keep this callback to one mailbox transaction and + * queue exact-filter reconciliation as fresh work. If that later + * reconciliation exhausts its retry budget, traffic may be dropped + * but PROMISC/ALLMULTI cannot remain enabled beyond the requested + * policy. + */ + err =3D enic_vf_set_pkt_filter(enic, flags, &applied_flags); + if (err) { + /* The previously applied broad mode may still be active. Do + * not rely on the core's bounded retry budget to narrow it. + * Fresh registration is the fail-closed policy boundary. A + * local send timeout is already terminal and cannot use ordinary + * reconnect recovery. + */ + if (!READ_ONCE(enic->mbox_tx_poisoned) && + !READ_ONCE(enic->vf_mbox_reconnect_required)) + enic_mbox_vf_require_reconnect(enic); + return err; + } + enic_vf_report_pkt_filter_denial(netdev, flags, applied_flags); + netif_rx_mode_schedule_fresh(netdev); + return 0; + } + + /* Keep the finite subset of exact filters already installed for an + * overflowing class. Broad mode covers the remaining addresses when PF + * policy permits it, while the independent finite class can still make + * progress. When a class becomes finite again, withdraw stale broad + * acceptance first, then schedule its exact-filter reconciliation. + */ + err =3D enic_vf_sync_mac_filters(enic, uc, mc, !uc_overflow, + !mc_overflow, &sent); + if (err =3D=3D -EACCES) { + /* Do not spend the core retry budget repeating an exact operation + * that the PF rejected permanently. If the independent packet + * policy still needs an update, queue one fresh callback for it. + */ + if (filter_needed) { + enic->vf_pkt_filter_update_pending =3D true; + netif_rx_mode_schedule_fresh(netdev); + } + return 0; + } + if (err) + return err; + + if (sent && filter_needed) { + /* The address transaction succeeded. Queue the policy half as a + * fresh update rather than consuming the failure-retry budget. + */ + netif_rx_mode_schedule_fresh(netdev); + return 0; + } + + if (filter_needed) { + err =3D enic_vf_set_pkt_filter(enic, flags, &applied_flags); + if (err) + return err; + } else { + applied_flags =3D enic->vf_pkt_filter_applied; + } + + enic_vf_report_pkt_filter_denial(netdev, flags, applied_flags); + + return 0; +} + /* netif_tx_lock held, BHs disabled */ static void enic_set_rx_mode(struct net_device *netdev) { struct enic *enic =3D netdev_priv(netdev); - int directed =3D 1; - int multicast =3D (netdev->flags & IFF_MULTICAST) ? 1 : 0; - int broadcast =3D (netdev->flags & IFF_BROADCAST) ? 1 : 0; - int promisc =3D (netdev->flags & IFF_PROMISC) || - netdev_uc_count(netdev) > ENIC_UNICAST_PERFECT_FILTERS; - int allmulti =3D (netdev->flags & IFF_ALLMULTI) || - netdev_mc_count(netdev) > ENIC_MULTICAST_PERFECT_FILTERS; + u16 filter_flags =3D enic_rx_mode_to_pkt_filter(netdev, + netdev_uc_count(netdev), + netdev_mc_count(netdev)); + int directed =3D !!(filter_flags & CMD_PFILTER_DIRECTED); + int multicast =3D !!(filter_flags & CMD_PFILTER_MULTICAST); + int broadcast =3D !!(filter_flags & CMD_PFILTER_BROADCAST); + int promisc =3D !!(filter_flags & CMD_PFILTER_PROMISCUOUS); + int allmulti =3D !!(filter_flags & CMD_PFILTER_ALL_MULTICAST); unsigned int flags =3D netdev->flags | (allmulti ? IFF_ALLMULTI : 0) | (promisc ? IFF_PROMISC : 0); @@ -2221,6 +2623,10 @@ static int enic_open(struct net_device *netdev) { struct enic *enic =3D netdev_priv(netdev); bool vf_mac_added =3D false; + u16 vf_filter_applied; + u16 vf_filter_flags; + unsigned int vf_mc_count; + unsigned int vf_uc_count; unsigned int i; int err, ret; unsigned int max_pkt_len =3D netdev->mtu + VLAN_ETH_HLEN; @@ -2301,7 +2707,6 @@ static int enic_open(struct net_device *netdev) =20 if (!enic_is_dynamic(enic) && !enic_is_sriov_vf(enic)) enic_dev_add_station_addr(enic); - if (enic_is_sriov_vf_v2(enic)) { if (!READ_ONCE(enic->vf_registered)) { netdev_err(netdev, "VF is not registered with its PF\n"); @@ -2319,9 +2724,36 @@ static int enic_open(struct net_device *netdev) enic_vf_station_addr_set(enic, netdev->dev_addr); enic_vf_station_sync_reset(enic); vf_mac_added =3D true; + + netif_addr_lock_bh(netdev); + vf_uc_count =3D enic_vf_addr_list_count(enic, &netdev->uc, true); + vf_mc_count =3D enic_vf_addr_list_count(enic, &netdev->mc, false); + netif_addr_unlock_bh(netdev); + + vf_filter_flags =3D enic_rx_mode_to_pkt_filter(netdev, + vf_uc_count, + vf_mc_count); + err =3D enic_vf_set_pkt_filter(enic, vf_filter_flags, + &vf_filter_applied); + if (err) { + netdev_err(netdev, + "Failed to configure VF packet filter: %d\n", + err); + goto err_out_disable_wq; + } + if ((vf_uc_count > ENIC_UNICAST_PERFECT_FILTERS && + !(vf_filter_applied & CMD_PFILTER_PROMISCUOUS)) || + (vf_mc_count > ENIC_MULTICAST_PERFECT_FILTERS && + !(vf_filter_applied & CMD_PFILTER_ALL_MULTICAST))) { + netdev_err(netdev, + "PF denied receive mode required by VF address lists\n"); + err =3D -EACCES; + goto err_out_disable_wq; + } } =20 - enic_set_rx_mode(netdev); + if (!enic_is_sriov_vf_v2(enic)) + enic_set_rx_mode(netdev); =20 netif_tx_wake_all_queues(netdev); =20 @@ -2956,6 +3388,13 @@ static void enic_reset(struct work_struct *work) if (err) netdev_err(enic->netdev, "Failed to reopen datapath after reset: %d\n", err); + else if (enic_is_sriov_vf_v2(enic)) { + /* Internal reset bypasses __dev_open(), which normally schedules the + * asynchronous receive-mode upload after ndo_open. Schedule the same + * core replay for lists cleared by enic_reset_addr_lists(). + */ + netif_rx_mode_schedule_fresh(enic->netdev); + } =20 /* A PF reopens its admin channel after the datapath and re-pushes link * state. The VF handshake, which open depends on, completed above. @@ -3017,6 +3456,8 @@ static void enic_tx_hang_reset(struct work_struct *wo= rk) if (err) netdev_err(enic->netdev, "Failed to reopen datapath after hang reset: %d\n", err); + else if (enic_is_sriov_vf_v2(enic)) + netif_rx_mode_schedule_fresh(enic->netdev); =20 /* A PF reopens its admin channel after the datapath and re-pushes link * state. The VF handshake, which open depends on, completed above. @@ -3269,6 +3710,30 @@ static const struct net_device_ops enic_netdev_dynam= ic_ops =3D { .ndo_features_check =3D enic_features_check, }; =20 +static const struct net_device_ops enic_netdev_vf_v2_ops =3D { + .ndo_open =3D enic_open, + .ndo_stop =3D enic_stop, + .ndo_start_xmit =3D enic_hard_start_xmit, + .ndo_get_stats64 =3D enic_get_stats, + .ndo_validate_addr =3D eth_validate_addr, + .ndo_set_rx_mode_async =3D enic_set_vf_rx_mode, + .ndo_set_mac_address =3D enic_set_mac_address_dynamic, + .ndo_change_mtu =3D enic_change_mtu, + .ndo_vlan_rx_add_vid =3D enic_vlan_rx_add_vid, + .ndo_vlan_rx_kill_vid =3D enic_vlan_rx_kill_vid, + .ndo_tx_timeout =3D enic_tx_timeout, + .ndo_set_vf_port =3D enic_set_vf_port, + .ndo_get_vf_port =3D enic_get_vf_port, + .ndo_set_vf_mac =3D enic_set_vf_mac, +#ifdef CONFIG_NET_POLL_CONTROLLER + .ndo_poll_controller =3D enic_poll_controller, +#endif +#ifdef CONFIG_RFS_ACCEL + .ndo_rx_flow_steer =3D enic_rx_flow_steer, +#endif + .ndo_features_check =3D enic_features_check, +}; + static const struct net_device_ops enic_netdev_ops =3D { .ndo_open =3D enic_open, .ndo_stop =3D enic_stop, @@ -3936,7 +4401,9 @@ static int enic_probe(struct pci_dev *pdev, const str= uct pci_device_id *ent) */ enic->rx_coalesce_usecs =3D enic->tx_coalesce_usecs; =20 - if (enic_is_dynamic(enic) || enic_is_sriov_vf(enic)) + if (enic_is_sriov_vf_v2(enic)) + netdev->netdev_ops =3D &enic_netdev_vf_v2_ops; + else if (enic_is_dynamic(enic) || enic_is_sriov_vf(enic)) netdev->netdev_ops =3D &enic_netdev_dynamic_ops; else netdev->netdev_ops =3D &enic_netdev_ops; @@ -4083,12 +4550,18 @@ static void enic_remove(struct pci_dev *pdev) =20 /* Close the admin channel and unregister from the PF before * unregister_netdev() to prevent a late PF notification from - * touching a netdev that is being torn down. VF_UNREGISTER is the + * touching a netdev that is being torn down. VF_UNREGISTER is the * protocol teardown operation: the PF removes all VF-requested * configuration, including the station address, before replying. + * Detach first while holding RTNL so any running asynchronous + * receive-mode update has completed and queued updates cannot enter + * the mailbox callback. */ if (enic_is_sriov_vf_v2(enic)) { enic_vf_admin_mac_quiesce(enic); + rtnl_lock(); + netif_device_detach(netdev); + rtnl_unlock(); =20 if (READ_ONCE(enic->vf_registered)) { int unreg_err =3D enic_mbox_vf_unregister(enic); --=20 2.43.0