Linux下的LCD驅動(二)
3.3 LCD檔案層
幀緩衝裝置作為一個字元裝置,其檔案操作函式就定義在檔案層fbmem.c中
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
};
幀緩衝裝置驅動的檔案操作介面已經在fbmem.c
使用者空間對幀裝置的操作主要包括open、close、ioctl和mmap實現,下面我們主要看看ioctl和mmap的實現
static long fb_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct inode *inode = file->f_path.dentry->d_inode;
int fbidx = iminor(inode); //獲得索引號
struct fb_info *info = registered_fb[fbidx]; //獲取fb_info結構體
return do_fb_ioctl(info, cmd, arg); //呼叫二次ioctl函式
}
static long do_fb_ioctl(struct fb_info *info, unsigned int cmd,
unsigned long arg)
{
struct fb_ops *fb;
struct fb_var_screeninfo var;
struct fb_fix_screeninfo fix;
struct fb_con2fbmap con2fb;
struct fb_cmap cmap_from;
struct fb_cmap_user cmap;
struct fb_event event;
void __user *argp = (void __user *)arg;
long ret = 0;
switch (cmd) {
case FBIOGET_VSCREENINFO: //獲得可變螢幕引數
if (!lock_fb_info(info))
return -ENODEV;
var = info->var;
unlock_fb_info(info);
ret = copy_to_user(argp, &var, sizeof(var)) ? -EFAULT : 0;
break;
case FBIOPUT_VSCREENINFO: //設定可變螢幕引數
if (copy_from_user(&var, argp, sizeof(var)))
return -EFAULT;
if (!lock_fb_info(info))
return -ENODEV;
acquire_console_sem();
info->flags |= FBINFO_MISC_USEREVENT;
ret = fb_set_var(info, &var);
info->flags &= ~FBINFO_MISC_USEREVENT;
release_console_sem();
unlock_fb_info(info);
if (!ret && copy_to_user(argp, &var, sizeof(var)))
ret = -EFAULT;
break;
case FBIOGET_FSCREENINFO: //獲得固定螢幕引數
if (!lock_fb_info(info))
return -ENODEV;
fix = info->fix;
unlock_fb_info(info);
ret = copy_to_user(argp, &fix, sizeof(fix)) ? -EFAULT : 0;
break;
case FBIOPUTCMAP: //設定顏色表
if (copy_from_user(&cmap, argp, sizeof(cmap)))
return -EFAULT;
ret = fb_set_user_cmap(&cmap, info);
break;
case FBIOGETCMAP: //獲得顏色表
if (copy_from_user(&cmap, argp, sizeof(cmap)))
return -EFAULT;
if (!lock_fb_info(info))
return -ENODEV;
cmap_from = info->cmap;
unlock_fb_info(info);
ret = fb_cmap_to_user(&cmap_from, &cmap);
break;
case FBIOPAN_DISPLAY:
if (copy_from_user(&var, argp, sizeof(var)))
return -EFAULT;
if (!lock_fb_info(info))
return -ENODEV;
acquire_console_sem();
ret = fb_pan_display(info, &var);
release_console_sem();
unlock_fb_info(info);
if (ret == 0 && copy_to_user(argp, &var, sizeof(var)))
return -EFAULT;
break;
case FBIO_CURSOR:
ret = -EINVAL;
break;
case FBIOGET_CON2FBMAP:
if (copy_from_user(&con2fb, argp, sizeof(con2fb)))
return -EFAULT;
if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES)
return -EINVAL;
con2fb.framebuffer = -1;
event.data = &con2fb;
if (!lock_fb_info(info))
return -ENODEV;
event.info = info;
fb_notifier_call_chain(FB_EVENT_GET_CONSOLE_MAP, &event);
unlock_fb_info(info);
ret = copy_to_user(argp, &con2fb, sizeof(con2fb)) ? -EFAULT : 0;
break;
case FBIOPUT_CON2FBMAP:
if (copy_from_user(&con2fb, argp, sizeof(con2fb)))
return -EFAULT;
if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES)
return -EINVAL;
if (con2fb.framebuffer < 0 || con2fb.framebuffer >= FB_MAX)
return -EINVAL;
if (!registered_fb[con2fb.framebuffer])
request_module("fb%d", con2fb.framebuffer);
if (!registered_fb[con2fb.framebuffer]) {
ret = -EINVAL;
break;
}
event.data = &con2fb;
if (!lock_fb_info(info))
return -ENODEV;
event.info = info;
ret = fb_notifier_call_chain(FB_EVENT_SET_CONSOLE_MAP, &event);
unlock_fb_info(info);
break;
case FBIOBLANK:
if (!lock_fb_info(info))
return -ENODEV;
acquire_console_sem();
info->flags |= FBINFO_MISC_USEREVENT;
ret = fb_blank(info, arg);
info->flags &= ~FBINFO_MISC_USEREVENT;
release_console_sem();
unlock_fb_info(info);
break;
default:
if (!lock_fb_info(info))
return -ENODEV;
fb = info->fbops;
if (fb->fb_ioctl)
ret = fb->fb_ioctl(info, cmd, arg);
else
ret = -ENOTTY;
unlock_fb_info(info);
}
return ret;
}
下面是fb_mmap函式
static int fb_mmap(struct file *file, struct vm_area_struct * vma)
{
int fbidx = iminor(file->f_path.dentry->d_inode);
struct fb_info *info = registered_fb[fbidx];
struct fb_ops *fb = info->fbops;
unsigned long off;
unsigned long start;
u32 len;
if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT))
return -EINVAL;
off = vma->vm_pgoff << PAGE_SHIFT;
if (!fb)
return -ENODEV;
mutex_lock(&info->mm_lock);
if (fb->fb_mmap) {
int res;
res = fb->fb_mmap(info, vma);
mutex_unlock(&info->mm_lock);
return res;
}
start = info->fix.smem_start; //幀緩衝區記憶體
len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.smem_len);
if (off >= len) { //記憶體對映的I/O
off -= len;
if (info->var.accel_flags) {
mutex_unlock(&info->mm_lock);
return -EINVAL;
}
start = info->fix.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;
//這是一個I/O對映,告訴maydump跳過此VMA
vma->vm_flags |= VM_IO | VM_RESERVED;
fb_pgprotect(file, vma, off);
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;
}
好了,我們已經分析完了幀緩衝驅動的檔案層和裝置層程式碼了。
總結下,幀緩衝裝置為使用者提供file_operations結構體,其實現定義在fbmem.c中;特定的幀緩衝裝置fb_info結構體成員的註冊,尤其是fb_ops中成員的實現則是由s3c2410fb.c實現,fb_ops中的成員將最終操作lcd控制器硬體暫存器。那fbmem.c和s3c2410fb.c怎麼相連的呢?其實是通過fb_info結構體,在s3c2410fb.c中呼叫register_framebuffer註冊fb_info時,其實是把fb_info註冊到一個叫struct fb_info *registered_fb[FB_MAX]這樣的一個數組中的,那麼我們在fbmem.c中就可以通過次裝置號作為registered_fb陣列的索引號查詢到相應的fb_info,從而能夠呼叫fb_info中實現的fb_ops的。
四.LCD驅動測試
對於我們的Mini2440開發板,使用的是X35型號的LCD,根據X35LCD屏的說明文件,進行移植LCD驅動,並編譯成核心,燒寫到開發板中。
實驗環境:核心linux2.6.32.2,arm-linux-gcc交叉編譯器,mini2440開發板。
核心配置:配置時候我們需要選中fbmem.c、s3c2410.c檔案以及X35LCD型號
具體測試程式碼如下
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/mman.h>
int main()
{
int fbfd=0;
struct fb_var_screeninfo vinfo;
unsigned long screensize=0;
char *fbp=0;
int x=0,y=0,i=0;
fbfd=open("/dev/fb0",O_RDWR); //開啟幀緩衝裝置
if(!fbfd){
printf("error\n");
exit(1);
}
if(ioctl(fbfd,FBIOGET_VSCREENINFO,&vinfo)){ //獲取螢幕可變引數
printf("error\n");
exit(1);
}
//列印螢幕可變引數
printf("%dx%d,%dbpp\n",vinfo.xres,vinfo.yres,vinfo.bits_per_pixel);
screensize=vinfo.xres*vinfo.yres*2; //緩衝區位元組大小
fbp=(char *)mmap(0,screensize,PROT_READ|PROT_WRITE,MAP_SHARED,fbfd,0);//對映
if((int)fbp==-1){
printf("error\n");
exit(4);
}
for(i=0;i<3;i++){ //畫圖
for(y=i*(vinfo.yres/3);y<(i+1)*(vinfo.yres/3);y++){
for(x=0;x<vinfo.xres;x++){
long location=x*2+y*vinfo.xres*2;
int r=0,g=0,b=0;
unsigned short rgb;
if (i==0)
r=((x*1.0)/vinfo.xres)*32;
if (i==1)
g=((x*1.0)/vinfo.xres)*64;
if (i==2)
b=((x*1.0)/vinfo.xres)*32;
rgb=(r<<11)|(g<<5)|b;
*((unsigned short *)(fbp+location))=rgb;
}
}
}
munmap(fbp,screensize);
close(fbfd);
return 0;
}
虛擬機器下編譯arm-linux-gcc lcd.c -o lcd
在超級終端下執行:./lcd
可以見到:螢幕上有三道顏色