User:Easonxiang
出自Linux Wiki
目录 |
Video System
Introduce
本文介绍Android系统中有关显示部分的内容,主要关注HAL和Driver两层的源代码分析,包括通用驱动和高通芯片特有的驱动。希望通过阅读本文能够了解Android系统中显示系统的架构和图像显示的基本流程,并为今后驱动移植提供有效的参考。
下图是是显示系统的分层情况,包括各层涉及的主要源文件。
- 蓝色部分——应用程序层,位于用户空间,包含图形应用程序、Android 框架和系统运行库等,主要由 Android 的 Surface Manager 负责显示系统的管理。
- 绿色部分——HAL 层,是用户空间和 kernel 空间交互的部分,它的存在主要有两点原因:kernel driver 涉及 GPL 协议,有些设备供应商不愿公开硬件驱动采用 HAL方式绕过 GPL 协议;Android 对底层设备有一些特殊需求。
- 橙色部分,它是标准 Linux 系统显示部分的驱动程序接口,并且统一管理系统注册的 framebuffer 设备。FrameBuffer 机制就是将显卡硬件结构抽象出来,可以通过对FrameBuffer 的读写直接对显存进行操作。具体涉及的代码路径:/kernel/drivers/video/fbmem.c
- 黄色部分——高通驱动层,是 Android 在 Linux 基础上新增的内容,包括高通framebuffer 驱动、MDP 驱动与 Mddi 驱动、外围 LCD 驱动等,是 framebuffer的具体实现,涉及的代码为 msm_fb.c、mdp.c、mddi_ta8851.c、 mddihost.c等,详细解析将在后续章节展开。
HAL
在Android系统中,定义HAL是为了给上层提供统一接口来访问底层硬件,HAL对上层提供一个接口来通过模块ID获取模块结构指针,接口如下:
int hw_get_module(const char *id, const struct hw_module_t **module)
这个接口的作用是根据模块ID动态打开模块编译生成的.so文件,从so文件的符号表分析出模块的hw_module_t指针,这个指针的名字是必须为HAL_MODULE_INFO_SYM,所以每个模块都需要定义这个结构,这是HAL模块的入口。
Structure
/** * 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;
这个结构的id就是生成so文件的前缀,也是打开这个so所使用的id。
此外hw_module_t结构就提供一个函数接口methods,通过函数hw_get_module获取到hw_module_t结构指针后首先就调用这个接口了。
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;
hw_device_t结构也只需要提供一个接口close,用于把hw_device_t的资源释放掉,可以看到以上这些结构只提供了一个打开关闭设备的途径,而并没有提供实际操作设备的接口,这对于具体的设备来说肯定是不够的,Android设计者在这里使用了面向对象的设计方法,把hw_module_t和hw_device_t两个结构作为基类,用户使用这两个基类来开发自己有HAL模块,方法就是在模块里定义自己的module和device,定义时必须分别包含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); .... /** Not important interface**/ } gralloc_module_t;
可以看到gralloc_module_t的第一个成员是hardware定义struct hw_module_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 fps; /* framebuffer's display panel refresh rate in frames per second */ 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_device_t包含了Framebuffer的属性和操作,比较常用的是post接口,用于把一幅图像显示出来。
Call trace
下图是Framebuffer的HAL层初始化过程中主要调用的函数接口,从此图可以看出,Frame buffer在初始化过程中就已经打开了/dev/graphics/fb0设备,并做了内存映射,方便在后续的使用。
Frame Buffer Driver
Introduce
Linux是工作在保护模式下,所以用户态进程是无法像DOS那样使用显卡BIOS里提供的中断调用来实现直接写屏。因此Linux抽象出了Framebuffer这个设备来供用户态进程实现直接写屏而不用去关心显卡和LCDC的具体工作。Framebuffer的主要工作是分配一段内存作为显存(在有显存的显卡直接从显存里分配),然后一方面把这段显存设置给LCDC,LCDC以固定的频率从这里面取数据去显示,另一方面把这段显存映射成到虚拟地址空间,让用户可以把数据写到这里面就立即显示出来。
Android由于硬件性能上的原因,要求Framebuffer提供两个屏幕大小的Buffer,一个用于绘制,一个用于显示,绘制好后调用Framebuffer提供的接口互换两个Buffer,LCDC会在下一个刷新周期把绘制好的图像显示出来,避免画面出现撕裂现象。
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使用这种方法为相同类型的设备提供了一种简便的操作方法,即有多个相同类型的不同设备,如果功能都相同,只需要为每个设备创建一个结点,但是可以只用一个驱动根据子设备号不同来统一操作,功能不同的话只针对不同的功能编写驱动,可以有效减少重复工作量。
如上图所示,是对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的统一管理
fbmem.c 实现了 Linux FrameBuffer 的中间层,任何一个 FrameBuffer 驱 动,在系统初始化时,必须向 fbmem.c 注册,即需要调用 register_framebuffer() 函数,在这个过程中,设备驱动的信息被存放入名为 registered_fb 数组中,这个数组定义为:
struct fb_info *registered_fb[FB_MAX] __read_mostly; int num_registered_fb __read_mostly;
它是类型为 fb_info 的数组,另外 num_registered_fb 存放了注册过的 fb 设备数量。 当有底层设备调用 register_framebuffer()注册时,会首先查看 registered_fb 这个数组,在当前的 code 中 FB_MAX 为 32,即最多可以注册 32 个 framebuffer 设备,当 num_registered_fb < 32 即数组未被填满时,将底层设备自己的 fb_info 顺序填入 registered_fb 数组,数组下标与 framebuffer 设备的次设备号相同。 下图简单地介绍 fbmem.c 如何实现 Framebuffer 驱动的中间层:
在具体的底层设备驱动中,会调用register_framebuffer(),将设备自身的fb_info注册到fbmem.c,其具体流程为:
- 查看num_registered_fb,即已注册的framebuffer数量是否超过最大给定值;
- 读取从底层设备传递来的fb_info,查看framebuffer映射地址是有重叠;
- 查找registered_fb[]数组中的空闲空间;
- 获取空闲的数组空间,将数组下标i作为待注册设备的次设备号,生成设备节点;并将传递来的fb_info填入registered_fb[i]中,num_registered_fd加1;
- 广播FB_EVENT_FB_REGISTERED事件,当前代码中,会被/msm/drivers/video/console/Fbcon.c中查看,涉及的接受代码为:
static int fbcon_event_notify(struct notifier_block *self, unsigned long action, void *data) { .... switch (action){ case FB_EVENT_FB_REGISTERED: ret = fbcon_fb_registered(info); break; .... }
MSM LCD Driver
Introduce
当用户空间有操作framebuffer设备请求时,fbmem.c接受这一请求,并具体的调用msm_fb.c文件的文件操作符。由于msm_fb.c是Android在标准Linux内核上增加的基于高通平台的”fbmem.c”,为了和Linux内核的fbmem.c相区分,把它归于高通驱动层。
该部分主要是高通显卡的驱动,具体负责framebuffer的底层驱动,自定义的I/O控制命令也在此层实现,在具体移植时,修改多为这层的文件,例如,要加入一个新的MDDI接口的LCD,就需要提供相应的mddi_xxxx.c(此次解析的文件为mddi_ta8851.c),并且完成和这个LCD相关的初始化工作,添加自定义的I/O控制功能就需要修改msm_fb.c文件等。
Device&Driver数据流
Linux设备模型关心总线、设备、驱动,LCD作为SOC的独立外设单元,通常被挂载到platform总线,采用platform_device和platform_driver机制管理。下面以mddi_ta8851涉及的初始化过程为例,简单介绍lcd在开机后device数据流程: 代码路径:
/msm/drivers/video/msm/mddi_hitachi_wvga_pt.c /msm/drivers/video/msm/mddi_ta8851.c /msm/drivers/video/msm/msm_fb.c /msm/drivers/video/fb_mem.c
上图中,mddi_hitachi_wvga_pt.c实现了panel的注册(通过调用mddi_ta8851.c中的mddi_ta8851_device_register());在mddi_ta8851.c被初始化后,利用platform device.name与platform driver.name匹配,将设备与驱动绑定,接着调用mddi_ta8851_probe()函数,其中会调用msm_fb.c中提供的msm_fb_add_device()接口函数,将具体设备mddi_ta8851的fb_info传入msm_fb.c中,由msm_fb.c的msm_fb_probe()探测,并注册fb_info到registered_fb[]数组,实现framebuffer的注册。
具体文件与功能接口解析
mddi_hitachi_wvga_pt.c
此文件代码较少,主要是一些panel参数的设置,对一些重要数据列表:
pinfo.xres = 480; /** 屏幕分辨率信息 */ pinfo.yres = 800; pinfo.type = MDDI_PANEL; /** panel采用MDDI接口 */ pinfo.pdest = DISPLAY_1; /** panel默认接在fb0上 */ pinfo.mddi.vdopkt = MDDI_DEFAULT_PRIM_PIX_ATTR; pinfo.wait_cycle = 0; pinfo.bpp = 16; /** bytes per pixel **/ pinfo.lcd.vsync_enable = TRUE; pinfo.lcd.hw_vsync_mode = TRUE; pinfo.lcd.refx100 = 6000; /** Check Datasheet*/ pinfo.lcd.v_back_porch = 10; pinfo.lcd.v_front_porch = 8; pinfo.lcd.v_pulse_width = 13; pinfo.lcd.vsync_notifier_period = (1 * HZ); pinfo.lcd.rev = 1; pinfo.bl_max = 0x64; /* max 0x64 */ /**panel 背光调节范围**/ pinfo.bl_min = 0x00; /* min 0x00 */ pinfo.clk_rate = 192000000; /**MDDI 工作clock**/ pinfo.clk_min = 190000000; pinfo.clk_max = 200000000; pinfo.fb_num = 2; /** 注册panel设备 **/ ret = mddi_ta8851_device_register(&pinfo, 1, 1);
msm_fb.c
msm_fb device接口
在board-msm7x30.c_TTL.c中有如下定义:
static struct platform_device msm_fb_device = { .name = "msm_fb", .id = 0, .num_resources = ARRAY_SIZE(msm_fb_resources), .resource = msm_fb_resources, .dev = { .platform_data = &msm_fb_pdata, } };
msm_fb driver接口
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 = { .name = "msm_fb", .pm = &msm_fb_dev_pm_ops, }, };
采用platform_driver机制,msm_fb会调用msm_fb_probe,实现探测驱动、并创建sysfs group功能。
注意:platform总线可以自动进行device与driver的匹配,但driver的name必须与device的name完全一致。
msm_fb_probe()的具体实现流程为:
- platform_get_drvdata(pdev),获取平台设备driver信息
- pm_runtime_set_active,启用pm_runtime
- 调用同文件的msm_fb_register()功能函数(d节详细介绍),注册framebuffer;
- 将已注册的设备,添加入pdev_list,创建/sys节点。
msm_fb file_operation
static struct fb_ops msm_fb_ops = { .owner = THIS_MODULE, .fb_open = msm_fb_open, .fb_release = msm_fb_release, .fb_read = NULL, .fb_write = NULL, .fb_cursor = NULL, .fb_check_var = msm_fb_check_var, .fb_set_par = msm_fb_set_par, .fb_setcolreg = NULL, .fb_blank = msm_fb_blank, .fb_pan_display = msm_fb_pan_display, /** 实现双buffer的切换 */ .fb_fillrect = msm_fb_fillrect, /** 画矩形 */ .fb_copyarea = msm_fb_copyarea, /** 块copy */ .fb_imageblit = msm_fb_imageblit, .fb_rotate = NULL, .fb_sync = NULL, .fb_ioctl = msm_fb_ioctl, /** msm自定义io控制功能*/ .fb_mmap = msm_fb_mmap, };
当用户空间有操作fb设备请求时,fbmem.c接受这一请求,并通过调用具体的msm_fb.c中的文件操作符来进行操作,即填充fbmem.c中涉及的fops域。 其中,msm_fb_ioctl()中实现了一些自定义io控制功能,是通过调用mddi_ta8851.c中的具体功能函数,实现其功能的,详细分析请参见mddi_ta8851.c文件解析。
mddi_ta8851.c
该文件包含了所有与具体LCD(ta8851)相关的信息和驱动,具体如下:
mddi_ta8851 device接口
mddi_ta8851 driver接口
static struct platform_driver this_driver = { .probe = mddi_ta8851_probe, .driver = { .name = "mddi_ta8851", }, };
其中,mddi_ta8851_probe驱动探测,调用msm_fb.c中的msm_fb_add _device(),将ta8851的详细信息添加到系统中,申请一个framebuffer。
mddi_ta8851_device_register()
此函数被/msm/drivers/video/msm/Mddi_hitachi_wvga_pt.c文件中的mddi_hitachi_wvga_pt _init(void)函数调用,实现将检测到的lcd设备panel_info添加到系统中的功能,其流程为:
- 调用platform_device_alloc,申请mddi_ta8851的platform_device结构体内存空间;
- 接收mddi_hitachi_wvga_pt_init(void)中设置的lcd参数,添加入pdev->panel_info;
- 调用platform_device_add(pdev),将lcd设备添加入platform总线。