显卡总是有一定的内存。这个内存是图像数据的位图缓冲显示的地方。从软件的角度来看,帧缓冲区是一个字符设备,提供对内存的访问。
也就是说,帧缓冲驱动提供了一个接口,用于:
- 显示模式设置
- 对视频缓冲区的内存访问
- 基本 2D 加速操作(例如,滚动)
为了提供这个接口,framebuffer 驱动通常直接与硬件对话。有一些众所周知的帧缓冲驱动,例如:
- intelfb ,是各种英特尔 8xx/9xx 兼容图形设备的帧缓冲器
- vesafb ,这是一个帧缓冲驱动,使用 VESA 标准接口与视频硬件对话
- mxcfb ,i.MX6 芯片系列的帧缓冲驱动
Framebuffer 驱动是 Linux 下最简单的图形驱动形式,不要把它们和 X.org 驱动混淆,后者实现了 3D 加速等高级功能,或者是内核模式设置(KMS)驱动,它同时公开了 framebuffer 和 GPU 功能(就像 X.org 驱动一样)。
i.MX6 X.org driver is a closed source and called vivante.
回到我们的帧缓冲驱动,它们是非常简单的应用编程接口驱动,通过字符设备展示显卡功能,可通过/dev/fbX
条目从用户空间访问。你可以在马丁·费德勒的综合演讲 Linux 图形去神秘化:http://keyj.emphy.de/files/linuxgraphics_en.pdf中找到更多关于 Linux 图形堆栈的信息。
在本章中,我们将讨论以下主题:
- Framebuffer 驱动数据结构和方法,从而覆盖整个驱动架构
- 帧缓冲区设备操作,加速和非加速
- 从用户空间访问帧缓冲区
framebuffer 驱动严重依赖于四种数据结构,它们都在include/linux/fb.h
中定义,这也是为了处理 framebuffer 驱动,您应该在代码中包含的头:
#include <linux/fb.h>
这些结构是fb_var_screeninfo
、fb_fix_screeninfo
、fb_cmap
和fb_info
。前三个可以从用户空间代码获得。现在让我们描述每个结构的目的,它们的意义,以及它们的用途。
- 内核使用
struct struct fb_var_screeninfo
的一个实例来保存显卡的可变属性。这些值由用户定义,例如分辨率深度:
struct fb_var_screeninfo {
__u32 xres; /* visible resolution */
__u32 yres;
__u32 xres_virtual; /* virtual resolution */
__u32 yres_virtual;
__u32 xoffset; /* offset from virtual to visible resolution */
__u32 yoffset;
__u32 bits_per_pixel; /* # of bits needed to hold a pixel */
[...]
/* Timing: All values in pixclocks, except pixclock (of course) */
__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 upper_margin; /* time from sync to picture */
__u32 lower_margin;
__u32 hsync_len; /* length of horizontal sync */
__u32 vsync_len; /* length of vertical sync */
__u32 rotate; /* angle we rotate counter clockwise */
};
这可以总结为如下图所示:
- 视频卡有一些属性是固定的,或者是由制造商固定的,或者是在设置模式时应用的,否则无法更改。这一般是硬件信息。这方面的一个很好的例子是帧缓冲存储器的启动,即使用户程序也不能改变。内核在
struct 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 */
__u32 type; /* see FB_TYPE_* */
__u32 type_aux; /* Interleave for interleaved Planes */
__u32 visual; /* see FB_VISUAL_* */
__u16 xpanstep; /* zero if no hardware panning */
__u16 ypanstep; /* zero if no hardware panning */
__u16 ywrapstep; /* zero if no hardware ywrap */
__u32 line_length; /* length of a line in bytes */
unsigned long mmio_start; /* Start of Memory Mapped I/O
*(physical address)
*/
__u32 mmio_len; /* Length of Memory Mapped I/O */
__u32 accel; /* Indicate to driver which */
/* specific chip/card we have */
__u16 capabilities; /* see FB_CAP_* */
};
- struct
fb_cmap
结构指定颜色映射,用于以内核可以理解的方式存储用户对颜色的定义,以便将其发送到底层视频硬件。可以使用这种结构来定义不同颜色所需的 RGB 比率:
struct fb_cmap {
__u32 start; /* First entry */
__u32 len; /* Number of entries */
__u16 *red; /* Red values */
__u16 *green; /* Green values */
__u16 *blue; /* Blue values */
__u16 *transp; /* Transparency. Discussed later on */
};
struct fb_info
结构代表帧缓冲区本身,是帧缓冲区驱动的主要数据结构。与前面讨论的其他结构不同,fb_info
只存在于内核中,不是用户空间 framebuffer API 的一部分:
struct fb_info {
[...]
struct fb_var_screeninfo var; /* Variable screen information.
Discussed earlier. */
struct fb_fix_screeninfo fix; /* Fixed screen information. */
struct fb_cmap cmap; /* Color map. */
struct fb_ops *fbops; /* Driver operations.*/
char __iomem *screen_base; /* Frame buffer's
virtual address */
unsigned long screen_size; /* Frame buffer's size */
[...]
struct device *device; /* This is the parent */
struct device *dev; /* This is this fb device */
#ifdef CONFIG_FB_BACKLIGHT
/* assigned backlight device */
/* set before framebuffer registration,
remove after unregister */
struct backlight_device *bl_dev;
/* Backlight level curve */
struct mutex bl_curve_mutex;
u8 bl_curve[FB_BACKLIGHT_LEVELS];
#endif
[...]
void *par; /* Pointer to private memory */
};
struct fb_info
结构应该总是动态分配的,使用framebuffer_alloc()
,这是一个内核(帧缓冲区核心)助手函数,为帧缓冲区设备的实例分配内存,以及它们的私有数据内存:
struct fb_info *framebuffer_alloc(size_t size, struct device *dev)
在这个原型中,size
将私有区域的大小表示为一个参数,并将其附加到已分配的fb_info
的末尾。可以使用fb_info
结构中的.par
指针来引用该私有区域。framebuffer_release()
做反操作:
void framebuffer_release(struct fb_info *info)
一旦设置好了,应该使用register_framebuffer()
向内核注册一个帧缓冲区,如果错误则返回否定的errno
,如果成功则返回zero
:
int register_framebuffer(struct fb_info *fb_info)
注册后,可以使用unregister_framebuffer()
函数取消注册帧缓冲区,该函数在出错时也会返回一个否定的errno
,如果成功则返回zero
:
int unregister_framebuffer(struct fb_info *fb_info)
分配和注册应该在设备探测期间完成,而取消注册和取消分配(释放)应该在驱动的remove()
功能中完成。
在struct fb_info
结构中,有一个.fbops
字段,它是struct fb_ops
结构的一个实例。该结构包含需要在 framebuffer 设备上执行一些操作的函数的集合。这些是fbdev
和fbcon
工具的入口点。该结构中的一些方法是强制性的,是帧缓冲区工作所需的最低要求,而其他方法是可选的,取决于驱动需要公开的功能,假设设备本身支持这些功能。
以下是struct fb_ops
结构的定义:
struct fb_ops {
/* open/release and usage marking */
struct module *owner;
int (*fb_open)(struct fb_info *info, int user);
int (*fb_release)(struct fb_info *info, int user);
/* For framebuffers with strange nonlinear layouts or that do not
* work with normal memory mapped access
*/
ssize_t (*fb_read)(struct fb_info *info, char __user *buf,
size_t count, loff_t *ppos);
ssize_t (*fb_write)(struct fb_info *info, const char __user *buf,
size_t count, loff_t *ppos);
/* checks var and eventually tweaks it to something supported,
* DO NOT MODIFY PAR */
int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);
/* set the video mode according to info->var */
int (*fb_set_par)(struct fb_info *info);
/* set color register */
int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
unsigned blue, unsigned transp, struct fb_info *info);
/* set color registers in batch */
int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info);
/* blank display */
int (*fb_blank)(int blank_mode, struct fb_info *info);
/* pan display */
int (*fb_pan_display)(struct fb_var_screeninfo *var, struct fb_info *info);
/* Draws a rectangle */
void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);
/* Copy data from area to another */
void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);
/* Draws a image to the display */
void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);
/* Draws cursor */
int (*fb_cursor) (struct fb_info *info, struct fb_cursor *cursor);
/* wait for blit idle, optional */
int (*fb_sync)(struct fb_info *info);
/* perform fb specific ioctl (optional) */
int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,
unsigned long arg);
/* Handle 32bit compat ioctl (optional) */
int (*fb_compat_ioctl)(struct fb_info *info, unsigned cmd,
unsigned long arg);
/* perform fb specific mmap */
int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);
/* get capability given var */
void (*fb_get_caps)(struct fb_info *info, struct fb_blit_caps *caps,
struct fb_var_screeninfo *var);
/* teardown any resources to do with this framebuffer */
void (*fb_destroy)(struct fb_info *info);
[...]
};
可以根据想要实现的功能设置不同的回调。
在第四章、字符设备驱动中,我们了解到字符设备通过struct file_operations
结构,可以导出一组文件操作,这些操作是文件相关系统调用的入口点,如open()
、close()
、read()
、write()
、mmap()
、ioctl()
等。
也就是说,不要把fb_ops
和file_operations
结构混淆。fb_ops
提供了底层操作的抽象,而file_operations
则是上层系统调用接口。内核在drivers/video/fbdev/core/fbmem.c
中实现帧缓冲区文件操作,内部调用我们在fb_ops
中定义的方法。这样就可以根据系统调用接口的需要实现底层的硬件操作,即file_operations
结构。比如用户open()
设备时,核心的打开文件操作方式会执行一些核心操作,如果设置了fb_ops.fb_open()
方式,则执行release
、mmap
等同样的方式。
帧缓冲设备支持一些在include/uapi/linux/fb.h
中定义的 ioctl 命令,用户程序可以使用这些命令在硬件上进行操作。这些命令都由核心的fops.ioctl
方法处理。对于其中一些命令,核心的 ioctl 方法可以在内部执行在fb_ops
结构中定义的方法。
人们可能想知道fb_ops.ffb_ioctl
是用来做什么的。只有当内核不知道给定的 ioctl 命令时,framebuffer 内核才执行fb_ops.fb_ioctl
。换句话说,fb_ops.fb_ioctl
在 framebuffer 核心的fops.ioctl
方法的默认语句中执行。
驱动方法由probe()
和remove()
功能组成。在进一步描述这些方法之前,让我们建立我们的fb_ops
结构:
static struct fb_ops myfb_ops = {
.owner = THIS_MODULE,
.fb_check_var = myfb_check_var,
.fb_set_par = myfb_set_par,
.fb_setcolreg = myfb_setcolreg,
.fb_fillrect = cfb_fillrect, /* Those three hooks are */
.fb_copyarea = cfb_copyarea, /* non accelerated and */
.fb_imageblit = cfb_imageblit, /* are provided by kernel */
.fb_blank = myfb_blank,
};
Probe
:驱动probe
功能负责初始化硬件,使用framebuffer_alloc()
功能创建struct fb_info
结构,上面register_framebuffer()
。以下示例假设设备是内存映射的。因此,你的非内存映射可以存在,比如屏幕坐在 SPI 总线上。在这种情况下,应使用总线专用例程:
static int myfb_probe(struct platform_device *pdev)
{
struct fb_info *info;
struct resource *res;
[...]
dev_info(&pdev->dev, "My framebuffer driver\n");
/*
* Query resource, like DMA channels, I/O memory,
* regulators, and so on.
*/
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -ENODEV;
/* use request_mem_region(), ioremap() and so on */
[...]
pwr = regulator_get(&pdev->dev, "lcd");
info = framebuffer_alloc(sizeof(
struct my_private_struct), &pdev->dev);
if (!info)
return -ENOMEM;
/* Device init and default info value*/
[...]
info->fbops = &myfb_ops;
/* Clock setup, using devm_clk_get() and so on */
[...]
/* DMA setup using dma_alloc_coherent() and so on*/
[...]
/* Register with the kernel */
ret = register_framebuffer(info);
hardware_enable_controller(my_private_struct);
return 0;
}
Remove
:remove()
功能应该释放在probe()
获得的任何东西,并调用:
static int myfb_remove(struct platform_device *pdev)
{
/* iounmap() memory and release_mem_region() */
[...]
/* Reverse DMA, dma_free_*();*/
[...]
hardware_disable_controller(fbi);
/* first unregister, */
unregister_framebuffer(info);
/* and then free the memory */
framebuffer_release(info);
return 0;
}
- 假设您使用管理器版本进行资源分配,您只需要使用
unregister_framebuffer()
和framebuffer_release()
。其他一切都将由内核完成。
让我们描述一下fb_ops
结构中声明的一些钩子。也就是说,关于编写 framebuffer 驱动的想法,您可以看看drivers/video/fbdev/vfb.c
,它是内核中一个简单的虚拟 framebuffer 驱动。您还可以在drivers/video/fbdev/imxfb.c
查看其他特定的帧缓冲驱动,比如 i.MX6 one,或者在Documentation/fb/api.txt
查看关于帧缓冲驱动 API 的内核文档。
钩子fb_ops->fb_check_var
负责检查帧缓冲区参数。其原型如下:
int (*fb_check_var)(struct fb_var_screeninfo *var,
struct fb_info *info);
这个函数应该检查 framebuffer 变量参数并调整到有效值。var
代表帧缓冲区变量参数,应检查和调整:
static int myfb_check_var(struct fb_var_screeninfo *var,
struct fb_info *info)
{
if (var->xres_virtual < var->xres)
var->xres_virtual = var->xres;
if (var->yres_virtual < var->yres)
var->yres_virtual = var->yres;
if ((var->bits_per_pixel != 32) &&
(var->bits_per_pixel != 24) &&
(var->bits_per_pixel != 16) &&
(var->bits_per_pixel != 12) &&
(var->bits_per_pixel != 8))
var->bits_per_pixel = 16;
switch (var->bits_per_pixel) {
case 8:
/* Adjust red*/
var->red.length = 3;
var->red.offset = 5;
var->red.msb_right = 0;
/*adjust green*/
var->green.length = 3;
var->green.offset = 2;
var->green.msb_right = 0;
/* adjust blue */
var->blue.length = 2;
var->blue.offset = 0;
var->blue.msb_right = 0;
/* Adjust transparency */
var->transp.length = 0;
var->transp.offset = 0;
var->transp.msb_right = 0;
break;
case 16:
[...]
break;
case 24:
[...]
break;
case 32:
var->red.length = 8;
var->red.offset = 16;
var->red.msb_right = 0;
var->green.length = 8;
var->green.offset = 8;
var->green.msb_right = 0;
var->blue.length = 8;
var->blue.offset = 0;
var->blue.msb_right = 0;
var->transp.length = 8;
var->transp.offset = 24;
var->transp.msb_right = 0;
break;
}
/*
* Any other field in *var* can be adjusted
* like var->xres, var->yres, var->bits_per_pixel,
* var->pixclock and so on.
*/
return 0;
}
上述代码根据用户选择的配置调整可变帧缓冲区属性。
钩子fp_ops->fb_set_par
是另一个硬件特定的钩子,负责向硬件发送参数。它根据用户设置(info->var
对硬件进行编程:
static int myfb_set_par(struct fb_info *info)
{
struct fb_var_screeninfo *var = &info->var;
/* Make some compute or other sanity check */
[...]
/*
* This function writes value to the hardware,
* in the appropriate registers
*/
set_controller_vars(var, info);
return 0;
}
钩子fb_ops->fb_blank
是硬件专用钩子,负责屏幕下料。其原型如下:
int (*fb_blank)(int blank_mode, struct fb_info *info)
blank_mode
参数始终是以下值之一:
enum {
/* screen: unblanked, hsync: on, vsync: on */
FB_BLANK_UNBLANK = VESA_NO_BLANKING,
/* screen: blanked, hsync: on, vsync: on */
FB_BLANK_NORMAL = VESA_NO_BLANKING + 1,
/* screen: blanked, hsync: on, vsync: off */
FB_BLANK_VSYNC_SUSPEND = VESA_VSYNC_SUSPEND + 1,
/* screen: blanked, hsync: off, vsync: on */
FB_BLANK_HSYNC_SUSPEND = VESA_HSYNC_SUSPEND + 1,
/* screen: blanked, hsync: off, vsync: off */
FB_BLANK_POWERDOWN = VESA_POWERDOWN + 1
};
空白显示的通常方式是在blank_mode
参数上做switch case
,如下所示:
static int myfb_blank(int blank_mode, struct fb_info *info)
{
pr_debug("fb_blank: blank=%d\n", blank);
switch (blank) {
case FB_BLANK_POWERDOWN:
case FB_BLANK_VSYNC_SUSPEND:
case FB_BLANK_HSYNC_SUSPEND:
case FB_BLANK_NORMAL:
myfb_disable_controller(fbi);
break;
case FB_BLANK_UNBLANK:
myfb_enable_controller(fbi);
break;
}
return 0;
}
消隐操作应禁用控制器,停止其时钟并断电。取消冻结应该执行相反的操作。
用户视频操作,如混合、拉伸、移动位图或动态渐变生成都是繁重的任务。它们需要图形加速来获得可接受的性能。可以使用struct fp_ops
结构的以下字段实现帧缓冲加速方法:
.fb_imageblit()
:这个方法在显示器上画一个图像,非常有用.fb_copyarea()
:此方法将一个矩形区域从一个屏幕区域复制到另一个屏幕区域.fb_fillrect():
该方法以优化的方式用像素线填充矩形
因此,内核开发者想到了没有硬件加速的控制器,并提供了软件优化的方法。这使得加速实现成为可选的,因为存在软件回退。也就是说,如果帧缓冲控制器不提供任何加速机制,那么必须使用内核通用例程来填充这些方法。
这些分别是:
cfb_imageblit()
:这是 imageblit 的内核提供的回退。内核使用它在启动时向屏幕输出一个徽标。cfb_copyarea()
:这是区域复制操作。cfb_fillrect
():这是实现同名操作的 framebuffer 核心非加速方法。
在这一节中,让我们总结一下上一节讨论的内容。为了编写 framebuffer 驱动,必须:
- 填充一个
struct fb_var_screeninfo
结构,以便提供有关 framebuffer 变量属性的信息。用户空间可以更改这些属性。 - 填充一个
struct fb_fix_screeninfo
结构,提供固定的参数。 - 建立
struct fb_ops
结构,提供必要的回调函数,帧缓冲子系统将使用这些函数来响应用户的动作。 - 仍然在
struct fb_ops
结构中,如果设备支持,必须提供加速函数回调。 - 设置一个
struct fb_info
结构,给它填充上一步填充的结构,并在上面调用register_framebuffer()
,以便在内核中注册。
关于编写简单的帧缓冲驱动的想法,可以看看drivers/video/fbdev/vfb.c
,这是一个内核中的虚拟帧缓冲驱动。您可以通过CONGIF_FB_VIRTUAL
选项在内核中启用此功能。
人们通常通过mmap()
命令访问帧缓冲存储器,以便将帧缓冲存储器映射到系统内存的一部分,从而在屏幕上绘制像素成为影响内存值的简单事情。屏幕参数(可变和固定)通过 ioctl 命令提取,尤其是FBIOGET_VSCREENINFO
和FBIOGET_FSCREENINFO
。完整列表可在内核源代码中的include/uapi/linux/fb.h
处获得。
下面是在 framebuffer 上绘制 300*300 正方形的示例代码:
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#define FBCTL(_fd, _cmd, _arg) \
if(ioctl(_fd, _cmd, _arg) == -1) { \
ERROR("ioctl failed"); \
exit(1); }
int main()
{
int fd;
int x, y, pos;
int r, g, b;
unsigned short color;
void *fbmem;
struct fb_var_screeninfo var_info;
struct fb_fix_screeninfo fix_info;
fd = open(FBVIDEO, O_RDWR);
if (tfd == -1 || vfd == -1) {
exit(-1);
}
/* Gather variable screen info (virtual and visible) */
FBCTL(fd, FBIOGET_VSCREENINFO, &var_info);
/* Gather fixed screen info */
FBCTL(fd, FBIOGET_FSCREENINFO, &fix_info);
printf("****** Frame Buffer Info ******\n");
printf("Visible: %d,%d \nvirtual: %d,%d \n line_len %d\n",
var_info.xres, this->var_info.yres,
var_info.xres_virtual, var_info.yres_virtual,
fix_info.line_length);
printf("dim %d,%d\n\n", var_info.width, var_info.height);
/* Let's mmap frame buffer memory */
fbmem = mmap(0, v_var.yres_virtual * v_fix.line_length, \
PROT_WRITE | PROT_READ, \
MAP_SHARED, fd, 0);
if (fbmem == MAP_FAILED) {
perror("Video or Text frame bufer mmap failed");
exit(1);
}
/* upper left corner (100,100). The square is 300px width */
for (y = 100; y < 400; y++) {
for (x = 100; x < 400; x++) {
pos = (x + vinfo.xoffset) * (vinfo.bits_per_pixel / 8)
+ (y + vinfo.yoffset) * finfo.line_length;
/* if 32 bits per pixel */
if (vinfo.bits_per_pixel == 32) {
/* We prepare some blue color */
*(fbmem + pos) = 100;
/* adding a little green */
*(fbmem + pos + 1) = 15+(x-100)/2;
/* With lot of read */
*(fbmem + pos + 2) = 200-(y-100)/5;
/* And no transparency */
*(fbmem + pos + 3) = 0;
} else { /* This assume 16bpp */
r = 31-(y-100)/16;
g = (x-100)/6;
b = 10;
/* Compute color */
color = r << 11 | g << 5 | b;
*((unsigned short int*)(fbmem + pos)) = color;
}
}
}
munmap(fbp, screensize);
close(fbfd);
return 0;
}
还可以使用cat
或dd
命令将帧缓冲存储器转储为原始图像:
# cat /dev/fb0 > my_image
使用以下方法写回:
# cat my_image > /dev/fb0
可以通过特殊的/sys/class/img/fb<N>/blank sysfs
文件来清空/取消屏幕,其中<N>
是帧缓冲区索引。写 1 会使屏幕空白,而写 0 会使屏幕不空白:
# echo 0 > /sys/class/img/fb0/blank
# echo 1 > /sys/class/img/fb0/blank
帧缓冲驱动是 Linux 图形驱动的最简单形式,几乎不需要实现工作。他们大量抽象硬件。在这个阶段,您应该能够增强现有的驱动(例如图形加速功能),或者从头开始编写一个新的驱动。但是,建议依赖于现有的驱动,该驱动的硬件与您需要为其编写驱动的硬件共享尽可能多的特性。让我们跳到下一章,也是最后一章,讨论网络设备。