一. 关于mmap
1. linux I/O端口与I/O内存
IO端口:当一个寄存器或者内存位于IO空间时;/*x86体系结构处理器*/
IO内存:当一个内存或者寄存器位于内存空间时;/*ARM体系结构处理器*/
在访问IO内存之前需要首先使用ioremap函数将设备所处的物理地址映射到虚拟地址上。
----why?
在系统运行时,外设的I/O内存资源的物理地址是已知的,由硬件的设计决定。
但是CPU通常并没有为这些已知的外设I/O内存资源的物理地址预定义虚拟地址范围,
驱动程序并不能直接通过物理地址访问I/O内存资源(因为要考虑考Linux系统设计分层的理念--yang提示),而必须将它们映射到核心虚地址空间内(通过页表),
然后才能根据映射所得到的核心虚地址范围,通过访内指令访问这些I/O内存资源。
2. 关于ioremap和mmap函数
ioremap主要是用来将内核空间的一段虚拟空间映射到外部设备的存取区(设备I/O地址空间).
mmap主要是将用户空间的一段虚拟地址映射到设备的I/O空间中.用户空间进程就可以直接访问设备内存。
驱动程序中完成的功能是在file_operations中的mmap方法.
3. mmap系统调用过程(应用层-->驱动中mmap实现方法)
mmap->
-->
SYSCALL_DEFINE6(mmap....
{
}
--->SYSCALL_DEFINE6(mmap_pgoff...
sys_mmap_pgoff
{
file = fget(fd); /*通过fd获得相应的struct file文件对象指针*/
do_mmap_pgoff(); /*用来完成后续的内存映射工作*/
}
--->do_mmap_pgoff
{
addr = get_unmapped_area(file, addr, len, pgoff, flags);
mmap_region(file, addr, len, flags, vm_flags, pgoff);
}
/*do_mmap_pgoff 根据用户进程调用mmap时,传入的参数,
构造一个struct vm_area_struct对象示例,然后再调用
file->f_op->mmap(),对应着驱动程序中的mmap的方法。
*/
---->
mmap_region()
{
if (file) {
vma->vm_file = file;
get_file(file);
error = file->f_op->mmap(file, vma); /*设备驱动中mmap方法*/
}
}
4. 驱动中mmap实现
设备驱动中的mmap方法主要功能是将内核提供的用户进程空间来自MMAP区域的一段内存
(内核将这段区域以struct vm_area_struct对象作为参数的方式告诉设备驱动程序)
映射到设备内存上。
--->驱动程序需要配置相应的页目录表项的方式来完成(linux内核提供了一些接口函数来提供映射)
--->操作页目录表项建立页面映射的一些接口函数
int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,
unsigned long pfn, unsigned long size, pgprot_t prot)
/**
* remap_pfn_range - remap kernel memory to userspace
* @vma: user vma to map to
* @addr: target user address to start at
* @pfn: physical address of kernel memory -->页框号(page frame number)
在页面大小为4KB的系统中,一个物理地址右移12位即可得到该物理地址对应的页框号
* @size: size of map area
* @prot: page protection flags for this mapping
*
* Note: this is only safe if the mm semaphore is held when called.
*/
二. LCD架构分析
/*******************************LCD架构分析****************************************/
fb.h中定义了一些主要的数据结构,Frameuffer设备在很大程度上依靠了下面的3个数据结构。
struct fb_var_screeninfo、struct fb_fix_screeninfo 和 struct fb_info
1. 第一个结构体用来描述图形卡的特性,通常是被用户设置的
2. 第二个结构体定义了图形卡的硬件特性,是不能改变的,用户选定了LCD控制器和显示器后,那么硬件特性也就定下来了
3. 第三个结构体定义了当前图形卡Framebuffer设备的独立状态,一个图形卡可能有两个Framebuffer,
在这种情况下,就需要两个fb_info结构。这个结构是唯一内核空间可见的
数据结构:
1 struct fb_cmap结构体:用来定义帧缓存区设备的颜色表(colormap)信息,
可以通过ioctl()函数的FBIOGETCMAP和FBIOPUTCMAP命令来设置colormap
2 struct fb_info 结构体:包含当前显示卡的状态信息,struct fb_info结构体只对内核可见
3 struct fb_ops结构体:应用程序使用这些函数操作底层的LCD硬件,fb_ops结构中定义的方法用于支持这些操作。
这些操作需要驱动开发人员来实现
4 struct fb_fix_screeninfo结构体:定义了显卡信息,如framebuffer内存的起始地址,地址的长度
5 struct fb_var_screeninfo结构体:描述了一种显示模式的所有信息,如宽、高、颜色深度等,
不同显示模式对应不同的信息
在Framebuffer设备驱动程序中,这些结构是互相关联,互相配合使用的。
只有每一个结构体起到自己的作用,才能使整个Framebuffer设备驱动程序正常工作。
/************************LCD接口层(fbmem.c)*************************************/
在LCD注册的时候用的是字符设备
register_chrdev(FB_MAJOR,"fb",&fb_fops)
为控制器驱动提供统一的调用接口。
/*在设备控制器层调用register_framebuffer注册了字函数符设备,设备号是29
//fbmem.c
int register_framebuffer(struct fb_info *fb_info)
{
for (i = 0 ; i < FB_MAX; i++)
if (!registered_fb[i])
break;
fb_info->dev = device_create(fb_class, fb_info->device,
MKDEV(FB_MAJOR, i), NULL, "fb%d", i);
}
*/
1.当应用层调用open函数后
-->open( )
->fb_open()
{
int fbidx = iminor(inode);
struct fb_info *info;
info = registered_fb[fbidx];
if (info->fbops->fb_open) {
res = info->fbops->fb_open(info,1); /*底层函数基本没有实现*/
}
}
fb_open接着调用--->s3c_fb_open()//在底层控制驱动中实现
2.当应用层调用write函数后
-->wirte()
-->fb_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
if (info->fbops->fb_write) /*s3c6410底层没有实现*/
return info->fbops->fb_write(info, buf, count, ppos);
buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
GFP_KERNEL);
dst = (u8 __iomem *) (info->screen_base + p); /*获取lcd缓冲区地址*/
while (count) {
c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
src = buffer;
if (copy_from_user(src, buf, c)) {
err = -EFAULT;
break;
}
memcpy(dst, src, c); /*往缓冲区写数据*/
dst += c;
src += c;
*ppos += c;
buf += c;
cnt += c;
count -= c;
}
kfree(buffer);
}
3. 当调用read函数后--->过程和write相似,不分析了
/*************************LCD控制器驱动(s3c-fb.c)***********************/
lcd控制器驱动--采用平台总线形式,直接分析 .probe = s3c_fb_probe函数。
1.
static int s3c_fb_probe(struct platform_device *pdev)
{
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
sfb->irq_no = res->start;
ret = request_irq(sfb->irq_no, s3c_fb_irq,
0, "s3c_fb", sfb);
s3c_fb_probe_win(sfb, win, fbdrv->win[win], &sfb->windows[win]);
}
2.
static int s3c_fb_probe_win(struct s3c_fb *sfb, unsigned int win_no,
struct s3c_fb_win_variant *variant,
struct s3c_fb_win **res)
{
fbinfo = framebuffer_alloc(sizeof(struct s3c_fb_win) +
palette_size * sizeof(u32), sfb->dev);
s3c_fb_alloc_memory(sfb, win); /********fbi->screen_base初始化********/
fbinfo->fbops = &s3c_fb_ops; /*为fbinfo->fbops 赋值, s3c_fb_ops属于控制器操作方法*/
/* create initial colour map */
ret = fb_alloc_cmap(&fbinfo->cmap, win->variant.palette_sz, 1);
if (ret == 0)
fb_set_cmap(&fbinfo->cmap, fbinfo);
s3c_fb_set_par(fbinfo); /*初始化fb_fix_screeninfo*/
ret = register_framebuffer(fbinfo);
}
3. static int s3c_fb_alloc_memory(struct s3c_fb *sfb, struct s3c_fb_win *win)
{
/*Virtual address 帧缓存虚拟地址*/
fbi->screen_base = dma_alloc_writecombine(sfb->dev, size,
&map_dma, GFP_KERNEL); /******关于dma?******/
memset(fbi->screen_base, 0x0, size);
fbi->fix.smem_start = map_dma; /* Start of frame buffer mem (physical address)*/
}