/* Proposal for User space <-> scatterlist mapping * by Ingo Oeser * and Kai Makisara */ #define SGMAP_MAX_UDMA_PAGES (1 << (19 - PAGE_SHIFT)) #define SGMAP_MAX_UDMA_PAGES_INLINE 16 /* An experiment ... */ /* Pin down user pages and put them into a scatter gather list */ int sg_map_user_pages(struct scatterlist *sgl, const unsigned int max_pages, unsigned long uaddr, size_t count, int rw) { int res, i; unsigned int nr_pages = ((uaddr & ~PAGE_MASK) + count - 1 + ~PAGE_MASK) >> PAGE_SHIFT; struct page *inline_pages[SGMAP_MAX_UDMA_PAGES_INLINE]; struct page **pages = inline_pages; /* User attempted Overflow! * NOTE: This kind of request must be split by the caller. */ if ((uaddr + count) < uaddr) return -EINVAL; /* To big for provided scatterlist array */ if (nr_pages > max_pages) return -ENOMEM; /* Hmm? */ if (count == 0) return 0; if (unlikely(nr_pages > SGMAP_MAX_UDMA_PAGES_INLINE)) { pages = kmalloc(nr_pages * sizeof(pages[0]), GFP_USER); if (!pages) return -ENOMEM; } down_read(¤t->mm->mmap_sem); res = get_user_pages( current, current->mm, uaddr, nr_pages, rw == READ, /* logic is perversed^Wreversed here :-( */ 0, /* don't force */ &pages[0], NULL); up_read(¤t->mm->mmap_sem); /* Errors and no page mapped should return here */ if (res <= 0) goto out_free; memset(sgl, 0, sizeof(*sgl) * nr_pages); sgl[0].page = pages[0]; sgl[0].offset = uaddr & ~PAGE_MASK; /* FIXME: flush superflous for rw==READ, * probably wrong function for rw==WRITE */ flush_dcache_page(pages[0]); /* Page crossing transfers need these adjustments */ if (res > 1) { for (i = 1; i < res; i++) { sgl[i].offset = 0; sgl[i].page = pages[i]; sgl[i].length = PAGE_SIZE; flush_dcache_page(pages[i]); } sgl[0].length = PAGE_SIZE - sgl[0].offset; count -= sgl[0].length; count -= (res - 2) * PAGE_SIZE; } sgl[res - 1].length = count; out_free: if (pages != inline_pages) kfree(pages); return res; } /* And unmap them... */ int sg_unmap_user_pages(struct scatterlist *sgl, const unsigned int nr_pages) { int i; for (i = 0; i < nr_pages; i++) page_cache_release(sgl[i].page); return 0; }