1. 程式人生 > 其它 >LCD驅動研究

LCD驅動研究

技術標籤:Linux驅動分析linux作業系統嵌入式

文章目錄

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);
     
}

簡單的測試

[email protected]:/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寫了以上應用程式進行測試,以下是列印資訊。

[email protected]:/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
[email protected]:/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,

[email protected]:/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
[email protected]:/dev# dd if=/dev/zero of=/dev/fb0 bs=1024 count=3000
3000+0 records in
3000+0 records out
[email protected]:/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顯示是一塊一塊的消失的,比例也符合實際佔比。

[email protected]:/dev# dd if=/dev/zero of=/dev/fb bs=1024 count=750
750+0 records in
750+0 records out
[email protected]:/dev# dd if=/dev/zero of=/dev/fb bs=1024 count=1000
1000+0 records in
1000+0 records out
[email protected]:/dev# dd if=/dev/zero of=/dev/fb bs=1024 count=1250
1250+0 records in
1250+0 records out
[email protected]:/dev# dd if=/dev/zero of=/dev/fb bs=1024 count=1450
1450+0 records in
1450+0 records out
[email protected]:/dev# dd if=/dev/zero of=/dev/fb bs=1024 count=1500
1500+0 records in
1500+0 records out
[email protected]:/dev#