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内核输入子系统框图
十九.内核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总线驱动,开发者只需要调用总线驱动提供的传输接口即可
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.原理图
at24c02芯片三个地址引脚A0 A1 A2都接地,写保护WP接地(关闭),IIC接口连接到了CPU的第二路IIC。
2.at24c02芯片手册
从设备地址:1010000 ===> 0x50
字节写操作:
发送起始信号,发送从设备地址+写信号,等待ACK,发送写的片内地址,等待ACK,发送写的数据,等待ACK,发送停止信号。
字节读操作:
发送起始信号,发送从设备地址+写信号,等待ACK,发送读的片内地址,等待ACK;发送起始信号,发送从设备地址+读信号,等待ACK,接收读的数据,发送NO ACK,发送停止信号。
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)读操作
符合at24c04的读一个字节的操作
s32 i2c_smbus_read_byte_data(const struct i2c_client *client, u8 command);
参数:
client - i2c_client结构
command - 读的命令(片内地址)
成功返回读到的数据,失败返回错误码
2)写操作
符合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结构);