Linux-IIC驱动(3)-IIC用户态驱动程序设计

2024-08-31 06:38

本文主要是介绍Linux-IIC驱动(3)-IIC用户态驱动程序设计,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

 

之前已经说过,有2种i2c驱动程序的设计,比如说针对EEPROM的驱动程序。我们可以专门编写一个针对EEPROM的驱动程序。另一种方式就是通过i2c-dev,即通过i2c通用通用驱动,来编写一个应用程序,来完成对设备的控制。

 

我们现在就来实现i2c用户态驱动程序的设计。

通用设备驱动分析

首先需要分析i2c-dev,先打开i2c-dev.c这个文件,找到i2c_dev_init函数

 

/* ------------------------------------------------------------------------- *//** module load/unload record keeping*/static int __init i2c_dev_init(void)
{int res;printk(KERN_INFO "i2c /dev entries driver\n");res = register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);if (res)goto out;i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");if (IS_ERR(i2c_dev_class)) {res = PTR_ERR(i2c_dev_class);goto out_unreg_chrdev;}res = i2c_add_driver(&i2cdev_driver);if (res)goto out_unreg_class;return 0;out_unreg_class:class_destroy(i2c_dev_class);
out_unreg_chrdev:unregister_chrdev(I2C_MAJOR, "i2c");
out:printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);return res;
}

 

 

 

register_chrdev用于创建注册一个字符设备,class_create用于生产一个字符类的设备文件,i2c_add_driver这是用来向Linux系统注册一个i2c设备驱动。

 

接下来分析操作函数

 

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,
};


这里包含很多操作,我们重点分析i2cdev_ioctl,因为在用户态中,主要通过这个函数来实现对设备的操作。

 

 

 

static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{struct i2c_client *client = (struct i2c_client *)file->private_data;unsigned long funcs;dev_dbg(&client->adapter->dev, "ioctl, cmd=0x%02x, arg=0x%02lx\n",cmd, arg);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;elseclient->flags &= ~I2C_M_TEN;return 0;case I2C_PEC:if (arg)client->flags |= I2C_CLIENT_PEC;elseclient->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_RDWR这个操作,即读和写。我们看看这个函数i2cdev_ioctl_rdrw

 

 

static noinline int i2cdev_ioctl_rdrw(struct i2c_client *client,unsigned long arg)
{struct i2c_rdwr_ioctl_data rdwr_arg;struct i2c_msg *rdwr_pa;u8 __user **data_ptrs;int i, res;if (copy_from_user(&rdwr_arg,(struct i2c_rdwr_ioctl_data __user *)arg,sizeof(rdwr_arg)))return -EFAULT;/* Put an arbitrary limit on the number of messages that can* be sent at once */if (rdwr_arg.nmsgs > I2C_RDRW_IOCTL_MAX_MSGS)return -EINVAL;rdwr_pa = (struct i2c_msg *)kmalloc(rdwr_arg.nmsgs * sizeof(struct i2c_msg),GFP_KERNEL);if (!rdwr_pa)return -ENOMEM;if (copy_from_user(rdwr_pa, rdwr_arg.msgs,rdwr_arg.nmsgs * sizeof(struct i2c_msg))) {kfree(rdwr_pa);return -EFAULT;}data_ptrs = kmalloc(rdwr_arg.nmsgs * sizeof(u8 __user *), GFP_KERNEL);if (data_ptrs == NULL) {kfree(rdwr_pa);return -ENOMEM;}res = 0;for (i = 0; i < rdwr_arg.nmsgs; i++) {/* Limit the size of the message to a sane amount;* and don't let length change either. */if ((rdwr_pa[i].len > 8192) ||(rdwr_pa[i].flags & I2C_M_RECV_LEN)) {res = -EINVAL;break;}data_ptrs[i] = (u8 __user *)rdwr_pa[i].buf;rdwr_pa[i].buf = kmalloc(rdwr_pa[i].len, GFP_KERNEL);if (rdwr_pa[i].buf == NULL) {res = -ENOMEM;break;}if (copy_from_user(rdwr_pa[i].buf, data_ptrs[i],rdwr_pa[i].len)) {++i; /* Needs to be kfreed too */res = -EFAULT;break;}}if (res < 0) {int j;for (j = 0; j < i; ++j)kfree(rdwr_pa[j].buf);kfree(data_ptrs);kfree(rdwr_pa);return res;}res = i2c_transfer(client->adapter, rdwr_pa, rdwr_arg.nmsgs);while (i-- > 0) {if (res >= 0 && (rdwr_pa[i].flags & I2C_M_RD)) {if (copy_to_user(data_ptrs[i], rdwr_pa[i].buf,rdwr_pa[i].len))res = -EFAULT;}kfree(rdwr_pa[i].buf);}kfree(data_ptrs);kfree(rdwr_pa);return res;
}


先来分析这个函数的参数,参数有2个client和arg,client应该是需要操作的设备,arg则是需要读写的参数,这个参数首先被赋值给这个结构i2c_rdwr_ioctl_data

 

 

/* This is the structure as used in the I2C_RDWR ioctl call */
struct i2c_rdwr_ioctl_data {struct i2c_msg __user *msgs;	/* pointers to i2c_msgs */__u32 nmsgs;			/* number of i2c_msgs */
};


这里有2个成员,一个是消息指针,另一个是消息的数量。消息数量很好理解,我们看看消息指针的类型:

 

 

struct i2c_msg {__u16 addr;	/* slave address			*/__u16 flags;
#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;		/* msg length				*/__u8 *buf;		/* pointer to msg data			*/
};

里面包含了设备的地址addr,flags(0为写,1为读)读写标志,消息的字节数,消息的数据指针。
 

 

接着来分析这个函数,做一些判断之后,接下来肯定就是读取消息数据了,它通过一个大循环for (i = 0; i < rdwr_arg.nmsgs; i++) 来读取参数里面的数据,然后使用i2c_transfer来传输这些数据。这个函数是属于i2c-croe里面的一个函数,但是这个函数并不会直接读写,而是找到挂载i2c总线上的适配器,通过设备器上面的算法来真正实现数据的传输。这个数据传输的线路和上面一节的数据流程图一摸一样。

 

因此对于用户态的i2c设备驱动编写就很明了了,首先需要构造一条i2c消息i2c_rdwr_ioctl_data,然后通过i2cdev_ioctl_rdrw函数把这些数据读写到设备中去。我们接下来就编写用户态下面i2c驱动程序的编写。

 

用户态驱动设计

我们先分析一下程序大概的流程:

 

1、打开通用的字符设备文件

依然是使用open打开设备文件,在开发板的/dev/下面我们可以找到一个叫做i2c-0的设备文件,我们以读写的方式打开这个设备文件

 

2、构造需要写入到EEPROM中的消息

我们首先需要赋值消息的定义到我们的程序中。即i2c_msg和i2c_rdwr_ioctl_data。可以把一些不需要的数据删掉。

然后定义一个消息结构,i2c_rdwr_ioctl_data eeprom_data,然后初始化这个结构(别忘了给指针分配空间)。特别要注意的是对应消息的数量读和写肯定是不一样的,因为对于写只需要一个消息,而对于读只需要2个消息,因为先做了一次写,然后在做了一次读。因此我们按最大的长度2,来给i2c_msg 分配空间。

接下来可以初始化写的消息,写的信息有2个字节,所以len=2,第一个是偏移地址,第二个是需要写入的数据。初始化后如下:

	eeprom_data.nmsgs = 1;  //写只有一条消息(eeprom_data.msgs) = (struct i2c_msg *)malloc(2 * sizeof(struct i2c_msg));(eeprom_data.msgs[0]).addr = 0x50;(eeprom_data.msgs[0]).flags = 0;(eeprom_data.msgs[0]).len = 2;(eeprom_data.msgs[0]).buf = (unsigned char *)malloc(2);(eeprom_data.msgs[0]).buf[0] = 0x10;//写入到EEPROM的偏移地址(eeprom_data.msgs[0]).buf[1] = 0x60;//写入到偏移地址的数据

 

 

3、使用ioctl写入数据

ioctl的第一个参数是fd,第二个参数是操作类型,这里是I2C_RDWR,我们需要拷贝I2C_RDWR到自己的程序中,第三个是参数就是eeprom_data了,我们在取地址之后需要进行类型转换,因为i2cdev_ioctl_rdrw的参数是unsigned long

 

4、构造从EEPROM读数据的消息

读消息的构造也类似,不过这里需要2个消息,第一个实现写,第二个实现读:

	//构造从EEPROM读数据的消息eeprom_data.nmsgs = 2;  //读有二条消息(eeprom_data.msgs[0]).addr = 0x50;//先写入需要开始读取的偏移地址,然后开始读(eeprom_data.msgs[0]).flags = 0;(eeprom_data.msgs[0]).len = 1;(eeprom_data.msgs[0]).buf[0] = 0x10;(eeprom_data.msgs[1]).addr = 0x50;(eeprom_data.msgs[1]).flags = 1;(eeprom_data.msgs[1]).len = 1;(eeprom_data.msgs[1]).buf = (unsigned char *)malloc(2);(eeprom_data.msgs[1]).buf[0] = 0;//先把读取缓冲清0

 

5、使用ioctl读出消息

ioctl(fd, I2C_RDWR, (unsigned long)&eeprom_data);

读取到的消息会保存在以buf[0]为起始地址的存储空间中。

6、关闭字符设备

很简单 close(fd)

 

最后,整体代码如下:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>#define I2C_RDWR	0x0707	/* Combined R/W transfer (one STOP only) */struct i2c_msg {unsigned short addr;	/* slave address			*/unsigned short flags;unsigned short len;		/* msg length				*/unsigned char *buf;		/* pointer to msg data			*/
};/* This is the structure as used in the I2C_RDWR ioctl call */
struct i2c_rdwr_ioctl_data {struct i2c_msg  *msgs;	/* pointers to i2c_msgs */unsigned long nmsgs;			/* number of i2c_msgs */
};int main()
{int fd=0;struct i2c_rdwr_ioctl_data eeprom_data;//打开字符设备文件fd = open("/dev/i2c-0", O_RDWR);//构造需要写入到EEPROM的消息eeprom_data.nmsgs = 1;  //写只有一条消息(eeprom_data.msgs) = (struct i2c_msg *)malloc(2 * sizeof(struct i2c_msg));(eeprom_data.msgs[0]).addr = 0x50;//I2C设备地址(eeprom_data.msgs[0]).flags = 0;//0为写,1为读(eeprom_data.msgs[0]).len = 2;//写入数据长度(eeprom_data.msgs[0]).buf = (unsigned char *)malloc(2);//申请2个字节(eeprom_data.msgs[0]).buf[0] = 0x10;//写入到EEPROM的偏移地址(eeprom_data.msgs[0]).buf[1] = 0x60;//写入到偏移地址的数据//使用ioctl把数据写入到EEPROM中ioctl(fd, I2C_RDWR, (unsigned long)&eeprom_data);//需要做类型转换,因为i2cdev_ioctl_rdrw的参数是unsigned long//构造从EEPROM读数据的消息eeprom_data.nmsgs = 2;  //读有二条消息(eeprom_data.msgs[0]).addr = 0x50;//先写入需要开始读取的偏移地址,然后开始读(eeprom_data.msgs[0]).flags = 0;(eeprom_data.msgs[0]).len = 1;(eeprom_data.msgs[0]).buf[0] = 0x10;(eeprom_data.msgs[1]).addr = 0x50;//然后开始读取数据,len的长度为1,表示读取数据的长度(eeprom_data.msgs[1]).flags = 1;(eeprom_data.msgs[1]).len = 1;(eeprom_data.msgs[1]).buf = (unsigned char *)malloc(2);(eeprom_data.msgs[1]).buf[0] = 0;//先把读取缓冲清0//使用ioctl读出消息ioctl(fd, I2C_RDWR, (unsigned long)&eeprom_data);printf("buf[0]:%x\n", (eeprom_data.msgs[1]).buf[0]);//关闭字符设备close(fd);return 0;
}

 

更多Linux资料及视频教程点击这里

 

 

这篇关于Linux-IIC驱动(3)-IIC用户态驱动程序设计的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1123176

相关文章

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

linux-基础知识3

打包和压缩 zip 安装zip软件包 yum -y install zip unzip 压缩打包命令: zip -q -r -d -u 压缩包文件名 目录和文件名列表 -q:不显示命令执行过程-r:递归处理,打包各级子目录和文件-u:把文件增加/替换到压缩包中-d:从压缩包中删除指定的文件 解压:unzip 压缩包名 打包文件 把压缩包从服务器下载到本地 把压缩包上传到服务器(zip

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

怎么让1台电脑共享给7人同时流畅设计

在当今的创意设计与数字内容生产领域,图形工作站以其强大的计算能力、专业的图形处理能力和稳定的系统性能,成为了众多设计师、动画师、视频编辑师等创意工作者的必备工具。 设计团队面临资源有限,比如只有一台高性能电脑时,如何高效地让七人同时流畅地进行设计工作,便成为了一个亟待解决的问题。 一、硬件升级与配置 1.高性能处理器(CPU):选择多核、高线程的处理器,例如Intel的至强系列或AMD的Ry

Linux_kernel驱动开发11

一、改回nfs方式挂载根文件系统         在产品将要上线之前,需要制作不同类型格式的根文件系统         在产品研发阶段,我们还是需要使用nfs的方式挂载根文件系统         优点:可以直接在上位机中修改文件系统内容,延长EMMC的寿命         【1】重启上位机nfs服务         sudo service nfs-kernel-server resta

【Linux 从基础到进阶】Ansible自动化运维工具使用

Ansible自动化运维工具使用 Ansible 是一款开源的自动化运维工具,采用无代理架构(agentless),基于 SSH 连接进行管理,具有简单易用、灵活强大、可扩展性高等特点。它广泛用于服务器管理、应用部署、配置管理等任务。本文将介绍 Ansible 的安装、基本使用方法及一些实际运维场景中的应用,旨在帮助运维人员快速上手并熟练运用 Ansible。 1. Ansible的核心概念

Linux服务器Java启动脚本

Linux服务器Java启动脚本 1、初版2、优化版本3、常用脚本仓库 本文章介绍了如何在Linux服务器上执行Java并启动jar包, 通常我们会使用nohup直接启动,但是还是需要手动停止然后再次启动, 那如何更优雅的在服务器上启动jar包呢,让我们一起探讨一下吧。 1、初版 第一个版本是常用的做法,直接使用nohup后台启动jar包, 并将日志输出到当前文件夹n

[Linux]:进程(下)

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ 🎈🎈养成好习惯,先赞后看哦~🎈🎈 所属专栏:Linux学习 贝蒂的主页:Betty’s blog 1. 进程终止 1.1 进程退出的场景 进程退出只有以下三种情况: 代码运行完毕,结果正确。代码运行完毕,结果不正确。代码异常终止(进程崩溃)。 1.2 进程退出码 在编程中,我们通常认为main函数是代码的入口,但实际上它只是用户级