// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) /* Copyright (c) 2020 Marvell International Ltd. */ #include #include #include #include "qed_dev_api.h" static void qed_chain_init(struct qed_chain *chain, const struct qed_chain_init_params *params, u32 page_cnt) { memset(chain, 0, sizeof(*chain)); chain->elem_size = params->elem_size; chain->intended_use = params->intended_use; chain->mode = params->mode; chain->cnt_type = params->cnt_type; chain->elem_per_page = ELEMS_PER_PAGE(params->elem_size, params->page_size); chain->usable_per_page = USABLE_ELEMS_PER_PAGE(params->elem_size, params->page_size, params->mode); chain->elem_unusable = UNUSABLE_ELEMS_PER_PAGE(params->elem_size, params->mode); chain->elem_per_page_mask = chain->elem_per_page - 1; chain->next_page_mask = chain->usable_per_page & chain->elem_per_page_mask; chain->page_size = params->page_size; chain->page_cnt = page_cnt; chain->capacity = chain->usable_per_page * page_cnt; chain->size = chain->elem_per_page * page_cnt; if (params->ext_pbl_virt) { chain->pbl_sp.table_virt = params->ext_pbl_virt; chain->pbl_sp.table_phys = params->ext_pbl_phys; chain->b_external_pbl = true; } } static void qed_chain_init_next_ptr_elem(const struct qed_chain *chain, void *virt_curr, void *virt_next, dma_addr_t phys_next) { struct qed_chain_next *next; u32 size; size = chain->elem_size * chain->usable_per_page; next = virt_curr + size; DMA_REGPAIR_LE(next->next_phys, phys_next); next->next_virt = virt_next; } static void qed_chain_init_mem(struct qed_chain *chain, void *virt_addr, dma_addr_t phys_addr) { chain->p_virt_addr = virt_addr; chain->p_phys_addr = phys_addr; } static void qed_chain_free_next_ptr(struct qed_dev *cdev, struct qed_chain *chain) { struct device *dev = &cdev->pdev->dev; struct qed_chain_next *next; dma_addr_t phys, phys_next; void *virt, *virt_next; u32 size, i; size = chain->elem_size * chain->usable_per_page; virt = chain->p_virt_addr; phys = chain->p_phys_addr; for (i = 0; i < chain->page_cnt; i++) { if (!virt) break; next = virt + size; virt_next = next->next_virt; phys_next = HILO_DMA_REGPAIR(next->next_phys); dma_free_coherent(dev, chain->page_size, virt, phys); virt = virt_next; phys = phys_next; } } static void qed_chain_free_single(struct qed_dev *cdev, struct qed_chain *chain) { if (!chain->p_virt_addr) return; dma_free_coherent(&cdev->pdev->dev, chain->page_size, chain->p_virt_addr, chain->p_phys_addr); } static void qed_chain_free_pbl(struct qed_dev *cdev, struct qed_chain *chain) { struct device *dev = &cdev->pdev->dev; struct addr_tbl_entry *entry; u32 i; if (!chain->pbl.pp_addr_tbl) return; for (i = 0; i < chain->page_cnt; i++) { entry = chain->pbl.pp_addr_tbl + i; if (!entry->virt_addr) break; dma_free_coherent(dev, chain->page_size, entry->virt_addr, entry->dma_map); } if (!chain->b_external_pbl) dma_free_coherent(dev, chain->pbl_sp.table_size, chain->pbl_sp.table_virt, chain->pbl_sp.table_phys); vfree(chain->pbl.pp_addr_tbl); chain->pbl.pp_addr_tbl = NULL; } /** * qed_chain_free() - Free chain DMA memory. * * @cdev: Main device structure. * @chain: Chain to free. */ void qed_chain_free(struct qed_dev *cdev, struct qed_chain *chain) { switch (chain->mode) { case QED_CHAIN_MODE_NEXT_PTR: qed_chain_free_next_ptr(cdev, chain); break; case QED_CHAIN_MODE_SINGLE: qed_chain_free_single(cdev, chain); break; case QED_CHAIN_MODE_PBL: qed_chain_free_pbl(cdev, chain); break; default: return; } qed_chain_init_mem(chain, NULL, 0); } static int qed_chain_alloc_sanity_check(struct qed_dev *cdev, const struct qed_chain_init_params *params, u32 page_cnt) { u64 chain_size; chain_size = ELEMS_PER_PAGE(params->elem_size, params->page_size); chain_size *= page_cnt; if (!chain_size) return -EINVAL; /* The actual chain size can be larger than the maximal possible value * after rounding up the requested elements number to pages, and after * taking into account the unusuable elements (next-ptr elements). * The size of a "u16" chain can be (U16_MAX + 1) since the chain * size/capacity fields are of u32 type. */ switch (params->cnt_type) { case QED_CHAIN_CNT_TYPE_U16: if (chain_size > U16_MAX + 1) break; return 0; case QED_CHAIN_CNT_TYPE_U32: if (chain_size > U32_MAX) break; return 0; default: return -EINVAL; } DP_NOTICE(cdev, "The actual chain size (0x%llx) is larger than the maximal possible value\n", chain_size); return -EINVAL; } static int qed_chain_alloc_next_ptr(struct qed_dev *cdev, struct qed_chain *chain) { struct device *dev = &cdev->pdev->dev; void *virt, *virt_prev = NULL; dma_addr_t phys; u32 i; for (i = 0; i < chain->page_cnt; i++) { virt = dma_alloc_coherent(dev, chain->page_size, &phys, GFP_KERNEL); if (!virt) return -ENOMEM; if (i == 0) { qed_chain_init_mem(chain, virt, phys); qed_chain_reset(chain); } else { qed_chain_init_next_ptr_elem(chain, virt_prev, virt, phys); } virt_prev = virt; } /* Last page's next element should point to the beginning of the * chain. */ qed_chain_init_next_ptr_elem(chain, virt_prev, chain->p_virt_addr, chain->p_phys_addr); return 0; } static int qed_chain_alloc_single(struct qed_dev *cdev, struct qed_chain *chain) { dma_addr_t phys; void *virt; virt = dma_alloc_coherent(&cdev->pdev->dev, chain->page_size, &phys, GFP_KERNEL); if (!virt) return -ENOMEM; qed_chain_init_mem(chain, virt, phys); qed_chain_reset(chain); return 0; } static int qed_chain_alloc_pbl(struct qed_dev *cdev, struct qed_chain *chain) { struct device *dev = &cdev->pdev->dev; struct addr_tbl_entry *addr_tbl; dma_addr_t phys, pbl_phys; __le64 *pbl_virt; u32 page_cnt, i; size_t size; void *virt; page_cnt = chain->page_cnt; size = array_size(page_cnt, sizeof(*addr_tbl)); if (unlikely(size == SIZE_MAX)) return -EOVERFLOW; addr_tbl = vzalloc(size); if (!addr_tbl) return -ENOMEM; chain->pbl.pp_addr_tbl = addr_tbl; if (chain->b_external_pbl) { pbl_virt = chain->pbl_sp.table_virt; goto alloc_pages; } size = array_size(page_cnt, sizeof(*pbl_virt)); if (unlikely(size == SIZE_MAX)) return -EOVERFLOW; pbl_virt = dma_alloc_coherent(dev, size, &pbl_phys, GFP_KERNEL); if (!pbl_virt) return -ENOMEM; chain->pbl_sp.table_virt = pbl_virt; chain->pbl_sp.table_phys = pbl_phys; chain->pbl_sp.table_size = size; alloc_pages: for (i = 0; i < page_cnt; i++) { virt = dma_alloc_coherent(dev, chain->page_size, &phys, GFP_KERNEL); if (!virt) return -ENOMEM; if (i == 0) { qed_chain_init_mem(chain, virt, phys); qed_chain_reset(chain); } /* Fill the PBL table with the physical address of the page */ pbl_virt[i] = cpu_to_le64(phys); /* Keep the virtual address of the page */ addr_tbl[i].virt_addr = virt; addr_tbl[i].dma_map = phys; } return 0; } /** * qed_chain_alloc() - Allocate and initialize a chain. * * @cdev: Main device structure. * @chain: Chain to be processed. * @params: Chain initialization parameters. * * Return: 0 on success, negative errno otherwise. */ int qed_chain_alloc(struct qed_dev *cdev, struct qed_chain *chain, struct qed_chain_init_params *params) { u32 page_cnt; int rc; if (!params->page_size) params->page_size = QED_CHAIN_PAGE_SIZE; if (params->mode == QED_CHAIN_MODE_SINGLE) page_cnt = 1; else page_cnt = QED_CHAIN_PAGE_CNT(params->num_elems, params->elem_size, params->page_size, params->mode); rc = qed_chain_alloc_sanity_check(cdev, params, page_cnt); if (rc) { DP_NOTICE(cdev, "Cannot allocate a chain with the given arguments:\n"); DP_NOTICE(cdev, "[use_mode %d, mode %d, cnt_type %d, num_elems %d, elem_size %zu, page_size %u]\n", params->intended_use, params->mode, params->cnt_type, params->num_elems, params->elem_size, params->page_size); return rc; } qed_chain_init(chain, params, page_cnt); switch (params->mode) { case QED_CHAIN_MODE_NEXT_PTR: rc = qed_chain_alloc_next_ptr(cdev, chain); break; case QED_CHAIN_MODE_SINGLE: rc = qed_chain_alloc_single(cdev, chain); break; case QED_CHAIN_MODE_PBL: rc = qed_chain_alloc_pbl(cdev, chain); break; default: return -EINVAL; } if (!rc) return 0; qed_chain_free(cdev, chain); return rc; }