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));

Reply via email to