Linux V4L2 源码分析

    科技2025-04-09  12

    Linux V4L2 源码分析

    前言层次必要的数据结构源码分析ov2640.c

    前言

    Video For Linux 2真的是一个很复杂的框架,抽象倒不是它复杂的原因,是因为耦合了其他框架的内容,导致要掌握V4L2必须得需要一个非常广的内核层知识面,以及Linux抽象思想。这篇文章将会以ov2640.c为例子进行源码分析,我会尽量从上往下,也就是用户层调用到内核层进行分析。 注:在Linux内核层学习,你得学会适应庞大代码给你带来的不安感。

    层次

    首先,像OV2640这样类似的摄像头传感器,是由多部分组成的,需要I2C作为控制,MIPI-CSI作为数据传输。 那么在V4L2的框架里,V4L2_device就是OV2640设备,而V4L2_device负责管理V4L2_subdev,V4L2_subdev分别对于I2C和MIPI-CSI硬件设备。

    必要的数据结构

    video_device是负责和用户层对接的数据结构,/dev/videoX和/dev/subdevX都是使用video_device及相关函数向用户空间开放用户接口(字符设备)。

    struct video_device { #if defined(CONFIG_MEDIA_CONTROLLER) struct media_entity entity; struct media_intf_devnode *intf_devnode; struct media_pipeline pipe; #endif const struct v4l2_file_operations *fops; u32 device_caps; /* sysfs */ struct device dev; struct cdev *cdev; struct v4l2_device *v4l2_dev; struct device *dev_parent; struct v4l2_ctrl_handler *ctrl_handler; struct vb2_queue *queue; struct v4l2_prio_state *prio; /* device info */ char name[32]; enum vfl_devnode_type vfl_type; enum vfl_devnode_direction vfl_dir; int minor; u16 num; unsigned long flags; int index; /* V4L2 file handles */ spinlock_t fh_lock; struct list_head fh_list; int dev_debug; v4l2_std_id tvnorms; /* callbacks */ void (*release)(struct video_device *vdev); const struct v4l2_ioctl_ops *ioctl_ops; DECLARE_BITMAP(valid_ioctls, BASE_VIDIOC_PRIVATE); struct mutex *lock; };

    v4l2_device 是用于管理全体子设备subdevs的数据结构。

    struct v4l2_device { struct device *dev; struct media_device *mdev; struct list_head subdevs; spinlock_t lock; char name[V4L2_DEVICE_NAME_SIZE]; void (*notify)(struct v4l2_subdev *sd, unsigned int notification, void *arg); struct v4l2_ctrl_handler *ctrl_handler; struct v4l2_prio_state prio; struct kref ref; void (*release)(struct v4l2_device *v4l2_dev); };

    v4l2_subdev是代表子设备的数据结构,I2C和MIPI,ISP都应该有一个subdev

    struct v4l2_subdev { #if defined(CONFIG_MEDIA_CONTROLLER) struct media_entity entity; #endif struct list_head list; struct module *owner; bool owner_v4l2_dev; u32 flags; struct v4l2_device *v4l2_dev; const struct v4l2_subdev_ops *ops; const struct v4l2_subdev_internal_ops *internal_ops; struct v4l2_ctrl_handler *ctrl_handler; char name[V4L2_SUBDEV_NAME_SIZE]; u32 grp_id; void *dev_priv; void *host_priv; struct video_device *devnode; struct device *dev; struct fwnode_handle *fwnode; struct list_head async_list; struct v4l2_async_subdev *asd; struct v4l2_async_notifier *notifier; struct v4l2_async_notifier *subdev_notifier; struct v4l2_subdev_platform_data *pdata; };

    代表数据流,不是实体设备。

    struct media_device { /* dev->driver_data points to this struct. */ struct device *dev; struct media_devnode *devnode; char model[32]; char driver_name[32]; char serial[40]; char bus_info[32]; u32 hw_revision; u64 topology_version; u32 id; struct ida entity_internal_idx; int entity_internal_idx_max; struct list_head entities; //存储pad的链表 struct list_head interfaces; struct list_head pads; struct list_head links; /* notify callback list invoked when a new entity is registered */ struct list_head entity_notify; /* Serializes graph operations. */ struct mutex graph_mutex; struct media_graph pm_count_walk; void *source_priv; int (*enable_source)(struct media_entity *entity, struct media_pipeline *pipe); void (*disable_source)(struct media_entity *entity); const struct media_device_ops *ops; struct mutex req_queue_mutex; atomic_t request_id; };

    源码分析ov2640.c

    直接看Probe函数:

    static int ov2640_probe(struct i2c_client *client, const struct i2c_device_id *did) { struct ov2640_priv *priv; 私有数据 struct i2c_adapter *adapter = client->adapter; 获取I2C控制器 int ret; if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { dev_err(&adapter->dev, "OV2640: I2C-Adapter doesn't support SMBUS\n"); return -EIO; } priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; if (client->dev.of_node) { 如果设备树有解析 priv->clk = devm_clk_get(&client->dev, "xvclk"); 系统时钟输入 if (IS_ERR(priv->clk)) return PTR_ERR(priv->clk); ret = clk_prepare_enable(priv->clk); 启动时钟 if (ret) return ret; } ret = ov2640_probe_dt(client, priv); 从设备树中查找指定GPIO,找不到报错 if (ret) goto err_clk; priv->win = ov2640_select_win(SVGA_WIDTH, SVGA_HEIGHT); priv->cfmt_code = MEDIA_BUS_FMT_UYVY8_2X8;

    前面大概做了些用于OV2640的数据填充工作。

    v4l2_i2c_subdev_init(&priv->subdev, client, &ov2640_subdev_ops); //初始化subdev,绑定subdev I2C,操作函数 priv->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | 子节点需要设备节点 V4L2_SUBDEV_FL_HAS_EVENTS; 子节点有事件发送

    v4l2_i2c_subdev_init初始化subdev里面的数据,将ops赋值给subdev。

    v4l2_ctrl_handler_init(&priv->hdl, 3); 分配空间 priv->hdl.lock = &priv->lock; v4l2_ctrl_new_std(&priv->hdl, &ov2640_ctrl_ops, V4L2_CID_VFLIP, 0, 1, 1, 0); 添加非菜单控件,用户在使用ioctl时候会调用的函数 V4L2_CID_VFLIP为属性 v4l2_ctrl_new_std(&priv->hdl, &ov2640_ctrl_ops, V4L2_CID_HFLIP, 0, 1, 1, 0); v4l2_ctrl_new_std_menu_items(&priv->hdl, &ov2640_ctrl_ops, V4L2_CID_TEST_PATTERN, ARRAY_SIZE(ov2640_test_pattern_menu) - 1, 0, 0, ov2640_test_pattern_menu); priv->subdev.ctrl_handler = &priv->hdl; 给subdev里添加控件(用户空间使用) if (priv->hdl.error) { ret = priv->hdl.error; goto err_hdl; }

    这里是一些Ctrl的设置

    ret = ov2640_video_probe(client); if (ret < 0) goto err_videoprobe; ret = v4l2_async_register_subdev(&priv->subdev);//注册subdev if (ret < 0) goto err_videoprobe; dev_info(&adapter->dev, "OV2640 Probed\n"); return 0;

    设置好subdev的相关信息后,通知sun4i_csi模块异步注册video_device.

    Processed: 0.008, SQL: 8