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

文章目录

  • 环境
  • LCD设备树信息
  • 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,说明该驱动依赖的另外一个驱动没有准备好,会暂时把该驱动加入到一个延时链表中,过一会会重新执行probereturn -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=0X021C8000res = 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_infoif (!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_infofb_info->par = host;						// g, 设置parent指向hostret = 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指向本devplatform_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, 向内核注册framebufferif (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");elsesprintf(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_IOstruct delayed_work deferred_work;struct fb_deferred_io *fbdefio;
#endifstruct 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_TILEBLITTINGstruct fb_tile_ops *tileops;    /* Tile Blitting */
#endifchar __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	1u32 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 notsmem_start/size as smem_start may just be an objectallocated 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;} elsefb_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);elsepm_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_infostruct 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;} elsereturn -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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/143130.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

JDBC编程详细总结

一、JDBC编程 JDBC编程有标准步骤(八股文) 注册驱动 将sql语句的运行环境加载到JVM 连接数据库 获得执行SQL的对象 执行SQL语句,获得结果 关流 1、 注册驱动 Class.forName("com.mysql.jdbc.Driver");//5.7版本 加载驱动 Class.forName("com.mysql.cj.jdb…

Android TV RecyclerView列表获得焦点左右换行

在TV上&#xff0c;用RecyclerView显示一个列表&#xff0c;飞鼠遥控左右遥控获得Item焦点&#xff0c;到最后一个进行右键换行&#xff0c;是不能做到的&#xff0c;因此需要监听key事件处理换行。 效果图如下 代码实现 Item.xml布局 <?xml version"1.0" enc…

红帽 Quay- 配置镜像代理缓存

《OpenShift / RHEL / DevSecOps 汇总目录》 说明&#xff1a;本文已经在 Quay 3.12 环境中验证 说明&#xff1a;可先根据《红帽 Quay - 安装篇》完成 Quay 安装。 镜像代理缓存功能 Quay 的镜像代理缓存功能可以将用户拉取的远程镜像保存到本地 Quay 的 proxy cache 中&am…

C++速通LeetCode中等第2题-最长连续序列

方法一&#xff0c;排序后遍历&#xff0c;后减前1&#xff0c;计数&#xff0c; 相等跳过&#xff0c;后减前&#xff01;1就保存。 class Solution { public:int longestConsecutive(vector<int>& nums) {vector<int> ans;int count 1;sort(nums.begin(),n…

Facebook的用户隐私保护:从争议到革新

Facebook早期的数据收集方式引发了隐私担忧。平台的快速增长和用户数据的大规模收集使得隐私问题逐渐显现。尤其是在2018年&#xff0c;剑桥分析事件暴露了数千万用户数据被不当使用的问题。这一事件揭示了Facebook在数据保护方面的严重漏洞&#xff0c;引发了公众对隐私保护的…

C++中的const \static \this

目录 前言 一、const关键字 1、const修饰类的成员变量 2、const修饰类的成员函数 3、const修饰类的对象 二、static关键字 1、static修饰类中的成员变量 1. 共享性 2. 初始化 3. 访问权限 4. 内存分配 5. 不依赖于对象 2、static修饰类中的成员函数 三、this关键字…

「C++系列」异常处理

【人工智能教程】&#xff0c;前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。 点击跳转到网站&#xff1a;【人工智能教程】 文章目录 一、异常处理1. 基本概念2. 示例代码3. 注意事项 二、常见的异常类…

三维天地创新方案助力实验室信息自动化技术深入发展

实验室环境条件控制非常重要,它直接影响着最终的实验或检测结果。例如不同的实验室对于温湿度有不同的要求,这就给实验室温湿度监测与采集带来了一定的困难。 三维天地自主研发的实验室信息管理系统(SW-LIMS)提出了一种检化验记录温湿度自动采集的创新方案,这一方案致力于实现…

一键文本提示实现图像对象高质量剪切与透明背景生成

按照提示词裁剪 按照边框裁剪 要实现您描述的功能,即通过一个文本提示就能自动从图片中切割出指定的对象并生成一个带有透明背景的新图像,这需要一个结合了先进的计算机视觉技术和自然语言处理能力的系统。这样的系统可以理解输入的文本指令,并将其转化为对图像内容的精确分…

解决nginx代理SSE接口的响应没有流式返回

目录 现象原来的nginx配置解决 现象 前后端分离的项目&#xff0c;前端访问被nginx反向代理的后端SSE接口&#xff0c;预期是流式返回&#xff0c;但经常是很久不响应&#xff0c;一响应全部结果一下子都返回了。查看后端项目的日志&#xff0c;响应其实是流式产生的。推测是n…

大数据新视界 --大数据大厂之探索ES:大数据时代的高效搜索引擎实战攻略

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

Markdown书写技巧深度解析

引言 在数字化时代&#xff0c;文本编辑与格式化的效率与美观性显得尤为重要。Markdown&#xff0c;作为一种轻量级的标记语言&#xff0c;以其简洁的语法和高效的文档转换能力&#xff0c;在多个领域得到广泛应用。本文将全面探讨Markdown的由来、定义、原理、内部流程、应用场…

构建自己的文生图工具:Python + Stable Diffusion + CUDA

构建自己的文生图工具&#xff1a;Python Stable Diffusion CUDA 前言概述环境搭建安装PyTorch安装Stable Diffusion编写Python代码结论结语 前言 在这个数字化和人工智能飞速发展的时代&#xff0c;图像生成技术正逐渐成为现实。想象一下&#xff0c;只需输入几个关键词&…

el-form动态标题和输入值,并且最后一个输入框不校验

需求&#xff1a;给了固定的label&#xff0c;叫xx单位&#xff0c;要输入单位的信息&#xff0c;但是属性名称都一样的&#xff0c;UI画图也是表单的形式&#xff0c;所以改为动态添加的形式&#xff0c;实现方式也很简单&#xff0c;循环就完事了&#xff0c;连着表单校验也动…

探索Facebook的黑暗面:数字化社交的双面剑

Facebook作为全球最大的社交平台&#xff0c;改变了我们的沟通和互动方式。虽然它带来了便利&#xff0c;但也存在不少隐忧。本文将探讨Facebook的负面影响&#xff0c;包括隐私问题、信息操控、心理健康危机及社交表面化等。 一、隐私问题&#xff1a;数据收集的隐忧 Facebo…

2024蓝桥杯省B好题分析

题解来自洛谷&#xff0c;作为学习 目录 宝石组合 数字接龙 爬山 拔河 宝石组合 # [蓝桥杯 2024 省 B] 宝石组合## 题目描述在一个神秘的森林里&#xff0c;住着一个小精灵名叫小蓝。有一天&#xff0c;他偶然发现了一个隐藏在树洞里的宝藏&#xff0c;里面装满了闪烁着美…

乐vs悲观锁,重vs轻量级锁,公vs非公平锁,不vs可重入锁,等等锁策略

这里讲的“乐观锁”“悲观锁”“轻量级锁”等等&#xff0c;都不是一个锁&#xff0c;而是一类锁。 比如&#xff1a;我们班有“带眼镜”的同学&#xff0c;这里“带眼镜”并不是指一个人&#xff0c;而是指一类人。 并且这里的锁&#xff0c;并不局限于Java&#xff0c;而是只…

优化数据的抓取规则:减少无效请求

在爬取房价信息的过程中&#xff0c;如何有效过滤无效链接、减少冗余请求&#xff0c;是提升数据抓取效率的关键。本文将介绍如何优化爬虫抓取贝壳等二手房平台中的房价、小区信息&#xff0c;并通过代理IP、多线程、User-Agent和Cookies的设置&#xff0c;确保数据抓取的稳定性…

(娱乐)魔改浏览器-任务栏图标右上角加提示徽章

一、目标&#xff1a; windows中&#xff0c;打开chromium&#xff0c;任务栏中会出现一个chromium的图标。我们的目标是给这个图标的右上角&#xff0c;加上"有1条新消息"的小提示图标&#xff0c;也叫徽章(badge)注意&#xff1a;本章节纯属娱乐&#xff0c;有需要…

手脱简单upx

大一下的事情&#xff0c;补个档 手动脱壳の新年快乐 查壳&#xff0c;有壳&#xff0c;UPX X32dbg打开文件&#xff0c;查看初始断点 点击PUSHAD跟进&#xff0c;CTRL*设置EIP&#xff0c;开始F8步过&#xff0c;寻找ESP寄存器第一次单个变红的地址 此时的内存窗口 开始步过…