版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_16777851/article/details/83716640
前面framebuffer章节我们了解了通过write函数来对fremebbuffer中的显存写数据的方式。
在开始分析mmap之前我们再次回顾一下fb_write函数
static ssize_t
fb_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
unsigned long p = *ppos; //显存起始地址的偏移
struct inode *inode = file->f_path.dentry->d_inode;
int fbidx = iminor(inode); //得到次设备号
struct fb_info *info = registered_fb[fbidx]; //得到设备信息
u32 *buffer, *src;
u32 __iomem *dst;
int c, i, cnt = 0, err = 0;
unsigned long total_size;
if (!info || !info->screen_base) /* 参数有效性判断 */
return -ENODEV;
if (info->state != FBINFO_STATE_RUNNING)
return -EPERM;
/* 设备信息中的fb_ops中如果有定义fb_wrute函数,则调用具体驱动中的,不使用通用的(三星的都输通用的,所以这里是没定义的) */
if (info->fbops->fb_write)
return info->fbops->fb_write(info, buf, count, ppos);
total_size = info->screen_size; /* 显存大小 */
if (total_size == 0)
total_size = info->fix.smem_len; /* 显存为0,则使用fb帧长度作为显存大小 */
if (p > total_size) /* 偏移不能超过显存大小 */
return -EFBIG;
if (count > total_size) { /* 要写的长度大于显存显存长度,则只写显存大小内容 */
err = -EFBIG;
count = total_size;
}
if (count + p > total_size) { /* 偏移+要写的字节,不能超过显存大小 */
if (!err)
err = -ENOSPC;
count = total_size - p; /* 超出显存大小,则把超出的丢弃 */
}
/* 要写的内容大于1页(0x1000),则申请一页空间,否则申请需要的字节数
(这里主要作为中间,用来把用户空间拷贝过来的数据做个暂存) */
buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
GFP_KERNEL);
if (!buffer)
return -ENOMEM;
/* 显存基址+偏移 ---> 真正数据要写的显存开始地址 */
dst = (u32 __iomem *) (info->screen_base + p);
if (info->fbops->fb_sync) /* 是都需要同步,三星没这个函数 */
info->fbops->fb_sync(info);
/* 要写的大小大于一页,则通过buffer 多次把用户空间的数据拷贝到内核空间,之后再通过
fb_writel和fb_writeb函数写入显存(要写的内容小于1页,那一次就可以写完了)
*/
while (count) {
c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
src = buffer;
if (copy_from_user(src, buf, c)) {
err = -EFAULT;
break;
}
for (i = c >> 2; i--; )
fb_writel(*src++, dst++);
if (c & 3) {
u8 *src8 = (u8 *) src;
u8 __iomem *dst8 = (u8 __iomem *) dst;
for (i = c & 3; i--; )
fb_writeb(*src8++, dst8++);
dst = (u32 __iomem *) dst8;
}
*ppos += c;
buf += c;
cnt += c;
count -= c;
}
/* 释放掉暂存数据的空间 */
kfree(buffer);
/* 返回成功写入的数据字节数 */
return (cnt) ? cnt : err;
}
这里我们看到了,数据的传输需要两个过程
1.从用户空间拷贝数据到内核空间copy_from_user
2.把从用户空间拷贝的数据写入显存中fb_writel
这对fb设备特别是分辨率很高,数据量非常大的情况,效率是非常低的。
而前两节我们学过的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;
if (!info)
return -ENODEV;
fb = info->fbops;
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;
}
/*
* Ugh. This can be either the frame buffer mapping, or
* if pgoff points past it, the mmio mapping.
*/
start = info->fix.smem_start; /* 显存起始地址(这个是物理地址) */
len = info->fix.smem_len; /* 显存大小 */
/* 计算显存起始地址对应的那个物理页 */
mmio_pgoff = PAGE_ALIGN((start & ~PAGE_MASK) + len) >> PAGE_SHIFT;
/* 用户传过来的比实际物理页小,则有问题(因为实际的已经是从显存起始地址开始了) */
if (vma->vm_pgoff >= mmio_pgoff) {
if (info->var.accel_flags) {
mutex_unlock(&info->mm_lock);
return -EINVAL;
}
vma->vm_pgoff -= mmio_pgoff;
start = info->fix.mmio_start;
len = info->fix.mmio_len;
}
mutex_unlock(&info->mm_lock);
/* 得到页的访问模式权限(读/写/可执行/私有) */
vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
fb_pgprotect(file, vma, start); /* 页额访问权限增加一种,不使用cache使用write buffer*/
/* vma时用户空间的一块分配的空间,statr是要映射的物理地址,len是要映射的长度 */
/* 即把下面物理地址起始的一块空间映射带用户空间 */
return vm_iomap_memory(vma, start, len); /* 映射页io */
}
增加访问权限
static inline void fb_pgprotect(struct file *file, struct vm_area_struct *vma,
unsigned long off)
{
vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); /* 在原来的权限基础上增加write buffer功能 */
}
#define pgprot_writecombine(prot) \
__pgprot_modify(prot, L_PTE_MT_MASK, L_PTE_MT_BUFFERABLE)
#define __pgprot_modify(prot,mask,bits) \
__pgprot((pgprot_val(prot) & ~(mask)) | (bits))
/* 强制类型转换 */
#define pte_val(x) ((x).pte)
#define pmd_val(x) ((x).pmd)
#define pgd_val(x) ((x).pgd[0])
#define pgprot_val(x) ((x).pgprot)
#define __pte(x) ((pte_t) { (x) } )
#define __pmd(x) ((pmd_t) { (x) } )
#define __pgprot(x) ((pgprot_t) { (x) } )
/**
* vm_iomap_memory - remap memory to userspace
* @vma: user vma to map to
* @start: start of area
* @len: size of area
*
* This is a simplified io_remap_pfn_range() for common driver use. The
* driver just needs to give us the physical memory range to be mapped,
* we'll figure out the rest from the vma information.
*
* NOTE! Some drivers might want to tweak vma->vm_page_prot first to get
* whatever write-combining details or similar.
*/
int vm_iomap_memory(struct vm_area_struct *vma, phys_addr_t start, unsigned long len)
{
unsigned long vm_len, pfn, pages;
/* Check that the physical memory area passed in looks valid 检查不要越界 */
if (start + len < start)
return -EINVAL;
/*
* You *really* shouldn't map things that aren't page-aligned,
* but we've historically allowed it because IO memory might
* just have smaller alignment.
*/
len += start & ~PAGE_MASK; /* 得到结束地址 */
pfn = start >> PAGE_SHIFT; /* 得到显存物理页号 */
pages = (len + ~PAGE_MASK) >> PAGE_SHIFT; /* 得到显存页数 */
if (pfn + pages < pfn)
return -EINVAL;
/* We start the mapping 'vm_pgoff' pages into the area */
if (vma->vm_pgoff > pages) /* 用户空间传过来的起始页的偏移必须在范围内 */
return -EINVAL;
pfn += vma->vm_pgoff; /* 原始基物理页号+用户空间传过来偏移页数 = 显存起始物理页号 */
pages -= vma->vm_pgoff; /* 显存页数 - 用户空传来的偏移页数 = 剩下最多可以映射的页数 */
/* Can we fit all of the mapping? */
vm_len = vma->vm_end - vma->vm_start; /* 用户空间映射大小 */
if (vm_len >> PAGE_SHIFT > pages) /* 用户空间要求映射的页不能大于实际显存可映射的页 */
return -EINVAL;
/* Ok, let it rip */
/* 把用户空间虚拟地址vma->vm_start开始的vm_len长度映射到物理内存pfn页开始的内存 */
return io_remap_pfn_range(vma, vma->vm_start, pfn, vm_len, vma->vm_page_prot);
}
#define io_remap_pfn_range remap_pfn_range
可见使用了mmap对lcd的显存进行了映射以后,除了这里增加了对页表的一次操作以外。对显存写数据可以直接使用写地址方式操作。
和write相比没有数据的拷贝拷贝,这样可以大大的提高运行效率。
下面简单举例使用mmap来映射,并把lcd显示器的背景刷成红色。
#include <stdio.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int fd = -1;
int i;
unsigned int *mmaped = NULL;
fd = open("/dev/fb0", O_RDWR);
if (fd < 0) {
fprintf(stderr, "open fb0 fail\n");
exit(-1);
}
/* 将文件映射至进程的地址空间 */
mmaped = (unsigned int *)mmap(NULL, 1024*600*4, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (mmaped == (unsigned int*)-1) {
fprintf(stderr, "mmap fail\n");
close(fd);
return -1;
}
/* 映射完后, 关闭文件也可以操纵内存 */
close(fd);
/* 刷全屏红色背景 */
for(i = 0; i < (1024*600); i++)
mmaped[i] = 0x00ff0000;
/* 同步mmap映射的文件从内存写到硬盘文件中 */
msync(mmaped, 1024*600*4, MS_SYNC);
return 0;
}