在Linux内核中,Framebuffer(帖缓冲)驱动是显示驱动的标准,Framebuffer
将显示设备抽象为帖缓冲区,用户通过内存映射到进程地址空间之后,就可以直
接进行读写操作,且写操作可以立即在屏幕上进行显示,在Linux内核
/linux/drivers/video/下有相关的显示驱动与接口,其中Frmaebuffer驱动接口为
fbmem.c。
framebuffer驱动架构如下:
由此我们可以看出,fbmem.c是整个framebuffer的核心,它有应用程序调用的接口,又跟具体设备打交道。在具体驱动中,fd_info是核心结构,所有的操作都是围绕这个结构展开。
所以在解析源码之前,首先先介绍一些重要的结构。
在fb.h中:
Fb_info是最关键的一个数据结构。它包含了关于帧缓冲设备的属性和操作的完整描述。
struct fb_info {
int 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图像硬件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 */
int class_flag; /* private sysfs flags */
#ifdef CONFIG_FB_TILEBLITTING
struct fb_tile_ops *tileops; /* Tile Blitting图块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伪16色颜色表*/
#define FBINFO_STATE_RUNNING0
#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;
};
Fb_info中的fb_ops结构为指向底层操作函数的指针。这些函数是需要驱动工程师编写实现的。
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);/*释放设备*/
/* For framebuffers with strange non linear layouts or that do not
* work with normal memory mapped access
*/
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);
/* checks var and eventually tweaks it to something supported,
* DO NOT MODIFY PAR检查可变参数并调整到支持的值*/
int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);
/* set the video mode according to info->var根据info->var设置video模式*/
int (*fb_set_par)(struct fb_info *info);
/*设置color寄存器*/
/* set color register */
int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
unsigned blue, unsigned transp, struct fb_info *info);
/*批量设置color寄存器,设置颜色表*/
/* set color registers in batch */
int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info);
/*显示空白*/
/* blank display */
int (*fb_blank)(int blank, struct fb_info *info);
/* pan display */
int (*fb_pan_display)(struct fb_var_screeninfo *var, struct fb_info *info);
/* Draws a rectangle矩形填充*/
void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);
/* Copy data from area to another从其他地方复制数据*/
void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);
/* Draws a image to the display图形填充*/
void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);
/* Draws cursor */
int (*fb_cursor) (struct fb_info *info, struct fb_cursor *cursor);
/* Rotates the display */
void (*fb_rotate)(struct fb_info *info, int angle);
/* wait for blit idle, optional */
int (*fb_sync)(struct fb_info *info);
/* perform fb specific ioctl (optional) */
int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,
unsigned long arg);
/* Handle 32bit compat ioctl (optional) */
int (*fb_compat_ioctl)(struct fb_info *info, unsigned cmd,
unsigned long arg);
/* perform fb specific 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);
};
fb_info结构中的fb_var_screeninfo记录用户可修改的显示器控制参数,包括屏幕的分辨率和每个像素比特数。其中结构中xres定义一行有多少像素点,yres定义一列由多少像素点,bits_per_pixel定义每个点用了多少字节表示。
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 Graylevels instead of colors非0时为灰度*/
struct fb_bitfield red; /* bitfield in fb mem if true color, */
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; /* (OBSOLETE) see fb_info.flags */
/* 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 rotate; /* angle we rotate counter clockwise顺时针旋转的角度*/
__u32 reserved[5]; /* Reserved for future compatibility保留*/
};
fb_info结构中的fd_fix_screeninfo成员记录了用户的不可修改的显示器参数,如屏幕尺寸,RGB,缓冲区物理地址,长度等,当对设备进行映射时,就从该结构中拿出物理地址。
struct fb_fix_screeninfo {
char id[16]; /* identification string eg "TT Builtin"字符串形式ide标示符*/
unsigned long smem_start; /* Start of frame buffer mem fb缓存的开始位置*/
/* (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 1行的字节数 */
unsigned long mmio_start; /* Start of Memory Mapped I/O 内存映射的I/O开始i位置 */
/* (physical address) */
__u32 mmio_len; /* Length of Memory Mapped I/O 内存映射IO长度*/
__u32 accel; /* Indicate to driver which */
/* specific chip/card we have */
__u16 reserved[3]; /* Reserved for future compatibility保留*/
};
fb_var_screeninfo结构中的fb_bitfield结构描述了每一像素显示缓冲区的组织方式。
struct fb_bitfield {
__u32 offset; /* beginning of bitfield位域偏移*/
__u32 length; /* length of bitfield 位域长度*/
__u32 msb_right; /* != 0 : Most significant bit is */
/* right */
};
如果驱动支持framebuffer,那么首先执行fbmem.c中的subsys_initcall(fbmem_init)。因为这是初始化framebuffer子系统。
static int __init
fbmem_init(void)
{
/*创建proc相关文件在/proc/下fb*/
proc_create("fb", 0, NULL, &fb_proc_fops);
/*注册cdev主设备号为29*/
if (register_chrdev(FB_MAJOR,"fb",&fb_fops))
printk("unable to get major %d for fb devs\n", FB_MAJOR);
/*创建class类,在/sys/class下*/
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;
}
fbmem_init做了(1)创建proc文件(2)注册cdev主设备号为29(3)创建class类
fb子系统初始化之后,下面就是具体驱动的实现了。我们以s3c2410fb.c为分析对象:
首先执行module_init(s3c2410fb_init)。
int __init s3c2410fb_init(void)
{
/*注册平台驱动*/
int ret = platform_driver_register(&s3c2410fb_driver);
if (ret == 0)
ret = platform_driver_register(&s3c2412fb_driver);
return ret;
}
s3c2410fb_init做了(1)注册平台驱动.
这些还要查一句:
之前我们已经提到:在mini2440_machine_init函数中,很多设备都是以platform_device的形式加载进内核。其中就包括LCD。
s3c24xx_fb_set_platdata(&mini2440_fb_info);
void __init s3c24xx_fb_set_platdata(struct s3c2410fb_mach_info *pd)
{
struct s3c2410fb_mach_info *npd;
npd = kmalloc(sizeof(*npd), GFP_KERNEL);
if (npd) {
memcpy(npd, pd, sizeof(*npd));
s3c_device_lcd.dev.platform_data = npd;
} else {
printk(KERN_ERR "no memory for LCD platform data\n");
}
}
这个函数的目的就是s3c_device_lcd.dev.platform_data=mini2440_fb_info。
static struct s3c2410fb_mach_info mini2440_fb_info __initdata = {
.displays = &mini2440_lcd_cfg,
.num_displays = 1,
.default_display = 0,
.gpccon = 0xaa955699,
.gpccon_mask = 0xffc003cc,
.gpcup = 0x0000ffff,
.gpcup_mask = 0xffffffff,
.gpdcon = 0xaa95aaa1,
.gpdcon_mask = 0xffc0fff0,
.gpdup = 0x0000faff,
.gpdup_mask = 0xffffffff,
.lpcsel = 0xf82,
};
static struct s3c2410fb_display mini2440_lcd_cfg __initdata = {
#if !defined (LCD_CON5)
.lcdcon5 = S3C2410_LCDCON5_FRM565 |
S3C2410_LCDCON5_INVVLINE |
S3C2410_LCDCON5_INVVFRAME |
S3C2410_LCDCON5_PWREN |
S3C2410_LCDCON5_HWSWP,
#else
.lcdcon5 = LCD_CON5,
#endif
.type = S3C2410_LCDCON1_TFT,
.width = LCD_WIDTH,
.height = LCD_HEIGHT,
.pixclock = LCD_PIXCLOCK,
.xres = LCD_WIDTH,
.yres = LCD_HEIGHT,
.bpp = 16,
.left_margin = LCD_LEFT_MARGIN + 1,
.right_margin = LCD_RIGHT_MARGIN + 1,
.hsync_len = LCD_HSYNC_LEN + 1,
.upper_margin = LCD_UPPER_MARGIN + 1,
.lower_margin = LCD_LOWER_MARGIN + 1,
.vsync_len = LCD_VSYNC_LEN + 1,
};
mini2440_lcd_cfg 和 mini2440_fb_info之后都将跟fb_info结构中的var fix结构产生联系。
LCD的platform_device结构:
struct platform_device s3c_device_lcd = {
.name = "s3c2410-lcd",
.id = -1,
/***num_resources = 2***/
.num_resources = ARRAY_SIZE(s3c_lcd_resource),
.resource = s3c_lcd_resource,
.dev = {
.dma_mask = &s3c_device_lcd_dmamask,
.coherent_dma_mask = 0xffffffffUL
}
};
回到s3c2410fb_init,加载完platform_driver之后:
static struct platform_driver s3c2410fb_driver = {
.probe = s3c2410fb_probe,
.remove = s3c2410fb_remove,
.suspend = s3c2410fb_suspend,
.resume = s3c2410fb_resume,
.driver = {
.name = "s3c2410-lcd",
.owner = THIS_MODULE,
},
};
就会执行s3c2410fb_probe函数。
static int __init s3c2410fb_probe(struct platform_device *pdev)
{
return s3c24xxfb_probe(pdev, DRV_S3C2410);
}
static int __init s3c24xxfb_probe(struct platform_device *pdev,
enum s3c_drv_type drv_type)
{
struct s3c2410fb_info *info;
struct s3c2410fb_display *display;
struct fb_info *fbinfo;
struct s3c2410fb_mach_info *mach_info;
struct resource *res;
int ret;
int irq;
int i;
int size;
u32 lcdcon1;
/*获取mini2440_fb_info结构*/
mach_info = pdev->dev.platform_data;
if (mach_info == NULL) {
dev_err(&pdev->dev,
"no platform data for lcd, cannot attach\n");
return -EINVAL;
}
/*mach_info->default_display为0*/
if (mach_info->default_display >= mach_info->num_displays) {
dev_err(&pdev->dev, "default is %d but only %d displays\n",
mach_info->default_display, mach_info->num_displays);
return -EINVAL;
}
/*display就是 mini2440_lcd_cfg结构*/
display = mach_info->displays + mach_info->default_display;
/***获取中断号***/
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "no irq for device\n");
return -ENOENT;
}
/***分配fb_info结构,并将fbinfo->dev指向pdev->dev***/
fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);
if (!fbinfo)
return -ENOMEM;
/***将pdev结构和fbinfo结构联系起来 pdev->dev->p->data = fbinfo***/
platform_set_drvdata(pdev, fbinfo);
info = fbinfo->par;
info->dev = &pdev->dev;
info->drv_type = drv_type;
/***获取LCDCON寄存器对应的内存资源***/
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res == NULL) {
dev_err(&pdev->dev, "failed to get memory registers\n");
ret = -ENXIO;
goto dealloc_fb;
}
size = (res->end - res->start) + 1;
/***申请内存区域***/
info->mem = request_mem_region(res->start, size, pdev->name);
if (info->mem == NULL) {
dev_err(&pdev->dev, "failed to get memory region\n");
ret = -ENOENT;
goto dealloc_fb;
}
/***io重映射***/
info->io = ioremap(res->start, size);
if (info->io == NULL) {
dev_err(&pdev->dev, "ioremap() of registers failed\n");
ret = -ENXIO;
goto release_mem;
}
/***将映射后LCDINTPND的地址info->irq_base***/
info->irq_base = info->io + ((drv_type == DRV_S3C2412) ? S3C2412_LCDINTBASE : S3C2410_LCDINTBASE);
dprintk("devinit\n");
/*填充fix的id域*/
strcpy(fbinfo->fix.id, driver_name);
/* Stop the video */
/***写LCDCON1寄存器***/
lcdcon1 = readl(info->io + S3C2410_LCDCON1);
/***允许视频输出和LCD信号***/
writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, info->io + S3C2410_LCDCON1);
/***填写固定参数结构***/
fbinfo->fix.type = FB_TYPE_PACKED_PIXELS;
fbinfo->fix.type_aux = 0;
fbinfo->fix.xpanstep = 0;
fbinfo->fix.ypanstep = 0;
fbinfo->fix.ywrapstep = 0;
fbinfo->fix.accel = FB_ACCEL_NONE;
/*填充可变参数*/
fbinfo->var.nonstd = 0;
fbinfo->var.activate = FB_ACTIVATE_NOW;
fbinfo->var.accel_flags = 0;
fbinfo->var.vmode = FB_VMODE_NONINTERLACED;
/*填充操作函数集*/
fbinfo->fbops = &s3c2410fb_ops;
fbinfo->flags = FBINFO_FLAG_DEFAULT;
fbinfo->pseudo_palette = &info->pseudo_pal;
for (i = 0; i < 256; i++)
info->palette_buffer[i] = PALETTE_BUFF_CLEAR;
/***申请中断***/
ret = request_irq(irq, s3c2410fb_irq, IRQF_DISABLED, pdev->name, info);
if (ret) {
dev_err(&pdev->dev, "cannot get irq %d - err %d\n", irq, ret);
ret = -EBUSY;
goto release_regs;
}
/***获取LCD时钟***/
info->clk = clk_get(NULL, "lcd");
if (!info->clk || IS_ERR(info->clk)) {
printk(KERN_ERR "failed to get lcd clock source\n");
ret = -ENOENT;
goto release_irq;
}
/***开启LCD时钟***/
clk_enable(info->clk);
dprintk("got and enabled clock\n");
msleep(1);
info->clk_rate = clk_get_rate(info->clk);
/* find maximum required memory size for display */
/*计算一帧数据所占字节数*/
for (i = 0; i < mach_info->num_displays; i++) {
unsigned long smem_len = mach_info->displays[i].xres;/*320*/
smem_len *= mach_info->displays[i].yres; /*240*/
smem_len *= mach_info->displays[i].bpp;/*16*/
smem_len >>= 3;
if (fbinfo->fix.smem_len < smem_len)
/*将一帧数据大小赋值给smem_len*/
fbinfo->fix.smem_len = smem_len;
}
/* Initialize video memory */
/*初始化LCD缓存*/
ret = s3c2410fb_map_video_memory(fbinfo);
if (ret) {
printk(KERN_ERR "Failed to allocate video RAM: %d\n", ret);
ret = -ENOMEM;
goto release_clock;
}
dprintk("got video memory\n");
/*将 mini2440_lcd_cfg的xres yres bpp赋值给var*/
fbinfo->var.xres = display->xres;
fbinfo->var.yres = display->yres;
fbinfo->var.bits_per_pixel = display->bpp;
/*初始化相应寄存器*/
s3c2410fb_init_registers(fbinfo);
/*填充var可变参数*/
s3c2410fb_check_var(&fbinfo->var, fbinfo);
/*注册时钟频率*/
ret = s3c2410fb_cpufreq_register(info);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to register cpufreq\n");
goto free_video_memory;
}
/*注册帧缓冲设备*/
ret = register_framebuffer(fbinfo);
if (ret < 0) {
printk(KERN_ERR "Failed to register framebuffer device: %d\n",
ret);
goto free_cpufreq;
}
/* create device files */
/*创建/sys设备文件*/
ret = device_create_file(&pdev->dev, &dev_attr_debug);
if (ret) {
printk(KERN_ERR "failed to add debug attribute\n");
}
printk(KERN_INFO "fb%d: %s frame buffer device\n",
fbinfo->node, fbinfo->fix.id);
return 0;
free_cpufreq:
s3c2410fb_cpufreq_deregister(info);
free_video_memory:
s3c2410fb_unmap_video_memory(fbinfo);
release_clock:
clk_disable(info->clk);
clk_put(info->clk);
release_irq:
free_irq(irq, info);
release_regs:
iounmap(info->io);
release_mem:
release_resource(info->mem);
kfree(info->mem);
dealloc_fb:
platform_set_drvdata(pdev, NULL);
framebuffer_release(fbinfo);
return ret;
}
由上可知:probe函数主要做了以下事情:
(1) 分配一个fb_info结构
(2) 获取平台资源(中断和IO基地址)
(3) 进行io重映射和中断申请,并将重映射地址和中断基地址给info相应结构
(4) 初始化LCD缓存
(5) 填充fb_info结构中的fix域和var域及fbops等:通过mini2440_lcd_cfg结构填充var
(6) 开启LCD时钟
(7) 初始化相应LCD寄存器
(8) 注册帧缓冲
(9) 创建/sys设备文件
对于(4)对应的函数:
static int __init s3c2410fb_map_video_memory(struct fb_info *info)
{
struct s3c2410fb_info *fbi = info->par;
dma_addr_t map_dma;
unsigned map_size = PAGE_ALIGN(info->fix.smem_len);
dprintk("map_video_memory(fbi=%p) map_size %u\n", fbi, map_size);
/*分配内存(虚拟地址)*/
info->screen_base = dma_alloc_writecombine(fbi->dev, map_size,
&map_dma, GFP_KERNEL);
if (info->screen_base) {
/* prevent initial garbage on screen */
/***对内存清空***/
dprintk("map_video_memory: clear %p:%08x\n",
info->screen_base, map_size);
memset(info->screen_base, 0x00, map_size);
/***此时的map_dma是页表首地址(物理地址)***/
info->fix.smem_start = map_dma;
dprintk("map_video_memory: dma=%08lx cpu=%p size=%08x\n",
info->fix.smem_start, info->screen_base, map_size);
}
return info->screen_base ? 0 : -ENOMEM;
}
对于(8)注册帧缓冲:
int
register_framebuffer(struct fb_info *fb_info)
{
int i;
struct fb_event event;
struct fb_videomode mode;
if (num_registered_fb == FB_MAX)
return -ENXIO;
if (fb_check_foreignness(fb_info))
return -ENOSYS;
/* check all firmware fbs and kick off if the base addr overlaps */
/*检测所有已经注册的fb结构是否跟要注册的fd结构缓冲地址重叠*/
for (i = 0 ; i < FB_MAX; i++) {
if (!registered_fb[i])
continue;
if (registered_fb[i]->flags & FBINFO_MISC_FIRMWARE) {
if (fb_do_apertures_overlap(registered_fb[i], fb_info)) {
printk(KERN_ERR "fb: conflicting fb hw usage "
"%s vs %s - removing generic driver\n",
fb_info->fix.id,
registered_fb[i]->fix.id);
unregister_framebuffer(registered_fb[i]);
break;
}
}
}
/*注册计数加一*/
num_registered_fb++;
for (i = 0 ; i < FB_MAX; i++)
/*找到数组中第一个没有被注册的元素*/
if (!registered_fb[i])
break;
/*将所在数组的位置信息给fb_info方便以后查找*/
fb_info->node = i;
mutex_init(&fb_info->lock);
mutex_init(&fb_info->mm_lock);
/*创建设备文件*/
fb_info->dev = device_create(fb_class, fb_info->device,
MKDEV(FB_MAJOR, i), NULL, "fb%d", i);
if (IS_ERR(fb_info->dev)) {
/* Not fatal */
printk(KERN_WARNING "Unable to create device for framebuffer %d; errno = %ld\n", i, PTR_ERR(fb_info->dev));
fb_info->dev = NULL;
} else
fb_init_device(fb_info);
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_info->pixmap.offset = 0;
if (!fb_info->pixmap.blit_x)
fb_info->pixmap.blit_x = ~(u32)0;
if (!fb_info->pixmap.blit_y)
fb_info->pixmap.blit_y = ~(u32)0;
if (!fb_info->modelist.prev || !fb_info->modelist.next)
INIT_LIST_HEAD(&fb_info->modelist);
/*填充mode结构*/
fb_var_to_videomode(&mode, &fb_info->var);
fb_add_videomode(&mode, &fb_info->modelist);
/*将fb_info结构赋予数组*/
registered_fb[i] = fb_info;
event.info = fb_info;
if (!lock_fb_info(fb_info))
return -ENODEV;
fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
unlock_fb_info(fb_info);
return 0;
}
我们从上可知:register_framebuffer函数有这样一个技巧:有一个registered_fb全局数组,这个数组是存放所以已注册的fb结构,当我们应用程序open ioctl等通过次设备号就能找到fb结构
register_framebuffer主要做了以下事情:
(1) 检测registered_fb数组中已经注册的fb,判断他们的缓冲地址是否跟要注册的地址冲突,如果冲突,则将已经注册的unregister。
(2) 找到registered_fb中第一个没有被注册的位置,并将位置信息给fb结构(其实就是次设备号)
(3) 创建设备文件
(4) 将fb_info记录到registered_fb数组。
就此,fb基本的驱动就实现了。
当应用程序使用open系统调用打开/dev/fb0时,经过一系列的过程到了fbmem.c的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;
int res = 0;
/*通过次设备号获得fd_info结构*/
if (fbidx >= FB_MAX)
return -ENODEV;
/*通过次设备号在全局数据中找到对应的fb_info结构*/
info = registered_fb[fbidx];
if (!info)
request_module("fb%d", fbidx);
info = registered_fb[fbidx];
if (!info)
return -ENODEV;
mutex_lock(&info->lock);
if (!try_module_get(info->fbops->owner)) {
res = -ENODEV;
goto out;
}
/*将info结构赋给file私有指针域*/
file->private_data = info;
/*如果ops域中有open函数则执行open函数*/
if (info->fbops->fb_open) {
res = info->fbops->fb_open(info,1);
if (res)
module_put(info->fbops->owner);
}
#ifdef CONFIG_FB_DEFERRED_IO
if (info->fbdefio)
fb_deferred_io_open(info, inode, file);
#endif
out:
mutex_unlock(&info->lock);
return res;
}
从上可以看出:针对s3c2410的fb来说,open函数的作用仅仅是将2410对应的fb_info结构赋值于file的私有指针域。
对于fb来说,read write系统调用没有意义,下面介绍Ioctl.
static long fb_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct inode *inode = file->f_path.dentry->d_inode;
int fbidx = iminor(inode);
/*通过次设备号获得fb_info结构*/
struct fb_info *info = registered_fb[fbidx];
return do_fb_ioctl(info, cmd, arg);
}
static long do_fb_ioctl(struct fb_info *info, unsigned int cmd,
unsigned long arg)
{
struct fb_ops *fb;
struct fb_var_screeninfo var;
struct fb_fix_screeninfo fix;
struct fb_con2fbmap con2fb;
struct fb_cmap cmap_from;
struct fb_cmap_user cmap;
struct fb_event event;
void __user *argp = (void __user *)arg;
long ret = 0;
switch (cmd) {
/*获得可变参数信息*/
case FBIOGET_VSCREENINFO:
if (!lock_fb_info(info))
return -ENODEV;
var = info->var;
unlock_fb_info(info);
ret = copy_to_user(argp, &var, sizeof(var)) ? -EFAULT : 0;
break;
case FBIOPUT_VSCREENINFO:
/*修改可变参数信息*/
if (copy_from_user(&var, argp, sizeof(var)))
return -EFAULT;
if (!lock_fb_info(info))
return -ENODEV;
acquire_console_sem();
info->flags |= FBINFO_MISC_USEREVENT;
ret = fb_set_var(info, &var);
info->flags &= ~FBINFO_MISC_USEREVENT;
release_console_sem();
unlock_fb_info(info);
if (!ret && copy_to_user(argp, &var, sizeof(var)))
ret = -EFAULT;
break;
case FBIOGET_FSCREENINFO:
/*获取固定参数*/
if (!lock_fb_info(info))
return -ENODEV;
fix = info->fix;
unlock_fb_info(info);
ret = copy_to_user(argp, &fix, sizeof(fix)) ? -EFAULT : 0;
break;
case FBIOPUTCMAP:
if (copy_from_user(&cmap, argp, sizeof(cmap)))
return -EFAULT;
ret = fb_set_user_cmap(&cmap, info);
break;
case FBIOGETCMAP:
if (copy_from_user(&cmap, argp, sizeof(cmap)))
return -EFAULT;
if (!lock_fb_info(info))
return -ENODEV;
cmap_from = info->cmap;
unlock_fb_info(info);
ret = fb_cmap_to_user(&cmap_from, &cmap);
break;
case FBIOPAN_DISPLAY:
/*滚屏*/
if (copy_from_user(&var, argp, sizeof(var)))
return -EFAULT;
if (!lock_fb_info(info))
return -ENODEV;
acquire_console_sem();
ret = fb_pan_display(info, &var);
release_console_sem();
unlock_fb_info(info);
if (ret == 0 && copy_to_user(argp, &var, sizeof(var)))
return -EFAULT;
break;
case FBIO_CURSOR:
ret = -EINVAL;
break;
case FBIOGET_CON2FBMAP:
if (copy_from_user(&con2fb, argp, sizeof(con2fb)))
return -EFAULT;
if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES)
return -EINVAL;
con2fb.framebuffer = -1;
event.data = &con2fb;
if (!lock_fb_info(info))
return -ENODEV;
event.info = info;
fb_notifier_call_chain(FB_EVENT_GET_CONSOLE_MAP, &event);
unlock_fb_info(info);
ret = copy_to_user(argp, &con2fb, sizeof(con2fb)) ? -EFAULT : 0;
break;
case FBIOPUT_CON2FBMAP:
if (copy_from_user(&con2fb, argp, sizeof(con2fb)))
return -EFAULT;
if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES)
return -EINVAL;
if (con2fb.framebuffer < 0 || con2fb.framebuffer >= FB_MAX)
return -EINVAL;
if (!registered_fb[con2fb.framebuffer])
request_module("fb%d", con2fb.framebuffer);
if (!registered_fb[con2fb.framebuffer]) {
ret = -EINVAL;
break;
}
event.data = &con2fb;
if (!lock_fb_info(info))
return -ENODEV;
event.info = info;
ret = fb_notifier_call_chain(FB_EVENT_SET_CONSOLE_MAP, &event);
unlock_fb_info(info);
break;
case FBIOBLANK:
if (!lock_fb_info(info))
return -ENODEV;
acquire_console_sem();
info->flags |= FBINFO_MISC_USEREVENT;
ret = fb_blank(info, arg);
info->flags &= ~FBINFO_MISC_USEREVENT;
release_console_sem();
unlock_fb_info(info);
break;
default:
if (!lock_fb_info(info))
return -ENODEV;
fb = info->fbops;
if (fb->fb_ioctl)
ret = fb->fb_ioctl(info, cmd, arg);
else
ret = -ENOTTY;
unlock_fb_info(info);
}
return ret;
}
获取fix var参数很简单,就是copy_to_user就可以,下面浅析修改var参数。
case FBIOPUT_VSCREENINFO:
/*修改可变参数信息*/
if (copy_from_user(&var, argp, sizeof(var)))
return -EFAULT;
if (!lock_fb_info(info))
return -ENODEV;
acquire_console_sem();
info->flags |= FBINFO_MISC_USEREVENT;
ret = fb_set_var(info, &var);
info->flags &= ~FBINFO_MISC_USEREVENT;
release_console_sem();
unlock_fb_info(info);
if (!ret && copy_to_user(argp, &var, sizeof(var)))
ret = -EFAULT;
break;
首先调用fb_set_var。
int
fb_set_var(struct fb_info *info, struct fb_var_screeninfo *var)
{
int flags = info->flags;
int ret = 0;
/*如果var参数存在违法*/
if (var->activate & FB_ACTIVATE_INV_MODE) {
struct fb_videomode mode1, mode2;
fb_var_to_videomode(&mode1, var);
fb_var_to_videomode(&mode2, &info->var);
/* make sure we don't delete the videomode of current var */
ret = fb_mode_is_equal(&mode1, &mode2);
if (!ret) {
struct fb_event event;
event.info = info;
event.data = &mode1;
ret = fb_notifier_call_chain(FB_EVENT_MODE_DELETE, &event);
}
if (!ret)
fb_delete_videomode(&mode1, &info->modelist);
ret = (ret) ? -EINVAL : 0;
goto done;
}
/*使用memcmp将要修改的var参数和info本色的参数对比,如果完全一样则 activate = var->activate*/
if ((var->activate & FB_ACTIVATE_FORCE) ||
memcmp(&info->var, var, sizeof(struct fb_var_screeninfo))) {
u32 activate = var->activate;
if (!info->fbops->fb_check_var) {
*var = info->var;
goto done;
}
/*调用fb_check_var*/
ret = info->fbops->fb_check_var(var, info);
if (ret)
goto done;
if ((var->activate & FB_ACTIVATE_MASK) == FB_ACTIVATE_NOW) {
struct fb_var_screeninfo old_var;
struct fb_videomode mode;
if (info->fbops->fb_get_caps) {
ret = fb_check_caps(info, var, activate);
if (ret)
goto done;
}
old_var = info->var;
info->var = *var;
/*调用set_var*/
if (info->fbops->fb_set_par) {
ret = info->fbops->fb_set_par(info);
if (ret) {
info->var = old_var;
printk(KERN_WARNING "detected "
"fb_set_par error, "
"error code: %d\n", ret);
goto done;
}
}
fb_pan_display(info, &info->var);
fb_set_cmap(&info->cmap, info);
fb_var_to_videomode(&mode, &info->var);
if (info->modelist.prev && info->modelist.next &&
!list_empty(&info->modelist))
ret = fb_add_videomode(&mode, &info->modelist);
if (!ret && (flags & FBINFO_MISC_USEREVENT)) {
struct fb_event event;
int evnt = (activate & FB_ACTIVATE_ALL) ?
FB_EVENT_MODE_CHANGE_ALL :
FB_EVENT_MODE_CHANGE;
info->flags &= ~FBINFO_MISC_USEREVENT;
event.info = info;
event.data = &mode;
fb_notifier_call_chain(evnt, &event);
}
}
}
done:
return ret;
}
函数主要做了(1)函数调用fb_info的fb_check_var
对应s3c2410就是
static int s3c2410fb_check_var(struct fb_var_screeninfo *var,
struct fb_info *info)
{
struct s3c2410fb_info *fbi = info->par;
struct s3c2410fb_mach_info *mach_info = fbi->dev->platform_data;
struct s3c2410fb_display *display = NULL;
struct s3c2410fb_display *default_display = mach_info->displays +
mach_info->default_display;
int type = default_display->type;
unsigned i;
dprintk("check_var(var=%p, info=%p)\n", var, info);
/* validate x/y resolution */
/* choose default mode if possible */
if (var->yres == default_display->yres &&
var->xres == default_display->xres &&
var->bits_per_pixel == default_display->bpp)
display = default_display;
else
for (i = 0; i < mach_info->num_displays; i++)
if (type == mach_info->displays[i].type &&
var->yres == mach_info->displays[i].yres &&
var->xres == mach_info->displays[i].xres &&
var->bits_per_pixel == mach_info->displays[i].bpp) {
display = mach_info->displays + i;
break;
}
if (!display) {
dprintk("wrong resolution or depth %dx%d at %d bpp\n",
var->xres, var->yres, var->bits_per_pixel);
return -EINVAL;
}
/* it is always the size as the display */
var->xres_virtual = display->xres;
var->yres_virtual = display->yres;
var->height = display->height;
var->width = display->width;
/* copy lcd settings */
var->pixclock = display->pixclock;
var->left_margin = display->left_margin;
var->right_margin = display->right_margin;
var->upper_margin = display->upper_margin;
var->lower_margin = display->lower_margin;
var->vsync_len = display->vsync_len;
var->hsync_len = display->hsync_len;
fbi->regs.lcdcon5 = display->lcdcon5;
/* set display type */
fbi->regs.lcdcon1 = display->type;
var->transp.offset = 0;
var->transp.length = 0;
/* set r/g/b positions */
switch (var->bits_per_pixel) {
case 1:
case 2:
case 4:
var->red.offset = 0;
var->red.length = var->bits_per_pixel;
var->green = var->red;
var->blue = var->red;
break;
case 8:
if (display->type != S3C2410_LCDCON1_TFT) {
/* 8 bpp 332 */
var->red.length = 3;
var->red.offset = 5;
var->green.length = 3;
var->green.offset = 2;
var->blue.length = 2;
var->blue.offset = 0;
} else {
var->red.offset = 0;
var->red.length = 8;
var->green = var->red;
var->blue = var->red;
}
break;
case 12:
/* 12 bpp 444 */
var->red.length = 4;
var->red.offset = 8;
var->green.length = 4;
var->green.offset = 4;
var->blue.length = 4;
var->blue.offset = 0;
break;
default:
case 16:
if (display->lcdcon5 & S3C2410_LCDCON5_FRM565) {
/* 16 bpp, 565 format */
var->red.offset = 11;
var->green.offset = 5;
var->blue.offset = 0;
var->red.length = 5;
var->green.length = 6;
var->blue.length = 5;
} else {
/* 16 bpp, 5551 format */
var->red.offset = 11;
var->green.offset = 6;
var->blue.offset = 1;
var->red.length = 5;
var->green.length = 5;
var->blue.length = 5;
}
break;
case 32:
/* 24 bpp 888 and 8 dummy */
var->red.length = 8;
var->red.offset = 16;
var->green.length = 8;
var->green.offset = 8;
var->blue.length = 8;
var->blue.offset = 0;
break;
}
return 0;
}
函数主要就是填充一些var参数。
(2)函数调用fb_info的fb_set_var,对应s3c2410就是s3c2410fb_set_par
static int s3c2410fb_set_par(struct fb_info *info)
{
struct fb_var_screeninfo *var = &info->var;
switch (var->bits_per_pixel) {
case 32:
case 16:
case 12:
info->fix.visual = FB_VISUAL_TRUECOLOR;
break;
case 1:
info->fix.visual = FB_VISUAL_MONO01;
break;
default:
info->fix.visual = FB_VISUAL_PSEUDOCOLOR;
break;
}
info->fix.line_length = (var->xres_virtual * var->bits_per_pixel) / 8;
/* activate this new configuration */
/*根据var的值对LCD寄存器重新配置*/
s3c2410fb_activate_var(info);
return 0;
}
这个函数大体作用就是填充一些固定参数,根据var和fix去重新配置LCD寄存器,这里不去详解,可根据2410的数据手册分析,代码容易理解。
应用程序要使用fb,那么就要通过mmap系统调用将fb的缓存地址映射到用户空间,这样用户空间对这段地址操作就完成了对LCD的操作,下面介绍fbmem.c的 fb_mmap函数。
static int fb_mmap(struct file *file, struct vm_area_struct * vma)
{
1 int fbidx = iminor(file->f_path.dentry->d_inode);
2 struct fb_info *info = registered_fb[fbidx];
3 struct fb_ops *fb = info->fbops;
4 unsigned long off;
5 unsigned long start;
6 u32 len;
/*当第一页的地址大于0xfffff是非法的*/
7 if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT))
8 return -EINVAL;
/*off表示设备首位置所在的页面左移12位*/
9 off = vma->vm_pgoff << PAGE_SHIFT;
10 if (!fb)
11 return -ENODEV;
12 mutex_lock(&info->mm_lock);
/*如果存在fb-mmap*/
13 if (fb->fb_mmap) {
14 int res;
15 res = fb->fb_mmap(info, vma);
16 mutex_unlock(&info->mm_lock);
17 return res;
18 }
/* frame buffer memory获得framebuffer的物理地址*/
19 start = info->fix.smem_start;
/*对framebuffer对应的内存末地址进行页对齐*/
20 len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.smem_len);
21 if (off >= len) {
/* memory mapped io */
22 off -= len;
23 if (info->var.accel_flags) {
24 mutex_unlock(&info->mm_lock);
25 return -EINVAL;
26 }
27 start = info->fix.mmio_start;
28 len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.mmio_len);
29 }
30 mutex_unlock(&info->mm_lock);
31 /*start=start&0xffff000的意思是start此时是原来start所在的页表*/
32 start &= PAGE_MASK;
/*如果设备所映射的末页面大于framebuffer对应的内存末页面是违法的*/
33 if ((vma->vm_end - vma->vm_start + off) > len)
34 return -EINVAL;
35 off += start;
/*这时vma->vm_pgoff就是被映射设备首地址*/
36 vma->vm_pgoff = off >> PAGE_SHIFT;
/* This is an IO map - tell maydump to skip this VMA */
37 vma->vm_flags |= VM_IO | VM_RESERVED;
38 fb_pgprotect(file, vma, off);
/*建立新页表*/
39 if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT,
vma->vm_end - vma->vm_start, vma->vm_page_prot))
40 return -EAGAIN;
return 0;
}
这个函数我的理解方式是这样的首先off = vma->vm_pgoff << PAGE_SHIFT;这一句的意思是之前所映射设备(不是该fb设备)首位置所在的页面左移12位。也就是之前设备对应的物理内存页帧号。off += start就是现在要映射的设备的物理内存。vma->vm_pgoff = off >> PAGE_SHIFT就是现在要映射的设备对应的物理内存页帧号。
最后,对应ioctl的cmd的其他参数,目前还正在研究。