162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* Copyright(c) 2018 Oracle and/or its affiliates. All rights reserved. */ 362306a36Sopenharmony_ci 462306a36Sopenharmony_ci#include "ixgbevf.h" 562306a36Sopenharmony_ci#include <net/xfrm.h> 662306a36Sopenharmony_ci#include <crypto/aead.h> 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#define IXGBE_IPSEC_KEY_BITS 160 962306a36Sopenharmony_cistatic const char aes_gcm_name[] = "rfc4106(gcm(aes))"; 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci/** 1262306a36Sopenharmony_ci * ixgbevf_ipsec_set_pf_sa - ask the PF to set up an SA 1362306a36Sopenharmony_ci * @adapter: board private structure 1462306a36Sopenharmony_ci * @xs: xfrm info to be sent to the PF 1562306a36Sopenharmony_ci * 1662306a36Sopenharmony_ci * Returns: positive offload handle from the PF, or negative error code 1762306a36Sopenharmony_ci **/ 1862306a36Sopenharmony_cistatic int ixgbevf_ipsec_set_pf_sa(struct ixgbevf_adapter *adapter, 1962306a36Sopenharmony_ci struct xfrm_state *xs) 2062306a36Sopenharmony_ci{ 2162306a36Sopenharmony_ci u32 msgbuf[IXGBE_VFMAILBOX_SIZE] = { 0 }; 2262306a36Sopenharmony_ci struct ixgbe_hw *hw = &adapter->hw; 2362306a36Sopenharmony_ci struct sa_mbx_msg *sam; 2462306a36Sopenharmony_ci int ret; 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci /* send the important bits to the PF */ 2762306a36Sopenharmony_ci sam = (struct sa_mbx_msg *)(&msgbuf[1]); 2862306a36Sopenharmony_ci sam->dir = xs->xso.dir; 2962306a36Sopenharmony_ci sam->spi = xs->id.spi; 3062306a36Sopenharmony_ci sam->proto = xs->id.proto; 3162306a36Sopenharmony_ci sam->family = xs->props.family; 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci if (xs->props.family == AF_INET6) 3462306a36Sopenharmony_ci memcpy(sam->addr, &xs->id.daddr.a6, sizeof(xs->id.daddr.a6)); 3562306a36Sopenharmony_ci else 3662306a36Sopenharmony_ci memcpy(sam->addr, &xs->id.daddr.a4, sizeof(xs->id.daddr.a4)); 3762306a36Sopenharmony_ci memcpy(sam->key, xs->aead->alg_key, sizeof(sam->key)); 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci msgbuf[0] = IXGBE_VF_IPSEC_ADD; 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci spin_lock_bh(&adapter->mbx_lock); 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci ret = ixgbevf_write_mbx(hw, msgbuf, IXGBE_VFMAILBOX_SIZE); 4462306a36Sopenharmony_ci if (ret) 4562306a36Sopenharmony_ci goto out; 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci ret = ixgbevf_poll_mbx(hw, msgbuf, 2); 4862306a36Sopenharmony_ci if (ret) 4962306a36Sopenharmony_ci goto out; 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci ret = (int)msgbuf[1]; 5262306a36Sopenharmony_ci if (msgbuf[0] & IXGBE_VT_MSGTYPE_FAILURE && ret >= 0) 5362306a36Sopenharmony_ci ret = -1; 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ciout: 5662306a36Sopenharmony_ci spin_unlock_bh(&adapter->mbx_lock); 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci return ret; 5962306a36Sopenharmony_ci} 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci/** 6262306a36Sopenharmony_ci * ixgbevf_ipsec_del_pf_sa - ask the PF to delete an SA 6362306a36Sopenharmony_ci * @adapter: board private structure 6462306a36Sopenharmony_ci * @pfsa: sa index returned from PF when created, -1 for all 6562306a36Sopenharmony_ci * 6662306a36Sopenharmony_ci * Returns: 0 on success, or negative error code 6762306a36Sopenharmony_ci **/ 6862306a36Sopenharmony_cistatic int ixgbevf_ipsec_del_pf_sa(struct ixgbevf_adapter *adapter, int pfsa) 6962306a36Sopenharmony_ci{ 7062306a36Sopenharmony_ci struct ixgbe_hw *hw = &adapter->hw; 7162306a36Sopenharmony_ci u32 msgbuf[2]; 7262306a36Sopenharmony_ci int err; 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci memset(msgbuf, 0, sizeof(msgbuf)); 7562306a36Sopenharmony_ci msgbuf[0] = IXGBE_VF_IPSEC_DEL; 7662306a36Sopenharmony_ci msgbuf[1] = (u32)pfsa; 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci spin_lock_bh(&adapter->mbx_lock); 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci err = ixgbevf_write_mbx(hw, msgbuf, 2); 8162306a36Sopenharmony_ci if (err) 8262306a36Sopenharmony_ci goto out; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci err = ixgbevf_poll_mbx(hw, msgbuf, 2); 8562306a36Sopenharmony_ci if (err) 8662306a36Sopenharmony_ci goto out; 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ciout: 8962306a36Sopenharmony_ci spin_unlock_bh(&adapter->mbx_lock); 9062306a36Sopenharmony_ci return err; 9162306a36Sopenharmony_ci} 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci/** 9462306a36Sopenharmony_ci * ixgbevf_ipsec_restore - restore the IPsec HW settings after a reset 9562306a36Sopenharmony_ci * @adapter: board private structure 9662306a36Sopenharmony_ci * 9762306a36Sopenharmony_ci * Reload the HW tables from the SW tables after they've been bashed 9862306a36Sopenharmony_ci * by a chip reset. While we're here, make sure any stale VF data is 9962306a36Sopenharmony_ci * removed, since we go through reset when num_vfs changes. 10062306a36Sopenharmony_ci **/ 10162306a36Sopenharmony_civoid ixgbevf_ipsec_restore(struct ixgbevf_adapter *adapter) 10262306a36Sopenharmony_ci{ 10362306a36Sopenharmony_ci struct ixgbevf_ipsec *ipsec = adapter->ipsec; 10462306a36Sopenharmony_ci struct net_device *netdev = adapter->netdev; 10562306a36Sopenharmony_ci int i; 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci if (!(adapter->netdev->features & NETIF_F_HW_ESP)) 10862306a36Sopenharmony_ci return; 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci /* reload the Rx and Tx keys */ 11162306a36Sopenharmony_ci for (i = 0; i < IXGBE_IPSEC_MAX_SA_COUNT; i++) { 11262306a36Sopenharmony_ci struct rx_sa *r = &ipsec->rx_tbl[i]; 11362306a36Sopenharmony_ci struct tx_sa *t = &ipsec->tx_tbl[i]; 11462306a36Sopenharmony_ci int ret; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci if (r->used) { 11762306a36Sopenharmony_ci ret = ixgbevf_ipsec_set_pf_sa(adapter, r->xs); 11862306a36Sopenharmony_ci if (ret < 0) 11962306a36Sopenharmony_ci netdev_err(netdev, "reload rx_tbl[%d] failed = %d\n", 12062306a36Sopenharmony_ci i, ret); 12162306a36Sopenharmony_ci } 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci if (t->used) { 12462306a36Sopenharmony_ci ret = ixgbevf_ipsec_set_pf_sa(adapter, t->xs); 12562306a36Sopenharmony_ci if (ret < 0) 12662306a36Sopenharmony_ci netdev_err(netdev, "reload tx_tbl[%d] failed = %d\n", 12762306a36Sopenharmony_ci i, ret); 12862306a36Sopenharmony_ci } 12962306a36Sopenharmony_ci } 13062306a36Sopenharmony_ci} 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci/** 13362306a36Sopenharmony_ci * ixgbevf_ipsec_find_empty_idx - find the first unused security parameter index 13462306a36Sopenharmony_ci * @ipsec: pointer to IPsec struct 13562306a36Sopenharmony_ci * @rxtable: true if we need to look in the Rx table 13662306a36Sopenharmony_ci * 13762306a36Sopenharmony_ci * Returns the first unused index in either the Rx or Tx SA table 13862306a36Sopenharmony_ci **/ 13962306a36Sopenharmony_cistatic 14062306a36Sopenharmony_ciint ixgbevf_ipsec_find_empty_idx(struct ixgbevf_ipsec *ipsec, bool rxtable) 14162306a36Sopenharmony_ci{ 14262306a36Sopenharmony_ci u32 i; 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci if (rxtable) { 14562306a36Sopenharmony_ci if (ipsec->num_rx_sa == IXGBE_IPSEC_MAX_SA_COUNT) 14662306a36Sopenharmony_ci return -ENOSPC; 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci /* search rx sa table */ 14962306a36Sopenharmony_ci for (i = 0; i < IXGBE_IPSEC_MAX_SA_COUNT; i++) { 15062306a36Sopenharmony_ci if (!ipsec->rx_tbl[i].used) 15162306a36Sopenharmony_ci return i; 15262306a36Sopenharmony_ci } 15362306a36Sopenharmony_ci } else { 15462306a36Sopenharmony_ci if (ipsec->num_tx_sa == IXGBE_IPSEC_MAX_SA_COUNT) 15562306a36Sopenharmony_ci return -ENOSPC; 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci /* search tx sa table */ 15862306a36Sopenharmony_ci for (i = 0; i < IXGBE_IPSEC_MAX_SA_COUNT; i++) { 15962306a36Sopenharmony_ci if (!ipsec->tx_tbl[i].used) 16062306a36Sopenharmony_ci return i; 16162306a36Sopenharmony_ci } 16262306a36Sopenharmony_ci } 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci return -ENOSPC; 16562306a36Sopenharmony_ci} 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci/** 16862306a36Sopenharmony_ci * ixgbevf_ipsec_find_rx_state - find the state that matches 16962306a36Sopenharmony_ci * @ipsec: pointer to IPsec struct 17062306a36Sopenharmony_ci * @daddr: inbound address to match 17162306a36Sopenharmony_ci * @proto: protocol to match 17262306a36Sopenharmony_ci * @spi: SPI to match 17362306a36Sopenharmony_ci * @ip4: true if using an IPv4 address 17462306a36Sopenharmony_ci * 17562306a36Sopenharmony_ci * Returns a pointer to the matching SA state information 17662306a36Sopenharmony_ci **/ 17762306a36Sopenharmony_cistatic 17862306a36Sopenharmony_cistruct xfrm_state *ixgbevf_ipsec_find_rx_state(struct ixgbevf_ipsec *ipsec, 17962306a36Sopenharmony_ci __be32 *daddr, u8 proto, 18062306a36Sopenharmony_ci __be32 spi, bool ip4) 18162306a36Sopenharmony_ci{ 18262306a36Sopenharmony_ci struct xfrm_state *ret = NULL; 18362306a36Sopenharmony_ci struct rx_sa *rsa; 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci rcu_read_lock(); 18662306a36Sopenharmony_ci hash_for_each_possible_rcu(ipsec->rx_sa_list, rsa, hlist, 18762306a36Sopenharmony_ci (__force u32)spi) { 18862306a36Sopenharmony_ci if (spi == rsa->xs->id.spi && 18962306a36Sopenharmony_ci ((ip4 && *daddr == rsa->xs->id.daddr.a4) || 19062306a36Sopenharmony_ci (!ip4 && !memcmp(daddr, &rsa->xs->id.daddr.a6, 19162306a36Sopenharmony_ci sizeof(rsa->xs->id.daddr.a6)))) && 19262306a36Sopenharmony_ci proto == rsa->xs->id.proto) { 19362306a36Sopenharmony_ci ret = rsa->xs; 19462306a36Sopenharmony_ci xfrm_state_hold(ret); 19562306a36Sopenharmony_ci break; 19662306a36Sopenharmony_ci } 19762306a36Sopenharmony_ci } 19862306a36Sopenharmony_ci rcu_read_unlock(); 19962306a36Sopenharmony_ci return ret; 20062306a36Sopenharmony_ci} 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci/** 20362306a36Sopenharmony_ci * ixgbevf_ipsec_parse_proto_keys - find the key and salt based on the protocol 20462306a36Sopenharmony_ci * @xs: pointer to xfrm_state struct 20562306a36Sopenharmony_ci * @mykey: pointer to key array to populate 20662306a36Sopenharmony_ci * @mysalt: pointer to salt value to populate 20762306a36Sopenharmony_ci * 20862306a36Sopenharmony_ci * This copies the protocol keys and salt to our own data tables. The 20962306a36Sopenharmony_ci * 82599 family only supports the one algorithm. 21062306a36Sopenharmony_ci **/ 21162306a36Sopenharmony_cistatic int ixgbevf_ipsec_parse_proto_keys(struct xfrm_state *xs, 21262306a36Sopenharmony_ci u32 *mykey, u32 *mysalt) 21362306a36Sopenharmony_ci{ 21462306a36Sopenharmony_ci struct net_device *dev = xs->xso.real_dev; 21562306a36Sopenharmony_ci unsigned char *key_data; 21662306a36Sopenharmony_ci char *alg_name = NULL; 21762306a36Sopenharmony_ci int key_len; 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci if (!xs->aead) { 22062306a36Sopenharmony_ci netdev_err(dev, "Unsupported IPsec algorithm\n"); 22162306a36Sopenharmony_ci return -EINVAL; 22262306a36Sopenharmony_ci } 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci if (xs->aead->alg_icv_len != IXGBE_IPSEC_AUTH_BITS) { 22562306a36Sopenharmony_ci netdev_err(dev, "IPsec offload requires %d bit authentication\n", 22662306a36Sopenharmony_ci IXGBE_IPSEC_AUTH_BITS); 22762306a36Sopenharmony_ci return -EINVAL; 22862306a36Sopenharmony_ci } 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci key_data = &xs->aead->alg_key[0]; 23162306a36Sopenharmony_ci key_len = xs->aead->alg_key_len; 23262306a36Sopenharmony_ci alg_name = xs->aead->alg_name; 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci if (strcmp(alg_name, aes_gcm_name)) { 23562306a36Sopenharmony_ci netdev_err(dev, "Unsupported IPsec algorithm - please use %s\n", 23662306a36Sopenharmony_ci aes_gcm_name); 23762306a36Sopenharmony_ci return -EINVAL; 23862306a36Sopenharmony_ci } 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci /* The key bytes come down in a big endian array of bytes, so 24162306a36Sopenharmony_ci * we don't need to do any byte swapping. 24262306a36Sopenharmony_ci * 160 accounts for 16 byte key and 4 byte salt 24362306a36Sopenharmony_ci */ 24462306a36Sopenharmony_ci if (key_len > IXGBE_IPSEC_KEY_BITS) { 24562306a36Sopenharmony_ci *mysalt = ((u32 *)key_data)[4]; 24662306a36Sopenharmony_ci } else if (key_len == IXGBE_IPSEC_KEY_BITS) { 24762306a36Sopenharmony_ci *mysalt = 0; 24862306a36Sopenharmony_ci } else { 24962306a36Sopenharmony_ci netdev_err(dev, "IPsec hw offload only supports keys up to 128 bits with a 32 bit salt\n"); 25062306a36Sopenharmony_ci return -EINVAL; 25162306a36Sopenharmony_ci } 25262306a36Sopenharmony_ci memcpy(mykey, key_data, 16); 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci return 0; 25562306a36Sopenharmony_ci} 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci/** 25862306a36Sopenharmony_ci * ixgbevf_ipsec_add_sa - program device with a security association 25962306a36Sopenharmony_ci * @xs: pointer to transformer state struct 26062306a36Sopenharmony_ci * @extack: extack point to fill failure reason 26162306a36Sopenharmony_ci **/ 26262306a36Sopenharmony_cistatic int ixgbevf_ipsec_add_sa(struct xfrm_state *xs, 26362306a36Sopenharmony_ci struct netlink_ext_ack *extack) 26462306a36Sopenharmony_ci{ 26562306a36Sopenharmony_ci struct net_device *dev = xs->xso.real_dev; 26662306a36Sopenharmony_ci struct ixgbevf_adapter *adapter; 26762306a36Sopenharmony_ci struct ixgbevf_ipsec *ipsec; 26862306a36Sopenharmony_ci u16 sa_idx; 26962306a36Sopenharmony_ci int ret; 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci adapter = netdev_priv(dev); 27262306a36Sopenharmony_ci ipsec = adapter->ipsec; 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci if (xs->id.proto != IPPROTO_ESP && xs->id.proto != IPPROTO_AH) { 27562306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "Unsupported protocol for IPsec offload"); 27662306a36Sopenharmony_ci return -EINVAL; 27762306a36Sopenharmony_ci } 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ci if (xs->props.mode != XFRM_MODE_TRANSPORT) { 28062306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "Unsupported mode for ipsec offload"); 28162306a36Sopenharmony_ci return -EINVAL; 28262306a36Sopenharmony_ci } 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_ci if (xs->xso.type != XFRM_DEV_OFFLOAD_CRYPTO) { 28562306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "Unsupported ipsec offload type"); 28662306a36Sopenharmony_ci return -EINVAL; 28762306a36Sopenharmony_ci } 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_ci if (xs->xso.dir == XFRM_DEV_OFFLOAD_IN) { 29062306a36Sopenharmony_ci struct rx_sa rsa; 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci if (xs->calg) { 29362306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "Compression offload not supported"); 29462306a36Sopenharmony_ci return -EINVAL; 29562306a36Sopenharmony_ci } 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci /* find the first unused index */ 29862306a36Sopenharmony_ci ret = ixgbevf_ipsec_find_empty_idx(ipsec, true); 29962306a36Sopenharmony_ci if (ret < 0) { 30062306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "No space for SA in Rx table!"); 30162306a36Sopenharmony_ci return ret; 30262306a36Sopenharmony_ci } 30362306a36Sopenharmony_ci sa_idx = (u16)ret; 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci memset(&rsa, 0, sizeof(rsa)); 30662306a36Sopenharmony_ci rsa.used = true; 30762306a36Sopenharmony_ci rsa.xs = xs; 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ci if (rsa.xs->id.proto & IPPROTO_ESP) 31062306a36Sopenharmony_ci rsa.decrypt = xs->ealg || xs->aead; 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci /* get the key and salt */ 31362306a36Sopenharmony_ci ret = ixgbevf_ipsec_parse_proto_keys(xs, rsa.key, &rsa.salt); 31462306a36Sopenharmony_ci if (ret) { 31562306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "Failed to get key data for Rx SA table"); 31662306a36Sopenharmony_ci return ret; 31762306a36Sopenharmony_ci } 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_ci /* get ip for rx sa table */ 32062306a36Sopenharmony_ci if (xs->props.family == AF_INET6) 32162306a36Sopenharmony_ci memcpy(rsa.ipaddr, &xs->id.daddr.a6, 16); 32262306a36Sopenharmony_ci else 32362306a36Sopenharmony_ci memcpy(&rsa.ipaddr[3], &xs->id.daddr.a4, 4); 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ci rsa.mode = IXGBE_RXMOD_VALID; 32662306a36Sopenharmony_ci if (rsa.xs->id.proto & IPPROTO_ESP) 32762306a36Sopenharmony_ci rsa.mode |= IXGBE_RXMOD_PROTO_ESP; 32862306a36Sopenharmony_ci if (rsa.decrypt) 32962306a36Sopenharmony_ci rsa.mode |= IXGBE_RXMOD_DECRYPT; 33062306a36Sopenharmony_ci if (rsa.xs->props.family == AF_INET6) 33162306a36Sopenharmony_ci rsa.mode |= IXGBE_RXMOD_IPV6; 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci ret = ixgbevf_ipsec_set_pf_sa(adapter, xs); 33462306a36Sopenharmony_ci if (ret < 0) 33562306a36Sopenharmony_ci return ret; 33662306a36Sopenharmony_ci rsa.pfsa = ret; 33762306a36Sopenharmony_ci 33862306a36Sopenharmony_ci /* the preparations worked, so save the info */ 33962306a36Sopenharmony_ci memcpy(&ipsec->rx_tbl[sa_idx], &rsa, sizeof(rsa)); 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_ci xs->xso.offload_handle = sa_idx + IXGBE_IPSEC_BASE_RX_INDEX; 34262306a36Sopenharmony_ci 34362306a36Sopenharmony_ci ipsec->num_rx_sa++; 34462306a36Sopenharmony_ci 34562306a36Sopenharmony_ci /* hash the new entry for faster search in Rx path */ 34662306a36Sopenharmony_ci hash_add_rcu(ipsec->rx_sa_list, &ipsec->rx_tbl[sa_idx].hlist, 34762306a36Sopenharmony_ci (__force u32)rsa.xs->id.spi); 34862306a36Sopenharmony_ci } else { 34962306a36Sopenharmony_ci struct tx_sa tsa; 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_ci /* find the first unused index */ 35262306a36Sopenharmony_ci ret = ixgbevf_ipsec_find_empty_idx(ipsec, false); 35362306a36Sopenharmony_ci if (ret < 0) { 35462306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "No space for SA in Tx table"); 35562306a36Sopenharmony_ci return ret; 35662306a36Sopenharmony_ci } 35762306a36Sopenharmony_ci sa_idx = (u16)ret; 35862306a36Sopenharmony_ci 35962306a36Sopenharmony_ci memset(&tsa, 0, sizeof(tsa)); 36062306a36Sopenharmony_ci tsa.used = true; 36162306a36Sopenharmony_ci tsa.xs = xs; 36262306a36Sopenharmony_ci 36362306a36Sopenharmony_ci if (xs->id.proto & IPPROTO_ESP) 36462306a36Sopenharmony_ci tsa.encrypt = xs->ealg || xs->aead; 36562306a36Sopenharmony_ci 36662306a36Sopenharmony_ci ret = ixgbevf_ipsec_parse_proto_keys(xs, tsa.key, &tsa.salt); 36762306a36Sopenharmony_ci if (ret) { 36862306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "Failed to get key data for Tx SA table"); 36962306a36Sopenharmony_ci memset(&tsa, 0, sizeof(tsa)); 37062306a36Sopenharmony_ci return ret; 37162306a36Sopenharmony_ci } 37262306a36Sopenharmony_ci 37362306a36Sopenharmony_ci ret = ixgbevf_ipsec_set_pf_sa(adapter, xs); 37462306a36Sopenharmony_ci if (ret < 0) 37562306a36Sopenharmony_ci return ret; 37662306a36Sopenharmony_ci tsa.pfsa = ret; 37762306a36Sopenharmony_ci 37862306a36Sopenharmony_ci /* the preparations worked, so save the info */ 37962306a36Sopenharmony_ci memcpy(&ipsec->tx_tbl[sa_idx], &tsa, sizeof(tsa)); 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_ci xs->xso.offload_handle = sa_idx + IXGBE_IPSEC_BASE_TX_INDEX; 38262306a36Sopenharmony_ci 38362306a36Sopenharmony_ci ipsec->num_tx_sa++; 38462306a36Sopenharmony_ci } 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_ci return 0; 38762306a36Sopenharmony_ci} 38862306a36Sopenharmony_ci 38962306a36Sopenharmony_ci/** 39062306a36Sopenharmony_ci * ixgbevf_ipsec_del_sa - clear out this specific SA 39162306a36Sopenharmony_ci * @xs: pointer to transformer state struct 39262306a36Sopenharmony_ci **/ 39362306a36Sopenharmony_cistatic void ixgbevf_ipsec_del_sa(struct xfrm_state *xs) 39462306a36Sopenharmony_ci{ 39562306a36Sopenharmony_ci struct net_device *dev = xs->xso.real_dev; 39662306a36Sopenharmony_ci struct ixgbevf_adapter *adapter; 39762306a36Sopenharmony_ci struct ixgbevf_ipsec *ipsec; 39862306a36Sopenharmony_ci u16 sa_idx; 39962306a36Sopenharmony_ci 40062306a36Sopenharmony_ci adapter = netdev_priv(dev); 40162306a36Sopenharmony_ci ipsec = adapter->ipsec; 40262306a36Sopenharmony_ci 40362306a36Sopenharmony_ci if (xs->xso.dir == XFRM_DEV_OFFLOAD_IN) { 40462306a36Sopenharmony_ci sa_idx = xs->xso.offload_handle - IXGBE_IPSEC_BASE_RX_INDEX; 40562306a36Sopenharmony_ci 40662306a36Sopenharmony_ci if (!ipsec->rx_tbl[sa_idx].used) { 40762306a36Sopenharmony_ci netdev_err(dev, "Invalid Rx SA selected sa_idx=%d offload_handle=%lu\n", 40862306a36Sopenharmony_ci sa_idx, xs->xso.offload_handle); 40962306a36Sopenharmony_ci return; 41062306a36Sopenharmony_ci } 41162306a36Sopenharmony_ci 41262306a36Sopenharmony_ci ixgbevf_ipsec_del_pf_sa(adapter, ipsec->rx_tbl[sa_idx].pfsa); 41362306a36Sopenharmony_ci hash_del_rcu(&ipsec->rx_tbl[sa_idx].hlist); 41462306a36Sopenharmony_ci memset(&ipsec->rx_tbl[sa_idx], 0, sizeof(struct rx_sa)); 41562306a36Sopenharmony_ci ipsec->num_rx_sa--; 41662306a36Sopenharmony_ci } else { 41762306a36Sopenharmony_ci sa_idx = xs->xso.offload_handle - IXGBE_IPSEC_BASE_TX_INDEX; 41862306a36Sopenharmony_ci 41962306a36Sopenharmony_ci if (!ipsec->tx_tbl[sa_idx].used) { 42062306a36Sopenharmony_ci netdev_err(dev, "Invalid Tx SA selected sa_idx=%d offload_handle=%lu\n", 42162306a36Sopenharmony_ci sa_idx, xs->xso.offload_handle); 42262306a36Sopenharmony_ci return; 42362306a36Sopenharmony_ci } 42462306a36Sopenharmony_ci 42562306a36Sopenharmony_ci ixgbevf_ipsec_del_pf_sa(adapter, ipsec->tx_tbl[sa_idx].pfsa); 42662306a36Sopenharmony_ci memset(&ipsec->tx_tbl[sa_idx], 0, sizeof(struct tx_sa)); 42762306a36Sopenharmony_ci ipsec->num_tx_sa--; 42862306a36Sopenharmony_ci } 42962306a36Sopenharmony_ci} 43062306a36Sopenharmony_ci 43162306a36Sopenharmony_ci/** 43262306a36Sopenharmony_ci * ixgbevf_ipsec_offload_ok - can this packet use the xfrm hw offload 43362306a36Sopenharmony_ci * @skb: current data packet 43462306a36Sopenharmony_ci * @xs: pointer to transformer state struct 43562306a36Sopenharmony_ci **/ 43662306a36Sopenharmony_cistatic bool ixgbevf_ipsec_offload_ok(struct sk_buff *skb, struct xfrm_state *xs) 43762306a36Sopenharmony_ci{ 43862306a36Sopenharmony_ci if (xs->props.family == AF_INET) { 43962306a36Sopenharmony_ci /* Offload with IPv4 options is not supported yet */ 44062306a36Sopenharmony_ci if (ip_hdr(skb)->ihl != 5) 44162306a36Sopenharmony_ci return false; 44262306a36Sopenharmony_ci } else { 44362306a36Sopenharmony_ci /* Offload with IPv6 extension headers is not support yet */ 44462306a36Sopenharmony_ci if (ipv6_ext_hdr(ipv6_hdr(skb)->nexthdr)) 44562306a36Sopenharmony_ci return false; 44662306a36Sopenharmony_ci } 44762306a36Sopenharmony_ci 44862306a36Sopenharmony_ci return true; 44962306a36Sopenharmony_ci} 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_cistatic const struct xfrmdev_ops ixgbevf_xfrmdev_ops = { 45262306a36Sopenharmony_ci .xdo_dev_state_add = ixgbevf_ipsec_add_sa, 45362306a36Sopenharmony_ci .xdo_dev_state_delete = ixgbevf_ipsec_del_sa, 45462306a36Sopenharmony_ci .xdo_dev_offload_ok = ixgbevf_ipsec_offload_ok, 45562306a36Sopenharmony_ci}; 45662306a36Sopenharmony_ci 45762306a36Sopenharmony_ci/** 45862306a36Sopenharmony_ci * ixgbevf_ipsec_tx - setup Tx flags for IPsec offload 45962306a36Sopenharmony_ci * @tx_ring: outgoing context 46062306a36Sopenharmony_ci * @first: current data packet 46162306a36Sopenharmony_ci * @itd: ipsec Tx data for later use in building context descriptor 46262306a36Sopenharmony_ci **/ 46362306a36Sopenharmony_ciint ixgbevf_ipsec_tx(struct ixgbevf_ring *tx_ring, 46462306a36Sopenharmony_ci struct ixgbevf_tx_buffer *first, 46562306a36Sopenharmony_ci struct ixgbevf_ipsec_tx_data *itd) 46662306a36Sopenharmony_ci{ 46762306a36Sopenharmony_ci struct ixgbevf_adapter *adapter = netdev_priv(tx_ring->netdev); 46862306a36Sopenharmony_ci struct ixgbevf_ipsec *ipsec = adapter->ipsec; 46962306a36Sopenharmony_ci struct xfrm_state *xs; 47062306a36Sopenharmony_ci struct sec_path *sp; 47162306a36Sopenharmony_ci struct tx_sa *tsa; 47262306a36Sopenharmony_ci u16 sa_idx; 47362306a36Sopenharmony_ci 47462306a36Sopenharmony_ci sp = skb_sec_path(first->skb); 47562306a36Sopenharmony_ci if (unlikely(!sp->len)) { 47662306a36Sopenharmony_ci netdev_err(tx_ring->netdev, "%s: no xfrm state len = %d\n", 47762306a36Sopenharmony_ci __func__, sp->len); 47862306a36Sopenharmony_ci return 0; 47962306a36Sopenharmony_ci } 48062306a36Sopenharmony_ci 48162306a36Sopenharmony_ci xs = xfrm_input_state(first->skb); 48262306a36Sopenharmony_ci if (unlikely(!xs)) { 48362306a36Sopenharmony_ci netdev_err(tx_ring->netdev, "%s: no xfrm_input_state() xs = %p\n", 48462306a36Sopenharmony_ci __func__, xs); 48562306a36Sopenharmony_ci return 0; 48662306a36Sopenharmony_ci } 48762306a36Sopenharmony_ci 48862306a36Sopenharmony_ci sa_idx = xs->xso.offload_handle - IXGBE_IPSEC_BASE_TX_INDEX; 48962306a36Sopenharmony_ci if (unlikely(sa_idx >= IXGBE_IPSEC_MAX_SA_COUNT)) { 49062306a36Sopenharmony_ci netdev_err(tx_ring->netdev, "%s: bad sa_idx=%d handle=%lu\n", 49162306a36Sopenharmony_ci __func__, sa_idx, xs->xso.offload_handle); 49262306a36Sopenharmony_ci return 0; 49362306a36Sopenharmony_ci } 49462306a36Sopenharmony_ci 49562306a36Sopenharmony_ci tsa = &ipsec->tx_tbl[sa_idx]; 49662306a36Sopenharmony_ci if (unlikely(!tsa->used)) { 49762306a36Sopenharmony_ci netdev_err(tx_ring->netdev, "%s: unused sa_idx=%d\n", 49862306a36Sopenharmony_ci __func__, sa_idx); 49962306a36Sopenharmony_ci return 0; 50062306a36Sopenharmony_ci } 50162306a36Sopenharmony_ci 50262306a36Sopenharmony_ci itd->pfsa = tsa->pfsa - IXGBE_IPSEC_BASE_TX_INDEX; 50362306a36Sopenharmony_ci 50462306a36Sopenharmony_ci first->tx_flags |= IXGBE_TX_FLAGS_IPSEC | IXGBE_TX_FLAGS_CSUM; 50562306a36Sopenharmony_ci 50662306a36Sopenharmony_ci if (xs->id.proto == IPPROTO_ESP) { 50762306a36Sopenharmony_ci itd->flags |= IXGBE_ADVTXD_TUCMD_IPSEC_TYPE_ESP | 50862306a36Sopenharmony_ci IXGBE_ADVTXD_TUCMD_L4T_TCP; 50962306a36Sopenharmony_ci if (first->protocol == htons(ETH_P_IP)) 51062306a36Sopenharmony_ci itd->flags |= IXGBE_ADVTXD_TUCMD_IPV4; 51162306a36Sopenharmony_ci 51262306a36Sopenharmony_ci /* The actual trailer length is authlen (16 bytes) plus 51362306a36Sopenharmony_ci * 2 bytes for the proto and the padlen values, plus 51462306a36Sopenharmony_ci * padlen bytes of padding. This ends up not the same 51562306a36Sopenharmony_ci * as the static value found in xs->props.trailer_len (21). 51662306a36Sopenharmony_ci * 51762306a36Sopenharmony_ci * ... but if we're doing GSO, don't bother as the stack 51862306a36Sopenharmony_ci * doesn't add a trailer for those. 51962306a36Sopenharmony_ci */ 52062306a36Sopenharmony_ci if (!skb_is_gso(first->skb)) { 52162306a36Sopenharmony_ci /* The "correct" way to get the auth length would be 52262306a36Sopenharmony_ci * to use 52362306a36Sopenharmony_ci * authlen = crypto_aead_authsize(xs->data); 52462306a36Sopenharmony_ci * but since we know we only have one size to worry 52562306a36Sopenharmony_ci * about * we can let the compiler use the constant 52662306a36Sopenharmony_ci * and save us a few CPU cycles. 52762306a36Sopenharmony_ci */ 52862306a36Sopenharmony_ci const int authlen = IXGBE_IPSEC_AUTH_BITS / 8; 52962306a36Sopenharmony_ci struct sk_buff *skb = first->skb; 53062306a36Sopenharmony_ci u8 padlen; 53162306a36Sopenharmony_ci int ret; 53262306a36Sopenharmony_ci 53362306a36Sopenharmony_ci ret = skb_copy_bits(skb, skb->len - (authlen + 2), 53462306a36Sopenharmony_ci &padlen, 1); 53562306a36Sopenharmony_ci if (unlikely(ret)) 53662306a36Sopenharmony_ci return 0; 53762306a36Sopenharmony_ci itd->trailer_len = authlen + 2 + padlen; 53862306a36Sopenharmony_ci } 53962306a36Sopenharmony_ci } 54062306a36Sopenharmony_ci if (tsa->encrypt) 54162306a36Sopenharmony_ci itd->flags |= IXGBE_ADVTXD_TUCMD_IPSEC_ENCRYPT_EN; 54262306a36Sopenharmony_ci 54362306a36Sopenharmony_ci return 1; 54462306a36Sopenharmony_ci} 54562306a36Sopenharmony_ci 54662306a36Sopenharmony_ci/** 54762306a36Sopenharmony_ci * ixgbevf_ipsec_rx - decode IPsec bits from Rx descriptor 54862306a36Sopenharmony_ci * @rx_ring: receiving ring 54962306a36Sopenharmony_ci * @rx_desc: receive data descriptor 55062306a36Sopenharmony_ci * @skb: current data packet 55162306a36Sopenharmony_ci * 55262306a36Sopenharmony_ci * Determine if there was an IPsec encapsulation noticed, and if so set up 55362306a36Sopenharmony_ci * the resulting status for later in the receive stack. 55462306a36Sopenharmony_ci **/ 55562306a36Sopenharmony_civoid ixgbevf_ipsec_rx(struct ixgbevf_ring *rx_ring, 55662306a36Sopenharmony_ci union ixgbe_adv_rx_desc *rx_desc, 55762306a36Sopenharmony_ci struct sk_buff *skb) 55862306a36Sopenharmony_ci{ 55962306a36Sopenharmony_ci struct ixgbevf_adapter *adapter = netdev_priv(rx_ring->netdev); 56062306a36Sopenharmony_ci __le16 pkt_info = rx_desc->wb.lower.lo_dword.hs_rss.pkt_info; 56162306a36Sopenharmony_ci __le16 ipsec_pkt_types = cpu_to_le16(IXGBE_RXDADV_PKTTYPE_IPSEC_AH | 56262306a36Sopenharmony_ci IXGBE_RXDADV_PKTTYPE_IPSEC_ESP); 56362306a36Sopenharmony_ci struct ixgbevf_ipsec *ipsec = adapter->ipsec; 56462306a36Sopenharmony_ci struct xfrm_offload *xo = NULL; 56562306a36Sopenharmony_ci struct xfrm_state *xs = NULL; 56662306a36Sopenharmony_ci struct ipv6hdr *ip6 = NULL; 56762306a36Sopenharmony_ci struct iphdr *ip4 = NULL; 56862306a36Sopenharmony_ci struct sec_path *sp; 56962306a36Sopenharmony_ci void *daddr; 57062306a36Sopenharmony_ci __be32 spi; 57162306a36Sopenharmony_ci u8 *c_hdr; 57262306a36Sopenharmony_ci u8 proto; 57362306a36Sopenharmony_ci 57462306a36Sopenharmony_ci /* Find the IP and crypto headers in the data. 57562306a36Sopenharmony_ci * We can assume no VLAN header in the way, b/c the 57662306a36Sopenharmony_ci * hw won't recognize the IPsec packet and anyway the 57762306a36Sopenharmony_ci * currently VLAN device doesn't support xfrm offload. 57862306a36Sopenharmony_ci */ 57962306a36Sopenharmony_ci if (pkt_info & cpu_to_le16(IXGBE_RXDADV_PKTTYPE_IPV4)) { 58062306a36Sopenharmony_ci ip4 = (struct iphdr *)(skb->data + ETH_HLEN); 58162306a36Sopenharmony_ci daddr = &ip4->daddr; 58262306a36Sopenharmony_ci c_hdr = (u8 *)ip4 + ip4->ihl * 4; 58362306a36Sopenharmony_ci } else if (pkt_info & cpu_to_le16(IXGBE_RXDADV_PKTTYPE_IPV6)) { 58462306a36Sopenharmony_ci ip6 = (struct ipv6hdr *)(skb->data + ETH_HLEN); 58562306a36Sopenharmony_ci daddr = &ip6->daddr; 58662306a36Sopenharmony_ci c_hdr = (u8 *)ip6 + sizeof(struct ipv6hdr); 58762306a36Sopenharmony_ci } else { 58862306a36Sopenharmony_ci return; 58962306a36Sopenharmony_ci } 59062306a36Sopenharmony_ci 59162306a36Sopenharmony_ci switch (pkt_info & ipsec_pkt_types) { 59262306a36Sopenharmony_ci case cpu_to_le16(IXGBE_RXDADV_PKTTYPE_IPSEC_AH): 59362306a36Sopenharmony_ci spi = ((struct ip_auth_hdr *)c_hdr)->spi; 59462306a36Sopenharmony_ci proto = IPPROTO_AH; 59562306a36Sopenharmony_ci break; 59662306a36Sopenharmony_ci case cpu_to_le16(IXGBE_RXDADV_PKTTYPE_IPSEC_ESP): 59762306a36Sopenharmony_ci spi = ((struct ip_esp_hdr *)c_hdr)->spi; 59862306a36Sopenharmony_ci proto = IPPROTO_ESP; 59962306a36Sopenharmony_ci break; 60062306a36Sopenharmony_ci default: 60162306a36Sopenharmony_ci return; 60262306a36Sopenharmony_ci } 60362306a36Sopenharmony_ci 60462306a36Sopenharmony_ci xs = ixgbevf_ipsec_find_rx_state(ipsec, daddr, proto, spi, !!ip4); 60562306a36Sopenharmony_ci if (unlikely(!xs)) 60662306a36Sopenharmony_ci return; 60762306a36Sopenharmony_ci 60862306a36Sopenharmony_ci sp = secpath_set(skb); 60962306a36Sopenharmony_ci if (unlikely(!sp)) 61062306a36Sopenharmony_ci return; 61162306a36Sopenharmony_ci 61262306a36Sopenharmony_ci sp->xvec[sp->len++] = xs; 61362306a36Sopenharmony_ci sp->olen++; 61462306a36Sopenharmony_ci xo = xfrm_offload(skb); 61562306a36Sopenharmony_ci xo->flags = CRYPTO_DONE; 61662306a36Sopenharmony_ci xo->status = CRYPTO_SUCCESS; 61762306a36Sopenharmony_ci 61862306a36Sopenharmony_ci adapter->rx_ipsec++; 61962306a36Sopenharmony_ci} 62062306a36Sopenharmony_ci 62162306a36Sopenharmony_ci/** 62262306a36Sopenharmony_ci * ixgbevf_init_ipsec_offload - initialize registers for IPsec operation 62362306a36Sopenharmony_ci * @adapter: board private structure 62462306a36Sopenharmony_ci **/ 62562306a36Sopenharmony_civoid ixgbevf_init_ipsec_offload(struct ixgbevf_adapter *adapter) 62662306a36Sopenharmony_ci{ 62762306a36Sopenharmony_ci struct ixgbevf_ipsec *ipsec; 62862306a36Sopenharmony_ci size_t size; 62962306a36Sopenharmony_ci 63062306a36Sopenharmony_ci switch (adapter->hw.api_version) { 63162306a36Sopenharmony_ci case ixgbe_mbox_api_14: 63262306a36Sopenharmony_ci case ixgbe_mbox_api_15: 63362306a36Sopenharmony_ci break; 63462306a36Sopenharmony_ci default: 63562306a36Sopenharmony_ci return; 63662306a36Sopenharmony_ci } 63762306a36Sopenharmony_ci 63862306a36Sopenharmony_ci ipsec = kzalloc(sizeof(*ipsec), GFP_KERNEL); 63962306a36Sopenharmony_ci if (!ipsec) 64062306a36Sopenharmony_ci goto err1; 64162306a36Sopenharmony_ci hash_init(ipsec->rx_sa_list); 64262306a36Sopenharmony_ci 64362306a36Sopenharmony_ci size = sizeof(struct rx_sa) * IXGBE_IPSEC_MAX_SA_COUNT; 64462306a36Sopenharmony_ci ipsec->rx_tbl = kzalloc(size, GFP_KERNEL); 64562306a36Sopenharmony_ci if (!ipsec->rx_tbl) 64662306a36Sopenharmony_ci goto err2; 64762306a36Sopenharmony_ci 64862306a36Sopenharmony_ci size = sizeof(struct tx_sa) * IXGBE_IPSEC_MAX_SA_COUNT; 64962306a36Sopenharmony_ci ipsec->tx_tbl = kzalloc(size, GFP_KERNEL); 65062306a36Sopenharmony_ci if (!ipsec->tx_tbl) 65162306a36Sopenharmony_ci goto err2; 65262306a36Sopenharmony_ci 65362306a36Sopenharmony_ci ipsec->num_rx_sa = 0; 65462306a36Sopenharmony_ci ipsec->num_tx_sa = 0; 65562306a36Sopenharmony_ci 65662306a36Sopenharmony_ci adapter->ipsec = ipsec; 65762306a36Sopenharmony_ci 65862306a36Sopenharmony_ci adapter->netdev->xfrmdev_ops = &ixgbevf_xfrmdev_ops; 65962306a36Sopenharmony_ci 66062306a36Sopenharmony_ci#define IXGBEVF_ESP_FEATURES (NETIF_F_HW_ESP | \ 66162306a36Sopenharmony_ci NETIF_F_HW_ESP_TX_CSUM | \ 66262306a36Sopenharmony_ci NETIF_F_GSO_ESP) 66362306a36Sopenharmony_ci 66462306a36Sopenharmony_ci adapter->netdev->features |= IXGBEVF_ESP_FEATURES; 66562306a36Sopenharmony_ci adapter->netdev->hw_enc_features |= IXGBEVF_ESP_FEATURES; 66662306a36Sopenharmony_ci 66762306a36Sopenharmony_ci return; 66862306a36Sopenharmony_ci 66962306a36Sopenharmony_cierr2: 67062306a36Sopenharmony_ci kfree(ipsec->rx_tbl); 67162306a36Sopenharmony_ci kfree(ipsec->tx_tbl); 67262306a36Sopenharmony_ci kfree(ipsec); 67362306a36Sopenharmony_cierr1: 67462306a36Sopenharmony_ci netdev_err(adapter->netdev, "Unable to allocate memory for SA tables"); 67562306a36Sopenharmony_ci} 67662306a36Sopenharmony_ci 67762306a36Sopenharmony_ci/** 67862306a36Sopenharmony_ci * ixgbevf_stop_ipsec_offload - tear down the IPsec offload 67962306a36Sopenharmony_ci * @adapter: board private structure 68062306a36Sopenharmony_ci **/ 68162306a36Sopenharmony_civoid ixgbevf_stop_ipsec_offload(struct ixgbevf_adapter *adapter) 68262306a36Sopenharmony_ci{ 68362306a36Sopenharmony_ci struct ixgbevf_ipsec *ipsec = adapter->ipsec; 68462306a36Sopenharmony_ci 68562306a36Sopenharmony_ci adapter->ipsec = NULL; 68662306a36Sopenharmony_ci if (ipsec) { 68762306a36Sopenharmony_ci kfree(ipsec->rx_tbl); 68862306a36Sopenharmony_ci kfree(ipsec->tx_tbl); 68962306a36Sopenharmony_ci kfree(ipsec); 69062306a36Sopenharmony_ci } 69162306a36Sopenharmony_ci} 692