Merge branch 'work.ecryptfs' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs
[linux-2.6-microblaze.git] / drivers / misc / bcm-vk / bcm_vk_sg.c
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Copyright 2018-2020 Broadcom.
4  */
5 #include <linux/dma-mapping.h>
6 #include <linux/mm.h>
7 #include <linux/pagemap.h>
8 #include <linux/pgtable.h>
9 #include <linux/vmalloc.h>
10
11 #include <asm/page.h>
12 #include <asm/unaligned.h>
13
14 #include <uapi/linux/misc/bcm_vk.h>
15
16 #include "bcm_vk.h"
17 #include "bcm_vk_msg.h"
18 #include "bcm_vk_sg.h"
19
20 /*
21  * Valkyrie has a hardware limitation of 16M transfer size.
22  * So limit the SGL chunks to 16M.
23  */
24 #define BCM_VK_MAX_SGL_CHUNK SZ_16M
25
26 static int bcm_vk_dma_alloc(struct device *dev,
27                             struct bcm_vk_dma *dma,
28                             int dir,
29                             struct _vk_data *vkdata);
30 static int bcm_vk_dma_free(struct device *dev, struct bcm_vk_dma *dma);
31
32 /* Uncomment to dump SGLIST */
33 /* #define BCM_VK_DUMP_SGLIST */
34
35 static int bcm_vk_dma_alloc(struct device *dev,
36                             struct bcm_vk_dma *dma,
37                             int direction,
38                             struct _vk_data *vkdata)
39 {
40         dma_addr_t addr, sg_addr;
41         int err;
42         int i;
43         int offset;
44         u32 size;
45         u32 remaining_size;
46         u32 transfer_size;
47         u64 data;
48         unsigned long first, last;
49         struct _vk_data *sgdata;
50
51         /* Get 64-bit user address */
52         data = get_unaligned(&vkdata->address);
53
54         /* offset into first page */
55         offset = offset_in_page(data);
56
57         /* Calculate number of pages */
58         first = (data & PAGE_MASK) >> PAGE_SHIFT;
59         last  = ((data + vkdata->size - 1) & PAGE_MASK) >> PAGE_SHIFT;
60         dma->nr_pages = last - first + 1;
61
62         /* Allocate DMA pages */
63         dma->pages = kmalloc_array(dma->nr_pages,
64                                    sizeof(struct page *),
65                                    GFP_KERNEL);
66         if (!dma->pages)
67                 return -ENOMEM;
68
69         dev_dbg(dev, "Alloc DMA Pages [0x%llx+0x%x => %d pages]\n",
70                 data, vkdata->size, dma->nr_pages);
71
72         dma->direction = direction;
73
74         /* Get user pages into memory */
75         err = get_user_pages_fast(data & PAGE_MASK,
76                                   dma->nr_pages,
77                                   direction == DMA_FROM_DEVICE,
78                                   dma->pages);
79         if (err != dma->nr_pages) {
80                 dma->nr_pages = (err >= 0) ? err : 0;
81                 dev_err(dev, "get_user_pages_fast, err=%d [%d]\n",
82                         err, dma->nr_pages);
83                 return err < 0 ? err : -EINVAL;
84         }
85
86         /* Max size of sg list is 1 per mapped page + fields at start */
87         dma->sglen = (dma->nr_pages * sizeof(*sgdata)) +
88                      (sizeof(u32) * SGLIST_VKDATA_START);
89
90         /* Allocate sglist */
91         dma->sglist = dma_alloc_coherent(dev,
92                                          dma->sglen,
93                                          &dma->handle,
94                                          GFP_KERNEL);
95         if (!dma->sglist)
96                 return -ENOMEM;
97
98         dma->sglist[SGLIST_NUM_SG] = 0;
99         dma->sglist[SGLIST_TOTALSIZE] = vkdata->size;
100         remaining_size = vkdata->size;
101         sgdata = (struct _vk_data *)&dma->sglist[SGLIST_VKDATA_START];
102
103         /* Map all pages into DMA */
104         size = min_t(size_t, PAGE_SIZE - offset, remaining_size);
105         remaining_size -= size;
106         sg_addr = dma_map_page(dev,
107                                dma->pages[0],
108                                offset,
109                                size,
110                                dma->direction);
111         transfer_size = size;
112         if (unlikely(dma_mapping_error(dev, sg_addr))) {
113                 __free_page(dma->pages[0]);
114                 return -EIO;
115         }
116
117         for (i = 1; i < dma->nr_pages; i++) {
118                 size = min_t(size_t, PAGE_SIZE, remaining_size);
119                 remaining_size -= size;
120                 addr = dma_map_page(dev,
121                                     dma->pages[i],
122                                     0,
123                                     size,
124                                     dma->direction);
125                 if (unlikely(dma_mapping_error(dev, addr))) {
126                         __free_page(dma->pages[i]);
127                         return -EIO;
128                 }
129
130                 /*
131                  * Compress SG list entry when pages are contiguous
132                  * and transfer size less or equal to BCM_VK_MAX_SGL_CHUNK
133                  */
134                 if ((addr == (sg_addr + transfer_size)) &&
135                     ((transfer_size + size) <= BCM_VK_MAX_SGL_CHUNK)) {
136                         /* pages are contiguous, add to same sg entry */
137                         transfer_size += size;
138                 } else {
139                         /* pages are not contiguous, write sg entry */
140                         sgdata->size = transfer_size;
141                         put_unaligned(sg_addr, (u64 *)&sgdata->address);
142                         dma->sglist[SGLIST_NUM_SG]++;
143
144                         /* start new sg entry */
145                         sgdata++;
146                         sg_addr = addr;
147                         transfer_size = size;
148                 }
149         }
150         /* Write last sg list entry */
151         sgdata->size = transfer_size;
152         put_unaligned(sg_addr, (u64 *)&sgdata->address);
153         dma->sglist[SGLIST_NUM_SG]++;
154
155         /* Update pointers and size field to point to sglist */
156         put_unaligned((u64)dma->handle, &vkdata->address);
157         vkdata->size = (dma->sglist[SGLIST_NUM_SG] * sizeof(*sgdata)) +
158                        (sizeof(u32) * SGLIST_VKDATA_START);
159
160 #ifdef BCM_VK_DUMP_SGLIST
161         dev_dbg(dev,
162                 "sgl 0x%llx handle 0x%llx, sglen: 0x%x sgsize: 0x%x\n",
163                 (u64)dma->sglist,
164                 dma->handle,
165                 dma->sglen,
166                 vkdata->size);
167         for (i = 0; i < vkdata->size / sizeof(u32); i++)
168                 dev_dbg(dev, "i:0x%x 0x%x\n", i, dma->sglist[i]);
169 #endif
170
171         return 0;
172 }
173
174 int bcm_vk_sg_alloc(struct device *dev,
175                     struct bcm_vk_dma *dma,
176                     int dir,
177                     struct _vk_data *vkdata,
178                     int num)
179 {
180         int i;
181         int rc = -EINVAL;
182
183         /* Convert user addresses to DMA SG List */
184         for (i = 0; i < num; i++) {
185                 if (vkdata[i].size && vkdata[i].address) {
186                         /*
187                          * If both size and address are non-zero
188                          * then DMA alloc.
189                          */
190                         rc = bcm_vk_dma_alloc(dev,
191                                               &dma[i],
192                                               dir,
193                                               &vkdata[i]);
194                 } else if (vkdata[i].size ||
195                            vkdata[i].address) {
196                         /*
197                          * If one of size and address are zero
198                          * there is a problem.
199                          */
200                         dev_err(dev,
201                                 "Invalid vkdata %x 0x%x 0x%llx\n",
202                                 i, vkdata[i].size, vkdata[i].address);
203                         rc = -EINVAL;
204                 } else {
205                         /*
206                          * If size and address are both zero
207                          * don't convert, but return success.
208                          */
209                         rc = 0;
210                 }
211
212                 if (rc)
213                         goto fail_alloc;
214         }
215         return rc;
216
217 fail_alloc:
218         while (i > 0) {
219                 i--;
220                 if (dma[i].sglist)
221                         bcm_vk_dma_free(dev, &dma[i]);
222         }
223         return rc;
224 }
225
226 static int bcm_vk_dma_free(struct device *dev, struct bcm_vk_dma *dma)
227 {
228         dma_addr_t addr;
229         int i;
230         int num_sg;
231         u32 size;
232         struct _vk_data *vkdata;
233
234         dev_dbg(dev, "free sglist=%p sglen=0x%x\n", dma->sglist, dma->sglen);
235
236         /* Unmap all pages in the sglist */
237         num_sg = dma->sglist[SGLIST_NUM_SG];
238         vkdata = (struct _vk_data *)&dma->sglist[SGLIST_VKDATA_START];
239         for (i = 0; i < num_sg; i++) {
240                 size = vkdata[i].size;
241                 addr = get_unaligned(&vkdata[i].address);
242
243                 dma_unmap_page(dev, addr, size, dma->direction);
244         }
245
246         /* Free allocated sglist */
247         dma_free_coherent(dev, dma->sglen, dma->sglist, dma->handle);
248
249         /* Release lock on all pages */
250         for (i = 0; i < dma->nr_pages; i++)
251                 put_page(dma->pages[i]);
252
253         /* Free allocated dma pages */
254         kfree(dma->pages);
255         dma->sglist = NULL;
256
257         return 0;
258 }
259
260 int bcm_vk_sg_free(struct device *dev, struct bcm_vk_dma *dma, int num,
261                    int *proc_cnt)
262 {
263         int i;
264
265         *proc_cnt = 0;
266         /* Unmap and free all pages and sglists */
267         for (i = 0; i < num; i++) {
268                 if (dma[i].sglist) {
269                         bcm_vk_dma_free(dev, &dma[i]);
270                         *proc_cnt += 1;
271                 }
272         }
273
274         return 0;
275 }