跳到主要内容位置

Linux驱动-LCD驱动框架

从应用视角到内核,看LCD驱动程序#

整体流程如图:

查看qemu中百问网编写的fb_test程序。

fb-test.c

1.APP-先看main函数#

static struct fb_info fb_info;
int main(int argc, char **argv)
{
int opt;
int req_fb = 0;
int req_pattern = 0;
printf("fb-test %d.%d.%d (%s)\n", VERSION, PATCHLEVEL, SUBLEVEL,
VERSION_NAME);
...
fb_open(req_fb, &fb_info);
do_fill_screen(&fb_info, req_pattern);
return 0;
}

先调用fb_open打开frame buffer,传入默认0,表示打开fb0,返回一个fb_info结构体。获得LCD的信息后,就可以操作屏幕了

2.APP-fb_open,获取fb_info#

应用程序open、ioctl、mmap#

定义在common.c

void fb_open(int fb_num, struct fb_info *fb_info)
{
char str[64];
int fd,tty;
tty = open("/dev/tty1", O_RDWR);
if(ioctl(tty, KDSETMODE, KD_GRAPHICS) == -1)
printf("Failed to set graphics mode on tty1\n");
sprintf(str, "/dev/fb%d", fb_num);
fd = open(str, O_RDWR);
ASSERT(fd >= 0);
fb_info->fd = fd;
IOCTL1(fd, FBIOGET_VSCREENINFO, &fb_info->var);
IOCTL1(fd, FBIOGET_FSCREENINFO, &fb_info->fix);
printf("fb res %dx%d virtual %dx%d, line_len %d, bpp %d\n",
fb_info->var.xres, fb_info->var.yres,
fb_info->var.xres_virtual, fb_info->var.yres_virtual,
fb_info->fix.line_length, fb_info->var.bits_per_pixel);
void *ptr = mmap(0,
fb_info->var.yres_virtual * fb_info->fix.line_length,
PROT_WRITE | PROT_READ,
MAP_SHARED, fd, 0);
ASSERT(ptr != MAP_FAILED);
fb_info->ptr = ptr;
}

主要包括以下三个步骤:

  • fd = open(str, O_RDWR) -> open("/dev/fb0", O_RDWR)调用系统调用-open打开fb0

  • IOCTRL1 -> ioctl获取LCD信息

    #define IOCTL1(fd, ctl, arg1) if (ioctl(fd, ctl, arg1))\
    { perror("ioctl1(" __FILE__ ":" TOSTRING(__LINE__) "): "); exit(1); }
  • mmap虚拟内存映射: 应用程序中需要映射一块虚拟内存到显存的物理地址

    ```c

    void ptr = mmap(0, fb_info->var.yres_virtual fb_info->fix.line_length, PROT_WRITE | PROT_READ, MAP_SHARED, fd, 0);

    ```

    接下来,将详细解析以上系统调用的过程。

2.1 驱动框架中file_operationsfb_open#

APP:

fd = open(str, O_RDWR);
=> fd = open("/dev/fb0", O_RDWR);

fbmem.c,提供了frame buffer操作的框架,就是完成字符设备驱动程序的基本流程,然后再去调用下层的具体单板驱动程序。

static int
fb_open(struct inode *inode, struct file *file)
__acquires(&info->lock)
__releases(&info->lock)
{
int fbidx = iminor(inode);
struct fb_info *info;
int res = 0;
info = get_fb_info(fbidx);
if (!info) {
request_module("fb%d", fbidx);
info = get_fb_info(fbidx);
if (!info)
return -ENODEV;
}
...
file->private_data = info;
if (info->fbops->fb_open) {
res = info->fbops->fb_open(info,1);
if (res)
module_put(info->fbops->owner);
}
...
out:
mutex_unlock(&info->lock);
if (res)
put_fb_info(info);
return res;
}
  • info = get_fb_info(fbidx);通过传入的次设备号获取显存信息,实际是通过一个数组registered_fb获得,注册过程看下面第4小节。

    static struct fb_info *get_fb_info(unsigned int idx)
    {
    struct fb_info *fb_info;
    ...
    fb_info = registered_fb[idx];
    if (fb_info)
    atomic_inc(&fb_info->count);
    ...
    return fb_info;
    }
  • 在打开过程中,也会把info放入到file中,file->private_data = info;,后面会用到

2.2 驱动框架中file_operationsfb_ioctl#

IOCTL1(fd, FBIOGET_VSCREENINFO, &fb_info->var);

应用程序的ioctl会调用到驱动的fb_ioctl

fbmem.c

// 通过file获取fb_info
static struct fb_info *file_fb_info(struct file *file)
{
struct inode *inode = file_inode(file);
int fbidx = iminor(inode);
struct fb_info *info = registered_fb[fbidx];
if (info != file->private_data)
info = NULL;
return info;
}
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)
{
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 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;
...
return ret;
}

这里就是先从file中获得fb_info,然后传入do_fb_ioctl,根据不同的ioctrl命令cmd,获取信息。如FBIOGET_VSCREENINFO就是将LCD的可变参数信息var,拷贝到用户空间的arg.

2.3 驱动框架中file_operationsfb_mmap#

void *ptr = mmap(0,
fb_info->var.yres_virtual * fb_info->fix.line_length,
PROT_WRITE | PROT_READ,
MAP_SHARED, fd, 0);

同样的,应用程序的mmap也会调用到驱动程序中的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;
fb = info->fbops;
mutex_lock(&info->mm_lock);
... //这里会调用具体单板的fb_ops,我们没有提供
/*
* 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;
...
mutex_unlock(&info->mm_lock);
vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
fb_pgprotect(file, vma, start);
return vm_iomap_memory(vma, start, len);
}

可以看到最后就是调用内核函数vm_iomap_memory

vm_iomap_memory是Linux内核中用于映射设备内存到用户空间的函数。它允许用户空间程序直接访问设备内存,而不需要通过内核态的中间层。

3.具体LCD驱动中,注册fb_info过程#

既然上面fb_open是通过获取一个数组的某一项获得fb_info,那么一开始LCD初始化就会注册这个fb_info到数组中。 比如,我们编写的imx6ull的LCD驱动,在装载module的时候,就会分配/配置/注册fb_info

在下层实现的具体单板的LCD驱动中,会注册到上层框架fbmem.c

int __init (void)
{
dma_addr_t phy_addr;
/* 1.分配fb_info */
myfb_info = framebuffer_alloc(0, NULL);
/* 2.设置fb_info */
// a.var : LCD分辨率(虚拟分辨率、物理分辨率)、颜色格式
...
// b.fix
...
// c.fbops
...
/* 3.注册fb_info */
register_framebuffer(myfb_info);
/* 4.硬件操作 */
...
return 0;
}

此函数上一节编程已经看过了。这里主要看register_framebuffer

int
register_framebuffer(struct fb_info *fb_info)
{
int ret;
mutex_lock(&registration_lock);
ret = do_register_framebuffer(fb_info);
mutex_unlock(&registration_lock);
return ret;
}

调用do_register_framebuffer

static int do_register_framebuffer(struct fb_info *fb_info)
{
int i, ret;
struct fb_event event;
struct fb_videomode mode;
...
num_registered_fb++;
//找到数组的空位置
for (i = 0 ; i < FB_MAX; i++)
if (!registered_fb[i])
break;
fb_info->node = i;
atomic_set(&fb_info->count, 1);
mutex_init(&fb_info->lock);
mutex_init(&fb_info->mm_lock);
// 创建一个对应的具体设备节点,供应用程序访问
fb_info->dev = device_create(fb_class, fb_info->device,
MKDEV(FB_MAJOR, i), NULL, "fb%d", i);
...
if (fb_info->pixmap.addr == NULL) {
fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL);
if (fb_info->pixmap.addr) {
fb_info->pixmap.size = FBPIXMAPSIZE;
fb_info->pixmap.buf_align = 1;
fb_info->pixmap.scan_align = 1;
fb_info->pixmap.access_align = 32;
fb_info->pixmap.flags = FB_PIXMAP_DEFAULT;
}
}
fb_info->pixmap.offset = 0;
... // 添加各种信息
registered_fb[i] = fb_info; //把当前的fb_info放入数组第i项
...
return 0;
}