分类: LINUX
2014-02-19 17:02:06
原文地址:全面的framebuffer详解一 作者:zhhsboy
一、FrameBuffer的原理 FrameBuffer 是出现在 2.2.xx 内核当中的一种驱动程序接口。 Linux是工作在保护模式下,所以用户态进程是无法象DOS那样使用显卡BIOS里提供的中断调用来实现直接写屏,Linux抽象出 FrameBuffer这个设备来供用户态进程实现直接写屏。Framebuffer机制模仿显卡的功能,将显卡硬件结构抽象掉,可以通过 Framebuffer的读写直接对显存进行操作。用户可以将Framebuffer看成是显示内存的一个映像,将其映射到进程地址空间之后,就可以直接进行读写操作,而写操作可以立即反应在屏幕上。这种操作是抽象的,统一的。用户不必关心物理显存的位置、换页机制等等具体细节。这些都是由 Framebuffer设备驱动来完成的。 但Framebuffer本身不具备任何运算数据的能力,就只好比是一个暂时存放水的水池.CPU将运算后的结果放到这个水池,水池再将结果流到显示器. 中间不会对数据做处理. 应用程序也可以直接读写这个水池的内容.在这种机制下,尽管Framebuffer需要真正的显卡驱动的支持,但所有显示任务都有CPU完成,因此CPU 负担很重 framebuffer的设备文件一般是 /dev/fb0、/dev/fb1 等等。 可以用命令: #dd if=/dev/zero of=/dev/fb 清空屏幕. 如果显示模式是 1024x768-8 位色,用命令:$ dd if=/dev/zero of=/dev/fb0 bs=1024 count=768 清空屏幕; 用命令: #dd if=/dev/fb of=fbfile 可以将fb中的内容保存下来; 可以重新写回屏幕: #dd if=fbfile of=/dev/fb; 在使用Framebuffer时,Linux是将显卡置于图形模式下的. 在应用程序中,一般通过将 FrameBuffer 设备映射到进程地址空间的方式使用,比如下面的程序就打开 /dev/fb0 设备,并通过 mmap 系统调用进行地址映射,随后用 memset 将屏幕清空(这里假设显示模式是 1024x768-8 位色模式,线性内存模式): int fb; unsigned char* fb_mem; fb = open ("/dev/fb0", O_RDWR); fb_mem = mmap (NULL, 1024*768, PROT_READ|PROT_WRITE,MAP_SHARED,fb,0); memset (fb_mem, 0, 1024*768); //这个命令应该只有在root可以执行 FrameBuffer 设备还提供了若干 ioctl 命令,通过这些命令,可以获得显示设备的一些固定信息(比如显示内存大小)、与显示模式相关的可变信息(比如分辨率、象素结构、每扫描线的字节宽度),以及伪彩色模式下的调色板信息等等。 通过 FrameBuffer 设备,还可以获得当前内核所支持的加速显示卡的类型(通过固定信息得到),这种类型通常是和特定显示芯片相关的。比如目前最新的内核(2.4.9)中,就包含有对 S3、Matrox、nVidia、3Dfx 等等流行显示芯片的加速支持。在获得了加速芯片类型之后,应用程序就可以将 PCI 设备的内存I/O(memio)映射到进程的地址空间。这些 memio 一般是用来控制显示卡的寄存器,通过对这些寄存器的操作,应用程序就可以控制特定显卡的加速功能。 PCI 设备可以将自己的控制寄存器映射到物理内存空间,而后,对这些控制寄存器的访问,给变成了对物理内存的访问。因此,这些寄存器又被称为"memio"。一旦被映射到物理内存,Linux 的普通进程就可以通过 mmap 将这些内存 I/O 映射到进程地址空间,这样就可以直接访问这些寄存器了。 当然,因为不同的显示芯片具有不同的加速能力,对memio 的使用和定义也各自不同,这时,就需要针对加速芯片的不同类型来编写实现不同的加速功能。比如大多数芯片都提供了对矩形填充的硬件加速支持,但不同的芯片实现方式不同,这时,就需要针对不同的芯片类型编写不同的用来完成填充矩形的函数。 FrameBuffer 只是一个提供显示内存和显示芯片寄存器从物理内存映射到进程地址空间中的设备。所以,对于应用程序而言,如果希望在 FrameBuffer 之上进行图形编程,还需要自己动手完成其他许多工作。 二、FrameBuffer在Linux中的实现和机制 Framebuffer对应的源文件在linux/drivers/video/目录下。总的抽象设备文件为fbcon.c,在这个目录下还有与各种显卡驱动相关的源文件。 //这个文件要好好看看 (一)、分析Framebuffer设备驱动 需要特别提出的是在INTEL平台上,老式的VESA 1.2 卡,如CGA/EGA卡,是不能支持Framebuffer的,因为Framebuffer要求显卡支持线性帧缓冲,即CPU可以访问显缓冲中的每一位,但是VESA 1.2 卡只能允许CPU一次访问64K的地址空间。 FrameBuffer设备驱动基于如下两个文件: 1) linux/include/linux/fb.h 2) linux/drivers/video/fbmem.c 下面分析这两个文件。 1、fb.h 几乎主要的结构都是在这个中文件定义的。这些结构包括: 1)fb_var_screeninfo 这个结构描述了显示卡的特性: NOTE:::: __u32 是表示 unsigned 不带符号的 32 bits 的数据类型,其余类推。这是 Linux 内核中所用到的数据类型,如果是开发用户空间(user-space)的程序,可以根据具体计算机平台的情况,用 unsigned long 等等来代替 struct fb_var_screeninfo { __u32 xres; /* visible resolution */ //可视区域 __u32 yres; __u32 xres_virtual; /* virtual resolution */ //可视区域 __u32 yres_virtual; __u32 xoffset; /* offset from virtual to visible resolution */ //可视区域的偏移 __u32 yoffset; __u32 bits_per_pixel; /* guess what */ //每一象素的bit数 __u32 grayscale; /* != 0 Gray levels instead of colors *///等于零就成黑白 struct fb_bitfield red; /* bitfield in fb mem if true color, */真彩的bit机构 struct fb_bitfield green; /* else only length is significant */ struct fb_bitfield blue; struct fb_bitfield transp; /* transparency */ 透明 __u32 nonstd; /* != 0 Non standard pixel format */ 不是标准格式 __u32 activate; /* see FB_ACTIVATE_* */ __u32 height; /* height of picture in mm */ 内存中的图像高度 __u32 width; /* width of picture in mm */ 内存中的图像宽度 __u32 accel_flags; /* acceleration flags (hints) */ 加速标志 /* Timing: All values in pixclocks, except pixclock (of course) */ 时序-_-这些部分就是显示器的显示方法了,可以找相关的资料看看 __u32 pixclock; /* pixel clock in ps (pico seconds) */ __u32 left_margin; /* time from sync to picture */ __u32 right_margin; /* time from picture to sync */ __u32 upper_margin; /* time from sync to picture */ __u32 lower_margin; __u32 hsync_len; /* length of horizontal sync */ 水平可视区域 __u32 vsync_len; /* length of vertical sync */ 垂直可视区域 __u32 sync; /* see FB_SYNC_* */ __u32 vmode; /* see FB_VMODE_* */ __u32 reserved[6]; /* Reserved for future compatibility */ 备用-以后开发 }; 2) fb_fix_screeninfon 这个结构在显卡被设定模式后创建,它描述显示卡的属性,并且系统运行时不能被修改;比如FrameBuffer内存的起始地址。它依赖于被设定的模式,当一个模式被设定后,内存信息由显示卡硬件给出,内存的位置等信息就不可以修改。 struct fb_fix_screeninfo { char id[16]; /* identification string eg "TT Builtin" */ID unsigned long smem_start; /* Start of frame buffer mem */ 内存起始 /* (physical address) */ 物理地址 __u32 smem_len; /* Length of frame buffer mem */ 内存大小 __u32 type; /* see FB_TYPE_* */ __u32 type_aux; /* Interleave for interleaved Planes */插入区域? __u32 visual; /* see FB_VISUAL_* */ __u16 xpanstep; /* zero if no hardware panning */没有硬件设备就为零 __u16 ypanstep; /* zero if no hardware panning */ __u16 ywrapstep; /* zero if no hardware ywrap */ __u32 line_length; /* length of a line in bytes */ 一行的字节表示 unsigned long mmio_start; /* Start of Memory Mapped I/O */内存映射的I/O起始 /* (physical address) */ __u32 mmio_len; /* Length of Memory Mapped I/O */ I/O的大小 __u32 accel; /* Type of acceleration available */ 可用的加速类型 __u16 reserved[3]; /* Reserved for future compatibility */ }; 3) fb_cmap 描述设备无关的颜色映射信息。可以通过FBIOGETCMAP 和 FBIOPUTCMAP 对应的ioctl操作设定或获取颜色映射信息. struct fb_cmap { __u32 start; /* First entry */ 第一个入口 __u32 len; /* Number of entries */ 入口的数字 __u16 *red; /* Red values */ 红 __u16 *green; __u16 *blue; __u16 *transp; /* transparency, can be NULL */ 透明,可以为零 }; 4) fb_info 定义当显卡的当前状态;fb_info结构仅在内核中可见,在这个结构中有一个fb_ops指针, 指向驱动设备工作所需的函数集。 struct fb_info { char modename[40]; /* default video mode */ 默认的视频卡类型 kdev_t node; int flags; int open; /* Has this been open already ? */ 被打开过么? #define FBINFO_FLAG_MODULE 1 /* Low-level driver is a module */ struct fb_var_screeninfo var; /* Current var */ 现在的视频信息 struct fb_fix_screeninfo fix; /* Current fix */ 修正的信息 struct fb_monspecs monspecs; /* Current Monitor specs */ 现在的显示器模式 struct fb_cmap cmap; /* Current cmap */ 当前优先级 struct fb_ops *fbops; char *screen_base; /* Virtual address */ 物理基址 struct display *disp; /* initial display variable */初始化 struct vc_data *display_fg; /* Console visible on this display */ char fontname[40]; /* default font name */默认的字体 devfs_handle_t devfs_handle; /* Devfs handle for new name */ devfs_handle_t devfs_lhandle; /* Devfs handle for compat. symlink */兼容 int (*changevar)(int); /* tell console var has changed */ 告诉console变量修改了 int (*switch_con)(int, struct fb_info*); /* tell fb to switch consoles */ 告诉fb选择consoles int (*updatevar)(int, struct fb_info*); /* tell fb to update the vars */ 告诉fb更新变量 void (*blank)(int, struct fb_info*); /* tell fb to (un)blank the screen */告诉fb使用黑白模式(或者不黑) /* arg = 0: unblank */arg=0的时候黑白模式 /* arg > 0: VESA level (arg-1) */ arg>0时候选择VESA模式 void *pseudo_palette; /* Fake palette of 16 colors and the cursor's color for non palette mode */ 修正调色板 /* From here on everything is device dependent */ 现在就可以使用了 void *par; }; 5) struct fb_ops 用户应用可以使用ioctl()系统调用来操作设备,这个结构就是用一支持ioctl()的这些操作的。 struct fb_ops { /* open/release and usage marking */ struct module *owner; int (*fb_open)(struct fb_info *info, int user); int (*fb_release)(struct fb_info *info, int user); /* get non settable parameters */ int (*fb_get_fix)(struct fb_fix_screeninfo *fix, int con, struct fb_info *info); /* get settable parameters */ int (*fb_get_var)(struct fb_var_screeninfo *var, int con, struct fb_info *info); /* set settable parameters */ int (*fb_set_var)(struct fb_var_screeninfo *var, int con, struct fb_info *info); /* get colormap */ int (*fb_get_cmap)(struct fb_cmap *cmap, int kspc, int con, struct fb_info *info); /* set colormap */ int (*fb_set_cmap)(struct fb_cmap *cmap, int kspc, int con, struct fb_info *info); /* pan display (optional) */ int (*fb_pan_display)(struct fb_var_screeninfo *var, int con, struct fb_info *info); /* perform fb specific ioctl (optional) */ int (*fb_ioctl)(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg, int con, struct fb_info *info); /* perform fb specific mmap */ int (*fb_mmap)(struct fb_info *info, struct file *file, struct vm_area_struct *vma); /* switch to/from raster image mode */ int (*fb_rasterimg)(struct fb_info *info, int start); }; 6) structure map struct fb_info_gen | struct fb_info | fb_var_screeninfo | | fb_fix_screeninfo | | fb_cmap | | modename[40] | | fb_ops ---|--->ops on var | | ... | fb_open | | | fb_release | | | fb_ioctl | | | fb_mmap | struct fbgen_hwswitch \-----|-> detect | encode_fix | encode_var | decode_fix | decode_var | get_var | set_var | getcolreg | setcolreg | pan_display | blank | set_disp [编排有点困难,第一行的第一条竖线和下面的第一列竖线对齐,第一行的第二条竖线和下面的第二列竖线对齐就可以了] 这个结构 fbgen_hwswitch抽象了硬件的操作.虽然它不是必需的,但有时候很有用. 2、 fbmem.c fbmem.c 处于Framebuffer设备驱动技术的中心位置.它为上层应用程序提供系统调用也为下一层的特定硬件驱动提供接口;那些底层硬件驱动需要用到这儿的接口来向系统内核注册它们自己. fbmem.c 为所有支持FrameBuffer的设备驱动提供了通用的接口,避免重复工作. 1) 全局变量 struct fb_info *registered_fb[FB_MAX]; int num_registered_fb; 这两变量记录了所有fb_info 结构的实例,fb_info 结构描述显卡的当前状态,所有设备对应的fb_info 结构都保存在这个数组中,当一个FrameBuffer设备驱动向系统注册自己时,其对应的fb_info 结构就会添加到这个结构中,同时num_registered_fb 为自动加1. static struct { const char *name; int (*init)(void); int (*setup)(void); } fb_drivers[] __initdata= { ....}; 如果FrameBuffer设备被静态链接到内核,其对应的入口就会添加到这个表中;如果是动态加载的,即使用insmod/rmmod,就不需要关心这个表。 static struct file_operations fb_ops ={ owner: THIS_MODULE, read: fb_read, write: fb_write, ioctl: fb_ioctl, mmap: fb_mmap, open: fb_open, release: fb_release }; 这是一个提供给应用程序的接口. 2)fbmem.c 实现了如下函数. register_framebuffer(struct fb_info *fb_info); unregister_framebuffer(struct fb_info *fb_info); 这两个是提供给下层FrameBuffer设备驱动的接口,设备驱动通过这两函数向系统注册或注销自己。几乎底层设备驱动所要做的所有事情就是填充fb_info结构然后向系统注册或注销它。