分类:
2008-05-15 17:11:47
2、 fbmem.c
fbmem.c 处于Framebuffer设备驱动技术的中心位置.它为上层应用程序提供系统调用也为下一层的特定硬件驱动提供接口;那些底层硬件驱动需要用到这儿的接口来向系统内核注册它们自己. fbmem.c 为所有支持FrameBuffer的设备驱动提供了通用的接口,避免重复工作.
1) 全局变量
struct fb_info *registered_fb[FB_MAX];
int num_registered_fb;
这两变量记录了所有fb_info 结构的实例,fb_info
结构描述显卡的当前状态,所有设备对应的fb_info
结构都保存在这个数组中,当一个FrameBuffer设备驱动向系统注册自己时,其对应的fb_info
结构就会添加到这个结构中,同时num_registered_fb 为自动加1.
static struct {
const char *name;
int (*init)(void);
int (*setup)(void);
} fb_drivers[] __initdata= { ....};
如果FrameBuffer设备被静态链接到内核,其对应的入口就会添加到这个表中;如果是动态加载的,即使用insmod/rmmod,就不需要关心这个表。
static struct file_operations fb_ops ={
owner: THIS_MODULE,
read: fb_read,
write: fb_write,
ioctl: fb_ioctl,
mmap: fb_mmap,
open: fb_open,
release: fb_release
};
这是一个提供给应用程序的接口.
2)fbmem.c 实现了如下函数.
register_framebuffer(struct fb_info *fb_info);
unregister_framebuffer(struct fb_info *fb_info);
这两个是提供给下层FrameBuffer设备驱动的接口,设备驱动通过这两函数向系统注册或注销自己。几乎底层设备驱动所要做的所有事情就是填充fb_info结构然后向系统注册或注销它。
(二)一个LCD显示芯片的驱动实例
以Skeleton LCD
控制器驱动为例,在LINUX中存有一个/fb/skeleton.c的skeleton的Framebuffer驱动程序,很简单,仅仅是填充了
fb_info结构,并且注册/注销自己。设备驱动是向用户程序提供系统调用接口,所以我们需要实现底层硬件操作并且定义file_operations
结构来向系统提供系统调用接口,从而实现更有效的LCD控制器驱动程序。
1)在系统内存中分配显存
在fbmem.c文件中可以看到,
file_operations 结构中的open()和release()操作不需底层支持,但read()、write()和
mmap()操作需要函数fb_get_fix()的支持.因此需要重新实现函数fb_get_fix()。另外还需要在系统内存中分配显存空间,大多数的LCD控制器都没有自己的显存空间,被分配的地址空间的起始地址与长度将会被填充到fb_fix_screeninfo 结构的smem_start 和smem_len 的两个变量中.被分配的空间必须是物理连续的。
2)实现 fb_ops 中的函数
用户应用程序通过ioctl()系统调用操作硬件,fb_ops 中的函数就用于支持这些操作。(注:
fb_ops结构与file_operations
结构不同,fb_ops是底层操作的抽象,而file_operations是提供给上层系统调用的接口,可以直接调用.
ioctl()系统调用在文件fbmem.c中实现,通过观察可以发现ioctl()命令与fb_ops’s 中函数的关系:
FBIOGET_VSCREENINFO fb_get_var
FBIOPUT_VSCREENINFO fb_set_var
FBIOGET_FSCREENINFO fb_get_fix
FBIOPUTCMAP fb_set_cmap
FBIOGETCMAP fb_get_cmap
FBIOPAN_DISPLAY fb_pan_display
如果我们定义了fb_XXX_XXX 方法,用户程序就可以使用FBIOXXXX宏的ioctl()操作来操作硬件。
文件linux/drivers/video/fbgen.c或者linux/drivers/video目录下的其它设备驱动是比较好的参考资料。在所有的这些函数中fb_set_var()是最重要的,它用于设定显示卡的模式和其它属性,下面是函数fb_set_var()的执行步骤:
1)检测是否必须设定模式
2)设定模式
3)设定颜色映射
4) 根据以前的设定重新设置LCD控制器的各寄存器。
第四步表明了底层操作到底放置在何处。在系统内存中分配显存后,显存的起始地址及长度将被设定到 LCD控制器的各寄存器中(一般通过fb_set_var() 函数),显存中的内容将自动被LCD控制器输出到屏幕上。另一方面,用户程序通过函数mmap()将显存映射到用户进程地址空间中,然后用户进程向映射空 间发送的所有数据都将会被显示到LCD显示器上。
三、FrameBuffer的应用
(一)、一个使用FrameBuffer的例子
好,现在可以让我们开始实现直接写屏:
1、打开一个FrameBuffer设备
2、通过mmap调用把显卡的物理内存空间映射到用户空间
3、直接写内存。
/********************************
File name : fbtools.h
*/
#ifndef _FBTOOLS_H_
#define _FBTOOLS_H_
#include
//a framebuffer device structure;
typedef struct fbdev{
int fb;
unsigned long fb_mem_offset;
unsigned long fb_mem;
struct fb_fix_screeninfo fb_fix;
struct fb_var_screeninfo fb_var;
char dev[20];
} FBDEV, *PFBDEV;
//open & init a frame buffer
//to use this function,
//you must set FBDEV.dev="/dev/fb0"
//or "/dev/fbX"
//it's your frame buffer.
int fb_open(PFBDEV pFbdev);
//close a frame buffer
int fb_close(PFBDEV pFbdev);
//get display depth
int get_display_depth(PFBDEV pFbdev);
//full screen clear
void fb_memset(void *addr, int c, size_t len);
#endif
/******************
File name : fbtools.c
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include "fbtools.h"
#define TRUE 1
#define FALSE 0
#define MAX(x,y) ((x)>(y)?(x)y))
#define MIN(x,y) ((x)<(y)?(x)y))
//open & init a frame buffer
int fb_open(PFBDEV pFbdev)
{
pFbdev->fb = open(pFbdev->dev, O_RDWR);
if(pFbdev->fb < 0)
{
printf("Error opening %s: %m. Check kernel config\n", pFbdev->dev);
return FALSE;
}
if (-1 == ioctl(pFbdev->fb,FBIOGET_VSCREENINFO,&(pFbdev->fb_var)))
{
printf("ioctl FBIOGET_VSCREENINFO\n");
return FALSE;
}
if (-1 == ioctl(pFbdev->fb,FBIOGET_FSCREENINFO,&(pFbdev->fb_fix)))
{
printf("ioctl FBIOGET_FSCREENINFO\n");
return FALSE;
}
//map physics address to virtual address
pFbdev->fb_mem_offset = (unsigned long)(pFbdev->fb_fix.smem_start) & (~PAGE_MASK);
pFbdev->fb_mem = (unsigned long int)mmap(NULL,
pFbdev->fb_fix.smem_len + pFbdev->fb_mem_offset,
PROT_READ | PROT_WRITE, MAP_SHARED, pFbdev->fb, 0);
if (-1L == (long) pFbdev->fb_mem)
{
printf("mmap error! mem:%d offset:%d\n", pFbdev->fb_mem, pFbdev->fb_mem_offset);
return FALSE;
}
return TRUE;
}
//close frame buffer
int fb_close(PFBDEV pFbdev)
{
close(pFbdev->fb);
pFbdev->fb=-1;
}
//get display depth
int get_display_depth(PFBDEV pFbdev);
{
if(pFbdev->fb<=0)
{
printf("fb device not open, open it first\n");
return FALSE;
}
return pFbdev->fb_var.bits_per_pixel;
}
//full screen clear
void fb_memset (void *addr, int c, size_t len)
{
memset(addr, c, len);
}
//use by test
#define DEBUG
#ifdef DEBUG
main()
{
FBDEV fbdev;
memset(&fbdev, 0, sizeof(FBDEV));
strcpy(fbdev.dev, "/dev/fb0");
if(fb_open(&fbdev)==FALSE)
{
printf("open frame buffer error\n");
return;
}
fb_memset(fbdev.fb_mem + fbdev.fb_mem_offset, 0, fbdev.fb_fix.smem_len);
fb_close(&fbdev);
}
(二)基于Linux核心的汉字显示的尝试
我们以一个简单的例子来说明字符显示的过程。我们假设是在虚拟终端1(/dev/tty1)下运行一个如下的简单程序。
main ( )
{
puts("hello, world.\n");
}
puts
函数向缺省输出文件(/dev/tty1)发出写的系统调用write(2)。系统调用到linux核心里面对应的核心函数是console.c中的
con_write(),con_write()最终会调用do_con_write( )。在do_con_write( )中负责把"hello,
world.\n"这个字符串放到tty1对应的缓冲区中去。
do_con_write( )还负责处理控制字符和光标的位置。让我们来看一下do_con_write()这个函数的声明。
static int do_con_write(struct tty_struct * tty, int from_user, const unsigned char *buf, int count)
其中tty是指向tty_struct结构的指针,这个结构里面存放着关于这个tty的所有信息(请参照
linux/include/linux/tty.h)。Tty_struct结构中定义了通用(或高层)tty的属性(例如宽度和高度等)。在
do_con_write(
)函数中用到了tty_struct结构中的driver_data变量。driver_data是一个vt_struct指针。在vt_struct结
构中包含这个tty的序列号(我们正使用tty1,所以这个序号为1)。Vt_struct结构中有一个vc结构的数组vc_cons,这个数组就是各虚
拟终端的私有数据。
static int do_con_write(struct tty_struct * tty, int from_user,const unsigned char *buf, int count)
{
struct vt_struct *vt = (struct vt_struct *)tty->driver_data;//我们用到了driver_data变量
. . . . .
currcons = vt->vc_num; file://我们在这里的vc_nums就是1
. . . . .
}
要访问虚拟终端的私有数据,需使用vc_cons〔currcons〕.d指针。这个指针指向的结构含有当前虚拟终端上光标的位置、缓冲区的起始地址、缓冲区大小等等。
"hello, world.\n"中的每一个字符都要经过conv_uni_to_pc(
)这个函数转换成8位的显示字符。这要做的主要目的是使不同语言的国家能把16位的UniCode码映射到8位的显示字符集上,目前还是主要针对欧洲国家
的语言,映射结果为8位,不包含对双字节(double byte)的范围。
这种UNICODE到显示字符的映射关系可以由用户自行定义。在缺省的映射表上,会把中文的字符映射到其他的字符上,这是我们不希望看到也是不需要的。所以我们有两个选择∶
经过conv_uni_to_pc( )转换之后,"hello, world.\n"中的字符被一个一个地填写到tty1的缓冲区中。然后do_con_write( )调用下层的驱动,把缓冲区中的内容输出到显示器上(也就相当于把缓冲区的内容拷贝到VGA显存中去)。
sw->con_putcs(vc_cons〔currcons〕.d, (u16 *)draw_from, (u16*)draw_to-(u16 *)draw_from, y, draw_x);
之所以要调用底层驱动,是因为存在不同的显示设备,其对应VGA显存的存取方式也不一样。