User:Easonxiang

出自Linux Wiki

在2011年10月18日 (二) 06:19由Alvin (讨论 | 贡献)所做的修订版本

目录

Video System

替换文字

Introduct

Frame Buffer

Introduce

Linux是工作在保护模式下,所以用户态进程是无法像DOS那样使用显卡BIOS里提供的中断调用来实现直接写屏。因此Linux抽象出了Framebuffer这个设备来供用户态进程实现直接写屏而不用去关心显卡和LCDC的具体工作。Framebuffer的主要工作是分配一段内存作为显存(在有显存的显卡直接从显存里分配),然后一方面把这段显存设置给LCDC,LCDC以固定的频率从这里面取数据去显示,另一方面把这段显存映射成到虚拟地址空间,让用户可以把数据写到这里面就立即显示出来。

Android由于硬件性能上的原因,要求Framebuffer提供两个屏幕大小的Buffer,一个用于绘制,一个用于显示,绘制好后调用Framebuffer提供的接口互换两个Buffer,LCDC会在下一个刷新周期把绘制好的图像显示出来,避免画面出现撕裂现象。

Two buffer.png

HAL

在Android中,定义HAL是为了给上层提供统一接口来访问低层硬件,HAL对上层提供一个接口来通过模块ID获取模块结构指针,接口如下:

int hw_get_module(const char *id, const struct hw_module_t **module)

这个接口在这里不详细分析,他的作用是根据模块ID动态打开模块编译生成的.so文件,从里面的符号表里面分析出模块的hw_module_t指针,返回给上层调用。

基本结构

/**
* Every hardware module must have a data structure named HAL_MODULE_INFO_SYM
* and the fields of this data structure must begin with hw_module_t
* followed by module specific information.
*/
typedef struct hw_module_t {
   uint32_t tag;   /** tag must be initialized to HARDWARE_MODULE_TAG */
   uint16_t version_major;    /** major version number for the module */
   uint16_t version_minor;    /** minor version number of the module */
   const char *id;    /** Identifier of module */
   const char *name;    /** Name of this module */
   const char *author;    /** Author/owner/implementor of the module */
   struct hw_module_methods_t* methods;    /** Modules methods */
   void* dso;    /** module's dso */
   uint32_t reserved[32-7];    /** padding to 128 bytes, reserved for future use */
} hw_module_t;

在这个结构里我们主要关心struct hw_module_methods_t结构,这个结构只提供一个open接口,如下:

typedef struct hw_module_methods_t {
    /** Open a specific device */
    int (*open)(const struct hw_module_t* module, const char* id,
            struct hw_device_t** device);
} hw_module_methods_t;

通过open接口可以获得hw_device_t结构体指针,这就对应具体的设备:

/**
* Every device data structure must begin with hw_device_t
* followed by module specific public methods and attributes.
*/
typedef struct hw_device_t {
   uint32_t tag;    /** tag must be initialized to HARDWARE_DEVICE_TAG */
   uint32_t version;    /** version number for hw_device_t */
   struct hw_module_t* module;    /** reference to the module this device belongs to */
   uint32_t reserved[12];    /** padding reserved for future use */
   int (*close)(struct hw_device_t* device);    /** Close this device */
} hw_device_t;

以上是HAL定义的标准结构,可以看到这些结构只提供了一个打开关闭设备的途径,而并没有提供实际操作设备的接口, 因此,每个模块或者设备需要定义自己的结构,根据hw_module_t和hw_device_t的注释,定义自己的结构必须分别 包含这两个结构,并且要把放到每个结构的开始处。

所以Framebuffer的HAL模块GRALLOC定义私有结构如下:

typedef struct gralloc_module_t {
   struct hw_module_t common;
   int (*registerBuffer)(struct gralloc_module_t const* module,
           buffer_handle_t handle);
   int (*unregisterBuffer)(struct gralloc_module_t const* module,
           buffer_handle_t handle);
   int (*lock)(struct gralloc_module_t const* module,
           buffer_handle_t handle, int usage,
           int l, int t, int w, int h,
           void** vaddr);
   int (*unlock)(struct gralloc_module_t const* module,
           buffer_handle_t handle);
   ....   /** Not important interface**/
} gralloc_module_t;

可以看到gralloc_module_t的第一个成员是hardware定义struct hw_module_t结构,而后面的成员是自己模块的私有变量。

在GRALLOC模块内包含两个device,分别是alloc_device_t和framebuffer_device_t,

typedef struct alloc_device_t {
   struct hw_device_t common;
   int (*alloc)(struct alloc_device_t* dev,
           int w, int h, int format, int usage,
           buffer_handle_t* handle, int* stride);
   int (*free)(struct alloc_device_t* dev,
           buffer_handle_t handle);
} alloc_device_t;

alloc_device_t包含两个接口,分别用于从显存里面分配和释放内存。

typedef struct framebuffer_device_t {
   struct hw_device_t common;
   const uint32_t  flags;    /* flags describing some attributes of the framebuffer */
   const uint32_t  width;    /* dimensions of the framebuffer in pixels */
   const uint32_t  height;
   const int       stride;    /* frambuffer stride in pixels */
   const int       format;    /* framebuffer pixel format */
   const float     xdpi;    /* resolution of the framebuffer's display panel in pixel per inch*/
   const float     ydpi;
   const float     fps;    /* framebuffer's display panel refresh rate in frames per second */
   const int       minSwapInterval;    /* min swap interval supported by this framebuffer */
   const int       maxSwapInterval;    /* max swap interval supported by this framebuffer */
   int (*setSwapInterval)(struct framebuffer_device_t* window,
           int interval);
   int (*post)(struct framebuffer_device_t* dev, buffer_handle_t buffer);
   int (*compositionComplete)(struct framebuffer_device_t* dev);
   void* reserved_proc[8];
} framebuffer_device_t;

和gralloc_module_t结构一样,framebuffer_device_t结构的第一个成员也是hardware定义的struct hw_device_t,后面是私有变量。 包含了Framebuffer的属性和操作,比较常用的是post接口,用于把一幅图像显示出来。

初始化流程

从下图可以看到,Frame buffer在初始化过程中就已经打开了/dev/graphics/fb0设备,并做了内存映射,方便在后续的使用。

HAL.png

Driver

驱动层包括FB的驱动,为HAL提供接口。

Platform device driver

FB驱动是基于Platform驱动架构写的,Platform驱动机制适用于比较简单、独立的设备,如LCD,UART等, 和PCI驱动类似,Platform驱动也提供Platform device和Platform driver两个结构供用户使用, 在定义这两个结构体时都会指定name成员,内核是通过这个变量来匹配设备和驱动的。

以下是分析的MSM8x60中的代码,FB定义的Platform device为msm_fb_device, 路径在kernel/arch/arm/marc-msm/board-msm8x60.c,从代码路径可以看出, 这在芯片初始化的过程中就定义好了。

static struct platform_device msm_fb_device = {
        .name   = "msm_fb",
        .id     = 0,
        .num_resources     = ARRAY_SIZE(msm_fb_resources),
        .resource          = msm_fb_resources,
#ifdef CONFIG_FB_MSM_LCDC_AUTO_DETECT
        .dev.platform_data = &msm_fb_pdata,
#endif /* CONFIG_FB_MSM_LCDC_AUTO_DETECT */
};
  • name:设备名,用于和驱动匹配
  • resource:定义设备的资源,在这里用于保存FB的物理地址和长度

对于FB设备来说,它只对外提供了一个用于显示的空间,这个空间就是这里的resource。

static struct platform_driver msm_fb_driver = {
        .probe = msm_fb_probe,
        .remove = msm_fb_remove,
#ifndef CONFIG_HAS_EARLYSUSPEND
        .suspend = msm_fb_suspend,
        .resume = msm_fb_resume,
#endif
        .shutdown = NULL,
        .driver = {
              /* Driver name must match the device name added in platform.c. */
              .name = "msm_fb",
              .pm = &msm_fb_dev_pm_ops,
         },
};

前面几个接口是Linux驱动通用的接口,这里我们可以看到它的name和FB设备定义的相同, 加载FB驱动时,内核从Platform设备里寻找和FB驱动name相同的设备,如果找到了就进行FB驱动的probe函数。

static int msm_fb_probe(struct platform_device *pdev)
{
     /**...**/
     fbram_size = pdev->resource[0].end - pdev->resource[0].start + 1;
     fbram_phys = (char *)pdev->resource[0].start;
     fbram = ioremap((unsigned long)fbram_phys, fbram_size);
     /**...**/
}

probe函数主要从device指针里获取FB设备的物理地址,大小以及经过ioremap之后的虚拟地址,

fbmem

Frame Buffer驱动和其它驱动不同,它由一个叫fbmem模块的驱动来统一管理,区别在于FB驱动在创建虚拟设备文件节点的时候不需要提供文件操作函数,而是有一个专门fb_ops结构提供给fbmem,由fbmem根据FB设备的次设备号来调用相应的文件操作。

fbmem驱动在初始化时使用register_chrdev注册了一个主设备号为29的字符设备,并且提供了file_operations结构,FB设备在注册时使用device_create函数创建设备结点,主设备号也为29,子设备号从0开始递增,因此操作FB设备结点的时候实际上是调用了fbmem的操作接口,在fbmem的操作接口里再根据子设备号的不同调用相应子设备号的文件操作接口。

Linux使用这种方法为相同类型的设备提供了一种简便的操作方法,即有多个相同类型的不同设备,如果功能都相同,只需要为每个设备创建一个结点,但是可以只用一个驱动根据子设备号不同来统一操作,功能不同的话只针对不同的功能编写驱动,可以有效减少重复工作量。

Fbmem.png

如上图所示,是对fb设备进行IOCTL的简单流程,fbmem实际上是一个中间层,对FB设备的所有操作都要经过它,它对所有FB设备都支持的功能作统一处理,否则把这个IOCTL传送给相应fb驱动进行处理。

fbmem主要维护着fb_info这个结构,存放在registered_fb[FB_MAX]数组中。

struct fb_info

定义FB的所有信息,这个结构代表一个FB设备。 通过register_framebuffer(struct fb_info *fb_info)来注册FB设备到registered_fb[FB_MAX]数组中。

主要成员如下:

struct fb_info {
int node;
struct fb_var_screeninfo var; /* Current var */
struct fb_fix_screeninfo fix;  /* Current fix */
struct fb_videomode *mode; /* current mode */
struct fb_ops *fbops;
struct device *device;   /* This is the parent */
struct device *dev;   /* This is this fb device */
char __iomem *screen_base; /* Virtual address */
unsigned long screen_size; /* Amount of ioremapped VRAM or 0 */ 
…………
};
  • node: 标记FB设备的次设备号
  • fb_var_screeninfo: 显示设备的可变参数,对用户来说可以通过IOCTL更改
  • fb_fix_screeninfo: 显示设备的固定参数,对用户来说不可更改
  • fbops: FB设备的文件操作符,fbmem通过次设备号来调用fb_info的fbops来操作FB设备
struct fb_var_screeninfo

显示设备的可变参数,包括屏幕分辨率和每个像素点的比特数。

struct fb_var_screeninfo {
__u32 xres;   /* visible resolution */
__u32 yres;
__u32 xoffset;  /* offset from virtual to visible */
__u32 yoffset;  /* resolution */
__u32 bits_per_pixel; /* bits/pixel */
__u32 pixclock;  /* pixel clock in ps (pico seconds) */
__u32 left_margin; /* time from sync to picture */
__u32 right_margin; /* time from picture to sync */
__u32 hsync_len;  /* length of horizontal sync */
__u32 vsync_len;  /* length of vertical sync */
…………
};
  • xres: 定义一行有多少个点
  • yres: 定义一列有多少个点
  • bits_per_pixel: 定义每个点用多少个bits表示
struct fb_fix_screeninfo

显示设备的固定参数,如屏幕缓冲区的物理地址,长度。当对帧缓冲设备进行映射操作的时候,就是从fb_fix_screeninfo中取得缓冲区物理地址的。

struct fb_fix_screeninfo {
char id[16];        /* identification string eg "TT Builtin" */
unsigned long smem_start;    /* Start of frame buffer mem (physical address) */
__u32 smem_len;        /* Length of frame buffer mem */
unsigned long mmio_start;    /* Start of Mem Mapped I/O(physical address) */
__u32 mmio_len;      /* Length of Memory Mapped I/O  */
…………
};

固定参数在FB初始化时就定义好,在后面的使用中不可更改。

struct fb_ops

fb_info的成员变量fbops为指向底层操作的函数指针集,这些函数由底层具体的FB设备驱动填充。

struct fb_ops {
int (*fb_open)(struct fb_info *info, int user);
int (*fb_release)(struct fb_info *info, int user);
ssize_t (*fb_read)(struct file *file, char __user *buf, size_t count, loff_t *ppos);
ssize_t (*fb_write)(struct file *file, const char __user *buf, size_t count,
loff_t *ppos);
int (*fb_set_par)(struct fb_info *info);
int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
unsigned blue, unsigned transp, struct fb_info *info);
int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info)
int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);
……………
}

这个结构和struct file_operations是有区别的,举例说明, 代码路径:/msm/drivers/video/fbmem.c

static ssize_t
fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
 ...
 if (info->fbops->fb_read)
   return info->fbops->fb_read(info, buf, count, ppos);
 ...
}

在以上代码中,在调用fb_read()时,会首先判断fb_info->fbops->fb_read()是否存在,即具体的底层FB设备是否实现了自己的read操作(一般对于非线性布局的/常规内存映射无法工作的帧缓冲设备需要),如果底层设备驱动实现了自己的read操作,就调用底层的fb_read(),绕过fbmem.c提供的fb_read()函数。

其他功能函数的实现,同理。

Framebuffer的统一管理
个人工具
简体繁体转换