LCD驱动研究

LCD驱动研究

1、入口函数分析

static int __init da8xx_fb_init(void)
{
	return platform_driver_register(&da8xx_fb_driver);
}

init函数是从platform总线上注册da8xx_fb_driver。platform总线驱动暂未分析,这里不做详细研究。

2、probe函数研究

注册完成之后,内核启动时会调用fb_probe函数。

fb_probe函数非常长,下面结合代码语句进行注释分析。

static int __devinit fb_probe(struct platform_device *device)
{
	struct da8xx_lcdc_platform_data *fb_pdata =
						device->dev.platform_data;
	struct lcd_ctrl_config *lcd_cfg;
	struct da8xx_panel *lcdc_info;
	struct fb_info *da8xx_fb_info;
	struct clk *fb_clk = NULL;
	struct da8xx_fb_par *par;
	resource_size_t len;
	int ret, i;
	unsigned long lcm;
	if (fb_pdata == NULL) {
		dev_err(&device->dev, "Can not get platform data\n");
		return -ENOENT;
	}
	lcdc_regs = platform_get_resource(device, IORESOURCE_MEM, 0);//通过platform获取设备的io物理地址

	if (!lcdc_regs) {
		dev_err(&device->dev,
			"Can not get memory resource for LCD controller\n");
		return -ENOENT;
	}
	len = resource_size(lcdc_regs);
    /*********************************
#define request_mem_region(start,n,name) __request_region(&iomem_resource, (start), (n),(name),0)
申请IO内存,__request_region检查是否可以安全占用起始物理地址lcdc_regs->start之后的连续len字节大小空间
	***********************/
	lcdc_regs = request_mem_region(lcdc_regs->start, len, lcdc_regs->name);

	if (!lcdc_regs)
		return -EBUSY;
	da8xx_fb_reg_base = (resource_size_t)ioremap(lcdc_regs->start, len);
    //ioremap主要是检查传入地址的合法性,建立页表(包括访问权限),完成物理地址到虚拟地址的转换。
	if (!da8xx_fb_reg_base) {
		ret = -EBUSY;
		goto err_request_mem;
	}
	fb_clk = clk_get(&device->dev, NULL);//获取时钟
	if (IS_ERR(fb_clk)) {
		dev_err(&device->dev, "Can not get device clock\n");
		ret = -ENODEV;
		goto err_ioremap;
	}
	pm_runtime_irq_safe(&device->dev);
	pm_runtime_enable(&device->dev);
	pm_runtime_get_sync(&device->dev);
	/* Determine LCD IP Version */
	switch (lcdc_read(LCD_PID_REG)) {
            //lcdc_read实际上调用的是readl函数,该函数是用来专门读写IO内存资源的
            //而不是直接调用指针来访问数据。
	case 0x4C100102:
		lcd_revision = LCD_VERSION_1;
		break;
	case 0x4F200800:
	case 0x4F201000:
		lcd_revision = LCD_VERSION_2;
		break;
	default:
		dev_warn(&device->dev, "Unknown PID Reg value 0x%x, "
				"defaulting to LCD revision 1\n",
				lcdc_read(LCD_PID_REG));
		lcd_revision = LCD_VERSION_1;
		break;
	}
#ifndef CONFIG_FB_AUTO_SCAN_TQ_LCD
#if 1	//使用静态参数,简化下面的判断
	lcdc_info = known_lcd_panels;
    /*known_lcd_panels包含关于硬件的一些设置。known_lcd_panels如下所示:
    [0] ={	.name = "A70_TN92"
                .width = 800,
                .height = 480,
                .hfp = 40,
                .hbp = 88,
                .hsw = 47,//128
                .vfp = 11,
                .vbp = 4,
                .vsw = 2,
                .pxl_clk = 30000000,
                .invert_pxl_clk = 0,},*/
#else
	for (i = 0, lcdc_info = known_lcd_panels;
		i < ARRAY_SIZE(known_lcd_panels);
		i++, lcdc_info++) {
		if (strcmp(fb_pdata->type, lcdc_info->name) == 0)
			break;
	}
#endif
#else
	lcdc_info = get_EmbedSky_fb();//不执行
	printd("auto select lcdtype  \n");
#endif  /*CONFIG_FB_AUTO_SCAN_TQ_LCD*/
	if (i == ARRAY_SIZE(known_lcd_panels)) {
		dev_err(&device->dev, "GLCD: No valid panel found\n");
		ret = -ENODEV;
		goto err_pm_runtime_disable;
	} else
	{
#if 1
		dev_info(&device->dev, "GLCD: Found %s panel\n",
					lcdc_info->name);//获取name?
#else
		dev_info(&device->dev, "GLCD: Found %s panel\n",
					fb_pdata->type);
#endif
	}
	lcd_cfg = (struct lcd_ctrl_config *)fb_pdata->controller_data;
    //lcd_cfg赋值。
	da8xx_fb_info = framebuffer_alloc(sizeof(struct da8xx_fb_par),
					&device->dev);
    //framebuffer内存申请,值得注意的是申请内存时传入的变量有一个(sizeof(struct da8xx_fb_par)
    //这属于framebuffer_alloc函数的操作,这里暂不做分析
	if (!da8xx_fb_info) {
		dev_dbg(&device->dev, "Memory allocation failed for fb_info\n");
		ret = -ENOMEM;
		goto err_pm_runtime_disable;
	}
    /************************************************
     两地址相等,之后操作par相当于操作da8xx_fb_info->par
	da8xx_fb_info->par指针是void类型,由于framebuffer_alloc的操作,par才可以使用的。
	*****************************************************/
	par = da8xx_fb_info->par;

    par->dev = &device->dev;
	par->lcdc_clk = fb_clk;
#ifdef CONFIG_CPU_FREQ
	par->lcd_fck_rate = clk_get_rate(fb_clk);
#endif
	par->pxl_clk = lcdc_info->pxl_clk;
	if (fb_pdata->panel_power_ctrl) {
		par->panel_power_ctrl = fb_pdata->panel_power_ctrl;
		par->panel_power_ctrl(1);
	}
	if (lcd_init(par, lcd_cfg, lcdc_info) < 0) {
		dev_err(&device->dev, "lcd_init failed\n");
		ret = -EFAULT;
		goto err_release_fb;
	}
	/* allocate frame buffer */
    //分配framebuffer,对da8xx_fb_info->par进行赋值。
    //计算vram_size
	par->vram_size = lcdc_info->width * lcdc_info->height * lcd_cfg->bpp;
	lcm = LCM((lcdc_info->width * lcd_cfg->bpp)/8, PAGE_SIZE);
	par->vram_size = RoundUp(par->vram_size/8, lcm);
	par->vram_size = par->vram_size * LCD_NUM_BUFFERS;
      
	par->vram_virt = dma_alloc_coherent(NULL,//通过vram_size申请内存,供dma使用。
					    par->vram_size,
					    (resource_size_t *) &par->vram_phys,
					    GFP_KERNEL | GFP_DMA);
  
	if (!par->vram_virt) {//异常处理
		dev_err(&device->dev,
			"GLCD: kmalloc for frame buffer failed\n");
		ret = -EINVAL;
		goto err_release_fb;
	}
    //da8xx_fb_fix and da8xx_fb_var is global value
	da8xx_fb_info->screen_base = (char __iomem *) par->vram_virt;//fb's vram_virt
	da8xx_fb_fix.smem_start    = par->vram_phys;//phys addr 
	da8xx_fb_fix.smem_len      = par->vram_size;//phys size
	da8xx_fb_fix.line_length   = (lcdc_info->width * lcd_cfg->bpp) / 8;//行长度
    //dma start and end,对DMA的起始和终止位置进行配置
	par->dma_start = par->vram_phys;
	par->dma_end   = par->dma_start + lcdc_info->height *
		da8xx_fb_fix.line_length - 1;
	/* allocate palette buffer 申请调色板内存*/
	par->v_palette_base = dma_alloc_coherent(NULL,
					       PALETTE_SIZE,//大小
					       (resource_size_t *)
					       &par->p_palette_base,//地址
					       GFP_KERNEL | GFP_DMA);
	if (!par->v_palette_base) {
		dev_err(&device->dev,
			"GLCD: kmalloc for palette buffer failed\n");
		ret = -EINVAL;
		goto err_release_fb_mem;
	}
	memset(par->v_palette_base, 0, PALETTE_SIZE);//对调色板内存清零

	par->irq = platform_get_irq(device, 0);//配置irq
	if (par->irq < 0) {
		ret = -ENOENT;
		goto err_release_pl_mem;
	}
	/* Initialize par*/ 
	/*将lcdc_info的数值赋予da8xx_fb_var,最终赋予da8xx_fb_info*/
    /*lcd_info是为了在一开始方便赋值,并且daxxfb_var的数值不仅保存info的数值*/
	da8xx_fb_info->var.bits_per_pixel = lcd_cfg->bpp;//每个像素所占的位数
	da8xx_fb_var.xres = lcdc_info->width;//宽度
	da8xx_fb_var.xres_virtual = lcdc_info->width;//虚拟宽度
	da8xx_fb_var.yres         = lcdc_info->height;//高度
	da8xx_fb_var.yres_virtual = lcdc_info->height * LCD_NUM_BUFFERS;//虚拟高度
	da8xx_fb_var.grayscale =
	    lcd_cfg->p_disp_panel->panel_shade == MONOCHROME ? 1 : 0;//灰度等级
	da8xx_fb_var.bits_per_pixel = lcd_cfg->bpp;//每个像素所占的位数
	da8xx_fb_var.hsync_len = lcdc_info->hsw;//行同步电平脉冲宽度
	da8xx_fb_var.vsync_len = lcdc_info->vsw;//列同步电平脉冲宽度
	da8xx_fb_var.right_margin = lcdc_info->hfp;//右边沿
	da8xx_fb_var.left_margin  = lcdc_info->hbp;//左边沿
	da8xx_fb_var.lower_margin = lcdc_info->vfp;//低边沿
	da8xx_fb_var.upper_margin = lcdc_info->vbp;//高边沿
	/* Initialize fbinfo 对da8xx_fb_info的数据汇总赋值*/
	da8xx_fb_info->flags = FBINFO_FLAG_DEFAULT;
	da8xx_fb_info->fix = da8xx_fb_fix;//关于地址的一些信息
	da8xx_fb_info->var = da8xx_fb_var;//关于LCD控制器的一些信息
	da8xx_fb_info->fbops = &da8xx_fb_ops;//操作函数
	da8xx_fb_info->pseudo_palette = par->pseudo_palette;//
	da8xx_fb_info->fix.visual = (da8xx_fb_info->var.bits_per_pixel <= 8) ?
				FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR;//关于颜色参数
    //fb分配cmap内存
	ret = fb_alloc_cmap(&da8xx_fb_info->cmap, PALETTE_SIZE, 0);
	if (ret)//异常处理
		goto err_release_pl_mem;
	da8xx_fb_info->cmap.len = par->palette_sz;
	/* initialize var_screeninfo */
	da8xx_fb_var.activate = FB_ACTIVATE_FORCE;//初始化屏幕信息
	fb_set_var(da8xx_fb_info, &da8xx_fb_var);
	dev_set_drvdata(&device->dev, da8xx_fb_info);
	/* initialize the vsync wait queue 配置帧同步等待队列*/
	init_waitqueue_head(&par->vsync_wait);
	par->vsync_timeout = HZ / 5;
	par->which_dma_channel_done = -1;
	spin_lock_init(&par->lock_for_chan_update);
	/* Register the Frame Buffer 最终注册framebuffer */
	if (register_framebuffer(da8xx_fb_info) < 0) {
		dev_err(&device->dev,
			"GLCD: Frame Buffer Registration Failed!\n");
		ret = -EINVAL;
		goto err_dealloc_cmap;
	}
#ifdef CONFIG_CPU_FREQ//未定义
	ret = lcd_da8xx_cpufreq_register(par);
	if (ret) {
		dev_err(&device->dev, "failed to register cpufreq\n");
		goto err_cpu_freq;
	}
#endif
    //对LCD中断配置中断处理函数
	if (lcd_revision == LCD_VERSION_1)
		lcdc_irq_handler = lcdc_irq_handler_rev01;
	else
		lcdc_irq_handler = lcdc_irq_handler_rev02;

	ret = request_irq(par->irq, lcdc_irq_handler, 0,
			DRIVER_NAME, par);
	if (ret)
		goto irq_freq;
	return 0;
//以下为一些异常处理,暂不做分析
irq_freq:
#ifdef CONFIG_CPU_FREQ
	lcd_da8xx_cpufreq_deregister(par);
err_cpu_freq:
#endif
	unregister_framebuffer(da8xx_fb_info);

err_dealloc_cmap:
	fb_dealloc_cmap(&da8xx_fb_info->cmap);

err_release_pl_mem:
	dma_free_coherent(NULL, PALETTE_SIZE, par->v_palette_base,
			  par->p_palette_base);

err_release_fb_mem:
	dma_free_coherent(NULL, par->vram_size, par->vram_virt, par->vram_phys);

err_release_fb:
	framebuffer_release(da8xx_fb_info);

err_pm_runtime_disable:
	pm_runtime_put_sync(&device->dev);
	pm_runtime_disable(&device->dev);

err_ioremap:

	iounmap((void __iomem *)da8xx_fb_reg_base);

err_request_mem:
	release_mem_region(lcdc_regs->start, len);

	return ret;
}

3、操作函数f_ops研究

在da8xx当中的操作函数有以下内容:

static struct fb_ops da8xx_fb_ops = {
	.owner = THIS_MODULE,
	.fb_check_var = fb_check_var,//检测var数据
	.fb_setcolreg = fb_setcolreg,//设置颜色RGB,实现伪颜色表
	.fb_pan_display = da8xx_pan_display,//设置显示区域
	.fb_ioctl = fb_ioctl,//命令处理
	.fb_fillrect = cfb_fillrect,//绘制矩形
	.fb_copyarea = cfb_copyarea,//复制某区域
	.fb_imageblit = cfb_imageblit,//绘制图片
	.fb_blank = cfb_blank,//设置是否使能LCD控制器
};

按照顺序对这些操作函数进行分析:

static int fb_check_var(struct fb_var_screeninfo *var,
			struct fb_info *info)
{
    /*对传入的var->bits_per_pixel进行判断,根据该值对RGB的offset和length进行赋值。*/
	int err = 0;
	switch (var->bits_per_pixel) {
	case 1:
	case 8:
		var->red.offset = 0;//根据bpp设置
		var->red.length = 8;
···
		break;···
	}
	var->red.msb_right = 0;
	var->green.msb_right = 0;
	var->blue.msb_right = 0;
	var->transp.msb_right = 0;
	return err;
}
static int fb_setcolreg(unsigned regno, unsigned red, unsigned green,
			      unsigned blue, unsigned transp,
			      struct fb_info *info)
{
    //传入的参数有6个,其中4个为RGB+透明色
    //*info是fb信息,regno作用暂时不详,在下边代码中继续分析
	struct da8xx_fb_par *par = info->par;//将info的par传入内部,方便调用
	unsigned short *palette = (unsigned short *) par->v_palette_base;//同上
	u_short pal;//定义pal
	int update_hw = 0;//定义update_hw

	if (regno > 255)//用于异常处理
		return 1;
	if (info->fix.visual == FB_VISUAL_DIRECTCOLOR)//异常处理
		return 1;
	if (info->var.bits_per_pixel == 8) {//将颜色统一使用pal来表示
		red >>= 4;
		green >>= 8;
		blue >>= 12;
		pal = (red & 0x0f00);pal |= (green & 0x00f0);pal |= (blue & 0x000f);
		if (palette[regno] != pal) {//regno用于统计pal
			update_hw = 1;//需要更新则将update_hw标志为1
			palette[regno] = pal;
		}
	} else if ((info->var.bits_per_pixel == 16) && regno < 16) {
		red >>= (16 - info->var.red.length);//根据长度与偏移进行移位
		red <<= info->var.red.offset;
···
		par->pseudo_palette[regno] = red | green | blue;
		if (palette[0] != 0x4000) {
			update_hw = 1;
			palette[0] = 0x4000;
		}
	} else if (((info->var.bits_per_pixel == 32) && regno < 32) ||
		    ((info->var.bits_per_pixel == 24) && regno < 24)) {//根据长度与偏移移位
	···
	}
	/* Update the palette in the h/w as needed. */
	if (update_hw)
		lcd_blit(LOAD_PALETTE, par);
	return 0;
}
/*
 * Set new x,y offsets in the virtual display for the visible area and switch
 * to the new mode.
 *在虚拟显示区域上设置新的xy偏移,并且选择新的模式
 */
static int da8xx_pan_display(struct fb_var_screeninfo *var,
			     struct fb_info *fbi)
{
	int ret = 0;
	struct fb_var_screeninfo new_var;//中间变量,赋值使用
	struct da8xx_fb_par         *par = fbi->par;//数据传入
	struct fb_fix_screeninfo    *fix = &fbi->fix;//数据传入,方便调用
···
	if (var->xoffset != fbi->var.xoffset ||
			var->yoffset != fbi->var.yoffset) {
		memcpy(&new_var, &fbi->var, sizeof(new_var));//将fbi->var的值拷贝至new_var
		new_var.xoffset = var->xoffset;new_var.yoffset = var->yoffset;
		if (fb_check_var(&new_var, fbi))···
		else {
			memcpy(&fbi->var, &new_var, sizeof(new_var));//重新赋值回去
			start	= fix->smem_start +
				new_var.yoffset * fix->line_length +
				new_var.xoffset * fbi->var.bits_per_pixel / 8;//修改start
			end	= start + fbi->var.yres * fix->line_length - 1;//修改end
			par->dma_start	= start;
			par->dma_end	= end;//同上
			spin_lock_irqsave(&par->lock_for_chan_update,
					irq_flags);//加锁
			if (par->which_dma_channel_done == 0) {//对DMA进行配置
				lcdc_write(par->dma_start,
					   LCD_DMA_FRM_BUF_BASE_ADDR_0_REG);
				lcdc_write(par->dma_end,
					   LCD_DMA_FRM_BUF_CEILING_ADDR_0_REG);
			} else if ···
			spin_unlock_irqrestore(&par->lock_for_chan_update,
					irq_flags);//解锁
		}
	}
	return ret;
}

/*
*ioctl函数在fbmem.c当中已经实现了大部分的命令,
这里是在da8xx_fb.c当中添加的关于本lcd特性的ioctl命令,
该函数与fbmem.c的函数相兼容,在fbmem.c找不到的命令,会进一步到该函数中寻找。
*/
static int fb_ioctl(struct fb_info *info, unsigned int cmd,
			  unsigned long arg)
{
	struct lcd_sync_arg sync_arg;

	switch (cmd) {
	case FBIOGET_CONTRAST:
	···
	case FBIPUT_COLOR:
		return -ENOTTY;
	case FBIPUT_HSYNC:
		if (copy_from_user(&sync_arg, (char *)arg,//将数据从用户空间拷贝到内核空间
				sizeof(struct lcd_sync_arg)))
			return -EFAULT;
		lcd_cfg_horizontal_sync(sync_arg.back_porch,//配置lcd控制器关于行同步的参数
					sync_arg.pulse_width,
					sync_arg.front_porch);
		break;
	case FBIPUT_VSYNC:
		if (copy_from_user(&sync_arg, (char *)arg,//将数据从用户空间拷贝到内核空间
				sizeof(struct lcd_sync_arg)))
			return -EFAULT;
		lcd_cfg_vertical_sync(sync_arg.back_porch,//配置lcd控制器关于帧同步的参数
					sync_arg.pulse_width,
					sync_arg.front_porch);
		break;
	case FBIO_WAITFORVSYNC:
		return fb_wait_for_vsync(info);
	default:
		return -EINVAL;
	}
	return 0;
	}

static int cfb_blank(int blank, struct fb_info *info)
{
	struct da8xx_fb_par *par = info->par;
	int ret = 0;
···

	if (par->blank == blank)
		return 0;
	par->blank = blank;
	switch (blank) {//判断blank命令
	case FB_BLANK_UNBLANK://使能lcd控制器
		if (par->panel_power_ctrl)
			par->panel_power_ctrl(1);//改变lcd控制器存储的状态
		lcd_enable_raster();
		break;
	case FB_BLANK_POWERDOWN://判断blank命令
		if (par->panel_power_ctrl)
			par->panel_power_ctrl(0);//改变lcd控制器存储的状态
		lcd_disable_raster(WAIT_FOR_FRAME_DONE);//停止lcd控制器使能
		break;
	default:
		ret = -EINVAL;
	}
	
	return ret;
}

以下三个函数是比较通用的函数,实现起来比较复杂,但是使用方法非常简单,这里不再分析。

	.fb_fillrect = cfb_fillrect,//绘制矩形
	.fb_copyarea = cfb_copyarea,//复制某区域
	.fb_imageblit = cfb_imageblit,//绘制图片

4、编写应用程序测试LCD驱动

4.1、简单的测试应用

在之前的驱动分析中,分析了一系列的接口函数,但都未经过实际的应用,不知道函数是否分析的正确,这里编写简单的应用程序进行分析。

#include <stdio.h>
#include <endian.h>
#include <linux/fb.h>
void main (void)
{
    struct fb_var_screeninfo fb_var;
    struct fb_fix_screeninfo fb_fix;
    char *fbdev;
    int x;
    printf("ready %d\n",x);
    fbdev="/dev/fb0";
    x=open(fbdev);
    printf("open sucess %d\n",x);
    ioctl (x, FBIOGET_VSCREENINFO, &fb_var);//获取fb允许用户修改的LCD控制器数据
    ioctl (x, FBIOGET_FSCREENINFO, &fb_fix);//获取fb不允许用户修改的LCD控制器数据
    printf("width=%d\nheight=%d\n",fb_var.xres,fb_var.yres);
     
}

简单的测试

root@am335x-evm:/nfs# ./fbtest
ready 0
open sucess 3
width=800
height=480

成功打开设备,获取到了一些参数,下一步构建结构体,将从内核空间获取到的fb_var和fb_fix赋值到用户空间的结构体当中,用户使用该结构体中的数值进行相应的处理,最终要的就是对framebuffer内存进行数据填充,通过同该块内存的操作,就能够实现对LCD显示的控制。

4.2、实际绘图应用

#include <stdio.h>
​```
#include <unistd.h>

enum RGBMode {
	    RGB565,
	    BGR565,
​```
};
typedef struct PSplashFB
{
	  int            fd;			
      struct termios save_termios;	        
	  int            type;		        
      int            visual;		
	  int            width, height;
      int            bpp;
	  int            stride;
      char		*data;
	  char		*base;
      int            angle;
	  int            real_width, real_height;
	  enum RGBMode   rgbmode;
	  int            red_offset;
	  int            red_length;
···
}PSplashFB;
#define OFFSET(fb,x,y) (((y) * (fb)->stride) + ((x) * ((fb)->bpp >> 3)))
static inline void
psplash_fb_plot_pixel (PSplashFB    *fb,
		       int          x,
		       int          y,
		       uint8_t        red,
		       uint8_t        green,
		       uint8_t        blue)
{
  int off;
  if (x < 0 || x > fb->width-1 || y < 0 || y > fb->height-1)
    return;
  off = OFFSET (fb, x, y);
*(volatile uint32_t *) (fb->data + off) = (blue << 16) | (green << 8) | (red);//对内存直接操作
}

void main (void)
{
	struct fb_var_screeninfo fb_var;
	struct fb_fix_screeninfo fb_fix;
	struct fb_fillrect rect;
	char *fbdev;
	PSplashFB *fb=NULL;
	int x,off,i,j;
	printf("ready %d\n",x);
	fbdev="/dev/fb0";
	x=open(fbdev,O_RDWR);//打开设备文件
	printf("open sucess %d\n",x);
	ioctl (x, FBIOGET_VSCREENINFO, &fb_var);//获取可修改数值
	ioctl (x, FBIOGET_FSCREENINFO, &fb_fix);//获取不可修改数值
	printf("width=%d\nheight=%d\n",fb_var.xres,fb_var.yres);
	fb=malloc(sizeof(PSplashFB));//申请结构体内存
	fb->fd=x;
	fb->real_width  = fb->width  = fb_var.xres;//宽度赋值
  	fb->real_height = fb->height = fb_var.yres;//高度赋值
  	fb->bpp    = fb_var.bits_per_pixel;//一个像素占几位
  	fb->stride = fb_fix.line_length;//一行的长度?
  	fb->type   = fb_fix.type;//类型
  	fb->visual = fb_fix.visual;//虚拟地址
/*下边是重点,将framebuffer在内核中申请的内存映射到用户空间,用户才能对framebuffer直接操作
mmap函数在fbmem.c中定义,后面会再次分析*/
  	fb->base = (char *) mmap ((caddr_t) NULL,
				 /*fb_fix.smem_len */
				 fb->stride * fb->height,
				 PROT_READ|PROT_WRITE,
				 MAP_SHARED,
				 fb->fd, 0);
   	off = (unsigned long) fb_fix.smem_start % (unsigned long) getpagesize();
   	fb->data = fb->base + off;

  printf("width: %i, height: %i, bpp: %i, stride: %i\n",
      fb->width, fb->height, fb->bpp, fb->stride);
  printf("base:0x%x,off:%i,rgbmode:%i\n",fb->base,off,fb->rgbmode);

/*绘制一个黑色矩形*/  
  for(i=0;i<100;i++)
  	for(j=0;j<100;j++)
  		psplash_fb_plot_pixel(fb,100+i,100+j,0,0,0);
}

参考psplash写了以上应用程序进行测试,以下是打印信息。

root@am335x-evm:/nfs# ./fbtest
ready 1075301292
open sucess 3
width=800
height=480
width: 800, height: 480, bpp: 32, stride: 3200
base:0x401f8000,off:0,rgbmode:1
root@am335x-evm:/nfs#

4.3、重点函数mmap分析

4.3.1、mmap第一阶段——用户接口分析

分析该mmap函数,传入参数有5个:

1、(caddr_t) NULL 2、 fb->stride * fb->height, 3、 MAP_SHARED, 4、fb->fd, 5、0

fb->base = (char *) mmap ((caddr_t) NULL,
				 /*fb_fix.smem_len */
				 fb->stride * fb->height,
				 PROT_READ|PROT_WRITE,
				 MAP_SHARED,
				 fb->fd, 0);

使用man到Ubuntu下查看该函数,可以看到该函数提供的输入参数名字为以下内容。

   void *mmap(void *addr, size_t length, int prot, int flags,
              int fd, off_t offset);

通过这些信息再进一步的分析,addr为地址,length为长度大小,prot为是否可读写,flags作用不详,fd文件,offset为地址偏移。

关于addr为什么是NULL,在man mmap中做出了解释。一般为NULL时是内核选择地址创建映射,指定地址的话,内核会根据页边界创建映射。最终映射到的地址会通过函数返回数值。

If addr is NULL, then the kernel chooses the address at which to create the mapping; this is the most portable method of creating a new mapping. If addr is not NULL, then the kernel takes it as a hint about where to place the mapping; on Linux, the mapping will be created at a nearby page boundary. The address of the new mapping is returned as the result of the call.

经过分析flags,可以看到flags是关于map属性设置,例如以下两个类型,map_shared类型可以让其他进程也可视,MAP_PRIVATE则是仅自己进程可视,其他进程不可视。

MAP_SHARED
Share this mapping. Updates to the mapping are visible to other processes that map this file, and are carried through to the underlying file. (To precisely control when updates are carried through to the underlying file requires the use of msync(2).)
MAP_PRIVATE
Create a private copy-on-write mapping. Updates to the mapping are not visible to other processes mapping the same file, and are not carried through to the underlying file. It is unspecified whether changes made to the file after the mmap() call are visible in the mapped region.

至此完成分析mmap函数的传入参数情况。mmap函数经常用于进程间通信,对同一个文件进行映射,然后对这个文件进行读写,另一个进程也可以对这个文件进行映射,两个进程的虚拟内存地址都指向了同一个物理地址,当进程对自己的虚拟内存进行读写时,对另一个进程来说也是可见的,虽然两者都未将文件真正迅速同步到物理地址当中,这是由内核的机制决定的,当产生了共享映射之后,两者在读取时其实是读取的同一个位置,所以表现出来的就是两者的数据是同步的,也就实现了进程间的通信。

本次使用mmap不是用于进程间通信,仅仅是为了使用映射,对映射到用户空间的内存进行操作,减少使用read、write函数,直接对内存操作就相当于对framebuffer操作。

4.3.2、mmap第二阶段——驱动函数分析

该函数在分析fbmem.c驱动时就已经分析过,但是当时对于该函数如何使用不清楚,所以这里再次进行分析。

static int
fb_mmap(struct file *file, struct vm_area_struct * vma)
{
	struct fb_info *info = file_fb_info(file);//通过文件获取info信息
	struct fb_ops *fb;
	unsigned long off;
	unsigned long start;
	u32 len;
if (!info)
	return -ENODEV;//异常判断
if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT))
	return -EINVAL;//异常判断
off = vma->vm_pgoff << PAGE_SHIFT;//偏移
fb = info->fbops;
if (!fb)
	return -ENODEV;//异常判断
mutex_lock(&info->mm_lock);
if (fb->fb_mmap) {//判断da8xx是否存在fb_mmap函数,不存在则不执行。
	//···不存在,省略
}

/* frame buffer memory */
start = info->fix.smem_start; /* fb缓冲内存的开始位置(物理地址) */  
len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.smem_len);//获取framebuffer大小
if (off >= len) { /* 偏移值大于len长度 */
	/* memory mapped io */    /* 内存映射的IO */ 
	off -= len;
	if (info->var.accel_flags) {
		mutex_unlock(&info->mm_lock);
		return -EINVAL;
	}
	start = info->fix.mmio_start;   //mmio_start作用暂时不详
	len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.mmio_len);
}
mutex_unlock(&info->mm_lock);
start &= PAGE_MASK;
if ((vma->vm_end - vma->vm_start + off) > len)
	return -EINVAL;//异常判断
off += start;
vma->vm_pgoff = off >> PAGE_SHIFT;
/* This is an IO map - tell maydump to skip this VMA */
vma->vm_flags |= VM_IO | VM_RESERVED;
vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
fb_pgprotect(file, vma, off);
/* io_remap_pfn_range正式映射物理内存到用户空间虚拟地址 
通过remap_pfn_range函数建立页表,即实现了文件地址和虚拟地址区域的映射关系。
此时,这片虚拟地址并没有任何数据关联到主存中。*/  
if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT,
		     vma->vm_end - vma->vm_start, vma->vm_page_prot))
	return -EAGAIN;
return 0;
}

这一部分有一个非常重要的结构体vm_area_struct。

此处难度变大,主要是内核的虚拟内存管理,涉及到的知识较多,暂不做深入分析

4.3.3、mmap第三阶段——进程发起访问

当进程发起访问时,会查看实际的物理内存上数据是否正常,如果不正常就会触发页缺失异常,就会从磁盘中将数据拷贝到内存当中。之后就可以通过指针直接对该文件上的数据进行操作,通过虚拟内存地址直接操作就会映射到物理内存上,这就是mmap的作用。

5、关于fb的直接操作

使用dd命令可以直接操作framebuffer,通过dd命令可以清空framebuffer,

root@am335x-evm:/dev# dd if=/dev/zero of=/dev/fb0 bs=1024 count=20480
dd: writing ‘/dev/fb0’: No space left on device
3001+0 records in
3000+0 records out
root@am335x-evm:/dev# dd if=/dev/zero of=/dev/fb0 bs=1024 count=3000
3000+0 records in
3000+0 records out
root@am335x-evm:/dev# dd if=/dev/zero of=/dev/fb0 bs=1024 count=3001
dd: writing ‘/dev/fb0’: No space left on device
3001+0 records in
3000+0 records out

通过以上命令可以看到framebuffer大小共3000k;

通过以下命令,和通过应用程序查看的实际映射大小可以判断控制lcd显示的1500k就足够了。通过下列命令测试可以看到实际的lcd显示是一块一块的消失的,比例也符合实际占比。

root@am335x-evm:/dev# dd if=/dev/zero of=/dev/fb bs=1024 count=750
750+0 records in
750+0 records out
root@am335x-evm:/dev# dd if=/dev/zero of=/dev/fb bs=1024 count=1000
1000+0 records in
1000+0 records out
root@am335x-evm:/dev# dd if=/dev/zero of=/dev/fb bs=1024 count=1250
1250+0 records in
1250+0 records out
root@am335x-evm:/dev# dd if=/dev/zero of=/dev/fb bs=1024 count=1450
1450+0 records in
1450+0 records out
root@am335x-evm:/dev# dd if=/dev/zero of=/dev/fb bs=1024 count=1500
1500+0 records in
1500+0 records out
root@am335x-evm:/dev#

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

塔通天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值