4. 实现Camera设备的IOCTL接口框架
任务说明
本案例为Linux内核V4L2摄像头驱动实现ioctl接口框架。目标是为Camera设备定义ioctl命令集,并实现基本命令分发,包括查询设备能力(QUERYCAP)与设置图像格式(S/FMT)。通过本案例,你将得到一个可直接编译运行的最小字符设备驱动,支持用户空间通过标准ioctl调用完成设备交互。
环境与依赖
- Linux内核源码树(建议5.10+),已配置内核头文件
- 内核模块编译工具(make, gcc)
- 开发板或虚拟机,具备root权限加载模块
核心实现
定义三个ioctl命令:查询驱动能力、获取当前格式、设置新格式。命令编号使用V4L2标准宏,确保与用户空间头文件<linux/videodev2.h>兼容。驱动内部维护一个格式状态结构,通过互斥锁保护并发访问。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/mutex.h>
#include <linux/videodev2.h>
#include <linux/slab.h>
#define CAMERA_DEV_NAME "camera_ioctl"
#define CAMERA_CLASS_NAME "camera_class"
static dev_t camera_dev_num;
static struct cdev camera_cdev;
static struct class *camera_class;
static struct device *camera_device;
struct camera_format {
__u32 width;
__u32 height;
__u32 pixelformat;
};
static struct camera_format current_fmt = {
.width = 640,
.height = 480,
.pixelformat = V4L2_PIX_FMT_YUYV,
};
static DEFINE_MUTEX(fmt_lock);
static int camera_open(struct inode *inode, struct file *file)
{
return 0;
}
static int camera_release(struct inode *inode, struct file *file)
{
return 0;
}
static long camera_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
void __user *user_arg = (void __user *)arg;
int ret = 0;
switch (cmd) {
case VIDIOC_QUERYCAP: {
struct v4l2_capability cap = {0};
strscpy(cap.driver, "camera_ioctl_drv", sizeof(cap.driver));
strscpy(cap.card, "Camera IOCTL Demo", sizeof(cap.card));
strscpy(cap.bus_info, "platform:demo", sizeof(cap.bus_info));
cap.version = KERNEL_VERSION(1, 0, 0);
cap.capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
if (copy_to_user(user_arg, &cap, sizeof(cap)))
ret = -EFAULT;
break;
}
case VIDIOC_G_FMT: {
struct v4l2_format vfmt = {0};
vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
vfmt.fmt.pix.width = current_fmt.width;
vfmt.fmt.pix.height = current_fmt.height;
vfmt.fmt.pix.pixelformat = current_fmt.pixelformat;
vfmt.fmt.pix.field = V4L2_FIELD_NONE;
vfmt.fmt.pix.bytesperline = current_fmt.width * 2;
vfmt.fmt.pix.sizeimage = current_fmt.width * current_fmt.height * 2;
if (copy_to_user(user_arg, &vfmt, sizeof(vfmt)))
ret = -EFAULT;
break;
}
case VIDIOC_S_FMT: {
struct v4l2_format vfmt;
if (copy_from_user(&vfmt, user_arg, sizeof(vfmt))) {
ret = -EFAULT;
break;
}
if (vfmt.type != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
ret = -EINVAL;
break;
}
mutex_lock(&fmt_lock);
current_fmt.width = vfmt.fmt.pix.width;
current_fmt.height = vfmt.fmt.pix.height;
current_fmt.pixelformat = vfmt.fmt.pix.pixelformat;
mutex_unlock(&fmt_lock);
vfmt.fmt.pix.bytesperline = current_fmt.width * 2;
vfmt.fmt.pix.sizeimage = current_fmt.width * current_fmt.height * 2;
vfmt.fmt.pix.field = V4L2_FIELD_NONE;
if (copy_to_user(user_arg, &vfmt, sizeof(vfmt)))
ret = -EFAULT;
break;
}
default:
ret = -ENOTTY;
break;
}
return ret;
}
static struct file_operations camera_fops = {
.owner = THIS_MODULE,
.open = camera_open,
.release = camera_release,
.unlocked_ioctl = camera_ioctl,
};
static int __init camera_init(void)
{
int ret;
ret = alloc_chrdev_region(&camera_dev_num, 0, 1, CAMERA_DEV_NAME);
if (ret < 0)
return ret;
cdev_init(&camera_cdev, &camera_fops);
ret = cdev_add(&camera_cdev, camera_dev_num, 1);
if (ret)
goto err_cdev;
camera_class = class_create(THIS_MODULE, CAMERA_CLASS_NAME);
if (IS_ERR(camera_class)) {
ret = PTR_ERR(camera_class);
goto err_class;
}
camera_device = device_create(camera_class, NULL, camera_dev_num,
NULL, CAMERA_DEV_NAME);
if (IS_ERR(camera_device)) {
ret = PTR_ERR(camera_device);
goto err_device;
}
pr_info("camera_ioctl: initialized (major=%d, minor=%d)\n",
MAJOR(camera_dev_num), MINOR(camera_dev_num));
return 0;
err_device:
class_destroy(camera_class);
err_class:
cdev_del(&camera_cdev);
err_cdev:
unregister_chrdev_region(camera_dev_num, 1);
return ret;
}
static void __exit camera_exit(void)
{
device_destroy(camera_class, camera_dev_num);
class_destroy(camera_class);
cdev_del(&camera_cdev);
unregister_chrdev_region(camera_dev_num, 1);
pr_info("camera_ioctl: exited\n");
}
module_init(camera_init);
module_exit(camera_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kernel Camera Course");
MODULE_DESCRIPTION("Camera device ioctl interface framework");
编译与加载
# Makefile
obj-m += camera_ioctl.o
# 编译命令
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
# 加载模块
sudo insmod camera_ioctl.ko
# 查看设备节点(应生成 /dev/camera_ioctl)
ls -l /dev/camera_ioctl
验证方法
编写用户空间测试程序,依次执行以下操作:
- 打开
/dev/camera_ioctl设备节点 - 调用
VIDIOC_QUERYCAP,检查返回的driver字段是否为camera_ioctl_drv - 调用
VIDIOC_S_FMT设置格式为1280x720,像素格式V4L2_PIX_FMT_NV12 - 调用
VIDIOC_G_FMT读取当前格式,确认宽高与像素格式与设置一致
通过条件:所有ioctl调用返回0,且G_FMT读回的值与S_FMT设置的值完全匹配。若返回-ENOTTY,说明命令分发异常;若返回-EFAULT,说明用户空间地址访问错误。
常见问题
- 设备节点未生成:检查
dmesg输出,确认模块加载时是否报错,以及/dev目录权限。 - ioctl返回-ENOTTY:确认用户空间头文件版本与内核命令编号一致,避免
_IOR/_IOW类型不匹配。 - 并发访问冲突:S_FMT与G_FMT同时调用时,互斥锁确保格式状态一致性,测试时可多线程验证。