继上篇继续分析。驱动第一层frame buffer框架层(fbmem.c)。
2.1 提供给外部模块的注册接口
int register_framebuffer(struct fb_info *fb_info)
{
int ret;
ret = do_register_framebuffer(fb_info);
return ret;
}
static int do_register_framebuffer(struct fb_info *fb_info)
{
int i;
struct fb_event event;
struct fb_videomode mode;
do_remove_conflicting_framebuffers(fb_info->apertures, fb_info->fix.id, fb_is_primary_device(fb_info));
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), NULL, "fb%d", i); //创建字符设备
if (IS_ERR(fb_info->dev)) {
printk(KERN_WARNING "Unable to create device for framebuffer %d; errno = %ld\n", i, PTR_ERR(fb_info->dev));
fb_info->dev = NULL;
} else
fb_init_device(fb_info);
fb_var_to_videomode(&mode, &fb_info->var);
fb_add_videomode(&mode, &fb_info->modelist);
registered_fb[i] = fb_info; //将传入的fb_info结构体放入到registered_fb[]数组中
fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
return 0;
}
2.2 初始化
subsys_initcall(fbmem_init);
static int __init
fbmem_init(void)
{
proc_create("fb", 0, NULL, &fb_proc_fops);
if (register_chrdev(FB_MAJOR,"fb",&fb_fops)) //注册字符设备
printk("unable to get major %d for fb devs\n", FB_MAJOR);
fb_class = class_create(THIS_MODULE, "graphics");
return 0;
}
static const struct file_operations fb_fops = {
.owner = THIS_MODULE,
.read = fb_read,
.write = fb_write,
.unlocked_ioctl = fb_ioctl,
.mmap = fb_mmap,
.open = fb_open,
.release = fb_release,
.llseek = default_llseek,
};
2.3 open 操作
static int fb_open(struct inode *inode, struct file *file)
{
int fbidx = iminor(inode);
struct fb_info *info;
int res = 0;
info = get_fb_info(fbidx);
if (!try_module_get(info->fbops->owner)) {
res = -ENODEV;
goto out;
}
file->private_data = info;
if (info->fbops->fb_open) { //由第二层代码的2-2可知,此处调用的fb_open并不存在,是NULL
res = info->fbops->fb_open(info,1);
if (res)
module_put(info->fbops->owner);
}
return res;
}
2.4 write 操作
static ssize_t fb_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
unsigned long p = *ppos;
struct fb_info *info = file_fb_info(file);
u8 *buffer, *src;
u8 __iomem *dst;
int c, cnt = 0, err = 0;
unsigned long total_size;
if (!info || !info->screen_base)
return -ENODEV;
if (info->state != FBINFO_STATE_RUNNING)
return -EPERM;
if (info->fbops->fb_write) //由第二层代码的2-2可知,此处调用的fb_write并不存在,是NULL
return info->fbops->fb_write(info, buf, count, ppos);
//具体的显示屏驱动为提供write接口的话,则使用默认的write 操作
total_size = info->screen_size;
if (total_size == 0)
total_size = info->fix.smem_len;
buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count, GFP_KERNEL);
dst = (u8 __iomem *) (info->screen_base + p); //mxcfb_probe()中设置的显存地址(fbi->screen_base)
while (count) {
c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
src = buffer;
if (copy_from_user(src, buf, c)) { //从用户空间拷贝数据至内核空间的 src缓冲区
err = -EFAULT;
break;
}
fb_memcpy_tofb(dst, src, c); //将src 缓冲区中的图像数据拷贝至显存缓冲区
dst += c;
src += c;
*ppos += c;
buf += c;
cnt += c;
count -= c;
}
kfree(buffer);
return (cnt) ? cnt : err;
}
通过write 方式来显示图片的话,需要经过两次拷贝动作;而mmap 方式只需要经过一次拷贝,所以mmap 的方式比write 的方式效率更高,下面我们看看mmap 的实现。
注:write和mmap的区别是什么,可以看我的博客《I/O操作》中的“read/write和mmap的区别”部分。
2.5 mmap操作
static int fb_mmap(struct file *file, struct vm_area_struct * vma)
{
struct fb_info *info = file_fb_info(file);
struct fb_ops *fb;
unsigned long mmio_pgoff;
unsigned long start;
u32 len;
fb = info->fbops;
if (fb->fb_mmap) { //具体驱动中有定义,则优先用驱动里面的,由第二层代码的2-2可知,此处调用的fb_mmap是存在的
int res;
res = fb->fb_mmap(info, vma); //调用mxcfb_mmap()
mutex_unlock(&info->mm_lock);
return res;
}
start = info->fix.smem_start; //显存起始地址(这个是物理地址)
len = info->fix.smem_len; //显存大小
vma->vm_page_prot = vm_get_page_prot(vma->vm_flags); //得到页的访问模式权限(读/写/可执行/私有)
fb_pgprotect(file, vma, start); //页额访问权限增加一种,不使用cache使用write buffer
/*vma时用户空间的一块分配的空间,start是要映射的物理地址,len是要映射的长度*/
return vm_iomap_memory(vma, start, len); //即将start开始、大小为len的物理地址(显存)映射到用户空间
}
static int mxcfb_mmap(struct fb_info *fbi, struct vm_area_struct *vma)
{
bool found = false;
u32 len;
unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
struct mxcfb_alloc_list *mem;
struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par;
if (offset < fbi->fix.smem_len) {
/* mapping framebuffer memory */
len = fbi->fix.smem_len - offset;
vma->vm_pgoff = (fbi->fix.smem_start + offset) >> PAGE_SHIFT;
} else if ((vma->vm_pgoff ==
(mxc_fbi->alpha_phy_addr0 >> PAGE_SHIFT)) ||
(vma->vm_pgoff ==
(mxc_fbi->alpha_phy_addr1 >> PAGE_SHIFT))) {
len = mxc_fbi->alpha_mem_len;
} else {
list_for_each_entry(mem, &fb_alloc_list, list) {
if (offset == mem->phy_addr) {
found = true;
len = mem->size;
break;
}
}
if (!found)
return -EINVAL;
}
/*进行映射*/
if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, vma->vm_end - vma->vm_start, vma->vm_page_prot)) {
dev_dbg(fbi->device, "mmap remap_pfn_range failed\n");
return -ENOBUFS;
}
return 0;
}
2.6 ioctl 操作
单纯地显示图片,只需要使用open和mmap/write接口就可以实现,但是如果应用程序想要修改驱动中的一些配置或者获取驱动中的配置信息时,需要通过ioctl 操作来实现。
static long fb_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct fb_info *info = file_fb_info(file);
if (!info)
return -ENODEV;
return do_fb_ioctl(info, cmd, arg);
}
static long do_fb_ioctl(struct fb_info *info, unsigned int cmd,
unsigned long arg)
{
void __user *argp = (void __user *)arg;
long ret = 0;
switch (cmd) {
case FBIOGET_VSCREENINFO: //获取与Framebuffer有关的固定信息,比如分辨率
var = info->var;
ret = copy_to_user(argp, &var, sizeof(var)) ? -EFAULT : 0;
break;
case FBIOPUT_VSCREENINFO:
······
case FBIOGET_FSCREENINFO: //获取与Framebuffer有关的可变信息,比如图形模式、颜色深度等等
······
case FBIOPUTCMAP:
······
case FBIOGETCMAP:
······
case FBIOPAN_DISPLAY:
if (copy_from_user(&var, argp, sizeof(var)))
return -EFAULT;
ret = fb_pan_display(info, &var);
if (ret == 0 && copy_to_user(argp, &var, sizeof(var)))
return -EFAULT;
break;
case FBIO_CURSOR:
······
case FBIOGET_CON2FBMAP:
······
case FBIOPUT_CON2FBMAP:
······
case FBIOBLANK:
······
default:
fb = info->fbops; //调用mxcfb_ioctl()
if (fb->fb_ioctl)
ret = fb->fb_ioctl(info, cmd, arg);
}
return ret;
}
static int mxcfb_ioctl(struct fb_info *fbi, unsigned int cmd, unsigned long arg)
{
switch (cmd) {
case MXCFB_SET_GBL_ALPHA:
······
case MXCFB_SET_LOC_ALPHA:
······
case MXCFB_SET_LOC_ALP_BUF:
······
case MXCFB_SET_CLR_KEY:
······
case MXCFB_SET_GAMMA:
······
case MXCFB_WAIT_FOR_VSYNC:
······
case FBIO_ALLOC:
······
case FBIO_FREE:
······
case MXCFB_SET_OVERLAY_POS:
······
case MXCFB_GET_FB_IPU_CHAN:
······
case MXCFB_GET_DIFMT:
······
case MXCFB_GET_FB_IPU_DI:
······
case MXCFB_GET_FB_BLANK:
······
case MXCFB_SET_DIFMT:
······
case MXCFB_CSC_UPDATE:
······
default:
retval = -EINVAL;
}
return retval;
}