| // SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) | 
| /* Copyright (c) 2020 Marvell International Ltd. */ | 
|   | 
| #include <linux/dma-mapping.h> | 
| #include <linux/qed/qed_chain.h> | 
| #include <linux/vmalloc.h> | 
|   | 
| #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; | 
| } |