Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Atul Gupta | 9112 | 99.84% | 7 | 70.00% |
Dan Carpenter | 10 | 0.11% | 1 | 10.00% |
Kees Cook | 3 | 0.03% | 1 | 10.00% |
Thomas Gleixner | 2 | 0.02% | 1 | 10.00% |
Total | 9127 | 10 |
// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2018 Chelsio Communications, Inc. * * Written by: Atul Gupta (atul.gupta@chelsio.com) */ #include <linux/module.h> #include <linux/list.h> #include <linux/workqueue.h> #include <linux/skbuff.h> #include <linux/timer.h> #include <linux/notifier.h> #include <linux/inetdevice.h> #include <linux/ip.h> #include <linux/tcp.h> #include <linux/sched/signal.h> #include <net/tcp.h> #include <net/busy_poll.h> #include <crypto/aes.h> #include "chtls.h" #include "chtls_cm.h" static bool is_tls_tx(struct chtls_sock *csk) { return csk->tlshws.txkey >= 0; } static bool is_tls_rx(struct chtls_sock *csk) { return csk->tlshws.rxkey >= 0; } static int data_sgl_len(const struct sk_buff *skb) { unsigned int cnt; cnt = skb_shinfo(skb)->nr_frags; return sgl_len(cnt) * 8; } static int nos_ivs(struct sock *sk, unsigned int size) { struct chtls_sock *csk = rcu_dereference_sk_user_data(sk); return DIV_ROUND_UP(size, csk->tlshws.mfs); } static int set_ivs_imm(struct sock *sk, const struct sk_buff *skb) { int ivs_size = nos_ivs(sk, skb->len) * CIPHER_BLOCK_SIZE; int hlen = TLS_WR_CPL_LEN + data_sgl_len(skb); if ((hlen + KEY_ON_MEM_SZ + ivs_size) < MAX_IMM_OFLD_TX_DATA_WR_LEN) { ULP_SKB_CB(skb)->ulp.tls.iv = 1; return 1; } ULP_SKB_CB(skb)->ulp.tls.iv = 0; return 0; } static int max_ivs_size(struct sock *sk, int size) { return nos_ivs(sk, size) * CIPHER_BLOCK_SIZE; } static int ivs_size(struct sock *sk, const struct sk_buff *skb) { return set_ivs_imm(sk, skb) ? (nos_ivs(sk, skb->len) * CIPHER_BLOCK_SIZE) : 0; } static int flowc_wr_credits(int nparams, int *flowclenp) { int flowclen16, flowclen; flowclen = offsetof(struct fw_flowc_wr, mnemval[nparams]); flowclen16 = DIV_ROUND_UP(flowclen, 16); flowclen = flowclen16 * 16; if (flowclenp) *flowclenp = flowclen; return flowclen16; } static struct sk_buff *create_flowc_wr_skb(struct sock *sk, struct fw_flowc_wr *flowc, int flowclen) { struct chtls_sock *csk = rcu_dereference_sk_user_data(sk); struct sk_buff *skb; skb = alloc_skb(flowclen, GFP_ATOMIC); if (!skb) return NULL; memcpy(__skb_put(skb, flowclen), flowc, flowclen); skb_set_queue_mapping(skb, (csk->txq_idx << 1) | CPL_PRIORITY_DATA); return skb; } static int send_flowc_wr(struct sock *sk, struct fw_flowc_wr *flowc, int flowclen) { struct chtls_sock *csk = rcu_dereference_sk_user_data(sk); struct tcp_sock *tp = tcp_sk(sk); struct sk_buff *skb; int flowclen16; int ret; flowclen16 = flowclen / 16; if (csk_flag(sk, CSK_TX_DATA_SENT)) { skb = create_flowc_wr_skb(sk, flowc, flowclen); if (!skb) return -ENOMEM; skb_entail(sk, skb, ULPCB_FLAG_NO_HDR | ULPCB_FLAG_NO_APPEND); return 0; } ret = cxgb4_immdata_send(csk->egress_dev, csk->txq_idx, flowc, flowclen); if (!ret) return flowclen16; skb = create_flowc_wr_skb(sk, flowc, flowclen); if (!skb) return -ENOMEM; send_or_defer(sk, tp, skb, 0); return flowclen16; } static u8 tcp_state_to_flowc_state(u8 state) { switch (state) { case TCP_ESTABLISHED: return FW_FLOWC_MNEM_TCPSTATE_ESTABLISHED; case TCP_CLOSE_WAIT: return FW_FLOWC_MNEM_TCPSTATE_CLOSEWAIT; case TCP_FIN_WAIT1: return FW_FLOWC_MNEM_TCPSTATE_FINWAIT1; case TCP_CLOSING: return FW_FLOWC_MNEM_TCPSTATE_CLOSING; case TCP_LAST_ACK: return FW_FLOWC_MNEM_TCPSTATE_LASTACK; case TCP_FIN_WAIT2: return FW_FLOWC_MNEM_TCPSTATE_FINWAIT2; } return FW_FLOWC_MNEM_TCPSTATE_ESTABLISHED; } int send_tx_flowc_wr(struct sock *sk, int compl, u32 snd_nxt, u32 rcv_nxt) { struct flowc_packed { struct fw_flowc_wr fc; struct fw_flowc_mnemval mnemval[FW_FLOWC_MNEM_MAX]; } __packed sflowc; int nparams, paramidx, flowclen16, flowclen; struct fw_flowc_wr *flowc; struct chtls_sock *csk; struct tcp_sock *tp; csk = rcu_dereference_sk_user_data(sk); tp = tcp_sk(sk); memset(&sflowc, 0, sizeof(sflowc)); flowc = &sflowc.fc; #define FLOWC_PARAM(__m, __v) \ do { \ flowc->mnemval[paramidx].mnemonic = FW_FLOWC_MNEM_##__m; \ flowc->mnemval[paramidx].val = cpu_to_be32(__v); \ paramidx++; \ } while (0) paramidx = 0; FLOWC_PARAM(PFNVFN, FW_PFVF_CMD_PFN_V(csk->cdev->lldi->pf)); FLOWC_PARAM(CH, csk->tx_chan); FLOWC_PARAM(PORT, csk->tx_chan); FLOWC_PARAM(IQID, csk->rss_qid); FLOWC_PARAM(SNDNXT, tp->snd_nxt); FLOWC_PARAM(RCVNXT, tp->rcv_nxt); FLOWC_PARAM(SNDBUF, csk->sndbuf); FLOWC_PARAM(MSS, tp->mss_cache); FLOWC_PARAM(TCPSTATE, tcp_state_to_flowc_state(sk->sk_state)); if (SND_WSCALE(tp)) FLOWC_PARAM(RCV_SCALE, SND_WSCALE(tp)); if (csk->ulp_mode == ULP_MODE_TLS) FLOWC_PARAM(ULD_MODE, ULP_MODE_TLS); if (csk->tlshws.fcplenmax) FLOWC_PARAM(TXDATAPLEN_MAX, csk->tlshws.fcplenmax); nparams = paramidx; #undef FLOWC_PARAM flowclen16 = flowc_wr_credits(nparams, &flowclen); flowc->op_to_nparams = cpu_to_be32(FW_WR_OP_V(FW_FLOWC_WR) | FW_WR_COMPL_V(compl) | FW_FLOWC_WR_NPARAMS_V(nparams)); flowc->flowid_len16 = cpu_to_be32(FW_WR_LEN16_V(flowclen16) | FW_WR_FLOWID_V(csk->tid)); return send_flowc_wr(sk, flowc, flowclen); } /* Copy IVs to WR */ static int tls_copy_ivs(struct sock *sk, struct sk_buff *skb) { struct chtls_sock *csk; unsigned char *iv_loc; struct chtls_hws *hws; unsigned char *ivs; u16 number_of_ivs; struct page *page; int err = 0; csk = rcu_dereference_sk_user_data(sk); hws = &csk->tlshws; number_of_ivs = nos_ivs(sk, skb->len); if (number_of_ivs > MAX_IVS_PAGE) { pr_warn("MAX IVs in PAGE exceeded %d\n", number_of_ivs); return -ENOMEM; } /* generate the IVs */ ivs = kmalloc_array(CIPHER_BLOCK_SIZE, number_of_ivs, GFP_ATOMIC); if (!ivs) return -ENOMEM; get_random_bytes(ivs, number_of_ivs * CIPHER_BLOCK_SIZE); if (skb_ulp_tls_iv_imm(skb)) { /* send the IVs as immediate data in the WR */ iv_loc = (unsigned char *)__skb_push(skb, number_of_ivs * CIPHER_BLOCK_SIZE); if (iv_loc) memcpy(iv_loc, ivs, number_of_ivs * CIPHER_BLOCK_SIZE); hws->ivsize = number_of_ivs * CIPHER_BLOCK_SIZE; } else { /* Send the IVs as sgls */ /* Already accounted IV DSGL for credits */ skb_shinfo(skb)->nr_frags--; page = alloc_pages(sk->sk_allocation | __GFP_COMP, 0); if (!page) { pr_info("%s : Page allocation for IVs failed\n", __func__); err = -ENOMEM; goto out; } memcpy(page_address(page), ivs, number_of_ivs * CIPHER_BLOCK_SIZE); skb_fill_page_desc(skb, skb_shinfo(skb)->nr_frags, page, 0, number_of_ivs * CIPHER_BLOCK_SIZE); hws->ivsize = 0; } out: kfree(ivs); return err; } /* Copy Key to WR */ static void tls_copy_tx_key(struct sock *sk, struct sk_buff *skb) { struct ulptx_sc_memrd *sc_memrd; struct chtls_sock *csk; struct chtls_dev *cdev; struct ulptx_idata *sc; struct chtls_hws *hws; u32 immdlen; int kaddr; csk = rcu_dereference_sk_user_data(sk); hws = &csk->tlshws; cdev = csk->cdev; immdlen = sizeof(*sc) + sizeof(*sc_memrd); kaddr = keyid_to_addr(cdev->kmap.start, hws->txkey); sc = (struct ulptx_idata *)__skb_push(skb, immdlen); if (sc) { sc->cmd_more = htonl(ULPTX_CMD_V(ULP_TX_SC_NOOP)); sc->len = htonl(0); sc_memrd = (struct ulptx_sc_memrd *)(sc + 1); sc_memrd->cmd_to_len = htonl(ULPTX_CMD_V(ULP_TX_SC_MEMRD) | ULP_TX_SC_MORE_V(1) | ULPTX_LEN16_V(hws->keylen >> 4)); sc_memrd->addr = htonl(kaddr); } } static u64 tlstx_incr_seqnum(struct chtls_hws *hws) { return hws->tx_seq_no++; } static bool is_sg_request(const struct sk_buff *skb) { return skb->peeked || (skb->len > MAX_IMM_ULPTX_WR_LEN); } /* * Returns true if an sk_buff carries urgent data. */ static bool skb_urgent(struct sk_buff *skb) { return ULP_SKB_CB(skb)->flags & ULPCB_FLAG_URG; } /* TLS content type for CPL SFO */ static unsigned char tls_content_type(unsigned char content_type) { switch (content_type) { case TLS_HDR_TYPE_CCS: return CPL_TX_TLS_SFO_TYPE_CCS; case TLS_HDR_TYPE_ALERT: return CPL_TX_TLS_SFO_TYPE_ALERT; case TLS_HDR_TYPE_HANDSHAKE: return CPL_TX_TLS_SFO_TYPE_HANDSHAKE; case TLS_HDR_TYPE_HEARTBEAT: return CPL_TX_TLS_SFO_TYPE_HEARTBEAT; } return CPL_TX_TLS_SFO_TYPE_DATA; } static void tls_tx_data_wr(struct sock *sk, struct sk_buff *skb, int dlen, int tls_immd, u32 credits, int expn, int pdus) { struct fw_tlstx_data_wr *req_wr; struct cpl_tx_tls_sfo *req_cpl; unsigned int wr_ulp_mode_force; struct tls_scmd *updated_scmd; unsigned char data_type; struct chtls_sock *csk; struct net_device *dev; struct chtls_hws *hws; struct tls_scmd *scmd; struct adapter *adap; unsigned char *req; int immd_len; int iv_imm; int len; csk = rcu_dereference_sk_user_data(sk); iv_imm = skb_ulp_tls_iv_imm(skb); dev = csk->egress_dev; adap = netdev2adap(dev); hws = &csk->tlshws; scmd = &hws->scmd; len = dlen + expn; dlen = (dlen < hws->mfs) ? dlen : hws->mfs; atomic_inc(&adap->chcr_stats.tls_pdu_tx); updated_scmd = scmd; updated_scmd->seqno_numivs &= 0xffffff80; updated_scmd->seqno_numivs |= SCMD_NUM_IVS_V(pdus); hws->scmd = *updated_scmd; req = (unsigned char *)__skb_push(skb, sizeof(struct cpl_tx_tls_sfo)); req_cpl = (struct cpl_tx_tls_sfo *)req; req = (unsigned char *)__skb_push(skb, (sizeof(struct fw_tlstx_data_wr))); req_wr = (struct fw_tlstx_data_wr *)req; immd_len = (tls_immd ? dlen : 0); req_wr->op_to_immdlen = htonl(FW_WR_OP_V(FW_TLSTX_DATA_WR) | FW_TLSTX_DATA_WR_COMPL_V(1) | FW_TLSTX_DATA_WR_IMMDLEN_V(immd_len)); req_wr->flowid_len16 = htonl(FW_TLSTX_DATA_WR_FLOWID_V(csk->tid) | FW_TLSTX_DATA_WR_LEN16_V(credits)); wr_ulp_mode_force = TX_ULP_MODE_V(ULP_MODE_TLS); if (is_sg_request(skb)) wr_ulp_mode_force |= FW_OFLD_TX_DATA_WR_ALIGNPLD_F | ((tcp_sk(sk)->nonagle & TCP_NAGLE_OFF) ? 0 : FW_OFLD_TX_DATA_WR_SHOVE_F); req_wr->lsodisable_to_flags = htonl(TX_ULP_MODE_V(ULP_MODE_TLS) | TX_URG_V(skb_urgent(skb)) | T6_TX_FORCE_F | wr_ulp_mode_force | TX_SHOVE_V((!csk_flag(sk, CSK_TX_MORE_DATA)) && skb_queue_empty(&csk->txq))); req_wr->ctxloc_to_exp = htonl(FW_TLSTX_DATA_WR_NUMIVS_V(pdus) | FW_TLSTX_DATA_WR_EXP_V(expn) | FW_TLSTX_DATA_WR_CTXLOC_V(CHTLS_KEY_CONTEXT_DDR) | FW_TLSTX_DATA_WR_IVDSGL_V(!iv_imm) | FW_TLSTX_DATA_WR_KEYSIZE_V(hws->keylen >> 4)); /* Fill in the length */ req_wr->plen = htonl(len); req_wr->mfs = htons(hws->mfs); req_wr->adjustedplen_pkd = htons(FW_TLSTX_DATA_WR_ADJUSTEDPLEN_V(hws->adjustlen)); req_wr->expinplenmax_pkd = htons(FW_TLSTX_DATA_WR_EXPINPLENMAX_V(hws->expansion)); req_wr->pdusinplenmax_pkd = FW_TLSTX_DATA_WR_PDUSINPLENMAX_V(hws->pdus); req_wr->r10 = 0; data_type = tls_content_type(ULP_SKB_CB(skb)->ulp.tls.type); req_cpl->op_to_seg_len = htonl(CPL_TX_TLS_SFO_OPCODE_V(CPL_TX_TLS_SFO) | CPL_TX_TLS_SFO_DATA_TYPE_V(data_type) | CPL_TX_TLS_SFO_CPL_LEN_V(2) | CPL_TX_TLS_SFO_SEG_LEN_V(dlen)); req_cpl->pld_len = htonl(len - expn); req_cpl->type_protover = htonl(CPL_TX_TLS_SFO_TYPE_V ((data_type == CPL_TX_TLS_SFO_TYPE_HEARTBEAT) ? TLS_HDR_TYPE_HEARTBEAT : 0) | CPL_TX_TLS_SFO_PROTOVER_V(0)); /* create the s-command */ req_cpl->r1_lo = 0; req_cpl->seqno_numivs = cpu_to_be32(hws->scmd.seqno_numivs); req_cpl->ivgen_hdrlen = cpu_to_be32(hws->scmd.ivgen_hdrlen); req_cpl->scmd1 = cpu_to_be64(tlstx_incr_seqnum(hws)); } /* * Calculate the TLS data expansion size */ static int chtls_expansion_size(struct sock *sk, int data_len, int fullpdu, unsigned short *pducnt) { struct chtls_sock *csk = rcu_dereference_sk_user_data(sk); struct chtls_hws *hws = &csk->tlshws; struct tls_scmd *scmd = &hws->scmd; int fragsize = hws->mfs; int expnsize = 0; int fragleft; int fragcnt; int expppdu; if (SCMD_CIPH_MODE_G(scmd->seqno_numivs) == SCMD_CIPH_MODE_AES_GCM) { expppdu = GCM_TAG_SIZE + AEAD_EXPLICIT_DATA_SIZE + TLS_HEADER_LENGTH; if (fullpdu) { *pducnt = data_len / (expppdu + fragsize); if (*pducnt > 32) *pducnt = 32; else if (!*pducnt) *pducnt = 1; expnsize = (*pducnt) * expppdu; return expnsize; } fragcnt = (data_len / fragsize); expnsize = fragcnt * expppdu; fragleft = data_len % fragsize; if (fragleft > 0) expnsize += expppdu; } return expnsize; } /* WR with IV, KEY and CPL SFO added */ static void make_tlstx_data_wr(struct sock *sk, struct sk_buff *skb, int tls_tx_imm, int tls_len, u32 credits) { unsigned short pdus_per_ulp = 0; struct chtls_sock *csk; struct chtls_hws *hws; int expn_sz; int pdus; csk = rcu_dereference_sk_user_data(sk); hws = &csk->tlshws; pdus = DIV_ROUND_UP(tls_len, hws->mfs); expn_sz = chtls_expansion_size(sk, tls_len, 0, NULL); if (!hws->compute) { hws->expansion = chtls_expansion_size(sk, hws->fcplenmax, 1, &pdus_per_ulp); hws->pdus = pdus_per_ulp; hws->adjustlen = hws->pdus * ((hws->expansion / hws->pdus) + hws->mfs); hws->compute = 1; } if (tls_copy_ivs(sk, skb)) return; tls_copy_tx_key(sk, skb); tls_tx_data_wr(sk, skb, tls_len, tls_tx_imm, credits, expn_sz, pdus); hws->tx_seq_no += (pdus - 1); } static void make_tx_data_wr(struct sock *sk, struct sk_buff *skb, unsigned int immdlen, int len, u32 credits, u32 compl) { struct fw_ofld_tx_data_wr *req; unsigned int wr_ulp_mode_force; struct chtls_sock *csk; unsigned int opcode; csk = rcu_dereference_sk_user_data(sk); opcode = FW_OFLD_TX_DATA_WR; req = (struct fw_ofld_tx_data_wr *)__skb_push(skb, sizeof(*req)); req->op_to_immdlen = htonl(WR_OP_V(opcode) | FW_WR_COMPL_V(compl) | FW_WR_IMMDLEN_V(immdlen)); req->flowid_len16 = htonl(FW_WR_FLOWID_V(csk->tid) | FW_WR_LEN16_V(credits)); wr_ulp_mode_force = TX_ULP_MODE_V(csk->ulp_mode); if (is_sg_request(skb)) wr_ulp_mode_force |= FW_OFLD_TX_DATA_WR_ALIGNPLD_F | ((tcp_sk(sk)->nonagle & TCP_NAGLE_OFF) ? 0 : FW_OFLD_TX_DATA_WR_SHOVE_F); req->tunnel_to_proxy = htonl(wr_ulp_mode_force | TX_URG_V(skb_urgent(skb)) | TX_SHOVE_V((!csk_flag(sk, CSK_TX_MORE_DATA)) && skb_queue_empty(&csk->txq))); req->plen = htonl(len); } static int chtls_wr_size(struct chtls_sock *csk, const struct sk_buff *skb, bool size) { int wr_size; wr_size = TLS_WR_CPL_LEN; wr_size += KEY_ON_MEM_SZ; wr_size += ivs_size(csk->sk, skb); if (size) return wr_size; /* frags counted for IV dsgl */ if (!skb_ulp_tls_iv_imm(skb)) skb_shinfo(skb)->nr_frags++; return wr_size; } static bool is_ofld_imm(struct chtls_sock *csk, const struct sk_buff *skb) { int length = skb->len; if (skb->peeked || skb->len > MAX_IMM_ULPTX_WR_LEN) return false; if (likely(ULP_SKB_CB(skb)->flags & ULPCB_FLAG_NEED_HDR)) { /* Check TLS header len for Immediate */ if (csk->ulp_mode == ULP_MODE_TLS && skb_ulp_tls_inline(skb)) length += chtls_wr_size(csk, skb, true); else length += sizeof(struct fw_ofld_tx_data_wr); return length <= MAX_IMM_OFLD_TX_DATA_WR_LEN; } return true; } static unsigned int calc_tx_flits(const struct sk_buff *skb, unsigned int immdlen) { unsigned int flits, cnt; flits = immdlen / 8; /* headers */ cnt = skb_shinfo(skb)->nr_frags; if (skb_tail_pointer(skb) != skb_transport_header(skb)) cnt++; return flits + sgl_len(cnt); } static void arp_failure_discard(void *handle, struct sk_buff *skb) { kfree_skb(skb); } int chtls_push_frames(struct chtls_sock *csk, int comp) { struct chtls_hws *hws = &csk->tlshws; struct tcp_sock *tp; struct sk_buff *skb; int total_size = 0; struct sock *sk; int wr_size; wr_size = sizeof(struct fw_ofld_tx_data_wr); sk = csk->sk; tp = tcp_sk(sk); if (unlikely(sk_in_state(sk, TCPF_SYN_SENT | TCPF_CLOSE))) return 0; if (unlikely(csk_flag(sk, CSK_ABORT_SHUTDOWN))) return 0; while (csk->wr_credits && (skb = skb_peek(&csk->txq)) && (!(ULP_SKB_CB(skb)->flags & ULPCB_FLAG_HOLD) || skb_queue_len(&csk->txq) > 1)) { unsigned int credit_len = skb->len; unsigned int credits_needed; unsigned int completion = 0; int tls_len = skb->len;/* TLS data len before IV/key */ unsigned int immdlen; int len = skb->len; /* length [ulp bytes] inserted by hw */ int flowclen16 = 0; int tls_tx_imm = 0; immdlen = skb->len; if (!is_ofld_imm(csk, skb)) { immdlen = skb_transport_offset(skb); if (skb_ulp_tls_inline(skb)) wr_size = chtls_wr_size(csk, skb, false); credit_len = 8 * calc_tx_flits(skb, immdlen); } else { if (skb_ulp_tls_inline(skb)) { wr_size = chtls_wr_size(csk, skb, false); tls_tx_imm = 1; } } if (likely(ULP_SKB_CB(skb)->flags & ULPCB_FLAG_NEED_HDR)) credit_len += wr_size; credits_needed = DIV_ROUND_UP(credit_len, 16); if (!csk_flag_nochk(csk, CSK_TX_DATA_SENT)) { flowclen16 = send_tx_flowc_wr(sk, 1, tp->snd_nxt, tp->rcv_nxt); if (flowclen16 <= 0) break; csk->wr_credits -= flowclen16; csk->wr_unacked += flowclen16; csk->wr_nondata += flowclen16; csk_set_flag(csk, CSK_TX_DATA_SENT); } if (csk->wr_credits < credits_needed) { if (skb_ulp_tls_inline(skb) && !skb_ulp_tls_iv_imm(skb)) skb_shinfo(skb)->nr_frags--; break; } __skb_unlink(skb, &csk->txq); skb_set_queue_mapping(skb, (csk->txq_idx << 1) | CPL_PRIORITY_DATA); if (hws->ofld) hws->txqid = (skb->queue_mapping >> 1); skb->csum = (__force __wsum)(credits_needed + csk->wr_nondata); csk->wr_credits -= credits_needed; csk->wr_unacked += credits_needed; csk->wr_nondata = 0; enqueue_wr(csk, skb); if (likely(ULP_SKB_CB(skb)->flags & ULPCB_FLAG_NEED_HDR)) { if ((comp && csk->wr_unacked == credits_needed) || (ULP_SKB_CB(skb)->flags & ULPCB_FLAG_COMPL) || csk->wr_unacked >= csk->wr_max_credits / 2) { completion = 1; csk->wr_unacked = 0; } if (skb_ulp_tls_inline(skb)) make_tlstx_data_wr(sk, skb, tls_tx_imm, tls_len, credits_needed); else make_tx_data_wr(sk, skb, immdlen, len, credits_needed, completion); tp->snd_nxt += len; tp->lsndtime = tcp_time_stamp(tp); if (completion) ULP_SKB_CB(skb)->flags &= ~ULPCB_FLAG_NEED_HDR; } else { struct cpl_close_con_req *req = cplhdr(skb); unsigned int cmd = CPL_OPCODE_G(ntohl (OPCODE_TID(req))); if (cmd == CPL_CLOSE_CON_REQ) csk_set_flag(csk, CSK_CLOSE_CON_REQUESTED); if ((ULP_SKB_CB(skb)->flags & ULPCB_FLAG_COMPL) && (csk->wr_unacked >= csk->wr_max_credits / 2)) { req->wr.wr_hi |= htonl(FW_WR_COMPL_F); csk->wr_unacked = 0; } } total_size += skb->truesize; if (ULP_SKB_CB(skb)->flags & ULPCB_FLAG_BARRIER) csk_set_flag(csk, CSK_TX_WAIT_IDLE); t4_set_arp_err_handler(skb, NULL, arp_failure_discard); cxgb4_l2t_send(csk->egress_dev, skb, csk->l2t_entry); } sk->sk_wmem_queued -= total_size; return total_size; } static void mark_urg(struct tcp_sock *tp, int flags, struct sk_buff *skb) { if (unlikely(flags & MSG_OOB)) { tp->snd_up = tp->write_seq; ULP_SKB_CB(skb)->flags = ULPCB_FLAG_URG | ULPCB_FLAG_BARRIER | ULPCB_FLAG_NO_APPEND | ULPCB_FLAG_NEED_HDR; } } /* * Returns true if a connection should send more data to TCP engine */ static bool should_push(struct sock *sk) { struct chtls_sock *csk = rcu_dereference_sk_user_data(sk); struct chtls_dev *cdev = csk->cdev; struct tcp_sock *tp = tcp_sk(sk); /* * If we've released our offload resources there's nothing to do ... */ if (!cdev) return false; /* * If there aren't any work requests in flight, or there isn't enough * data in flight, or Nagle is off then send the current TX_DATA * otherwise hold it and wait to accumulate more data. */ return csk->wr_credits == csk->wr_max_credits || (tp->nonagle & TCP_NAGLE_OFF); } /* * Returns true if a TCP socket is corked. */ static bool corked(const struct tcp_sock *tp, int flags) { return (flags & MSG_MORE) || (tp->nonagle & TCP_NAGLE_CORK); } /* * Returns true if a send should try to push new data. */ static bool send_should_push(struct sock *sk, int flags) { return should_push(sk) && !corked(tcp_sk(sk), flags); } void chtls_tcp_push(struct sock *sk, int flags) { struct chtls_sock *csk = rcu_dereference_sk_user_data(sk); int qlen = skb_queue_len(&csk->txq); if (likely(qlen)) { struct sk_buff *skb = skb_peek_tail(&csk->txq); struct tcp_sock *tp = tcp_sk(sk); mark_urg(tp, flags, skb); if (!(ULP_SKB_CB(skb)->flags & ULPCB_FLAG_NO_APPEND) && corked(tp, flags)) { ULP_SKB_CB(skb)->flags |= ULPCB_FLAG_HOLD; return; } ULP_SKB_CB(skb)->flags &= ~ULPCB_FLAG_HOLD; if (qlen == 1 && ((ULP_SKB_CB(skb)->flags & ULPCB_FLAG_NO_APPEND) || should_push(sk))) chtls_push_frames(csk, 1); } } /* * Calculate the size for a new send sk_buff. It's maximum size so we can * pack lots of data into it, unless we plan to send it immediately, in which * case we size it more tightly. * * Note: we don't bother compensating for MSS < PAGE_SIZE because it doesn't * arise in normal cases and when it does we are just wasting memory. */ static int select_size(struct sock *sk, int io_len, int flags, int len) { const int pgbreak = SKB_MAX_HEAD(len); /* * If the data wouldn't fit in the main body anyway, put only the * header in the main body so it can use immediate data and place all * the payload in page fragments. */ if (io_len > pgbreak) return 0; /* * If we will be accumulating payload get a large main body. */ if (!send_should_push(sk, flags)) return pgbreak; return io_len; } void skb_entail(struct sock *sk, struct sk_buff *skb, int flags) { struct chtls_sock *csk = rcu_dereference_sk_user_data(sk); struct tcp_sock *tp = tcp_sk(sk); ULP_SKB_CB(skb)->seq = tp->write_seq; ULP_SKB_CB(skb)->flags = flags; __skb_queue_tail(&csk->txq, skb); sk->sk_wmem_queued += skb->truesize; if (TCP_PAGE(sk) && TCP_OFF(sk)) { put_page(TCP_PAGE(sk)); TCP_PAGE(sk) = NULL; TCP_OFF(sk) = 0; } } static struct sk_buff *get_tx_skb(struct sock *sk, int size) { struct sk_buff *skb; skb = alloc_skb(size + TX_HEADER_LEN, sk->sk_allocation); if (likely(skb)) { skb_reserve(skb, TX_HEADER_LEN); skb_entail(sk, skb, ULPCB_FLAG_NEED_HDR); skb_reset_transport_header(skb); } return skb; } static struct sk_buff *get_record_skb(struct sock *sk, int size, bool zcopy) { struct chtls_sock *csk = rcu_dereference_sk_user_data(sk); struct sk_buff *skb; skb = alloc_skb(((zcopy ? 0 : size) + TX_TLSHDR_LEN + KEY_ON_MEM_SZ + max_ivs_size(sk, size)), sk->sk_allocation); if (likely(skb)) { skb_reserve(skb, (TX_TLSHDR_LEN + KEY_ON_MEM_SZ + max_ivs_size(sk, size))); skb_entail(sk, skb, ULPCB_FLAG_NEED_HDR); skb_reset_transport_header(skb); ULP_SKB_CB(skb)->ulp.tls.ofld = 1; ULP_SKB_CB(skb)->ulp.tls.type = csk->tlshws.type; } return skb; } static void tx_skb_finalize(struct sk_buff *skb) { struct ulp_skb_cb *cb = ULP_SKB_CB(skb); if (!(cb->flags & ULPCB_FLAG_NO_HDR)) cb->flags = ULPCB_FLAG_NEED_HDR; cb->flags |= ULPCB_FLAG_NO_APPEND; } static void push_frames_if_head(struct sock *sk) { struct chtls_sock *csk = rcu_dereference_sk_user_data(sk); if (skb_queue_len(&csk->txq) == 1) chtls_push_frames(csk, 1); } static int chtls_skb_copy_to_page_nocache(struct sock *sk, struct iov_iter *from, struct sk_buff *skb, struct page *page, int off, int copy) { int err; err = skb_do_copy_data_nocache(sk, skb, from, page_address(page) + off, copy, skb->len); if (err) return err; skb->len += copy; skb->data_len += copy; skb->truesize += copy; sk->sk_wmem_queued += copy; return 0; } /* Read TLS header to find content type and data length */ static int tls_header_read(struct tls_hdr *thdr, struct iov_iter *from) { if (copy_from_iter(thdr, sizeof(*thdr), from) != sizeof(*thdr)) return -EFAULT; return (__force int)cpu_to_be16(thdr->length); } static int csk_mem_free(struct chtls_dev *cdev, struct sock *sk) { return (cdev->max_host_sndbuf - sk->sk_wmem_queued); } static int csk_wait_memory(struct chtls_dev *cdev, struct sock *sk, long *timeo_p) { DEFINE_WAIT_FUNC(wait, woken_wake_function); int err = 0; long current_timeo; long vm_wait = 0; bool noblock; current_timeo = *timeo_p; noblock = (*timeo_p ? false : true); if (csk_mem_free(cdev, sk)) { current_timeo = (prandom_u32() % (HZ / 5)) + 2; vm_wait = (prandom_u32() % (HZ / 5)) + 2; } add_wait_queue(sk_sleep(sk), &wait); while (1) { sk_set_bit(SOCKWQ_ASYNC_NOSPACE, sk); if (sk->sk_err || (sk->sk_shutdown & SEND_SHUTDOWN)) goto do_error; if (!*timeo_p) { if (noblock) set_bit(SOCK_NOSPACE, &sk->sk_socket->flags); goto do_nonblock; } if (signal_pending(current)) goto do_interrupted; sk_clear_bit(SOCKWQ_ASYNC_NOSPACE, sk); if (csk_mem_free(cdev, sk) && !vm_wait) break; set_bit(SOCK_NOSPACE, &sk->sk_socket->flags); sk->sk_write_pending++; sk_wait_event(sk, ¤t_timeo, sk->sk_err || (sk->sk_shutdown & SEND_SHUTDOWN) || (csk_mem_free(cdev, sk) && !vm_wait), &wait); sk->sk_write_pending--; if (vm_wait) { vm_wait -= current_timeo; current_timeo = *timeo_p; if (current_timeo != MAX_SCHEDULE_TIMEOUT) { current_timeo -= vm_wait; if (current_timeo < 0) current_timeo = 0; } vm_wait = 0; } *timeo_p = current_timeo; } do_rm_wq: remove_wait_queue(sk_sleep(sk), &wait); return err; do_error: err = -EPIPE; goto do_rm_wq; do_nonblock: err = -EAGAIN; goto do_rm_wq; do_interrupted: err = sock_intr_errno(*timeo_p); goto do_rm_wq; } int chtls_sendmsg(struct sock *sk, struct msghdr *msg, size_t size) { struct chtls_sock *csk = rcu_dereference_sk_user_data(sk); struct chtls_dev *cdev = csk->cdev; struct tcp_sock *tp = tcp_sk(sk); struct sk_buff *skb; int mss, flags, err; int recordsz = 0; int copied = 0; long timeo; lock_sock(sk); flags = msg->msg_flags; timeo = sock_sndtimeo(sk, flags & MSG_DONTWAIT); if (!sk_in_state(sk, TCPF_ESTABLISHED | TCPF_CLOSE_WAIT)) { err = sk_stream_wait_connect(sk, &timeo); if (err) goto out_err; } sk_clear_bit(SOCKWQ_ASYNC_NOSPACE, sk); err = -EPIPE; if (sk->sk_err || (sk->sk_shutdown & SEND_SHUTDOWN)) goto out_err; mss = csk->mss; csk_set_flag(csk, CSK_TX_MORE_DATA); while (msg_data_left(msg)) { int copy = 0; skb = skb_peek_tail(&csk->txq); if (skb) { copy = mss - skb->len; skb->ip_summed = CHECKSUM_UNNECESSARY; } if (!csk_mem_free(cdev, sk)) goto wait_for_sndbuf; if (is_tls_tx(csk) && !csk->tlshws.txleft) { struct tls_hdr hdr; recordsz = tls_header_read(&hdr, &msg->msg_iter); size -= TLS_HEADER_LENGTH; copied += TLS_HEADER_LENGTH; csk->tlshws.txleft = recordsz; csk->tlshws.type = hdr.type; if (skb) ULP_SKB_CB(skb)->ulp.tls.type = hdr.type; } if (!skb || (ULP_SKB_CB(skb)->flags & ULPCB_FLAG_NO_APPEND) || copy <= 0) { new_buf: if (skb) { tx_skb_finalize(skb); push_frames_if_head(sk); } if (is_tls_tx(csk)) { skb = get_record_skb(sk, select_size(sk, recordsz, flags, TX_TLSHDR_LEN), false); } else { skb = get_tx_skb(sk, select_size(sk, size, flags, TX_HEADER_LEN)); } if (unlikely(!skb)) goto wait_for_memory; skb->ip_summed = CHECKSUM_UNNECESSARY; copy = mss; } if (copy > size) copy = size; if (skb_tailroom(skb) > 0) { copy = min(copy, skb_tailroom(skb)); if (is_tls_tx(csk)) copy = min_t(int, copy, csk->tlshws.txleft); err = skb_add_data_nocache(sk, skb, &msg->msg_iter, copy); if (err) goto do_fault; } else { int i = skb_shinfo(skb)->nr_frags; struct page *page = TCP_PAGE(sk); int pg_size = PAGE_SIZE; int off = TCP_OFF(sk); bool merge; if (page) pg_size <<= compound_order(page); if (off < pg_size && skb_can_coalesce(skb, i, page, off)) { merge = 1; goto copy; } merge = 0; if (i == (is_tls_tx(csk) ? (MAX_SKB_FRAGS - 1) : MAX_SKB_FRAGS)) goto new_buf; if (page && off == pg_size) { put_page(page); TCP_PAGE(sk) = page = NULL; pg_size = PAGE_SIZE; } if (!page) { gfp_t gfp = sk->sk_allocation; int order = cdev->send_page_order; if (order) { page = alloc_pages(gfp | __GFP_COMP | __GFP_NOWARN | __GFP_NORETRY, order); if (page) pg_size <<= compound_order(page); } if (!page) { page = alloc_page(gfp); pg_size = PAGE_SIZE; } if (!page) goto wait_for_memory; off = 0; } copy: if (copy > pg_size - off) copy = pg_size - off; if (is_tls_tx(csk)) copy = min_t(int, copy, csk->tlshws.txleft); err = chtls_skb_copy_to_page_nocache(sk, &msg->msg_iter, skb, page, off, copy); if (unlikely(err)) { if (!TCP_PAGE(sk)) { TCP_PAGE(sk) = page; TCP_OFF(sk) = 0; } goto do_fault; } /* Update the skb. */ if (merge) { skb_shinfo(skb)->frags[i - 1].size += copy; } else { skb_fill_page_desc(skb, i, page, off, copy); if (off + copy < pg_size) { /* space left keep page */ get_page(page); TCP_PAGE(sk) = page; } else { TCP_PAGE(sk) = NULL; } } TCP_OFF(sk) = off + copy; } if (unlikely(skb->len == mss)) tx_skb_finalize(skb); tp->write_seq += copy; copied += copy; size -= copy; if (is_tls_tx(csk)) csk->tlshws.txleft -= copy; if (corked(tp, flags) && (sk_stream_wspace(sk) < sk_stream_min_wspace(sk))) ULP_SKB_CB(skb)->flags |= ULPCB_FLAG_NO_APPEND; if (size == 0) goto out; if (ULP_SKB_CB(skb)->flags & ULPCB_FLAG_NO_APPEND) push_frames_if_head(sk); continue; wait_for_sndbuf: set_bit(SOCK_NOSPACE, &sk->sk_socket->flags); wait_for_memory: err = csk_wait_memory(cdev, sk, &timeo); if (err) goto do_error; } out: csk_reset_flag(csk, CSK_TX_MORE_DATA); if (copied) chtls_tcp_push(sk, flags); done: release_sock(sk); return copied; do_fault: if (!skb->len) { __skb_unlink(skb, &csk->txq); sk->sk_wmem_queued -= skb->truesize; __kfree_skb(skb); } do_error: if (copied) goto out; out_err: if (csk_conn_inline(csk)) csk_reset_flag(csk, CSK_TX_MORE_DATA); copied = sk_stream_error(sk, flags, err); goto done; } int chtls_sendpage(struct sock *sk, struct page *page, int offset, size_t size, int flags) { struct chtls_sock *csk; struct chtls_dev *cdev; int mss, err, copied; struct tcp_sock *tp; long timeo; tp = tcp_sk(sk); copied = 0; csk = rcu_dereference_sk_user_data(sk); cdev = csk->cdev; timeo = sock_sndtimeo(sk, flags & MSG_DONTWAIT); err = sk_stream_wait_connect(sk, &timeo); if (!sk_in_state(sk, TCPF_ESTABLISHED | TCPF_CLOSE_WAIT) && err != 0) goto out_err; mss = csk->mss; csk_set_flag(csk, CSK_TX_MORE_DATA); while (size > 0) { struct sk_buff *skb = skb_peek_tail(&csk->txq); int copy, i; if (!skb || (ULP_SKB_CB(skb)->flags & ULPCB_FLAG_NO_APPEND) || (copy = mss - skb->len) <= 0) { new_buf: if (!csk_mem_free(cdev, sk)) goto wait_for_sndbuf; if (is_tls_tx(csk)) { skb = get_record_skb(sk, select_size(sk, size, flags, TX_TLSHDR_LEN), true); } else { skb = get_tx_skb(sk, 0); } if (!skb) goto wait_for_memory; copy = mss; } if (copy > size) copy = size; i = skb_shinfo(skb)->nr_frags; if (skb_can_coalesce(skb, i, page, offset)) { skb_shinfo(skb)->frags[i - 1].size += copy; } else if (i < MAX_SKB_FRAGS) { get_page(page); skb_fill_page_desc(skb, i, page, offset, copy); } else { tx_skb_finalize(skb); push_frames_if_head(sk); goto new_buf; } skb->len += copy; if (skb->len == mss) tx_skb_finalize(skb); skb->data_len += copy; skb->truesize += copy; sk->sk_wmem_queued += copy; tp->write_seq += copy; copied += copy; offset += copy; size -= copy; if (corked(tp, flags) && (sk_stream_wspace(sk) < sk_stream_min_wspace(sk))) ULP_SKB_CB(skb)->flags |= ULPCB_FLAG_NO_APPEND; if (!size) break; if (unlikely(ULP_SKB_CB(skb)->flags & ULPCB_FLAG_NO_APPEND)) push_frames_if_head(sk); continue; wait_for_sndbuf: set_bit(SOCK_NOSPACE, &sk->sk_socket->flags); wait_for_memory: err = csk_wait_memory(cdev, sk, &timeo); if (err) goto do_error; } out: csk_reset_flag(csk, CSK_TX_MORE_DATA); if (copied) chtls_tcp_push(sk, flags); done: release_sock(sk); return copied; do_error: if (copied) goto out; out_err: if (csk_conn_inline(csk)) csk_reset_flag(csk, CSK_TX_MORE_DATA); copied = sk_stream_error(sk, flags, err); goto done; } static void chtls_select_window(struct sock *sk) { struct chtls_sock *csk = rcu_dereference_sk_user_data(sk); struct tcp_sock *tp = tcp_sk(sk); unsigned int wnd = tp->rcv_wnd; wnd = max_t(unsigned int, wnd, tcp_full_space(sk)); wnd = max_t(unsigned int, MIN_RCV_WND, wnd); if (wnd > MAX_RCV_WND) wnd = MAX_RCV_WND; /* * Check if we need to grow the receive window in response to an increase in * the socket's receive buffer size. Some applications increase the buffer * size dynamically and rely on the window to grow accordingly. */ if (wnd > tp->rcv_wnd) { tp->rcv_wup -= wnd - tp->rcv_wnd; tp->rcv_wnd = wnd; /* Mark the receive window as updated */ csk_reset_flag(csk, CSK_UPDATE_RCV_WND); } } /* * Send RX credits through an RX_DATA_ACK CPL message. We are permitted * to return without sending the message in case we cannot allocate * an sk_buff. Returns the number of credits sent. */ static u32 send_rx_credits(struct chtls_sock *csk, u32 credits) { struct cpl_rx_data_ack *req; struct sk_buff *skb; skb = alloc_skb(sizeof(*req), GFP_ATOMIC); if (!skb) return 0; __skb_put(skb, sizeof(*req)); req = (struct cpl_rx_data_ack *)skb->head; set_wr_txq(skb, CPL_PRIORITY_ACK, csk->port_id); INIT_TP_WR(req, csk->tid); OPCODE_TID(req) = cpu_to_be32(MK_OPCODE_TID(CPL_RX_DATA_ACK, csk->tid)); req->credit_dack = cpu_to_be32(RX_CREDITS_V(credits) | RX_FORCE_ACK_F); cxgb4_ofld_send(csk->cdev->ports[csk->port_id], skb); return credits; } #define CREDIT_RETURN_STATE (TCPF_ESTABLISHED | \ TCPF_FIN_WAIT1 | \ TCPF_FIN_WAIT2) /* * Called after some received data has been read. It returns RX credits * to the HW for the amount of data processed. */ static void chtls_cleanup_rbuf(struct sock *sk, int copied) { struct chtls_sock *csk = rcu_dereference_sk_user_data(sk); struct tcp_sock *tp; int must_send; u32 credits; u32 thres; thres = 15 * 1024; if (!sk_in_state(sk, CREDIT_RETURN_STATE)) return; chtls_select_window(sk); tp = tcp_sk(sk); credits = tp->copied_seq - tp->rcv_wup; if (unlikely(!credits)) return; /* * For coalescing to work effectively ensure the receive window has * at least 16KB left. */ must_send = credits + 16384 >= tp->rcv_wnd; if (must_send || credits >= thres) tp->rcv_wup += send_rx_credits(csk, credits); } static int chtls_pt_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int nonblock, int flags, int *addr_len) { struct chtls_sock *csk = rcu_dereference_sk_user_data(sk); struct chtls_hws *hws = &csk->tlshws; struct tcp_sock *tp = tcp_sk(sk); unsigned long avail; int buffers_freed; int copied = 0; int target; long timeo; buffers_freed = 0; timeo = sock_rcvtimeo(sk, nonblock); target = sock_rcvlowat(sk, flags & MSG_WAITALL, len); if (unlikely(csk_flag(sk, CSK_UPDATE_RCV_WND))) chtls_cleanup_rbuf(sk, copied); do { struct sk_buff *skb; u32 offset = 0; if (unlikely(tp->urg_data && tp->urg_seq == tp->copied_seq)) { if (copied) break; if (signal_pending(current)) { copied = timeo ? sock_intr_errno(timeo) : -EAGAIN; break; } } skb = skb_peek(&sk->sk_receive_queue); if (skb) goto found_ok_skb; if (csk->wr_credits && skb_queue_len(&csk->txq) && chtls_push_frames(csk, csk->wr_credits == csk->wr_max_credits)) sk->sk_write_space(sk); if (copied >= target && !sk->sk_backlog.tail) break; if (copied) { if (sk->sk_err || sk->sk_state == TCP_CLOSE || (sk->sk_shutdown & RCV_SHUTDOWN) || signal_pending(current)) break; if (!timeo) break; } else { if (sock_flag(sk, SOCK_DONE)) break; if (sk->sk_err) { copied = sock_error(sk); break; } if (sk->sk_shutdown & RCV_SHUTDOWN) break; if (sk->sk_state == TCP_CLOSE) { copied = -ENOTCONN; break; } if (!timeo) { copied = -EAGAIN; break; } if (signal_pending(current)) { copied = sock_intr_errno(timeo); break; } } if (sk->sk_backlog.tail) { release_sock(sk); lock_sock(sk); chtls_cleanup_rbuf(sk, copied); continue; } if (copied >= target) break; chtls_cleanup_rbuf(sk, copied); sk_wait_data(sk, &timeo, NULL); continue; found_ok_skb: if (!skb->len) { skb_dst_set(skb, NULL); __skb_unlink(skb, &sk->sk_receive_queue); kfree_skb(skb); if (!copied && !timeo) { copied = -EAGAIN; break; } if (copied < target) { release_sock(sk); lock_sock(sk); continue; } break; } offset = hws->copied_seq; avail = skb->len - offset; if (len < avail) avail = len; if (unlikely(tp->urg_data)) { u32 urg_offset = tp->urg_seq - tp->copied_seq; if (urg_offset < avail) { if (urg_offset) { avail = urg_offset; } else if (!sock_flag(sk, SOCK_URGINLINE)) { /* First byte is urgent, skip */ tp->copied_seq++; offset++; avail--; if (!avail) goto skip_copy; } } } if (skb_copy_datagram_msg(skb, offset, msg, avail)) { if (!copied) { copied = -EFAULT; break; } } copied += avail; len -= avail; hws->copied_seq += avail; skip_copy: if (tp->urg_data && after(tp->copied_seq, tp->urg_seq)) tp->urg_data = 0; if ((avail + offset) >= skb->len) { if (ULP_SKB_CB(skb)->flags & ULPCB_FLAG_TLS_HDR) { tp->copied_seq += skb->len; hws->rcvpld = skb->hdr_len; } else { tp->copied_seq += hws->rcvpld; } chtls_free_skb(sk, skb); buffers_freed++; hws->copied_seq = 0; if (copied >= target && !skb_peek(&sk->sk_receive_queue)) break; } } while (len > 0); if (buffers_freed) chtls_cleanup_rbuf(sk, copied); release_sock(sk); return copied; } /* * Peek at data in a socket's receive buffer. */ static int peekmsg(struct sock *sk, struct msghdr *msg, size_t len, int nonblock, int flags) { struct tcp_sock *tp = tcp_sk(sk); u32 peek_seq, offset; struct sk_buff *skb; int copied = 0; size_t avail; /* amount of available data in current skb */ long timeo; lock_sock(sk); timeo = sock_rcvtimeo(sk, nonblock); peek_seq = tp->copied_seq; do { if (unlikely(tp->urg_data && tp->urg_seq == peek_seq)) { if (copied) break; if (signal_pending(current)) { copied = timeo ? sock_intr_errno(timeo) : -EAGAIN; break; } } skb_queue_walk(&sk->sk_receive_queue, skb) { offset = peek_seq - ULP_SKB_CB(skb)->seq; if (offset < skb->len) goto found_ok_skb; } /* empty receive queue */ if (copied) break; if (sock_flag(sk, SOCK_DONE)) break; if (sk->sk_err) { copied = sock_error(sk); break; } if (sk->sk_shutdown & RCV_SHUTDOWN) break; if (sk->sk_state == TCP_CLOSE) { copied = -ENOTCONN; break; } if (!timeo) { copied = -EAGAIN; break; } if (signal_pending(current)) { copied = sock_intr_errno(timeo); break; } if (sk->sk_backlog.tail) { /* Do not sleep, just process backlog. */ release_sock(sk); lock_sock(sk); } else { sk_wait_data(sk, &timeo, NULL); } if (unlikely(peek_seq != tp->copied_seq)) { if (net_ratelimit()) pr_info("TCP(%s:%d), race in MSG_PEEK.\n", current->comm, current->pid); peek_seq = tp->copied_seq; } continue; found_ok_skb: avail = skb->len - offset; if (len < avail) avail = len; /* * Do we have urgent data here? We need to skip over the * urgent byte. */ if (unlikely(tp->urg_data)) { u32 urg_offset = tp->urg_seq - peek_seq; if (urg_offset < avail) { /* * The amount of data we are preparing to copy * contains urgent data. */ if (!urg_offset) { /* First byte is urgent */ if (!sock_flag(sk, SOCK_URGINLINE)) { peek_seq++; offset++; avail--; } if (!avail) continue; } else { /* stop short of the urgent data */ avail = urg_offset; } } } /* * If MSG_TRUNC is specified the data is discarded. */ if (likely(!(flags & MSG_TRUNC))) if (skb_copy_datagram_msg(skb, offset, msg, len)) { if (!copied) { copied = -EFAULT; break; } } peek_seq += avail; copied += avail; len -= avail; } while (len > 0); release_sock(sk); return copied; } int chtls_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int nonblock, int flags, int *addr_len) { struct tcp_sock *tp = tcp_sk(sk); struct chtls_sock *csk; unsigned long avail; /* amount of available data in current skb */ int buffers_freed; int copied = 0; long timeo; int target; /* Read at least this many bytes */ buffers_freed = 0; if (unlikely(flags & MSG_OOB)) return tcp_prot.recvmsg(sk, msg, len, nonblock, flags, addr_len); if (unlikely(flags & MSG_PEEK)) return peekmsg(sk, msg, len, nonblock, flags); if (sk_can_busy_loop(sk) && skb_queue_empty(&sk->sk_receive_queue) && sk->sk_state == TCP_ESTABLISHED) sk_busy_loop(sk, nonblock); lock_sock(sk); csk = rcu_dereference_sk_user_data(sk); if (is_tls_rx(csk)) return chtls_pt_recvmsg(sk, msg, len, nonblock, flags, addr_len); timeo = sock_rcvtimeo(sk, nonblock); target = sock_rcvlowat(sk, flags & MSG_WAITALL, len); if (unlikely(csk_flag(sk, CSK_UPDATE_RCV_WND))) chtls_cleanup_rbuf(sk, copied); do { struct sk_buff *skb; u32 offset; if (unlikely(tp->urg_data && tp->urg_seq == tp->copied_seq)) { if (copied) break; if (signal_pending(current)) { copied = timeo ? sock_intr_errno(timeo) : -EAGAIN; break; } } skb = skb_peek(&sk->sk_receive_queue); if (skb) goto found_ok_skb; if (csk->wr_credits && skb_queue_len(&csk->txq) && chtls_push_frames(csk, csk->wr_credits == csk->wr_max_credits)) sk->sk_write_space(sk); if (copied >= target && !sk->sk_backlog.tail) break; if (copied) { if (sk->sk_err || sk->sk_state == TCP_CLOSE || (sk->sk_shutdown & RCV_SHUTDOWN) || signal_pending(current)) break; } else { if (sock_flag(sk, SOCK_DONE)) break; if (sk->sk_err) { copied = sock_error(sk); break; } if (sk->sk_shutdown & RCV_SHUTDOWN) break; if (sk->sk_state == TCP_CLOSE) { copied = -ENOTCONN; break; } if (!timeo) { copied = -EAGAIN; break; } if (signal_pending(current)) { copied = sock_intr_errno(timeo); break; } } if (sk->sk_backlog.tail) { release_sock(sk); lock_sock(sk); chtls_cleanup_rbuf(sk, copied); continue; } if (copied >= target) break; chtls_cleanup_rbuf(sk, copied); sk_wait_data(sk, &timeo, NULL); continue; found_ok_skb: if (!skb->len) { chtls_kfree_skb(sk, skb); if (!copied && !timeo) { copied = -EAGAIN; break; } if (copied < target) continue; break; } offset = tp->copied_seq - ULP_SKB_CB(skb)->seq; avail = skb->len - offset; if (len < avail) avail = len; if (unlikely(tp->urg_data)) { u32 urg_offset = tp->urg_seq - tp->copied_seq; if (urg_offset < avail) { if (urg_offset) { avail = urg_offset; } else if (!sock_flag(sk, SOCK_URGINLINE)) { tp->copied_seq++; offset++; avail--; if (!avail) goto skip_copy; } } } if (likely(!(flags & MSG_TRUNC))) { if (skb_copy_datagram_msg(skb, offset, msg, avail)) { if (!copied) { copied = -EFAULT; break; } } } tp->copied_seq += avail; copied += avail; len -= avail; skip_copy: if (tp->urg_data && after(tp->copied_seq, tp->urg_seq)) tp->urg_data = 0; if (avail + offset >= skb->len) { if (likely(skb)) chtls_free_skb(sk, skb); buffers_freed++; if (copied >= target && !skb_peek(&sk->sk_receive_queue)) break; } } while (len > 0); if (buffers_freed) chtls_cleanup_rbuf(sk, copied); release_sock(sk); return copied; }
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1