(gdb) p q->ops->start_streaming
$26 = (int (*)(struct vb2_queue *, unsigned int)) 0xffffff80089eda10 <rkisp_start_streaming>
(gdb) p ((struct rkisp_stream*)q->drv_priv)->ispdev
$27 = (struct rkisp_device *) 0xffffffc0e6880080
(gdb) p q
$28 = (struct vb2_queue *) 0xffffffc0e6880d78
(gdb) p (p->subdevs[0])->name
$9 = "rkisp-csi-subdev", '\000' <repeats 15 times>
(gdb) p (p->subdevs[1])->name
$10 = "rockchip-csi2-dphy0", '\000' <repeats 12 times>
(gdb) p (p->subdevs[2])->name
$11 = "m00_b_ov13850 4-0010", '\000' <repeats 11 times>
phy -> sensor:启动视频流时,先启动 phy(接收端),后启动 sensor(发送端),避免数据丢失;
sensor -> phy:停止视频流时,先停止 sensor(发送端),后停止 phy(接收端),避免硬件卡死;
核心原则:数据流启停需遵循「接收端先就绪、发送端先关闭」的顺序,这是 MIPI CSI 硬件的通用规范;
底层逻辑:p->subdevs数组按phy在前、sensor在后排列,因此正向遍历是 phy→sensor,反向是 sensor→phy。
这个顺序是嵌入式 MIPI 摄像头开发的 "通用坑点",如果顺序反了,会出现摄像头启动后无图像、停止后设备卡死等问题,
(gdb) p ((struct v4l2_ctrl*)hdl->ctrls->next->next->next->next->next->next->next->next->next->next)->name
$30 = 0xffffff8009203da3 "Test Pattern"
ubuntu20@NYX:~/opt/test$ ./a.out
VIDIOC_QUERYCAP: 2154321408 (0x80685600)
VIDIOC_ENUM_FMT: 3225441794 (0xC0405602)
VIDIOC_G_FMT: 3234878980 (0xC0D05604)
VIDIOC_S_FMT: 3234878981 (0xC0D05605)
VIDIOC_REQBUFS: 3222558216 (0xC0145608)
VIDIOC_QUERYBUF: 3227014665 (0xC0585609)
VIDIOC_G_FBUF: 2150651402 (0x8030560A)
VIDIOC_S_FBUF: 1076909579 (0x4030560B)
VIDIOC_OVERLAY: 1074025998 (0x4004560E)
VIDIOC_QBUF: 3227014671 (0xC058560F)
VIDIOC_EXPBUF: 3225441808 (0xC0405610)
VIDIOC_DQBUF: 3227014673 (0xC0585611)
VIDIOC_STREAMON: 1074026002 (0x40045612)
VIDIOC_STREAMOFF: 1074026003 (0x40045613)
ubuntu20@NYX:~/opt/test$ cat a.c
#include <stdio.h>
#include <linux/videodev2.h> // 包含V4L2宏定义
#include <linux/ioctl.h> // 包含_IO/_IOR/_IOW/_IOWR宏
int main() {
// 打印所有命令的十进制和十六进制值
printf("VIDIOC_QUERYCAP: %u (0x%X)\n", VIDIOC_QUERYCAP, VIDIOC_QUERYCAP);
printf("VIDIOC_ENUM_FMT: %u (0x%X)\n", VIDIOC_ENUM_FMT, VIDIOC_ENUM_FMT);
printf("VIDIOC_G_FMT: %u (0x%X)\n", VIDIOC_G_FMT, VIDIOC_G_FMT);
printf("VIDIOC_S_FMT: %u (0x%X)\n", VIDIOC_S_FMT, VIDIOC_S_FMT);
printf("VIDIOC_REQBUFS: %u (0x%X)\n", VIDIOC_REQBUFS, VIDIOC_REQBUFS);
printf("VIDIOC_QUERYBUF: %u (0x%X)\n", VIDIOC_QUERYBUF, VIDIOC_QUERYBUF);
printf("VIDIOC_G_FBUF: %u (0x%X)\n", VIDIOC_G_FBUF, VIDIOC_G_FBUF);
printf("VIDIOC_S_FBUF: %u (0x%X)\n", VIDIOC_S_FBUF, VIDIOC_S_FBUF);
printf("VIDIOC_OVERLAY: %u (0x%X)\n", VIDIOC_OVERLAY, VIDIOC_OVERLAY);
printf("VIDIOC_QBUF: %u (0x%X)\n", VIDIOC_QBUF, VIDIOC_QBUF);
printf("VIDIOC_EXPBUF: %u (0x%X)\n", VIDIOC_EXPBUF, VIDIOC_EXPBUF);
printf("VIDIOC_DQBUF: %u (0x%X)\n", VIDIOC_DQBUF, VIDIOC_DQBUF);
printf("VIDIOC_STREAMON: %u (0x%X)\n", VIDIOC_STREAMON, VIDIOC_STREAMON);
printf("VIDIOC_STREAMOFF: %u (0x%X)\n", VIDIOC_STREAMOFF, VIDIOC_STREAMOFF);
return 0;
}
ubuntu20@NYX:~/opt/test$
//test_pattern
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
// 自定义ARRAY_SIZE宏(用户态通用,替代内核宏)
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
#endif
// 定义测试模式menu的映射(和驱动侧保持一致)
static const char *ov13850_test_pattern_str[] = {
"Disabled",
"Vertical Color Bar Type 1",
"Vertical Color Bar Type 2",
"Vertical Color Bar Type 3",
"Vertical Color Bar Type 4"
};
// 函数:查看V4L2_CID_TEST_PATTERN的menu选项
static int query_test_pattern_menu(int fd) {
struct v4l2_queryctrl qctrl = {0};
qctrl.id = V4L2_CID_TEST_PATTERN;
// 查询控件基本信息
if (ioctl(fd, VIDIOC_QUERYCTRL, &qctrl) < 0) {
perror("VIDIOC_QUERYCTRL failed (test pattern ctrl not exist?)");
return -1;
}
printf("==== Test Pattern Control Info ====\n");
printf("Name: %s\n", qctrl.name);
printf("Min: %d, Max: %d, Default: %d\n", qctrl.minimum, qctrl.maximum, qctrl.default_value);
printf("Menu Options:\n");
// 遍历menu选项并打印
struct v4l2_querymenu qmenu = {0};
qmenu.id = V4L2_CID_TEST_PATTERN;
for (qmenu.index = qctrl.minimum; qmenu.index <= qctrl.maximum; qmenu.index++) {
if (ioctl(fd, VIDIOC_QUERYMENU, &qmenu) < 0) {
perror("VIDIOC_QUERYMENU failed");
continue;
}
printf(" %d: %s\n", qmenu.index, qmenu.name);
}
printf("====================================\n\n");
return 0;
}
// 函数:设置测试模式
static int set_test_pattern(int fd, int value) {
struct v4l2_control ctrl = {0};
ctrl.id = V4L2_CID_TEST_PATTERN;
ctrl.value = value;
if (ioctl(fd, VIDIOC_S_CTRL, &ctrl) < 0) {
perror("VIDIOC_S_CTRL failed");
return -1;
}
printf("Set test pattern to: %d (%s)\n", value,
(value >=0 && value < ARRAY_SIZE(ov13850_test_pattern_str)) ?
ov13850_test_pattern_str[value] : "Unknown");
return 0;
}
// 函数:读取当前测试模式
static int get_test_pattern(int fd) {
struct v4l2_control ctrl = {0};
ctrl.id = V4L2_CID_TEST_PATTERN;
if (ioctl(fd, VIDIOC_G_CTRL, &ctrl) < 0) {
perror("VIDIOC_G_CTRL failed");
return -1;
}
printf("Current test pattern: %d (%s)\n", ctrl.value,
(ctrl.value >=0 && ctrl.value < ARRAY_SIZE(ov13850_test_pattern_str)) ?
ov13850_test_pattern_str[ctrl.value] : "Unknown");
return ctrl.value;
}
int main(int argc, char *argv[]) {
if (argc != 3) {
fprintf(stderr, "Usage: %s <video_dev> <test_pattern_value>\n", argv[0]);
fprintf(stderr, "Example: %s /dev/video0 1 (set to Vertical Color Bar Type 1)\n", argv[0]);
return -1;
}
// 1. 打开视频设备
int fd = open(argv[1], O_RDWR);
if (fd < 0) {
perror("open video dev failed");
return -1;
}
// 2. 查询测试模式的menu选项
if (query_test_pattern_menu(fd) < 0) {
close(fd);
return -1;
}
// 3. 设置测试模式(参数2为要设置的值)
int target_value = atoi(argv[2]);
if (set_test_pattern(fd, target_value) < 0) {
close(fd);
return -1;
}
// 4. 读取并验证设置结果
get_test_pattern(fd);
// 5. 关闭设备
close(fd);
return 0;
}
///
root@ATK-DLRK3568:/data# ./v4l2TestPt /dev/video0 3
==== Test Pattern Control Info ====
Name: Test Pattern
Min: 0, Max: 4, Default: 0
Menu Options:
0: Disabled
1: Vertical Color Bar Type 1
2: Vertical Color Bar Type 2
3: Vertical Color Bar Type 3
4: Vertical Color Bar Type 4
====================================
Set test pattern to: 3 (Vertical Color Bar Type 3)
Current test pattern: 3 (Vertical Color Bar Type 3)
open camera
//////////////////////////////////////////////////////////////////////////
这块注意到v4l2_ioctls里面其实是带有debug函数的.
(gdb) p v4l2_ioctls
$8 = {{ioctl = 2154321408, flags = 0, name = 0xffffff8009201eed "VIDIOC_QUERYCAP",
func = 0xffffff800898da1c <v4l_querycap>, debug = 0xffffff800898cd3c <v4l_print_querycap>}, {ioctl = 0, flags = 0,
name = 0x0, func = 0x0, debug = 0x0}, {ioctl = 3225441794, flags = 524288,
name = 0xffffff8009201efd "VIDIOC_ENUM_FMT", func = 0xffffff800898f950 <v4l_enum_fmt>,
debug = 0xffffff800898ccb4 <v4l_print_fmtdesc>}, {ioctl = 0, flags = 0, name = 0x0, func = 0x0, debug = 0x0}, {
//开启的条件如下
drivers/media/v4l2-core/v4l2-ioctl.c
static long __video_do_ioctl(struct file *file,
unsigned int cmd, void *arg)
{
...
ret = info->func(ops, file, fh, arg);
...
info->debug(arg, write_only); // enable debug when dev_debug !=0
}
// sys 子系统下需要kernel开启debug模式
root@ATK-DLRK3568:/# cat /sys/class/video4linux/video0/dev_debug
0
drivers/media/v4l2-core/v4l2-dev.c
static ssize_t dev_debug_store(struct device *cd, struct device_attribute *attr,
const char *buf, size_t len)
{
struct video_device *vdev = to_video_device(cd);
int res = 0;
u16 value;
res = kstrtou16(buf, 0, &value);
if (res)
return res;
vdev->dev_debug = value;
return len;
}