核心文件:/drivers/video/fbmem.c
18.2.3.Linux帧缓冲相关数据结构与函数
1. fb_info结构体(最关键)
/* struct fb_info 结构体 */
struct fb_info {
int node;
int flags;
struct mutex lock; /* 用于 open/release/ioctl的锁 */
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; /* 显示器标准 */
struct work_struct queue; /* Framebuffer event queue 帧缓冲事件队列 */
struct fb_pixmap pixmap; /* Image hardware mapper 图像硬件mapper */
struct fb_pixmap sprite; /* Cursor hardware mapper 光标硬件mapper */
struct fb_cmap cmap; /* Current cmap 目前颜色表 */
struct list_head modelist; /* mode list */
struct fb_videomode *mode; /* current mode 目前video模式 */
#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 fb设备 */
int class_flag; /* private sysfs flags */
#ifdef CONFIG_FB_TILEBLITTING
struct fb_tile_ops *tileops; /* Tile Blitting */
#endif
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;
/* we need the PCI or similiar aperture base/size not
smem_start/size as smem_start may just be an object
allocated inside the aperture so may not actually overlap */
resource_size_t aperture_base;
resource_size_t aperture_size;
};
2.fb_ops结构体
fb_info成员变量fb_ops为指向底层操作的函数指针,这些函数需要驱动程序猿自己编写。
/* fb_ops 结构体 */fb_ops 的 fb_check_var() 成员函数用于检查可以修改的屏幕参数并调整到合适的值,而 fb_set_par() 则使得用户设置的屏幕参数在硬件上有效。
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);
/* 对于非线性布局的/常规内存映射无法工作的帧缓冲设备*/
ssize_t (*fb_read)(struct fb_info *info, char __user *buf,size_t count, loff_t *ppos);
ssize_t (*fb_write)(struct fb_info *info, const char __user *buf,size_t count, loff_t *ppos);
/* 调整可变参数,并调整到支持的值 */
int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);
/* 根据info->var设置video模式 */
int (*fb_set_par)(struct fb_info *info);
/* set color register */
int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,unsigned blue, unsigned transp, struct fb_info *info);
/* 批量设置color寄存器,设置颜色表 */
int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info);
/* 显示空白 */
int (*fb_blank)(int blank, struct fb_info *info);
/* pan display */
int (*fb_pan_display)(struct fb_var_screeninfo *var, struct fb_info *info);
/* 矩形填充 */
void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);
/* 数据复制 */
void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);
/* 图形填充 */
void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);
/* 绘制光标 */
int (*fb_cursor) (struct fb_info *info, struct fb_cursor *cursor);
/* 旋转显示 */
void (*fb_rotate)(struct fb_info *info, int angle);
/* 等待blit空闲(可选) */
int (*fb_sync)(struct fb_info *info);
/* fb特定的ioctl(可选) */
int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,unsigned long arg);
/* 处理32位的compat ioctl(可选) */
int (*fb_compat_ioctl)(struct fb_info *info, unsigned cmd,unsigned long arg);
/* fb特定的mmap */
int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);
/* get capability given var */
void (*fb_get_caps)(struct fb_info *info, struct fb_blit_caps *caps,struct fb_var_screeninfo *var);
/* teardown any resources to do with this framebuffer */
void (*fb_destroy)(struct fb_info *info);
};
3. fb_var_screeninfo 和 fb_fix_screeninfo 结构体
fb_var_screeninfo 记录用户可以修改的显示控制器参数,包括屏幕分辨率和每个像素点的比特数。
fb_fix_screeninfo 记录用户不能修改的显示控制器参数,包括屏幕缓冲区的物理地址、长度。当对帧缓冲舍内进行映射操作的时候,就是从fb_fix_screeninfo中取得缓冲区的物理地址。上述结构体都需要在驱动程序中初始化和设置。
/* fb_var_screeninfo 结构体 */
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 虚拟到可见之间的偏移 */
__u32 yoffset; /* resolution */
__u32 bits_per_pixel; /* guess what */
__u32 grayscale; /* 非0时指灰度 */
/* fb缓存的R/G/B位域 */
struct fb_bitfield red; /* bitfield in fb mem if true color, */
struct fb_bitfield green;
struct fb_bitfield blue;
struct fb_bitfield transp; /* transparency 透明度 */
__u32 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 */
/* 定时:除了pixclock本身外,其他的都以像素时钟为单位 */
__u32 pixclock; /* 像素时钟(皮秒) */
__u32 left_margin; /* 行切换:从同步到绘图之间的延迟 */
__u32 right_margin; /* 行切换:从绘图到同步之间的延迟 */
__u32 upper_margin; /* 帧切换:从同步到绘图之间的延迟 */
__u32 lower_margin; /* 帧切换:从绘图到同步之间的延迟 */
__u32 hsync_len; /* 水平同步的长度 */
__u32 vsync_len; /* 垂直同步的长度 */
__u32 sync; /* see FB_SYNC_* */
__u32 vmode; /* see FB_VMODE_* */
__u32 rotate; /* 顺时针旋转的角度 */
__u32 reserved[5]; /* Reserved for future compatibility */
};
/* fb_fix_screeninfo 结构体*/
struct fb_fix_screeninfo {
char id[16]; /* identification string eg "TT Builtin" */
unsigned long smem_start; /* fb缓存的开始位置(物理地址) */
__u32 smem_len; /* fb缓存的长度 */
__u32 type; /* see FB_TYPE_* */
__u32 type_aux; /* Interleave for interleaved Planes */
/* visual:记录屏幕使用的色彩模式 ①Monochrome(FB_VISUAL_MON01、FB_VISUAL_MON10) 每个像素都是黑或白
②Pseudo color(FB_VISUAL_PSEDOCOLOR、FB_VISUAL_STATIC_PSEUDOCOLOR), 即伪彩色,采用索引颜色显示
③True color(FB_VISUAL_TRUECOLOR),即彩色,分成红绿蓝三基色
④Direct color(FB_VISUAL_DIRECTCOLOR)每个像素偃师市也是由红绿蓝组成,不过每个颜色值是个索引,需要查表
⑤Grayscale displays,灰度显示,红、绿、蓝的值都一样
*/
__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; /* 1行的字节数 */
unsigned long mmio_start; /* 内存映射IO长度 */
__u32 mmio_len; /* 内存映射IO长度 */
__u32 accel; /* Indicate to driver which */
__u16 reserved[3]; /* Reserved for future compatibility */
};
4.fb_bitfield 结构体
fb_bitfield描述每一像素显示缓冲区的组织方式,包含位域偏移、位域长度和MSB(最高有效位)指示。
/* fb_bitfield 结构体 */
struct fb_bitfield {
__u32 offset; /* beginning of bitfield */
__u32 length; /* length of bitfield */
__u32 msb_right; /* != 0 : Most significant bit MSB在右边 */
};
5.fb_cmap结构体
fb_cmap结构体记录设备无关的颜色表信息,用户空间可以通过 ioctl() 的 FBIOGETCMAP 和 FBIOPUTCMAP 命令读取或设定 颜色表。
/* fb_cmap 结构体 */
struct fb_cmap {
__u32 start; /* First entry 第一个元素入口 */
__u32 len; /* Number of entries 原色数量 */
__u16 *red; /* Red values RGB透明度 */
__u16 *green;
__u16 *blue;
__u16 *transp; /* transparency, can be NULL */
};
/* 用户空间获取颜色表的例程对于一个256色(BPP=8)的800*600的图像,若使用红绿蓝分别用一个字节描述,则需要800*600*3=1 440 000Byte,若使用颜色表,需要800*600*1+256*3=480 768Byte的空间。
* 若BPP为8位,颜色表长度为256
* 若BPP为4位,颜色表长度为16,否则颜色表长度为0
* 因为对于BPP大于等于16的情况,使用颜色表超不划算
*/
/* 读取颜色表 */
if( (vinfo.bits_per_pixel == 8) || (vinfo.bits_per_pixel == 4) ){
screencols = (vinfo.bits_per_pixel == 8) ? 256 : 16; /* 颜色表的大小 */
int loopc;
startcmap = new fb_cmap;
startcmap->start = 0;
startcmap->len = screencols;
/* 分配颜色表的内存 */
startcmap->red = (unsigned short int*)malloc(sizeof(unsigned short int) * screencols);
startcmap->green = (unsigned short int*)malloc(sizeof(unsigned short int) * screencols);
startcmap->blue = (unsigned short int*)malloc(sizeof(unsigned short int) * screencols);
startcmap->transp = (unsigned short int*)malloc(sizeof(unsigned short int) * screencols);;
/* 获取颜色表 */
ioctl(df, FBIOGETCMAP, startcmap);
for( loopc = 0; loopc < screencols; loopc++ ){
screenclut[loopc] = qRgb(startcmap->red[loopc] >> 8, startcmap->green[loopc] >> 8, startcmap->blue[loopc] >> 8)
}
}
7.文件操作结构体
/* fb_ops */
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
};
7.注册于注销帧缓冲设备
int register_framebuffer(struct fb_info *fb_info); /* 注册成功返回0, 超过设备最大数目FB_MAX(32)返回-ENXIO */
int unregister_framebuffer(struct fb_info *fb_info);
18.3帧缓冲设备驱动结构
18.4帧缓冲设备驱动的模块加载与卸载函数
帧缓冲设备驱动的模块加载函数:
(1)申请FBI结构体的内存空间,初始化FBI结构体中固定和可变的屏幕参数。
(2)根据具体的LCD屏幕特点,完成LCD控制器硬件的初始化。
(3)申请帧缓冲设备的显示缓冲区空间。
(4)注册帧缓冲设备。
在帧缓冲设备驱动的模块加载函数中完成的工作只是注册平台驱动,初始化FBI结构体中的固定和可变参数、LCD控制器硬件的初始化、申请帧缓冲设备的显示缓冲区空间和注册帧缓冲设备的工作在平台驱动的探测函数中完成。
/* 帧缓冲设备驱动的模块加载/卸载及平台驱动的探测/移除函数模板*/
/* 平台驱动结构体 */
static struct platform_driver xxxfb_driver = {
.probe = xxxfb_probe,
.remove = xxxfb_remove,
.suspend = xxxfb_suspend,
.resume = xxxfb_resume,
.driver = {
.name = "xxx-lcd", /* 驱动名 */
.owner = THIS_MODULE,
},
};
/* 平台驱动探测函数 */
static int __init xxxfb_probe(...)
{
struct fb_info* info;
/* 分配fb_info结构体 */
info = framebuffer_alloc(...);
info->screen_base = framebuffer_virtual_memory;
info->var = xxxfb_var;
info->fix = xxxfb_fix;
/* 分配显示缓冲区 */
alloc_dis_buffer(...);
/* 初始化LCD控制器 */
lcd_init(...);
/* 检查可变参数 */
xxxfb_check_var(&info->var, info);
/* 注册fb_info */
if( register_framebuffer(info)<0 )
return -EINVAL;
return 0;
}
/* 平台驱动移除函数 */
static void __exit xxxfb_remove(...)
{
struct fb_info* info = dev_get_drv_data(dev);
if(info){
unregister_framebuffer(info);
dealloc_dis_buffer(...); /* 释放缓冲区 */
framebuffer_release(info); /* 注销 fb_info */
}
}
int __init xxxfb_init(void)
{
return platform_driver_register(&xxxfb_driver); /* 注册平台驱动 */
}
static void __exit xxxfb_cleanup()
{
platform_driver_unregister(&xxxfb_driver);
}
module_init(xxxfb_init);
module_exit(xxxfb_cleanup);
18.5 帧缓冲设备显示缓冲区的申请与释放
在分配缓冲区时一定要考虑cache的一致性问题,因为系统往往会通过DMA方式搬移显示数据。合适的方式是使用 dma_alloc_writecombine() 函数分配一段 writecombining 区域,对应的 writecombining 区域由 dma_free_writecombine() 函数释放。
writecombing 意味着“写合并”,它允许写入的数据被合并,并临时保存在写合并缓冲区(WCB)中,直到进行一次 burst 传输而不再需要多次 single 传输。通过 dma_alloc_writecombine() 分配的显示缓冲区不会出现 cache 一致性问题,这一点类似 dma_alloc_coherent()。
/* 帧缓冲设备显示缓冲区的分配与释放 */
static int __init xxxfb_map_video_memory(struct xxxfb_info *fbi)
{
fbi->map_size = PAGE_ALIGN(fbi->fb->fix.smem_len + PAGE_SIZE);
fbi->map_cpu = dma_alloc_writecombine(fbi->dev, fbi->map_size, &fbi->map_dma, GFP_KERNEL); /* 分配内存 */
fbi->map_size = fbi->fb->fix.smem_len; /* 显示缓冲区大小 */
if( fbi->map_cpu ){
memset(fbi->map_cpu, 0xf0, fbi->map_size);
fbi->screen_dma = fbi->map_dma;
fbi->fb->screen_base = fbi->map_cpu;
fbi->fb->fix.smem_start = fbi->screen_dma;
}
return fbi->map_cpu ? 0 : -ENOMEM;
}
static inline void xxxfb_unmap_video_memory(struct s3c2410fb_info* fbi)
{
/* 释放显示缓冲区 */
dma_free_writecombine(fbi->dev, fbi->map_size, fbi->map_cpu, fbi->map_dma);
}
18.6 帧缓冲设备的参数设置
18.6.1 定时参数
FBI结构体可变参数var中的left_margin、right_margin、upper_margin、lower_margin、hsync_len和vsync_len直接查LCD数据手册就可以得到。
18.6.2 像素时钟
FBI可变参数var中的pixclock意味着像素时钟,例如,如果为28.37516MHz,那么画一个像素需要35242ps(皮秒):
1/(28.37516MHz) = 35.242E-9s
如果屏幕的分辨率是640*480,显示一行的时间是:640*35.242E-9s = 22.555E-6 s
每条扫描线是640,但是水平回扫和水平同步也需要时间,假设水平回扫和同步需要272个像素时钟,因此,画一条扫描线完整的时间是:(640+272)* 35.242E-9 s - 9 s = 32.141E-6 s
可以计算出水平扫描率大约是32kHz:1/(32.141E-6 s) = 31.113E3 Hz
完整的屏幕有480条线,但是垂直回扫和垂直同步也需要时间,假设垂直回扫和垂直同步需要49个像素时钟,因此,画一个完整的屏幕的时间是:(480+49)*32.141E-6 s = 17.002E-3 s
可以计算出垂直扫描率大约是59kHz:1/(17.002E-3 s) = 58.815 Hz
这意味着屏幕数据每秒钟大约刷新59次。
18.6.3 颜色位域
FBI可变参数var中的red、green、blue、位域的设置直接由显示缓冲区与显示点的对应关系决定,例如,对于RGB565模式,red占居5位,偏移11位,green占居6位,偏移5位,blue占居5位,偏移0位,即:
fbinfo->var.red.offset = 11;
fbinfo->var.green.offset = 5;
fbinfo->var.blue.offset = 0;
fbinfo->var.transp.offset = 0;
fbinfo->var.red.length = 5;
fbinfo->var.green.length = 6;
gbinfo->var.blue.length = 5;
18.6.4 固定参数
FBI固定参数 fix 中 smem_start 指示帧缓冲设备显示缓冲区的首地址,smem_len 为帧缓冲设备显示缓冲区的大小,计算公式为:
smem_len = max_xres * max_yres * max_bpp;
即:帧缓冲设备显示缓冲区的大小 = 最大的 x 解析度 * 最大的 y 解析度 * 最大的 BPP;
18.7 帧缓冲设备驱动的 fb_ops 成员函数
fb_check_var() 用于调整可变参数,并修正为硬件所支持的值;fb_set_par() 则根据屏幕参数设置具体读写LCD控制器的寄存器以使得LCD控制器进入相应的工作状态。
对于 fb_ops 中的 fb_fillrect() 、 fb_copyarea() 和 fb_imageblit() 成员函数,通常直接使用对应的通用的 cfb_fillrect() 、cfb_copyarea() 和 cfb_imageblit() 函数即可。cfb_fillrect() 函数定义在 drivers/video/cfbfillrect.c 文件中,cfb_copyarea() 定义在 /drivers/video/cfbcopyarea.c 文件中, cfb_imageblit() 定义在 /drivers/cfbimagblt.c 文件中。
/* fb_setcolreg() 函数模板,实现伪颜色表(针对 FB_VISUAL_TRUECOLOCK、FB_VISUAL_DIRECTCOLOR模式)和颜色表的填充*/
static int xxxfb_setcolreg(unsigned regno, unsigned red, unsigned green, unsigned blue, unsigned transp, struct fb_info * info)
{
struct xxxfb_info* fbi = info->par;
unsigned int val;
switch( fbi->fb->fix.visual ){
case FB_VISUAL_TRUECOLOR:
/* 真彩色,设置伪颜色表 */
if( regno < 16 ){ /* 伪颜色表只有16个成员,实际上,他们对应16种控制台颜色,logo显示也会使用伪颜色表 */
u32 * pal = fbi->fb->pseudo_palette;
val = chan_to_field(red, &fbi->fb->var.red);
val |= chan_to_field(green, &fbi->fb->var.green);
val |= charn_to_field(blue, &fbi->fb->var.blue);
pal[regno] = val;
}
break;
case FB_VISUAL_PSEUDOCOLOR:
if( regno < 256 ){
/* RGB565模式 */
val = ( (red >> 0) & 0xf800 );
val |= ( (green >> 5) & 0x07e0 );
val |= ( (blue >> 11) & 0x001f );
writel(val, XXX_TFTPAL(regno));
schedule_palette_update(fbi, regno, val);
}
break;
...
}
return 0;
}
18.8 LCD设备驱动的读写、mmap 和 ioctl 函数
(仔细分析)
18.9 帧缓冲设备的用户空间访问
通过/dev/fbn,应用程序可进行的针对帧缓冲设备的操作主要有:
(1)读/写 /dev/fbn : 相当于读写帧缓冲区。例如 cp /dev/fb0 tmp 可以将当前屏幕的内容复制到一个文件中。cp tmp > /dev/fb0则讲图像文件tmp显示在屏幕上。
(2)映射操作:对于帧缓冲设备,可通过 mmap() 映射操作将屏幕缓冲区的物理地址映射到用户控件的一段虚拟地址中,之后用户控件就可以通过读写这段虚拟地址访问屏幕缓冲区,在屏幕上绘图。而且若干个进程可以映射到同一个显示缓冲区。实际上,使用帧缓冲设备的应用程序都是通过映射操作来显示图形的。
(3)I/O控制:对于帧缓冲设备,对设备文件的 ioctl() 操作可读取/设置显示设备及屏幕的参数,如分辨率、显示颜色数、屏幕大小等。
应用程序操作/dev/fbn的一般步骤:
(1)打开 /dev/fbn 设备文件。
(2)用 ioctl() 操作取得当前显示屏幕的参数,如分辨率、每个像素点的比特位数和偏移。根据屏幕参数课计算屏幕缓冲区的大小。
(3)将屏幕缓冲区映射到用户空间。
(4)映射后就可以直接读/写屏幕缓冲区进行绘图和图片显示了。
/* 用户空间访问帧缓冲设备显示缓冲区范例 */
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/mman.h>
int main()
{
int fbfd = 0;
struct fb_var_screeninfo vinfo;
unsigned long screensize = 0;
char* fbp = 0;
int x = 0, y = 0;
int i = 0;
fbfd = open("/dev/fb0", O_RDWR);
if( !fbfd ){
printf("Error:cannot open framebuffer device.\n");
exit(1);
}
printf("The framebuffer device was opened successfully.\n");
//Get Variable screen information
if(ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo)){
printf("Error reading variable information.\n");
exit(1);
}
printf("%dx%d, %dbpp\n", vinfo.xres, vinfo.yres, vinfo.bits_per_pixel);
if(vinfo.bits_per_pixel != 16){
printf("Error:not supported bits_per_pixel,it only supports 16 bit color\n");
exit(1);
}
//Figure out the size of the screen in bytes
screensize = vinfo.xres * vinfo.yres * 2;
//Map the device to memory
fbp = (char*)mmap(0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fbfd, 0);
if( (int)fbp == -1 ){
printf("Error:failed to map framebuffer device to memory.\n");
exit(4);
}
printf("The framebuffer device was mapped to memory successfully.\n");
//Draw 3 rect with graduated RED/GREEN/BLUE
for(i = 0; i < 3; i++ ){
for(y = i * (vinfo.yres / 3); y < (i + 1) * (vinfo.yres / 3); y++ ){
for(x = 0; x < vinfo.xres; x++ ){
long location = x * 2 + y * vinfo.xres * 2;
int r = 0, g = 0, b = 0;
unsigned short rgb;
if(i == 0)
r = ((x * 1.0) / vinfo.xres) * 32;
if(i == 1)
g = ((x * 1.0) / vinfo.xres) * 64;
if(i == 2)
b = ((x * 1.0) / vinfo.xres) * 32;
rgb = (r << 11) | (g << 5) | b;
*((unsigned short*)(fbp + location)) = rgb;
}
}
}
munmap(fbp, screensize);
close(fbfd);
return 0;
}