Lnux驱动开发学习 -- imx6ull LCD驱动学习

环境

正点提供的 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用到的框架有:

  1. 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;
}

做的工作大致可以分为:

  1. 获取设备树的信息,因为把设备加到了设备树里,所以基本上是使用启动时生成的device_node + of函数来获取信息的。
  2. 申请一个framebuffer结构体struct fb_info
  3. 使用解析设备树得到的信息初始化imx6ull自定义的结构体host以及刚刚申请的fb_info。
  4. 使用设备树信息初始化一些硬件寄存器。
  5. 调用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(&registration_lock);
	ret = do_register_framebuffer(fb_info);
	mutex_unlock(&registration_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;
}

挑几个主要工作:

  1. 使用device_create()创建一个设备,信息有:①在fb_class这个类下注册设备;②注册设备名为"fb%d",index;③使用了设备号MKDEV(FB_MAJOR, i)
  2. 将该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,
};

其实用户程序能用到的函数,大概率就这么几个:

  1. open
  2. close
  3. mmap
  4. 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值