1. 使用QEMU的优点
-
概述:使用QEMU模型LCD屏幕,可以只写驱动程序,不需要设置LCD硬件相关,也不需要映射Framebuffer。
-
使用QEMU可以非常方便地调试内核、查看驱动程序执行过程
-
有助于深入研究内核及驱动
-
Linux驱动 = 驱动框架 + 硬件操作。 如果硬件操作足够简单,我们就可以把精力放在驱动程序的框架上,这才是Linux的核心。
对于硬件操作,你至少要做这些事情:
-
设置引脚用于LCD
-
阅读LCD手册,阅读LCD控制器手册,根据LCD参数设置LCD控制器
-
设置LCD控制器时,你还需要了解所用的主控芯片的时钟系统
-
-
分配Framebuffer,把Framebuffer地址告诉LCD控制器
总之,非常复杂。如果你换了芯片,这些工作又得重来一次。 如果你本身已经对阅读芯片手册很熟悉,对硬件操作很熟悉,那么学习时没必要把时间浪费在这方面。 使用QEMU,虚拟出一款简单的LCD控制器,可以简化硬件操作,让我们把精力放在驱动框架上。
2. 虚拟LCD相关的控制器手册
概述:这些设置是在构建QEMU这个软件的时候配置的一些相关信息
实现了一个虚拟的LCD控制器,它的操作很简单。 只有4个寄存器,手册如下:
虚拟的LCD芯片参数:目虚拟的LCD分辨率为500x300,16bpp。暂时未支持其他参数。
3. 上机实验_基于QEMU
3.1 实验思路
概述:使用 QEMU思路:QEMU是虚拟的LCD,需要调用内核的驱动程序进行驱动,我们更改驱动程序需要再内核目录例如(linux/drivers/video/fbdev)更改对应的驱动程序,主要是将写好的驱动程序放入此目录中,然后修改makefile文件,如下:
最后,即可重新执行make zImage
编译内核,内核里就含有新的驱动程序了。
然后调用一些测试函数,例如fb_test,即可判断驱动是否更改成功。
3.2 驱动代码分析
声明用到的一些头文件
声明用到的变量和操作函数,定义LCD控制器结构体,声明两个变量指向LCD控制器和Framebuffer信息,定义Framebuffer操作函数集合。
入口函数:分配fb内存,设置fb,包括分辨率,像素深度,各个颜色偏移量和位数,
计算内存大小依据:
DMA(Direct Memory Access,直接内存访问)是一种硬件特性,它允许某些硬件子系统(如网络接口卡、图形卡、声卡等)直接访问系统内存,而无需中央处理单元(CPU)的介入。这种能力使得这些硬件设备能够独立于CPU执行内存读写操作,从而提高系统的整体性能和效率。
分配DMA一致性内存的目的是为了让Framebuffer能够被显示控制器等硬件设备直接访问,同时确保CPU能够看到由DMA操作引起的内存更改,而无需进行额外的缓存同步操作。
设置fb类型(颜色方面),颜色视觉类型(真彩色),给结构体赋值具体操作函数
register_framebuffer(myfb_info);
作用是将之前初始化和配置的Framebuffer设备注册到Linux内核中。注册Framebuffer设备是Linux驱动程序开发中的一个重要步骤,具体作用如下:
总结:注册相当于在内核空间将自己的参数和操作函数登记,此时内核空间可以对其进行内存,中断,分配资源,处理,同步等管理,
-
设备识别:注册后,内核会识别Framebuffer设备作为一个可用的显示设备,可以在
/var/log
目录下的某些日志文件中看到它被识别和注册的信息。 -
文件系统集成:注册Framebuffer设备会在
/dev
目录下创建一个设备文件(如/dev/fb0
),用户空间程序可以通过这个文件与Framebuffer设备进行交互。 -
统一访问接口:注册过程将Framebuffer的固定和可变属性、操作函数等信息注册到内核的统一框架中,为其他内核组件或用户空间程序提供统一的访问接口。
-
驱动程序钩子:注册Framebuffer设备时,会将
fb_info
结构体中的fbops
(Framebuffer操作)钩子函数注册到内核,这些函数定义了如何操作Framebuffer,例如屏幕的打开、关闭、读写等。 -
内存管理:注册后,Framebuffer的内存区域会被内核纳入管理,确保内存的合法访问和同步。
-
硬件同步:注册Framebuffer设备后,内核可以协调不同用户空间程序对Framebuffer的访问,确保显示内容的正确更新和硬件状态的同步。
-
中断处理:如果Framebuffer硬件支持中断,注册时也可以设置中断处理函数,以便在特定事件发生时进行处理。
-
资源分配:注册Framebuffer设备时,可能还会涉及到对其他硬件资源的请求和管理,如中断号、DMA通道等。
-
启动显示:注册Framebuffer设备后,系统可以启动显示过程,如有必要,还可以设置显示模式或分辨率。
-
错误处理:如果在注册过程中出现问题,内核将记录错误信息,并且Framebuffer设备将不会对用户空间程序可用。
注册Framebuffer设备是显示系统正常工作的必要条件,它确保了硬件设备与软件接口的正确连接和协调。在Framebuffer驱动程序的生命周期中,通常在模块加载时进行注册,在模块卸载时进行反注册。
映射LCD控制寄存器到内核空间,设置LCD控制器相关参数
出口函数则是反注册和释放内存,以及解除LCD控制器的内核空间映射。
4. 结合APP分析LCD驱动程序
4.1 open
这段代码是Linux内核中的一个片段,描述了打开一个帧缓冲设备(framebuffer device)的过程。帧缓冲设备是用于图形显示的设备,比如连接到计算机的显示器或LCD屏幕。下面是对这段代码的逐行解释:
-
app: open("/dev/fb0", ...)
:这是应用程序调用open
系统调用的示例,尝试打开设备文件/dev/fb0
。/dev/fb0
是帧缓冲设备的一个常见设备文件,...
表示还有其他参数传递给open
函数。 -
主设备号: 29, 次设备号: 0
:这些是设备号,用于唯一标识系统中的设备。主设备号用于标识设备驱动程序,次设备号用于标识特定的设备实例。 -
fb_open
:这是内核中处理打开帧缓冲设备的函数。 -
struct fb_info *info;
:声明一个指向fb_info
结构的指针,fb_info
是内核中用于存储帧缓冲设备信息的结构体。 -
info = get_fb_info(fbidx);
:调用get_fb_info
函数获取帧缓冲设备的相关信息。fbidx
是帧缓冲设备的索引号,通常由设备文件名(如/dev/fb0
中的0
)决定。 -
if (info->fbops->fb_open) {
:检查fb_info
结构中的fbops
成员是否有定义fb_open
方法。fbops
是fb_info
中的一个结构,包含了帧缓冲设备操作的函数指针。 -
res = info->fbops->fb_open(info,1);
:如果fb_open
方法存在,则调用它来执行实际的打开操作。传递给fb_open
的参数info
是帧缓冲设备的相关信息,1
通常表示以非阻塞方式打开设备。 -
if (res)
:检查fb_open
调用的返回值。如果返回值非零,表示打开操作失败。 -
module_put(info->fbops->owner);
:如果打开失败,调用module_put
来减少模块的引用计数。info->fbops->owner
是指向模块所有者的指针,这通常是一个指向模块结构的指针。
这段代码是内核中处理设备文件打开请求的一部分,涉及到设备驱动程序的加载和初始化。当应用程序尝试打开一个设备文件时,内核会找到相应的设备驱动程序并调用它的fb_open
函数来完成打开操作。如果打开成功,应用程序就可以与设备进行通信;如果失败,则会释放与设备驱动程序相关的资源。
4.2 获得可变信息(含有分辨率等)
这段代码描述了在Linux内核中,应用程序通过ioctl
系统调用来获取帧缓冲设备(framebuffer device)的虚拟屏幕信息(virtual screen information)的过程。下面是对这段代码的逐行解释:
-
app: ioctl(fd, FBIOGET_VSCREENINFO, &fb_info->var);
:这是在应用程序中调用ioctl
函数的示例。fd
是文件描述符,它是之前通过open
系统调用获得的。FBIOGET_VSCREENINFO
是ioctl
命令,用于获取帧缓冲的虚拟屏幕信息。&fb_info->var
是指向一个结构体的地址,该结构体用于接收获取到的屏幕信息。 -
fb_ioctl
:这是内核中处理帧缓冲设备ioctl
请求的函数。 -
struct fb_info *info = file_fb_info(file);
:这行代码获取与文件描述符关联的帧缓冲信息结构体。file
是指向文件对象的指针,它通过文件描述符fd
获得。 -
do_fb_ioctl(info, cmd, arg);
:这行代码调用一个函数来执行实际的ioctl
操作。info
是帧缓冲信息结构体的指针,cmd
是ioctl
命令,arg
是指向用户提供的参数的指针。 -
var = info->var;
:这行代码将帧缓冲信息结构体中的var
成员(包含虚拟屏幕信息)赋值给局部变量var
。这个成员是由硬件相关的驱动程序设置的。 -
ret = copy_to_user(argp, &var, sizeof(var)) ? -EFAULT : 0;
:这行代码尝试将var
结构体的内容复制到用户空间的内存地址argp
。copy_to_user
函数用于在内核空间和用户空间之间复制数据。如果复制失败(即返回非零值),则ret
被设置为-EFAULT
,表示发生了访问错误;如果复制成功,则ret
被设置为0
。
整个流程是:应用程序通过ioctl
请求获取帧缓冲设备的屏幕信息,内核接收到请求后,找到对应的帧缓冲信息结构体,并将其中的屏幕信息复制到用户提供的内存地址。如果复制成功,内核会返回0
,表示操作成功;如果复制失败,内核会返回错误码-EFAULT
。
4.3 获得固定信息(含有显存信息)
这段代码描述了应用程序使用ioctl
系统调用来获取帧缓冲设备(framebuffer device)的固定屏幕信息(fixed screen information)的过程。下面是对这段代码的逐行解释:
-
app: ioctl(fd, FBIOGET_FSCREENINFO, &fb_info->fix);
:这是在应用程序中调用ioctl
函数的示例。fd
是文件描述符,它是之前通过open
系统调用获得的。FBIOGET_FSCREENINFO
是ioctl
命令,用于获取帧缓冲的固定屏幕信息。&fb_info->fix
是指向一个结构体的地址,该结构体用于接收获取到的屏幕信息。 -
fb_ioctl
:这是内核中处理帧缓冲设备ioctl
请求的函数。 -
struct fb_info *info = file_fb_info(file);
:这行代码获取与文件描述符关联的帧缓冲信息结构体。file
是指向文件对象的指针,它通过文件描述符fd
获得。 -
do_fb_ioctl(info, cmd, arg);
:这行代码调用一个函数来执行实际的ioctl
操作。info
是帧缓冲信息结构体的指针,cmd
是ioctl
命令,arg
是指向用户提供的参数的指针。 -
fix = info->fix;
:这行代码将帧缓冲信息结构体中的fix
成员(包含固定屏幕信息)赋值给局部变量fix
。这个成员是由硬件相关的驱动程序设置的,通常包含屏幕的物理特性,如屏幕的宽度、高度、内存布局等。 -
ret = copy_to_user(argp, &fix, sizeof(fix)) ? -EFAULT : 0;
:这行代码尝试将fix
结构体的内容复制到用户空间的内存地址argp
。copy_to_user
函数用于在内核空间和用户空间之间复制数据。如果复制失败(即返回非零值),则ret
被设置为-EFAULT
,表示发生了访问错误;如果复制成功,则ret
被设置为0
。
整个流程是:应用程序通过ioctl
请求获取帧缓冲设备的固定屏幕信息,内核接收到请求后,找到对应的帧缓冲信息结构体,并将其中的固定屏幕信息复制到用户提供的内存地址。如果复制成功,内核会返回0
,表示操作成功;如果复制失败,内核会返回错误码-EFAULT
。
固定屏幕信息(fix
)通常包括:
id
:帧缓冲设备的标识符。smem_start
:帧缓冲内存的起始物理地址。smem_len
:帧缓冲内存的长度。type
:帧缓冲的类型。type_aux
:辅助类型信息。visual
:颜色空间的视觉表示。xpanstep
和ypanstep
:水平和垂直的扫描步长。ywrapstep
:行的包装步长。line_length
:每行的字节长度。
4.4 mmap
这段代码展示了在应用程序和Linux内核中使用mmap
系统调用来映射帧缓冲设备的内存到用户空间的过程。下面是对这段代码的逐行解释:
应用程序代码解释:
-
void *ptr = mmap(0, ...);
:这是应用程序中调用mmap
函数的示例,用于创建一个内存映射。 -
fb_info->var.yres_virtual * fb_info->fix.line_length
:这是映射的大小,计算方法为帧缓冲的虚拟垂直分辨率(yres_virtual
)乘以每行的字节长度(fix.line_length
)。yres_virtual
通常大于实际屏幕的垂直分辨率(yres
),以支持额外的显示功能,如双缓冲或额外的帧缓冲。 -
PROT_WRITE | PROT_READ
:这些标志指定映射的内存区域应该是可读写的。 -
MAP_SHARED
:这个标志表示映射应该是共享的,即对映射区域的修改对其他映射同一文件的进程可见。 -
fd
:这是之前通过open
系统调用获得的帧缓冲设备的文件描述符。 -
0
:这是文件中映射开始的偏移量,通常设置为0
以从文件的开始处映射。
内核代码解释:
-
fb_mmap
:这是内核中处理帧缓冲设备mmap
请求的函数。 -
struct fb_info *info = file_fb_info(file);
:这行代码获取与文件描述符关联的帧缓冲信息结构体。 -
start = info->fix.smem_start;
:这行代码获取帧缓冲的起始物理地址,从fix
结构体中的smem_start
成员获得。 -
len = info->fix.smem_len;
:这行代码获取帧缓冲的内存长度,从fix
结构体中的smem_len
成员获得。 -
return vm_iomap_memory(vma, start, len);
:这行代码调用vm_iomap_memory
函数来实际创建内存映射。vma
是虚拟内存区域的结构体,start
是物理地址的起始位置,len
是要映射的内存长度。这个函数处理物理内存到虚拟内存的映射,并返回指向映射区域的指针。
整个流程是:应用程序请求映射帧缓冲设备的内存到用户空间,内核接收到请求后,找到对应的帧缓冲信息结构体,并使用vm_iomap_memory
函数来创建内存映射。这样,应用程序就可以直接读写帧缓冲的内存,而不需要通过read
和write
系统调用来进行。这种直接访问方式通常用于性能敏感的图形显示操作。