Linux内核 LCD 驱动程序框架

1. framebuffer 简介

1.1 什么是 framebuffer

framebuffer 帧缓冲设备(简称fb)是 Linux内核中虚拟出的一个设备,属于字符设备;

它的主设备号为 FB_MAJOR = 29,次设备号用来区分内核中不同的 framebuffer。Linux 内核中最多支持 32 个 framebuffer,设备文件位于 /dev/fb*。

1.2 framebuffer的作用

framebuffer 的主要功能是向应用层提供一个统一标准接口的显示设备。

它将 显示设备的硬件结构 抽象为一系列的数据结构,应用程序通过 framebuffer 的读写从而实现间接对显存进行操作。用户可以将 framebuffer 看成是显存的一个映像,将其映射到进程空间后,就可以直接进行读写操作,写操作会直接反映在屏幕上。

对于现代 LCD,有一种“多屏叠加”的机制,即一个 LCD 设备可以有多个独立虚拟屏幕,以达到画面叠加的效果。所以 fb 与 LCD 不是一对一的关系,在常见的情况下,一个 LCD 对应了fb0~fb4。

2. framebuffer 驱动的框架

fb 的结构和 misc 极为类似,由内核中的 fb 框架实现一部分,然后再由设备驱动本身实现一部分。设备驱动本身就是一个普通的 platform 总线驱动。
虽然每家原厂写的fb设备驱动可能有些差异,但是基本的套路还是相同的

在这里插入图片描述
在内核 fb 框架中,所有的 fb 设备公用一个主设备号(都是29),它们之间以次设备号互相区分。所以在框架中使用 register_chrdev 注册了一个主设备号为29的设备,而在驱动中 device_create 创建设备文件主设备号都为29,次设备号不同。
由上图可以看出,内核提供了 fb 框架,原厂提供了 fb 的 platform 设备和驱动;不论有多少 LCD 屏幕,用的都是这一套 platform 驱动,它们的操作方式都是固定的,唯一的区别就在 platform_data 里的硬件参数。而我们驱动工程师重点关注的就是该硬件参数。
在这里插入图片描述

3. 相关数据结构

从驱动来看,fb 是一个典型的字符设备,而且创建了一个类 /sys/class/graphics。

framebuffer 的使用:

  1. 打开 framebuffer 设备文件: /dev/fb0

  2. 获取 framebuffer 设备信息 #include <linux/fb.h>

  3. mmap 做映射

  4. 填充 framebuffer

FB 驱动框架相关代码:drivers\video 这个目录中。

我们以 drivers/video/fbmem.c 这个文件为入口,对 framebuffer 框架进行分析。

fbmem.c

我们先从 init 入口函数着手进行分析:

static int __init fbmem_init(void)
{
    /* ...省略... */
	if (register_chrdev(FB_MAJOR,"fb",&fb_fops))	/* 注册字符设备 */
		printk("unable to get major %d for fb devs\n", FB_MAJOR);
    /* ...省略... */
}

根据 register_chrdev(FB_MAJOR,"fb",&fb_fops) 这条注册字符设备的函数调用。我们可以得到,对于 LCD 的操作使用 fb_fops 这个结构体。

其定义如下:

static const struct file_operations fb_fops = {
	.owner =	THIS_MODULE,
	.read =		fb_read,
	.write =	fb_write,
	.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
};

假设应用程序调用 open 函数来打开 /dev/fb0(设备来源后面会说明) 文件时,最终会调用 fb_open 函数。同理,如果调用了 read 函数,最终会调用 fb_read 函数,我们先分析这两个函数。

fd_open 函数,该函数的定义如下:

static int fb_open(struct inode *inode, struct file *file)
{
	int fbidx = iminor(inode);	/* 获取设备的次设备号 */
	struct fb_info *info;
	int res = 0;
	/* ...省略... */
	if (!(info = registered_fb[fbidx]))	/* 在全局变量registered_fb中获取设备的fd_info */
		return -ENODEV;
	/* ...省略... */
}

fd_read 函数,该函数的定义如下:

static ssize_t fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
    unsigned long p = *ppos;
    struct inode *inode = file->f_path.dentry->d_inode;
    int fbidx = iminor(inode);
    struct fb_info *info = registered_fb[fbidx]; /* 同样的操作,获取fb_info */
	/* ...省略... */
}

从上面的 fd_read 和 fd_open 函数,我们可以看到,无论是打开文件,还是读取操作,都需要用到 fb_info 这个结构体。

那么 fb_info 是什么呢?

struct fb_info

Linux 内核中使用 fb_info 结构体变量来描述一个 framebuffer,在调用 register_framebuffer 接口注册 framebuffer 之前,必须要初始化其中的重要数据成员。

struct fb_info 中成员众多,我们需要着重关注以下成员:

  • fb_var_screeninfo:代表可修改的LCD显示参数,如:分辨率和像素比特数等;

  • fb_fix_screeninfo:代表不可修改的LCD属性参数,如:显示内存的物理地址和长度等;

  • fb_ops:LCD 底层硬件操作接口函数集合。

fb_info 的定义如下(略有删减):

struct fb_info {
	int node;
	int flags;
	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 */

	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 */
	char __iomem *screen_base;	/* Virtual address */
	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;	
};
  • screen_base:记录的是该 framebuffer 显存的虚拟地址。一般通过 dma_alloc_writecombine() 后内核会返回一个显存的虚拟地址,应用程序可以通过该地址直接对显存进行操作。
  • screen_size:显存的大小。

struct fb_var_screeninfo

Linux 内核中使用 struct fb_var_screeninfo 来描述可修改的 LCD 显示参数,如:分辨率和像素比特数等。

/* struct fb_info 的成员(可变参数),其记录用户可修改的显示控制器的参数,包括分
 * 辨率和每个像素点的比特数,其成员需要在驱动程序中初始化和设置 */
struct fb_var_screeninfo {  
    /********可见解析度(实际屏幕)********/ /* visible resolution*/      
    __u32 xres;			/* 定义屏幕一行有多少个像素点 */
    __u32 yres;         /* 定义屏幕一列由多少个像素点 */

    /********虚拟解析度(虚拟屏幕)********/ /* virtual resolution*/
    __u32 xres_virtual; 	/* 虚拟屏幕一行有多少个像素点 */
    __u32 yres_virtual;     /* 虚拟屏幕一列由多少个像素点*/
    __u32 xoffset;			/* 虚拟到可见(实际)之间的行方向偏移 *//* offset from virtual to visible */
    __u32 yoffset;			/* 虚拟到可见(实际)之间的列方向偏移 *//* resolution*/

    __u32 bits_per_pixel;	/* 每像素位数(多少BPP),单位为字节 *//* guess what */
    __u32 grayscale; 		/* 非0时指灰度 *//* != 0 Graylevels instead of colors */  

    /********fb缓存的RGB位域**********/
    /* bitfield in fb mem if true color, else only length is significant*/ 
    struct fb_bitfield red;		/* fb缓存的红色位域*/
    struct fb_bitfield green;	/* fb缓存的绿色位域*/
    struct fb_bitfield blue;	/* fb缓存的蓝色位域*/                                                    
    struct fb_bitfield transp;	/* 透明度 =0 *//* transparency*/                 

    /* != 0 Non standard pixel format */
    __u32 nonstd;	/* 非标准像素格式时应该为非0值 (标志像素格式时 nonstd=0) */ 

    __u32 activate;	/* see FB_ACTIVATE_**/   

    __u32 height;	/* 表示屏幕的实际物理高度,以毫米为单位 *//* height of picture in mm */ 
    __u32 width;	/* 表示屏幕的实际物理宽度,以毫米为单位 *//* width of picture in mm*/     

    __u32 accel_flags;	/* (OBSOLETE)(已经淘汰使用) see fb_info.flags */ 


    /************这参数必须通过查看LCD数据手册得到**************/
    /* Timing: All values in pixclocks, except pixclock (of course) */
    /* pixel clock in ps (pico seconds) */
    __u32 pixclock;		/*像素时钟(皮秒),pixclock=1/Dclk=... */

    /* 行切换,从同步到绘图之间的延迟即 HFPD(有效数据之后无效的像素的个数) ,
     * 对应于LCD数据手册的Hsyn的front-porch */
    __u32 left_margin; /* time from sync to picture */  
    /* 行切换,从绘图到同步之间的延迟即HBPD(Hsyn脉冲下降沿之后的无效像素的个数) ,
     * 对应于LCD数据手册的Hsyn的back-porch*/
    __u32 right_margin; /* time from picture to sync */  
    /* 帧切换,从同步到绘图之间的延迟即VFPD(有效数据之后还要经历的无效行数(之后是下一帧数据)) ,
     * 对应于LCD数据手册的Vsyn的front-porch*/					  
    __u32 upper_margin; /* time from sync to picture */   
    /* 帧切换,从绘图到同步之间的延迟即VBPD(Vsyn脉冲下降沿之后还要经历的无效行数) ,
     * 对应于LCD数据手册的Vsyn的back-porch */
    __u32 lower_margin;  
    /* 水平同步的长度即HSPW(Hsyn信号的脉冲宽度),
     * 对应于LCD数据手册的Hsyn的pulse Width */ 					 
    __u32 hsync_len;  /* length of horizontal sync*/
    /* 垂直同步的长度即VSPW(Vsyn信号的脉冲宽度),
     * 对应于LCD数据手册的Vsyn的pulse Width */
    __u32 vsync_len;  /* length of vertical sync*/    

    __u32 sync;   		/* see FB_SYNC_* */      
    __u32 vmode;  		/* see FB_VMODE_* */
    __u32 rotate;  		/*顺时钟旋转的角度 *//* angle we rotate counter clockwise */ 
    __u32 reserved[5];  /* Reserved for future compatibility */
}; 

大部分的成员说明已经写在注释当中,其中有几个特殊的成员需要看宏定义:

  1. activate 具体有什么作用,我暂时也还没有完全搞明白。大致可以猜测出来是响应或者起作用的时间,可以表示的是修改了 var_info 之后,这些新的修改在什么时候其作用。

    NOW 是立即、NXTOPEN 表应该是下一次打开时起作用、TEST 表示只是测试,有点像演示功能。

    #define FB_ACTIVATE_NOW			0	/* set values immediately (or vbl)*/
    #define FB_ACTIVATE_NXTOPEN		1	/* activate on next open */
    #define FB_ACTIVATE_TEST		2	/* don't set, round up impossible */
    #define FB_ACTIVATE_MASK       15   /* values */
    #define FB_ACTIVATE_VBL	       16	/* activate values on next vbl */
    #define FB_CHANGE_CMAP_VBL     32	/* change colormap on vbl */
    #define FB_ACTIVATE_ALL	       64	/* change all VCs on this fb */
    #define FB_ACTIVATE_FORCE     128	/* force apply even when no change*/
    #define FB_ACTIVATE_INV_MODE  256   /* invalidate videomode */
    
  2. sync 应该表示的是在何时进行同步。

    FB_SYNC_HOR_HIGH_ACT 表示在水平同步信号为高时起作用,FB_SYNC_VERT_HIGH_ACT 表示垂直信号。

    #define FB_SYNC_HOR_HIGH_ACT	1	/* horizontal sync high active	*/
    #define FB_SYNC_VERT_HIGH_ACT	2	/* vertical sync high active	*/
    #define FB_SYNC_EXT				4	/* external sync		*/
    #define FB_SYNC_COMP_HIGH_ACT	8	/* composite sync high active   */
    #define FB_SYNC_BROADCAST		16	/* broadcast video timings      */
    									/* vtotal = 144d/288n/576i => PAL  */
    									/* vtotal = 121d/242n/484i => NTSC */
    #define FB_SYNC_ON_GREEN		32	/* sync on green */
    
  3. vmode 记录了一些屏幕的模式,如:屏幕的扫描方式 non-interlaced、interlaced、double scan。

    #define FB_VMODE_NONINTERLACED  0	/* non interlaced */
    #define FB_VMODE_INTERLACED		1	/* interlaced	*/
    #define FB_VMODE_DOUBLE			2	/* double scan */
    #define FB_VMODE_MASK			255
    #define FB_VMODE_YWRAP			256	/* ywrap instead of panning */
    #define FB_VMODE_SMOOTH_XPAN	512	/* smooth xpan possible (internally used) */
    #define FB_VMODE_CONUPDATE		512	/* don't update x/yoffset */
    

struct fb_fix_screeninfo

Linux 内核中使用 struct fb_fix_screeninfo 来描述不可修改的 LCD 属性参数,如:显示内存的物理地址和长度等。

/* struct fb_info的成员(固定参数),其记录用户不能修改的显示控制器的参数,如屏幕缓冲区物理地址,
 * 长度,当对帧缓冲设备进行映射操作时,就是从此结构中取得缓冲区物理地址,其成员需要在驱动程序中初始化和设置 */
struct fb_fix_screeninfo {                                              
    char id[16];	/* 字符串形式的标识符 *//* identification string eg "TT Builtin" */
    
    /* fb 缓冲内存的开始地址(物理地址),
     * 它一般是作为 dma_alloc_writecombine 的参数,
     * 该函数会将显存的物理地址存放在该变量中*/
    unsigned long smem_start; 		/* Start of frame buffer mem physical address) */                   
    /* fb缓冲的长度,等于 max_xres*max_yres*max_bpp/8 */ 
    __u32 smem_len;	/* Length of frame buffer mem */
    __u32 type;  	/* see FB_TYPE_**/
    __u32 type_aux; /* Interleave for interleaved Planes*/      /* 分界,=0 */
    __u32 visual;  	/* see FB_VISUAL_* */
    
    __u16 xpanstep; 	/* zero if no hardware panning */ /* 如果没有硬件 panning,=0 */
    __u16 ypanstep; 	/* zero if no hardware panning */ /* 如果没有硬件 panning,=0 */
    __u16 ywrapstep;	/* zero if no hardware ywrap */ /* 如果没有硬件 panning,=0 */
    __u32 line_length;	/* length of a line in bytes */ /* 一行的字节数 */
    
    /* 内存映射的I/O的开始位置 */
    unsigned long mmio_start; 	/* Start of Memory Mapped I/O (physical address) */
    /* 内存映射的I/O的长度 */
    __u32 mmio_len; 			/* Length of Memory Mapped I/O  */            
    __u32 accel; 	/* Indicate to driver which */
    				/* specific chip/card we have */
    				/* = FB_ACCEL_NONE */
    
    __u16 reserved[3];	/* Reserved for future compatibility */
 };

大部分的成员说明已经写在注释当中,其中有几个特殊的成员需要看宏定义:

对于 type 和 visual 内核文档中有这样一段描述:

Pixels are stored in memory in hardware-dependent formats. Applications need to be aware of the pixel storage format in order to write image data to the frame buffer memory in the format expected by the hardware.

Formats are described by frame buffer types and visuals. Some visuals require additional information, which are stored in the variable screen information bits_per_pixel, grayscale, red, green, blue and transp fields.

Visuals describe how color information is encoded and assembled to create macropixels. Types describe how macropixels are stored in memory. The following types and visuals are supported.

  • type

    #define FB_TYPE_PACKED_PIXELS		0	/* Packed Pixels	*/
    #define FB_TYPE_PLANES				1	/* Non interleaved planes */
    #define FB_TYPE_INTERLEAVED_PLANES	2	/* Interleaved planes	*/
    #define FB_TYPE_TEXT				3	/* Text/attributes	*/
    #define FB_TYPE_VGA_PLANES			4	/* EGA/VGA planes	*/
    
    • FB TYPE PACKED PIXELS :紧缩像素格式 (最常见的一种格式) 。 像素被连续地存放在一个平面中 。
    • FB TYPE PLANES :非间隔平面格式 。 像素中的各分量被分离地存放在不同的平面中,平面的个数等于像素的有效位数。 例如,可以先依次存储所有像素的第 0 位,接着存储第 1 位,以此类推。
    • FB TYPE INTERLEAVED PLANES :间隔平面格式。 像素中的各分量被分离地存放在不同平面中 , 平面的个数等于像素的有效位数,只是每个平面在内存中并不连续。
      例如,可以先按照非间隔平面格式存储 8 像素,接着储存后面 8 像素 , 以此类推。
    • FB TYPE TEXT :文本模式 。 有的显示设备支持直接文本显示,这时只需要在帧缓冲中存储需要显示文本的索引,而不是构成这些文本的像素信息 。
  • visual

    #define FB_VISUAL_MONO01				0	/* Monochr. 1=Black 0=White */
    #define FB_VISUAL_MONO10				1	/* Monochr. 1=White 0=Black */
    #define FB_VISUAL_TRUECOLOR				2	/* True color	*/
    #define FB_VISUAL_PSEUDOCOLOR			3	/* Pseudo color (like atari) */
    #define FB_VISUAL_DIRECTCOLOR			4	/* Direct color */
    #define FB_VISUAL_STATIC_PSEUDOCOLOR	5	/* Pseudo color readonly */
    
    • FB VISUAL MONOOI 、 FB VISUAL MONOlO :单色模式.每像素由二进制的 0 或 1 表示是黑色或者白色 。
    • FB VISUAL PSEUDOCOLOR 、 FB VISUAL STATIC PSEUDOCOLOR :伪彩色模式,每像素数据记录的是颜色的索引,通过索引可以在颜色表中查找到相应的颜色。
    • FB VISUAL TRUECOLOR :真影色模式,每像素由红、绿 、 蓝 3 个颜色分量构成。每个颜色分量都是一个索引,对应的索引表是只读的,并依赖于具体的显示设备。
    • FB VISUAL DIRECTCOLOR :直接影色模式 , 每像素同样由红、绿、蓝 3 个颜色分量构成。 每个颜色分量也是一个索引,不过索引表是可编程的 。
  • accel 用于告知驱动,我们有哪些特别的芯片,可能可以用于加速显示之类。

    #define FB_ACCEL_NONE			0	/* no hardware accelerator	*/
    #define FB_ACCEL_ATARIBLITT		1	/* Atari Blitter		*/
    #define FB_ACCEL_AMIGABLITT		2	/* Amiga Blitter                */
    #define FB_ACCEL_S3_TRIO64		3	/* Cybervision64 (S3 Trio64)    */
    #define FB_ACCEL_NCR_77C32BLT	4	/* RetinaZ3 (NCR 77C32BLT)      */
    #define FB_ACCEL_S3_VIRGE		5	/* Cybervision64/3D (S3 ViRGE)	*/
    #define FB_ACCEL_ATI_MACH64GX	6	/* ATI Mach 64GX family		*/
    #define FB_ACCEL_DEC_TGA		7	/* DEC 21030 TGA		*/
    #define FB_ACCEL_ATI_MACH64CT	8	/* ATI Mach 64CT family		*/
    #define FB_ACCEL_ATI_MACH64VT	9	/* ATI Mach 64CT family VT class */
    #define FB_ACCEL_ATI_MACH64GT	10	/* ATI Mach 64CT family GT class */
    #define FB_ACCEL_SUN_CREATOR	11	/* Sun Creator/Creator3D	*/
    #define FB_ACCEL_SUN_CGSIX		12	/* Sun cg6			*/
    #define FB_ACCEL_SUN_LEO		13	/* Sun leo/zx			*/
    #define FB_ACCEL_IMS_TWINTURBO	14	/* IMS Twin Turbo		*/
    #define FB_ACCEL_3DLABS_PERMEDIA2 		15	/* 3Dlabs Permedia 2		*/
    #define FB_ACCEL_MATROX_MGA2064W 		16	/* Matrox MGA2064W (Millenium)	*/
    #define FB_ACCEL_MATROX_MGA1064SG 		17	/* Matrox MGA1064SG (Mystique)	*/
    #define FB_ACCEL_MATROX_MGA2164W 		18	/* Matrox MGA2164W (Millenium II) */
    #define FB_ACCEL_MATROX_MGA2164W_AGP 	19	/* Matrox MGA2164W (Millenium II) */
    #define FB_ACCEL_MATROX_MGAG100	20	/* Matrox G100 (Productiva G100) */
    #define FB_ACCEL_MATROX_MGAG200	21	/* Matrox G200 (Myst, Mill, ...) */
    #define FB_ACCEL_SUN_CG14		22	/* Sun cgfourteen		 */
    #define FB_ACCEL_SUN_BWTWO		23	/* Sun bwtwo			*/
    #define FB_ACCEL_SUN_CGTHREE	24	/* Sun cgthree			*/
    #define FB_ACCEL_SUN_TCX		25	/* Sun tcx			*/
    #define FB_ACCEL_MATROX_MGAG400	26	/* Matrox G400			*/
    #define FB_ACCEL_NV3			27	/* nVidia RIVA 128              */
    #define FB_ACCEL_NV4			28	/* nVidia RIVA TNT		*/
    #define FB_ACCEL_NV5			29	/* nVidia RIVA TNT2		*/
    #define FB_ACCEL_CT_6555x		30	/* C&T 6555x			*/
    #define FB_ACCEL_3DFX_BANSHEE	31	/* 3Dfx Banshee			*/
    #define FB_ACCEL_ATI_RAGE128	32	/* ATI Rage128 family		*/
    #define FB_ACCEL_IGS_CYBER2000	33	/* CyberPro 2000		*/
    #define FB_ACCEL_IGS_CYBER2010	34	/* CyberPro 2010		*/
    #define FB_ACCEL_IGS_CYBER5000	35	/* CyberPro 5000		*/
    #define FB_ACCEL_SIS_GLAMOUR    36	/* SiS 300/630/540              */
    #define FB_ACCEL_3DLABS_PERMEDIA3 	37	/* 3Dlabs Permedia 3		*/
    #define FB_ACCEL_ATI_RADEON		38	/* ATI Radeon family		*/
    #define FB_ACCEL_I810           39      /* Intel 810/815                */
    #define FB_ACCEL_SIS_GLAMOUR_2  40	/* SiS 315, 650, 740		*/
    #define FB_ACCEL_SIS_XABRE      41	/* SiS 330 ("Xabre")		*/
    #define FB_ACCEL_I830           42      /* Intel 830M/845G/85x/865G     */
    #define FB_ACCEL_NV_10          43      /* nVidia Arch 10               */
    #define FB_ACCEL_NV_20          44      /* nVidia Arch 20               */
    #define FB_ACCEL_NV_30          45      /* nVidia Arch 30               */
    #define FB_ACCEL_NV_40          46      /* nVidia Arch 40               */
    #define FB_ACCEL_XGI_VOLARI_V	47	/* XGI Volari V3XT, V5, V8      */
    #define FB_ACCEL_XGI_VOLARI_Z	48	/* XGI Volari Z7                */
    #define FB_ACCEL_NEOMAGIC_NM2070 90	/* NeoMagic NM2070              */
    #define FB_ACCEL_NEOMAGIC_NM2090 91	/* NeoMagic NM2090              */
    #define FB_ACCEL_NEOMAGIC_NM2093 92	/* NeoMagic NM2093              */
    #define FB_ACCEL_NEOMAGIC_NM2097 93	/* NeoMagic NM2097              */
    #define FB_ACCEL_NEOMAGIC_NM2160 94	/* NeoMagic NM2160              */
    #define FB_ACCEL_NEOMAGIC_NM2200 95	/* NeoMagic NM2200              */
    #define FB_ACCEL_NEOMAGIC_NM2230 96	/* NeoMagic NM2230              */
    #define FB_ACCEL_NEOMAGIC_NM2360 97	/* NeoMagic NM2360              */
    #define FB_ACCEL_NEOMAGIC_NM2380 98	/* NeoMagic NM2380              */
    #define FB_ACCEL_SAVAGE4        0x80	/* S3 Savage4                   */
    #define FB_ACCEL_SAVAGE3D       0x81	/* S3 Savage3D                  */
    #define FB_ACCEL_SAVAGE3D_MV    0x82	/* S3 Savage3D-MV               */
    #define FB_ACCEL_SAVAGE2000     0x83	/* S3 Savage2000                */
    #define FB_ACCEL_SAVAGE_MX_MV   0x84	/* S3 Savage/MX-MV              */
    #define FB_ACCEL_SAVAGE_MX      0x85	/* S3 Savage/MX                 */
    #define FB_ACCEL_SAVAGE_IX_MV   0x86	/* S3 Savage/IX-MV              */
    #define FB_ACCEL_SAVAGE_IX      0x87	/* S3 Savage/IX                 */
    #define FB_ACCEL_PROSAVAGE_PM   0x88	/* S3 ProSavage PM133           */
    #define FB_ACCEL_PROSAVAGE_KM   0x89	/* S3 ProSavage KM133           */
    #define FB_ACCEL_S3TWISTER_P    0x8a	/* S3 Twister                   */
    #define FB_ACCEL_S3TWISTER_K    0x8b	/* S3 TwisterK                  */
    #define FB_ACCEL_SUPERSAVAGE    0x8c    /* S3 Supersavage               */
    #define FB_ACCEL_PROSAVAGE_DDR  0x8d	/* S3 ProSavage DDR             */
    #define FB_ACCEL_PROSAVAGE_DDRK 0x8e	/* S3 ProSavage DDR-K           */
    

4. framebuffer 驱动框架分析

Linux Framebuffer的驱动框架主要涉及以下文件:

  1. drivers/video/fbmem.c:主要任务是创建 graphic s类、注册 FB 的字符设备驱动(主设备号是29)、提供 register_framebuffer 接口给具体 framebuffer 驱动编写着来注册fb设备的;

  2. drivers/video/fbsys.c:由是 fbmem.c 引出来的,处理 fb 在 /sys/class/graphics/fb0 目录下的一些属性文件的;

  3. drivers/video/modedb.c:由是 fbmem.c 引出来的,管理显示模式(譬如 VGA、720P 等就是显示模式);

  4. drivers/video/fb_notify.c:由是 fbmem.c 引出来的。

4.1 framebuffer的初始化

Linux 内核中将 framebuffer 的驱动框架实现为模块的形式。

framebuffer 的初始化函数 fbmem_init() 被 module_init 或 subsys_initcall修饰,即 fbmem_init() 会在 framebuffer 驱动被加载时由 Linux 内核调度运行。

fbmem_init() 函数的主要工作是通过 register_chrdev 接口向内核注册一个主设备号位29的字符设备。通过 class_create 在 /sys/class 下创建 graphics 设备类,配合 mdev 机制生成供用户访问的设备文件(位于 /dev 目录)。

static int __init fbmem_init(void)
{
    proc_create("fb", 0, NULL, &fb_proc_fops);  	/* 向proc文件系统报告驱动状态和参数 */

    if (register_chrdev(FB_MAJOR,"fb",&fb_fops))	/* 注册字符设备驱动,主设备号是29 */
        printk("unable to get major %d for fb devs\n", FB_MAJOR);
  
  /* 创建sys/class/graphics设备类,配合mdev生成设备文件 */
    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;
}

4.2 fb_fops

在 framebuffer 的初始化函数 fbmem_init() 中,调用 register_chrdev(FB_MAJOR,"fb",&fb_fops) 向内核注册一个主设备号位FB_MAJOR = 29 的字符设备,其中 fb_fops 为 fb 设备的操作集。

static const struct file_operations fb_fops = 
{
    .owner =    THIS_MODULE,
    .read  =    fb_read,
    .write =    fb_write,
    .unlocked_ioctl = fb_ioctl,
    .mmap  =    fb_mmap,
    .open  =    fb_open,
    .release =  fb_release,
    ... ...
};

我们着重分析一下 fb_fops->fb_open。

假设在注册 fb 设备过程中生成 /dev/input/fb0 设备文件,我们来跟踪打开这个设备的过程。

Open(“/dev/input/fb0”)

  1. vfs_open 打开该设备文件,读出文件的 inode 内容,得到该设备的主设备号和次设备号;

  2. chardev_open 字符设备驱动框架的 open 根据主设备号得到 framebuffer 的 fb_fops 操作集;

  3. 进入到 fb_fops->open, 即 fb_open()。从 fb_open( )函数中可以看出,最终调用的是 fb0 设备下的 fb_ops 来对 fb0 设备进行访问,即 fb_info->fb_ops->fb_open。

static int fb_open(struct inode *inode, struct file *file)
    __acquires(&info->lock)
    __releases(&info->lock)
{
    int fbidx = iminor(inode);      /* 获得次设备号 */
    struct fb_info *info;           /* 代表一个framebuffer的全局数据结构 */
    int res = 0;

    if (fbidx >= FB_MAX)
        return -ENODEV;
    
    info = registered_fb[fbidx];    /* 找到对应的 fb_info */
    /* 将 info 存入 file 的私有数据中,便于用户调用其他接口(mmap、ioctl等)能够找到相应的 info */
    file->private_data = info;	
    if (info->fbops->fb_open)       /* 调用info->fbops->fb_open */
    {        
        res = info->fbops->fb_open(info,1);
        if (res)
            module_put(info->fbops->owner);
    }
	/*...省略...*/
    return res;
}

4.3 framebuffer设备注册/注销接口

framebuffer 驱动加载初始化时需要通过 register_framebuffer 接口向framebuffer子系统注册自己。

数组 struct fb_info *registered_fb[FB_MAX],是 fb 驱动框架维护的一个用来管理记录 fb 设备的数组,里面的元素就是 fb_info 指针,一个 fb_info 就代表一个 fb 设备。由此可知,Linux 内核最多支持 FB_MAX = 32 个 fb 设备。

int register_framebuffer(struct fb_info *fb_info)
{
    int i;
    struct fb_event event;
    struct fb_videomode mode;

    /* 如果当前注册的 fb 设备的数量已经满了,则不能在进行注册了 最大值32 */
    if (num_registered_fb == FB_MAX)
        return -ENXIO;
    /* ... ... */

    /* 找到一个最小的没有被使用的次设备号 */
    num_registered_fb++;
    for (i = 0 ; i < FB_MAX; i++)
        if (!registered_fb[i])
            break;
    fb_info->node = i;	/* 将次设备号存放在 fb_info->node 中 */
    mutex_init(&fb_info->lock);
    mutex_init(&fb_info->mm_lock);

    /* 创建 framebuffer 设备 */
    fb_info->dev = device_create(fb_class, fb_info->device, MKDEV(FB_MAJOR, i), NULL, "fb%d", i);
    /* ... ... */
    /* 初始化framebuffer设备信息,创建fb的设备属性文件 */
    fb_init_device(fb_info);
    /* pixmp相关设置 */
    if (fb_info->pixmap.addr == NULL)   {
        fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL);
        if (fb_info->pixmap.addr)      {
            fb_info->pixmap.size = FBPIXMAPSIZE;
            fb_info->pixmap.buf_align = 1;
            fb_info->pixmap.scan_align = 1;
            fb_info->pixmap.access_align = 32;
            fb_info->pixmap.flags = FB_PIXMAP_DEFAULT;
        }
    }    
    /* ... ... */

    fb_var_to_videomode(&mode, &fb_info->var);  /* 从fb_info结构体中获取显示模式存放在mode变量中 */
    fb_add_videomode(&mode, &fb_info->modelist);/* 添加显示模式 */
    registered_fb[i] = fb_info;                 /* 将fb_info 结构体存放到 registered_fb数组中 */

    /* ... ... */
    /* 异步通知: 通知那些正在等待fb注册事件发生的进程 */
    fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event); 
    /* ... ... */
    return 0;
}

相应的 fb 设备的注销接口为 unregister_framebuffer() 函数。

/*
*功能:注销fb设备
*参数:struct fb_info *fb_info:需要注销的fb设备
*/
int unregister_framebuffer(struct fb_info *fb_info);

4.4 mmap映射

framebuffer 驱动最重要的功能就是给用户提供一个进程空间映射到实际的显示物理内存的接口 (fb_fops->mmap)。

应用层 mmap 映射接口会经历以下层次调用:

  1. sys_mmap(),虚拟文件系统层(VFS)的映射实现;

  2. fb_ops.mmap = fb_mmap,此处的 fb_fops 为 framebuffer 驱动框架的操作集;

    从 fb_mmap() 函数中可以看出,最终调用的是 fb 设备的 fb_ops 中的 fb_mmap,即 fb_info->fb_fops->fb_mmap。

static int fb_mmap(struct file *file, struct vm_area_struct * vma)
{
    int fbidx = iminor(file->f_path.dentry->d_inode); /* 获取fb的子设备号 */
    struct fb_info *info = registered_fb[fbidx];      /* 获取相应的fb_info */
    struct fb_ops *fb = info->fbops;                  /* fb设备的操作集 */
   
    /* ... ... */
    if (fb->fb_mmap) {
        int res;
        res = fb->fb_mmap(info, vma);  /* 调用fb设备操作集下的fb_mmap */
        mutex_unlock(&info->mm_lock);
        return res;
    }
  /* ... ... */
    return 0;
}

5. fb 与应用程序的交互

在应用层中,用户可以将fb设备看成是显存的一个映像,将其映射到进程空间后,就可以直接进行读写操作,写操作会直接反映在屏幕上。

在应用程序中,操作 /dev/fbn 的一般步骤如下:

  1. 打开 /dev/fbn 设备文件;

  2. ioctl() 操作取得当前显示屏幕的参数,如屏幕分辨率、每个像素点的比特数。根据屏幕参数可计算屏幕缓冲区的大小;

  3. mmap() 函数,将屏幕缓冲区映射到用户空间;

  4. 映射后就可以直接读/写屏幕缓冲区,进行绘图和图片显示;

  5. 使用完帧缓冲设备后需要将其释放;

  6. 关闭文件。

应用示例:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/ioctl.h>
#include <sys/mman.h>

#define FBDEVICE    "/dev/fb0"

#define WHITE        0xffffffff
#define BLACK        0x00000000

void draw_back(unsigned int *pfb, unsigned int width, unsigned int height, unsigned int color)
{
    unsigned int x, y;
    
    for (y=0; y<height; y++)
    {
        for (x=0; x<width; x++)
        {
            *(pfb + y * width + x) = color;
        }
    }
}

int main(void)
{
    int fd = -1;
    unsigned int *pfb = NULL;
    unsigned int width;
    unsigned int height;
    
    struct fb_fix_screeninfo finfo = {0};
    struct fb_var_screeninfo vinfo = {0};

    /************************第1步:打开设备**************************/ 
    fd = open(FBDEVICE, O_RDWR);
    if (fd < 0)
    {
        perror("open");
        return -1;
    }
    printf("open %s success.\n", FBDEVICE);


    /*******************第2步:获取设备的硬件信息********************/ 
    ioctl(fd, FBIOGET_FSCREENINFO, &finfo);//获取LCD的固定属性参数
    /*
     * finfo.smem_start:LCD显存的起始地址
     * finfo.smem_len:LCD显存的字节大小
     */
    printf("smem_start = 0x%x, smem_len = %u.\n", finfo.smem_start, finfo.smem_len);

    
    ioctl(fd, FBIOGET_VSCREENINFO, &vinfo);//获取LCD的可变参数
    /*
     * vinfo.xres:水平分辨率
     * vinfo.yres:垂直分辨率
     * vinfo.xres_virtual:虚拟水平分辨率
     * vinfo.yres_virtual:虚拟垂直分辨率
     * vinfo.bits_per_pixel:像素深度
     */
    printf("xres = %u, yres = %u.\n", vinfo.xres, vinfo.yres);
    printf("xres_virtual = %u, yres_virtual = %u.\n", vinfo.xres_virtual, vinfo.yres_virtual);
    printf("bpp = %u.\n", vinfo.bits_per_pixel);
    
    width = vinfo.xres;
    height = vinfo.yres;
    

    /*************************第3步:进行mmap***********************/ 
    //计算LCD显存大小(单位:字节)
    unsigned long len = vinfo.xres_virtual * vinfo.yres_virtual * vinfo.bits_per_pixel / 8;
    printf("len = %ld\n", len);
    
    pfb = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    printf("pfb = %p.\n", pfb);


    /********************第4步:进行lcd相关的操作********************/
    draw_back(pfb, width, height, RED);
    

    /***********************第五步:释放显存************************/
    munmap(pfb, len);
    

    /***********************第六步:关闭文件************************/ 
    close(fd);

    return 0;
}

6. 参考与原文

Linux LCD Frambuffer 基础介绍和使用(1) - 知乎 (zhihu.com)

The Frame Buffer Device API — The Linux Kernel documentation

Linux字符设备驱动框架(五):Linux内核的framebuffer驱动框架 - LinFeng-Learning - 博客园 (cnblogs.com)

Linux驱动框架之framebuffer驱动框架 - 涛少& - 博客园 (cnblogs.com)

  • 1
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值