This patch, against 2.4.0-test10-pre6, updates bttv to use the new and
groovy method of supporting mmap.
Advantages:
* Code more simple.
* mmap now only limited to number of free pages in the system.
* mmap no longer requires vmalloc, or remap_page_range.
Disadvantages:
* Requires videodev API update (new member: mmap_vma).
* KNOWN BUG: If your gbufsize causes a frame to cross a page boundary,
you lose. The code doesn't support this, and doesn't check for it
either. Boom. (this is fixable though)
* Totally untested. I don't own any bttv hardware.
--
Jeff Garzik | "Mind if I drive?" -Sam
Building 1024 | "Not if you don't mind me clawing at the
MandrakeSoft | dash and screaming like a cheerleader."
| -Max
Index: include/linux/videodev.h
===================================================================
RCS file: /cvsroot/gkernel/linux_2_4/include/linux/videodev.h,v
retrieving revision 1.1.1.1
diff -u -r1.1.1.1 videodev.h
--- include/linux/videodev.h 2000/10/22 19:36:11 1.1.1.1
+++ include/linux/videodev.h 2000/10/28 07:31:20
@@ -32,6 +32,7 @@
int busy;
int minor;
devfs_handle_t devfs_handle;
+ int (*mmap_vma)(struct file *, struct vm_area_struct *, struct video_device *);
};
extern int videodev_init(void);
Index: drivers/media/video/bttv-driver.c
===================================================================
RCS file: /cvsroot/gkernel/linux_2_4/drivers/media/video/bttv-driver.c,v
retrieving revision 1.1.1.2
diff -u -r1.1.1.2 bttv-driver.c
--- drivers/media/video/bttv-driver.c 2000/10/22 22:02:34 1.1.1.2
+++ drivers/media/video/bttv-driver.c 2000/10/28 07:31:24
@@ -142,16 +142,6 @@
return ret;
}
-static inline unsigned long uvirt_to_bus(unsigned long adr)
-{
- unsigned long kva, ret;
-
- kva = uvirt_to_kva(pgd_offset(current->mm, adr), adr);
- ret = virt_to_bus((void *)kva);
- MDEBUG(printk("uv2b(%lx-->%lx)", adr, ret));
- return ret;
-}
-
static inline unsigned long kvirt_to_bus(unsigned long adr)
{
unsigned long va, kva, ret;
@@ -163,79 +153,60 @@
return ret;
}
-/* Here we want the physical address of the memory.
- * This is used when initializing the contents of the
- * area and marking the pages as reserved.
+/*
+ * Alloc and free DMA pages for mmap(2)
*/
-static inline unsigned long kvirt_to_pa(unsigned long adr)
-{
- unsigned long va, kva, ret;
-
- va = VMALLOC_VMADDR(adr);
- kva = uvirt_to_kva(pgd_offset_k(va), va);
- ret = __pa(kva);
- MDEBUG(printk("kv2pa(%lx-->%lx)", adr, ret));
- return ret;
-}
-
-static void * rvmalloc(signed long size)
+
+static void free_dmabuffers(struct bttv *btv)
{
- void * mem;
- unsigned long adr, page;
+ unsigned int i;
- mem=vmalloc_32(size);
- if (mem)
- {
- memset(mem, 0, size); /* Clear the ram out, no junk to the user */
- adr=(unsigned long) mem;
- while (size > 0)
- {
- page = kvirt_to_pa(adr);
- mem_map_reserve(virt_to_page(__va(page)));
- adr+=PAGE_SIZE;
- size-=PAGE_SIZE;
- }
+ if (btv->page) {
+ for (i = 0; i < btv->n_pages; i++)
+ if (btv->page[i].cpuaddr)
+ pci_free_consistent (btv->dev, PAGE_SIZE,
+ btv->page[i].cpuaddr,
+ btv->page[i].handle);
+ memset(btv->page, 0, sizeof(btv->page) * btv->n_pages);
+ btv->n_pages = 0;
+ kfree(btv->page);
+ btv->page = NULL;
}
- return mem;
}
-static void rvfree(void * mem, signed long size)
+static int alloc_dmabuffers(struct bttv *btv)
{
- unsigned long adr, page;
-
- if (mem)
- {
- adr=(unsigned long) mem;
- while (size > 0)
- {
- page = kvirt_to_pa(adr);
- mem_map_unreserve(virt_to_page(__va(page)));
- adr+=PAGE_SIZE;
- size-=PAGE_SIZE;
- }
- vfree(mem);
- }
-}
+ unsigned int i;
+ if (btv->page) {
+ printk(KERN_ERR "bttv%d: Double alloc of DMA pages!\n", btv->nr);
+ return 0;
+ }
+ btv->n_pages = (gbuffers * gbufsize) >> PAGE_SHIFT;
+ if ((gbuffers * gbufsize) % PAGE_SIZE)
+ btv->n_pages++;
-/*
- * Create the giant waste of buffer space we need for now
- * until we get DMA to user space sorted out (probably 2.3.x)
- *
- * We only create this as and when someone uses mmap
- */
-
-static int fbuffer_alloc(struct bttv *btv)
-{
- if(!btv->fbuffer)
- btv->fbuffer=(unsigned char *) rvmalloc(gbuffers*gbufsize);
- else
- printk(KERN_ERR "bttv%d: Double alloc of fbuffer!\n",
- btv->nr);
- if(!btv->fbuffer)
- return -ENOBUFS;
+ btv->page = kmalloc (sizeof(btv->page) * btv->n_pages, GFP_KERNEL);
+ if (!btv->page) {
+ btv->n_pages = 0;
+ return -ENOMEM;
+ }
+ memset (btv->page, 0, sizeof(btv->page) * btv->n_pages);
+
+ for (i = 0; i < btv->n_pages; i++) {
+ btv->page[i].cpuaddr = pci_alloc_consistent (
+ btv->dev, PAGE_SIZE, &btv->page[i].handle);
+ if (!btv->page[i].cpuaddr)
+ goto err_out;
+ memset(btv->page[i].cpuaddr, 0, PAGE_SIZE);
+ }
+
return 0;
+
+err_out:
+ free_dmabuffers(btv);
+ return -ENOMEM;
}
@@ -1265,13 +1236,13 @@
static int vgrab(struct bttv *btv, struct video_mmap *mp)
{
unsigned int *ro, *re;
- unsigned int *vbuf;
+ unsigned int *vbuf, page, page_ofs;
unsigned long flags;
- if(btv->fbuffer==NULL)
+ if (btv->page == NULL)
{
- if(fbuffer_alloc(btv))
- return -ENOBUFS;
+ int rc = alloc_dmabuffers(btv);
+ if (rc) return rc;
}
if(mp->frame >= gbuffers || mp->frame < 0)
@@ -1294,7 +1265,9 @@
* Ok load up the BT848
*/
- vbuf=(unsigned int *)(btv->fbuffer+gbufsize*mp->frame);
+ page = (gbufsize * mp->frame) >> PAGE_SHIFT;
+ page_ofs = (gbufsize * mp->frame) % PAGE_SIZE;
+ vbuf = (unsigned int *) (btv->page[page].cpuaddr + page_ofs);
ro=btv->gbuf[mp->frame].risc;
re=ro+2048;
make_vrisctab(btv, ro, re, vbuf, mp->width, mp->height, mp->format);
@@ -1433,9 +1406,8 @@
if (btv->user)
goto out_unlock;
- btv->fbuffer=(unsigned char *) rvmalloc(gbuffers*gbufsize);
- ret = -ENOMEM;
- if (!btv->fbuffer)
+ ret = alloc_dmabuffers(btv);
+ if (ret)
goto out_unlock;
btv->gq_in = 0;
@@ -1495,9 +1467,8 @@
* We have allowed it to drain.
*/
- if(btv->fbuffer)
- rvfree((void *) btv->fbuffer, gbuffers*gbufsize);
- btv->fbuffer=0;
+ if (btv->page)
+ free_dmabuffers(btv);
up(&btv->lock);
MOD_DEC_USE_COUNT;
}
@@ -2166,45 +2137,81 @@
return 0;
}
+static struct page * bttv_mm_nopage (struct vm_area_struct * vma,
+ unsigned long address, int write_access)
+{
+ struct bttv *btv = vma->vm_private_data;
+ struct page *dmapage;
+ unsigned long pgoff;
+
+ if (address > vma->vm_end) return NOPAGE_SIGBUS; /* Disallow mremap */
+ if (!btv) return NOPAGE_OOM; /* Nothing allocated */
+
+ pgoff = vma->vm_pgoff + ((address - vma->vm_start) >> PAGE_SHIFT);
+ if (pgoff > btv->n_pages) return NOPAGE_SIGBUS;
+
+ dmapage = virt_to_page (btv->page[pgoff].cpuaddr);
+ get_page (dmapage);
+ return dmapage;
+}
+
+#ifndef VM_RESERVE
+static int bttv_mm_swapout (struct page *page, struct file *filp)
+{
+ return 0;
+}
+#endif /* VM_RESERVE */
+
+struct vm_operations_struct bttv_mm_ops = {
+ nopage: bttv_mm_nopage,
+#ifndef VM_RESERVE
+ swapout: bttv_mm_swapout,
+#endif
+};
+
/*
* This maps the vmalloced and reserved fbuffer to user space.
- *
- * FIXME:
- * - PAGE_READONLY should suffice!?
- * - remap_page_range is kind of inefficient for page by page remapping.
- * But e.g. pte_alloc() does not work in modules ... :-(
*/
-static int do_bttv_mmap(struct bttv *btv, const char *adr, unsigned long size)
+static int do_bttv_mmap (struct file *file, struct vm_area_struct *vma,
+ struct bttv *btv)
{
- unsigned long start=(unsigned long) adr;
- unsigned long page,pos;
+ int rc = -EINVAL;
+ unsigned long max_size, size, start, offset;
- if (size>gbuffers*gbufsize)
- return -EINVAL;
- if (!btv->fbuffer) {
- if(fbuffer_alloc(btv))
- return -EINVAL;
- }
- pos=(unsigned long) btv->fbuffer;
- while (size > 0) {
- page = kvirt_to_pa(pos);
- if (remap_page_range(start, page, PAGE_SIZE, PAGE_SHARED))
- return -EAGAIN;
- start+=PAGE_SIZE;
- pos+=PAGE_SIZE;
- size-=PAGE_SIZE;
- }
- return 0;
+ max_size = (gbuffers * gbufsize);
+
+ start = vma->vm_start;
+ offset = (vma->vm_pgoff << PAGE_SHIFT);
+ size = vma->vm_end - vma->vm_start;
+
+ /* some basic size/offset sanity checks */
+ if (size > max_size)
+ goto out;
+ if (offset > max_size - size)
+ goto out;
+
+ vma->vm_ops = &bttv_mm_ops;
+ vma->vm_private_data = btv;
+
+#ifdef VM_RESERVE
+ vma->vm_flags |= VM_RESERVE;
+#endif
+
+ rc = 0;
+
+out:
+ return rc;
}
-static int bttv_mmap(struct video_device *dev, const char *adr, unsigned long size)
+static int bttv_mmap_vma(struct file *file, struct vm_area_struct *vma,
+ struct video_device *dev)
{
struct bttv *btv=(struct bttv *)dev;
int r;
down(&btv->lock);
- r=do_bttv_mmap(btv, adr, size);
+ r=do_bttv_mmap(file, vma, btv);
up(&btv->lock);
return r;
}
@@ -2212,20 +2219,18 @@
static struct video_device bttv_template=
{
- "UNSET",
- VID_TYPE_TUNER|VID_TYPE_CAPTURE|VID_TYPE_OVERLAY|VID_TYPE_TELETEXT,
- VID_HARDWARE_BT848,
- bttv_open,
- bttv_close,
- bttv_read,
- bttv_write,
- NULL,
- bttv_ioctl,
- bttv_mmap,
- bttv_init_done,
- NULL,
- 0,
- -1
+ name: "UNSET",
+ type: VID_TYPE_TUNER|VID_TYPE_CAPTURE|
+ VID_TYPE_OVERLAY|VID_TYPE_TELETEXT,
+ hardware: VID_HARDWARE_BT848,
+ open: bttv_open,
+ close: bttv_close,
+ read: bttv_read,
+ write: bttv_write,
+ ioctl: bttv_ioctl,
+ mmap_vma: bttv_mmap_vma,
+ initialize: bttv_init_done,
+ minor: -1,
};
@@ -2753,7 +2758,8 @@
memset(btv->vbibuf, 0, VBIBUF_SIZE); /* We don't want to return random
memory to the user */
- btv->fbuffer=NULL;
+ btv->page = NULL;
+ btv->n_pages = 0;
bt848_muxsel(btv, 1);
bt848_set_winsize(btv);
Index: drivers/media/video/bttv.h
===================================================================
RCS file: /cvsroot/gkernel/linux_2_4/drivers/media/video/bttv.h,v
retrieving revision 1.1.1.2
diff -u -r1.1.1.2 bttv.h
--- drivers/media/video/bttv.h 2000/10/22 22:02:34 1.1.1.2
+++ drivers/media/video/bttv.h 2000/10/28 07:31:24
@@ -251,6 +251,13 @@
unsigned long re;
};
+
+struct bttv_dma {
+ void *cpuaddr;
+ dma_addr_t handle;
+};
+
+
struct bttv {
struct video_device video_dev;
struct video_device radio_dev;
@@ -312,7 +319,9 @@
struct bttv_gbuf *gbuf;
int gqueue[MAX_GBUFFERS];
int gq_in,gq_out,gq_grab,gq_start;
- char *fbuffer;
+
+ struct bttv_dma *page;
+ unsigned int n_pages;
struct bttv_pll_info pll;
unsigned int Fsc;
Index: drivers/media/video/videodev.c
===================================================================
RCS file: /cvsroot/gkernel/linux_2_4/drivers/media/video/videodev.c,v
retrieving revision 1.1.1.1
diff -u -r1.1.1.1 videodev.c
--- drivers/media/video/videodev.c 2000/10/22 21:28:40 1.1.1.1
+++ drivers/media/video/videodev.c 2000/10/28 07:31:24
@@ -227,7 +227,9 @@
{
int ret = -EINVAL;
struct video_device *vfl=video_device[MINOR(file->f_dentry->d_inode->i_rdev)];
- if(vfl->mmap) {
+ if (vfl->mmap_vma)
+ return vfl->mmap_vma (file, vma, vfl);
+ if (vfl->mmap) {
lock_kernel();
ret = vfl->mmap(vfl, (char *)vma->vm_start,
(unsigned long)(vma->vm_end-vma->vm_start));