framebuffer驱动详解—以s3c2410fb为例
一、framebuffer介绍
1、什么是framebuffer
(1)裸机中如何操作LCD
LCD的显示原理:DDR中分出一块内存,将要显示的内容放到显存中,硬件自动将显存数据放到驱动器中,驱动器操控LCD显示内容。裸机操作LCD的方法由LCD的本身的工作原理决定。
(2)OS下操作LCD的难点
内核(驱动)做底层硬件操作相关的那部分(初始化LCD控制器的寄存器、内存,建立显存与LCD之间的映射关系),应用做让LCD显示具体内容的那部分(把显示的内容丢到显存中去,让LCD显示具体的内容)。
应用与内核的显存的虚拟地址不同,应用使用的是应用层的虚拟地址空间,而内核使用的是内核层的虚拟地址空间,但是二者可以对应同一块物理地址,这样就可以提升效率。
内核与应用进行数据交换:
小容量数据:copy_to_user,copy_from_user
大容量数据:mmap(可用于显示的情况下,显示画面时数据量较大)
(3)framebuffer帧缓冲(简称fb)是linux内核中虚拟出的一个设备,使用代码构建出的一个设备,具有设备文件可以去读写,代替LCD显示器这个硬件设施以及LCD显示器所需的软件设施和硬件设施,如显卡驱动和显卡。framebuffer可以进行不同的配置从而支持不同的接口如VGA、HDMI等等。
ls /dev/fb*
一般为fb0,fb为设备名称,数字为序号,有几个显示设备就有几个fbX,X表示从零开始的整数
一般只有一个屏幕但是却有好多个fbX文件,是因为这些表示的是虚拟屏幕
(4)framebuffer向应用层提供一个统一标准接口的显示设备,忽略不同显示设备的差异,屏蔽了不同的硬件的差异
(5)从驱动来看,fb是一个典型的字符设备,而且创建了一个类/sys/class/graphics
2、framebuffer的使用
(1)设备文件 /dev/fb0(我的开发板是这个,不同开发板不同内核驱动可能会有所差异)
(2)获取设备信息(显示设备的大小,分辨率等) #include <linux/fb.h>
(3)mmap做映射(详解:https://www.jianshu.com/p/755338d11865)
在LINUX中我们可以使用mmap用来在进程虚拟内存地址空间中分配地址空间,创建和物理内存的映射关系。mmap将一个文件或者其它对象映射进内存。
void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
mmap只是在虚拟内存分配了地址空间,只有在第一次访问虚拟内存的时候才分配物理内存。
驱动在内核中申请一块显存,多个进程可同时操控LCD,可进行多对一映射,后来的覆盖前面的。
(4)填充framebuffer(即将显示的内容放到framebuffer中)
二、framebuffer应用编程实践
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <unistd.h>
#include "image.h"
#define FBDEVICE "/dev/fb0"
//旧开发板
//#define WIDTH 800
//#define HEIGHT 480
//新开发板
#define WIDTH 1024
#define HEIGHT 600
//背景颜色数据
#define WHITE 0xffffffff
#define BLACK 0x00000000
#define RED 0xffff0000
#define GREEN 0xff00ff00
#define BLUE 0xff0000ff
unsigned int *pfb = NULL;//用于保存mmp映射返回的显存的地址
void draw_back(unsigned int width, unsigned height, unsigned int color);//刷新屏幕背景函数
void draw_line(unsigned int color);//画直线函数
void lcd_draw_picture(const unsigned char *pData);//显示图片函数
void draw_back(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;
}
}
}
void draw_line(unsigned int color)
{
unsigned int x, y;
for (x=50; x<600; x++)
{
*(pfb + 200 * WIDTH + x) = color;
}
}
//画1024*600像素的图片,图像数据存在pData所指向的数组中
void lcd_draw_picture(const unsigned char *pData)
{
unsigned int x, y, color, p = 0;
for(y = 0; y < 600;y++)
{
for(x = 0;x < 1024; x++)
{
// 在这里将坐标点(x, y)的那个像素填充上相应的颜色值即可
color = ((pData[p+2] << 0)|(pData[p+1] << 8)|(pData[p+0] << 16));
*(pfb + y * WIDTH + x) = color;
p += 3;
}
}
}
/*
显示图片的函数并未测试成功,经过分析后,发现虚拟分辨率的大小和图片的大小不同,导致在显存中存放的颜色数据的显示时位置发生错乱,尝试修改mmap参数中的finfo.smem_len,结果在执行时出现了段错误,访问到了不该访问的内存,对于实验时出现的显示错误,经初步分析与底层驱动相关,驱动的分辨率未设置正确,经过更换内核源码可以确定是底层驱动出错的问题
*/
int main(int argv, char*argc[])
{
int fd = -1, ret = -1;
struct fb_fix_screeninfo finfo = {0};
struct fb_var_screeninfo vinfo = {0};
//第一步:打开设备
fd = open(FBDEVICE, O_RDWR);
if (fd < 0)
{
perror("open fb0 error");
return -1;
}
printf("open fb0 successfully.\n");
//第二步:获取设备的硬件信息
ret = ioctl(fd, FBIOGET_FSCREENINFO, &finfo);//获取不可变信息
if (ret < 0)
{
perror("ioctl");
return -1;
}
printf("smem_start = 0x%x, smem_len = %u.\n", finfo.smem_start, finfo.smem_len);
ret = ioctl(fd, FBIOGET_VSCREENINFO, &vinfo);//获取可变信息
if (ret < 0)
{
perror("ioctl");
return -1;
}
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);
//第三步:进行mmp映射
//finfo.smem_len以虚拟分辨率进行计算的
pfb = mmap(NULL, finfo.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (NULL == pfb)
{
perror("mmap fail");
return -1;
}
printf("pfb = %p.\n", pfb);
//第四步:填充framebuffer
//draw_back(WIDTH, HEIGHT, WHITE);
//sleep(1);
//draw_back(700, 480, BLACK);
//draw_back(1024, 600, BLACK);
//sleep(3);
//draw_back(WIDTH, HEIGHT, BLACK);
//draw_line(BLACK);
lcd_draw_picture(gImage_1024600);
//最后一步:关闭设备文件
close(fd);
return 0;
}
三、framebuffer驱动框架总览
1、驱动框架部分(kernel/drivers/video/fbmem.c)
(1)drivers/video/fbmem.c。
主要任务:
1、创建graphics类(/sys/class/graphics/fb0)
2、注册FB的字符设备驱动
3、提供register_framebuffer(内部调用device_create创建某个类的设备)接口给具体framebuffer驱动编写着来注册fb设备的。
int register_framebuffer(struct fb_info *fb_info);
int unregister_framebuffer(struct fb_info *fb_info);
fbmem_init(void)
{
proc_create("fb", 0, NULL, &fb_proc_fops);
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;
}
fbmem_exit(void)
{
remove_proc_entry("fb", NULL);
class_destroy(fb_class);
unregister_chrdev(FB_MAJOR, "fb");
}
本文件相对于fb来说,地位和作用和misc.c文件相对于杂散类设备来说一样的,结构和分析方法也是类似的。
(2)drivers/video/fbsys.c。这个文件是处理fb在/sys目录下的一些属性文件的。
(3)drivers/video/modedb.c。这个文件是管理显示模式的(譬如VGA、720P、刷新率等就是显示模式)
(4)drivers/video/fb_notify.c,进行反向唤醒,管理了一个链表,链表发生变动时告知链表中的各个成员
四、framebuffer驱动框架分析
从kernel/drivers/video/fbmem.c开始分析
1、fbmem_init函数
#ifdef MODULE
module_init(fbmem_init);//其声明的可以去安装和卸载,内核作为一个模块
static void __exit
fbmem_exit(void)
{
remove_proc_entry("fb", NULL);
class_destroy(fb_class);
unregister_chrdev(FB_MAJOR, "fb");
}
module_exit(fbmem_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Framebuffer base");
#else
subsys_initcall(fbmem_init);//其声明的则是被编译进内核
#endif
(1)#ifdef MODULE
经过分析,发现并未定义该宏MODULE
(2)fb_proc_fops和fb在proc文件系统中的表现
proc_create("fb", 0, NULL, &fb_proc_fops);
#ifdef MODULE
module_init(fbmem_init);//其声明的可以去安装和卸载,内核作为一个模块
static void __exit
fbmem_exit(void)
{
remove_proc_entry("fb", NULL);
class_destroy(fb_class);
unregister_chrdev(FB_MAJOR, "fb");
}
module_exit(fbmem_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Framebuffer base");
#else
subsys_initcall(fbmem_init);//其声明的则是被编译进内核
#endif
cat /proc/fb
在驱动框架并不进行真正的硬件操作,而是通过函数指针指向一个真正可用的函数
(3)register_chrdev注册fb设备
(4)class_create创建graphics类
(5)fbmem_exit的对应:当以非模块的方式即以内核一部分集成编译进内核时,开机自动加载执行后是无法像模块卸载的,故而这种非模块方式并不需要fbmem_exit函数
2.fb_info结构体
fb_info描述一个fb设备状态,一般LCD驱动(s3cxx.c)会填充该结构体,然后通过register_framebuffer函数注册到fb驱动中。
#ifdef MODULE
module_init(fbmem_init);//其声明的可以去安装和卸载,内核作为一个模块
static void __exit
fbmem_exit(void)
{
remove_proc_entry("fb", NULL);
class_destroy(fb_class);
unregister_chrdev(FB_MAJOR, "fb");
}
module_exit(fbmem_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Framebuffer base");
#else
subsys_initcall(fbmem_init);//其声明的则是被编译进内核
#endif
3.fb_fix_screeninfon
这个结构在显卡被设定模式后创建,它描述显示卡的属性,并且系统运行时不能被修改;比如FrameBuffer内存的起始地址。它依赖于被设定的模式,当一个模 式被设定后,内存信息由显示卡硬件给出,内存的位置等信息就不可以修改。
struct fb_fix_screeninfo {
char id[16]; /* identification string eg "TT Builtin" */
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 */
/* (physical address) */
__u32 mmio_len; /* Length of Memory Mapped I/O */
__u32 accel; /* Indicate to driver which */
/* specific chip/card we have */
__u16 reserved[3]; /* Reserved for future compatibility */
};
计算方法:smem_len = xres*yres* bits_per_pixel /8
3.fb_var_screeninfo
可变信息VSCREENINFO,使用ioctl的FBIOGET_VSCREENINFO命令获得
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,bpp,每个像素用多少个字节来表示 */
__u32 grayscale; /* != 0 Graylevels instead of colors,灰度等级 */
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各个颜色以及透明度的阈值
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; //初始化LCD时序的六个参数
__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 */
};
虚拟分辨率与真实分辨率最好的理解就是一张分辨率高于屏幕的图片无法在这个屏幕完全显示,需要通过滑动左右栏或者上下栏来查看整张图片。
一个像素点数据由四个字节组成,包括RGB(RGB888颜色编码)以及透明度,各占一个字节。
三星驱动的虚拟分辨率是真是分辨率的两倍,采用了双缓冲结构(有时也称乒乓结构),屏幕首先从(0,0)开始显示,当上半部分显示完后,可以直接切换到(0, 480),显示剩下的内容。
4、fb_fops结构体
所有的framebuffer设备共用一个主设备号,用不同的次设备号进行区分,类似于misc类设备
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, //mmap函数通过这个ops绑定frambuffer
.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
};
- read/write/mmap/ioctl函数
mmap:
static int fb_mmap(struct file *file, struct vm_area_struct * vma)
{
struct fb_info *info = file_fb_info(file);
fb = info->fbops; //info即是lcd驱动填充fb_info结构体注册进来的。
if (!fb)
return -ENODEV;
mutex_lock(&info->mm_lock);
if (fb->fb_mmap) { //一般LCD驱动不会有fb_mmp的ops,不会进入
int res;
/*
* The framebuffer needs to be accessed decrypted, be sure
* SME protection is removed ahead of the call
*/
vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot);
res = fb->fb_mmap(info, vma);
mutex_unlock(&info->mm_lock);
return res;
}
/*
* Ugh. This can be either the frame buffer mapping, or
* if pgoff points past it, the mmio mapping.
*/
start = info->fix.smem_start; //lcd驱动注册进来的framebuffer的物理地址
len = info->fix.smem_len; //lcd驱动注册进来的framebuffer的size
mmio_pgoff = PAGE_ALIGN((start & ~PAGE_MASK) + len) >> PAGE_SHIFT;
if (vma->vm_pgoff >= mmio_pgoff) {
if (info->var.accel_flags) {
mutex_unlock(&info->mm_lock);
return -EINVAL;
}
vma->vm_pgoff -= mmio_pgoff;
start = info->fix.mmio_start;
len = info->fix.mmio_len;
}
mutex_unlock(&info->mm_lock);
vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
/*
* The framebuffer needs to be accessed decrypted, be sure
* SME protection is removed
*/
vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot);
fb_pgprotect(file, vma, start);
return vm_iomap_memory(vma, start, len);
}
Read:
static ssize_t
fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
if (info->fbops->fb_read) //一般LCD驱动不会有fb_ read的ops,不会进入
return info->fbops->fb_read(info, buf, count, ppos);
total_size = info->screen_size;
if (total_size == 0)
total_size = info->fix.smem_len; //LCD驱动注册的size
if (p >= total_size)
return 0;
if (count >= total_size)
count = total_size;
if (count + p > total_size)
count = total_size - p;
buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
GFP_KERNEL);
if (!buffer)
return -ENOMEM;
src = (u8 __iomem *) (info->screen_base + p);
if (info->fbops->fb_sync)
info->fbops->fb_sync(info);
while (count) {
c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
dst = buffer;
fb_memcpy_fromfb(dst, src, c);
dst += c;
src += c;
if (copy_to_user(buf, buffer, c)) {
err = -EFAULT;
break;
}
*ppos += c;
buf += c;
cnt += c;
count -= c;
}
kfree(buffer);
return (err) ? err : cnt;
}
Write:
static ssize_t fb_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
unsigned long p = *ppos;
struct fb_info *info = file_fb_info(file);
u8 *buffer, *src;
u8 __iomem *dst;
int c, cnt = 0, err = 0;
unsigned long total_size;
if (!info || !info->screen_base)
return -ENODEV;
if (info->state != FBINFO_STATE_RUNNING)
return -EPERM;
if (info->fbops->fb_write)
return info->fbops->fb_write(info, buf, count, ppos);
total_size = info->screen_size;
if (total_size == 0)
total_size = info->fix.smem_len;
if (p > total_size)
return -EFBIG;
if (count > total_size) {
err = -EFBIG;
count = total_size;
}
if (count + p > total_size) {
if (!err)
err = -ENOSPC;
count = total_size - p;
}
buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
GFP_KERNEL);
if (!buffer)
return -ENOMEM;
dst = (u8 __iomem *) (info->screen_base + p);
if (info->fbops->fb_sync)
info->fbops->fb_sync(info);
while (count) {
c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
src = buffer;
if (copy_from_user(src, buf, c)) {
err = -EFAULT;
break;
}
fb_memcpy_tofb(dst, src, c);
dst += c;
src += c;
*ppos += c;
buf += c;
cnt += c;
count -= c;
}
kfree(buffer);
return (cnt) ? cnt : err;
}
Ioctl:
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;
console_lock();
if (!lock_fb_info(info)) {
console_unlock();
return -ENODEV;
}
info->flags |= FBINFO_MISC_USEREVENT;
ret = fb_set_var(info, &var);
info->flags &= ~FBINFO_MISC_USEREVENT;
unlock_fb_info(info);
console_unlock();
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;
console_lock();
if (!lock_fb_info(info)) {
console_unlock();
return -ENODEV;
}
ret = fb_pan_display(info, &var);
unlock_fb_info(info);
console_unlock();
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 >= 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;
console_lock();
if (!lock_fb_info(info)) {
console_unlock();
return -ENODEV;
}
event.info = info;
ret = fb_notifier_call_chain(FB_EVENT_SET_CONSOLE_MAP, &event);
unlock_fb_info(info);
console_unlock();
break;
case FBIOBLANK:
console_lock();
if (!lock_fb_info(info)) {
console_unlock();
return -ENODEV;
}
info->flags |= FBINFO_MISC_USEREVENT;
ret = fb_blank(info, arg);
info->flags &= ~FBINFO_MISC_USEREVENT;
unlock_fb_info(info);
console_unlock();
break;
default:
if (!lock_fb_info(info))
return -ENODEV;
fb = info->fbops;
if (fb->fb_ioctl)
ret = fb->fb_ioctl(info, cmd, arg);//lcd驱动私有的iocrl命令
else
ret = -ENOTTY;
unlock_fb_info(info);
}
return ret;
}
- registered_fb和num_registered_fb
- struct fb_info描述一个framebuffer设备
5、register_framebuffer函数解析
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;
remove_conflicting_framebuffers(fb_info->apertures, fb_info->fix.id,
fb_is_primary_device(fb_info));//防止冲突机制
num_registered_fb++;
for (i = 0 ; i < FB_MAX; i++)//找到一个空的可用的
if (!registered_fb[i])
break;
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);
fb_var_to_videomode(&mode, &fb_info->var);
fb_add_videomode(&mode, &fb_info->modelist);
registered_fb[i] = fb_info; // registered_fb记录已经注册的fb
event.info = fb_info;
if (!lock_fb_info(fb_info))
return -ENODEV;
fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event); //阻塞通知
//当阻塞结束时进行通知,发送FB_EVENT_FB_REGISTERED消息给等待的人(也可以理解为其他线程或进程或任务)
unlock_fb_info(fb_info);
return 0;
}
详解
(1) register_framebuffer 是fb驱动框架开放给驱动编写者的注册接口
(2)fb_init_device:创建/sys/目录下的那些文件
void dev_set_drvdata(struct device *dev, void *data)
{
int error;
if (!dev)
return;
if (!dev->p) {
error = device_private_init(dev);
if (error)
return;
}
dev->p->driver_data = data;
}
void *dev_get_drvdata(const struct device *dev)
{
if (dev && dev->p)
return dev->p->driver_data;
return NULL;
}
位于:kernel/drivers/base/dd.c,base目录下的这些文件是内核提供的底层驱动的框架支持,我们是不需要去修改的。
dev->p->driver_data:struct device->struct device_private *p(该结构体保存设备的一些私有信息)->void *driver_data;
dev_set_drvdata(fb_info->dev, fb_info);//该函数
dev->p->driver_data = data;将fb_info结构体包含的数据保存到该设备的一个结构体成员中,结合dev_get_drvdata()函数实现数据的传递在不同函数中使用,从而避免使用过多的全局变量,破坏程序的结构
创建的/sys/class/graphics/fb0目录下的文件:
static struct device_attribute device_attrs[] = {
__ATTR(bits_per_pixel, S_IRUGO|S_IWUSR, show_bpp, store_bpp),
__ATTR(blank, S_IRUGO|S_IWUSR, show_blank, store_blank),
__ATTR(console, S_IRUGO|S_IWUSR, show_console, store_console),
__ATTR(cursor, S_IRUGO|S_IWUSR, show_cursor, store_cursor),
__ATTR(mode, S_IRUGO|S_IWUSR, show_mode, store_mode),
__ATTR(modes, S_IRUGO|S_IWUSR, show_modes, store_modes),
__ATTR(pan, S_IRUGO|S_IWUSR, show_pan, store_pan),
__ATTR(virtual_size, S_IRUGO|S_IWUSR, show_virtual, store_virtual),
__ATTR(name, S_IRUGO, show_name, NULL),
__ATTR(stride, S_IRUGO, show_stride, NULL),
__ATTR(rotate, S_IRUGO|S_IWUSR, show_rotate, store_rotate),
__ATTR(state, S_IRUGO|S_IWUSR, show_fbstate, store_fbstate),
#ifdef CONFIG_FB_BACKLIGHT
__ATTR(bl_curve, S_IRUGO|S_IWUSR, show_bl_curve, store_bl_curve),
#endif
};
分析示例:
__ATTR(bits_per_pixel, S_IRUGO|S_IWUSR, show_bpp, store_bpp),
static ssize_t show_bpp(struct device *device, struct device_attribute *attr,
char *buf)
{
struct fb_info *fb_info = dev_get_drvdata(device);
return snprintf(buf, PAGE_SIZE, "%d\n", fb_info->var.bits_per_pixel);
//bits_per_pixel值的来源
}
使用:echo 16 > bits_per_pixel可以进行写值通过调用store_bpp方法
五、framebuffer驱动分析
\drivers\video\fbdev\s3b2410fb.c
//使用平台总线框架进行LCD的驱动注册,故理解此部分流程可参考学习之前的LED框架注册的流
//程,都是match函数先进行设备和驱动的匹配,然后再调用probe函数
#define S3CFB_NAME "s3cfb"
static struct platform_driver s3cfb_driver = {
.probe = s3cfb_probe,//初始化硬件,安装硬件
.remove = __devexit_p(s3cfb_remove),
.driver = {
.name = S3CFB_NAME,//platform查找device的一种手段,通过名称匹配进行查找
.owner = THIS_MODULE,
},
};
static int __init s3cfb_register(void)
{
platform_driver_register(&s3cfb_driver);
return 0;
}
module_init(s3cfb_register);
使用平台总线框架进行驱动注册的设备都维护在一个平台总线设备数组中,若某个设备要使用
平台总线框架进行注册,则需再该设备数组中添加该设备。
Probe函数分析:
static int 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;
mach_info = dev_get_platdata(&pdev->dev);
if (mach_info == NULL) {
dev_err(&pdev->dev,
"no platform data for lcd, cannot attach\n");
return -EINVAL;
}
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 = 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;
}
//申请驱动中framebuffer设备的描述
fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);
if (!fbinfo)
return -ENOMEM;
platform_set_drvdata(pdev, fbinfo);
info = fbinfo->par;
info->dev = &pdev->dev;
info->drv_type = drv_type;
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 = resource_size(res);
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;
}
info->io = ioremap(res->start, size);
if (info->io == NULL) {
dev_err(&pdev->dev, "ioremap() of registers failed\n");
ret = -ENXIO;
goto release_mem;
}
if (drv_type == DRV_S3C2412)
info->irq_base = info->io + S3C2412_LCDINTBASE;
else
info->irq_base = info->io + S3C2410_LCDINTBASE;
dprintk("devinit\n");
strcpy(fbinfo->fix.id, driver_name);
/* Stop the video */
lcdcon1 = readl(info->io + S3C2410_LCDCON1);
writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, info->io + S3C2410_LCDCON1);
//填充fd_info结构体
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, 0, pdev->name, info);
if (ret) {
dev_err(&pdev->dev, "cannot get irq %d - err %d\n", irq, ret);
ret = -EBUSY;
goto release_regs;
}
info->clk = clk_get(NULL, "lcd");
if (IS_ERR(info->clk)) {
dev_err(&pdev->dev, "failed to get lcd clock source\n");
ret = PTR_ERR(info->clk);
goto release_irq;
}
clk_prepare_enable(info->clk);
dprintk("got and enabled clock\n");
usleep_range(1000, 1100);
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;
smem_len *= mach_info->displays[i].yres;
smem_len *= mach_info->displays[i].bpp;
smem_len >>= 3;
if (fbinfo->fix.smem_len < smem_len)
fbinfo->fix.smem_len = smem_len;
}
/* 重要:Initialize video memory */
ret = s3c2410fb_map_video_memory(fbinfo);
if (ret) {
dev_err(&pdev->dev, "Failed to allocate video RAM: %d\n", ret);
ret = -ENOMEM;
goto release_clock;
}
dprintk("got video memory\n");
fbinfo->var.xres = display->xres;
fbinfo->var.yres = display->yres;
fbinfo->var.bits_per_pixel = display->bpp;
s3c2410fb_init_registers(fbinfo);
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;
}
//注册fbinfo
ret = register_framebuffer(fbinfo);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to register framebuffer device: %d\n",
ret);
goto free_cpufreq;
}
/* create device files */
ret = device_create_file(&pdev->dev, &dev_attr_debug);
if (ret)
dev_err(&pdev->dev, "failed to add debug attribute\n");
dev_info(&pdev->dev, "fb%d: %s frame buffer device\n",
fbinfo->node, fbinfo->fix.id);
return 0;
}
s3c2410fb_map_video_memory(fbinfo)函数:
static int 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);//frambuffer的size页对其
dprintk("map_video_memory(fbi=%p) map_size %u\n", fbi, map_size);
//申请对应的framebuffer的物理地址map_dma,并返回虚拟地址screen_base
info->screen_base = dma_alloc_wc(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);
//赋值framebuffer的物理地址
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;
}
通过s3c2410fb_map_video_memory函数申请了framebuffer的物理地址,并将其赋值给fix.smem_start,同时也获取了对应的虚拟地址screen_base,这两个地址会通过register_framebuffer函数注册到fb层,并且通过mmap的时候也会反馈到用户层。
参考链接:https://blog.csdn.net/weixin_45842280/article/details/120572842