/alps/pcitool

To get this branch, use:
bzr branch http://darksoft.org/webbzr/alps/pcitool

« back to all changes in this revision

Viewing changes to driver/umem.c

  • Committer: Suren A. Chilingaryan
  • Date: 2011-02-13 02:07:11 UTC
  • Revision ID: csa@dside.dyndns.org-20110213020711-y9bjh3n4ke6p4t4n
Initial import

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/**
 
2
 *
 
3
 * @file umem.c
 
4
 * @brief This file contains the functions handling user space memory.
 
5
 * @author Guillermo Marcus
 
6
 * @date 2009-04-05
 
7
 *
 
8
 */
 
9
#include <linux/version.h>
 
10
#include <linux/string.h>
 
11
#include <linux/types.h>
 
12
#include <linux/list.h>
 
13
#include <linux/interrupt.h>
 
14
#include <linux/pci.h>
 
15
#include <linux/cdev.h>
 
16
#include <linux/wait.h>
 
17
#include <linux/mm.h>
 
18
#include <linux/pagemap.h>
 
19
#include <linux/sched.h>
 
20
 
 
21
#include "config.h"                     /* compile-time configuration */
 
22
#include "compat.h"                     /* compatibility definitions for older linux */
 
23
#include "pciDriver.h"                  /* external interface for the driver */
 
24
#include "common.h"             /* internal definitions for all parts */
 
25
#include "umem.h"               /* prototypes for kernel memory */
 
26
#include "sysfs.h"              /* prototypes for sysfs */
 
27
 
 
28
/**
 
29
 *
 
30
 * Reserve a new scatter/gather list and map it from memory to PCI bus addresses.
 
31
 *
 
32
 */
 
33
int pcidriver_umem_sgmap(pcidriver_privdata_t *privdata, umem_handle_t *umem_handle)
 
34
{
 
35
        int i, res, nr_pages;
 
36
        struct page **pages;
 
37
        struct scatterlist *sg = NULL;
 
38
        pcidriver_umem_entry_t *umem_entry;
 
39
        unsigned int nents;
 
40
        unsigned long count,offset,length;
 
41
 
 
42
        /*
 
43
         * We do some checks first. Then, the following is necessary to create a
 
44
         * Scatter/Gather list from a user memory area:
 
45
         *  - Determine the number of pages
 
46
         *  - Get the pages for the memory area
 
47
         *      - Lock them.
 
48
         *  - Create a scatter/gather list of the pages
 
49
         *  - Map the list from memory to PCI bus addresses
 
50
         *
 
51
         * Then, we:
 
52
         *  - Create an entry on the umem list of the device, to cache the mapping.
 
53
         *  - Create a sysfs attribute that gives easy access to the SG list
 
54
         */
 
55
 
 
56
        /* zero-size?? */
 
57
        if (umem_handle->size == 0)
 
58
                return -EINVAL;
 
59
 
 
60
        /* Direction is better ignoring during mapping. */
 
61
        /* We assume bidirectional buffers always, except when sync'ing */
 
62
 
 
63
        /* calculate the number of pages */
 
64
        nr_pages = ((umem_handle->vma & ~PAGE_MASK) + umem_handle->size + ~PAGE_MASK) >> PAGE_SHIFT;
 
65
 
 
66
        mod_info_dbg("nr_pages computed: %u\n", nr_pages);
 
67
 
 
68
        /* Allocate space for the page information */
 
69
        /* This can be very big, so we use vmalloc */
 
70
        if ((pages = vmalloc(nr_pages * sizeof(*pages))) == NULL)
 
71
                return -ENOMEM;
 
72
 
 
73
        mod_info_dbg("allocated space for the pages.\n");
 
74
 
 
75
        /* Allocate space for the scatterlist */
 
76
        /* We do not know how many entries will be, but the maximum is nr_pages. */
 
77
        /* This can be very big, so we use vmalloc */
 
78
        if ((sg = vmalloc(nr_pages * sizeof(*sg))) == NULL)
 
79
                goto umem_sgmap_pages;
 
80
 
 
81
        sg_init_table(sg, nr_pages);
 
82
 
 
83
        mod_info_dbg("allocated space for the SG list.\n");
 
84
 
 
85
        /* Get the page information */
 
86
        down_read(&current->mm->mmap_sem);
 
87
        res = get_user_pages(
 
88
                                current,
 
89
                                current->mm,
 
90
                                umem_handle->vma,
 
91
                                nr_pages,
 
92
                                1,
 
93
                                0,  /* do not force, FIXME: shall I? */
 
94
                                pages,
 
95
                                NULL );
 
96
        up_read(&current->mm->mmap_sem);
 
97
 
 
98
        /* Error, not all pages mapped */
 
99
        if (res < (int)nr_pages) {
 
100
                mod_info("Could not map all user pages (%d of %d)\n", res, nr_pages);
 
101
                /* If only some pages could be mapped, we release those. If a real
 
102
                 * error occured, we set nr_pages to 0 */
 
103
                nr_pages = (res > 0 ? res : 0);
 
104
                goto umem_sgmap_unmap;
 
105
        }
 
106
 
 
107
        mod_info_dbg("Got the pages (%d).\n", res);
 
108
 
 
109
        /* Lock the pages, then populate the SG list with the pages */
 
110
        /* page0 is different */
 
111
        if ( !PageReserved(pages[0]) )
 
112
                compat_lock_page(pages[0]);
 
113
 
 
114
        offset = (umem_handle->vma & ~PAGE_MASK);
 
115
        length = (umem_handle->size > (PAGE_SIZE-offset) ? (PAGE_SIZE-offset) : umem_handle->size);
 
116
 
 
117
        sg_set_page(&sg[0], pages[0], length, offset);
 
118
 
 
119
        count = umem_handle->size - length;
 
120
        for(i=1;i<nr_pages;i++) {
 
121
                /* Lock page first */
 
122
                if ( !PageReserved(pages[i]) )
 
123
                        compat_lock_page(pages[i]);
 
124
 
 
125
                /* Populate the list */
 
126
                sg_set_page(&sg[i], pages[i], ((count > PAGE_SIZE) ? PAGE_SIZE : count), 0);
 
127
                count -= sg[i].length;
 
128
        }
 
129
 
 
130
        /* Use the page list to populate the SG list */
 
131
        /* SG entries may be merged, res is the number of used entries */
 
132
        /* We have originally nr_pages entries in the sg list */
 
133
        if ((nents = pci_map_sg(privdata->pdev, sg, nr_pages, PCI_DMA_BIDIRECTIONAL)) == 0)
 
134
                goto umem_sgmap_unmap;
 
135
 
 
136
        mod_info_dbg("Mapped SG list (%d entries).\n", nents);
 
137
 
 
138
        /* Add an entry to the umem_list of the device, and update the handle with the id */
 
139
        /* Allocate space for the new umem entry */
 
140
        if ((umem_entry = kmalloc(sizeof(*umem_entry), GFP_KERNEL)) == NULL)
 
141
                goto umem_sgmap_entry;
 
142
 
 
143
        /* Fill entry to be added to the umem list */
 
144
        umem_entry->id = atomic_inc_return(&privdata->umem_count) - 1;
 
145
        umem_entry->nr_pages = nr_pages;        /* Will be needed when unmapping */
 
146
        umem_entry->pages = pages;
 
147
        umem_entry->nents = nents;
 
148
        umem_entry->sg = sg;
 
149
 
 
150
        if (pcidriver_sysfs_initialize_umem(privdata, umem_entry->id, &(umem_entry->sysfs_attr)) != 0)
 
151
                goto umem_sgmap_name_fail;
 
152
 
 
153
        /* Add entry to the umem list */
 
154
        spin_lock( &(privdata->umemlist_lock) );
 
155
        list_add_tail( &(umem_entry->list), &(privdata->umem_list) );
 
156
        spin_unlock( &(privdata->umemlist_lock) );
 
157
 
 
158
        /* Update the Handle with the Handle ID of the entry */
 
159
        umem_handle->handle_id = umem_entry->id;
 
160
 
 
161
        return 0;
 
162
 
 
163
umem_sgmap_name_fail:
 
164
        kfree(umem_entry);
 
165
umem_sgmap_entry:
 
166
        pci_unmap_sg( privdata->pdev, sg, nr_pages, PCI_DMA_BIDIRECTIONAL );
 
167
umem_sgmap_unmap:
 
168
        /* release pages */
 
169
        if (nr_pages > 0) {
 
170
                for(i=0;i<nr_pages;i++) {
 
171
                        if (PageLocked(pages[i]))
 
172
                                compat_unlock_page(pages[i]);
 
173
                        if (!PageReserved(pages[i]))
 
174
                                set_page_dirty(pages[i]);
 
175
                        page_cache_release(pages[i]);
 
176
                }
 
177
        }
 
178
        vfree(sg);
 
179
umem_sgmap_pages:
 
180
        vfree(pages);
 
181
        return -ENOMEM;
 
182
 
 
183
}
 
184
 
 
185
/**
 
186
 *
 
187
 * Unmap a scatter/gather list
 
188
 *
 
189
 */
 
190
int pcidriver_umem_sgunmap(pcidriver_privdata_t *privdata, pcidriver_umem_entry_t *umem_entry)
 
191
{
 
192
        int i;
 
193
        pcidriver_sysfs_remove(privdata, &(umem_entry->sysfs_attr));
 
194
 
 
195
        /* Unmap user memory */
 
196
        pci_unmap_sg( privdata->pdev, umem_entry->sg, umem_entry->nr_pages, PCI_DMA_BIDIRECTIONAL );
 
197
 
 
198
        /* Release the pages */
 
199
        if (umem_entry->nr_pages > 0) {
 
200
                for(i=0;i<(umem_entry->nr_pages);i++) {
 
201
                        /* Mark pages as Dirty and unlock it */
 
202
                        if ( !PageReserved( umem_entry->pages[i] )) {
 
203
                                SetPageDirty( umem_entry->pages[i] );
 
204
                                compat_unlock_page(umem_entry->pages[i]);
 
205
                        }
 
206
                        /* and release it from the cache */
 
207
                        page_cache_release( umem_entry->pages[i] );
 
208
                }
 
209
        }
 
210
 
 
211
        /* Remove the umem list entry */
 
212
        spin_lock( &(privdata->umemlist_lock) );
 
213
        list_del( &(umem_entry->list) );
 
214
        spin_unlock( &(privdata->umemlist_lock) );
 
215
 
 
216
        /* Release SG list and page list memory */
 
217
        /* These two are in the vm area of the kernel */
 
218
        vfree(umem_entry->pages);
 
219
        vfree(umem_entry->sg);
 
220
 
 
221
        /* Release umem_entry memory */
 
222
        kfree(umem_entry);
 
223
 
 
224
        return 0;
 
225
}
 
226
 
 
227
/**
 
228
 *
 
229
 * Unmap all scatter/gather lists.
 
230
 *
 
231
 */
 
232
int pcidriver_umem_sgunmap_all(pcidriver_privdata_t *privdata)
 
233
{
 
234
        struct list_head *ptr, *next;
 
235
        pcidriver_umem_entry_t *umem_entry;
 
236
 
 
237
        /* iterate safely over the entries and delete them */
 
238
        list_for_each_safe( ptr, next, &(privdata->umem_list) ) {
 
239
                umem_entry = list_entry(ptr, pcidriver_umem_entry_t, list );
 
240
                pcidriver_umem_sgunmap( privdata, umem_entry );                 /* spin lock inside! */
 
241
        }
 
242
 
 
243
        return 0;
 
244
}
 
245
 
 
246
/**
 
247
 *
 
248
 * Copies the scatter/gather list from kernelspace to userspace.
 
249
 *
 
250
 */
 
251
int pcidriver_umem_sgget(pcidriver_privdata_t *privdata, umem_sglist_t *umem_sglist)
 
252
{
 
253
        int i;
 
254
        pcidriver_umem_entry_t *umem_entry;
 
255
        struct scatterlist *sg;
 
256
        int idx = 0;
 
257
        dma_addr_t cur_addr;
 
258
        unsigned int cur_size;
 
259
 
 
260
        /* Find the associated umem_entry for this buffer */
 
261
        umem_entry = pcidriver_umem_find_entry_id( privdata, umem_sglist->handle_id );
 
262
        if (umem_entry == NULL)
 
263
                return -EINVAL;                                 /* umem_handle is not valid */
 
264
 
 
265
        /* Check if passed SG list is enough */
 
266
        if (umem_sglist->nents < umem_entry->nents)
 
267
                return -EINVAL;                                 /* sg has not enough entries */
 
268
 
 
269
        /* Copy the SG list to the user format */
 
270
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24)
 
271
        if (umem_sglist->type == PCIDRIVER_SG_MERGED) {
 
272
                for_each_sg(umem_entry->sg, sg, umem_entry->nents, i ) {
 
273
                        if (i==0) {
 
274
                                umem_sglist->sg[0].addr = sg_dma_address( sg );
 
275
                                umem_sglist->sg[0].size = sg_dma_len( sg );
 
276
                                idx = 0;
 
277
                        }
 
278
                        else {
 
279
                                cur_addr = sg_dma_address( sg );
 
280
                                cur_size = sg_dma_len( sg );
 
281
 
 
282
                                /* Check if entry fits after current entry */
 
283
                                if (cur_addr == (umem_sglist->sg[idx].addr + umem_sglist->sg[idx].size)) {
 
284
                                        umem_sglist->sg[idx].size += cur_size;
 
285
                                        continue;
 
286
                                }
 
287
 
 
288
                                /* Skip if the entry is zero-length (yes, it can happen.... at the end of the list) */
 
289
                                if (cur_size == 0)
 
290
                                        continue;
 
291
 
 
292
                                /* None of the above, add new entry */
 
293
                                idx++;
 
294
                                umem_sglist->sg[idx].addr = cur_addr;
 
295
                                umem_sglist->sg[idx].size = cur_size;
 
296
                        }
 
297
                }
 
298
                /* Set the used size of the SG list */
 
299
                umem_sglist->nents = idx+1;
 
300
        } else {
 
301
                for_each_sg(umem_entry->sg, sg, umem_entry->nents, i ) {
 
302
                        mod_info("entry: %d\n",i);
 
303
                        umem_sglist->sg[i].addr = sg_dma_address( sg );
 
304
                        umem_sglist->sg[i].size = sg_dma_len( sg );
 
305
                }
 
306
 
 
307
                /* Set the used size of the SG list */
 
308
                /* Check if the last one is zero-length */
 
309
                if ( umem_sglist->sg[ umem_entry->nents - 1].size == 0)
 
310
                        umem_sglist->nents = umem_entry->nents -1;
 
311
                else
 
312
                        umem_sglist->nents = umem_entry->nents;
 
313
        }
 
314
#else
 
315
        if (umem_sglist->type == PCIDRIVER_SG_MERGED) {
 
316
                /* Merge entries that are contiguous into a single entry */
 
317
                /* Non-optimal but fast for most cases */
 
318
                /* First one always true */
 
319
                sg=umem_entry->sg;
 
320
                umem_sglist->sg[0].addr = sg_dma_address( sg );
 
321
                umem_sglist->sg[0].size = sg_dma_len( sg );
 
322
                sg++;
 
323
                idx = 0;
 
324
 
 
325
                /* Iterate over the SG entries */
 
326
                for(i=1; i< umem_entry->nents; i++, sg++ ) {
 
327
                        cur_addr = sg_dma_address( sg );
 
328
                        cur_size = sg_dma_len( sg );
 
329
 
 
330
                        /* Check if entry fits after current entry */
 
331
                        if (cur_addr == (umem_sglist->sg[idx].addr + umem_sglist->sg[idx].size)) {
 
332
                                umem_sglist->sg[idx].size += cur_size;
 
333
                                continue;
 
334
                        }
 
335
 
 
336
                        /* Skip if the entry is zero-length (yes, it can happen.... at the end of the list) */
 
337
                        if (cur_size == 0)
 
338
                                continue;
 
339
 
 
340
                        /* None of the above, add new entry */
 
341
                        idx++;
 
342
                        umem_sglist->sg[idx].addr = cur_addr;
 
343
                        umem_sglist->sg[idx].size = cur_size;
 
344
                }
 
345
                /* Set the used size of the SG list */
 
346
                umem_sglist->nents = idx+1;
 
347
        } else {
 
348
                /* Assume pci_map_sg made a good job (ehem..) and just copy it.
 
349
                 * actually, now I assume it just gives them plainly to me. */
 
350
                for(i=0, sg=umem_entry->sg ; i< umem_entry->nents; i++, sg++ ) {
 
351
                        umem_sglist->sg[i].addr = sg_dma_address( sg );
 
352
                        umem_sglist->sg[i].size = sg_dma_len( sg );
 
353
                }
 
354
                /* Set the used size of the SG list */
 
355
                /* Check if the last one is zero-length */
 
356
                if ( umem_sglist->sg[ umem_entry->nents - 1].size == 0)
 
357
                        umem_sglist->nents = umem_entry->nents -1;
 
358
                else
 
359
                        umem_sglist->nents = umem_entry->nents;
 
360
        }
 
361
#endif
 
362
 
 
363
        return 0;
 
364
}
 
365
 
 
366
/**
 
367
 *
 
368
 * Sync user space memory from/to device
 
369
 *
 
370
 */
 
371
int pcidriver_umem_sync( pcidriver_privdata_t *privdata, umem_handle_t *umem_handle )
 
372
{
 
373
        pcidriver_umem_entry_t *umem_entry;
 
374
 
 
375
        /* Find the associated umem_entry for this buffer */
 
376
        umem_entry = pcidriver_umem_find_entry_id( privdata, umem_handle->handle_id );
 
377
        if (umem_entry == NULL)
 
378
                return -EINVAL;                                 /* umem_handle is not valid */
 
379
 
 
380
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,11)
 
381
        switch (umem_handle->dir) {
 
382
                case PCIDRIVER_DMA_TODEVICE:
 
383
                        pci_dma_sync_sg_for_device( privdata->pdev, umem_entry->sg, umem_entry->nents, PCI_DMA_TODEVICE );
 
384
                        break;
 
385
                case PCIDRIVER_DMA_FROMDEVICE:
 
386
                        pci_dma_sync_sg_for_cpu( privdata->pdev, umem_entry->sg, umem_entry->nents, PCI_DMA_FROMDEVICE );
 
387
                        break;
 
388
                case PCIDRIVER_DMA_BIDIRECTIONAL:
 
389
                        pci_dma_sync_sg_for_device( privdata->pdev, umem_entry->sg, umem_entry->nents, PCI_DMA_BIDIRECTIONAL );
 
390
                        pci_dma_sync_sg_for_cpu( privdata->pdev, umem_entry->sg, umem_entry->nents, PCI_DMA_BIDIRECTIONAL );
 
391
                        break;
 
392
                default:
 
393
                        return -EINVAL;                         /* wrong direction parameter */
 
394
        }
 
395
#else
 
396
        switch (umem_handle->dir) {
 
397
                case PCIDRIVER_DMA_TODEVICE:
 
398
                        pci_dma_sync_sg( privdata->pdev, umem_entry->sg, umem_entry->nents, PCI_DMA_TODEVICE );
 
399
                        break;
 
400
                case PCIDRIVER_DMA_FROMDEVICE:
 
401
                        pci_dma_sync_sg( privdata->pdev, umem_entry->sg, umem_entry->nents, PCI_DMA_FROMDEVICE );
 
402
                        break;
 
403
                case PCIDRIVER_DMA_BIDIRECTIONAL:
 
404
                        pci_dma_sync_sg( privdata->pdev, umem_entry->sg, umem_entry->nents, PCI_DMA_BIDIRECTIONAL );
 
405
                        break;
 
406
                default:
 
407
                        return -EINVAL;                         /* wrong direction parameter */
 
408
        }
 
409
#endif
 
410
 
 
411
        return 0;
 
412
}
 
413
 
 
414
/*
 
415
 *
 
416
 * Get the pcidriver_umem_entry_t structure for the given id.
 
417
 *
 
418
 * @param id ID of the umem entry to search for
 
419
 *
 
420
 */
 
421
pcidriver_umem_entry_t *pcidriver_umem_find_entry_id(pcidriver_privdata_t *privdata, int id)
 
422
{
 
423
        struct list_head *ptr;
 
424
        pcidriver_umem_entry_t *entry;
 
425
 
 
426
        spin_lock(&(privdata->umemlist_lock));
 
427
        list_for_each(ptr, &(privdata->umem_list)) {
 
428
                entry = list_entry(ptr, pcidriver_umem_entry_t, list );
 
429
 
 
430
                if (entry->id == id) {
 
431
                        spin_unlock( &(privdata->umemlist_lock) );
 
432
                        return entry;
 
433
                }
 
434
        }
 
435
 
 
436
        spin_unlock(&(privdata->umemlist_lock));
 
437
        return NULL;
 
438
}