linux LCD驅動(二)–FrameBuffer

來源:互聯網
上載者:User

2.  Linux 驅動
2.1  FrameBuffer
Linux是工作在保護模式下,所以使用者態進程是無法像DOS那樣使用顯卡BIOS裡提供的中斷調用來實現直接寫屏,Lin仿顯卡的功能,將顯ux抽象出FrameBuffer這個裝置來供使用者態進程實現直接寫屏。Framebuffer機制模卡硬體結構抽象掉,可以通過Framebuffer的讀寫直接對顯存進行操作。使用者可以將Framebuffer看成是顯示記憶體的一個映像,將其映射到進程地址空間之後,就可以直接進行讀寫操作,而寫操作可以立即反應在螢幕上。這種操作是抽象的,統一的。使用者不必關心物理顯存的位置、換頁機制等等具體細節。這些都是由Framebuffer裝置驅動來完成的。

在Linux系統下,FrameBuffer的主要的結構。Linux為了開發FrameBuffer程式的方便,使用了分層結構。fbmem.c處於Framebuffer裝置驅動技術的中心位置。它為上層應用程式提供系統調用,也為下一層的特定硬體驅動提供介面;那些底層硬體驅動需要用到這兒的介面來向系統核心註冊它們自己。

fbmem.c 為所有支援FrameBuffer的裝置驅動提供了通用的介面,避免重複工作。下將介紹fbmem.c主要的一些資料結構。

2.2  資料結構
2.2.1  Linux FrameBuffer的資料結構
在FrameBuffer中,fb_info可以說是最重要的一個結構體,它是Linux為幀緩衝裝置定義的驅動層介面。它不僅包含了底層函數,而且還有記錄裝置狀態的資料。每個幀緩衝裝置都與一個fb_info結構相對應。fb_info的主要成員如下
struct fb_info {
 int node;
 struct fb_var_screeninfo var; /* Current var */
 struct fb_fix_screeninfo fix;  /* Current fix */
 struct fb_videomode *mode; /* current mode */

 struct fb_ops *fbops;
 struct device *device;   /* This is the parent */
 struct device *dev;   /* This is this fb device */

 char __iomem *screen_base; /* Virtual address */
 unsigned long screen_size; /* Amount of ioremapped VRAM or 0 */ 
 …………
};
其中node成員域標示了特定的FrameBuffer,實際上也就是一個FrameBuffer裝置的次裝置號。fb_var_screeninfo結構體成員記錄使用者可修改的顯示控制器參數,包括螢幕解析度和每個像素點的位元數。fb_var_screeninfo中的xres定義螢幕一行有多少個點, yres定義螢幕一列有多少個點, bits_per_pixel定義每個點用多少個位元組表示。其他域見以下代碼注釋。
struct fb_var_screeninfo {
 __u32 xres;   /* visible resolution */
 __u32 yres;
 __u32 xoffset;  /* offset from virtual to visible */
 __u32 yoffset;  /* resolution */
 __u32 bits_per_pixel; /* bits/pixel */
 __u32 pixclock;  /* pixel clock in ps (pico seconds) */
 __u32 left_margin; /* time from sync to picture */
 __u32 right_margin; /* time from picture to sync */
 __u32 hsync_len;  /* length of horizontal sync */
 __u32 vsync_len;  /* length of vertical sync */
 …………
};
在fb_info結構體中,fb_fix_screeninfo中記錄使用者不能修改的顯示控制器的參數,如螢幕緩衝區的物理地址,長度。當對幀緩衝裝置進行映射操作的時候,就是從fb_fix_screeninfo中取得緩衝區物理地址的。
struct fb_fix_screeninfo {
 char id[16];        /* identification string eg "TT Builtin" */
 unsigned long smem_start;    /* Start of frame buffer mem (physical address) */
 __u32 smem_len;        /* Length of frame buffer mem */
 unsigned long mmio_start;    /* Start of Mem Mapped I/O(physical address) */
 __u32 mmio_len;      /* Length of Memory Mapped I/O  */
 …………
};
fb_info還有一個很重要的域就是fb_ops。它是提供給底層裝置驅動的一個介面。通常我們編寫字元驅動的時候,要填寫一個file_operations結構體,並使用register_chrdev()註冊之,以告訴Linux如何操控驅動。當我們編寫一個FrameBuffer的時候,就要依照Linux FrameBuffer編程的套路,填寫fb_ops結構體。這個fb_ops也就相當於通常的file_operations結構體。
struct fb_ops {
int (*fb_open)(struct fb_info *info, int user);
int (*fb_release)(struct fb_info *info, int user);
ssize_t (*fb_read)(struct file *file, char __user *buf, size_t count, loff_t *ppos);
ssize_t (*fb_write)(struct file *file, const char __user *buf, size_t count,
loff_t *ppos);
int (*fb_set_par)(struct fb_info *info);
int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
unsigned blue, unsigned transp, struct fb_info *info);
int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info)
int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);
……………
}
上面的結構體,根據函數的名字就可以看出它的作用,這裡不在一一說明。給出了Linux FrameBuffer的總體結構,作為這一部分的總結。
 
圖2.2

2.2.2  S3C2410中LCD的資料結構
 在S3C2410的LCD裝置驅動中,定義了s3c2410fb_info來標識一個LCD裝置,結構體如下:
struct s3c2410fb_info {
 struct fb_info  *fb;
 struct device  *dev;
 struct s3c2410fb_mach_info *mach_info;
 struct s3c2410fb_hw regs;  /* LCD Hardware Regs */
 dma_addr_t  map_dma;  /* physical */
 u_char *   map_cpu;  /* virtual */
 u_int   map_size;
 /* addresses of pieces placed in raw buffer */
 u_char *   screen_cpu;  /* virtual address of buffer */
 dma_addr_t  screen_dma;  /* physical address of buffer */
 …………
};
成員變數fb指向我們上面所說明的fb_info結構體,代表了一個FrameBuffer。dev則表示了這個LCD裝置。map_dma,map_cpu,map_size這三個域向了開闢給LCD DMA使用的記憶體位址。screen_cpu,screen_dma指向了LCD控制器映射的記憶體位址。另外regs標識了LCD控制器的寄存器。
struct s3c2410fb_hw {
 unsigned long lcdcon1;
 unsigned long lcdcon2;
 unsigned long lcdcon3;
 unsigned long lcdcon4;
 unsigned long lcdcon5;
};
這個寄存器和硬體的寄存器一一對應,主要作為實際寄存器的映像,以便程式使用。
 這個s3c2410fb_info中還有一個s3c2410fb_mach_info成員域。它存放了和體繫結構相關的一些資訊,如時鐘、LCD裝置的GPIO口等等。這個結構體定義為
struct s3c2410fb_mach_info {
 unsigned char fixed_syncs; /* do not update sync/border */
 int type;      /* LCD types */
 int width;      /* Screen size */
 int height;
 struct s3c2410fb_val xres;  /* Screen info */
 struct s3c2410fb_val yres;
 struct s3c2410fb_val bpp;
 struct s3c2410fb_hw  regs;  /* lcd configuration registers */
 /* GPIOs */
 unsigned long gpcup;
 unsigned long gpcup_mask;
 unsigned long gpccon;
 unsigned long gpccon_mask;
 ………… 
}; 
  
圖2.3
表示了S3C2410驅動的整體結構,反映了結構體之間的相互關係

2.3  主要代碼結構以及關鍵程式碼分析
2.3.1  FrameBuffer驅動的統一管理
 fbmem.c實現了Linux FrameBuffer的中介層,任何一個FrameBuffer驅動,在系統初始化時,必須向fbmem.c註冊,即需要調用register_framebuffer()函數,在這個過程中,裝置驅動的資訊將會存放入名稱為registered_fb數組中,這個數組定義為
struct fb_info *registered_fb[FB_MAX];
int num_registered_fb;
它是類型為fb_info的數組,另外num_register_fb則存放了註冊過的裝置數量。
 我們分析一下register_framebuffer的代碼。
int register_framebuffer(struct fb_info *fb_info)
{
 int i;
 struct fb_event event;
 struct fb_videomode mode;

 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;

 fb_info->dev = device_create(fb_class, fb_info->device,
     MKDEV(FB_MAJOR, i), "fb%d", i);  /* 為裝置建立裝置節點 */
 if (IS_ERR(fb_info->dev)) {
  …………
 } else{
  fb_init_device(fb_info);      /* 初始化改裝置 */
 }
 …………
 return 0;
}
從上面的代碼可知,當FrameBuffer驅動進行註冊的時候,它將驅動的fb_info結構體記錄到全域數組registered_fb中,並動態建立裝置節點,進行裝置的初始化。注意,這裡建立的裝置節點的次裝置號就是該驅動資訊在registered_fb存放的位置,即數組下標i 。在完成註冊之後,fbmem.c就記錄了驅動的fb_info。這樣我們就有可能實現fbmem.c對全部FrameBuffer驅動的統一處理。

2.3.2  實現訊息的指派
fbmem.c實現了對系統全部FrameBuffer裝置的統一管理。當使用者嘗試使用一個特定的FrameBuffer時,fbmem.c怎麼知道該調用那個特定的裝置驅動呢?
我們知道,Linux是通過主裝置號和次裝置號,對裝置進行唯一標識。不同的FrameBuffer裝置向fbmem.c註冊時,程式分配給它們的主裝置號是一樣的,而次裝置號是不一樣的。於是我們就可以通過使用者指明的次裝置號,來覺得具體該調用哪一個FrameBuffer驅動。下面通過分析fbmem.c的fb_open()函數來說明。(註:一般我們寫FrameBuffer驅動不需要實現open函數,這裡只是說明函數流程。)

static int fb_open(struct inode *inode, struct file *file){
 int fbidx = iminor(inode);
 struct fb_info *info;
 int res;
    /* 得到真正驅動的函數指標 */
 if (!(info = registered_fb[fbidx])) return -ENODEV;  
 if (info->fbops->fb_open) {
  res = info->fbops->fb_open(info,1); //調用驅動的open()
  if (res)  module_put(info->fbops->owner);
 }
 return res;
}
當使用者開啟一個FrameBuffer裝置的時,將調用這裡的fb_open()函數。傳進來的inode就是欲開啟裝置的裝置號,包括主裝置和次裝置號。fb_open函數首先通過iminor()函數取得次裝置號,然後查全域數組registered_fb得到裝置的fb_info資訊,而這裡面存放了裝置的操作函數集fb_ops。這樣,我們就可以調用具體驅動的fb_open() 函數,實現open的操作。下面給出了一個LCD驅動的open() 函數的調用流程圖,用以說明上面的步驟。
 
 圖2.4

2.3.3  開發板S3C2410 LCD驅動的流程
(1)在mach-smdk2410.c中,定義了初始的LCD參數。注意這是個全域變數。
static struct s3c2410fb_mach_info smdk2410_lcd_cfg = {
 .regs= {
  .lcdcon1 = S3C2410_LCDCON1_TFT16BPP |
   S3C2410_LCDCON1_TFT|
   S3C2410_LCDCON1_CLKVAL(7),
  ......
 },
 .width  = 240,   .height = 320,
 .xres = {.min = 240,.max= 240,.defval = 240},
 .bpp   = {.min = 16,  .max= 16,  .defval = 16},
 ......
};
(2)核心初始化時候調用s3c2410fb_probe函數。下面分析這個函數的做的工作。首先先動態分配s3c2410fb_info空間。
   fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info),&pdev->dev);
把域mach_info指向mach-smdk2410.c中的smdk2410_lcd_cfg 。
info->mach_info = pdev->dev.platform_data;
設定fb_info域的fix,var,fops欄位。

fbinfo->fix.type  =  FB_TYPE_PACKED_PIXELS;
fbinfo->fix.type_aux     = 0;
fbinfo->fix.xpanstep     = 0;

fbinfo->var.nonstd     = 0;
fbinfo->var.activate   = FB_ACTIVATE_NOW;
fbinfo->var.height     = mach_info->height;
fbinfo->var.width     = mach_info->width;

fbinfo->fbops      = &s3c2410fb_ops;
……
該函數調用s3c2410fb_map_video_memory()申請DMA記憶體,即顯存。

fbi->map_size = PAGE_ALIGN(fbi->fb->fix.smem_len + PAGE_SIZE);
fbi->map_cpu  = dma_alloc_writecombine(fbi->dev, fbi->map_size,
          &fbi->map_dma, GFP_KERNEL);

fbi->map_size = fbi->fb->fix.smem_len;
…….
設定控制寄存器,設定硬體寄存器。

memcpy(&info->regs, &mach_info->regs,sizeof(info->regs));
info->regs.lcdcon1 &= ~S3C2410_LCDCON1_ENVID;
……….
調用函數s3c2410fb_init_registers(),把初始值寫入寄存器。

writel(fbi->regs.lcdcon1, S3C2410_LCDCON1);
   writel(fbi->regs.lcdcon2, S3C2410_LCDCON2);

(3)當使用者調用mmap()映射記憶體的時候,Fbmem.c把剛才設定好的顯存區域對應給使用者。
  start = info->fix.smem_start;
  len = PAGE_ALIGN( (start & ~PAGE_MASK) + info->fix.smem_len);
  io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT,
vma->vm_end - vma->vm_start,vma->vm_page_prot);
  ……
這樣就完成了驅動初始化到使用者調用的整個過程。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.