| // SPDX-License-Identifier: GPL-2.0-only |
| /* Copyright (C) 2020 Marvell. */ |
| |
| #include "otx2_cptvf.h" |
| #include "otx2_cpt_common.h" |
| |
| /* 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 |
| |
| /* Default command timeout in seconds */ |
| #define CPT_COMMAND_TIMEOUT 4 |
| #define CPT_TIME_IN_RESET_COUNT 5 |
| |
| static void otx2_cpt_dump_sg_list(struct pci_dev *pdev, |
| struct otx2_cpt_req_info *req) |
| { |
| int i; |
| |
| pr_debug("Gather list size %d\n", req->in_cnt); |
| for (i = 0; i < req->in_cnt; 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->out_cnt); |
| for (i = 0; i < req->out_cnt; 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 otx2_cpt_pending_entry *get_free_pending_entry( |
| struct otx2_cpt_pending_queue *q, |
| int qlen) |
| { |
| struct otx2_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 otx2_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 otx2_cpt_buf_ptr *list, |
| int buf_count, u8 *buffer) |
| { |
| struct otx2_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 (unlikely(!list[i].vptr)) |
| continue; |
| 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 otx2_cpt_sglist_component *)buffer; |
| for (i = 0; i < components; i++) { |
| sg_ptr->len0 = cpu_to_be16(list[i * 4 + 0].size); |
| sg_ptr->len1 = cpu_to_be16(list[i * 4 + 1].size); |
| sg_ptr->len2 = cpu_to_be16(list[i * 4 + 2].size); |
| sg_ptr->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->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->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->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[j].dma_addr, |
| list[j].size, DMA_BIDIRECTIONAL); |
| } |
| |
| list[j].dma_addr = 0; |
| } |
| return ret; |
| } |
| |
| static inline struct otx2_cpt_inst_info *info_create(struct pci_dev *pdev, |
| struct otx2_cpt_req_info *req, |
| gfp_t gfp) |
| { |
| int align = OTX2_CPT_DMA_MINALIGN; |
| struct otx2_cpt_inst_info *info; |
| u32 dlen, align_dlen, info_len; |
| u16 g_sz_bytes, s_sz_bytes; |
| u32 total_mem_len; |
| |
| if (unlikely(req->in_cnt > OTX2_CPT_MAX_SG_IN_CNT || |
| req->out_cnt > OTX2_CPT_MAX_SG_OUT_CNT)) { |
| dev_err(&pdev->dev, "Error too many sg components\n"); |
| return NULL; |
| } |
| |
| g_sz_bytes = ((req->in_cnt + 3) / 4) * |
| sizeof(struct otx2_cpt_sglist_component); |
| s_sz_bytes = ((req->out_cnt + 3) / 4) * |
| sizeof(struct otx2_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); |
| total_mem_len = align_dlen + info_len + sizeof(union otx2_cpt_res_s); |
| |
| info = kzalloc(total_mem_len, gfp); |
| if (unlikely(!info)) |
| return NULL; |
| |
| info->dlen = dlen; |
| info->in_buffer = (u8 *)info + info_len; |
| |
| ((u16 *)info->in_buffer)[0] = req->out_cnt; |
| ((u16 *)info->in_buffer)[1] = req->in_cnt; |
| ((u16 *)info->in_buffer)[2] = 0; |
| ((u16 *)info->in_buffer)[3] = 0; |
| cpu_to_be64s((u64 *)info->in_buffer); |
| |
| /* Setup gather (input) components */ |
| if (setup_sgio_components(pdev, req->in, req->in_cnt, |
| &info->in_buffer[8])) { |
| dev_err(&pdev->dev, "Failed to setup gather list\n"); |
| goto destroy_info; |
| } |
| |
| if (setup_sgio_components(pdev, req->out, req->out_cnt, |
| &info->in_buffer[8 + g_sz_bytes])) { |
| dev_err(&pdev->dev, "Failed to setup scatter list\n"); |
| goto destroy_info; |
| } |
| |
| info->dma_len = total_mem_len - info_len; |
| info->dptr_baddr = dma_map_single(&pdev->dev, 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"); |
| goto destroy_info; |
| } |
| /* |
| * Get buffer for union otx2_cpt_res_s response |
| * structure and its physical address |
| */ |
| info->completion_addr = info->in_buffer + align_dlen; |
| info->comp_baddr = info->dptr_baddr + align_dlen; |
| |
| return info; |
| |
| destroy_info: |
| otx2_cpt_info_destroy(pdev, info); |
| return NULL; |
| } |
| |
| static int process_request(struct pci_dev *pdev, struct otx2_cpt_req_info *req, |
| struct otx2_cpt_pending_queue *pqueue, |
| struct otx2_cptlf_info *lf) |
| { |
| struct otx2_cptvf_request *cpt_req = &req->req; |
| struct otx2_cpt_pending_entry *pentry = NULL; |
| union otx2_cpt_ctrl_info *ctrl = &req->ctrl; |
| struct otx2_cpt_inst_info *info = NULL; |
| union otx2_cpt_res_s *result = NULL; |
| struct otx2_cpt_iq_command iq_cmd; |
| union otx2_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; |
| if (unlikely(!otx2_cptlf_started(lf->lfs))) |
| return -ENODEV; |
| |
| info = info_create(pdev, req, gfp); |
| if (unlikely(!info)) { |
| dev_err(&pdev->dev, "Setting up cpt inst info failed"); |
| return -ENOMEM; |
| } |
| cpt_req->dlen = info->dlen; |
| |
| result = info->completion_addr; |
| result->s.compcode = OTX2_CPT_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; |
| goto destroy_info; |
| } |
| |
| /* |
| * 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.u = 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); |
| |
| /* 64-bit swap for microcode data reads, not needed for addresses*/ |
| cpu_to_be64s(&iq_cmd.cmd.u); |
| iq_cmd.dptr = info->dptr_baddr; |
| iq_cmd.rptr = 0; |
| iq_cmd.cptr.u = 0; |
| iq_cmd.cptr.s.grp = ctrl->s.grp; |
| |
| /* Fill in the CPT_INST_S type command for HW interpretation */ |
| otx2_cpt_fill_inst(&cptinst, &iq_cmd, info->comp_baddr); |
| |
| /* Print debug info if enabled */ |
| otx2_cpt_dump_sg_list(pdev, req); |
| pr_debug("Cpt_inst_s hexdump (%d bytes)\n", OTX2_CPT_INST_SIZE); |
| print_hex_dump_debug("", 0, 16, 1, &cptinst, OTX2_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 */ |
| lf->lfs->ops->send_cmd(&cptinst, 1, lf); |
| |
| /* |
| * 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; |
| |
| destroy_info: |
| spin_unlock_bh(&pqueue->lock); |
| otx2_cpt_info_destroy(pdev, info); |
| return ret; |
| } |
| |
| int otx2_cpt_do_request(struct pci_dev *pdev, struct otx2_cpt_req_info *req, |
| int cpu_num) |
| { |
| struct otx2_cptvf_dev *cptvf = pci_get_drvdata(pdev); |
| struct otx2_cptlfs_info *lfs = &cptvf->lfs; |
| |
| return process_request(lfs->pdev, req, &lfs->lf[cpu_num].pqueue, |
| &lfs->lf[cpu_num]); |
| } |
| |
| static int cpt_process_ccode(struct otx2_cptlfs_info *lfs, |
| union otx2_cpt_res_s *cpt_status, |
| struct otx2_cpt_inst_info *info, |
| u32 *res_code) |
| { |
| u8 uc_ccode = lfs->ops->cpt_get_uc_compcode(cpt_status); |
| u8 ccode = lfs->ops->cpt_get_compcode(cpt_status); |
| struct pci_dev *pdev = lfs->pdev; |
| |
| switch (ccode) { |
| case OTX2_CPT_COMP_E_FAULT: |
| dev_err(&pdev->dev, |
| "Request failed with DMA fault\n"); |
| otx2_cpt_dump_sg_list(pdev, info->req); |
| break; |
| |
| case OTX2_CPT_COMP_E_HWERR: |
| dev_err(&pdev->dev, |
| "Request failed with hardware error\n"); |
| otx2_cpt_dump_sg_list(pdev, info->req); |
| break; |
| |
| case OTX2_CPT_COMP_E_INSTERR: |
| dev_err(&pdev->dev, |
| "Request failed with instruction error\n"); |
| otx2_cpt_dump_sg_list(pdev, info->req); |
| break; |
| |
| case OTX2_CPT_COMP_E_NOTDONE: |
| /* check for timeout */ |
| if (time_after_eq(jiffies, info->time_in + |
| CPT_COMMAND_TIMEOUT * HZ)) |
| dev_warn(&pdev->dev, |
| "Request timed out 0x%p", info->req); |
| else if (info->extra_time < CPT_TIME_IN_RESET_COUNT) { |
| info->time_in = jiffies; |
| info->extra_time++; |
| } |
| return 1; |
| |
| case OTX2_CPT_COMP_E_GOOD: |
| case OTX2_CPT_COMP_E_WARN: |
| /* |
| * Check microcode completion code, it is only valid |
| * when completion code is CPT_COMP_E::GOOD |
| */ |
| if (uc_ccode != OTX2_CPT_UCC_SUCCESS) { |
| /* |
| * 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 (info->req->is_trunc_hmac && |
| uc_ccode == OTX2_CPT_UCC_SG_WRITE_LENGTH) { |
| *res_code = 0; |
| break; |
| } |
| |
| dev_err(&pdev->dev, |
| "Request failed with software error code 0x%x\n", |
| cpt_status->s.uc_compcode); |
| otx2_cpt_dump_sg_list(pdev, info->req); |
| break; |
| } |
| /* Request has been processed with success */ |
| *res_code = 0; |
| break; |
| |
| default: |
| dev_err(&pdev->dev, |
| "Request returned invalid status %d\n", ccode); |
| break; |
| } |
| return 0; |
| } |
| |
| static inline void process_pending_queue(struct otx2_cptlfs_info *lfs, |
| struct otx2_cpt_pending_queue *pqueue) |
| { |
| struct otx2_cpt_pending_entry *resume_pentry = NULL; |
| void (*callback)(int status, void *arg, void *req); |
| struct otx2_cpt_pending_entry *pentry = NULL; |
| union otx2_cpt_res_s *cpt_status = NULL; |
| struct otx2_cpt_inst_info *info = NULL; |
| struct otx2_cpt_req_info *req = NULL; |
| struct crypto_async_request *areq; |
| struct pci_dev *pdev = lfs->pdev; |
| 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; |
| } |
| |
| info = pentry->info; |
| if (unlikely(!info)) { |
| dev_err(&pdev->dev, "Pending entry post arg NULL\n"); |
| goto process_pentry; |
| } |
| |
| req = info->req; |
| if (unlikely(!req)) { |
| dev_err(&pdev->dev, "Request NULL\n"); |
| goto process_pentry; |
| } |
| |
| cpt_status = pentry->completion_addr; |
| if (unlikely(!cpt_status)) { |
| dev_err(&pdev->dev, "Completion address NULL\n"); |
| goto process_pentry; |
| } |
| |
| if (cpt_process_ccode(lfs, cpt_status, info, &res_code)) { |
| spin_unlock_bh(&pqueue->lock); |
| return; |
| } |
| 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, 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, info); |
| } |
| } |
| |
| void otx2_cpt_post_process(struct otx2_cptlf_wqe *wqe) |
| { |
| process_pending_queue(wqe->lfs, |
| &wqe->lfs->lf[wqe->lf_num].pqueue); |
| } |
| |
| int otx2_cpt_get_kcrypto_eng_grp_num(struct pci_dev *pdev) |
| { |
| struct otx2_cptvf_dev *cptvf = pci_get_drvdata(pdev); |
| |
| return cptvf->lfs.kcrypto_eng_grp_num; |
| } |