// SPDX-License-Identifier: GPL-2.0 /* Marvell OcteonTX CPT driver * * Copyright (C) 2019 Marvell International Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include "otx_cptvf.h" #include "otx_cptvf_algs.h" /* Completion code size and initial value */ #define COMPLETION_CODE_SIZE 8 #define COMPLETION_CODE_INIT 0 /* SG list header size in bytes */ #define SG_LIST_HDR_SIZE 8 /* Default timeout when waiting for free pending entry in us */ #define CPT_PENTRY_TIMEOUT 1000 #define CPT_PENTRY_STEP 50 /* Default threshold for stopping and resuming sender requests */ #define CPT_IQ_STOP_MARGIN 128 #define CPT_IQ_RESUME_MARGIN 512 #define CPT_DMA_ALIGN 128 void otx_cpt_dump_sg_list(struct pci_dev *pdev, struct otx_cpt_req_info *req) { int i; pr_debug("Gather list size %d\n", req->incnt); for (i = 0; i < req->incnt; i++) { pr_debug("Buffer %d size %d, vptr 0x%p, dmaptr 0x%p\n", i, req->in[i].size, req->in[i].vptr, (void *) req->in[i].dma_addr); pr_debug("Buffer hexdump (%d bytes)\n", req->in[i].size); print_hex_dump_debug("", DUMP_PREFIX_NONE, 16, 1, req->in[i].vptr, req->in[i].size, false); } pr_debug("Scatter list size %d\n", req->outcnt); for (i = 0; i < req->outcnt; i++) { pr_debug("Buffer %d size %d, vptr 0x%p, dmaptr 0x%p\n", i, req->out[i].size, req->out[i].vptr, (void *) req->out[i].dma_addr); pr_debug("Buffer hexdump (%d bytes)\n", req->out[i].size); print_hex_dump_debug("", DUMP_PREFIX_NONE, 16, 1, req->out[i].vptr, req->out[i].size, false); } } static inline struct otx_cpt_pending_entry *get_free_pending_entry( struct otx_cpt_pending_queue *q, int qlen) { struct otx_cpt_pending_entry *ent = NULL; ent = &q->head[q->rear]; if (unlikely(ent->busy)) return NULL; q->rear++; if (unlikely(q->rear == qlen)) q->rear = 0; return ent; } static inline u32 modulo_inc(u32 index, u32 length, u32 inc) { if (WARN_ON(inc > length)) inc = length; index += inc; if (unlikely(index >= length)) index -= length; return index; } static inline void free_pentry(struct otx_cpt_pending_entry *pentry) { pentry->completion_addr = NULL; pentry->info = NULL; pentry->callback = NULL; pentry->areq = NULL; pentry->resume_sender = false; pentry->busy = false; } static inline int setup_sgio_components(struct pci_dev *pdev, struct otx_cpt_buf_ptr *list, int buf_count, u8 *buffer) { struct otx_cpt_sglist_component *sg_ptr = NULL; int ret = 0, i, j; int components; if (unlikely(!list)) { dev_err(&pdev->dev, "Input list pointer is NULL\n"); return -EFAULT; } for (i = 0; i < buf_count; i++) { if (likely(list[i].vptr)) { list[i].dma_addr = dma_map_single(&pdev->dev, list[i].vptr, list[i].size, DMA_BIDIRECTIONAL); if (unlikely(dma_mapping_error(&pdev->dev, list[i].dma_addr))) { dev_err(&pdev->dev, "Dma mapping failed\n"); ret = -EIO; goto sg_cleanup; } } } components = buf_count / 4; sg_ptr = (struct otx_cpt_sglist_component *)buffer; for (i = 0; i < components; i++) { sg_ptr->u.s.len0 = cpu_to_be16(list[i * 4 + 0].size); sg_ptr->u.s.len1 = cpu_to_be16(list[i * 4 + 1].size); sg_ptr->u.s.len2 = cpu_to_be16(list[i * 4 + 2].size); sg_ptr->u.s.len3 = cpu_to_be16(list[i * 4 + 3].size); sg_ptr->ptr0 = cpu_to_be64(list[i * 4 + 0].dma_addr); sg_ptr->ptr1 = cpu_to_be64(list[i * 4 + 1].dma_addr); sg_ptr->ptr2 = cpu_to_be64(list[i * 4 + 2].dma_addr); sg_ptr->ptr3 = cpu_to_be64(list[i * 4 + 3].dma_addr); sg_ptr++; } components = buf_count % 4; switch (components) { case 3: sg_ptr->u.s.len2 = cpu_to_be16(list[i * 4 + 2].size); sg_ptr->ptr2 = cpu_to_be64(list[i * 4 + 2].dma_addr); fallthrough; case 2: sg_ptr->u.s.len1 = cpu_to_be16(list[i * 4 + 1].size); sg_ptr->ptr1 = cpu_to_be64(list[i * 4 + 1].dma_addr); fallthrough; case 1: sg_ptr->u.s.len0 = cpu_to_be16(list[i * 4 + 0].size); sg_ptr->ptr0 = cpu_to_be64(list[i * 4 + 0].dma_addr); break; default: break; } return ret; sg_cleanup: for (j = 0; j < i; j++) { if (list[j].dma_addr) { dma_unmap_single(&pdev->dev, list[i].dma_addr, list[i].size, DMA_BIDIRECTIONAL); } list[j].dma_addr = 0; } return ret; } static inline int setup_sgio_list(struct pci_dev *pdev, struct otx_cpt_info_buffer **pinfo, struct otx_cpt_req_info *req, gfp_t gfp) { u32 dlen, align_dlen, info_len, rlen; struct otx_cpt_info_buffer *info; u16 g_sz_bytes, s_sz_bytes; int align = CPT_DMA_ALIGN; u32 total_mem_len; if (unlikely(req->incnt > OTX_CPT_MAX_SG_IN_CNT || req->outcnt > OTX_CPT_MAX_SG_OUT_CNT)) { dev_err(&pdev->dev, "Error too many sg components\n"); return -EINVAL; } g_sz_bytes = ((req->incnt + 3) / 4) * sizeof(struct otx_cpt_sglist_component); s_sz_bytes = ((req->outcnt + 3) / 4) * sizeof(struct otx_cpt_sglist_component); dlen = g_sz_bytes + s_sz_bytes + SG_LIST_HDR_SIZE; align_dlen = ALIGN(dlen, align); info_len = ALIGN(sizeof(*info), align); rlen = ALIGN(sizeof(union otx_cpt_res_s), align); total_mem_len = align_dlen + info_len + rlen + COMPLETION_CODE_SIZE; info = kzalloc(total_mem_len, gfp); if (unlikely(!info)) { dev_err(&pdev->dev, "Memory allocation failed\n"); return -ENOMEM; } *pinfo = info; info->dlen = dlen; info->in_buffer = (u8 *)info + info_len; ((__be16 *)info->in_buffer)[0] = cpu_to_be16(req->outcnt); ((__be16 *)info->in_buffer)[1] = cpu_to_be16(req->incnt); ((u16 *)info->in_buffer)[2] = 0; ((u16 *)info->in_buffer)[3] = 0; /* Setup gather (input) components */ if (setup_sgio_components(pdev, req->in, req->incnt, &info->in_buffer[8])) { dev_err(&pdev->dev, "Failed to setup gather list\n"); return -EFAULT; } if (setup_sgio_components(pdev, req->out, req->outcnt, &info->in_buffer[8 + g_sz_bytes])) { dev_err(&pdev->dev, "Failed to setup scatter list\n"); return -EFAULT; } info->dma_len = total_mem_len - info_len; info->dptr_baddr = dma_map_single(&pdev->dev, (void *)info->in_buffer, info->dma_len, DMA_BIDIRECTIONAL); if (unlikely(dma_mapping_error(&pdev->dev, info->dptr_baddr))) { dev_err(&pdev->dev, "DMA Mapping failed for cpt req\n"); return -EIO; } /* * Get buffer for union otx_cpt_res_s response * structure and its physical address */ info->completion_addr = (u64 *)(info->in_buffer + align_dlen); info->comp_baddr = info->dptr_baddr + align_dlen; /* Create and initialize RPTR */ info->out_buffer = (u8 *)info->completion_addr + rlen; info->rptr_baddr = info->comp_baddr + rlen; *((u64 *) info->out_buffer) = ~((u64) COMPLETION_CODE_INIT); return 0; } static void cpt_fill_inst(union otx_cpt_inst_s *inst, struct otx_cpt_info_buffer *info, struct otx_cpt_iq_cmd *cmd) { inst->u[0] = 0x0; inst->s.doneint = true; inst->s.res_addr = (u64)info->comp_baddr; inst->u[2] = 0x0; inst->s.wq_ptr = 0; inst->s.ei0 = cmd->cmd.u64; inst->s.ei1 = cmd->dptr; inst->s.ei2 = cmd->rptr; inst->s.ei3 = cmd->cptr.u64; } /* * On OcteonTX platform the parameter db_count is used as a count for ringing * door bell. The valid values for db_count are: * 0 - 1 CPT instruction will be enqueued however CPT will not be informed * 1 - 1 CPT instruction will be enqueued and CPT will be informed */ static void cpt_send_cmd(union otx_cpt_inst_s *cptinst, struct otx_cptvf *cptvf) { struct otx_cpt_cmd_qinfo *qinfo = &cptvf->cqinfo; struct otx_cpt_cmd_queue *queue; struct otx_cpt_cmd_chunk *curr; u8 *ent; queue = &qinfo->queue[0]; /* * cpt_send_cmd is currently called only from critical section * therefore no locking is required for accessing instruction queue */ ent = &queue->qhead->head[queue->idx * OTX_CPT_INST_SIZE]; memcpy(ent, (void *) cptinst, OTX_CPT_INST_SIZE); if (++queue->idx >= queue->qhead->size / 64) { curr = queue->qhead; if (list_is_last(&curr->nextchunk, &queue->chead)) queue->qhead = queue->base; else queue->qhead = list_next_entry(queue->qhead, nextchunk); queue->idx = 0; } /* make sure all memory stores are done before ringing doorbell */ smp_wmb(); otx_cptvf_write_vq_doorbell(cptvf, 1); } static int process_request(struct pci_dev *pdev, struct otx_cpt_req_info *req, struct otx_cpt_pending_queue *pqueue, struct otx_cptvf *cptvf) { struct otx_cptvf_request *cpt_req = &req->req; struct otx_cpt_pending_entry *pentry = NULL; union otx_cpt_ctrl_info *ctrl = &req->ctrl; struct otx_cpt_info_buffer *info = NULL; union otx_cpt_res_s *result = NULL; struct otx_cpt_iq_cmd iq_cmd; union otx_cpt_inst_s cptinst; int retry, ret = 0; u8 resume_sender; gfp_t gfp; gfp = (req->areq->flags & CRYPTO_TFM_REQ_MAY_SLEEP) ? GFP_KERNEL : GFP_ATOMIC; ret = setup_sgio_list(pdev, &info, req, gfp); if (unlikely(ret)) { dev_err(&pdev->dev, "Setting up SG list failed\n"); goto request_cleanup; } cpt_req->dlen = info->dlen; result = (union otx_cpt_res_s *) info->completion_addr; result->s.compcode = COMPLETION_CODE_INIT; spin_lock_bh(&pqueue->lock); pentry = get_free_pending_entry(pqueue, pqueue->qlen); retry = CPT_PENTRY_TIMEOUT / CPT_PENTRY_STEP; while (unlikely(!pentry) && retry--) { spin_unlock_bh(&pqueue->lock); udelay(CPT_PENTRY_STEP); spin_lock_bh(&pqueue->lock); pentry = get_free_pending_entry(pqueue, pqueue->qlen); } if (unlikely(!pentry)) { ret = -ENOSPC; spin_unlock_bh(&pqueue->lock); goto request_cleanup; } /* * Check if we are close to filling in entire pending queue, * if so then tell the sender to stop/sleep by returning -EBUSY * We do it only for context which can sleep (GFP_KERNEL) */ if (gfp == GFP_KERNEL && pqueue->pending_count > (pqueue->qlen - CPT_IQ_STOP_MARGIN)) { pentry->resume_sender = true; } else pentry->resume_sender = false; resume_sender = pentry->resume_sender; pqueue->pending_count++; pentry->completion_addr = info->completion_addr; pentry->info = info; pentry->callback = req->callback; pentry->areq = req->areq; pentry->busy = true; info->pentry = pentry; info->time_in = jiffies; info->req = req; /* Fill in the command */ iq_cmd.cmd.u64 = 0; iq_cmd.cmd.s.opcode = cpu_to_be16(cpt_req->opcode.flags); iq_cmd.cmd.s.param1 = cpu_to_be16(cpt_req->param1); iq_cmd.cmd.s.param2 = cpu_to_be16(cpt_req->param2); iq_cmd.cmd.s.dlen = cpu_to_be16(cpt_req->dlen); iq_cmd.dptr = info->dptr_baddr; iq_cmd.rptr = info->rptr_baddr; iq_cmd.cptr.u64 = 0; iq_cmd.cptr.s.grp = ctrl->s.grp; /* Fill in the CPT_INST_S type command for HW interpretation */ cpt_fill_inst(&cptinst, info, &iq_cmd); /* Print debug info if enabled */ otx_cpt_dump_sg_list(pdev, req); pr_debug("Cpt_inst_s hexdump (%d bytes)\n", OTX_CPT_INST_SIZE); print_hex_dump_debug("", 0, 16, 1, &cptinst, OTX_CPT_INST_SIZE, false); pr_debug("Dptr hexdump (%d bytes)\n", cpt_req->dlen); print_hex_dump_debug("", 0, 16, 1, info->in_buffer, cpt_req->dlen, false); /* Send CPT command */ cpt_send_cmd(&cptinst, cptvf); /* * We allocate and prepare pending queue entry in critical section * together with submitting CPT instruction to CPT instruction queue * to make sure that order of CPT requests is the same in both * pending and instruction queues */ spin_unlock_bh(&pqueue->lock); ret = resume_sender ? -EBUSY : -EINPROGRESS; return ret; request_cleanup: do_request_cleanup(pdev, info); return ret; } int otx_cpt_do_request(struct pci_dev *pdev, struct otx_cpt_req_info *req, int cpu_num) { struct otx_cptvf *cptvf = pci_get_drvdata(pdev); if (!otx_cpt_device_ready(cptvf)) { dev_err(&pdev->dev, "CPT Device is not ready\n"); return -ENODEV; } if ((cptvf->vftype == OTX_CPT_SE_TYPES) && (!req->ctrl.s.se_req)) { dev_err(&pdev->dev, "CPTVF-%d of SE TYPE got AE request\n", cptvf->vfid); return -EINVAL; } else if ((cptvf->vftype == OTX_CPT_AE_TYPES) && (req->ctrl.s.se_req)) { dev_err(&pdev->dev, "CPTVF-%d of AE TYPE got SE request\n", cptvf->vfid); return -EINVAL; } return process_request(pdev, req, &cptvf->pqinfo.queue[0], cptvf); } static int cpt_process_ccode(struct pci_dev *pdev, union otx_cpt_res_s *cpt_status, struct otx_cpt_info_buffer *cpt_info, struct otx_cpt_req_info *req, u32 *res_code) { u8 ccode = cpt_status->s.compcode; union otx_cpt_error_code ecode; ecode.u = be64_to_cpup((__be64 *)cpt_info->out_buffer); switch (ccode) { case CPT_COMP_E_FAULT: dev_err(&pdev->dev, "Request failed with DMA fault\n"); otx_cpt_dump_sg_list(pdev, req); break; case CPT_COMP_E_SWERR: dev_err(&pdev->dev, "Request failed with software error code %d\n", ecode.s.ccode); otx_cpt_dump_sg_list(pdev, req); break; case CPT_COMP_E_HWERR: dev_err(&pdev->dev, "Request failed with hardware error\n"); otx_cpt_dump_sg_list(pdev, req); break; case COMPLETION_CODE_INIT: /* check for timeout */ if (time_after_eq(jiffies, cpt_info->time_in + OTX_CPT_COMMAND_TIMEOUT * HZ)) dev_warn(&pdev->dev, "Request timed out 0x%p\n", req); else if (cpt_info->extra_time < OTX_CPT_TIME_IN_RESET_COUNT) { cpt_info->time_in = jiffies; cpt_info->extra_time++; } return 1; case CPT_COMP_E_GOOD: /* Check microcode completion code */ if (ecode.s.ccode) { /* * If requested hmac is truncated and ucode returns * s/g write length error then we report success * because ucode writes as many bytes of calculated * hmac as available in gather buffer and reports * s/g write length error if number of bytes in gather * buffer is less than full hmac size. */ if (req->is_trunc_hmac && ecode.s.ccode == ERR_SCATTER_GATHER_WRITE_LENGTH) { *res_code = 0; break; } dev_err(&pdev->dev, "Request failed with software error code 0x%x\n", ecode.s.ccode); otx_cpt_dump_sg_list(pdev, req); break; } /* Request has been processed with success */ *res_code = 0; break; default: dev_err(&pdev->dev, "Request returned invalid status\n"); break; } return 0; } static inline void process_pending_queue(struct pci_dev *pdev, struct otx_cpt_pending_queue *pqueue) { void (*callback)(int status, void *arg1, void *arg2); struct otx_cpt_pending_entry *resume_pentry = NULL; struct otx_cpt_pending_entry *pentry = NULL; struct otx_cpt_info_buffer *cpt_info = NULL; union otx_cpt_res_s *cpt_status = NULL; struct otx_cpt_req_info *req = NULL; struct crypto_async_request *areq; u32 res_code, resume_index; while (1) { spin_lock_bh(&pqueue->lock); pentry = &pqueue->head[pqueue->front]; if (WARN_ON(!pentry)) { spin_unlock_bh(&pqueue->lock); break; } res_code = -EINVAL; if (unlikely(!pentry->busy)) { spin_unlock_bh(&pqueue->lock); break; } if (unlikely(!pentry->callback)) { dev_err(&pdev->dev, "Callback NULL\n"); goto process_pentry; } cpt_info = pentry->info; if (unlikely(!cpt_info)) { dev_err(&pdev->dev, "Pending entry post arg NULL\n"); goto process_pentry; } req = cpt_info->req; if (unlikely(!req)) { dev_err(&pdev->dev, "Request NULL\n"); goto process_pentry; } cpt_status = (union otx_cpt_res_s *) pentry->completion_addr; if (unlikely(!cpt_status)) { dev_err(&pdev->dev, "Completion address NULL\n"); goto process_pentry; } if (cpt_process_ccode(pdev, cpt_status, cpt_info, req, &res_code)) { spin_unlock_bh(&pqueue->lock); return; } cpt_info->pdev = pdev; process_pentry: /* * Check if we should inform sending side to resume * We do it CPT_IQ_RESUME_MARGIN elements in advance before * pending queue becomes empty */ resume_index = modulo_inc(pqueue->front, pqueue->qlen, CPT_IQ_RESUME_MARGIN); resume_pentry = &pqueue->head[resume_index]; if (resume_pentry && resume_pentry->resume_sender) { resume_pentry->resume_sender = false; callback = resume_pentry->callback; areq = resume_pentry->areq; if (callback) { spin_unlock_bh(&pqueue->lock); /* * EINPROGRESS is an indication for sending * side that it can resume sending requests */ callback(-EINPROGRESS, areq, cpt_info); spin_lock_bh(&pqueue->lock); } } callback = pentry->callback; areq = pentry->areq; free_pentry(pentry); pqueue->pending_count--; pqueue->front = modulo_inc(pqueue->front, pqueue->qlen, 1); spin_unlock_bh(&pqueue->lock); /* * Call callback after current pending entry has been * processed, we don't do it if the callback pointer is * invalid. */ if (callback) callback(res_code, areq, cpt_info); } } void otx_cpt_post_process(struct otx_cptvf_wqe *wqe) { process_pending_queue(wqe->cptvf->pdev, &wqe->cptvf->pqinfo.queue[0]); }