4. 实现屏幕的填充和清屏功能

任务说明

在帧缓冲驱动中实现两个自定义 ioctl 命令:FBIO_FILL(用指定颜色填充整个屏幕)和 FBIO_CLEAR(清屏,等效于填充黑色)。通过用户态测试程序调用这些命令,验证驱动的基本功能。

环境与依赖

  • Linux 内核源码树(本案例基于 5.10+,接口兼容即可)
  • 帧缓冲设备框架(linux/fb.h、linux/uaccess.h)
  • 测试程序编译需要 linux/fb.h(用户态头文件)

驱动实现

以下代码实现了一个最小帧缓冲驱动,注册 fb_info,在 fb_ioctl 中处理自定义命令。填充/清屏操作直接操作 screen_base 指向的视频内存。

// fb_fill_clear.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fb.h>
#include <linux/uaccess.h>
#include <linux/platform_device.h>

#define FBIO_FILL   _IOW('F', 0x21, unsigned int)  // 填充颜色值
#define FBIO_CLEAR  _IO('F', 0x22)                 // 清屏(填黑)

static struct fb_info *fb_info;
static void *screen_base;
static unsigned long screen_size;

static int myfb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg)
{
    unsigned int color;

    switch (cmd) {
    case FBIO_FILL:
        if (copy_from_user(&color, (void __user *)arg, sizeof(color)))
            return -EFAULT;
        memset(screen_base, color, screen_size);
        break;
    case FBIO_CLEAR:
        memset(screen_base, 0, screen_size);
        break;
    default:
        return -ENOTTY;
    }
    return 0;
}

static int myfb_probe(struct platform_device *pdev)
{
    // 分配 1MB 视频内存(演示用)
    screen_size = SZ_1M;
    screen_base = kzalloc(screen_size, GFP_KERNEL);
    if (!screen_base)
        return -ENOMEM;

    fb_info = framebuffer_alloc(0, &pdev->dev);
    if (!fb_info) {
        kfree(screen_base);
        return -ENOMEM;
    }

    fb_info->screen_base = screen_base;
    fb_info->screen_size = screen_size;
    fb_info->fbops = &myfb_ops;
    fb_info->var.xres = 800;
    fb_info->var.yres = 600;
    fb_info->var.bits_per_pixel = 32;
    fb_info->fix.smem_start = virt_to_phys(screen_base);
    fb_info->fix.smem_len = screen_size;
    strcpy(fb_info->fix.id, "myfb");

    if (register_framebuffer(fb_info) < 0) {
        framebuffer_release(fb_info);
        kfree(screen_base);
        return -EINVAL;
    }
    return 0;
}

static int myfb_remove(struct platform_device *pdev)
{
    unregister_framebuffer(fb_info);
    framebuffer_release(fb_info);
    kfree(screen_base);
    return 0;
}

static struct platform_device *myfb_device;
static struct platform_driver myfb_driver = {
    .probe  = myfb_probe,
    .remove = myfb_remove,
    .driver = {
        .name = "myfb",
    },
};

static struct fb_ops myfb_ops = {
    .owner        = THIS_MODULE,
    .fb_read      = fb_sys_read,
    .fb_write     = fb_sys_write,
    .fb_fillrect  = sys_fillrect,
    .fb_copyarea  = sys_copyarea,
    .fb_imageblit = sys_imageblit,
    .fb_ioctl     = myfb_ioctl,
};

static int __init myfb_init(void)
{
    myfb_device = platform_device_register_simple("myfb", -1, NULL, 0);
    return platform_driver_register(&myfb_driver);
}

static void __exit myfb_exit(void)
{
    platform_driver_unregister(&myfb_driver);
    platform_device_unregister(myfb_device);
}

module_init(myfb_init);
module_exit(myfb_exit);
MODULE_LICENSE("GPL");

用户态测试程序

// test_fb.c
#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
#include <unistd.h>

#define FBIO_FILL   _IOW('F', 0x21, unsigned int)
#define FBIO_CLEAR  _IO('F', 0x22)

int main(void)
{
    int fd = open("/dev/fb0", O_RDWR);
    if (fd < 0) { perror("open"); return 1; }

    unsigned int red = 0x00FF0000;  // 红色(小端:B=0, G=0, R=255)
    if (ioctl(fd, FBIO_FILL, &red) < 0) { perror("fill"); return 1; }
    printf("Fill red: OK\n");

    if (ioctl(fd, FBIO_CLEAR) < 0) { perror("clear"); return 1; }
    printf("Clear: OK\n");

    close(fd);
    return 0;
}

编译与运行

  1. 编译驱动(内核树内):将驱动源码放入 drivers/video/fbdev/,在 Kconfig 中添加配置项,然后:
make menuconfig  # 启用 CONFIG_FB_MYFB
make drivers/video/fbdev/
  1. 加载模块:
insmod fb_fill_clear.ko
  1. 编译并运行测试程序:
gcc -o test_fb test_fb.c
./test_fb

验证方法

  • 测试程序输出 Fill red: OK 和 Clear: OK,说明 ioctl 命令被驱动正确接收并执行。
  • 在驱动加载后,通过 cat /dev/fb0 | hexdump -C | head 可观察填充后视频内存首字节为 00 00 FF 00(红色),清屏后变为全 00。
  • 若系统有真实显示设备连接,填充红色时屏幕应变为红色,清屏后变黑。

常见问题

  • ioctl 返回 -ENOTTY:确认命令码与驱动中定义一致,且 fb_ioctl 回调已正确注册。
  • 填充颜色与预期不符:检查 bits_per_pixel 和颜色字节序。32bpp 下小端格式为 0x00RRGGBB。
  • 屏幕无变化:确认 screen_base 指向的内存确实映射到显示控制器(真实硬件需额外配置 DMA 地址,本案例为演示分配内存)。