当前位置:网站首页>韦东山第二期课程内容概要
韦东山第二期课程内容概要
2022-07-07 22:21:00 【拾柒47】
韦东山第二期课程内容概要
2 输入子系统驱动
2.1 输入子系统概念介绍
为了写出通用的驱动程序,需要把我们写的驱动融合到Linux现有的驱动框架中,因为Linux内核是通用的,按键驱动属于输入子系统框架。我们需要弄清楚Linux内核的驱动框架,将我们需要的功能加进去,向上提供统一的标准Linux接口。
(一)各个具体设备的驱动程序如何向上注册
输入子系统驱动方面纯软件的代码主要在linux-2.6.22.6\drivers\input\evdev.c中,这个文件中的代码是Linux内核抽取出来像触摸屏、按键等输入设备所共有的东西,是稳定的,不随具体硬件而改变。这些软件主要是管理输入缓冲区以及提供同一的file_operations接口。
关于什么是evdev,见链接https://blog.csdn.net/wanywhn/article/details/83443843
1、evdev.c文件实现最底层的file_operations结构体实例
在Z:\linux-2.6.22.6\drivers\input\evdev.c中系统创建了一个file_operations实例:evdev_fops,并实现了其中的各个函数。
static const struct file_operations evdev_fops = {
.owner = THIS_MODULE,
.read = evdev_read,
.write = evdev_write,
.poll = evdev_poll,
.open = evdev_open,
.release = evdev_release,
.unlocked_ioctl = evdev_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = evdev_ioctl_compat,
#endif
.fasync = evdev_fasync,
.flush = evdev_flush
};
2、将file_operations实例进一步封装
在evdev.c中,将file_operations实例:evdev_fops进一步封装为一个input_handler结构体实例:evdev_handler
static struct input_handler evdev_handler = {
.event = evdev_event,
.connect = evdev_connect,
.disconnect = evdev_disconnect,
.fops = &evdev_fops,
.minor = EVDEV_MINOR_BASE,
.name = "evdev",
.id_table = evdev_ids,//表示evdev能够支持的设备,用于input_attach_handler和设备进行匹配
};
3、evdev文件入口函数向上注册已经实现的input_handler结构体实例:evdev_handler
同样evdev.c中,evdev驱动程序的入口函数static int __init evdev_init(void)中将实现的input_handler实例:evdev_handler向上注册。
return input_register_handler(&evdev_handler);
函数input_register_handler处于输入子系统核心层,所有输入外设(键盘、触摸屏等)在实现具体的input_handler实例(里面包括file_operations实例)后都通过这个函数向上注册,这个函数位于:Z:\linux-2.6.22.6\drivers\input\input.c,函数原型为:
int input_register_handler(struct input_handler *handler)
4、input_register_handler函数将input_handler实例注册到input_table表中
input_table在input.c中声明,是一个普通的指针数组,容量为8。
static struct input_handler *input_table[8];
在input_register_handler函数中根据传入实例的次设备号(最初在evdev文件定义input_handler结构体实例:evdev_handler时传入,在这个例子中是一个固定宏值EVDEV_MINOR_BASE:64),下面的语句将次设备号左移5位,也就是除32,即input_table[2]存放evdev文件的input_handler结构体实例:evdev_handler。
input_table[handler->minor >> 5] = handler;
至此,底层具体的驱动程序向上注册完毕,当系统insmod evdev驱动时,系统会自动调用evdev文件的驱动入口函数:static int __init evdev_init(void),最终会使得input_table中有指向本驱动的input_handler结构体实例:evdev_handler的指针。
5、input_register_handler函数将input_handler实例放入输入子系统handler链表中
input_register_handler函数顾名思义是向上注册输入子系统的解决方法(handler),有两种注册,一种是上文提到的将input_handler实例按次设备号为下标放入数组中,第二种是将evdev.c实现的input_handler实例放入一个输入子系统handler链表中。
list_add_tail(&handler->node, &input_handler_list);
6、input_register_handler函数遍历输入子系统device链表找寻handler对应的设备
子系统这边的结构类似与总线设备驱动模型,也是将驱动和设备分开,驱动在input_handler实例中实现,在调用input_register_handler函数注册input_handler实例时需要遍历输入子系统device链表,确定该input_handler能够处理哪些设备。也就是一个两两匹配的过程。
list_for_each_entry(dev, &input_dev_list, node)
input_attach_handler(dev, handler);
上面程序的作用是对input_dev_list中的每一个input_dev实例都执行input_attach_handler(dev, handler)函数,该函数将所注册的input_handler实例与每一个input_dev实例对比,判断两者是否匹配,函数原型为:
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
input_attach_handler函数中调用input_match_device函数:
id = input_match_device(handler->id_table, dev);
该函数比较input_handler实例的id和input_dev的id,从而判断是否匹配,如果匹配,调用input_handler实例的connect函数去建立设备和驱动之间的链接,每个input_handler实例的connect函数建立链接的方式都有所不同:
error = handler->connect(handler, dev, id);
问题:在实际操作中有很多具体的输入设备,每一个都需要insmod一次吗?感觉不现实(解答:热拔插)
(二)各个具体设备如何向上注册
(一)讲解了较为稳定的驱动程序如何注册,如evdev.c之类的文件是稳定的,包含在内核中。而注册各个具体的设备的文件一般是作者自己创建的。对于输入子系统其设备注册函数也在input.c中,函数原型为:
int input_register_device(struct input_dev *dev)
在这个函数中主要做两个工作,一是将input_dev实例加入到输入子系统device链表中:
list_add_tail(&dev->node, &input_dev_list);
二是遍历输入子系统handler链表,寻找所注册的设备实例能够被哪些驱动所支持。
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);
这部分代码基本和驱动注册时对照,如果设备和驱动匹配成功同样调用input_handler实例的connect函数。
也就是说,在注册设备或者驱动以后,如果有匹配的,都会调用到input_handler实例的connect函数,这部分是用户程序还没使用驱动之前就会完成的,用户程序使用驱动之前connect函数已经运行过了。
(三)双向注册完成后,具体如何connect
connect函数的具体实现在各个驱动的文件中,我们这里在evdev.c中,函数原型为:
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
1、声明并分配一个evdev结构体实例
在evdev.c的connect函数中,首先声明一个evdev结构体实例:
struct evdev *evdev;
结构体struct evdev的声明同样在evdev.c中:
struct evdev {
int exist;
int open;
int minor;
char name[16];
struct input_handle handle;//重点关注,注意与前面的handler相比没有r了
wait_queue_head_t wait;
struct evdev_client *grab;
struct list_head client_list;
};
随后再在connect函数中具体分配定义struct evdev *evdev:
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
2、设置evdev结构体实例中的evdev->handle
从上面我们知道struct evdev中包含struct input_handle handle成员,该结构体声明在input.h中:
struct input_handle {
void *private;
int open;
const char *name;
struct input_dev *dev;
struct input_handler *handler;
struct list_head d_node;
struct list_head h_node;
};
在connect函数中将已经注册的输入设备和驱动分别赋给struct input_handle的对应成员:
evdev->handle.dev = dev;
evdev->handle.handler = handler;
3、注册evdev结构体实例中的evdev->handle
同样在connect函数中,注册已经设置好的evdev->handle实例:
error = input_register_handle(&evdev->handle);
input_register_handle具体实现在input.c中:
int input_register_handle(struct input_handle *handle)
{
struct input_handler *handler = handle->handler;
list_add_tail(&handle->d_node, &handle->dev->h_list);
list_add_tail(&handle->h_node, &handler->h_list);
if (handler->start)
handler->start(handle);
return 0;
}
我们注册的设备input_dev现在是 &handle->dev,注册的input_handler是 &handle->handler,上面函数将所注册的设备和handler的h_list都指向input_handle实例,用c语言概括为:
input_handler->h_list= &input_handle;
input_dev->h_list= &input_handle;
也就是说,在connect函数运行完以后,我们可以直接通过input_handler->h_list或者input_dev->h_list找到对应的input_handle实例,再从input_handle实例提取出需要的input_dev和input_handler。input_handle实例相当于一个中间人。至此,注册和链接工作都已经完成。
(四)驱动和设备注册完成后,上层如何调用
1、所有输入设备的open调用都通过input的open函数来中转
所有的输入子系统open调用最终会调用到input.c中的open函数,在input.c中的入口函数static int __init input_init(void)中:
err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
该语句向上注册一个主设备号为INPUT_MAJOR(13)的字符设备程序,注意这里只注册了主设备号,暂时认为所有的输入子系统的字符设备共用一个主设备号,次设备号定义在各个具体驱动程序中(如本例的evdev)的input_handler结构体实例:evdev_handler中,然后通过input_register_handler向上注册。
input_fops为一个file_operations实例:
static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file,
};
注意这里只注册了一个open函数实例input_open_file。该函数同样在input.c中定义,原型为:
static int input_open_file(struct inode *inode, struct file *file)
用insmod将input.c中实现的驱动注册后,在应用程序调用open函数时(调用open时会传入参数struct inode inode, struct file file,这两个参数中有主次设备号的信息),会调用到input_open_file函数。由于各个具体的设备驱动程序已经在(一)**中通过次设备号将自身注册到了input_table中,在input_open_file函数中将input_table[iminor(inode) >> 5]得到具体设备的input_handler实例,然后通过fops_get得到input_handler实例中的file_operations实例,将该file_operations实例赋给new_fops,最后通过new_fops->open(inode, file)调用到具体驱动程序的file_operations实例中的具体的open函数。
struct input_handler *handler = input_table[iminor(inode) >> 5];
new_fops = fops_get(handler->fops));
err = new_fops->open(inode, file);
return err;
体会:上面这整套流程有两个点,一是在file_operations结构体的基础上进一步封装为input_handler结构体,个人目前猜想这么做的目的是为了实现Linux的总线-设备-驱动模型;二是将所有的输入子系统的open操作都通过input.c中的open函数来中转,个人猜想这样做体现了Linux驱动框架中分层的思想。具体可见《Linux设备驱动开发详解》第三版本P287。
2、open调用后,应用程序的各种调用最终会调用evdev.c的各种调用
open调用后应用程序知道了文件句柄,后续的read/write之类的调用就不用再通过input.c中转了,直接会调用到evdev.c中file_operations实例evdev_fops中的evdev_read和evdev_write等函数。这些函数主要是处理一些通用的事件,如缓冲区、事件的管理等。下一节具体讲解一下evdev_read函数。
3、如何读取按键值,evdev_read函数
应用空间调用read函数会调用到evdev_read函数,该函数的处理和我们之前写的简单的read函数类似,主要是读取环形缓冲区中的值。如果顺利读到值就调用evdev_event_to_user将值通过事件处理函数发送给用户:
if (evdev_event_to_user(buffer + retval, event))
return -EFAULT;
如果没读到且是非阻塞方式打开的,就直接返回:
if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK))
return -EAGAIN;
如果是阻塞方式,就进入休眠等待唤醒
retval = wait_event_interruptible(evdev->wait,
client->head != client->tail || !evdev->exist);
read如果通过wait_event_interruptible陷入休眠,由同样在evdev.c中的evdev_event唤醒。
4、evdev_event由谁调用
evdev_event函数是evdev.c下专门用于处理事件的函数,函数原型为:
static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
调用evdev_event函数的代码一般由驱动编写者处理,也就是我们具体需要做的部分,上面讲述的大部分都是Linux已经做好的,我们要做的就是比如在一个按键中断中上报“有按键按下”这一事件,随后就会调用evdev_event函数进行处理。归纳来说,基于子系统的驱动编写,我们需要做的就是在具体某个设备的文件中(比如中断服务程序中),确定所发生的事件是什么,然后调用相应的input_handler中的event处理函数。
Linux内核中有一个GPIO驱动的例子,linux-2.6.22.6\drivers\input\keyboard\gpio_keys.c,可以看下它的中断处理函数:
static irqreturn_t gpio_keys_isr(int irq, void *dev_id)
该中断处理函数里面调用input_event函数:
input_event(input, type, button->code, !!state);
input_sync(input);
该函数在input.c中实现,函数原型为:
/** * input_event() - report new input event * @dev: device that generated the event * @type: type of the event * @code: event code * @value: value of the event * * This function should be used by drivers implementing various input devices * See also input_inject_event() */
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
在input_event函数中:
list_for_each_entry(handle, &dev->h_list, d_node)
if (handle->open)
handle->handler->event(handle, type, code, value);
list_for_each_entry语句表示对dev->h_list链表中的每一个handle都执行下面的if (handle->open)语句,该语句表示如果handle中的open函数已经被调用,则执行对应的event函数。
(五)input子系统总结
1、设备和驱动的注册和链接
设备input_dev实例和驱动input_handler实例分别通过input_register_device和input_register_handler进行注册,input_register_handler注册的功能是将input_handler实例注册到input_table表中、将input_handler实例放入输入子系统handler链表中,input_register_device注册的功能是将input_dev实例加入到输入子系统device链表中;两者在注册的同时都会遍历链表以寻求匹配;匹配成功后会调用input_handler实例中的connect函数,该函数会创建并注册一个 input_handle实例,该结构体会包含注册的input_dev实例和input_handler实例,在connect函数运行完以后,我们可以直接通过input_handler->h_list或者input_dev->h_list找到对应的input_handle实例,再从input_handle实例提取出需要的input_dev和input_handler。input_handle实例相当于一个中间人。至此,注册和链接工作都已经完成。
2、对驱动的调用
对驱动的调用可以分为两个部分,一是用户程序通过open、read、write的主动调用,二是由具体设备的行为触发中断的被动调用。对于第一种调用,首先肯定要open,所有输入设备的open调用都通过input的open函数来中转,open以后会返回文件句柄,后续的read、write就会直接调用到对应文件的handler的函数。对于第二种调用,主要是写在具体设备文件的中断函数中,中断函数确定所发生的事件是什么,然后调用input_event函数,该函数会通过之前双向注册时用connect函数创建的handle结构体找到本设备对应的驱动中的input_handler中的event处理函数。
几个概念:kobject、kset、subsystem;device、device_deiver、bus_type;/sysfs、/dev
2.2 基于输入子系统编写驱动程序
视频写了一个基于输入子系统的按键驱动程序,我们只需要完成以下几个步骤:
1、建立一个设备相关的文件,比如buttons.c
2、编写驱动的init函数和exit函数,主要讲一下init函数,exit函数是相反的操作。在init函数中主要做以下几个步骤:
(1)分配一个input_dev结构体,也就是定义一个input_dev实例,该实例代表实际的设备
(2)设置该设备能够产生哪类事件,以及能产生这类事件里面的哪些操作。事件类和事件操作都是用一些已经定义了的宏表示。
(3)调用input_register_device函数将该设备向上注册
(4)ioremap,注册相关的中断函数
3、按键按下和松开会触发中断函数,在中断函数中调用input_event函数将事件上报。
自此我们所要编写的驱动程序就结束了。
问题: evdev中会对上报的事件进行具体的处理,可能会打印一些信息,这些信息去哪里看呢?具体写好以后实际运行大概是怎样个流程?去看看视频
3 分层分离驱动
(一)子系统和总线设备驱动模型之间的联系与区别
1、双向注册并配对
子系统以输入子系统为例,其表示输入设备的结构体为input_dev,表示输入驱动的结构体为input_handler,其设备注册函数为input_register_device,其驱动注册函数为input_register_handler,这两个函数分别将input_dev实例和input_handler实例加入到输入子系统device链表和输入子系统handler链表中,并且每次设备或者驱动注册加入对应链表后都会遍历链表寻求配对,遍历函数都为input_attach_handler,该函数内部又都调用input_match_device来配对。子系统的处理核心在于其对应的handler实例,比如输入子系统的input_handler实例,里面有事件的处理函数,有对应的open、write、read函数等,这些函数有对输入设备一般需要的输入缓冲区的处理,这些是输入子系统所特有的。
总线设备驱动模型同样需要双向注册并配对,下面用模型代指总线设备驱动模型。模型比子系统更底层一些,在模型之上实现各种子系统,模型本身并不包含像输入子系统那样处理输入缓冲区的功能,模型的实现依赖于更下一层的kobject和kset等。模型用bus_type、device、device_diver三个结构体来表示总线、设备和驱动。一般我们不需要再对总线进行初始化或者注册等,总线就用Linux本身自带的就行。各种设备是挂靠在各种总线上的,如pci等,没有挂靠的一般用虚拟平台总线platform。一般将device、device_diver进一步封装为对应总线的设备和驱动,比如封装为platform_device、platform_diver。platform_device和platform_diver分别通过platform_device_register和platform_driver_register进行注册,函数里面分别具体调用了device_register和driver_register进行注册。platform_diver和platform_device的注册会将该platform_diver和platform_device实例加入到platform总线所管理的驱动和设备链表中,并且每次设备或者驱动注册加入对应链表后都会遍历链表寻求配对,遍历函数都为bus_type结构体中的match函数成员。
2、配对以后建立链接
子系统以输入子系统为例,配对成功后会调用input_handler实例中的connect函数,具体connect操作见上文,注意connect操作基本上是Linux写好了的,是固定的,下面的probe函数是要自己写的。
总线设备驱动模型,简称模型,在配对成功后,会自动调用platform_diver实例中的probe函数。具体在probe函数中做什么的话,没有硬性要求,模型仅仅只是提供了这样一种机制,能够通过注册自动调用到probe函数。比如Linux内核中有一个GPIO驱动的例子gpio_keys.c,由于按键又属于输入子系统,所以在这个文件的probe函数中又进行了一些输入子系统相关的注册工作,从而将模型与子系统链接起来。
问题: gpio_keys.c中的probe函数具体怎么链接到输入子系统呢? 答:类似2.2,具体看宋宝华第三版302
实际上如果按照正常的先总线设备驱动模型再加子系统的话,我们在2.2中写的东西应该放到probe函数中。
问题: 既然我们在2.2中不用设备模型也能直接用子系统,干嘛还要用到probe函数呢,直接类似2.2不就行了?
答:有可能是子系统并不十全十美,比如点灯应该用哪个子系统?
(二)实际写一个符合总线设备驱动模型的程序
两个c文件,一个是设备文件,一个是驱动文件。
对于设备文件,存放和硬件相关的代码,在改动程序是只改动设备文件,不改动驱动文件。在设备文件的init函数中注册平台总线设备,表明该设备能使用的资源resource(关于资源的描述参考Linux设备驱动开发详解第三版295),定义该设备被释放时的release函数。对于设备的修改往往就是修改其所拥有的资源。
问题: 这里的设备跟输入子系统的设备有何区别,在2.2中我们也注册了输入子系统设备。
对于驱动文件,在init函数中注册平台驱动,驱动中主要实现probe和remove两个函数,另外单独建立files_operation实例,编写open和write函数,没有close函数。平台设备和平台驱动配对后首先运行probe函数,在该函数中获取平台设备的资源信息,然后ioremap,随后将该文件中实现的files_operation实例进行注册,最后通过mdev自动创建设备节点,probe函数所做工作和从前不加总线设备驱动模型时的init函数类似,probe函数函数运行完以后,驱动已经注册,设备对应的地址也已经映射完成。用户程序调用open和write函数就会自动调用对应的驱动。当驱动卸载时自动调用remove函数,功能和以前的close函数类似。
由于我们这里是led驱动,是点灯,不是输入,所以没必要在probe中再连接输入子系统。如果连接了某个子系统,我们就不需要自己写对应的files_operation实例,子系统的handler实例中已经写好了,我们需要在驱动文件的probe函数中写类似2.2节的内容,在设备文件中注册设备和资源。
4 LCD驱动
4.1 LCD驱动之层次分析
视频介绍:介绍了frame buffer子系统的基本框架,注意和input子系统区分开。
(一)层次介绍
LCD驱动归属于frame buffer子系统,整体框架也是和input子系统类似,先基于总线设备驱动模型,调用到对应的probe函数,在probe函数中链接到frame buffer子系统。
frame buffer子系统是Linux内核对frame buffer 设备的抽象,类似input子系统是对输入设备的抽象,frame buffer子系统主要代码在\linux-2.6.22\drivers\video\fbmem.c中。
在fbmem.c中的入口函数fbmem_init(void)中,通过:
register_chrdev(FB_MAJOR,"fb",&fb_fops)
向上注册一个file_operation结构体实例fb_fops,该实例也在fbmem.c中:
tatic const struct file_operations fb_fops = {
.owner = THIS_MODULE,
.read = fb_read,
.write = fb_write,
.ioctl = fb_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = fb_compat_ioctl,
#endif
.mmap = fb_mmap,
.open = fb_open,
.release = fb_release,
#ifdef HAVE_ARCH_FB_UNMAPPED_AREA
.get_unmapped_area = get_fb_unmapped_area,
#endif
#ifdef CONFIG_FB_DEFERRED_IO
.fsync = fb_deferred_io_fsync,
#endif
};
在该file_operation结构体实例fb_fops中实现了open、read等操作,这些函数内部进行具体操作的前提都需要:
struct inode *inode = file->f_path.dentry->d_inode;
int fbidx = iminor(inode);
struct fb_info *info = registered_fb[fbidx];
即通过对应设备的次设备号fbidx,索引到registered_fb数组中对应的struct fb_info结构,struct fb_info结构中记录了具体frame buffer设备的信息,所有对于frame buffer设备的操作都依赖于这个结构体进行展开,struct fb_info结构体声明在linux-2.6.22\include\linux\fb.h中,里面囊括了frame buffer设备的所有细节,一般我们需要关注的有:
struct fb_info {
int node;
int flags;
struct fb_var_screeninfo var; /* Current var */可变参数
struct fb_fix_screeninfo fix; /* Current fix */ 固定参数
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 */
};
其中固定参数有:
struct fb_fix_screeninfo {
char id[16]; /* identification string eg "TT Builtin" */名字,随便写
unsigned long smem_start; /* Start of frame buffer mem */frame buffr,也就是显存(内存中划分出来的一块区域)的首地址
/* (physical address) */注意这些都是物理地址
__u32 smem_len; /* Length of frame buffer mem */frame buffr长度,这个需要看LCD手册,分辨率(像素个数)乘以每个像素占据的位数
__u32 type; /* see FB_TYPE_* */LCD种类,去看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 reserved[3]; /* Reserved for future compatibility */
};
其中可变参数有:
struct fb_var_screeninfo {
__u32 xres; /* visible resolution */x方向分辨率
__u32 yres; y方向分辨率
__u32 xres_virtual; /* virtual resolution */虚拟X方向分辨率,其实屏幕分辨率在出厂时已经定死了,
但是我电脑桌面还是可以调整分辨率,调整的就是这个虚拟分辨率,我们不同这个
__u32 yres_virtual;
__u32 xoffset; /* offset from virtual to visible */
__u32 yoffset; /* resolution */
__u32 bits_per_pixel; /* guess what */每个像素多少位
__u32 grayscale; /* != 0 Graylevels instead of colors */
struct fb_bitfield red; /* bitfield in fb mem if true color, */在每个像素的颜色中,红色从哪位开始,哪位结束
struct fb_bitfield green; /* else only length is significant */
struct fb_bitfield blue;
struct fb_bitfield transp; /* transparency */
__u32 nonstd; /* != 0 Non standard pixel format */
__u32 activate; /* see FB_ACTIVATE_* */
__u32 height; /* height of picture in mm */
__u32 width; /* width of picture in mm */
__u32 accel_flags; /* (OBSOLETE) see fb_info.flags */
/* 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 sync; /* see FB_SYNC_* */
__u32 vmode; /* see FB_VMODE_* */
__u32 rotate; /* angle we rotate counter clockwise */
__u32 reserved[5]; /* Reserved for future compatibility */
};
所有需要用到frame buffer设备驱动的设备,在各自的xxxfb.c文件中会通过
register_framebuffer(struct fb_info *fb_info)
函数将自身的frame buffer设备对应的struct fb_info结构体以次设备号为索引注册到registered_fb[fbidx]数组中,register_framebuffer的具体实现在fbmem.c中。
(二)我们需要做什么
我们需要做的就是编写自己的xxxfb.c文件,Linux内核中有许多现成的例子,比如\linux-2.6.22\drivers\video\s3c2410fb.c,可以看看例子怎么做的。在该文件的init函数中:
static struct platform_driver s3c2410fb_driver = {
.probe = s3c2410fb_probe,
.remove = s3c2410fb_remove,
.suspend = s3c2410fb_suspend,
.resume = s3c2410fb_resume,
.driver = {
.name = "s3c2410-lcd",
.owner = THIS_MODULE,
},
};
int __devinit s3c2410fb_init(void)
{
return platform_driver_register(&s3c2410fb_driver);
}
依照总线设备驱动模型,注册了一个平台总线驱动s3c2410fb_driver,里面实现了probe等函数。
4.2 LCD驱动程序之硬件操作
视频介绍:重新简单讲了一下LCD的硬件原理,不喜欢看第一期的视频可以通过这个视频看个大概。
4.3-1 LCD驱动程序之编写代码之初步编写
视频介绍:讲解我们需要做什么,讲了struct fb_info 中固定参数、可变参数、操作函数的具体意义。我们要做的就是编写xxxfb.c文件。
主要操作都在init函数中(实际正规应该用总线设备模型,放在probe函数中):
1、分配一个fb_info结构体
2、设置
2.1 根据实际的设备,设置fb_info结构体中的固定参数、可变参数
2.2 设置操作函数(三个通用函数)
2.3 其他设置
3、硬件相关的操作
3.1 设置GPIO用于LCD
3.2 根据LCD手册设置LCD控制器, 比如VCLK的频率等
3.3 分配显存(framebuffer), 并把地址告诉LCD控制器
4、以上操作丰富了fb_info结构体,调用register_framebuffer将fb_info结构体向上注册,这里注册会提交次设备号,以后应用时,用户空间传递次设备号给内核,Linux内核通过次设备号调用到这里注册的fb_info结构体。
4.3-2 LCD驱动程序之编写代码之硬件设置
4.3 LCD驱动程序之编写代码之显存和调色板设置
4.4 LCD驱动程序之编写代码之编译测试
17:30介绍了内核自带s3c2410fb.c应用总线设备驱动模型之后的框架,韦写的这个是不带驱动模型的。
5 触摸屏驱动
5.1 触摸屏驱动之概念介绍
前面简单介绍,3min后开始写代码,参考Linux自带的s3c2410的触摸屏驱动,s3c2410_ts.c,但是代码并未用总线设备驱动模型,而是类似2.2,直接根据输入子系统来写。
5.2 触摸屏驱动之编写驱动
没看,和不带输入子系统的区别就是结构类似2.2,然后在中断中的处理变成了识别事件并上报事件。注意一个点就是在输入子系统中事件上报后处理的结果在哪里看(测试方法),我们之前是直接打印出触摸屏点的位置,链接输入子系统后可以通过hexdump看事件的输出信息,具体操作在2.2,5.3有具体触摸屏的应用。
5.3 触摸屏驱动之使用TSLIB测试
9min之前,包括5.2,只是实现了点击触摸屏能够输出电压信息,还没和LCD联系起来。9min后开始讲解用TSLIB将触摸屏和LCD联系起来,第一期触摸屏部分有具体讲怎么造轮子(五点校正),这里直接讲怎么调用TSLIB库。
边栏推荐
猜你喜欢
玩轉Sonar
80% of the people answered incorrectly. Does the leaf on the apple logo face left or right?
When creating body middleware, express Is there any difference between setting extended to true and false in urlencoded?
智慧监管入场,美团等互联网服务平台何去何从
[programming problem] [scratch Level 2] draw ten squares in December 2019
Robomaster visual tutorial (1) camera
The result of innovation in professional courses such as robotics (Automation)
面试题详解:用Redis实现分布式锁的血泪史
【史上最详细】信贷中逾期天数统计说明
The standby database has been delayed. Check that the MRP is wait_ for_ Log, apply after restarting MRP_ Log but wait again later_ for_ log
随机推荐
"An excellent programmer is worth five ordinary programmers", and the gap lies in these seven key points
How to learn a new technology (programming language)
Binder核心API
How does the markdown editor of CSDN input mathematical formulas--- Latex syntax summary
深潜Kotlin协程(二十三 完结篇):SharedFlow 和 StateFlow
【推荐系统基础】正负样本采样和构造
How does starfish OS enable the value of SFO in the fourth phase of SFO destruction?
Smart regulation enters the market, where will meituan and other Internet service platforms go
Notice on organizing the second round of the Southwest Division (Sichuan) of the 2021-2022 National Youth electronic information intelligent innovation competition
[programming problem] [scratch Level 2] December 2019 flying birds
Teach you to make a custom form label by hand
【編程題】【Scratch二級】2019.12 飛翔的小鳥
去了字节跳动,才知道年薪 40w 的测试工程师有这么多?
How to put recyclerview in nestedscrollview- How to put RecyclerView inside NestedScrollView?
The difference between -s and -d when downloading packages using NPM
How to insert highlighted code blocks in WPS and word
Sqlite数据库存储目录结构邻接表的实现2-目录树的构建
QT creator add custom new file / Project Template Wizard
The standby database has been delayed. Check that the MRP is wait_ for_ Log, apply after restarting MRP_ Log but wait again later_ for_ log
Fully automated processing of monthly card shortage data and output of card shortage personnel information