驱动框架流程

(一)init函数

i2c_add_driver 调用 i2c_register_driver驱动注册

i2c_register_driver 初始化驱动,将驱动添加到i2c驱动中,再调用driver_register

driver_register检查驱动在总线上是否注册过,若未注册,则调用bus_add_driver 在总线上添加驱动

bus_add_driver将驱动加到总线上(通过klist),调用driver_attach

driver_attach返回函数bus_for_each_dev

bus_for_each_dev遍历总线上每个设备,并调用__driver_attach函数

__driver_attach函数调用driver_match_device函数匹配设备与驱动

driver_match_device函数返回drv->bus->match ? drv->bus->match(dev, drv) 调用bus的match函数匹配,若匹配上(通过.compatible),则调用driver_probe_device进行设备初始化

driver_probe_device调用really_probe进行初始化

really_probe调用driver->probe()(如果bus->probe非空,则调用bus->probe)

 

(二)Probe过程

mxt_get_platform_data(client)获取平台数据

input_allocate_device()函数在内存中为输入设备结构体分配一个空间,并对其主要的成员进行了初始化.

再对输入设备及数据赋值

__set_bit(EV_ABS, input_dev->evbit);//绝对坐标事件,触摸屏每次发送的坐标都是绝对坐标,不同于鼠标的相对坐标

__set_bit(EV_KEY, input_dev->evbit);//按键事件,每次触摸都有一个BTN_TOUCH的按键事件

__set_bit(BTN_TOUCH, input_dev->keybit);//touch类型按键

__set_bit(INPUT_PROP_DIRECT, input_dev->propbit);

error = input_mt_init_slots(input_dev, MXT_MAX_FINGER, 0);//报告最大支持的点数

input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR,

  0, MXT_MAX_AREA, 0, 0);

//将触摸点看成一个椭圆,它的长轴长度

input_set_abs_params(input_dev, ABS_MT_POSITION_X,

  0, MXT_DISS_MAX_X, 0, 0);//x坐标取值范围

input_set_abs_params(input_dev, ABS_MT_POSITION_Y,

  0, MXT_DISS_MAX_Y, 0, 0); //y坐标取值范围

input_set_abs_params(input_dev, ABS_MT_PRESSURE,

  0, 255, 0, 0);

input_set_drvdata(input_dev, data);//保存data结构到input_dev里面

i2c_set_clientdata(client, data);//保存数据

error = mxt_configure_gpio(data,true);

//配置gpio,具体如下:

/*

gpio_request(data->pdata->reset_gpio, "mxt_reset_gpio")

//申请一个GPIO为reset

gpio_direction_output(data->pdata->reset_gpio, 0)

//设置reset_GPIO为输出,初始值为0

gpio_request(data->pdata->pow_en_gpio,"mxt_pow_en_gpio")

//申请一个GPIO为pow_en;

gpio_direction_output(data->pdata->pow_en_gpio, 1)

//设置pow_en为输出,初始值为1

msleep(1)

//延时1ms,休眠函数,是将当前调用线程挂起一段时间,不占用cpu资源。

gpio_set_value(data->pdata->reset_gpio, 1)

//将reset_GPIO设置为1(拉高)

msleep(100)//延时100ms

*/

error = mxt_configure_irq_gpio(data);//配置中断,具体如下:

/*

gpio_request(data->pdata->irq_gpio,"mxt_irq_gpio")

//申请一个GPIO为irq

gpio_direction_input(data->pdata->irq_gpio)

//设置irq_GPIO为输入

*/

error = input_register_device(input_dev);

//注册一个input设备

/*

__set_bit(EV_SYN, dev->evbit);

/*  事件类型由input_dev的evbit成员来表示,

在这里将其EV_SYN置位,表示设备支持所有的事件。

注意,一个设备可以支持一种或者多种事件类型

#define EV_SYN              0x00    //表示设备支持所有的事件

#define EV_KEY              0x01    //键盘或者按键,表示一个键码

#define EV_REL              0x02    //鼠标设备,表示一个相对的光标位置结果  

#define EV_ABS              0x03    //手写板产生的值,其是一个绝对整数值  

#define EV_MSC              0x04    //其他类型  

#define EV_LED              0x11    //LED灯设备

#define EV_SND              0x12    //蜂鸣器,输入声音  

#define EV_REP              0x14    //允许重复按键类型  

#define EV_PWR              0x16    //电源管理事件

*/

if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD])

        input_enable_softrepeat(dev, 250, 33);

/*  

 * rep主要是处理重复按键,如果没有定义dev->rep[REP_DELAY]和 dev->rep[REP_PERIOD],

 * 则将其赋值为默认值。dev->rep[REP_DELAY]是指第一次按下多久算一次,这里是250ms,

 * dev->rep[REP_PERIOD]指如果按键没有被抬起,每33ms算一次。

*/

if (!dev->getkeycode)

        dev->getkeycode = input_default_getkeycode;

 if (!dev->setkeycode)

        dev->setkeycode = input_default_setkeycode;

/*

 * 如果dev没有定义getkeycode和setkeycode,则赋默认值。

 * 他们的作用一个是获得键的扫描码,一个是设置键的扫描码

*/

list_add_tail(&dev->node, &input_dev_list);

//将新分配的input设备连接到input_dev_list链表上    

list_for_each_entry(handler, &input_handler_list, node)

//宏for循环

        input_attach_handler(dev, handler);

/*

 * input_attach_handler的主要功能就是调用了两个函数,一个input_match_device进行配对,一个connect处理配对成功后续工作

 * input_match_device通过handler->id_table(.driver_info = 1匹配所有设备)

 * connect调用input_register_handle将d_node挂到input_device的h_list,h_node挂到handler的h_list来连接input_dev、input_handler与input_handl)

*/

//遍历input_handler_list链表,配对 input_dev 和 input_handler   

/*

 * 这里先把input_dev挂载到input_dev_list 链表上,然后对每个挂载到input_handler_list 的handler

 * 调用input_attach_handler(dev, handler); 去匹配。

 * 所有的input_dev挂载到input_dev_list 链表上

 * 所有的handler挂载到input_handler_list 上

*/

/*

注册input device的过程就是为input device设置默认值,并将其挂在 input_dev_list上与挂在input_handler_list中的handler相匹配。如果匹配成功就会调用handler的connnect函数

*/

*/

snprintf(data->phys,sizeof(data->phys),"i2c-%u-%04x/input0", client->adapter->nr, client->addr);

//设将可变参数client->adapter->nr, client->addr按照 i2c-%u-%04x/input0格式化成字符串,并将字符串复制到 data->phys中,sizeof(data->phys)为要写入的字符的最大数目,超过会被截断。

init_completion(&data->chg_completion);

init_completion(&data->reset_completion);

init_completion(&data->crc_completion);

init_completion(&data->test_completion);//初始化completion

mutex_init(&data->debug_msg_lock);//初始化互斥体

irq_of_parse_and_map(client->dev.of_node, 0); //在设备树里查找中断的描述项,然后返回中断号。第二个参数是0,表示使用设备树中的第一个中断。

mxt_acquire_irq(data);//获取中断

mxt_acquire_irq

mxt_process_messages_until_invalid

mxt_read_and_process_messages           <<---

mxt_proc_message  

mxt_input_button  <--------

input_report_key

mxt_proc_t15_messages     <--------

input_event

input_sync

input_sync  <<---

disable_irq(data->irq);//屏蔽中断

mxt_probe_regulators(data);//初始化电源设备,具体如下:

/*

data->reg_vdd = regulator_get(dev, "vdd");

data->reg_avdd = regulator_get(dev, "avdd");

//获取设备regulator

mxt_regulator_enable(data);//使能电源输出。

*/

mxt_initialize(data);

//初始化设备:1.读取基本信息 2.检测bootloader基本状态 3.申请中断 4.检测是否需要更新固件

sysfs_create_group(&client->dev.kobj,             &mxt_fw_attr_group); //创建sys文件节点

mxt_sysfs_init(data); //初始化

mxt_debug_msg_init(data);

msm_drm_register_client(&data->mxt_fb_notifier);

mxt_self_test(data);

create_workqueue("mxt_queue");

INIT_WORK(&mxt_init, mxt_initialize_work);

(二)事件上报流程

 1. input.c

__init input_init(void)//input子系统的初始化函数

在input_init函数中,先注册了一个名为input_class的类,所有input device都属于这个类

在sysfs中表现就是:所有input device所代表的目录都位于/dev/class/input下面

然后调用input_proc_init()在/proc下面建立相关的交互文件

再调用register_chrdev()注册了主设备号为INPUT_MAJOR(13),次设备号为0~255的字符设备

2. 事件处理层 evdev.c (与上层交互)

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,
    .llseek     = no_llseek,
};

(1)evdev_open, 上层打开设备文件时调用

1. 分配并初始化一个client结构体,并将它和evdev关联起来

关联的内容是,将client->evdev指向它所表示的evdev,调用evdev_attach_client()将client挂到evdev->client_list上

驱动层上报的输入事件的键值,就是存放在evdev->buffer

2. 调用evdev_open_device()函数,打开输入设备使设备准备好接收或者发送数据

最终是通过调用核心层input.c中的input_open_device函数实现打开设备

(2)evdev_read

1. 调用input_event_to_user,在函数中调用copy_to_user上报键值

input_event_to_user

        copy_to_user

                __chk_user_ptr//检查buffer内存

                        volatile_memcpy//将event事件保存在buffer里

2. wait_event_interruptible等待队列等待事件,没有事件就阻塞

阻塞事件(以TP为例):触摸按键事件                                

(事件处理层对上层的读操作,用一个等待队列实现阻塞,这样就能保证,我们只有在触摸按键事件发生,中断到来,我们才去上报按键事件,并唤醒阻塞,让事件处理层的evdev_read将键值最终通过copy_to_user送到用户空间

evdev_read函数中多次调用input_event_size函数进行条件判断它判断缓存区大小是否足够,在读取数据的情况下,可能当前缓存区内没有数据可读,在这里先睡眠等待缓存区中有数据,如果在睡眠的时候,条件满足,是不会进行睡眠状态而直接返回的,然后根据read()提供的缓存区大小,将client中的数据(即上报的键值)写入到用户空间的缓存区中

 

驱动中

cyttsp6_setup_irq_gpio
    request_threaded_irq(cd->irq, cyttsp6_hard_irq, cyttsp6_irq, irq_flags, dev_name(dev), cd);

cyttsp6_irq
    call_atten_cb
        atten->func(atten->dev);(cyttsp6_mt_attention)
        /*
        * 通过最初cyttsp6_mt_open函数调用
        * cyttsp6_subscribe_attention_函数设置触摸call back线程,
        * 将	atten->func设置为cyttsp6_mt_attention
        */    
            cyttsp6_xy_worker
                cyttsp6_get_mt_touches
                    cyttsp6_report_event
                        input_report_abs
                cyttsp6_mt_lift_all
                    input_sync

1. input_event与events

驱动代码中,上报键值时调用

input_sync                                                    

input_report_key

两者都调用了input_event函数

input_event
    input_handle_event
        input_pass_values
            input_to_handler
                handler->event
                //evdev_handler的成员函数evdev_event
                    evdev_events
                handler->events
                //evdev_handler的成员函数evdev_events
                    evdev_pass_values
                        wake_up_interruptible//唤醒睡眠

总结:在evdev_read函数中通过等待队列实现了阻塞,等待输入事件的随机发生

而在触摸按键事件随机发生后,设备驱动层代码通过input_report_key等函数上报数据,逐级调用,通过在事件处理层evdev.c里定义的evdev_handler->events处理事件,最终调用到evdev_pass_values并唤醒睡眠

一旦在evdev_read中的等待队列被唤醒,则进入下一轮循环上报新的键值给用户空间

 

(三)event匹配

在evdev_connect中,在input_register_handle连接dev、handle和handler之后,cdev_init连接cdev与fops接口函数,

(kernel/msm-4.14/fs/char_dev.c:651)
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
	memset(cdev, 0, sizeof *cdev);
	INIT_LIST_HEAD(&cdev->list);
	kobject_init(&cdev->kobj, &ktype_cdev_default);
	cdev->ops = fops;
}

cdev_device_add通过cdev_add将dev与cdev进行关联,

int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
	int error;

	p->dev = dev;
	p->count = count;

	error = kobj_map(cdev_map, dev, count, NULL,
			 exact_match, exact_lock, p);
	if (error)
		return error;

	kobject_get(p->kobj.parent);

	return 0;
}

 
定义一个 cdev 结构,然后定义一个 file_operations 结构,然后把这两个结构关联到一起。当然,还要把这个cdev 关联到 dev 设备号上。

  • 22
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值