/* * Copyright (c) 2014, Cisco Systems, Inc. All rights reserved. * * LICENSE_BEGIN * * 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 * 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. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * LICENSE_END * * */ #include #include "usd.h" #include "usd_util.h" #include "cq_enet_desc.h" static inline void find_rx_lengths( struct usd_rq *rq, uint16_t q_index, size_t *posted_len_o, size_t *len_in_pkt_o) { dma_addr_t bus_addr; u16 len; u8 type; size_t rcvbuf_len; uint16_t i; i = q_index; rcvbuf_len = 0; do { rq_enet_desc_dec( (struct rq_enet_desc *) ((uintptr_t)rq->urq_desc_ring + (i<<4)), &bus_addr, &type, &len); rcvbuf_len += len; i = (i - 1) & rq->urq_post_index_mask; } while (type == RQ_ENET_TYPE_NOT_SOP); *posted_len_o = rcvbuf_len; *len_in_pkt_o = ntohs(((struct usd_udp_hdr *)bus_addr)->uh_ip.tot_len) + sizeof(struct ether_header); } static inline int usd_desc_to_rq_comp( struct usd_cq_impl *cq, struct cq_desc *desc, uint16_t qid, uint16_t q_index, struct usd_completion *comp) { struct usd_rq *rq; struct usd_qp_impl *qp; struct cq_enet_rq_desc *edesc; uint16_t bytes_written_flags; uint32_t bytes_written; uint32_t ci_flags; uint32_t ipudpok; unsigned credits; size_t len_in_pkt; size_t rcvbuf_len; edesc = (struct cq_enet_rq_desc *)desc; rq = cq->ucq_rq_map[qid]; qp = usd_container_of(rq, struct usd_qp_impl, uq_rq); bytes_written_flags = le16_to_cpu(edesc->bytes_written_flags); bytes_written = bytes_written_flags & CQ_ENET_RQ_DESC_BYTES_WRITTEN_MASK; ci_flags = le16_to_cpu(edesc->completed_index_flags); if (ci_flags & CQ_ENET_RQ_DESC_FLAGS_EOP) { comp->uc_bytes = bytes_written + rq->urq_accum_bytes; rq->urq_accum_bytes = 0; } else { rq->urq_accum_bytes += bytes_written; return -1; } comp->uc_context = rq->urq_context[q_index]; comp->uc_qp = &qp->uq_qp; ipudpok = CQ_ENET_RQ_DESC_FLAGS_IPV4_CSUM_OK | CQ_ENET_RQ_DESC_FLAGS_TCP_UDP_CSUM_OK; if (bytes_written_flags & CQ_ENET_RQ_DESC_FLAGS_TRUNCATED || (edesc->flags & ipudpok) != ipudpok) { if (((edesc->flags & CQ_ENET_RQ_DESC_FLAGS_FCS_OK) == 0) && bytes_written == 0) { find_rx_lengths(rq, q_index, &rcvbuf_len, &len_in_pkt); /* * If only the paddings to meet 64-byte minimum eth frame * requirement are truncated, do not mark packet as * error due to truncation. * The usnic hdr should not be split into multiple receive buffer * * If we could afford the extra cycles, we would also compute the * UDP checksum here and compare it to the UDP header. */ if (rcvbuf_len >= 60 || len_in_pkt > rcvbuf_len) { comp->uc_status = USD_COMPSTAT_ERROR_TRUNC; } else { comp->uc_status = USD_COMPSTAT_SUCCESS; /* TRUNC means bytes_written==0, so fix this too */ comp->uc_bytes = len_in_pkt; } } else { comp->uc_status = USD_COMPSTAT_ERROR_CRC; } } else { if (comp->uc_bytes <= 60) { /* * The sender may have attempted to send a small frame (<64-bytes) * that was padded out to 64-bytes by the sending VIC. * If we posted a recv buffer >= 60 bytes then we wouldn't see * truncation, but the bytes_written by the VIC will be larger than * the bytes the sender actually requested to send. Fix that up * here. */ find_rx_lengths(rq, q_index, &rcvbuf_len, &len_in_pkt); comp->uc_bytes = len_in_pkt; } comp->uc_status = USD_COMPSTAT_SUCCESS; } /* needs a little work in multi-SGE case, all credits currently not * reported as released until next RX */ credits = (q_index - rq->urq_last_comp) & rq->urq_post_index_mask; rq->urq_recv_credits += credits; rq->urq_last_comp = q_index; return 0; } static inline void usd_desc_to_wq_comp( struct usd_cq_impl *cq, uint16_t qid, uint16_t q_index, struct usd_completion *comp) { struct usd_wq *wq; struct usd_qp_impl *qp; struct usd_wq_post_info *info; unsigned credits; wq = cq->ucq_wq_map[qid]; qp = usd_container_of(wq, struct usd_qp_impl, uq_wq); comp->uc_qp = &qp->uq_qp; info = &wq->uwq_post_info[(q_index+1)&wq->uwq_post_index_mask]; comp->uc_context = info->wp_context; comp->uc_bytes = info->wp_len; comp->uc_status = USD_COMPSTAT_SUCCESS; credits = (q_index - wq->uwq_last_comp) & wq->uwq_post_index_mask; wq->uwq_send_credits += credits; wq->uwq_last_comp = q_index; } int usd_poll_cq_multi( struct usd_cq *ucq, int max_comps, struct usd_completion *comps) { int ret; int n; for (n = 0; n < max_comps; ++n) { ret = usd_poll_cq(ucq, comps + n); if (ret == -EAGAIN) { return n; } } return max_comps; } int usd_poll_cq( struct usd_cq *ucq, struct usd_completion *comp) { struct usd_cq_impl *cq; struct cq_desc *cq_desc; uint8_t color; uint8_t last_color; uint8_t type_color; uint8_t type; uint16_t qid; uint16_t q_index; cq = to_cqi(ucq); retry: /* check for a completion */ cq_desc = (struct cq_desc *)((uint8_t *)cq->ucq_desc_ring + (cq->ucq_next_desc << 4)); last_color = cq->ucq_last_color; type_color = cq_desc->type_color; type = type_color & 0x7f; color = type_color >> CQ_DESC_COLOR_SHIFT; qid = le16_to_cpu(cq_desc->q_number) & CQ_DESC_Q_NUM_MASK; q_index = le16_to_cpu(cq_desc->completed_index) & CQ_DESC_COMP_NDX_MASK; if (color == last_color) { return -EAGAIN; } else { /* bookkeeping */ cq->ucq_next_desc++; cq->ucq_last_color ^= (cq->ucq_next_desc >> cq->ucq_color_shift); cq->ucq_next_desc &= cq->ucq_cqe_mask; rmb(); comp->uc_type = (enum usd_completion_type) type; if (type == USD_COMPTYPE_RECV) { if (usd_desc_to_rq_comp(cq, cq_desc, qid, q_index, comp) == -1) { goto retry; } } else if (type == USD_COMPTYPE_SEND) { usd_desc_to_wq_comp(cq, qid, q_index, comp); } else { comp->uc_status = USD_COMPSTAT_ERROR_INTERNAL; } return 0; } } /* * Allow application to unmask interrupt explicitly */ int usd_poll_req_notify(struct usd_cq *ucq) { struct usd_cq_impl *cq; cq = to_cqi(ucq); /* * application uses a signal thread waiting for one completion FD, * then calling this function to unmask the interrupt source. If multiple * cqs are associated with the FD/interrupt, this may be unneccesarilly * called for subsequent cqs at each poll/wait, but it's OK. A lock isn't * used here to prevent simultaneous unmasking among multiple threads as * it's not a valid use case. * Also this call happens at data path, it's assumed that removing a * interrupt source from cq happens at control path tear down stage, when * data path is already finished. */ if (cq->comp_fd != -1 && cq->ucq_intr != NULL) vnic_intr_unmask(&cq->ucq_intr->uci_vintr); return 0; }