123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * Copyright 2018-2020 Broadcom.
- */
- #include <linux/dma-mapping.h>
- #include <linux/mm.h>
- #include <linux/pagemap.h>
- #include <linux/pgtable.h>
- #include <linux/vmalloc.h>
- #include <asm/page.h>
- #include <asm/unaligned.h>
- #include <uapi/linux/misc/bcm_vk.h>
- #include "bcm_vk.h"
- #include "bcm_vk_msg.h"
- #include "bcm_vk_sg.h"
- /*
- * Valkyrie has a hardware limitation of 16M transfer size.
- * So limit the SGL chunks to 16M.
- */
- #define BCM_VK_MAX_SGL_CHUNK SZ_16M
- static int bcm_vk_dma_alloc(struct device *dev,
- struct bcm_vk_dma *dma,
- int dir,
- struct _vk_data *vkdata);
- static int bcm_vk_dma_free(struct device *dev, struct bcm_vk_dma *dma);
- /* Uncomment to dump SGLIST */
- /* #define BCM_VK_DUMP_SGLIST */
- static int bcm_vk_dma_alloc(struct device *dev,
- struct bcm_vk_dma *dma,
- int direction,
- struct _vk_data *vkdata)
- {
- dma_addr_t addr, sg_addr;
- int err;
- int i;
- int offset;
- u32 size;
- u32 remaining_size;
- u32 transfer_size;
- u64 data;
- unsigned long first, last;
- struct _vk_data *sgdata;
- /* Get 64-bit user address */
- data = get_unaligned(&vkdata->address);
- /* offset into first page */
- offset = offset_in_page(data);
- /* Calculate number of pages */
- first = (data & PAGE_MASK) >> PAGE_SHIFT;
- last = ((data + vkdata->size - 1) & PAGE_MASK) >> PAGE_SHIFT;
- dma->nr_pages = last - first + 1;
- /* Allocate DMA pages */
- dma->pages = kmalloc_array(dma->nr_pages,
- sizeof(struct page *),
- GFP_KERNEL);
- if (!dma->pages)
- return -ENOMEM;
- dev_dbg(dev, "Alloc DMA Pages [0x%llx+0x%x => %d pages]\n",
- data, vkdata->size, dma->nr_pages);
- dma->direction = direction;
- /* Get user pages into memory */
- err = get_user_pages_fast(data & PAGE_MASK,
- dma->nr_pages,
- direction == DMA_FROM_DEVICE,
- dma->pages);
- if (err != dma->nr_pages) {
- dma->nr_pages = (err >= 0) ? err : 0;
- dev_err(dev, "get_user_pages_fast, err=%d [%d]\n",
- err, dma->nr_pages);
- return err < 0 ? err : -EINVAL;
- }
- /* Max size of sg list is 1 per mapped page + fields at start */
- dma->sglen = (dma->nr_pages * sizeof(*sgdata)) +
- (sizeof(u32) * SGLIST_VKDATA_START);
- /* Allocate sglist */
- dma->sglist = dma_alloc_coherent(dev,
- dma->sglen,
- &dma->handle,
- GFP_KERNEL);
- if (!dma->sglist)
- return -ENOMEM;
- dma->sglist[SGLIST_NUM_SG] = 0;
- dma->sglist[SGLIST_TOTALSIZE] = vkdata->size;
- remaining_size = vkdata->size;
- sgdata = (struct _vk_data *)&dma->sglist[SGLIST_VKDATA_START];
- /* Map all pages into DMA */
- size = min_t(size_t, PAGE_SIZE - offset, remaining_size);
- remaining_size -= size;
- sg_addr = dma_map_page(dev,
- dma->pages[0],
- offset,
- size,
- dma->direction);
- transfer_size = size;
- if (unlikely(dma_mapping_error(dev, sg_addr))) {
- __free_page(dma->pages[0]);
- return -EIO;
- }
- for (i = 1; i < dma->nr_pages; i++) {
- size = min_t(size_t, PAGE_SIZE, remaining_size);
- remaining_size -= size;
- addr = dma_map_page(dev,
- dma->pages[i],
- 0,
- size,
- dma->direction);
- if (unlikely(dma_mapping_error(dev, addr))) {
- __free_page(dma->pages[i]);
- return -EIO;
- }
- /*
- * Compress SG list entry when pages are contiguous
- * and transfer size less or equal to BCM_VK_MAX_SGL_CHUNK
- */
- if ((addr == (sg_addr + transfer_size)) &&
- ((transfer_size + size) <= BCM_VK_MAX_SGL_CHUNK)) {
- /* pages are contiguous, add to same sg entry */
- transfer_size += size;
- } else {
- /* pages are not contiguous, write sg entry */
- sgdata->size = transfer_size;
- put_unaligned(sg_addr, (u64 *)&sgdata->address);
- dma->sglist[SGLIST_NUM_SG]++;
- /* start new sg entry */
- sgdata++;
- sg_addr = addr;
- transfer_size = size;
- }
- }
- /* Write last sg list entry */
- sgdata->size = transfer_size;
- put_unaligned(sg_addr, (u64 *)&sgdata->address);
- dma->sglist[SGLIST_NUM_SG]++;
- /* Update pointers and size field to point to sglist */
- put_unaligned((u64)dma->handle, &vkdata->address);
- vkdata->size = (dma->sglist[SGLIST_NUM_SG] * sizeof(*sgdata)) +
- (sizeof(u32) * SGLIST_VKDATA_START);
- #ifdef BCM_VK_DUMP_SGLIST
- dev_dbg(dev,
- "sgl 0x%llx handle 0x%llx, sglen: 0x%x sgsize: 0x%x\n",
- (u64)dma->sglist,
- dma->handle,
- dma->sglen,
- vkdata->size);
- for (i = 0; i < vkdata->size / sizeof(u32); i++)
- dev_dbg(dev, "i:0x%x 0x%x\n", i, dma->sglist[i]);
- #endif
- return 0;
- }
- int bcm_vk_sg_alloc(struct device *dev,
- struct bcm_vk_dma *dma,
- int dir,
- struct _vk_data *vkdata,
- int num)
- {
- int i;
- int rc = -EINVAL;
- /* Convert user addresses to DMA SG List */
- for (i = 0; i < num; i++) {
- if (vkdata[i].size && vkdata[i].address) {
- /*
- * If both size and address are non-zero
- * then DMA alloc.
- */
- rc = bcm_vk_dma_alloc(dev,
- &dma[i],
- dir,
- &vkdata[i]);
- } else if (vkdata[i].size ||
- vkdata[i].address) {
- /*
- * If one of size and address are zero
- * there is a problem.
- */
- dev_err(dev,
- "Invalid vkdata %x 0x%x 0x%llx\n",
- i, vkdata[i].size, vkdata[i].address);
- rc = -EINVAL;
- } else {
- /*
- * If size and address are both zero
- * don't convert, but return success.
- */
- rc = 0;
- }
- if (rc)
- goto fail_alloc;
- }
- return rc;
- fail_alloc:
- while (i > 0) {
- i--;
- if (dma[i].sglist)
- bcm_vk_dma_free(dev, &dma[i]);
- }
- return rc;
- }
- static int bcm_vk_dma_free(struct device *dev, struct bcm_vk_dma *dma)
- {
- dma_addr_t addr;
- int i;
- int num_sg;
- u32 size;
- struct _vk_data *vkdata;
- dev_dbg(dev, "free sglist=%p sglen=0x%x\n", dma->sglist, dma->sglen);
- /* Unmap all pages in the sglist */
- num_sg = dma->sglist[SGLIST_NUM_SG];
- vkdata = (struct _vk_data *)&dma->sglist[SGLIST_VKDATA_START];
- for (i = 0; i < num_sg; i++) {
- size = vkdata[i].size;
- addr = get_unaligned(&vkdata[i].address);
- dma_unmap_page(dev, addr, size, dma->direction);
- }
- /* Free allocated sglist */
- dma_free_coherent(dev, dma->sglen, dma->sglist, dma->handle);
- /* Release lock on all pages */
- for (i = 0; i < dma->nr_pages; i++)
- put_page(dma->pages[i]);
- /* Free allocated dma pages */
- kfree(dma->pages);
- dma->sglist = NULL;
- return 0;
- }
- int bcm_vk_sg_free(struct device *dev, struct bcm_vk_dma *dma, int num,
- int *proc_cnt)
- {
- int i;
- *proc_cnt = 0;
- /* Unmap and free all pages and sglists */
- for (i = 0; i < num; i++) {
- if (dma[i].sglist) {
- bcm_vk_dma_free(dev, &dma[i]);
- *proc_cnt += 1;
- }
- }
- return 0;
- }
|