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);
……
這樣就完成了驅動初始化到使用者調用的整個過程。