Linux V4L2 源码分析
前言
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.