目录
[二、mxsfb.c 分析](#二、mxsfb.c 分析)
[1、fbmem.c 文件:搭建了LCD驱动框架](#1、fbmem.c 文件:搭建了LCD驱动框架)
[2、mxsfb.c 文件:底层硬件层](#2、mxsfb.c 文件:底层硬件层)
[四、应用层如何使用 /dev/fb0](#四、应用层如何使用 /dev/fb0)
简介:
Framebuffer(帧缓冲)是Linux系统中为显示设备提供的一套应用程序接口,它将显存抽象为一种设备,允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作。

-
核心层( fbmem.c**):** 主要实现字符设备的创建,为不同的显示设备提供文件通用处理接口;同时创建graphics设备类,占据主设备号29。内核自带,无需自己开发。
-
硬件设备层( mxsfb.c**):** 主要提供显示设备的时序、显存、像素格式等硬件信息,实现显示设备的私有文件接口,并创建显示设备文件/dev/fbx(x=0~n)暴露给用户空间。 硬件设备层的代码需要驱动开发人员根据具体的显示设备完成开发。
一、fbmem.c分析
FrameBuffer是抽象出来的通用的 LCD驱动框架程序,它依赖底层LCD驱动调用 register_framebuffer() 注册 "fb_info" 结构体,之后会生成 /dev/fb0 字符设备,/dev/fb0 的file_operations 操作集就定义在 drivers/video/fbdev/core/fbmem.c 文件中。fbmem.c 为所有支持FrameBuffer的设备驱动提供通用的接口,避免重复工作。
fbmem.c 处于Framebuffer设备驱动核心层,它为上层应用程序提供系统调用,也为下一层的特定硬件驱动提供接口。
fbmem.c核心代码如下:
cpp
/* 路径:drivers/video/fbdev/core/fbmem.c */
/* procfs操作集 */
static const struct file_operations fb_proc_fops = {
.owner = THIS_MODULE,
.open = proc_fb_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
};
/* file_operations 操作集 */
static const struct file_operations fb_fops = {
.owner = THIS_MODULE,
.read = fb_read,
.write = fb_write,
.unlocked_ioctl = fb_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = fb_compat_ioctl,
#endif
.mmap = fb_mmap,
.open = fb_open,
.release = fb_release,
#ifdef HAVE_ARCH_FB_UNMAPPED_AREA
.get_unmapped_area = get_fb_unmapped_area,
#endif
#ifdef CONFIG_FB_DEFERRED_IO
.fsync = fb_deferred_io_fsync,
#endif
.llseek = default_llseek,
};
static int __init
fbmem_init(void)
{
/* 创建 /proc/fb 文件,并设置文件操作集 */
proc_create("fb", 0, NULL, &fb_proc_fops);
/* 注册字符设备,fb_fops提供系统调用 write、read、ioctl 等 */
if (register_chrdev(FB_MAJOR,"fb",&fb_fops))
printk("unable to get major %d for fb devs\n", FB_MAJOR);
/* 自动创建设备节点:创建类,那在哪里创建设备的? */
fb_class = class_create(THIS_MODULE, "graphics");
if (IS_ERR(fb_class)) {
printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class));
fb_class = NULL;
}
return 0;
}
subsys_initcall(fbmem_init);
fbmem_init() 中做了3件事:
1)创建 /proc/fb 文件操作接口;
2)register_chrdev 注册字符设备驱动生成,提供 fb_fops 操作集;
3)创建类 "graphics"(没有创建设备)
此时并未生成 /dev 下的设备节点,因为没有创建设备,那么在哪里创建设备呢?继续往下看。
二、mxsfb.c 分析
下面分析 i.MX 6ULL 开发板的 LCD 驱动框架。
mxsfb.c 是NXP官方提供的 i.MX 6ULL 芯片针对一款 LCD 的驱动程序,它定义在 drivers/video/fbdev/mxsfb.c。当 of_match_table 与 imx6ull.dtsi 设备树文件中的compatible 属性值 "fsl,imx28-lcdif" 匹配时,则调用 mxsfb_probe 函数,该函数初始化并配置LCDIF控制器。
cpp
lcdif: lcdif@021c8000 {
compatible = "fsl,imx6ul-lcdif", "fsl,imx28-lcdif";
reg = <0x021c8000 0x4000>;
interrupts = <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_LCDIF_PIX>,
<&clks IMX6UL_CLK_LCDIF_APB>,
<&clks IMX6UL_CLK_DUMMY>;
clock-names = "pix", "axi", "disp_axi";
status = "okay";
};
&lcdif {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_lcdif_dat /* 使用到的 IO */
&pinctrl_lcdif_ctrl>;
display = <&display0>;
status = "okay";
display0: display { /* LCD 属性信息 */
bits-per-pixel = <24>; /* 一个像素占用 24bit */
bus-width = <24>; /* 总线宽度 */
display-timings {
native-mode = <&timing0>; /* 时序信息 */
timing0: timing0 {
clock-frequency = <51200000>; /* LCD 像素时钟,单位 Hz */
hactive = <1024>; /* LCD X 轴像素个数 */
vactive = <600>; /* LCD Y 轴像素个数 */
hfront-porch = <160>; /* LCD hfp 参数 */
hback-porch = <140>; /* LCD hbp 参数 */
hsync-len = <20>; /* LCD hspw 参数 */
vback-porch = <20>; /* LCD vbp 参数 */
vfront-porch = <12>; /* LCD vfp 参数 */
vsync-len = <3>; /* LCD vspw 参数 */
hsync-active = <0>; /* hsync 数据线极性 */
vsync-active = <0>; /* vsync 数据线极性 */
de-active = <1>; /* de 数据线极性 */
pixelclk-active = <0>; /* clk 数据线先极性 */
};
};
};
};
mxsfb.c 驱动代码分析:
cpp
static const struct of_device_id mxsfb_dt_ids[] = {
{ .compatible = "fsl,imx23-lcdif", .data = &mxsfb_devtype[0], },
{ .compatible = "fsl,imx28-lcdif", .data = &mxsfb_devtype[1], },
{ /* sentinel */ }
};
static struct platform_driver mxsfb_driver = {
.probe = mxsfb_probe,
.remove = mxsfb_remove,
.shutdown = mxsfb_shutdown,
.id_table = mxsfb_devtype,
.driver = {
.name = DRIVER_NAME,
.of_match_table = mxsfb_dt_ids,
.pm = &mxsfb_pm_ops,
},
};
module_platform_driver(mxsfb_driver);
mxsfb_probe() 函数主要功能:
1)framebuffer_alloc()分配一个fb_info
2)设置fb_info,如:fb_info->fbops = &mxsfb_ops; 设置操作集
3)硬件相关寄存器配置
4)register_framebuffer()注册fb_info
cpp
static int mxsfb_probe(struct platform_device *pdev)
{
const struct of_device_id *of_id =
of_match_device(mxsfb_dt_ids, &pdev->dev);
struct resource *res;
struct mxsfb_info *host;
struct fb_info *fb_info;
struct pinctrl *pinctrl;
int irq = platform_get_irq(pdev, 0);
int gpio, ret;
if (of_id)
pdev->id_entry = of_id->data;
gpio = of_get_named_gpio(pdev->dev.of_node, "enable-gpio", 0);
if (gpio == -EPROBE_DEFER)
return -EPROBE_DEFER;
if (gpio_is_valid(gpio)) {
ret = devm_gpio_request_one(&pdev->dev, gpio, GPIOF_OUT_INIT_LOW, "lcd_pwr_en");
if (ret) {
dev_err(&pdev->dev, "faild to request gpio %d, ret = %d\n", gpio, ret);
return ret;
}
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "Cannot get memory IO resource\n");
return -ENODEV;
}
host = devm_kzalloc(&pdev->dev, sizeof(struct mxsfb_info), GFP_KERNEL);
if (!host) {
dev_err(&pdev->dev, "Failed to allocate IO resource\n");
return -ENOMEM;
}
fb_info = framebuffer_alloc(sizeof(struct fb_info), &pdev->dev);
if (!fb_info) {
dev_err(&pdev->dev, "Failed to allocate fbdev\n");
devm_kfree(&pdev->dev, host);
return -ENOMEM;
}
host->fb_info = fb_info;
fb_info->par = host;
ret = devm_request_irq(&pdev->dev, irq, mxsfb_irq_handler, 0,
dev_name(&pdev->dev), host);
if (ret) {
dev_err(&pdev->dev, "request_irq (%d) failed with error %d\n",
irq, ret);
ret = -ENODEV;
goto fb_release;
}
host->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(host->base)) {
dev_err(&pdev->dev, "ioremap failed\n");
ret = PTR_ERR(host->base);
goto fb_release;
}
host->pdev = pdev;
platform_set_drvdata(pdev, host);
host->devdata = &mxsfb_devdata[pdev->id_entry->driver_data];
host->clk_pix = devm_clk_get(&host->pdev->dev, "pix");
if (IS_ERR(host->clk_pix)) {
host->clk_pix = NULL;
ret = PTR_ERR(host->clk_pix);
goto fb_release;
}
host->clk_axi = devm_clk_get(&host->pdev->dev, "axi");
if (IS_ERR(host->clk_axi)) {
host->clk_axi = NULL;
ret = PTR_ERR(host->clk_axi);
goto fb_release;
}
host->clk_disp_axi = devm_clk_get(&host->pdev->dev, "disp_axi");
if (IS_ERR(host->clk_disp_axi)) {
host->clk_disp_axi = NULL;
ret = PTR_ERR(host->clk_disp_axi);
goto fb_release;
}
host->reg_lcd = devm_regulator_get(&pdev->dev, "lcd");
if (IS_ERR(host->reg_lcd))
host->reg_lcd = NULL;
fb_info->pseudo_palette = devm_kzalloc(&pdev->dev, sizeof(u32) * 16,
GFP_KERNEL);
if (!fb_info->pseudo_palette) {
ret = -ENOMEM;
goto fb_release;
}
INIT_LIST_HEAD(&fb_info->modelist);
pm_runtime_enable(&host->pdev->dev);
/* fb_info->fbops = &mxsfb_ops; 设置fb_ops操作集 */
ret = mxsfb_init_fbinfo(host);
if (ret != 0)
goto fb_pm_runtime_disable;
mxsfb_dispdrv_init(pdev, fb_info);
if (!host->dispdrv) {
pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
if (IS_ERR(pinctrl)) {
ret = PTR_ERR(pinctrl);
goto fb_pm_runtime_disable;
}
}
if (!host->enabled) {
writel(0, host->base + LCDC_CTRL);
mxsfb_set_par(fb_info);
mxsfb_enable_controller(fb_info);
pm_runtime_get_sync(&host->pdev->dev);
}
ret = register_framebuffer(fb_info);
if (ret != 0) {
dev_err(&pdev->dev, "Failed to register framebuffer\n");
goto fb_destroy;
}
console_lock();
ret = fb_blank(fb_info, FB_BLANK_UNBLANK);
console_unlock();
if (ret < 0) {
dev_err(&pdev->dev, "Failed to unblank framebuffer\n");
goto fb_unregister;
}
dev_info(&pdev->dev, "initialized\n");
return 0;
fb_unregister:
unregister_framebuffer(fb_info);
fb_destroy:
if (host->enabled)
clk_disable_unprepare(host->clk_pix);
fb_destroy_modelist(&fb_info->modelist);
fb_pm_runtime_disable:
pm_runtime_disable(&host->pdev->dev);
devm_kfree(&pdev->dev, fb_info->pseudo_palette);
fb_release:
framebuffer_release(fb_info);
devm_kfree(&pdev->dev, host);
return ret;
}
先看下 struct fb_info,这个结构体非常重要
cpp
struct fb_info {
atomic_t count; //打开计数器
int node; //文件node节点,存放有设备信息
int flags; //标志信息
struct mutex lock; /* Lock for open/release/ioctl funcs */
struct mutex mm_lock; /* Lock for fb_mmap and smem_* fields */
struct fb_var_screeninfo var; /* Current var 可变的参数*/
struct fb_fix_screeninfo fix; /* Current fix 固定的参数*/
struct fb_monspecs monspecs; /* Current Monitor specs */
struct work_struct queue; /* Framebuffer event queue 事件等待队列*/
struct fb_pixmap pixmap; /* Image hardware mapper */
struct fb_pixmap sprite; /* Cursor hardware mapper */
struct fb_cmap cmap; /* Current cmap */
struct list_head modelist; /* mode list */
struct fb_videomode *mode; /* current mode */
#ifdef CONFIG_FB_BACKLIGHT
/* assigned backlight device */
/* set before framebuffer registration,
remove after unregister */
struct backlight_device *bl_dev; //背光设备
/* Backlight level curve */
struct mutex bl_curve_mutex;
u8 bl_curve[FB_BACKLIGHT_LEVELS]; //背光等级
#endif
#ifdef CONFIG_FB_DEFERRED_IO
struct delayed_work deferred_work;
struct fb_deferred_io *fbdefio;
#endif
struct fb_ops *fbops; // 设备操作函数指针集合
struct device *device; /* This is the parent 指向父设备*/
struct device *dev; /* This is this fb device 指向当前设备*/
int class_flag; /* private sysfs flags */
#ifdef CONFIG_FB_TILEBLITTING
struct fb_tile_ops *tileops; /* Tile Blitting */
#endif
union {
char __iomem *screen_base; /* Virtual address 显存的起始地址,虚拟地址*/
char *screen_buffer;
};
unsigned long screen_size; /* Amount of ioremapped VRAM or 0 显存大小,即虚拟内存大小*/
void *pseudo_palette; /* Fake palette of 16 colors */
#define FBINFO_STATE_RUNNING 0
#define FBINFO_STATE_SUSPENDED 1
u32 state; /* Hardware state i.e suspend */
void *fbcon_par; /* fbcon use-only private area */
/* From here on everything is device dependent */
void *par;
/* we need the PCI or similar aperture base/size not
smem_start/size as smem_start may just be an object
allocated inside the aperture so may not actually overlap */
struct apertures_struct {
unsigned int count;
struct aperture {
resource_size_t base;
resource_size_t size;
} ranges[0];
} *apertures;
bool skip_vt_switch; /* no VT switch on suspend/resume required */
};
register_framebuffer()注册fb_info功能:
1)创建设备节点
2)registered_fb[i] 添加fb_info
register_framebuffer()函数,路径:drivers\video\fbdev\core\fbmem.c
cpp
int
register_framebuffer(struct fb_info *fb_info)
{
int ret;
mutex_lock(®istration_lock);
ret = do_register_framebuffer(fb_info);
mutex_unlock(®istration_lock);
return ret;
}
do_register_framebuffer()函数,路径:drivers\video\fbdev\core\fbmem.c
cpp
static int do_register_framebuffer(struct fb_info *fb_info)
{
/* ... */
for (i = 0 ; i < FB_MAX; i++)
if (!registered_fb[i])
break;
fb_info->node = i;
/* ... */
/* 创建设备(在fbmem_init()中创建类)此时生成 /dev/fb0 节点 */
fb_info->dev = device_create(fb_class, fb_info->device,
MKDEV(FB_MAJOR, i), NULL, "fb%d", i);
/* ... */
registered_fb[i] = fb_info;//registered_fb[i]添加fb_info
}
主要做了2件事:
1)用 fbmem_init()中创建的类创建设备,会生成 /dev/fb0 节点
2)fb_info 添加到 registered_fb[i] 中,数组索引为次设备号
**registered_fb[i] 又有什么用呢?**我们先来分析下应用程序 open("/dev/fb0", ...) 过程
open("/dev/fb0", ...) 主设备号: 29, 次设备号: 0,最终调用fbmem.c 中的file_operations(fb_fops) 的.open(fb_open)。
cpp
static struct fb_info *get_fb_info(unsigned int idx)
{
... ...
fb_info = registered_fb[idx]; //registered_fb中通过index找到fb_info
... ...
return fb_info;
}
static int
fb_open(struct inode *inode, struct file *file)
__acquires(&info->lock)
__releases(&info->lock)
{
int fbidx = iminor(inode); // 获取次设备号
struct fb_info *info;
... ...
info = get_fb_info(fbidx); // info = registered_fb[fbidx];
... ...
if (info->fbops->fb_open) {
res = info->fbops->fb_open(info,1); // 调用 fb_info 的 fbops->fb_open
}
}
fb_open 中以次设备号为索引找到 registered_fb[i] 的 fb_info(fb_info *info = registered_fb[次设备号])。最终调用 fb_info 的 fbops->fb_open,也就是 mxsfb_probe 中注册的 fb_info。write、read、ioctl 等系统调用过程都类似。
同理,应用程序调用 ioctl(g_tLcd.fd, FBIOGET_VSCREENINFO, &fb_var),会先以次设备号为索引找到 registered_fb[i] 的 fb_info,然后获取 fb_info 中的参数传给应用。
cpp
static struct fb_info *file_fb_info(struct file *file)
{
struct inode *inode = file_inode(file);
int fbidx = iminor(inode); //获取次设备号
struct fb_info *info = registered_fb[fbidx]; //获取 registered_fb[i] 中的 fb_info
return info;
}
static long fb_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct fb_info *info = file_fb_info(file);
... ...
return do_fb_ioctl(info, cmd, arg); // 处理数据
}
static long do_fb_ioctl(struct fb_info *info, unsigned int cmd,
unsigned long arg)
{
switch (cmd) {
case FBIOGET_VSCREENINFO:
if (!lock_fb_info(info))
return -ENODEV;
var = info->var; //获取 fb_info 中的参数
unlock_fb_info(info);
ret = copy_to_user(argp, &var, sizeof(var)) ? -EFAULT : 0;//将 fb_info 中的参数传给应用
break;
}
}
三、总结
1、fbmem.c 文件:搭建了LCD驱动框架
1)创建 /proc/fb 文件操作接口;
2)构造 fb_fops(file_operation),register_chrdev 注册字符设备驱动;
3)class_create创建类 "graphics"(由设备相关驱动创建设备节点)
2、mxsfb.c 文件:底层硬件层
匹配过程就是platform设备驱动那套流程。设备树(platform_device)与 mxsfb.c(platform_driver) 匹配后,调用 .probe。这里 .probe就是 mxsfb_probe() 函数。
mxsfb_probe() 函数主要功能
1)framebuffer_alloc()分配一个fb_info
2)设置fb_info,如:fb_info->fbops = &mxsfb_ops; 设置操作集
3)硬件相关寄存器配置
4)register_framebuffer()注册fb_info
register_framebuffer()功能:
1)device_create() 创建设备,生成 /dev/fb0
2)registered_fb[i] = fb_info; //将fb_info放入空的registered_fb[i]位置,i设为次设备号
应用程序open,会获取 registered_fb[i] 中对应次设备号的 fb_info,并调用它的open函数。
由此可见,fb_info结构非常重要。
四、应用层如何使用 /dev/fb0
- open函数:用于打开Framebuffer设备文件(如/dev/fb0)。
- ioctl函数:用于获取和设置Framebuffer的参数,如分辨率、颜色深度等。
- mmap函数:用于将Framebuffer映射到进程的地址空间,以便进行读写操作。
cpp
#include "framebuffer.h"
#include <linux/fb.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <math.h>
void *pmem;
struct fb_var_screeninfo vinf;
int init_fb(char *devname)
{
//1. 打开显示设备
int fd = open(devname, O_RDWR);
if (-1 == fd)
{
perror("fail open fb");
return -1;
}
//2、获取显示设备相关参数 分辨率 位深度
int ret = ioctl(fd, FBIOGET_VSCREENINFO, &vinf);
if (-1 ==ret)
{
perror("fail ioctl");
return -1;
}
printf("xres = %d, yres = %d\n", vinf.xres, vinf.yres);
printf("xres_virtual = %d, yres_virtual = %d\n", vinf.xres_virtual, vinf.yres_virtual);
printf("bits_per_pixel : %d\n", vinf.bits_per_pixel);
size_t len = vinf.xres_virtual * vinf.yres_virtual * vinf.bits_per_pixel/8;
//3, 建立显存和用户空间的映射关系
pmem = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if ((void *)-1 == pmem)
{
perror("fail mmap");
return -1;
}
return fd;
}
void draw_point(int x, int y, unsigned int col)
{
if (x >= vinf.xres || y >= vinf.yres)
{
return ;
}
if (vinf.bits_per_pixel == RGB888_FMT)
{
unsigned int *p = (unsigned int *)pmem;
*(p + y * vinf.xres_virtual + x) = col;
}
else if (vinf.bits_per_pixel == RGB565_FMT)
{
unsigned short *p = (unsigned short *)pmem;
*(p + y * vinf.xres_virtual + x) = col;
}
return ;
}
void draw_x_line(int x, int y, int len, unsigned int col)
{
if (x >= vinf.xres || y >= vinf.yres)
{
return ;
}
for (int i = x; i < x+len; i++)
{
draw_point(i, y, col);
}
return;
}
void draw_y_line(int x, int y, int len, unsigned int col)
{
if (x >= vinf.xres || y >= vinf.yres)
{
return ;
}
for (int i = y; i < y+len; i++)
{
draw_point(x, i, col);
}
return;
}
void draw_bias(int x, int y, int len, unsigned int col)
{
if (x >= vinf.xres || y >= vinf.yres)
{
return ;
}
int i=0;
while(i<len)
{
draw_point(x, y, col);
++x;
++y;
++i;
}
}
void draw_circle(int x, int y, int r, unsigned int col)
{
int x0, y0;
for (int i = 0; i <= 360; ++i)
{
x0 = r * cos(2 * 3.1415/360 * i) + x;
y0 = r * sin(2 * 3.1415/360 * i) + y;
draw_point(x0, y0, col);
draw_point(x0+1, y0, col);
draw_point(x0, y0+1, col);
draw_point(x0-1, y0, col);
draw_point(x0, y0-1, col);
}
}
void draw_rectangle(int x,int y,int len,int wide,unsigned int col)
{
if (x >= vinf.xres || y >= vinf.yres)
{
return ;
}
draw_x_line(x,y,len,col);
draw_y_line(x,y,wide,col);
draw_x_line(x,y+wide,len,col);
draw_y_line(x+len,y,wide,col);
}
void draw_clear(unsigned int col)
{
for(int i =0;i<vinf.xres_virtual;++i)
{
for(int j=0;j<vinf.yres_virtual;++j)
{
draw_point(i,j,col);
}
}
}
void uninit_fb(int fd)
{
size_t len = vinf.xres_virtual * vinf.yres_virtual * vinf.bits_per_pixel/8;
munmap(pmem, len);
close(fd);
}