DAY10-输入子系统原代码分析和i2c驱动

252 阅读10分钟

2.input接口层(input_handler)

input接口层可以手动实现,也可以使用内核中提供的标准框架evdev,evdev的源代码位置 ---------- drivers/input/evdev.c

(1)module_init(evdev_init);//模块加载函数,内核启动时调用

static int __init evdev_init(void) 
​
{  
​
 //向内核注册input_handler结构 return input_register_handler(&evdev_handler);
​
 }              

(2)input_handler中的内容

static struct input_handler evdev_handler = {
    .event      = evdev_event,//调用input_event函数时调用的函数
    .connect    = evdev_connect,//匹配成功调用的函数
    .disconnect = evdev_disconnect,
    .fops       = &evdev_fops,//操作函数集合
    .minor      = EVDEV_MINOR_BASE,
    .name       = "evdev",
    .id_table   = evdev_ids,
};
//未定义匹配(match)函数,可以和任何input_dev匹配    

(3)注册input_handler的动作

input_register_handler函数内容:
    //根据次设备号在input_table数组中找到位置放入注册的input_handler结构
    if (handler->fops != NULL) {
        if (input_table[handler->minor >> 5]) {
            retval = -EBUSY;
            goto out;
        }
        input_table[handler->minor >> 5] = handler;
    }  
   //将input_handler(node)插入到input_handler_list链表中
   list_add_tail(&handler->node, &input_handler_list);
   //遍历input_dev_list,取出每一项和注册的input_handler匹配
    list_for_each_entry(dev, &input_dev_list, node)
        input_attach_handler(dev, handler); 

(4)匹配函数 ---------------- input_attach_handler

//匹配
id = input_match_device(handler, dev);
if (!id)
    return -ENODEV;
//匹配成功调用input_handler的connect函数    

(5)匹配成功调用 --------------- connect函数

evdev_connect函数内容:
    //声明evdev指针并分配空间
    struct evdev *evdev;
    evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
    if (!evdev)
        return -ENOMEM;
  
    //根据次设备号设置设备文件名 ----- event* 
    dev_set_name(&evdev->dev, "event%d", minor);
    
    //将自身,input_dev,input_handler,设备文件名存入evdev中的input_handle结构
    evdev->handle.dev = input_get_device(dev);
    evdev->handle.name = dev_name(&evdev->dev);
    evdev->handle.handler = handler;
    evdev->handle.private = evdev; 
    
    //将input_handle(d_node)加入到input_dev的h_list链表中
    //将input_handle(h_node)加入到input_handler的h_list链表中
    error = input_register_handle(&evdev->handle);
    if (error)
        goto err_free_evdev;
  
    //将evdev结构放入evdev_table数组中
    error = evdev_install_chrdev(evdev);
    if (error)
        goto err_unregister_handle;
  
    //创建设备文件
    error = device_add(&evdev->dev);
    if (error)
        goto err_cleanup_evdev;

(6)打开输入子系统设备文件的接口 --------------------- evdev_open

evdev_open函数内容:
    struct evdev *evdev;
    struct evdev_client *client;//内核input_event数组 ----- buffer
 
    //根据次设备号从evdev_table数组中取出一项
    int i = iminor(inode) - EVDEV_MINOR_BASE;
    evdev = evdev_table[i];
    
     //为evdev_client分配空间
    client = kzalloc(sizeof(struct evdev_client) +
                bufsize * sizeof(struct input_event),
             GFP_KERNEL);
    if (!client) {
        error = -ENOMEM;
        goto err_put_evdev;
    }
   
   //将evdev放入evdev_client中
   client->evdev = evdev;
   //evdev_client存入file结构中
   file->private_data = client;evdev_open函数内容:  
​
  struct evdev *evdev; 
​
struct evdev_client *client;//内核input_event数组 ----- buffer   
​
  //根据次设备号从evdev_table数组中取出一项    
​
int i = iminor(inode) - EVDEV_MINOR_BASE;  
​
  evdev = evdev_table[i];        
​
 //为evdev_client分配空间    
​
client = kzalloc(sizeof(struct evdev_client) +          
​
            bufsize * sizeof(struct input_event),       
​
 GFP_KERNEL); 
​
if (!client) {  
​
    error = -ENOMEM;    
​
    goto err_put_evdev;
​
 }      
​
//将evdev放入evdev_client中   
​
client->evdev = evdev; 
​
  //evdev_client存入file结构中 
​
  file->private_data = client;              

(7)读输入子系统设备文件的接口 ---------------- evdev_read

evdev_read函数内容:
    //从file结构中取出evdev_client
    struct evdev_client *client = file->private_data;
    //从evdev_client中取出evdev
    struct evdev *evdev = client->evdev;
    struct input_event event;
 
    //在阻塞模式下如果没有事件上报就进入睡眠
    if (!(file->f_flags & O_NONBLOCK)) {
        retval = wait_event_interruptible(evdev->wait,
                client->packet_head != client->tail ||
                !evdev->exist);
        if (retval)
            return retval;
    }
 
   //从evdev_client的input_event数组中取出每一个input_event上报到用户空间
   while (retval + input_event_size() <= count &&
           evdev_fetch_next_event(client, &event)) {
​
        if (input_event_to_user(buffer + retval, &event))
            return -EFAULT;
​
        retval += input_event_size();
    }

疑问?evdev_client中的input_event数组buffer在哪里填充的,等待队列在哪里唤醒的? ----------- input_event函数。

(8)上报事件的函数 ----------------- input_event

  input_pass_event函数内容:
    struct input_handler *handler;
    struct input_handle *handle;
     
    //遍历input_dev的h_list链表,取出其中的input_handle 
    list_for_each_entry_rcu(handle, &dev->h_list, d_node) {
        if (!handle->open)
            continue;
         
        //通过input_handle找到input_handler 
        handler = handle->handler;
        if (!handler->filter) {
            if (filtered)
                break;
            //调用input_handler的event函数
            handler->event(handle, type, code, value);
        
        } else if (handler->filter(handle, type, code, value))
            filtered = true;
    }

(9)evdev_event函数

      evdev_event函数内容:
    struct evdev *evdev = handle->private;
	struct evdev_client *client;
	struct input_event event;
 
    event.type = type;
	event.code = code;
	event.value = value;
    
   //将准备好的input_event结构填充到evdev_client的buffer数组中
   if (client)
		evdev_pass_event(client, &event, time_mono, time_real);
  
   //如果是同步事件,唤醒等待的读进程
   if (type == EV_SYN && code == SYN_REPORT) {
		evdev->hw_ts_sec = -1;
		evdev->hw_ts_nsec = -1;
		wake_up_interruptible(&evdev->wait);
	}

3.input设备驱动层(input_dev)

input_register_device函数内容:
    //将input_dev(node)加入到input_dev_list链表
    list_add_tail(&dev->node, &input_dev_list);
    //遍历input_handler_list链表,取出每一项input_handler和注册的input_dev匹配
	list_for_each_entry(handler, &input_handler_list, node)
		input_attach_handler(dev, handler);//匹配成功调用input_handler的connect函数

4.Linux内核输入子系统框图

image-20220802001004544

十九.内核IIC驱动

1.IIC通信协议

(1)IIC连接使用两根线,数据线SDA和时钟线SCL,接线上连接上拉电阻,默认高电平(空闲)

(2)IIC属于一主多从的通信方式,通信由主设备发起,从设备被动响应。每个从设备有自己的从设备地址,用于区分不同的从设备。

(3)通信过程

1.主设备发送起始信号
2.主设备放从设备地址和读写信号
3.对应的从设备发送应答信号(ACK)
4.发送/接收数据 --------- 单字节
5.接收方应答
6.不再通信主设备发送停止信号(stop)

2.s5p6818芯片的IIC实现

s5p6818内部集成了IIC控制器,IIC传输数据所需的时序要求,都可以通过控制器来实现。控制器发起的时序,有些事固定的,有些事可变的。

固定的:起始信号,停止信号,应答信号,读/写信号

可变的:从设备地址,读写的数据

可变的数据来自于从设备(从设备说明手册),这些数据通过硬件寄存器提供给控制器,控制器就可以开始相应的传输。

3.Linux内核中IIC驱动框架

内核中IIC驱动分为2个部分 ------------ IIC总线驱动和IIC设备驱动

(1)IIC总线驱动

管理的硬件时IIC控制器,关注的是如何进行数据传输,不关注具体的含义。总线驱动通常由芯片厂家实现,开发者只需要在内核中配置选择即可。

Device Drivers  ---> 
    <*> I2C support  --->
        I2C Hardware Bus support  --->

(2)IIC设备驱动

管理的对象时从设备,关注的是从设备地址和从设备传输数据的具体含义,不关注如何进行数据传输。传输依赖于IIC总线驱动,开发者只需要调用总线驱动提供的传输接口即可

image-20220802001011702

4.IIC设备驱动的实现使用 设备-总线-驱动模型

(1)内核实现了一条IIC虚拟总线 --------------- i2c_bus_type

   ```c
   struct bus_type i2c_bus_type = {
   	.name		= "i2c",
   	.match		= i2c_device_match,//匹配函数(名字匹配)
   	.probe		= i2c_device_probe,//匹配成功自动调用的函数
   	.remove		= i2c_device_remove,//解除匹配自动调用的函数
   	.shutdown	= i2c_device_shutdown,
   	.pm		= &i2c_device_pm_ops,
   };
   ```

(2)总线上挂着两条链表 ----------------- dev链表和drv链表

dev链表节点保存硬件信息,对应的数据类型 struct i2c_client

struct i2c_client {
	unsigned short flags;		/* div., see below		*/
	unsigned short addr;		/* chip address - NOTE: 7bit	*/
					/* addresses are stored in the	*/
					/* _LOWER_ 7 bits		*/
	char name[I2C_NAME_SIZE];
	struct i2c_adapter *adapter;	/* the adapter we sit on	*/
	struct i2c_driver *driver;	/* and our access routines	*/
	struct device dev;		/* the device structure		*/
	int irq;			/* irq issued by device		*/
	struct list_head detected;
};

drv链表节点保存软件信息,对应的数据类型 struct i2c_driver

```c
struct i2c_driver {
	unsigned int class;

	/* Notifies the driver that a new bus has appeared or is about to be
	 * removed. You should avoid using this, it will be removed in a
	 * near future.
	 */
	int (*attach_adapter)(struct i2c_adapter *) __deprecated;
	int (*detach_adapter)(struct i2c_adapter *) __deprecated;

	/* Standard driver model interfaces */
	int (*probe)(struct i2c_client *, const struct i2c_device_id *);
	int (*remove)(struct i2c_client *);

	/* driver model interfaces that don't relate to enumeration  */
	void (*shutdown)(struct i2c_client *);
	int (*suspend)(struct i2c_client *, pm_message_t mesg);
	int (*resume)(struct i2c_client *);

	/* Alert callback, for example for the SMBus alert protocol.
	 * The format and meaning of the data value depends on the protocol.
	 * For the SMBus alert protocol, there is a single bit of data passed
	 * as the alert response's low bit ("event flag").
	 */
	void (*alert)(struct i2c_client *, unsigned int data);

	/* a ioctl like command that can be used to perform specific functions
	 * with the device.
	 */
	int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);

	struct device_driver driver;
	const struct i2c_device_id *id_table;

	/* Device detection callback for automatic device creation */
	int (*detect)(struct i2c_client *, struct i2c_board_info *);
	const unsigned short *address_list;
	struct list_head clients;
};
```

当往其中一条链表添加节点时,就会遍历另外一条链表,取出每一个节点和新加入的节点匹配(i2c_client的name和i2c_driver中的id_table),匹配成功调用i2c_driver的probe函数,并且将i2c_client 的地址传递给probe函数。

5.如何向内核添加i2c_driver

需要包含的头文件:

#include <linux/i2c.h>         

(1)分配初始化i2c_driver

struct i2c_driver xxx_drv = {
        .id_table = 名字数组,
        .probe = xxx_probe,//在probe函数中实现字符设备驱动
        .remove = xxx_remove,
};         

(2)调用i2c_add_driver向内核添加i2c_driver

#define i2c_add_driver(driver) \
	i2c_register_driver(THIS_MODULE, driver)
 
int i2c_register_driver(struct module *owner, struct i2c_driver *driver);

//从内核删除i2c_driver
void i2c_del_driver(struct i2c_driver *driver);  

6.如何向内核添加i2c_client

i2c_client的注册都是由内核完成,开发者只需要对i2c_board_info进行分配初始化,内核会根据i2c_board_info的内容来填充i2c_client。

(1)分配初始化i2c_board_info

struct i2c_board_info xxx_info = {
    .type = xxx,//匹配的名称
    .addr = xxx,//从设备地址    
};

//可以使用以下宏来构造
#define I2C_BOARD_INFO(dev_type, dev_addr) \
	.type = dev_type, .addr = (dev_addr)

(2)将i2c_board_info添加到内核 ---------------- i2c_register_board_info

int i2c_register_board_info(int busnum,struct i2c_board_info const *info, unsigned len);
参数:
    busnum - 总线编号
    info - i2c_board_info数组首地址
    len - 数组长度

二十.使用内核IIC驱动框架实现at24c02系列的eeprom驱动

at24系列是一个eeprom,是一种电擦除的存储芯片,掉电不丢失,使用IIC接口。

1.原理图

image-20220802001025738

image-20220802001029860

at24c02芯片三个地址引脚A0 A1 A2都接地,写保护WP接地(关闭),IIC接口连接到了CPU的第二路IIC。

2.at24c02芯片手册

image-20220802001034562

从设备地址:1010000 ===> 0x50

字节写操作:

发送起始信号,发送从设备地址+写信号,等待ACK,发送写的片内地址,等待ACK,发送写的数据,等待ACK,发送停止信号。

image-20220802001039326

字节读操作:

发送起始信号,发送从设备地址+写信号,等待ACK,发送读的片内地址,等待ACK;发送起始信号,发送从设备地址+读信号,等待ACK,接收读的数据,发送NO ACK,发送停止信号。

image-20220802001056512

3.去掉内核中的at24c02驱动

(1)i2c_client在 arch/arm/plat-s5p6818/GEC6818/device.c

static struct i2c_board_info __initdata at24c02_i2c_bdi = {
	 I2C_BOARD_INFO("24c04", 0x50),
	.platform_data = &at24c04,		
};

i2c_register_board_info(AT24C02_I2C_BUS, &at24c02_i2c_bdi, 1);
//可以保留不修改

(2)i2c_driver在 drivers/misc/eeprom/at24.c

        ```c
        static const struct i2c_device_id at24_ids[] = {
        	/* needs 8 addresses as A0-A2 are ignored */
        	{ "24c00", AT24_DEVICE_MAGIC(128 / 8, AT24_FLAG_TAKE8ADDR) },
        	/* old variants can't be handled with this generic entry! */
        	{ "24c01", AT24_DEVICE_MAGIC(1024 / 8, 0) },
        	{ "24c02", AT24_DEVICE_MAGIC(2048 / 8, 0) },
        	/* spd is a 24c02 in memory DIMMs */
        	{ "spd", AT24_DEVICE_MAGIC(2048 / 8,
        		AT24_FLAG_READONLY | AT24_FLAG_IRUGO) },
        	{ "24c04", AT24_DEVICE_MAGIC(4096 / 8, 0) },
        	/* 24rf08 quirk is handled at i2c-core */
        	{ "24c08", AT24_DEVICE_MAGIC(8192 / 8, 0) },
        	{ "24c16", AT24_DEVICE_MAGIC(16384 / 8, 0) },
        	{ "24c32", AT24_DEVICE_MAGIC(32768 / 8, AT24_FLAG_ADDR16) },
        	{ "24c64", AT24_DEVICE_MAGIC(65536 / 8, AT24_FLAG_ADDR16) },
        	{ "24c128", AT24_DEVICE_MAGIC(131072 / 8, AT24_FLAG_ADDR16) },
        	{ "24c256", AT24_DEVICE_MAGIC(262144 / 8, AT24_FLAG_ADDR16) },
        	{ "24c512", AT24_DEVICE_MAGIC(524288 / 8, AT24_FLAG_ADDR16) },
        	{ "24c1024", AT24_DEVICE_MAGIC(1048576 / 8, AT24_FLAG_ADDR16) },
        	{ "at24", 0 },
        	{ /* END OF LIST */ }
        };
        
        //i2c_driver结构
        static struct i2c_driver at24_driver = {
        	.driver = {
        		.name = "at24",
        		.owner = THIS_MODULE,
        	},
        	.probe = at24_probe,
        	.remove = __devexit_p(at24_remove),
        	.id_table = at24_ids,
        };
        
        //模块加载函数
        static int __init at24_init(void)
        {
        	if (!io_limit) {
        		pr_err("at24: io_limit must not be 0!\n");
        		return -EINVAL;
        	}
        
        	io_limit = rounddown_pow_of_two(io_limit);
        	return i2c_add_driver(&at24_driver);
        }
        module_init(at24_init);
        
        //模块卸载函数
        static void __exit at24_exit(void)
        {
        	i2c_del_driver(&at24_driver);
        }
        module_exit(at24_exit);
        ```

从内核中去除i2c_driver

make menuconfig
Device Drivers  --->
    Misc devices  --->
        EEPROM support  --->
            < > I2C EEPROMs from most vendors
            
2.使用.config去覆盖arch/arm/configs/GEC6818_defconfig
    cp .config arch/arm/configs/GEC6818_defconfig

3.重新编译烧写内核

练习:

去掉内核eeprom驱动,重新烧写内核。

4.i2c_driver驱动中和总线驱动交互的接口

(1)i2c_transfer接口

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
参数:
    adap - 传输数据的媒介,存在于i2c_client结构中
    msgs - 传递的消息结构数组首地址
    num - 消息结构数组的元素个数
成功返回0,失败返回错误码

//消息结构
struct i2c_msg {
	__u16 addr;	/*从设备地址*/
	__u16 flags;    /*读/写方向 0---写 1---读*/
#define I2C_M_TEN		0x0010	/* this is a ten bit chip address */
#define I2C_M_RD		0x0001	/* read data, from slave to master */
#define I2C_M_NOSTART		0x4000	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR	0x2000	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK	0x1000	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NO_RD_ACK		0x0800	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_RECV_LEN		0x0400	/* length will be first received byte */
	__u16 len;		/*消息数据的长度*/
	__u8 *buf;		/*消息数据的首地址*/
};        

(2)smbus接口

smbus接口的应用可以通过阅读内核源码提供的说明手册Documentation/i2c/smbus-protocol来了解。

1)读操作

image-20220802001110427

符合at24c04的读一个字节的操作

s32 i2c_smbus_read_byte_data(const struct i2c_client *client, u8 command);
参数:
    client - i2c_client结构
    command - 读的命令(片内地址)
成功返回读到的数据,失败返回错误码     

2)写操作

image-20220802001127655

符合at24c04的写一个字节的操作

s32 i2c_smbus_write_byte_data(const struct i2c_client *client, u8 command,
			      u8 value);
参数:
    client - i2c_client结构
    command - 写的命令(片内地址)
    value - 写的数据
成功返回0,失败返回错误码        

5.i2c_driver的注册除了使用模块加载和模块卸载函数以外,也可以使用内核中封装好的宏来声明

                module_i2c_driver(i2c_driver结构);