| /* |
| * This file is part of the Chelsio T6 Crypto driver for Linux. |
| * |
| * Copyright (c) 2003-2017 Chelsio Communications, Inc. All rights reserved. |
| * |
| * This software is available to you under a choice of one of two |
| * licenses. You may choose to be licensed under the terms of the GNU |
| * General Public License (GPL) Version 2, available from the file |
| * COPYING in the main directory of this source tree, or the |
| * OpenIB.org BSD license below: |
| * |
| * Redistribution and use in source and binary forms, with or |
| * without modification, are permitted provided that the following |
| * conditions are met: |
| * |
| * - Redistributions of source code must retain the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer. |
| * |
| * - Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer in the documentation and/or other materials |
| * provided with the distribution. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS |
| * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
| * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
| * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| * SOFTWARE. |
| * |
| * Written and Maintained by: |
| * Atul Gupta (atul.gupta@chelsio.com) |
| */ |
| |
| #define pr_fmt(fmt) "ch_ipsec: " fmt |
| |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/crypto.h> |
| #include <linux/skbuff.h> |
| #include <linux/rtnetlink.h> |
| #include <linux/highmem.h> |
| #include <linux/if_vlan.h> |
| #include <linux/ip.h> |
| #include <linux/netdevice.h> |
| #include <net/esp.h> |
| #include <net/xfrm.h> |
| #include <crypto/aes.h> |
| #include <crypto/algapi.h> |
| #include <crypto/hash.h> |
| #include <crypto/sha1.h> |
| #include <crypto/sha2.h> |
| #include <crypto/authenc.h> |
| #include <crypto/internal/aead.h> |
| #include <crypto/null.h> |
| #include <crypto/internal/skcipher.h> |
| #include <crypto/aead.h> |
| #include <crypto/scatterwalk.h> |
| #include <crypto/internal/hash.h> |
| |
| #include "chcr_ipsec.h" |
| |
| /* |
| * Max Tx descriptor space we allow for an Ethernet packet to be inlined |
| * into a WR. |
| */ |
| #define MAX_IMM_TX_PKT_LEN 256 |
| #define GCM_ESP_IV_SIZE 8 |
| |
| static LIST_HEAD(uld_ctx_list); |
| static DEFINE_MUTEX(dev_mutex); |
| |
| static bool ch_ipsec_offload_ok(struct sk_buff *skb, struct xfrm_state *x); |
| static int ch_ipsec_uld_state_change(void *handle, enum cxgb4_state new_state); |
| static int ch_ipsec_xmit(struct sk_buff *skb, struct net_device *dev); |
| static void *ch_ipsec_uld_add(const struct cxgb4_lld_info *infop); |
| static void ch_ipsec_advance_esn_state(struct xfrm_state *x); |
| static void ch_ipsec_xfrm_free_state(struct xfrm_state *x); |
| static void ch_ipsec_xfrm_del_state(struct xfrm_state *x); |
| static int ch_ipsec_xfrm_add_state(struct xfrm_state *x); |
| |
| static const struct xfrmdev_ops ch_ipsec_xfrmdev_ops = { |
| .xdo_dev_state_add = ch_ipsec_xfrm_add_state, |
| .xdo_dev_state_delete = ch_ipsec_xfrm_del_state, |
| .xdo_dev_state_free = ch_ipsec_xfrm_free_state, |
| .xdo_dev_offload_ok = ch_ipsec_offload_ok, |
| .xdo_dev_state_advance_esn = ch_ipsec_advance_esn_state, |
| }; |
| |
| static struct cxgb4_uld_info ch_ipsec_uld_info = { |
| .name = CHIPSEC_DRV_MODULE_NAME, |
| .add = ch_ipsec_uld_add, |
| .state_change = ch_ipsec_uld_state_change, |
| .tx_handler = ch_ipsec_xmit, |
| .xfrmdev_ops = &ch_ipsec_xfrmdev_ops, |
| }; |
| |
| static void *ch_ipsec_uld_add(const struct cxgb4_lld_info *infop) |
| { |
| struct ipsec_uld_ctx *u_ctx; |
| |
| pr_info_once("%s - version %s\n", CHIPSEC_DRV_DESC, |
| CHIPSEC_DRV_VERSION); |
| u_ctx = kzalloc(sizeof(*u_ctx), GFP_KERNEL); |
| if (!u_ctx) { |
| u_ctx = ERR_PTR(-ENOMEM); |
| goto out; |
| } |
| u_ctx->lldi = *infop; |
| out: |
| return u_ctx; |
| } |
| |
| static int ch_ipsec_uld_state_change(void *handle, enum cxgb4_state new_state) |
| { |
| struct ipsec_uld_ctx *u_ctx = handle; |
| |
| pr_debug("new_state %u\n", new_state); |
| switch (new_state) { |
| case CXGB4_STATE_UP: |
| pr_info("%s: Up\n", pci_name(u_ctx->lldi.pdev)); |
| mutex_lock(&dev_mutex); |
| list_add_tail(&u_ctx->entry, &uld_ctx_list); |
| mutex_unlock(&dev_mutex); |
| break; |
| case CXGB4_STATE_START_RECOVERY: |
| case CXGB4_STATE_DOWN: |
| case CXGB4_STATE_DETACH: |
| pr_info("%s: Down\n", pci_name(u_ctx->lldi.pdev)); |
| list_del(&u_ctx->entry); |
| break; |
| default: |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static int ch_ipsec_setauthsize(struct xfrm_state *x, |
| struct ipsec_sa_entry *sa_entry) |
| { |
| int hmac_ctrl; |
| int authsize = x->aead->alg_icv_len / 8; |
| |
| sa_entry->authsize = authsize; |
| |
| switch (authsize) { |
| case ICV_8: |
| hmac_ctrl = CHCR_SCMD_HMAC_CTRL_DIV2; |
| break; |
| case ICV_12: |
| hmac_ctrl = CHCR_SCMD_HMAC_CTRL_IPSEC_96BIT; |
| break; |
| case ICV_16: |
| hmac_ctrl = CHCR_SCMD_HMAC_CTRL_NO_TRUNC; |
| break; |
| default: |
| return -EINVAL; |
| } |
| return hmac_ctrl; |
| } |
| |
| static int ch_ipsec_setkey(struct xfrm_state *x, |
| struct ipsec_sa_entry *sa_entry) |
| { |
| int keylen = (x->aead->alg_key_len + 7) / 8; |
| unsigned char *key = x->aead->alg_key; |
| int ck_size, key_ctx_size = 0; |
| unsigned char ghash_h[AEAD_H_SIZE]; |
| struct crypto_aes_ctx aes; |
| int ret = 0; |
| |
| if (keylen > 3) { |
| keylen -= 4; /* nonce/salt is present in the last 4 bytes */ |
| memcpy(sa_entry->salt, key + keylen, 4); |
| } |
| |
| if (keylen == AES_KEYSIZE_128) { |
| ck_size = CHCR_KEYCTX_CIPHER_KEY_SIZE_128; |
| } else if (keylen == AES_KEYSIZE_192) { |
| ck_size = CHCR_KEYCTX_CIPHER_KEY_SIZE_192; |
| } else if (keylen == AES_KEYSIZE_256) { |
| ck_size = CHCR_KEYCTX_CIPHER_KEY_SIZE_256; |
| } else { |
| pr_err("GCM: Invalid key length %d\n", keylen); |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| memcpy(sa_entry->key, key, keylen); |
| sa_entry->enckey_len = keylen; |
| key_ctx_size = sizeof(struct _key_ctx) + |
| ((DIV_ROUND_UP(keylen, 16)) << 4) + |
| AEAD_H_SIZE; |
| |
| sa_entry->key_ctx_hdr = FILL_KEY_CTX_HDR(ck_size, |
| CHCR_KEYCTX_MAC_KEY_SIZE_128, |
| 0, 0, |
| key_ctx_size >> 4); |
| |
| /* Calculate the H = CIPH(K, 0 repeated 16 times). |
| * It will go in key context |
| */ |
| ret = aes_expandkey(&aes, key, keylen); |
| if (ret) { |
| sa_entry->enckey_len = 0; |
| goto out; |
| } |
| memset(ghash_h, 0, AEAD_H_SIZE); |
| aes_encrypt(&aes, ghash_h, ghash_h); |
| memzero_explicit(&aes, sizeof(aes)); |
| |
| memcpy(sa_entry->key + (DIV_ROUND_UP(sa_entry->enckey_len, 16) * |
| 16), ghash_h, AEAD_H_SIZE); |
| sa_entry->kctx_len = ((DIV_ROUND_UP(sa_entry->enckey_len, 16)) << 4) + |
| AEAD_H_SIZE; |
| out: |
| return ret; |
| } |
| |
| /* |
| * ch_ipsec_xfrm_add_state |
| * returns 0 on success, negative error if failed to send message to FPGA |
| * positive error if FPGA returned a bad response |
| */ |
| static int ch_ipsec_xfrm_add_state(struct xfrm_state *x) |
| { |
| struct ipsec_sa_entry *sa_entry; |
| int res = 0; |
| |
| if (x->props.aalgo != SADB_AALG_NONE) { |
| pr_debug("Cannot offload authenticated xfrm states\n"); |
| return -EINVAL; |
| } |
| if (x->props.calgo != SADB_X_CALG_NONE) { |
| pr_debug("Cannot offload compressed xfrm states\n"); |
| return -EINVAL; |
| } |
| if (x->props.family != AF_INET && |
| x->props.family != AF_INET6) { |
| pr_debug("Only IPv4/6 xfrm state offloaded\n"); |
| return -EINVAL; |
| } |
| if (x->props.mode != XFRM_MODE_TRANSPORT && |
| x->props.mode != XFRM_MODE_TUNNEL) { |
| pr_debug("Only transport and tunnel xfrm offload\n"); |
| return -EINVAL; |
| } |
| if (x->id.proto != IPPROTO_ESP) { |
| pr_debug("Only ESP xfrm state offloaded\n"); |
| return -EINVAL; |
| } |
| if (x->encap) { |
| pr_debug("Encapsulated xfrm state not offloaded\n"); |
| return -EINVAL; |
| } |
| if (!x->aead) { |
| pr_debug("Cannot offload xfrm states without aead\n"); |
| return -EINVAL; |
| } |
| if (x->aead->alg_icv_len != 128 && |
| x->aead->alg_icv_len != 96) { |
| pr_debug("Cannot offload xfrm states with AEAD ICV length other than 96b & 128b\n"); |
| return -EINVAL; |
| } |
| if ((x->aead->alg_key_len != 128 + 32) && |
| (x->aead->alg_key_len != 256 + 32)) { |
| pr_debug("cannot offload xfrm states with AEAD key length other than 128/256 bit\n"); |
| return -EINVAL; |
| } |
| if (x->tfcpad) { |
| pr_debug("Cannot offload xfrm states with tfc padding\n"); |
| return -EINVAL; |
| } |
| if (!x->geniv) { |
| pr_debug("Cannot offload xfrm states without geniv\n"); |
| return -EINVAL; |
| } |
| if (strcmp(x->geniv, "seqiv")) { |
| pr_debug("Cannot offload xfrm states with geniv other than seqiv\n"); |
| return -EINVAL; |
| } |
| |
| sa_entry = kzalloc(sizeof(*sa_entry), GFP_KERNEL); |
| if (!sa_entry) { |
| res = -ENOMEM; |
| goto out; |
| } |
| |
| sa_entry->hmac_ctrl = ch_ipsec_setauthsize(x, sa_entry); |
| if (x->props.flags & XFRM_STATE_ESN) |
| sa_entry->esn = 1; |
| ch_ipsec_setkey(x, sa_entry); |
| x->xso.offload_handle = (unsigned long)sa_entry; |
| try_module_get(THIS_MODULE); |
| out: |
| return res; |
| } |
| |
| static void ch_ipsec_xfrm_del_state(struct xfrm_state *x) |
| { |
| /* do nothing */ |
| if (!x->xso.offload_handle) |
| return; |
| } |
| |
| static void ch_ipsec_xfrm_free_state(struct xfrm_state *x) |
| { |
| struct ipsec_sa_entry *sa_entry; |
| |
| if (!x->xso.offload_handle) |
| return; |
| |
| sa_entry = (struct ipsec_sa_entry *)x->xso.offload_handle; |
| kfree(sa_entry); |
| module_put(THIS_MODULE); |
| } |
| |
| static bool ch_ipsec_offload_ok(struct sk_buff *skb, struct xfrm_state *x) |
| { |
| if (x->props.family == AF_INET) { |
| /* Offload with IP options is not supported yet */ |
| if (ip_hdr(skb)->ihl > 5) |
| return false; |
| } else { |
| /* Offload with IPv6 extension headers is not support yet */ |
| if (ipv6_ext_hdr(ipv6_hdr(skb)->nexthdr)) |
| return false; |
| } |
| return true; |
| } |
| |
| static void ch_ipsec_advance_esn_state(struct xfrm_state *x) |
| { |
| /* do nothing */ |
| if (!x->xso.offload_handle) |
| return; |
| } |
| |
| static int is_eth_imm(const struct sk_buff *skb, |
| struct ipsec_sa_entry *sa_entry) |
| { |
| unsigned int kctx_len; |
| int hdrlen; |
| |
| kctx_len = sa_entry->kctx_len; |
| hdrlen = sizeof(struct fw_ulptx_wr) + |
| sizeof(struct chcr_ipsec_req) + kctx_len; |
| |
| hdrlen += sizeof(struct cpl_tx_pkt); |
| if (sa_entry->esn) |
| hdrlen += (DIV_ROUND_UP(sizeof(struct chcr_ipsec_aadiv), 16) |
| << 4); |
| if (skb->len <= MAX_IMM_TX_PKT_LEN - hdrlen) |
| return hdrlen; |
| return 0; |
| } |
| |
| static unsigned int calc_tx_sec_flits(const struct sk_buff *skb, |
| struct ipsec_sa_entry *sa_entry, |
| bool *immediate) |
| { |
| unsigned int kctx_len; |
| unsigned int flits; |
| int aadivlen; |
| int hdrlen; |
| |
| kctx_len = sa_entry->kctx_len; |
| hdrlen = is_eth_imm(skb, sa_entry); |
| aadivlen = sa_entry->esn ? DIV_ROUND_UP(sizeof(struct chcr_ipsec_aadiv), |
| 16) : 0; |
| aadivlen <<= 4; |
| |
| /* If the skb is small enough, we can pump it out as a work request |
| * with only immediate data. In that case we just have to have the |
| * TX Packet header plus the skb data in the Work Request. |
| */ |
| |
| if (hdrlen) { |
| *immediate = true; |
| return DIV_ROUND_UP(skb->len + hdrlen, sizeof(__be64)); |
| } |
| |
| flits = sgl_len(skb_shinfo(skb)->nr_frags + 1); |
| |
| /* Otherwise, we're going to have to construct a Scatter gather list |
| * of the skb body and fragments. We also include the flits necessary |
| * for the TX Packet Work Request and CPL. We always have a firmware |
| * Write Header (incorporated as part of the cpl_tx_pkt_lso and |
| * cpl_tx_pkt structures), followed by either a TX Packet Write CPL |
| * message or, if we're doing a Large Send Offload, an LSO CPL message |
| * with an embedded TX Packet Write CPL message. |
| */ |
| flits += (sizeof(struct fw_ulptx_wr) + |
| sizeof(struct chcr_ipsec_req) + |
| kctx_len + |
| sizeof(struct cpl_tx_pkt_core) + |
| aadivlen) / sizeof(__be64); |
| return flits; |
| } |
| |
| static void *copy_esn_pktxt(struct sk_buff *skb, |
| struct net_device *dev, |
| void *pos, |
| struct ipsec_sa_entry *sa_entry) |
| { |
| struct chcr_ipsec_aadiv *aadiv; |
| struct ulptx_idata *sc_imm; |
| struct ip_esp_hdr *esphdr; |
| struct xfrm_offload *xo; |
| struct sge_eth_txq *q; |
| struct adapter *adap; |
| struct port_info *pi; |
| __be64 seqno; |
| u32 qidx; |
| u32 seqlo; |
| u8 *iv; |
| int eoq; |
| int len; |
| |
| pi = netdev_priv(dev); |
| adap = pi->adapter; |
| qidx = skb->queue_mapping; |
| q = &adap->sge.ethtxq[qidx + pi->first_qset]; |
| |
| /* end of queue, reset pos to start of queue */ |
| eoq = (void *)q->q.stat - pos; |
| if (!eoq) |
| pos = q->q.desc; |
| |
| len = DIV_ROUND_UP(sizeof(struct chcr_ipsec_aadiv), 16) << 4; |
| memset(pos, 0, len); |
| aadiv = (struct chcr_ipsec_aadiv *)pos; |
| esphdr = (struct ip_esp_hdr *)skb_transport_header(skb); |
| iv = skb_transport_header(skb) + sizeof(struct ip_esp_hdr); |
| xo = xfrm_offload(skb); |
| |
| aadiv->spi = (esphdr->spi); |
| seqlo = ntohl(esphdr->seq_no); |
| seqno = cpu_to_be64(seqlo + ((u64)xo->seq.hi << 32)); |
| memcpy(aadiv->seq_no, &seqno, 8); |
| iv = skb_transport_header(skb) + sizeof(struct ip_esp_hdr); |
| memcpy(aadiv->iv, iv, 8); |
| |
| if (is_eth_imm(skb, sa_entry) && !skb_is_nonlinear(skb)) { |
| sc_imm = (struct ulptx_idata *)(pos + |
| (DIV_ROUND_UP(sizeof(struct chcr_ipsec_aadiv), |
| sizeof(__be64)) << 3)); |
| sc_imm->cmd_more = FILL_CMD_MORE(0); |
| sc_imm->len = cpu_to_be32(skb->len); |
| } |
| pos += len; |
| return pos; |
| } |
| |
| static void *copy_cpltx_pktxt(struct sk_buff *skb, |
| struct net_device *dev, |
| void *pos, |
| struct ipsec_sa_entry *sa_entry) |
| { |
| struct cpl_tx_pkt_core *cpl; |
| struct sge_eth_txq *q; |
| struct adapter *adap; |
| struct port_info *pi; |
| u32 ctrl0, qidx; |
| u64 cntrl = 0; |
| int left; |
| |
| pi = netdev_priv(dev); |
| adap = pi->adapter; |
| qidx = skb->queue_mapping; |
| q = &adap->sge.ethtxq[qidx + pi->first_qset]; |
| |
| left = (void *)q->q.stat - pos; |
| if (!left) |
| pos = q->q.desc; |
| |
| cpl = (struct cpl_tx_pkt_core *)pos; |
| |
| cntrl = TXPKT_L4CSUM_DIS_F | TXPKT_IPCSUM_DIS_F; |
| ctrl0 = TXPKT_OPCODE_V(CPL_TX_PKT_XT) | TXPKT_INTF_V(pi->tx_chan) | |
| TXPKT_PF_V(adap->pf); |
| if (skb_vlan_tag_present(skb)) { |
| q->vlan_ins++; |
| cntrl |= TXPKT_VLAN_VLD_F | TXPKT_VLAN_V(skb_vlan_tag_get(skb)); |
| } |
| |
| cpl->ctrl0 = htonl(ctrl0); |
| cpl->pack = htons(0); |
| cpl->len = htons(skb->len); |
| cpl->ctrl1 = cpu_to_be64(cntrl); |
| |
| pos += sizeof(struct cpl_tx_pkt_core); |
| /* Copy ESN info for HW */ |
| if (sa_entry->esn) |
| pos = copy_esn_pktxt(skb, dev, pos, sa_entry); |
| return pos; |
| } |
| |
| static void *copy_key_cpltx_pktxt(struct sk_buff *skb, |
| struct net_device *dev, |
| void *pos, |
| struct ipsec_sa_entry *sa_entry) |
| { |
| struct _key_ctx *key_ctx; |
| int left, eoq, key_len; |
| struct sge_eth_txq *q; |
| struct adapter *adap; |
| struct port_info *pi; |
| unsigned int qidx; |
| |
| pi = netdev_priv(dev); |
| adap = pi->adapter; |
| qidx = skb->queue_mapping; |
| q = &adap->sge.ethtxq[qidx + pi->first_qset]; |
| key_len = sa_entry->kctx_len; |
| |
| /* end of queue, reset pos to start of queue */ |
| eoq = (void *)q->q.stat - pos; |
| left = eoq; |
| if (!eoq) { |
| pos = q->q.desc; |
| left = 64 * q->q.size; |
| } |
| |
| /* Copy the Key context header */ |
| key_ctx = (struct _key_ctx *)pos; |
| key_ctx->ctx_hdr = sa_entry->key_ctx_hdr; |
| memcpy(key_ctx->salt, sa_entry->salt, MAX_SALT); |
| pos += sizeof(struct _key_ctx); |
| left -= sizeof(struct _key_ctx); |
| |
| if (likely(key_len <= left)) { |
| memcpy(key_ctx->key, sa_entry->key, key_len); |
| pos += key_len; |
| } else { |
| memcpy(pos, sa_entry->key, left); |
| memcpy(q->q.desc, sa_entry->key + left, |
| key_len - left); |
| pos = (u8 *)q->q.desc + (key_len - left); |
| } |
| /* Copy CPL TX PKT XT */ |
| pos = copy_cpltx_pktxt(skb, dev, pos, sa_entry); |
| |
| return pos; |
| } |
| |
| static void *ch_ipsec_crypto_wreq(struct sk_buff *skb, |
| struct net_device *dev, |
| void *pos, |
| int credits, |
| struct ipsec_sa_entry *sa_entry) |
| { |
| struct port_info *pi = netdev_priv(dev); |
| struct adapter *adap = pi->adapter; |
| unsigned int ivsize = GCM_ESP_IV_SIZE; |
| struct chcr_ipsec_wr *wr; |
| bool immediate = false; |
| u16 immdatalen = 0; |
| unsigned int flits; |
| u32 ivinoffset; |
| u32 aadstart; |
| u32 aadstop; |
| u32 ciphstart; |
| u16 sc_more = 0; |
| u32 ivdrop = 0; |
| u32 esnlen = 0; |
| u32 wr_mid; |
| u16 ndesc; |
| int qidx = skb_get_queue_mapping(skb); |
| struct sge_eth_txq *q = &adap->sge.ethtxq[qidx + pi->first_qset]; |
| unsigned int kctx_len = sa_entry->kctx_len; |
| int qid = q->q.cntxt_id; |
| |
| atomic_inc(&adap->ch_ipsec_stats.ipsec_cnt); |
| |
| flits = calc_tx_sec_flits(skb, sa_entry, &immediate); |
| ndesc = DIV_ROUND_UP(flits, 2); |
| if (sa_entry->esn) |
| ivdrop = 1; |
| |
| if (immediate) |
| immdatalen = skb->len; |
| |
| if (sa_entry->esn) { |
| esnlen = sizeof(struct chcr_ipsec_aadiv); |
| if (!skb_is_nonlinear(skb)) |
| sc_more = 1; |
| } |
| |
| /* WR Header */ |
| wr = (struct chcr_ipsec_wr *)pos; |
| wr->wreq.op_to_compl = htonl(FW_WR_OP_V(FW_ULPTX_WR)); |
| wr_mid = FW_CRYPTO_LOOKASIDE_WR_LEN16_V(ndesc); |
| |
| if (unlikely(credits < ETHTXQ_STOP_THRES)) { |
| netif_tx_stop_queue(q->txq); |
| q->q.stops++; |
| if (!q->dbqt) |
| wr_mid |= FW_WR_EQUEQ_F | FW_WR_EQUIQ_F; |
| } |
| wr_mid |= FW_ULPTX_WR_DATA_F; |
| wr->wreq.flowid_len16 = htonl(wr_mid); |
| |
| /* ULPTX */ |
| wr->req.ulptx.cmd_dest = FILL_ULPTX_CMD_DEST(pi->port_id, qid); |
| wr->req.ulptx.len = htonl(ndesc - 1); |
| |
| /* Sub-command */ |
| wr->req.sc_imm.cmd_more = FILL_CMD_MORE(!immdatalen || sc_more); |
| wr->req.sc_imm.len = cpu_to_be32(sizeof(struct cpl_tx_sec_pdu) + |
| sizeof(wr->req.key_ctx) + |
| kctx_len + |
| sizeof(struct cpl_tx_pkt_core) + |
| esnlen + |
| (esnlen ? 0 : immdatalen)); |
| |
| /* CPL_SEC_PDU */ |
| ivinoffset = sa_entry->esn ? (ESN_IV_INSERT_OFFSET + 1) : |
| (skb_transport_offset(skb) + |
| sizeof(struct ip_esp_hdr) + 1); |
| wr->req.sec_cpl.op_ivinsrtofst = htonl( |
| CPL_TX_SEC_PDU_OPCODE_V(CPL_TX_SEC_PDU) | |
| CPL_TX_SEC_PDU_CPLLEN_V(2) | |
| CPL_TX_SEC_PDU_PLACEHOLDER_V(1) | |
| CPL_TX_SEC_PDU_IVINSRTOFST_V( |
| ivinoffset)); |
| |
| wr->req.sec_cpl.pldlen = htonl(skb->len + esnlen); |
| aadstart = sa_entry->esn ? 1 : (skb_transport_offset(skb) + 1); |
| aadstop = sa_entry->esn ? ESN_IV_INSERT_OFFSET : |
| (skb_transport_offset(skb) + |
| sizeof(struct ip_esp_hdr)); |
| ciphstart = skb_transport_offset(skb) + sizeof(struct ip_esp_hdr) + |
| GCM_ESP_IV_SIZE + 1; |
| ciphstart += sa_entry->esn ? esnlen : 0; |
| |
| wr->req.sec_cpl.aadstart_cipherstop_hi = FILL_SEC_CPL_CIPHERSTOP_HI( |
| aadstart, |
| aadstop, |
| ciphstart, 0); |
| |
| wr->req.sec_cpl.cipherstop_lo_authinsert = |
| FILL_SEC_CPL_AUTHINSERT(0, ciphstart, |
| sa_entry->authsize, |
| sa_entry->authsize); |
| wr->req.sec_cpl.seqno_numivs = |
| FILL_SEC_CPL_SCMD0_SEQNO(CHCR_ENCRYPT_OP, 1, |
| CHCR_SCMD_CIPHER_MODE_AES_GCM, |
| CHCR_SCMD_AUTH_MODE_GHASH, |
| sa_entry->hmac_ctrl, |
| ivsize >> 1); |
| wr->req.sec_cpl.ivgen_hdrlen = FILL_SEC_CPL_IVGEN_HDRLEN(0, 0, 1, |
| 0, ivdrop, 0); |
| |
| pos += sizeof(struct fw_ulptx_wr) + |
| sizeof(struct ulp_txpkt) + |
| sizeof(struct ulptx_idata) + |
| sizeof(struct cpl_tx_sec_pdu); |
| |
| pos = copy_key_cpltx_pktxt(skb, dev, pos, sa_entry); |
| |
| return pos; |
| } |
| |
| /** |
| * flits_to_desc - returns the num of Tx descriptors for the given flits |
| * @n: the number of flits |
| * |
| * Returns the number of Tx descriptors needed for the supplied number |
| * of flits. |
| */ |
| static unsigned int flits_to_desc(unsigned int n) |
| { |
| WARN_ON(n > SGE_MAX_WR_LEN / 8); |
| return DIV_ROUND_UP(n, 8); |
| } |
| |
| static unsigned int txq_avail(const struct sge_txq *q) |
| { |
| return q->size - 1 - q->in_use; |
| } |
| |
| static void eth_txq_stop(struct sge_eth_txq *q) |
| { |
| netif_tx_stop_queue(q->txq); |
| q->q.stops++; |
| } |
| |
| static void txq_advance(struct sge_txq *q, unsigned int n) |
| { |
| q->in_use += n; |
| q->pidx += n; |
| if (q->pidx >= q->size) |
| q->pidx -= q->size; |
| } |
| |
| /* |
| * ch_ipsec_xmit called from ULD Tx handler |
| */ |
| int ch_ipsec_xmit(struct sk_buff *skb, struct net_device *dev) |
| { |
| struct xfrm_state *x = xfrm_input_state(skb); |
| unsigned int last_desc, ndesc, flits = 0; |
| struct ipsec_sa_entry *sa_entry; |
| u64 *pos, *end, *before, *sgl; |
| struct tx_sw_desc *sgl_sdesc; |
| int qidx, left, credits; |
| bool immediate = false; |
| struct sge_eth_txq *q; |
| struct adapter *adap; |
| struct port_info *pi; |
| struct sec_path *sp; |
| |
| if (!x->xso.offload_handle) |
| return NETDEV_TX_BUSY; |
| |
| sa_entry = (struct ipsec_sa_entry *)x->xso.offload_handle; |
| |
| sp = skb_sec_path(skb); |
| if (sp->len != 1) { |
| out_free: dev_kfree_skb_any(skb); |
| return NETDEV_TX_OK; |
| } |
| |
| pi = netdev_priv(dev); |
| adap = pi->adapter; |
| qidx = skb->queue_mapping; |
| q = &adap->sge.ethtxq[qidx + pi->first_qset]; |
| |
| cxgb4_reclaim_completed_tx(adap, &q->q, true); |
| |
| flits = calc_tx_sec_flits(skb, sa_entry, &immediate); |
| ndesc = flits_to_desc(flits); |
| credits = txq_avail(&q->q) - ndesc; |
| |
| if (unlikely(credits < 0)) { |
| eth_txq_stop(q); |
| dev_err(adap->pdev_dev, |
| "%s: Tx ring %u full while queue awake! cred:%d %d %d flits:%d\n", |
| dev->name, qidx, credits, ndesc, txq_avail(&q->q), |
| flits); |
| return NETDEV_TX_BUSY; |
| } |
| |
| last_desc = q->q.pidx + ndesc - 1; |
| if (last_desc >= q->q.size) |
| last_desc -= q->q.size; |
| sgl_sdesc = &q->q.sdesc[last_desc]; |
| |
| if (!immediate && |
| unlikely(cxgb4_map_skb(adap->pdev_dev, skb, sgl_sdesc->addr) < 0)) { |
| memset(sgl_sdesc->addr, 0, sizeof(sgl_sdesc->addr)); |
| q->mapping_err++; |
| goto out_free; |
| } |
| |
| pos = (u64 *)&q->q.desc[q->q.pidx]; |
| before = (u64 *)pos; |
| end = (u64 *)pos + flits; |
| /* Setup IPSec CPL */ |
| pos = (void *)ch_ipsec_crypto_wreq(skb, dev, (void *)pos, |
| credits, sa_entry); |
| if (before > (u64 *)pos) { |
| left = (u8 *)end - (u8 *)q->q.stat; |
| end = (void *)q->q.desc + left; |
| } |
| if (pos == (u64 *)q->q.stat) { |
| left = (u8 *)end - (u8 *)q->q.stat; |
| end = (void *)q->q.desc + left; |
| pos = (void *)q->q.desc; |
| } |
| |
| sgl = (void *)pos; |
| if (immediate) { |
| cxgb4_inline_tx_skb(skb, &q->q, sgl); |
| dev_consume_skb_any(skb); |
| } else { |
| cxgb4_write_sgl(skb, &q->q, (void *)sgl, end, |
| 0, sgl_sdesc->addr); |
| skb_orphan(skb); |
| sgl_sdesc->skb = skb; |
| } |
| txq_advance(&q->q, ndesc); |
| |
| cxgb4_ring_tx_db(adap, &q->q, ndesc); |
| return NETDEV_TX_OK; |
| } |
| |
| static int __init ch_ipsec_init(void) |
| { |
| cxgb4_register_uld(CXGB4_ULD_IPSEC, &ch_ipsec_uld_info); |
| |
| return 0; |
| } |
| |
| static void __exit ch_ipsec_exit(void) |
| { |
| struct ipsec_uld_ctx *u_ctx, *tmp; |
| struct adapter *adap; |
| |
| mutex_lock(&dev_mutex); |
| list_for_each_entry_safe(u_ctx, tmp, &uld_ctx_list, entry) { |
| adap = pci_get_drvdata(u_ctx->lldi.pdev); |
| atomic_set(&adap->ch_ipsec_stats.ipsec_cnt, 0); |
| list_del(&u_ctx->entry); |
| kfree(u_ctx); |
| } |
| mutex_unlock(&dev_mutex); |
| cxgb4_unregister_uld(CXGB4_ULD_IPSEC); |
| } |
| |
| module_init(ch_ipsec_init); |
| module_exit(ch_ipsec_exit); |
| |
| MODULE_DESCRIPTION("Crypto IPSEC for Chelsio Terminator cards."); |
| MODULE_LICENSE("GPL"); |
| MODULE_AUTHOR("Chelsio Communications"); |
| MODULE_VERSION(CHIPSEC_DRV_VERSION); |
| |