[][Add macsec HW offload backport from kernel 5.18]

[Description]
Add macsec HW offload backport from kernel 5.18.

[Release-log]
N/A

Change-Id: I5b143fe620ec4bcae4075d1d85db5e41c8d48717
Reviewed-on: https://gerrit.mediatek.inc/c/openwrt/feeds/mtk_openwrt_feeds/+/5981730
diff --git a/target/linux/mediatek/patches-5.4/999-1755-05-v5.18-net-macsec-hardware-offloading-infrastructure.patch b/target/linux/mediatek/patches-5.4/999-1755-05-v5.18-net-macsec-hardware-offloading-infrastructure.patch
new file mode 100644
index 0000000..1404301
--- /dev/null
+++ b/target/linux/mediatek/patches-5.4/999-1755-05-v5.18-net-macsec-hardware-offloading-infrastructure.patch
@@ -0,0 +1,825 @@
+From 3cf3227a21d1fb020fe26128e60321bd2151e922 Mon Sep 17 00:00:00 2001
+From: Antoine Tenart <antoine.tenart@bootlin.com>
+Date: Mon, 13 Jan 2020 23:31:43 +0100
+Subject: net: macsec: hardware offloading infrastructure
+
+This patch introduces the MACsec hardware offloading infrastructure.
+
+The main idea here is to re-use the logic and data structures of the
+software MACsec implementation. This allows not to duplicate definitions
+and structure storing the same kind of information. It also allows to
+use a unified genlink interface for both MACsec implementations (so that
+the same userspace tool, `ip macsec`, is used with the same arguments).
+The MACsec offloading support cannot be disabled if an interface
+supports it at the moment.
+
+The MACsec configuration is passed to device drivers supporting it
+through macsec_ops which are called from the MACsec genl helpers. Those
+functions call the macsec ops of PHY and Ethernet drivers in two steps:
+a preparation one, and a commit one. The first step is allowed to fail
+and should be used to check if a provided configuration is compatible
+with the features provided by a MACsec engine, while the second step is
+not allowed to fail and should only be used to enable a given MACsec
+configuration. Two extra calls are made: when a virtual MACsec interface
+is created and when it is deleted, so that the hardware driver can stay
+in sync.
+
+The Rx and TX handlers are modified to take in account the special case
+were the MACsec transformation happens in the hardware, whether in a PHY
+or in a MAC, as the packets seen by the networking stack on both the
+physical and MACsec virtual interface are exactly the same. This leads
+to some limitations: the hardware and software implementations can't be
+used on the same physical interface, as the policies would be impossible
+to fulfill (such as strict validation of the frames). Also only a single
+virtual MACsec interface can be offloaded to a physical port supporting
+hardware offloading as it would be impossible to guess onto which
+interface a given packet should go (for ingress traffic).
+
+Another limitation as of now is that the counters and statistics are not
+reported back from the hardware to the software MACsec implementation.
+This isn't an issue when using offloaded MACsec transformations, but it
+should be added in the future so that the MACsec state can be reported
+to the user (which would also improve the debug).
+
+Signed-off-by: Antoine Tenart <antoine.tenart@bootlin.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/macsec.c | 453 +++++++++++++++++++++++++++++++++++++++++++++++++--
+ 1 file changed, 441 insertions(+), 12 deletions(-)
+
+diff --git a/drivers/net/macsec.c b/drivers/net/macsec.c
+index a336eee018f0b..36b0416381bf1 100644
+--- a/drivers/net/macsec.c
++++ b/drivers/net/macsec.c
+@@ -11,12 +11,14 @@
+ #include <linux/module.h>
+ #include <crypto/aead.h>
+ #include <linux/etherdevice.h>
++#include <linux/netdevice.h>
+ #include <linux/rtnetlink.h>
+ #include <linux/refcount.h>
+ #include <net/genetlink.h>
+ #include <net/sock.h>
+ #include <net/gro_cells.h>
+ #include <net/macsec.h>
++#include <linux/phy.h>
+ 
+ #include <uapi/linux/if_macsec.h>
+ 
+@@ -98,6 +100,7 @@ struct pcpu_secy_stats {
+  * @real_dev: pointer to underlying netdevice
+  * @stats: MACsec device stats
+  * @secys: linked list of SecY's on the underlying device
++ * @offload: status of offloading on the MACsec device
+  */
+ struct macsec_dev {
+ 	struct macsec_secy secy;
+@@ -105,6 +108,7 @@ struct macsec_dev {
+ 	struct pcpu_secy_stats __percpu *stats;
+ 	struct list_head secys;
+ 	struct gro_cells gro_cells;
++	enum macsec_offload offload;
+ };
+ 
+ /**
+@@ -318,6 +322,56 @@ static void macsec_set_shortlen(struct macsec_eth_header *h, size_t data_len)
+ 		h->short_length = data_len;
+ }
+ 
++/* Checks if a MACsec interface is being offloaded to an hardware engine */
++static bool macsec_is_offloaded(struct macsec_dev *macsec)
++{
++	if (macsec->offload == MACSEC_OFFLOAD_PHY)
++		return true;
++
++	return false;
++}
++
++/* Checks if underlying layers implement MACsec offloading functions. */
++static bool macsec_check_offload(enum macsec_offload offload,
++				 struct macsec_dev *macsec)
++{
++	if (!macsec || !macsec->real_dev)
++		return false;
++
++	if (offload == MACSEC_OFFLOAD_PHY)
++		return macsec->real_dev->phydev &&
++		       macsec->real_dev->phydev->macsec_ops;
++
++	return false;
++}
++
++static const struct macsec_ops *__macsec_get_ops(enum macsec_offload offload,
++						 struct macsec_dev *macsec,
++						 struct macsec_context *ctx)
++{
++	if (ctx) {
++		memset(ctx, 0, sizeof(*ctx));
++		ctx->offload = offload;
++
++		if (offload == MACSEC_OFFLOAD_PHY)
++			ctx->phydev = macsec->real_dev->phydev;
++	}
++
++	return macsec->real_dev->phydev->macsec_ops;
++}
++
++/* Returns a pointer to the MACsec ops struct if any and updates the MACsec
++ * context device reference if provided.
++ */
++static const struct macsec_ops *macsec_get_ops(struct macsec_dev *macsec,
++					       struct macsec_context *ctx)
++{
++	if (!macsec_check_offload(macsec->offload, macsec))
++		return NULL;
++
++	return __macsec_get_ops(macsec->offload, macsec, ctx);
++}
++
+ /* validate MACsec packet according to IEEE 802.1AE-2006 9.12 */
+ static bool macsec_validate_skb(struct sk_buff *skb, u16 icv_len)
+ {
+@@ -867,8 +921,10 @@ static struct macsec_rx_sc *find_rx_sc_rtnl(struct macsec_secy *secy, sci_t sci)
+ 	return NULL;
+ }
+ 
+-static void handle_not_macsec(struct sk_buff *skb)
++static enum rx_handler_result handle_not_macsec(struct sk_buff *skb)
+ {
++	/* Deliver to the uncontrolled port by default */
++	enum rx_handler_result ret = RX_HANDLER_PASS;
+ 	struct macsec_rxh_data *rxd;
+ 	struct macsec_dev *macsec;
+ 
+@@ -883,7 +939,8 @@ static void handle_not_macsec(struct sk_buff *skb)
+ 		struct sk_buff *nskb;
+ 		struct pcpu_secy_stats *secy_stats = this_cpu_ptr(macsec->stats);
+ 
+-		if (macsec->secy.validate_frames == MACSEC_VALIDATE_STRICT) {
++		if (!macsec_is_offloaded(macsec) &&
++		    macsec->secy.validate_frames == MACSEC_VALIDATE_STRICT) {
+ 			u64_stats_update_begin(&secy_stats->syncp);
+ 			secy_stats->stats.InPktsNoTag++;
+ 			u64_stats_update_end(&secy_stats->syncp);
+@@ -902,9 +959,17 @@ static void handle_not_macsec(struct sk_buff *skb)
+ 			secy_stats->stats.InPktsUntagged++;
+ 			u64_stats_update_end(&secy_stats->syncp);
+ 		}
++
++		if (netif_running(macsec->secy.netdev) &&
++		    macsec_is_offloaded(macsec)) {
++			ret = RX_HANDLER_EXACT;
++			goto out;
++		}
+ 	}
+ 
++out:
+ 	rcu_read_unlock();
++	return ret;
+ }
+ 
+ static rx_handler_result_t macsec_handle_frame(struct sk_buff **pskb)
+@@ -929,12 +994,8 @@ static rx_handler_result_t macsec_handle_frame(struct sk_buff **pskb)
+ 		goto drop_direct;
+ 
+ 	hdr = macsec_ethhdr(skb);
+-	if (hdr->eth.h_proto != htons(ETH_P_MACSEC)) {
+-		handle_not_macsec(skb);
+-
+-		/* and deliver to the uncontrolled port */
+-		return RX_HANDLER_PASS;
+-	}
++	if (hdr->eth.h_proto != htons(ETH_P_MACSEC))
++		return handle_not_macsec(skb);
+ 
+ 	skb = skb_unshare(skb, GFP_ATOMIC);
+ 	*pskb = skb;
+@@ -1440,6 +1501,40 @@ static const struct nla_policy macsec_genl_sa_policy[NUM_MACSEC_SA_ATTR] = {
+ 				 .len = MACSEC_MAX_KEY_LEN, },
+ };
+ 
++/* Offloads an operation to a device driver */
++static int macsec_offload(int (* const func)(struct macsec_context *),
++			  struct macsec_context *ctx)
++{
++	int ret;
++
++	if (unlikely(!func))
++		return 0;
++
++	if (ctx->offload == MACSEC_OFFLOAD_PHY)
++		mutex_lock(&ctx->phydev->lock);
++
++	/* Phase I: prepare. The drive should fail here if there are going to be
++	 * issues in the commit phase.
++	 */
++	ctx->prepare = true;
++	ret = (*func)(ctx);
++	if (ret)
++		goto phy_unlock;
++
++	/* Phase II: commit. This step cannot fail. */
++	ctx->prepare = false;
++	ret = (*func)(ctx);
++	/* This should never happen: commit is not allowed to fail */
++	if (unlikely(ret))
++		WARN(1, "MACsec offloading commit failed (%d)\n", ret);
++
++phy_unlock:
++	if (ctx->offload == MACSEC_OFFLOAD_PHY)
++		mutex_unlock(&ctx->phydev->lock);
++
++	return ret;
++}
++
+ static int parse_sa_config(struct nlattr **attrs, struct nlattr **tb_sa)
+ {
+ 	if (!attrs[MACSEC_ATTR_SA_CONFIG])
+@@ -1555,13 +1650,40 @@ static int macsec_add_rxsa(struct sk_buff *skb, struct genl_info *info)
+ 	if (tb_sa[MACSEC_SA_ATTR_ACTIVE])
+ 		rx_sa->active = !!nla_get_u8(tb_sa[MACSEC_SA_ATTR_ACTIVE]);
+ 
+-	nla_memcpy(rx_sa->key.id, tb_sa[MACSEC_SA_ATTR_KEYID], MACSEC_KEYID_LEN);
+ 	rx_sa->sc = rx_sc;
++
++	/* If h/w offloading is available, propagate to the device */
++	if (macsec_is_offloaded(netdev_priv(dev))) {
++		const struct macsec_ops *ops;
++		struct macsec_context ctx;
++
++		ops = macsec_get_ops(netdev_priv(dev), &ctx);
++		if (!ops) {
++			err = -EOPNOTSUPP;
++			goto cleanup;
++		}
++
++		ctx.sa.assoc_num = assoc_num;
++		ctx.sa.rx_sa = rx_sa;
++		memcpy(ctx.sa.key, nla_data(tb_sa[MACSEC_SA_ATTR_KEY]),
++		       MACSEC_KEYID_LEN);
++
++		err = macsec_offload(ops->mdo_add_rxsa, &ctx);
++		if (err)
++			goto cleanup;
++	}
++
++	nla_memcpy(rx_sa->key.id, tb_sa[MACSEC_SA_ATTR_KEYID], MACSEC_KEYID_LEN);
+ 	rcu_assign_pointer(rx_sc->sa[assoc_num], rx_sa);
+ 
+ 	rtnl_unlock();
+ 
+ 	return 0;
++
++cleanup:
++	kfree(rx_sa);
++	rtnl_unlock();
++	return err;
+ }
+ 
+ static bool validate_add_rxsc(struct nlattr **attrs)
+@@ -1584,6 +1706,8 @@ static int macsec_add_rxsc(struct sk_buff *skb, struct genl_info *info)
+ 	struct nlattr **attrs = info->attrs;
+ 	struct macsec_rx_sc *rx_sc;
+ 	struct nlattr *tb_rxsc[MACSEC_RXSC_ATTR_MAX + 1];
++	bool was_active;
++	int ret;
+ 
+ 	if (!attrs[MACSEC_ATTR_IFINDEX])
+ 		return -EINVAL;
+@@ -1609,12 +1733,35 @@ static int macsec_add_rxsc(struct sk_buff *skb, struct genl_info *info)
+ 		return PTR_ERR(rx_sc);
+ 	}
+ 
++	was_active = rx_sc->active;
+ 	if (tb_rxsc[MACSEC_RXSC_ATTR_ACTIVE])
+ 		rx_sc->active = !!nla_get_u8(tb_rxsc[MACSEC_RXSC_ATTR_ACTIVE]);
+ 
++	if (macsec_is_offloaded(netdev_priv(dev))) {
++		const struct macsec_ops *ops;
++		struct macsec_context ctx;
++
++		ops = macsec_get_ops(netdev_priv(dev), &ctx);
++		if (!ops) {
++			ret = -EOPNOTSUPP;
++			goto cleanup;
++		}
++
++		ctx.rx_sc = rx_sc;
++
++		ret = macsec_offload(ops->mdo_add_rxsc, &ctx);
++		if (ret)
++			goto cleanup;
++	}
++
+ 	rtnl_unlock();
+ 
+ 	return 0;
++
++cleanup:
++	rx_sc->active = was_active;
++	rtnl_unlock();
++	return ret;
+ }
+ 
+ static bool validate_add_txsa(struct nlattr **attrs)
+@@ -1651,6 +1798,7 @@ static int macsec_add_txsa(struct sk_buff *skb, struct genl_info *info)
+ 	struct macsec_tx_sa *tx_sa;
+ 	unsigned char assoc_num;
+ 	struct nlattr *tb_sa[MACSEC_SA_ATTR_MAX + 1];
++	bool was_operational;
+ 	int err;
+ 
+ 	if (!attrs[MACSEC_ATTR_IFINDEX])
+@@ -1701,8 +1849,6 @@ static int macsec_add_txsa(struct sk_buff *skb, struct genl_info *info)
+ 		return err;
+ 	}
+ 
+-	nla_memcpy(tx_sa->key.id, tb_sa[MACSEC_SA_ATTR_KEYID], MACSEC_KEYID_LEN);
+-
+ 	spin_lock_bh(&tx_sa->lock);
+ 	tx_sa->next_pn = nla_get_u32(tb_sa[MACSEC_SA_ATTR_PN]);
+ 	spin_unlock_bh(&tx_sa->lock);
+@@ -1710,14 +1856,43 @@ static int macsec_add_txsa(struct sk_buff *skb, struct genl_info *info)
+ 	if (tb_sa[MACSEC_SA_ATTR_ACTIVE])
+ 		tx_sa->active = !!nla_get_u8(tb_sa[MACSEC_SA_ATTR_ACTIVE]);
+ 
++	was_operational = secy->operational;
+ 	if (assoc_num == tx_sc->encoding_sa && tx_sa->active)
+ 		secy->operational = true;
+ 
++	/* If h/w offloading is available, propagate to the device */
++	if (macsec_is_offloaded(netdev_priv(dev))) {
++		const struct macsec_ops *ops;
++		struct macsec_context ctx;
++
++		ops = macsec_get_ops(netdev_priv(dev), &ctx);
++		if (!ops) {
++			err = -EOPNOTSUPP;
++			goto cleanup;
++		}
++
++		ctx.sa.assoc_num = assoc_num;
++		ctx.sa.tx_sa = tx_sa;
++		memcpy(ctx.sa.key, nla_data(tb_sa[MACSEC_SA_ATTR_KEY]),
++		       MACSEC_KEYID_LEN);
++
++		err = macsec_offload(ops->mdo_add_txsa, &ctx);
++		if (err)
++			goto cleanup;
++	}
++
++	nla_memcpy(tx_sa->key.id, tb_sa[MACSEC_SA_ATTR_KEYID], MACSEC_KEYID_LEN);
+ 	rcu_assign_pointer(tx_sc->sa[assoc_num], tx_sa);
+ 
+ 	rtnl_unlock();
+ 
+ 	return 0;
++
++cleanup:
++	secy->operational = was_operational;
++	kfree(tx_sa);
++	rtnl_unlock();
++	return err;
+ }
+ 
+ static int macsec_del_rxsa(struct sk_buff *skb, struct genl_info *info)
+@@ -1730,6 +1905,7 @@ static int macsec_del_rxsa(struct sk_buff *skb, struct genl_info *info)
+ 	u8 assoc_num;
+ 	struct nlattr *tb_rxsc[MACSEC_RXSC_ATTR_MAX + 1];
+ 	struct nlattr *tb_sa[MACSEC_SA_ATTR_MAX + 1];
++	int ret;
+ 
+ 	if (!attrs[MACSEC_ATTR_IFINDEX])
+ 		return -EINVAL;
+@@ -1753,12 +1929,35 @@ static int macsec_del_rxsa(struct sk_buff *skb, struct genl_info *info)
+ 		return -EBUSY;
+ 	}
+ 
++	/* If h/w offloading is available, propagate to the device */
++	if (macsec_is_offloaded(netdev_priv(dev))) {
++		const struct macsec_ops *ops;
++		struct macsec_context ctx;
++
++		ops = macsec_get_ops(netdev_priv(dev), &ctx);
++		if (!ops) {
++			ret = -EOPNOTSUPP;
++			goto cleanup;
++		}
++
++		ctx.sa.assoc_num = assoc_num;
++		ctx.sa.rx_sa = rx_sa;
++
++		ret = macsec_offload(ops->mdo_del_rxsa, &ctx);
++		if (ret)
++			goto cleanup;
++	}
++
+ 	RCU_INIT_POINTER(rx_sc->sa[assoc_num], NULL);
+ 	clear_rx_sa(rx_sa);
+ 
+ 	rtnl_unlock();
+ 
+ 	return 0;
++
++cleanup:
++	rtnl_unlock();
++	return ret;
+ }
+ 
+ static int macsec_del_rxsc(struct sk_buff *skb, struct genl_info *info)
+@@ -1769,6 +1968,7 @@ static int macsec_del_rxsc(struct sk_buff *skb, struct genl_info *info)
+ 	struct macsec_rx_sc *rx_sc;
+ 	sci_t sci;
+ 	struct nlattr *tb_rxsc[MACSEC_RXSC_ATTR_MAX + 1];
++	int ret;
+ 
+ 	if (!attrs[MACSEC_ATTR_IFINDEX])
+ 		return -EINVAL;
+@@ -1795,10 +1995,31 @@ static int macsec_del_rxsc(struct sk_buff *skb, struct genl_info *info)
+ 		return -ENODEV;
+ 	}
+ 
++	/* If h/w offloading is available, propagate to the device */
++	if (macsec_is_offloaded(netdev_priv(dev))) {
++		const struct macsec_ops *ops;
++		struct macsec_context ctx;
++
++		ops = macsec_get_ops(netdev_priv(dev), &ctx);
++		if (!ops) {
++			ret = -EOPNOTSUPP;
++			goto cleanup;
++		}
++
++		ctx.rx_sc = rx_sc;
++		ret = macsec_offload(ops->mdo_del_rxsc, &ctx);
++		if (ret)
++			goto cleanup;
++	}
++
+ 	free_rx_sc(rx_sc);
+ 	rtnl_unlock();
+ 
+ 	return 0;
++
++cleanup:
++	rtnl_unlock();
++	return ret;
+ }
+ 
+ static int macsec_del_txsa(struct sk_buff *skb, struct genl_info *info)
+@@ -1810,6 +2031,7 @@ static int macsec_del_txsa(struct sk_buff *skb, struct genl_info *info)
+ 	struct macsec_tx_sa *tx_sa;
+ 	u8 assoc_num;
+ 	struct nlattr *tb_sa[MACSEC_SA_ATTR_MAX + 1];
++	int ret;
+ 
+ 	if (!attrs[MACSEC_ATTR_IFINDEX])
+ 		return -EINVAL;
+@@ -1830,12 +2052,35 @@ static int macsec_del_txsa(struct sk_buff *skb, struct genl_info *info)
+ 		return -EBUSY;
+ 	}
+ 
++	/* If h/w offloading is available, propagate to the device */
++	if (macsec_is_offloaded(netdev_priv(dev))) {
++		const struct macsec_ops *ops;
++		struct macsec_context ctx;
++
++		ops = macsec_get_ops(netdev_priv(dev), &ctx);
++		if (!ops) {
++			ret = -EOPNOTSUPP;
++			goto cleanup;
++		}
++
++		ctx.sa.assoc_num = assoc_num;
++		ctx.sa.tx_sa = tx_sa;
++
++		ret = macsec_offload(ops->mdo_del_txsa, &ctx);
++		if (ret)
++			goto cleanup;
++	}
++
+ 	RCU_INIT_POINTER(tx_sc->sa[assoc_num], NULL);
+ 	clear_tx_sa(tx_sa);
+ 
+ 	rtnl_unlock();
+ 
+ 	return 0;
++
++cleanup:
++	rtnl_unlock();
++	return ret;
+ }
+ 
+ static bool validate_upd_sa(struct nlattr **attrs)
+@@ -1868,6 +2113,9 @@ static int macsec_upd_txsa(struct sk_buff *skb, struct genl_info *info)
+ 	struct macsec_tx_sa *tx_sa;
+ 	u8 assoc_num;
+ 	struct nlattr *tb_sa[MACSEC_SA_ATTR_MAX + 1];
++	bool was_operational, was_active;
++	u32 prev_pn = 0;
++	int ret = 0;
+ 
+ 	if (!attrs[MACSEC_ATTR_IFINDEX])
+ 		return -EINVAL;
+@@ -1888,19 +2136,52 @@ static int macsec_upd_txsa(struct sk_buff *skb, struct genl_info *info)
+ 
+ 	if (tb_sa[MACSEC_SA_ATTR_PN]) {
+ 		spin_lock_bh(&tx_sa->lock);
++		prev_pn = tx_sa->next_pn;
+ 		tx_sa->next_pn = nla_get_u32(tb_sa[MACSEC_SA_ATTR_PN]);
+ 		spin_unlock_bh(&tx_sa->lock);
+ 	}
+ 
++	was_active = tx_sa->active;
+ 	if (tb_sa[MACSEC_SA_ATTR_ACTIVE])
+ 		tx_sa->active = nla_get_u8(tb_sa[MACSEC_SA_ATTR_ACTIVE]);
+ 
++	was_operational = secy->operational;
+ 	if (assoc_num == tx_sc->encoding_sa)
+ 		secy->operational = tx_sa->active;
+ 
++	/* If h/w offloading is available, propagate to the device */
++	if (macsec_is_offloaded(netdev_priv(dev))) {
++		const struct macsec_ops *ops;
++		struct macsec_context ctx;
++
++		ops = macsec_get_ops(netdev_priv(dev), &ctx);
++		if (!ops) {
++			ret = -EOPNOTSUPP;
++			goto cleanup;
++		}
++
++		ctx.sa.assoc_num = assoc_num;
++		ctx.sa.tx_sa = tx_sa;
++
++		ret = macsec_offload(ops->mdo_upd_txsa, &ctx);
++		if (ret)
++			goto cleanup;
++	}
++
+ 	rtnl_unlock();
+ 
+ 	return 0;
++
++cleanup:
++	if (tb_sa[MACSEC_SA_ATTR_PN]) {
++		spin_lock_bh(&tx_sa->lock);
++		tx_sa->next_pn = prev_pn;
++		spin_unlock_bh(&tx_sa->lock);
++	}
++	tx_sa->active = was_active;
++	secy->operational = was_operational;
++	rtnl_unlock();
++	return ret;
+ }
+ 
+ static int macsec_upd_rxsa(struct sk_buff *skb, struct genl_info *info)
+@@ -1913,6 +2194,9 @@ static int macsec_upd_rxsa(struct sk_buff *skb, struct genl_info *info)
+ 	u8 assoc_num;
+ 	struct nlattr *tb_rxsc[MACSEC_RXSC_ATTR_MAX + 1];
+ 	struct nlattr *tb_sa[MACSEC_SA_ATTR_MAX + 1];
++	bool was_active;
++	u32 prev_pn = 0;
++	int ret = 0;
+ 
+ 	if (!attrs[MACSEC_ATTR_IFINDEX])
+ 		return -EINVAL;
+@@ -1936,15 +2220,46 @@ static int macsec_upd_rxsa(struct sk_buff *skb, struct genl_info *info)
+ 
+ 	if (tb_sa[MACSEC_SA_ATTR_PN]) {
+ 		spin_lock_bh(&rx_sa->lock);
++		prev_pn = rx_sa->next_pn;
+ 		rx_sa->next_pn = nla_get_u32(tb_sa[MACSEC_SA_ATTR_PN]);
+ 		spin_unlock_bh(&rx_sa->lock);
+ 	}
+ 
++	was_active = rx_sa->active;
+ 	if (tb_sa[MACSEC_SA_ATTR_ACTIVE])
+ 		rx_sa->active = nla_get_u8(tb_sa[MACSEC_SA_ATTR_ACTIVE]);
+ 
++	/* If h/w offloading is available, propagate to the device */
++	if (macsec_is_offloaded(netdev_priv(dev))) {
++		const struct macsec_ops *ops;
++		struct macsec_context ctx;
++
++		ops = macsec_get_ops(netdev_priv(dev), &ctx);
++		if (!ops) {
++			ret = -EOPNOTSUPP;
++			goto cleanup;
++		}
++
++		ctx.sa.assoc_num = assoc_num;
++		ctx.sa.rx_sa = rx_sa;
++
++		ret = macsec_offload(ops->mdo_upd_rxsa, &ctx);
++		if (ret)
++			goto cleanup;
++	}
++
+ 	rtnl_unlock();
+ 	return 0;
++
++cleanup:
++	if (tb_sa[MACSEC_SA_ATTR_PN]) {
++		spin_lock_bh(&rx_sa->lock);
++		rx_sa->next_pn = prev_pn;
++		spin_unlock_bh(&rx_sa->lock);
++	}
++	rx_sa->active = was_active;
++	rtnl_unlock();
++	return ret;
+ }
+ 
+ static int macsec_upd_rxsc(struct sk_buff *skb, struct genl_info *info)
+@@ -1954,6 +2269,9 @@ static int macsec_upd_rxsc(struct sk_buff *skb, struct genl_info *info)
+ 	struct macsec_secy *secy;
+ 	struct macsec_rx_sc *rx_sc;
+ 	struct nlattr *tb_rxsc[MACSEC_RXSC_ATTR_MAX + 1];
++	unsigned int prev_n_rx_sc;
++	bool was_active;
++	int ret;
+ 
+ 	if (!attrs[MACSEC_ATTR_IFINDEX])
+ 		return -EINVAL;
+@@ -1971,6 +2289,8 @@ static int macsec_upd_rxsc(struct sk_buff *skb, struct genl_info *info)
+ 		return PTR_ERR(rx_sc);
+ 	}
+ 
++	was_active = rx_sc->active;
++	prev_n_rx_sc = secy->n_rx_sc;
+ 	if (tb_rxsc[MACSEC_RXSC_ATTR_ACTIVE]) {
+ 		bool new = !!nla_get_u8(tb_rxsc[MACSEC_RXSC_ATTR_ACTIVE]);
+ 
+@@ -1980,9 +2300,33 @@ static int macsec_upd_rxsc(struct sk_buff *skb, struct genl_info *info)
+ 		rx_sc->active = new;
+ 	}
+ 
++	/* If h/w offloading is available, propagate to the device */
++	if (macsec_is_offloaded(netdev_priv(dev))) {
++		const struct macsec_ops *ops;
++		struct macsec_context ctx;
++
++		ops = macsec_get_ops(netdev_priv(dev), &ctx);
++		if (!ops) {
++			ret = -EOPNOTSUPP;
++			goto cleanup;
++		}
++
++		ctx.rx_sc = rx_sc;
++
++		ret = macsec_offload(ops->mdo_upd_rxsc, &ctx);
++		if (ret)
++			goto cleanup;
++	}
++
+ 	rtnl_unlock();
+ 
+ 	return 0;
++
++cleanup:
++	secy->n_rx_sc = prev_n_rx_sc;
++	rx_sc->active = was_active;
++	rtnl_unlock();
++	return ret;
+ }
+ 
+ static int copy_tx_sa_stats(struct sk_buff *skb,
+@@ -2550,6 +2894,11 @@ static netdev_tx_t macsec_start_xmit(struct sk_buff *skb,
+ 	struct pcpu_secy_stats *secy_stats;
+ 	int ret, len;
+ 
++	if (macsec_is_offloaded(netdev_priv(dev))) {
++		skb->dev = macsec->real_dev;
++		return dev_queue_xmit(skb);
++	}
++
+ 	/* 10.5 */
+ 	if (!secy->protect_frames) {
+ 		secy_stats = this_cpu_ptr(macsec->stats);
+@@ -2663,6 +3012,22 @@ static int macsec_dev_open(struct net_device *dev)
+ 			goto clear_allmulti;
+ 	}
+ 
++	/* If h/w offloading is available, propagate to the device */
++	if (macsec_is_offloaded(macsec)) {
++		const struct macsec_ops *ops;
++		struct macsec_context ctx;
++
++		ops = macsec_get_ops(netdev_priv(dev), &ctx);
++		if (!ops) {
++			err = -EOPNOTSUPP;
++			goto clear_allmulti;
++		}
++
++		err = macsec_offload(ops->mdo_dev_open, &ctx);
++		if (err)
++			goto clear_allmulti;
++	}
++
+ 	if (netif_carrier_ok(real_dev))
+ 		netif_carrier_on(dev);
+ 
+@@ -2683,6 +3048,16 @@ static int macsec_dev_stop(struct net_device *dev)
+ 
+ 	netif_carrier_off(dev);
+ 
++	/* If h/w offloading is available, propagate to the device */
++	if (macsec_is_offloaded(macsec)) {
++		const struct macsec_ops *ops;
++		struct macsec_context ctx;
++
++		ops = macsec_get_ops(macsec, &ctx);
++		if (ops)
++			macsec_offload(ops->mdo_dev_stop, &ctx);
++	}
++
+ 	dev_mc_unsync(real_dev, dev);
+ 	dev_uc_unsync(real_dev, dev);
+ 
+@@ -2914,6 +3289,11 @@ static int macsec_changelink(struct net_device *dev, struct nlattr *tb[],
+ 			     struct nlattr *data[],
+ 			     struct netlink_ext_ack *extack)
+ {
++	struct macsec_dev *macsec = macsec_priv(dev);
++	struct macsec_tx_sa tx_sc;
++	struct macsec_secy secy;
++	int ret;
++
+ 	if (!data)
+ 		return 0;
+ 
+@@ -2923,7 +3303,41 @@ static int macsec_changelink(struct net_device *dev, struct nlattr *tb[],
+ 	    data[IFLA_MACSEC_PORT])
+ 		return -EINVAL;
+ 
+-	return macsec_changelink_common(dev, data);
++	/* Keep a copy of unmodified secy and tx_sc, in case the offload
++	 * propagation fails, to revert macsec_changelink_common.
++	 */
++	memcpy(&secy, &macsec->secy, sizeof(secy));
++	memcpy(&tx_sc, &macsec->secy.tx_sc, sizeof(tx_sc));
++
++	ret = macsec_changelink_common(dev, data);
++	if (ret)
++		return ret;
++
++	/* If h/w offloading is available, propagate to the device */
++	if (macsec_is_offloaded(macsec)) {
++		const struct macsec_ops *ops;
++		struct macsec_context ctx;
++		int ret;
++
++		ops = macsec_get_ops(netdev_priv(dev), &ctx);
++		if (!ops) {
++			ret = -EOPNOTSUPP;
++			goto cleanup;
++		}
++
++		ctx.secy = &macsec->secy;
++		ret = macsec_offload(ops->mdo_upd_secy, &ctx);
++		if (ret)
++			goto cleanup;
++	}
++
++	return 0;
++
++cleanup:
++	memcpy(&macsec->secy.tx_sc, &tx_sc, sizeof(tx_sc));
++	memcpy(&macsec->secy, &secy, sizeof(secy));
++
++	return ret;
+ }
+ 
+ static void macsec_del_dev(struct macsec_dev *macsec)
+@@ -2966,6 +3380,18 @@ static void macsec_dellink(struct net_device *dev, struct list_head *head)
+ 	struct net_device *real_dev = macsec->real_dev;
+ 	struct macsec_rxh_data *rxd = macsec_data_rtnl(real_dev);
+ 
++	/* If h/w offloading is available, propagate to the device */
++	if (macsec_is_offloaded(macsec)) {
++		const struct macsec_ops *ops;
++		struct macsec_context ctx;
++
++		ops = macsec_get_ops(netdev_priv(dev), &ctx);
++		if (ops) {
++			ctx.secy = &macsec->secy;
++			macsec_offload(ops->mdo_del_secy, &ctx);
++		}
++	}
++
+ 	macsec_common_dellink(dev, head);
+ 
+ 	if (list_empty(&rxd->secys)) {
+@@ -3077,6 +3503,9 @@ static int macsec_newlink(struct net *net, struct net_device *dev,
+ 
+ 	macsec->real_dev = real_dev;
+ 
++	/* MACsec offloading is off by default */
++	macsec->offload = MACSEC_OFFLOAD_OFF;
++
+ 	if (data && data[IFLA_MACSEC_ICV_LEN])
+ 		icv_len = nla_get_u8(data[IFLA_MACSEC_ICV_LEN]);
+ 	dev->mtu = real_dev->mtu - icv_len - macsec_extra_len(true);
+-- 
+cgit 1.2.3-1.el7
+