📄 video-buf.c
字号:
/*
* generic helper functions for video4linux capture buffers, to handle
* memory management and PCI DMA. Right now bttv + saa7134 use it.
*
* The functions expect the hardware being able to scatter gatter
* (i.e. the buffers are not linear in physical memory, but fragmented
* into PAGE_SIZE chunks). They also assume the driver does not need
* to touch the video data (thus it is probably not useful for USB as
* data often must be uncompressed by the drivers).
*
* (c) 2001,02 Gerd Knorr <kraxel@bytesex.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/version.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/vmalloc.h>
#include <linux/pagemap.h>
#include <linux/slab.h>
#include <linux/pci.h>
#include <linux/interrupt.h>
#include <asm/page.h>
#include <asm/pgtable.h>
#ifndef TryLockPage
# include "linux/page-flags.h"
# define TryLockPage TestSetPageLocked
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,64)
#include "video-buf.h"
#else
#include <media/video-buf.h>
#endif
static int debug = 0;
MODULE_DESCRIPTION("helper module to manage video4linux pci dma buffers");
MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]");
MODULE_LICENSE("GPL");
MODULE_PARM(debug,"i");
#define dprintk(level, fmt, arg...) if (debug >= level) \
printk(KERN_DEBUG "vbuf: " fmt , ## arg)
struct scatterlist*
videobuf_vmalloc_to_sg(unsigned char *virt, int nr_pages)
{
struct scatterlist *sglist;
struct page *pg;
int i;
sglist = kmalloc(sizeof(struct scatterlist)*nr_pages, GFP_KERNEL);
if (NULL == sglist)
return NULL;
memset(sglist,0,sizeof(struct scatterlist)*nr_pages);
for (i = 0; i < nr_pages; i++, virt += PAGE_SIZE) {
pg = vmalloc_to_page(virt);
if (NULL == pg)
goto err;
if (PageHighMem(pg))
BUG();
sglist[i].page = pg;
sglist[i].length = PAGE_SIZE;
}
return sglist;
err:
kfree(sglist);
return NULL;
}
struct scatterlist*
videobuf_pages_to_sg(struct page **pages, int nr_pages, int offset)
{
struct scatterlist *sglist;
int i = 0;
if (NULL == pages[0])
return NULL;
sglist = kmalloc(sizeof(*sglist) * nr_pages, GFP_KERNEL);
if (NULL == sglist)
return NULL;
memset(sglist, 0, sizeof(*sglist) * nr_pages);
if (NULL == pages[0])
goto nopage;
if (PageHighMem(pages[0]))
/* DMA to highmem pages might not work */
goto highmem;
sglist[0].page = pages[0];
sglist[0].offset = offset;
sglist[0].length = PAGE_SIZE - offset;
for (i = 1; i < nr_pages; i++) {
if (NULL == pages[i])
goto nopage;
if (PageHighMem(pages[i]))
goto highmem;
sglist[i].page = pages[i];
sglist[i].length = PAGE_SIZE;
}
return sglist;
nopage:
dprintk(2,"sgl: oops - no page\n");
kfree(sglist);
return NULL;
highmem:
dprintk(2,"sgl: oops - highmem page\n");
kfree(sglist);
return NULL;
}
int videobuf_lock(struct page **pages, int nr_pages)
{
int i;
dprintk(2,"lock start ...\n");
for (i = 0; i < nr_pages; i++)
if (TryLockPage(pages[i]))
goto err;
dprintk(2,"lock ok [%d pages]\n",nr_pages);
return 0;
err:
dprintk(2,"lock failed, unlock ...\n");
while (i > 0)
unlock_page(pages[--i]);
dprintk(2,"lock quit\n");
return -EINVAL;
}
int videobuf_unlock(struct page **pages, int nr_pages)
{
int i;
dprintk(2,"unlock start ...\n");
for (i = 0; i < nr_pages; i++)
unlock_page(pages[i]);
dprintk(2,"unlock ok [%d pages]\n",nr_pages);
return 0;
}
/* --------------------------------------------------------------------- */
int videobuf_dma_init_user(struct videobuf_dmabuf *dma, int direction,
unsigned long data, unsigned long size)
{
unsigned long first,last;
int err, rw = 0;
dma->direction = direction;
switch (dma->direction) {
case PCI_DMA_FROMDEVICE: rw = READ; break;
case PCI_DMA_TODEVICE: rw = WRITE; break;
default: BUG();
}
first = (data & PAGE_MASK) >> PAGE_SHIFT;
last = ((data+size-1) & PAGE_MASK) >> PAGE_SHIFT;
dma->offset = data & ~PAGE_MASK;
dma->nr_pages = last-first+1;
dma->pages = kmalloc(dma->nr_pages * sizeof(struct page*),
GFP_KERNEL);
if (NULL == dma->pages)
return -ENOMEM;
dprintk(1,"init user [0x%lx+0x%lx => %d pages]\n",
data,size,dma->nr_pages);
down_read(¤t->mm->mmap_sem);
err = get_user_pages(current,current->mm,
data & PAGE_MASK, dma->nr_pages,
rw == READ, 1, /* force */
dma->pages, NULL);
up_read(¤t->mm->mmap_sem);
if (err != dma->nr_pages) {
dma->nr_pages = (err >= 0) ? err : 0;
dprintk(1,"get_user_pages: err=%d [%d]\n",err,dma->nr_pages);
return err < 0 ? err : -EINVAL;
}
return 0;
}
int videobuf_dma_init_kernel(struct videobuf_dmabuf *dma, int direction,
int nr_pages)
{
dprintk(1,"init kernel [%d pages]\n",nr_pages);
dma->direction = direction;
dma->vmalloc = vmalloc_32(nr_pages << PAGE_SHIFT);
if (NULL == dma->vmalloc) {
dprintk(1,"vmalloc_32(%d pages) failed\n",nr_pages);
return -ENOMEM;
}
memset(dma->vmalloc,0,nr_pages << PAGE_SHIFT);
dma->nr_pages = nr_pages;
return 0;
}
int videobuf_dma_init_overlay(struct videobuf_dmabuf *dma, int direction,
dma_addr_t addr, int nr_pages)
{
dprintk(1,"init overlay [%d pages @ bus 0x%lx]\n",
nr_pages,(unsigned long)addr);
dma->direction = direction;
if (0 == addr)
return -EINVAL;
dma->bus_addr = addr;
dma->nr_pages = nr_pages;
return 0;
}
int videobuf_dma_pci_map(struct pci_dev *dev, struct videobuf_dmabuf *dma)
{
int err;
if (0 == dma->nr_pages)
BUG();
if (dma->pages) {
if (0 != (err = videobuf_lock(dma->pages, dma->nr_pages))) {
dprintk(1,"videobuf_lock: %d\n",err);
return err;
}
dma->sglist = videobuf_pages_to_sg(dma->pages, dma->nr_pages,
dma->offset);
if (NULL == dma->sglist)
videobuf_unlock(dma->pages, dma->nr_pages);
}
if (dma->vmalloc) {
dma->sglist = videobuf_vmalloc_to_sg
(dma->vmalloc,dma->nr_pages);
}
if (dma->bus_addr) {
dma->sglist = kmalloc(sizeof(struct scatterlist), GFP_KERNEL);
if (NULL != dma->sglist) {
dma->sglen = 1;
sg_dma_address(&dma->sglist[0]) = dma->bus_addr & PAGE_MASK;
dma->sglist[0].offset = dma->bus_addr & ~PAGE_MASK;
sg_dma_len(&dma->sglist[0]) = dma->nr_pages * PAGE_SIZE;
}
}
if (NULL == dma->sglist) {
dprintk(1,"scatterlist is NULL\n");
return -ENOMEM;
}
if (!dma->bus_addr)
dma->sglen = pci_map_sg(dev,dma->sglist,dma->nr_pages,
dma->direction);
return 0;
}
int videobuf_dma_pci_sync(struct pci_dev *dev, struct videobuf_dmabuf *dma)
{
if (!dma->sglen)
BUG();
if (!dma->bus_addr)
pci_dma_sync_sg(dev,dma->sglist,dma->nr_pages,dma->direction);
return 0;
}
int videobuf_dma_pci_unmap(struct pci_dev *dev, struct videobuf_dmabuf *dma)
{
if (!dma->sglen)
return 0;
if (!dma->bus_addr)
pci_unmap_sg(dev,dma->sglist,dma->nr_pages,dma->direction);
kfree(dma->sglist);
dma->sglist = NULL;
dma->sglen = 0;
if (dma->pages)
videobuf_unlock(dma->pages, dma->nr_pages);
return 0;
}
int videobuf_dma_free(struct videobuf_dmabuf *dma)
{
if (dma->sglen)
BUG();
if (dma->pages) {
int i;
for (i=0; i < dma->nr_pages; i++)
page_cache_release(dma->pages[i]);
kfree(dma->pages);
dma->pages = NULL;
}
if (dma->vmalloc) {
vfree(dma->vmalloc);
dma->vmalloc = NULL;
}
if (dma->bus_addr) {
dma->bus_addr = 0;
}
dma->direction = PCI_DMA_NONE;
return 0;
}
/* --------------------------------------------------------------------- */
void* videobuf_alloc(unsigned int size)
{
struct videobuf_buffer *vb;
vb = kmalloc(size,GFP_KERNEL);
if (NULL != vb) {
memset(vb,0,size);
init_waitqueue_head(&vb->done);
}
return vb;
}
int videobuf_waiton(struct videobuf_buffer *vb, int non_blocking, int intr)
{
int retval = 0;
DECLARE_WAITQUEUE(wait, current);
add_wait_queue(&vb->done, &wait);
while (vb->state == STATE_ACTIVE || vb->state == STATE_QUEUED) {
if (non_blocking) {
retval = -EAGAIN;
break;
}
set_current_state(intr ? TASK_INTERRUPTIBLE
: TASK_UNINTERRUPTIBLE);
if (vb->state == STATE_ACTIVE || vb->state == STATE_QUEUED)
schedule();
set_current_state(TASK_RUNNING);
if (intr && signal_pending(current)) {
dprintk(1,"buffer waiton: -EINTR\n");
retval = -EINTR;
break;
}
}
remove_wait_queue(&vb->done, &wait);
return retval;
}
int
videobuf_iolock(struct pci_dev *pci, struct videobuf_buffer *vb,
struct v4l2_framebuffer *fbuf)
{
int err,pages;
dma_addr_t bus;
switch (vb->memory) {
case V4L2_MEMORY_MMAP:
case V4L2_MEMORY_USERPTR:
if (0 == vb->baddr) {
/* no userspace addr -- kernel bounce buffer */
pages = PAGE_ALIGN(vb->size) >> PAGE_SHIFT;
err = videobuf_dma_init_kernel(&vb->dma,PCI_DMA_FROMDEVICE,
pages);
if (0 != err)
return err;
} else {
/* dma directly to userspace */
err = videobuf_dma_init_user(&vb->dma,PCI_DMA_FROMDEVICE,
vb->baddr,vb->bsize);
if (0 != err)
return err;
}
break;
case V4L2_MEMORY_OVERLAY:
if (NULL == fbuf)
return -EINVAL;
/* FIXME: need sanity checks for vb->boff */
bus = (dma_addr_t)fbuf->base + vb->boff;
pages = PAGE_ALIGN(vb->size) >> PAGE_SHIFT;
err = videobuf_dma_init_overlay(&vb->dma,PCI_DMA_FROMDEVICE,
bus, pages);
if (0 != err)
return err;
break;
default:
BUG();
}
err = videobuf_dma_pci_map(pci,&vb->dma);
if (0 != err)
return err;
return 0;
}
/* --------------------------------------------------------------------- */
void
videobuf_queue_init(struct videobuf_queue *q,
struct videobuf_queue_ops *ops,
struct pci_dev *pci,
spinlock_t *irqlock,
enum v4l2_buf_type type,
enum v4l2_field field,
unsigned int msize)
{
memset(q,0,sizeof(*q));
q->irqlock = irqlock;
q->pci = pci;
q->type = type;
q->field = field;
q->msize = msize;
q->ops = ops;
init_MUTEX(&q->lock);
INIT_LIST_HEAD(&q->stream);
}
int
videobuf_queue_is_busy(struct videobuf_queue *q)
{
int i;
if (q->streaming) {
dprintk(1,"busy: streaming active\n");
return 1;
}
if (q->reading) {
dprintk(1,"busy: pending read #1\n");
return 1;
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -