当前位置:网站首页>韦东山第二期课程内容概要

韦东山第二期课程内容概要

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库。

原网站

版权声明
本文为[拾柒47]所创,转载请带上原文链接,感谢
https://blog.csdn.net/weixin_42403122/article/details/124714673

随机推荐