3. 帧缓冲驱动 ioctl 框架:fb_ops 结构体、fb_ioctl 函数指针、默认 ioctl 处理流程

帧缓冲驱动的 ioctl 接口是用户态与内核态交互的核心通道。Linux 内核通过 fb_ops 结构体抽象了帧缓冲设备的全部操作,其中 fb_ioctl 函数指针是自定义控制命令的入口。理解这一框架是扩展私有 ioctl 命令的前提。

3.1 fb_ops 结构体:帧缓冲操作集

fb_ops 定义在 include/linux/fb.h 中,是帧缓冲驱动的“虚函数表”。驱动开发者通过填充该结构体向内核注册设备操作。核心成员如下:

成员 类型 说明
fb_open int (*)(struct fb_info *, int) 设备打开回调,可做资源初始化
fb_release int (*)(struct fb_info *, int) 设备关闭回调,清理资源
fb_read ssize_t (*)(struct fb_info *, char __user *, size_t, loff_t *) 从帧缓冲读取数据
fb_write ssize_t (*)(struct fb_info *, const char __user *, size_t, loff_t *) 向帧缓冲写入数据
fb_check_var int (*)(struct fb_var_screeninfo *, struct fb_info *) 校验并调整可变参数(分辨率、位深等)
fb_set_par int (*)(struct fb_info *) 根据当前可变参数设置硬件寄存器
fb_setcolreg int (*)(unsigned, unsigned, unsigned, unsigned, unsigned, struct fb_info *) 设置颜色寄存器(伪彩色模式)
fb_blank int (*)(int, struct fb_info *) 控制屏幕开关/节能
fb_ioctl int (*)(struct fb_info *, unsigned int, unsigned long) 自定义 ioctl 命令处理入口
fb_mmap int (*)(struct fb_info *, struct vm_area_struct *) 帧缓冲内存映射
关键点: fb_ioctl 是帧缓冲驱动扩展私有命令的唯一标准接口。所有未被内核默认 ioctl 处理的自定义命令,最终都会路由至此函数。

3.2 fb_ioctl 函数指针:自定义命令的入口

fb_ioctl 的原型为:

int (*fb_ioctl)(struct fb_info *info, unsigned int cmd, unsigned long arg);
  • info:指向当前帧缓冲设备实例的 fb_info 结构体,包含硬件参数、内存地址、操作集等。
  • cmd:用户态传入的 ioctl 命令码,由 _IO/_IOR/_IOW/_IOWR 宏构造。
  • arg:用户态传入的参数,通常为指针或整型值。

驱动开发者在 fb_ops 中注册该函数后,内核的帧缓冲核心层会在默认 ioctl 处理流程末尾调用它。典型的实现模式如下:

static int myfb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg)
{
    struct myfb_par *par = info->par;

    switch (cmd) {
    case MYFB_GET_BRIGHTNESS: {
        u32 brightness = par->brightness;
        if (copy_to_user((u32 __user *)arg, &brightness, sizeof(brightness)))
            return -EFAULT;
        return 0;
    }
    case MYFB_SET_BRIGHTNESS: {
        u32 brightness;
        if (copy_from_user(&brightness, (u32 __user *)arg, sizeof(brightness)))
            return -EFAULT;
        if (brightness > 100)
            return -EINVAL;
        par->brightness = brightness;
        /* 调用硬件接口设置背光 */
        return 0;
    }
    default:
        return -ENOTTY;  /* 不支持的命令 */
    }
}
最佳实践: 在 fb_ioctl 中始终使用 copy_from_user/copy_to_user 访问用户态数据,避免直接解引用用户指针。命令码应使用 _IOW/_IOR 宏定义,确保类型和方向正确。

3.3 默认 ioctl 处理流程

当用户态调用 ioctl(fd, cmd, arg) 时,内核帧缓冲核心层 drivers/video/fbdev/core/fbmem.c 中的 fb_ioctl() 函数会按以下顺序处理:

  1. 命令码分类:检查 cmd 是否属于帧缓冲标准命令(如 FBIOGET_VSCREENINFO、FBIOPUT_VSCREENINFO、FBIOGET_FSCREENINFO 等)。
  2. 标准命令处理:若为标准命令,内核直接调用对应的内部逻辑(如更新可变参数、获取固定参数、设置颜色寄存器等)。
  3. 文件系统专属命令:若命令码属于文件系统层(如 FBIOPAN_DISPLAY),由 do_fb_ioctl() 处理。
  4. 驱动自定义命令:若以上均不匹配,内核检查 fb_ops->fb_ioctl 是否注册。若已注册,则调用该函数;否则返回 -ENOTTY。
帧缓冲 ioctl 默认处理流程 用户态 ioctl() 调用 fbmem.c: fb_ioctl() 入口 判断命令码类型 标准命令处理 FBIOGET_VSCREENINFO 等 文件系统专属命令 FBIOPAN_DISPLAY 等 驱动自定义命令 fb_ops->fb_ioctl 返回结果给用户态 若 fb_ioctl 未注册且命令未被识别,内核返回 -ENOTTY(不支持的 ioctl 命令)

3.4 默认 ioctl 处理的关键代码路径

内核 fbmem.c 中的核心处理函数 do_fb_ioctl() 简化逻辑如下:

static long do_fb_ioctl(struct fb_info *info, unsigned int cmd,
                        unsigned long arg)
{
    struct fb_ops *fb = info->fbops;
    struct fb_var_screeninfo var;
    struct fb_fix_screeninfo fix;
    void __user *argp = (void __user *)arg;
    long ret = -ENOTTY;

    switch (cmd) {
    case FBIOGET_VSCREENINFO:
        /* 拷贝可变参数到用户态 */
        ret = copy_to_user(argp, &info->var, sizeof(var)) ? -EFAULT : 0;
        break;
    case FBIOPUT_VSCREENINFO:
        /* 从用户态拷贝可变参数并校验 */
        ret = copy_from_user(&var, argp, sizeof(var)) ? -EFAULT : 0;
        if (!ret) ret = fb->fb_check_var(&var, info);
        if (!ret) { info->var = var; ret = fb->fb_set_par(info); }
        break;
    case FBIOGET_FSCREENINFO:
        /* 拷贝固定参数 */
        ret = copy_to_user(argp, &info->fix, sizeof(fix)) ? -EFAULT : 0;
        break;
    /* ... 其他标准命令 ... */
    default:
        /* 路由到驱动自定义 ioctl */
        if (fb->fb_ioctl)
            ret = fb->fb_ioctl(info, cmd, arg);
        break;
    }
    return ret;
}
注意事项: 驱动自定义 fb_ioctl 中不应处理标准命令(如 FBIOGET_VSCREENINFO),这些命令已被内核默认处理。自定义命令码应使用 _IO('M', base) 形式,其中 base 从 0x80 开始,避免与标准命令冲突。

3.5 自定义命令码定义规范

为避免与内核标准 ioctl 命令冲突,自定义命令码应遵循以下约定:

宏 用途 示例
_IO(type, nr) 无数据传递的命令 #define MYFB_RESET _IO('M', 0x80)
_IOR(type, nr, size) 从驱动读取数据 #define MYFB_GET_TEMP _IOR('M', 0x81, u32)
_IOW(type, nr, size) 向驱动写入数据 #define MYFB_SET_BRIGHT _IOW('M', 0x82, u32)
_IOWR(type, nr, size) 双向数据传递 #define MYFB_GET_CFG _IOWR('M', 0x83, struct myfb_cfg)
推荐做法: 使用 'M' 作为帧缓冲自定义命令的幻数(type),序号从 0x80 开始递增,确保与内核标准命令(0x00-0x7F)不重叠。在驱动头文件中集中定义所有自定义命令码,并添加注释说明每个命令的用途和参数结构。

3.6 本章小结

  • fb_ops 是帧缓冲驱动的操作集,fb_ioctl 是自定义命令的唯一入口。
  • 内核默认 ioctl 处理流程按“标准命令 → 文件系统命令 → 驱动自定义命令”顺序执行。
  • 自定义 fb_ioctl 中应使用 switch-case 结构处理私有命令,不支持的命令返回 -ENOTTY。
  • 命令码定义需遵循 _IO/_IOR/_IOW/_IOWR 宏规范,避免与内核标准命令冲突。
  • 用户态数据访问必须使用 copy_from_user/copy_to_user,确保安全性和可移植性。