Linux驱动开发、18-I2C子系统之用户态驱动设计

时间:2023-02-04 04:36:31

Linux 系统下的I2C子系统

子系统架构

 Linux驱动开发、18-I2C子系统之用户态驱动设计

1. I2C核心
I2C 总线和 I2C设备驱动的中间枢纽,它提供了I2C总线驱动和设备驱动的注册、注销方法等。

2. i2c-dev

通用驱动
2.I2C控制器(适配器)驱动

I2C 控制器驱动的实现,属于总线驱动程序,通常由适配器驱动(i2c_adapter)adapter.algo成员(算法驱动程序;控制器(适配器)可在CPU外部,也可以集成在CPU 内部
3. I2C设备驱动
I2C从设备的驱动实现,如AT24C02的驱动

 

i2c-dev 允许在用户态模式下实现I2C客户驱动程序。

 Linux驱动开发、18-I2C子系统之用户态驱动设计

图解:

通路1:用户程序直接通过/sys/dev设备文件通过I2C设备驱动直接访问i2c设备.

通路2:直接使用用户态驱动通过i2c-dev(通用驱动程序),经过I2C核心控制适配器(struct i2c_adapter)驱动和算法(struct i2c_algorithm)驱动再控制I2C设备

 

通路2追踪分析:利用i2c-dev通用驱动开发用户态驱动

 

用户态驱动经过 i2c-dev,在这里面做了些什么?

 

-------------------------------------------i2c-dev------------------------------------------------------------------------

 

在模块初始化时除了注册通用字符设备外,使用以下语句:

/* Keep track of adapters which will be added or removed later */

res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);

 

/* Bind to already existing adapters right away */

i2c_for_each_dev(NULL, i2cdev_attach_adapter);

主要看的是第二条,绑定已经存在的控制器(适配器),在这里面偷偷的干了啥

相关结构:

struct i2c_adapter *adap; /*适配器,用来描述一个i2c控制器的结构体*/

好像也没干什么,获取设备的设备器,再分配空间,之后通过

i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,

     MKDEV(I2C_MAJOR, adap->nr), NULL,

     "i2c-%d", adap->nr);

向核心注册驱动:主要说明的是device_create这个函数:既在sys文件下创建设备文件

文件所在类名是

 Linux驱动开发、18-I2C子系统之用户态驱动设计

struct device *device_create(struct class *class, struct device *parent,

     dev_t devt, void *drvdata, const char *fmt, ...)

 

既然创建了设备文件,那么接下来应该是对应的文件操作集吧!继续找找看

static const struct file_operations i2cdev_fops = {

.owner = THIS_MODULE,

.llseek = no_llseek,

.read = i2cdev_read,

.write = i2cdev_write,

.unlocked_ioctl = i2cdev_ioctl, /*这个对用户态驱动来说就重要啦!*/

.open = i2cdev_open,

.release = i2cdev_release,

};

---------------------------------------------------------------i2c-core.c------------------------------------------------------------------------

进击open函数,这里面做怎么搞?

 

注意了,下面的调用函数已近进入核心 i2c-core.c文件了

首先遍历i2c设备链表,获取次设备号

再获取i2c设备的第N个设配器:adap = i2c_get_adapter(i2c_dev->adap->nr);

最后调用:i2c_put_adapter(adap);创建一个未指定的从设备,也就是分配一段空间

话说设备号,设配器这些哪来?肯定是设配器驱动程序啦!

 

TQ210i2c设配器(控制器)驱动程序在i2c-s3c2410.c文件中,进去里面看看就知道了。

 

-------------------------------------------------------------i2c-s3c2410.c----------------------------------------------------------------------

 

书籍上说对于集成在芯片上的i2c控制器驱动一般linux内核帮我们实现了,所以我们就不用操心了,但还是来看看怎么操作的吧。

平台设备初始化....直接来到捕获函数看看,里面就是对控制器初始化,中断注册之类的;最重要的就是ret = i2c_add_numbered_adapter(&i2c->adap);这里面又调用了

--->status = i2c_register_adapter(adap);向总线添加I2C控制器。(该函数是i2c-core.c文件的,这样脉络就出来啦!)

之后数据的传输在初始化中注册的中断函数中进行。好了。。。清楚了。回到用户层去

 

—————————————————————user space———————————————————

在这层如何编写用户层次驱动呢?你想的没错,就是通过file_operations 结构操作对应的设备文件,其中有一个注释:

/*

 * After opening an instance of this character special file, a file

 * descriptor starts out associated only with an i2c_adapter (and bus).

 *

 * Using the I2C_RDWR ioctl(), you can then *immediately* issue i2c_msg

 * traffic to any devices on the bus used by that adapter.  That's because

 * the i2c_msg vectors embed all the addressing information they need, and

 * are submitted directly to an i2c_adapter.  However, SMBus-only adapters

 * don't support that interface.

 *

 * To use read()/write() system calls on that file descriptor, or to use

 * SMBus interfaces (and work with SMBus-only hosts!), you must first issue

 * an I2C_SLAVE (or I2C_SLAVE_FORCE) ioctl.  That configures an anonymous

 * (never registered) i2c_client so it holds the addressing information

 * needed by those system calls and by this SMBus interface.

 */

所以说,如果想要直接操作I2C设备,那么我们就要来操作对应的ioctl函数啦!怎么操作呢?来分析一下

把整个代码给cp 过来了:

 

static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)

{

struct i2c_client *client = file->private_data;

unsigned long funcs;

switch (cmd) {

case I2C_SLAVE:

case I2C_SLAVE_FORCE:

/* NOTE:  devices set up to work with "new style" drivers

 * can't use I2C_SLAVE, even when the device node is not

 * bound to a driver.  Only I2C_SLAVE_FORCE will work.

 *

 * Setting the PEC flag here won't affect kernel drivers,

 * which will be using the i2c_client node registered with

 * the driver model core.  Likewise, when that client has

 * the PEC flag already set, the i2c-dev driver won't see

 * (or use) this setting.

 */

if ((arg > 0x3ff) ||

    (((client->flags & I2C_M_TEN) == 0) && arg > 0x7f))

return -EINVAL;

if (cmd == I2C_SLAVE && i2cdev_check_addr(client->adapter, arg))

return -EBUSY;

/* REVISIT: address could become busy later */

client->addr = arg;

return 0;

case I2C_TENBIT:

if (arg)

client->flags |= I2C_M_TEN;

else

client->flags &= ~I2C_M_TEN;

return 0;

case I2C_PEC:

if (arg)

client->flags |= I2C_CLIENT_PEC;

else

client->flags &= ~I2C_CLIENT_PEC;

return 0;

case I2C_FUNCS:

funcs = i2c_get_functionality(client->adapter);

return put_user(funcs, (unsigned long __user *)arg);

 

case I2C_RDWR:

return i2cdev_ioctl_rdrw(client, arg);

 

case I2C_SMBUS:

return i2cdev_ioctl_smbus(client, arg);

 

case I2C_RETRIES:

client->adapter->retries = arg;

break;

case I2C_TIMEOUT:

/* For historical reasons, user-space sets the timeout

 * value in units of 10 ms.

 */

client->adapter->timeout = msecs_to_jiffies(arg * 10);

break;

default:

/* NOTE:  returning a fault code here could cause trouble

 * in buggy userspace code.  Some old kernel bugs returned

 * zero in this case, and userspace code might accidentally

 * have depended on that bug.

 */

return -ENOTTY;

}

return 0;

}

如果我么要对i2c设备进行操作,那么就是读写操作了,当我们发送控制信号I2C_RDWR就可以进行操作了,进入

static noinline int i2cdev_ioctl_rdrw(struct i2c_client *client,unsigned long arg),查看函数信息:

第一眼就看到重要数据结构:

struct i2c_msg *rdwr_pa;

该函数通过copy_from_user(&rdwr_arg,

   (struct i2c_rdwr_ioctl_data __user *)arg,/*重点就在这个消怎么构造*/

   sizeof(rdwr_arg))

将用户程序传递进来的“消息”复制到指定的位置去,之后调用

res = i2c_transfer(client->adapter, rdwr_pa, rdwr_arg.nmsgs); 向下一层传递消息....

 

这样就清楚了:我们怎么编写用户态驱动呢?关键就是“消息”如何来构造的问题了

 

struct i2c_msg {

__u16 addr; /* slave address */

__u16 flags; /*读写标志,在协议那里说明了,01*/

__u16 len; /* msg length*/

__u8 *buf; /* pointer to msg data */

};

 

 

来了,现在就来试一下用户层如何来操作我的E2PROM....

问题是我们要针对哪个设备文件呢?其实在注册设配器的驱动时里面有一个总线号,如果没有指明,默认是0,也就是i2c-0,如果是用户操作,那么对应操作/dev/i2c-0文件就好了

补充一下:设备模型关系

 

 Linux驱动开发、18-I2C子系统之用户态驱动设计

用户态驱动程序设计

#include<stdio.h>

#include<sys/ioctl.h>

#include<sys/types.h>

#include<sys/stat.h>

#include<fcntl.h>

 

#include<stdlib.h>

typedef unsigned long long  __u64;

typedef unsigned int __u32;

typedef unsigned short __u16;

typedef unsigned char    __u8;

 

 

 

#define I2C_RDWR 0x0707 /* Combined R/W transfer (one STOP only) */

#define PATH  "/dev/i2c-0"

 

/*数据消息*/

typedef struct i2c_msg {

__u16 addr; /* slave address */

__u16 flags; /*读写标志,下面这些定义都是它的标志*/

 

__u16 len; /* msg length*/

__u8 *buf; /* pointer to msg data */

}I2C_MSG;

 

/* 传递给驱动的结构体 */

typedef struct i2c_rdwr_ioctl_data {

I2C_MSG  *msgs; /* pointers to i2c_msgs */

__u32 nmsgs; /* number of i2c_msgs */

}I2C_CtlData;

 

int main(void)

{

int fd = 0;

int ret = 0;

/*定义通过控制函数传递给驱动的数据*/

I2C_CtlData  e2p_Data;

/*指针类型的必须分配空间,要不会段错误*/

/*这里分配最大的消息空间,因为随机读数据需要两个消息*/

e2p_Data.msgs = (I2C_MSG *)malloc(2*sizeof(I2C_MSG));

(e2p_Data.msgs[0]).buf = (__u8 *)malloc(2); /*给指针分配两个字节*/

(e2p_Data.msgs[1]).buf = (__u8 *)malloc(1);

 

/*打开设备文件*/

if( (fd = open(PATH,O_RDWR)) < 0)

printf("No such file!\n");

/*i2c设备写入数据*/

/*构建写消息*/

e2p_Data.nmsgs = 1; /*写只有一条消息*/

(e2p_Data.msgs[0]).flags = 0; /*0表示主设备向从设备写数据*/

(e2p_Data.msgs[0]).addr = 0x50; /*看电路查看芯片手册*/

(e2p_Data.msgs[0]).len = 2; /*消息长度,每个消息需要两个字节,一个地址,一个数据*/

 

(e2p_Data.msgs[0]).buf[0] = 0X12; /*将数据方到芯片的0x12地址处*/

(e2p_Data.msgs[0]).buf[1] = 0x11; /*消息数据*/

if( (ret = ioctl(fd,I2C_RDWR,(unsigned long)&e2p_Data)) <0)

printf("Control err in write data ! fd is %d\n",fd);

/*读取i2c设备数据*/

/*构建读消息*/

e2p_Data.nmsgs = 2; /*读取数据需要两条消息,一条为写,一条读*/

(e2p_Data.msgs[0]).len = 1; /*消息长度,只读一个数据*/

(e2p_Data.msgs[0]).addr = 0x50; /*芯片片选地址*/

(e2p_Data.msgs[0]).flags = 0; /*0表示写*/

(e2p_Data.msgs[0]).buf[0] = 0X12; /*读取0x12地址*/

(e2p_Data.msgs[1]).len = 1; /*读一个数据*/

(e2p_Data.msgs[1]).addr = 0x50;

(e2p_Data.msgs[1]).flags = 1; /*1表示读取数据*/

 

(e2p_Data.msgs[1]).buf[0] = 0; /*将数据存放到此处*/

if( (ret = ioctl(fd,I2C_RDWR,(unsigned long )&e2p_Data)) <0 )

printf("Control err in read data! fd is:%d\n",fd);

/*关闭设备文件*/

printf("read data form e2prom is : %x\n",(e2p_Data.msgs[1]).buf[0]);

free((e2p_Data.msgs[1]).buf );

free((e2p_Data.msgs[0]).buf);

free(e2p_Data.msgs);

close(fd);

return 0;

}

 

出错啦!怎么解决呢?从新配置一下内核,打开I2C-总线所有调试信息:

 

写的时候有响应,读的时候无响应了,怎么回事?第二个消息包错了吗?

 Linux驱动开发、18-I2C子系统之用户态驱动设计

 

 

 

要追踪一下了,是不是在等待应答那里超时了?速度太快,咻咻咻的就以为超时了,应该是在控制器驱动中的中断收发。先这样吧,原理清楚了。问题等我有时间再来慢慢解决!