1.从fbmem.c入手
- 内核版本:linux 4.9
1.1 fbmem_init
static int __init
fbmem_init(void)
{
int ret;
if (!proc_create("fb", 0, NULL, &fb_proc_fops))
return -ENOMEM;
ret = register_chrdev(FB_MAJOR, "fb", &fb_fops);
if (ret) {
printk("unable to get major %d for fb devs\n", FB_MAJOR);
goto err_chrdev;
}
fb_class = class_create(THIS_MODULE, "graphics");
if (IS_ERR(fb_class)) {
ret = PTR_ERR(fb_class);
pr_warn("Unable to create fb class; errno = %d\n", ret);
fb_class = NULL;
goto err_class;
}
return 0;
err_class:
unregister_chrdev(FB_MAJOR, "fb");
err_chrdev:
remove_proc_entry("fb", NULL);
return ret;
}
? 首先在/proc下面创建一个名字为fb的节点
? register_chrdev(FB_MAJOR, "fb", &fb_fops);
向内核一个名字为fb,主设备号为29的设备节点。
? fb_class = class_create(THIS_MODULE, "graphics");
创建一个名字为graphics的类。可以在/sys/class中看到graphics这个类。
? 还有一个比较重要的地方,就是关于fb_fops结构体,这里面有对应的fb_open、fb_ioctl、fb_read等,如果你进去看这几个地方,就会发现,它会去遍历 registered_fb
这个数组,并且使用对应fb的fb_ops操作,那么其实这里的fb_fops是一个中转的操作。
1.2 register_framebuffer
static int do_register_framebuffer(struct fb_info *fb_info)
{
int i, ret;
struct fb_event event;
struct fb_videomode mode;
if (fb_check_foreignness(fb_info))
return -ENOSYS;
ret = do_remove_conflicting_framebuffers(fb_info->apertures,
fb_info->fix.id,
fb_is_primary_device(fb_info));
if (ret)
return ret;
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;
atomic_set(&fb_info->count, 1);
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);
if (fb_info->skip_vt_switch)
pm_vt_switch_required(fb_info->dev, false);
else
pm_vt_switch_required(fb_info->dev, true);
fb_var_to_videomode(&mode, &fb_info->var);
fb_add_videomode(&mode, &fb_info->modelist);
registered_fb[i] = fb_info;
event.info = fb_info;
if (!lockless_register_fb)
console_lock();
if (!lock_fb_info(fb_info)) {
if (!lockless_register_fb)
console_unlock();
return -ENODEV;
}
fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
unlock_fb_info(fb_info);
if (!lockless_register_fb)
console_unlock();
return 0;
}
? 有一个重要的地方 registered_fb[i] = fb_info;
,往这个数组填充fb_info, registered_fb,是由内核管理的,在应用程序中对某个fb节点的操作,内核就会到这个数组找到对应的fb,并且使用这个fb对应的fops函数。
? fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
是一个通知函数,最终会调用fbcon.c中的回调函数fbcon_event_notify
,然后调用fbcon_fb_registered
进行fb的注册。
? 通过分析基本上就能知道,需要填充fb_info这个结构体。
2.分析mxsfb.c
? 分析一个驱动的入口,要么就是module_init(xxx_init),要么就是xxx_probe。为什么会出现这两种入口?原因是后者引入了设备树,其实设备节点会被转换成一个platform_device。而前者就是自己手动写一个platform_device出来。
2.1 mxsfb_probe
? 那么mxsfb显然使用了设备树的。那么就从mxsfb_probe
开始分析:
static int mxsfb_probe(struct platform_device *pdev)
{
const struct of_device_id *of_id =
of_match_device(mxsfb_dt_ids, &pdev->dev);
struct resource *res;
struct mxsfb_info *host;
struct fb_info *fb_info;
struct fb_videomode *mode;
int ret;
if (of_id)
pdev->id_entry = of_id->data;
fb_info = framebuffer_alloc(sizeof(struct mxsfb_info), &pdev->dev);
if (!fb_info) {
dev_err(&pdev->dev, "Failed to allocate fbdev\n");
return -ENOMEM;
}
mode = devm_kzalloc(&pdev->dev, sizeof(struct fb_videomode),
GFP_KERNEL);
if (mode == NULL)
return -ENOMEM;
host = to_imxfb_host(fb_info);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
host->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(host->base)) {
ret = PTR_ERR(host->base);
goto fb_release;
}
host->pdev = pdev;
platform_set_drvdata(pdev, host);
host->devdata = &mxsfb_devdata[pdev->id_entry->driver_data];
host->clk = devm_clk_get(&host->pdev->dev, NULL);
if (IS_ERR(host->clk)) {
ret = PTR_ERR(host->clk);
goto fb_release;
}
host->clk_axi = devm_clk_get(&host->pdev->dev, "axi");
if (IS_ERR(host->clk_axi))
host->clk_axi = NULL;
host->clk_disp_axi = devm_clk_get(&host->pdev->dev, "disp_axi");
if (IS_ERR(host->clk_disp_axi))
host->clk_disp_axi = NULL;
host->reg_lcd = devm_regulator_get(&pdev->dev, "lcd");
if (IS_ERR(host->reg_lcd))
host->reg_lcd = NULL;
fb_info->pseudo_palette = devm_kzalloc(&pdev->dev, sizeof(u32) * 16,
GFP_KERNEL);
if (!fb_info->pseudo_palette) {
ret = -ENOMEM;
goto fb_release;
}
ret = mxsfb_init_fbinfo(host, mode);
if (ret != 0)
goto fb_release;
fb_videomode_to_var(&fb_info->var, mode);
/* init the color fields */
mxsfb_check_var(&fb_info->var, fb_info);
platform_set_drvdata(pdev, fb_info);
ret = register_framebuffer(fb_info);
if (ret != 0) {
dev_err(&pdev->dev,"Failed to register framebuffer\n");
goto fb_destroy;
}
if (!host->enabled) {
mxsfb_enable_axi_clk(host);
writel(0, host->base + LCDC_CTRL);
mxsfb_disable_axi_clk(host);
mxsfb_set_par(fb_info);
mxsfb_enable_controller(fb_info);
}
dev_info(&pdev->dev, "initialized\n");
return 0;
fb_destroy:
if (host->enabled)
clk_disable_unprepare(host->clk);
fb_release:
framebuffer_release(fb_info);
return ret;
}
? 从一开始就有一个值得注意的地方,就是framebuffer_alloc的使用,传进大小是struct mxsfb_info
结构体的大小,一开始我还挺懵的,难道不应该是传fb_info
的大小吗?然后往下再看就明白了。原来虽然是申请的大小不是fb_info
,但是下面的to_imxfb_host
函数实际上是container_of
的宏定义,并且值得注意的是,fb_info是mxsfb_info的第一个成员,所以才这么使用。
? 申请完了内存,就该完善里面的参数了。里面使用了很多of类函数来获取设备树中的lcd寄存器地址,并且使用ioremap
进行映射。mxsfb_init_fbinfo
这里就是填充fb_info了,并且使用DMA。
? 最后就是调用register_framebuffer
注册fb_info了。
顺便看看mxs_ops:
static struct fb_ops mxsfb_ops = {
.owner = THIS_MODULE,
.fb_check_var = mxsfb_check_var,
.fb_set_par = mxsfb_set_par,
.fb_setcolreg = mxsfb_setcolreg,
.fb_blank = mxsfb_blank,
.fb_pan_display = mxsfb_pan_display,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
};
这是一些关于屏幕的操作,例如填充、划线、清屏等。还有设置lcd参数的函数。