2.2.14 Videobuf Framework

注:videobuf框架已弃用,建议使用videobuf2。不应该在新的驱动程序中使用。
2.2.14.1 Introduction
videobuf层作为V4L2驱动程序和用户空间之间的一种粘合层。它处理为存储视频帧而分配和管理缓冲区。它有一组函数,可以用于实现许多标准POSIX I/O系统调用,包括read()、poll()和mmap()。另一组函数可用于实现与流I/O相关的大部分V4L2 ioctl()调用,包括缓冲区分配、排队和出队以及流控制。使用videobuf对驱动程序作者施加了一些设计决策的限制,但回报是减少了驱动程序中的代码,并且保持了V4L2用户空间API的一致实现。
2.2.14.2 Buffer types
并非所有视频设备都使用相同类型的缓冲区。实际上,至少有三种常见变体:
• 在物理和(内核)虚拟地址空间中分散的缓冲区。几乎所有用户空间缓冲区都是这样的,但当可能时,为内核空间分配这种方式的缓冲区也是很有意义的。不幸的是,这种类型的缓冲区通常需要能够执行分散/聚集DMA操作的硬件来使用。
• 物理上分散但在虚拟上是连续的缓冲区;换句话说,使用vmalloc()分配的缓冲区。这些缓冲区对于DMA操作同样难以使用,但在DMA不可用但虚拟连续缓冲区很方便的情况下可以派上用场。
• 物理上连续的缓冲区。在碎片化的系统上,分配这种类型的缓冲区可能不可靠,但更简单的DMA控制器无法处理其他任何类型的缓冲区。
Videobuf可以与所有三种类型的缓冲区一起工作,但驱动程序的作者必须在开始时选择其中一种类型,并围绕该决策设计驱动程序。
值得注意的是,还有第四种类型的缓冲区,即位于系统视频内存中的"覆盖"缓冲区。虽然覆盖功能在大多数情况下已被认为是过时的,但在某些系统级芯片的驱动程序中,由于性能优势明显,偶尔还会出现使用这种技术的情况。覆盖缓冲区可以作为一种散布式缓冲区来处理,但在内核中实现这种技术的数量非常有限,因此本文档的范围不包括对该技术的详细描述。
DMA控制器要求物理内存连续么?
DMA控制器通常对物理内存的连续性没有硬性要求。传统上,DMA控制器可以使用两种方式来处理内存传输:基于物理内存地址的传输和基于虚拟内存地址的传输。
在基于物理内存地址的传输中,DMA控制器会直接访问物理内存,并且传输的数据必须在物理内存中是连续的。这是因为DMA控制器通过内存控制器直接与物理内存进行通信,需要按照物理内存的布局进行操作。
然而,在现代计算机系统中,基于虚拟内存地址的传输更为常见。在这种情况下,DMA控制器通过内存管理单元(MMU)将虚拟内存地址转换为物理内存地址。由于内存管理单元可以将虚拟内存地址映射到物理内存中的任意位置,因此物理内存的连续性对于DMA控制器的工作不是严格要求。
虽然大多数DMA控制器不要求物理内存连续,但某些特殊情况下可能存在一些限制。例如,某些硬件设备可能要求DMA缓冲区必须是物理内存连续的,以满足其特定的存储器访问要求。在这种情况下,应根据设备的要求来分配连续的物理内存区域。
总之,一般情况下DMA控制器对物理内存的连续性没有严格要求,因为虚拟内存地址转换可以允许非连续的物理内存使用。然而,在特定硬件设备要求下,可能需要分配连续的物理内存区域。
2.2.14.3 Data structures, callbacks, and initialization
根据使用的缓冲区类型不同,驱动程序应包含以下文件之一:

<media/videobuf-dma-sg.h>        /* Physically scattered */
<media/videobuf-vmalloc.h>       /* vmalloc() buffers */
<media/videobuf-dma-contig.h>    /* Physically contiguous */

驱动程序描述V4L2设备的数据结构应该包括`struct videobuf_queue`的一个实例,用于管理缓冲区队列,以及一个`list_head`用于可用缓冲区的队列。还需要一个中断安全的自旋锁,用于保护至少队列。
下一步是编写四个简单的回调函数,以帮助`videobuf`处理缓冲区的管理:

struct videobuf_queue_ops {
int (*buf_setup)(struct videobuf_queue *q,
unsigned int *count, unsigned int *size);
int (*buf_prepare)(struct videobuf_queue *q,
struct videobuf_buffer *vb,
enum v4l2_field field);
void (*buf_queue)(struct videobuf_queue *q,
struct videobuf_buffer *vb);
void (*buf_release)(struct videobuf_queue *q,
struct videobuf_buffer *vb);
};

在I/O过程的早期,当开始流传输时,会调用`buf_setup()`函数;其目的是告诉`videobuf`关于I/O流的信息。`count`参数将是建议使用的缓冲区数量;驱动程序应检查其合理性,并在需要时进行调整。作为实际规则,至少需要两个缓冲区进行正确的流传输,并且通常每个设备都有一个合理的最大值(不能超过32)。`size`参数应设置为每帧数据的预期(最大)大小。
每个缓冲区(以`struct videobuf_buffer`指针的形式)将传递给`buf_prepare()`函数,该函数应正确设置缓冲区的大小、宽度、高度和字段属性。如果缓冲区的状态字段为`VIDEOBUF_NEEDS_INIT`,驱动程序应将其传递给:

int videobuf_iolock(struct videobuf_queue* q, struct videobuf_buffer *vb,
struct v4l2_framebuffer *fbuf);

除其他操作外,此调用通常会为缓冲区分配内存。最后,`buf_prepare()`函数应将缓冲区的状态设置为`VIDEOBUF_PREPARED`。
当缓冲区排队进行I/O时,它会被传递给`buf_queue()`函数,该函数应将其放入驱动程序的可用缓冲区列表,并将其状态设置为`VIDEOBUF_QUEUED`。请注意,此函数在持有队列自旋锁的情况下被调用;如果它尝试再次获取锁,将会停滞不前。是的,这是经验之谈。还要注意,`videobuf`可能会在队列中的第一个缓冲区上等待;将其他缓冲区放在其前面可能会导致问题。因此,请使用`list_add_tail()`函数将缓冲区加入队列。
最后,当不再使用缓冲区时,会调用`buf_release()`函数。驱动程序应确保缓冲区上没有活动的I/O,然后将其传递给相应的释放函数:

/* Scatter/gather drivers */
int videobuf_dma_unmap(struct videobuf_queue *q,
struct videobuf_dmabuf *dma);
int videobuf_dma_free(struct videobuf_dmabuf *dma);
/* vmalloc drivers */
void videobuf_vmalloc_free (struct videobuf_buffer *buf);
/* Contiguous drivers */
void videobuf_dma_contig_free(struct videobuf_queue *q,
struct videobuf_buffer *buf);

确保缓冲区不再处于I/O状态的一种方式是将其传递给:

int videobuf_waiton(struct videobuf_buffer *vb, int non_blocking, int intr);

在这里,`vb`表示缓冲区,`non_blocking`表示是否应该使用非阻塞的I/O(在`buf_release()`情况下应该为零),而`intr`控制是否使用可中断的等待。
2.2.14.4 File operations
在这一点上,大部分工作已经完成;其余的工作是将`videobuf`调用嵌入到其他驱动程序回调函数的实现中。第一步是在`open()`函数中初始化`videobuf`队列。要使用的函数取决于所使用的缓冲区类型:

void videobuf_queue_sg_init(struct videobuf_queue *q,
struct videobuf_queue_ops *ops,
struct device *dev,
spinlock_t *irqlock,
enum v4l2_buf_type type,
enum v4l2_field field,
unsigned int msize,
void *priv);
void videobuf_queue_vmalloc_init(struct videobuf_queue *q,
struct videobuf_queue_ops *ops,
struct device *dev,
spinlock_t *irqlock,
enum v4l2_buf_type type,
enum v4l2_field field,
unsigned int msize,
void *priv);
void videobuf_queue_dma_contig_init(struct videobuf_queue *q,
struct videobuf_queue_ops *ops,
struct device *dev,
spinlock_t *irqlock,
enum v4l2_buf_type type,
enum v4l2_field field,
unsigned int msize,
void *priv);

在每种情况下,参数是相同的:`q`是设备的队列结构,`ops`是上述描述的回调函数集,`dev`是此视频设备的设备结构,`irqlock`是一个中断安全的自旋锁,用于保护数据结构的访问,`type`是设备使用的缓冲区类型(例如,摄像头将使用`V4L2_BUF_TYPE_VIDEO_CAPTURE`),`field`描述要捕获的字段(对于渐进式设备通常为`V4L2_FIELD_NONE`),`msize`是围绕`struct videobuf_buffer`使用的任何包含结构的大小,`priv`是一个私有数据指针,出现在`struct videobuf_queue`的`priv_data`字段中。请注意,这些都是无返回值的函数,显然不会发生失败。
V4L2捕获驱动程序可以编写以支持两种API之一:`read()`系统调用和相对复杂的流式机制。作为一般规则,必须同时支持两者,以确保所有应用程序都有与设备配合工作的机会。`Videobuf`使使用相同的代码实现这一点变得容易。为了实现`read()`,驱动程序只需要调用以下其中之一:

ssize_t videobuf_read_one(struct videobuf_queue *q,
char __user *data, size_t count,
loff_t *ppos, int nonblocking);
ssize_t videobuf_read_stream(struct videobuf_queue *q,
char __user *data, size_t count,
loff_t *ppos, int vbihack, int nonblocking);

这两个函数中的任意一个都会将帧数据读入`data`,并返回实际读取的量;不同之处在于,`videobuf_read_one()`只会读取单个帧,而`videobuf_read_stream()`如果需要满足应用程序请求的计数,将读取多个帧。典型的驱动程序`read()`实现将启动捕获引擎,在调用上述函数之前,然后在返回之前停止引擎(尽管更智能的实现可能会预期在不久的将来发生另一个`read()`调用,并保持引擎运行一段时间)。
`poll()`函数通常可以直接调用以下函数来实现:

unsigned int videobuf_poll_stream(struct file *file,
struct videobuf_queue *q,
poll_table *wait);

需要注意的是,最终使用的等待队列将是与第一个可用缓冲区相关联的队列。
当对内核空间缓冲区进行流式I/O时,驱动程序必须支持`mmap()`系统调用,以使用户空间能够访问数据。在许多V4L2驱动程序中,常见复杂的`mmap()`实现简化为单个调用:

int videobuf_mmap_mapper(struct videobuf_queue *q,
struct vm_area_struct *vma);

其他所有的处理都由`videobuf`代码处理。
`release()`函数需要调用两个独立的`videobuf`函数:

void videobuf_stop(struct videobuf_queue *q);
int videobuf_mmap_free(struct videobuf_queue *q);

调用`videobuf_stop()`函数会终止任何正在进行的I/O操作,但是驱动程序仍然需要停止捕获引擎。调用`videobuf_mmap_free()`函数将确保所有缓冲区都已取消映射;如果成功取消映射,它们将都传递给`buf_release()`回调函数。如果仍然存在映射的缓冲区,`videobuf_mmap_free()`函数将返回一个错误代码。显然,其目的是在仍有映射的缓冲区时导致关闭文件描述符失败,但是在2.6.32内核中的每个驱动程序都会忽略它的返回值。
2.2.14.5 ioctl() operations
V4L2 API包括一个非常长的驱动程序回调函数列表,用于响应提供给用户空间的许多`ioctl()`命令。其中一些(与流式I/O相关的)几乎直接转换为`videobuf`调用。相关的辅助函数包括:

int videobuf_reqbufs(struct videobuf_queue *q,
struct v4l2_requestbuffers *req);
int videobuf_querybuf(struct videobuf_queue *q, struct v4l2_buffer *b);
int videobuf_qbuf(struct videobuf_queue *q, struct v4l2_buffer *b);
int videobuf_dqbuf(struct videobuf_queue *q, struct v4l2_buffer *b,
int nonblocking);
int videobuf_streamon(struct videobuf_queue *q);
int videobuf_streamoff(struct videobuf_queue *q);

例如,对于`VIDIOC_REQBUFS`调用,将转换为对驱动程序的`vidioc_reqbufs()`回调函数的调用,该回调通常只需要找到正确的`struct videobuf_queue`指针并将其传递给`videobuf_reqbufs()`函数。这些支持函数可以在许多V4L2驱动程序中替代大量的缓冲区管理样板代码。
`vidioc_streamon()`和`vidioc_streamoff()`函数会稍微复杂一些,因为它们还需要处理启动和停止捕获引擎的问题。
2.2.14.6 Buffer allocation
到目前为止,我们已经讨论了缓冲区,但还没有看它们是如何分配的。对于散射/聚集(scatter/gather)情况来说,在这方面最复杂。对于分配,驱动程序可以完全将缓冲区分配工作交给`videobuf`层处理;在这种情况下,缓冲区将作为匿名用户空间页面进行分配,并且确实非常散乱。如果应用程序使用用户空间缓冲区,则不需要分配;`videobuf`层将负责调用`get_user_pages()`并填充散列表数组。
如果驱动程序需要自行进行内存分配,应该在调用`videobuf_reqbufs()`后的`vidioc_reqbufs()`函数中完成。第一步是调用:

struct videobuf_dmabuf *videobuf_to_dma(struct videobuf_buffer *buf);

返回的`videobuf_dmabuf`结构(在`<media/videobuf-dma-sg.h>`中定义)包括一些相关的字段:

struct scatterlist *sglist;
int sglen;

驱动程序必须分配适当大小的散列表数组,并将指针填充到分配的缓冲区的各个部分;`sglen`应设置为数组的长度。
使用`vmalloc()`方法的驱动程序不需要(也不能)关心缓冲区的分配;`videobuf`将处理这些细节。对于连续DMA驱动程序,通常也是如此;`videobuf`在适当的时候使用`dma_alloc_coherent()`分配缓冲区。这意味着这些驱动程序可能随时尝试进行高阶分配,这种操作并不总是保证有效。一些驱动程序通过在系统引导时分配DMA空间来玩一些花招;目前`videobuf`与这些驱动程序不太兼容。
从2.6.31版本开始,连续DMA驱动程序可以与用户提供的缓冲区一起使用,只要该缓冲区是物理连续的。普通的用户空间分配不满足这个条件,但从其他内核驱动程序获取的缓冲区或包含在巨大页面中的缓冲区可以与这些驱动程序一起使用。
2.2.14.7 Filling the buffers
videobuf的最后一部分实现没有直接的回调函数,它是将帧数据放入缓冲区的代码部分,通常是响应来自设备的中断。对于所有类型的驱动程序,该过程大致按照以下步骤进行:
- 获取下一个可用的缓冲区,并确保有人在等待它。
- 获取内存指针,并将视频数据放入其中。
- 将缓冲区标记为已完成,并唤醒等待它的进程。
上述第一步是通过查看驱动程序管理的`list_head`结构完成的,这个结构在`buf_queue()`回调中填充。因为启动引擎和将缓冲区入队是分开的步骤,所以引擎有可能在没有可用缓冲区的情况下运行,尤其是在使用`vmalloc()`的情况下。因此,驱动程序应准备好列表为空的情况。同样有可能还没有进程对缓冲区感兴趣;驱动程序不应在没有进程等待时从列表中移除缓冲区或填充它。可以通过使用`waitqueue_active()`检查缓冲区的`done`字段(一个`wait_queue_head_t`结构)来进行测试。
在对缓冲区进行DMA映射之前,应将缓冲区的状态设置为`VIDEOBUF_ACTIVE`,以确保在设备传输数据时,videobuf层不会尝试对其执行任何操作。
对于散列/聚集驱动程序,所需的内存指针将在上述的scatterlist结构中找到。使用`vmalloc()`方法的驱动程序可以通过以下方式获得内存指针:

void *videobuf_to_vmalloc(struct videobuf_buffer *buf);

对于连续DMA驱动程序,要使用的函数是:

dma_addr_t videobuf_to_dma_contig(struct videobuf_buffer *buf);

连续DMA API在尽可能地隐藏DMA缓冲区的内核空间地址方面做了很多工作,以防止驱动程序访问它。
最后一步是将相关的videobuf_buffer结构的size字段设置为捕获图像的实际大小,将state设置为VIDEOBUF_DONE,然后在done队列上调用wake_up()函数。
此时,缓冲区归videobuf层所有,驱动程序不应再触碰它。
对于对此更多信息感兴趣的开发人员,可以查看相关的头文件;其中声明了一些未在此处讨论的低级函数。另请注意,所有这些调用仅导出为GPL(通用公共许可证)的模块,因此非GPL内核模块将无法使用这些功能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值