环境
正点提供的 linux 4.15
LCD设备树信息
看下imx6ull提供的设备树信息:
arch/arm/boot/dts/imx6ull.dtsi:
lcdif: lcdif@021c8000 {
compatible = "fsl,imx6ul-lcdif", "fsl,imx28-lcdif";
reg = <0x021c8000 0x4000>;
interrupts = <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_LCDIF_PIX>,
<&clks IMX6UL_CLK_LCDIF_APB>,
<&clks IMX6UL_CLK_DUMMY>;
clock-names = "pix", "axi", "disp_axi";
status = "disabled";
};
&lcdif {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_lcdif_dat
&pinctrl_lcdif_ctrl>;
display = <&display0>;
status = "okay";
/* 7寸1024*600 */
display0: display {
bits-per-pixel = <24>;
bus-width = <24>;
display-timings {
native-mode = <&timing0>;
timing0: timing0 {
clock-frequency = <51200000>; // 不同尺寸时钟频率不同
hactive = <1024>; // 下面这些长宽高、垂直/水平同步设置都不同
vactive = <600>;
hfront-porch = <160>;
hback-porch = <140>;
hsync-len = <20>;
vback-porch = <20>;
vfront-porch = <12>;
vsync-len = <3>;
// 这些active信息是一样的,想开就开不想开就不开
hsync-active = <0>;
vsync-active = <0>;
de-active = <1>;
pixelclk-active = <0>;
};
};
};
};
其中提供了寄存器信息,中断信息,时钟信息。本来想去documetation下看看imx6ull有没有为用户提供些设备树使用说明什么的,发现没有文档:
那就无所谓了,也可能是我没找到,应该是原厂直接给正点提供的sdk里面有讲,但是没放到内核文件夹下。直接看驱动就可以了。本质上,原厂提供的sdk的设备树配置教程,就是驱动的一个映射,原厂的驱动要使用一些of函数解析这些property,所以才让用户按照要求写。
imx6ull的LCD驱动
当然原厂也不是乱写的,linux发展到现在,每一种设备的驱动都有框架的支持(如果不是特别奇怪的设备),而且大概率能通过框架的支持简化一些工作,平台化的工作可以提高效率。LCD用到的框架有:
- framebuffer框架
看一下imx6ull的LCD驱动,通过compatible找到:
drivers/video/fbdev/mxsfb.c:
static const struct of_device_id mxsfb_dt_ids[] = {
{ .compatible = "fsl,imx23-lcdif", .data = &mxsfb_devtype[0], },
{ .compatible = "fsl,imx28-lcdif", .data = &mxsfb_devtype[1], }, // compatible匹配
{ /* sentinel */ }
};
...
...
static struct platform_driver mxsfb_driver = {
.probe = mxsfb_probe,
.remove = mxsfb_remove,
.shutdown = mxsfb_shutdown,
.id_table = mxsfb_devtype,
.driver = {
.name = DRIVER_NAME,
.of_match_table = mxsfb_dt_ids,
.pm = &mxsfb_pm_ops,
},
};
找到probe函数,也就是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; // g, 本驱动下自己定义的一个结构体,使用该结构体描述LCD的主控接口等等一些信息。
struct fb_info *fb_info; // g, framebuffer结构体,框架相关
struct pinctrl *pinctrl;
int irq = platform_get_irq(pdev, 0); // g, in 获取pdev设备的irq,需要提供"interrupts"或"interrupts-extended"属性,或者手动为pdev->resource设置中断信息资源
int gpio, ret;
if (of_id)
pdev->id_entry = of_id->data;
gpio = of_get_named_gpio(pdev->dev.of_node, "enable-gpio", 0); // g,根据属性名获取GPIO号,但是设备树中是没有使用"enable-gpio"这个GPIO属名的,所以自然获取不到有效的gpio号,
if (gpio == -EPROBE_DEFER) // g, 这个EPROBE_DEFER有点意思,因为驱动有依赖关系,如果某个驱动的probe函数返回了-EPROBE_DEFER,说明该驱动依赖的另外一个驱动没有准备好,会暂时把该驱动加入到一个延时链表中,过一会会重新执行probe
return -EPROBE_DEFER;
if (gpio_is_valid(gpio)) { // g, 检测gpio号是否满足:0 <= gpio <= ARCH_NR_GPIOS(默认为ARCH_NR_GPIOS=512个GPIO)
ret = devm_gpio_request_one(&pdev->dev, gpio, GPIOF_OUT_INIT_LOW, "lcd_pwr_en");
if (ret) {
dev_err(&pdev->dev, "faild to request gpio %d, ret = %d\n", gpio, ret);
return ret;
}
}
// g, 从设备树中获取eLCDIF接口控制器的寄存器首地址
// g, 设备树中lcdif节点已经设置了eLCDIF寄存器首地址为0X021C8000(lcdif: lcdif@021c8000),因此res=0X021C8000
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "Cannot get memory IO resource\n");
return -ENODEV;
}
host = devm_kzalloc(&pdev->dev, sizeof(struct mxsfb_info), GFP_KERNEL); // g, 为主控接口host申请内存
if (!host) {
dev_err(&pdev->dev, "Failed to allocate IO resource\n");
return -ENOMEM;
}
fb_info = framebuffer_alloc(sizeof(struct fb_info), &pdev->dev); // g, 申请一个fb_info
if (!fb_info) {
dev_err(&pdev->dev, "Failed to allocate fbdev\n");
devm_kfree(&pdev->dev, host);
return -ENOMEM;
}
host->fb_info = fb_info; // g, 设置host的fb指针指向申请的fb_info
fb_info->par = host; // g, 设置parent指向host
ret = devm_request_irq(&pdev->dev, irq, mxsfb_irq_handler, 0, // g, 为virq号(也就是这里的参数irq)申请一个中断处理函数(mxsfb_irq_handler),
dev_name(&pdev->dev), host);
if (ret) {
dev_err(&pdev->dev, "request_irq (%d) failed with error %d\n",
irq, ret);
ret = -ENODEV;
goto fb_release;
}
host->base = devm_ioremap_resource(&pdev->dev, res); // g, 开始映射实际物理基址到虚拟地址,并保存到host->base,以后就可以通过虚拟基址+偏移的方式访问一些寄存器。
if (IS_ERR(host->base)) {
dev_err(&pdev->dev, "ioremap failed\n");
ret = PTR_ERR(host->base);
goto fb_release;
}
host->pdev = pdev; // g, 绑定host->pdev指向本dev
platform_set_drvdata(pdev, host);
host->devdata = &mxsfb_devdata[pdev->id_entry->driver_data];
// g, 最终找到要调用__of_clk_get_by_name(dev, "pix")函数
// g, 该函数首先找到属性"clock-names"中"pix"是第几个index,然后使用该index去索引"clocks"属性。所以设备数中"clocks"属性内容与"clock-names"属性内容要一一对应。
host->clk_pix = devm_clk_get(&host->pdev->dev, "pix");
if (IS_ERR(host->clk_pix)) {
host->clk_pix = NULL;
ret = PTR_ERR(host->clk_pix);
goto fb_release;
}
host->clk_axi = devm_clk_get(&host->pdev->dev, "axi");
if (IS_ERR(host->clk_axi)) {
host->clk_axi = NULL;
ret = PTR_ERR(host->clk_axi);
goto fb_release;
}
host->clk_disp_axi = devm_clk_get(&host->pdev->dev, "disp_axi");
if (IS_ERR(host->clk_disp_axi)) {
host->clk_disp_axi = NULL;
ret = PTR_ERR(host->clk_disp_axi);
goto fb_release;
}
host->reg_lcd = devm_regulator_get(&pdev->dev, "lcd"); // g, 电源相关,做PMIC驱动时分析过regulator。
if (IS_ERR(host->reg_lcd))
host->reg_lcd = NULL;
fb_info->pseudo_palette = devm_kzalloc(&pdev->dev, sizeof(u32) * 16, // g, 为pseudo_palette(伪16位调色板)分配内存
GFP_KERNEL);
if (!fb_info->pseudo_palette) {
ret = -ENOMEM;
goto fb_release;
}
INIT_LIST_HEAD(&fb_info->modelist);
pm_runtime_enable(&host->pdev->dev);
// g, 调用mxsfb_init_fbinfo函数初始化fb_info,重点是fb_info的var、fix、fbops,screen_base和screen_size,同时为fb_info绑定fbops为mxsfb_ops(在本文件下)。
// g, 并在最后调用mxsfb_map_videomem为framebuffer申请显存,首地址保存在fb_info->screen_base中。
ret = mxsfb_init_fbinfo(host);
if (ret != 0)
goto fb_pm_runtime_disable;
// g, 下面这个函数不起作用,只有当设备数中有"disp-dev"这个属性的时候, 才会真的发挥作用
// g, 根本没有disp设备?没时间看了
mxsfb_dispdrv_init(pdev, fb_info);
if (!host->dispdrv) { // g, 如果存在disp设备
pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
if (IS_ERR(pinctrl)) {
ret = PTR_ERR(pinctrl);
goto fb_pm_runtime_disable;
}
}
if (!host->enabled) { // g, 如果还没使能,就要配置一些重要的控制寄存器,下面的函数都是在配置一些寄存器。
writel(0, host->base + LCDC_CTRL);
mxsfb_set_par(fb_info);
mxsfb_enable_controller(fb_info);
pm_runtime_get_sync(&host->pdev->dev);
}
// g, 该api会调用do_register_framebuffer,通过device_create向fb_class(名为"graphics")这个类中注册fb设备,fb设备命名为"fb%d",index为registered_fb[]表中还没有使用的index
// g, 可以在/sys/class/graphics/下找到fbx
// g, 其中新注册的fb设备的父设备指向fb_info->device,在使用framebuffer_alloc时已经指向本LCD设备。
// g, 注册完后会向registered_fb[]表中添加该fb设备。
ret = register_framebuffer(fb_info); // g, 向内核注册framebuffer
if (ret != 0) {
dev_err(&pdev->dev, "Failed to register framebuffer\n");
goto fb_destroy;
}
console_lock();
ret = fb_blank(fb_info, FB_BLANK_UNBLANK);
console_unlock();
if (ret < 0) {
dev_err(&pdev->dev, "Failed to unblank framebuffer\n");
goto fb_unregister;
}
dev_info(&pdev->dev, "initialized\n");
return 0;
fb_unregister:
unregister_framebuffer(fb_info);
fb_destroy:
if (host->enabled)
clk_disable_unprepare(host->clk_pix);
fb_destroy_modelist(&fb_info->modelist);
fb_pm_runtime_disable:
pm_runtime_disable(&host->pdev->dev);
devm_kfree(&pdev->dev, fb_info->pseudo_palette);
fb_release:
framebuffer_release(fb_info);
devm_kfree(&pdev->dev, host);
return ret;
}
做的工作大致可以分为:
- 获取设备树的信息,因为把设备加到了设备树里,所以基本上是使用启动时生成的device_node + of函数来获取信息的。
- 申请一个framebuffer结构体struct fb_info
- 使用解析设备树得到的信息初始化imx6ull自定义的结构体host以及刚刚申请的fb_info。
- 使用设备树信息初始化一些硬件寄存器。
- 调用register_framebuffer()向内核注册framebuffer设备。
主要分析一下framebuffer的初始化以及注册过程。首先分析一下fb在申请完之后的初始化过程:
drivers/video/fbdev/mxsfb.c:
static int mxsfb_init_fbinfo(struct mxsfb_info *host)
{
struct fb_info *fb_info = host->fb_info;
struct fb_var_screeninfo *var = &fb_info->var;
struct fb_modelist *modelist;
int ret;
fb_info->fbops = &mxsfb_ops;
fb_info->flags = FBINFO_FLAG_DEFAULT | FBINFO_READS_FAST;
fb_info->fix.type = FB_TYPE_PACKED_PIXELS;
fb_info->fix.ypanstep = 1;
fb_info->fix.ywrapstep = 1;
fb_info->fix.visual = FB_VISUAL_TRUECOLOR,
fb_info->fix.accel = FB_ACCEL_NONE;
ret = mxsfb_init_fbinfo_dt(host);
if (ret)
return ret;
if (host->id < 0)
sprintf(fb_info->fix.id, "mxs-lcdif");
else
sprintf(fb_info->fix.id, "mxs-lcdif%d", host->id);
if (!list_empty(&fb_info->modelist)) {
/* first video mode in the modelist as default video mode */
modelist = list_first_entry(&fb_info->modelist,
struct fb_modelist, list);
fb_videomode_to_var(var, &modelist->mode);
}
/* save the sync value getting from dtb */
host->sync = fb_info->var.sync;
var->nonstd = 0;
var->activate = FB_ACTIVATE_NOW;
var->accel_flags = 0;
var->vmode = FB_VMODE_NONINTERLACED;
/* init the color fields */
mxsfb_check_var(var, fb_info);
fb_info->fix.line_length =
fb_info->var.xres * (fb_info->var.bits_per_pixel >> 3);
fb_info->fix.smem_len = SZ_32M;
/* Memory allocation for framebuffer */
if (mxsfb_map_videomem(fb_info) < 0)
return -ENOMEM;
if (mxsfb_restore_mode(host))
memset((char *)fb_info->screen_base, 0, fb_info->fix.smem_len);
return 0;
}
主要工作就是初始化fb_info的各个域,fb_info各个域的作用如下:
include/linux/fb.h:
struct fb_info {
atomic_t count;
int node;
int flags;
struct mutex lock; /* Lock for open/release/ioctl funcs, 互斥锁*/
struct mutex mm_lock; /* Lock for fb_mmap and smem_* fields,互斥锁,用于fb_mmap和smem_域 */
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,图像硬件映射 */
struct fb_pixmap sprite; /* Cursor hardware mapper,光标硬件映射 */
struct fb_cmap cmap; /* Current cmap,当前调色板 */
struct list_head modelist; /* mode list,当前模式列表 */
struct fb_videomode *mode; /* current mode,当前视频模式 */
#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; /* 操作集,framebuffer框架会调用到这个的 */
struct device *device; /* This is the parent,父设备*/
struct device *dev; /* This is this fb device,当前dev设备 */
int class_flag; /* private sysfs flags,sysfs相关 */
#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,伪16位调色板 */
#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 similar aperture base/size not
smem_start/size as smem_start may just be an object
allocated inside the aperture so may not actually overlap */
struct apertures_struct {
unsigned int count;
struct aperture {
resource_size_t base;
resource_size_t size;
} ranges[0];
} *apertures;
bool skip_vt_switch; /* no VT switch on suspend/resume required */
};
重点关注一下对fb_info->fbops的初始化:
fb_info->fbops = &mxsfb_ops;
最终指向了imx自己定义的操作函数:
drivers/video/fbdev/mxsfb.c:
// g, 关于这个东西,最终的显示设备是/dev/fb0,是个字符设备,该字符设备的操作集为 fb_fops 。
// g, 以fb_fops->mmap为例,最终会调用到fb->fb_mmap,
// g, 过程就是:先通过file信息,从registered_fbp[]全局数组中找到对应fb_info结构体,然后找到该fb_info的fb_ops,再调用fb_ops->fb_mmap
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_ioctl = mxsfb_ioctl,
.fb_blank = mxsfb_blank,
.fb_pan_display = mxsfb_pan_display,
.fb_mmap = mxsfb_mmap,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
};
这个后面大有用处,基本上很多驱动框架都是这一套模式,由框架为用户提供统一的api或者说设备文件,然后由框架提供一套标准的file_ops,然后在框架的file_ops中再调用差异性的ops,也就是我们这里的fb_info->fops,等会继续分析就知道了。其实与RTC驱动很像,框架提供一个RTC设备,然后绑定框架的ops,最终框架的ops再调用到我们自己的ops中。我猜这样做就是为了上层用户层的移植工作,相当于在提供hal层,因为框架提供的设备往往设备名都是一样的。就像是opencv一样,他底层封装的要打开什么名字的设备应该都是固定的。
初始化暂时只分析这一个点,接下来就是向系统中注册framebuffer设备,注意,这个framebuffer设备是每个fb_info对应的,并不是我们的硬件LCD,可以认为是个框架虚拟设备:
drivers/video/fbdev/core/fbmem.c:
int
register_framebuffer(struct fb_info *fb_info)
{
int ret;
mutex_lock(®istration_lock);
ret = do_register_framebuffer(fb_info);
mutex_unlock(®istration_lock);
return ret;
}
...
...
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);
// g, fb_class的name:graphics
// g, 主设备号使用FB_MAJOR,与fbmem_init()中使用register_chrdev(FB_MAJOR, .., fops)时传入的主设备号相同
// g, 所以会使用该主设备号对应的fops,也就是 fb_fops,在本文件下。
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;
console_lock();
if (!lock_fb_info(fb_info)) {
console_unlock();
return -ENODEV;
}
fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
unlock_fb_info(fb_info);
console_unlock();
return 0;
}
挑几个主要工作:
- 使用device_create()创建一个设备,信息有:①在fb_class这个类下注册设备;②注册设备名为"fb%d",index;③使用了设备号MKDEV(FB_MAJOR, i)
- 将该fb_info添加到了一个全局数组registered_fb[]中,索引号为index,与设备名fd%d使用的index相同
如果写过字符设备驱动,就知道该设备会在/sys/class/<fb_class>下出现,并且应该会有一个字符设备使用了相同的主设备号:
static int __init
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;
}
确实在init函数里注册了一个字符设备,绑定了操作集fb_fops,并且创建了一个名为"graphics"的fb_class类。所以当我们打开device_create()创建的设备(其实是mdev创建),也就是/dev/fb%x(index顺延,每注册一个+1)的时候,操作集使用的是同主设备号的字符设备的操作集,也就是fb_fops:
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
.llseek = default_llseek,
};
其实用户程序能用到的函数,大概率就这么几个:
- open
- close
- mmap
- ioctl
就以mmap为例,看一下框架提供的fops – fb_mmap()是做了什么操作:
drivers/video/fbdev/core/fbmem.c:
static int
fb_mmap(struct file *file, struct vm_area_struct * vma)
{
struct fb_info *info = file_fb_info(file); // g, 回去registered_fb[]数组中找对应的注册了的struct fb_info
struct fb_ops *fb;
unsigned long mmio_pgoff;
unsigned long start;
u32 len;
if (!info)
return -ENODEV;
fb = info->fbops;
if (!fb)
return -ENODEV;
mutex_lock(&info->mm_lock);
if (fb->fb_mmap) {
int res;
res = fb->fb_mmap(info, vma); // g, 重点,如果fb_info->fb_mmap实现了,
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;
len = info->fix.smem_len;
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);
fb_pgprotect(file, vma, start);
return vm_iomap_memory(vma, start, len);
}
如果fb_info实现了mmap,最终还是会调用fb->fb_mmap,也就是:
static int mxsfb_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
u32 len;
// g, vma->vm_pgoff表示这个vma中的第一页在地址空间里是第几页(页帧号)。
unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; // g, vma->vm_pgoff单位为页,*2^12转换为单位字节
if (offset < info->fix.smem_len) {
/* mapping framebuffer memory */
len = info->fix.smem_len - offset;
// g, 在mxsfb_map_videomem()中,已经使用dma_alloc_writecombine()将info->fix.smem_start映射到了分配的DMA内存l中恶
// g, 分配的DMA内存基址最终也会保存在LCD的显存基址寄存器中
vma->vm_pgoff = (info->fix.smem_start + offset) >> PAGE_SHIFT;
} else
return -EINVAL;
len = PAGE_ALIGN(len);
if (vma->vm_end - vma->vm_start > len)
return -EINVAL;
/* make buffers bufferable */
vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
// g, 第三个参数是内核页的物理地址,这一片vma的物理页帧号起始
// g, 最终将这一段使用mmap的用户空间vma映射到从在DMA中申请的显存中
if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff,
vma->vm_end - vma->vm_start, vma->vm_page_prot)) {
dev_dbg(info->device, "mmap remap_pfn_range failed\n");
return -ENOBUFS;
}
return 0;
}
看一下注释把,基本上都是一样的套路。但是在计算vma->pgoff时没太看懂。有空再研究研究出现的几个内存api