Linux字符设备与I2C驱动结合使用

news2025/2/25 18:13:15

引言

在Linux操作系统中,设备驱动程序充当硬件和软件之间的桥梁。字符设备驱动是一种特殊类型的驱动,它允许用户以字节流的形式访问硬件设备。这些设备包括键盘、鼠标、串口等。在本博客中,我们将探讨Linux字符设备驱动的基础知识,构建过程,以及如何管理这些设备。

第1章:Linux设备驱动概述

在Linux中,设备通常分为字符设备、块设备和网络设备。字符设备允许按字节进行数据传输,而块设备则基于数据块操作。网络设备则用于处理网络通信。

第2章:字符设备基础

字符设备是可以按字节流进行读写的设备。它们通常不支持随机访问,数据传输必须按顺序进行。在这一章节中,我们将详细讨论字符设备的定义和特点,以及它们在Linux系统中的表示方法。

第3章:字符设备驱动程序结构

在这一章节中,我们将探讨字符设备驱动程序的基本结构。这包括设备文件的创建和注册,以及cdev结构体的重要性。cdev结构体是Linux内核用来表示字符设备的核心数据结构,它包含了设备的主要信息和操作函数接口。

在Linux内核中,cdev结构体是用来表示字符设备的关键数据结构。它包含了字符设备的主要信息和操作函数接口。以下是cdev结构体的一些主要成员及其作用:

struct cdev {
    struct kobject kobj;               // 内嵌的内核对象
    struct module *owner;              // 指向拥有该字符设备的内核模块的指针
    const struct file_operations *ops; // 指向文件操作函数的指针,这些函数定义了设备的行为
    struct list_head list;             // 用于将所有已注册的字符设备链接成一个链表
    dev_t dev;                         // 设备号,由主设备号和次设备号构成
    unsigned int count;                // 隶属于同一主设备号的次设备号的个数
};
  • kobj:用于内核对象模型,提供设备模型与sysfs的接口。
  • owner:通常设置为THIS_MODULE,确保在模块被卸载时,设备驱动不会被使用。
  • ops:指向file_operations结构,定义了字符设备的操作方法,如openreadwrite等。
  • list:用于将设备添加到内核的设备链表中。
  • dev:设备号,用于唯一标识设备。
  • count:表示与该设备关联的次设备号的数量。

如何将一个I2C驱动 描述为字符设备驱动 运用:

...
#define MYMA 301      //主设备号
#define COUNT 1
typedef struct {
	struct cdev cdev;
	struct i2c_client *cli;
	struct class *cls;
} i2cDev_data_t;

int myprobe(struct i2c_client *cli, const struct i2c_device_id *id)
{
	struct device_node *np;
	struct device *dev;
	int ret;
	i2cDev_data_t *data;
	static int mi = 0;

    dev_t devid;
    devid = MKDEV(MYMA, mi);
	ret = register_chrdev_region(devid, COUNT, cli->name);
	if (ret < 0)
		goto err0;
	data = kzalloc(sizeof(*data), GFP_KERNEL);
	if (NULL == data) {
		ret = -ENOMEM;
		goto err1;
	}

	cdev_init(&data->cdev, &fops);
	data->cdev.owner = THIS_MODULE;
	ret = cdev_add(&data->cdev, devid, COUNT);
	if (ret < 0)
		goto err2;
	data->cls = class_create(THIS_MODULE, cli->name);
	device_create(data->cls, NULL, devid, NULL, "%s.%d", cli->name, mi++);
	data->cli = cli;


	i2c_set_clientdata(cli, data);
	dev = &cli->dev;
	if (!dev)
		return -ENODEV;
	np = dev->of_node;

	return 0;
err2:
	kfree(data);
err1:
	unregister_chrdev_region(devid, COUNT);
err0:
	return ret;
}
...

各API作用如下:

 1.MKDEV: #define MKDEV(major, minor) (((major) << MINORBITS) | (minor))
MKDEV宏用于将主设备号(major number)和次设备号(minor number)组合成一个dev_t类型的设备编号。这个设备编号通常用于设备文件的创建和设备驱动程序的注册。

2.register_chrdev_region:

在Linux内核编程中,register_chrdev_region函数用于静态注册一组字符设备编号。如果您已经知道要使用的主设备号和次设备号,可以使用此函数进行注册。以下是该函数的原型和简要说明:

int register_chrdev_region(dev_t first, unsigned int count, char *name);
  • first:要注册的第一个设备编号,包括主设备号和起始次设备号。
  • count:要注册的设备数量,即次设备号的个数。
  • name:与设备编号关联的设备名称,这个名称会出现在/proc/devices目录下。

当调用register_chrdev_region函数时,如果指定的设备编号范围已经被占用,函数将返回一个负值错误代码。如果注册成功,函数将返回0。在注册设备编号之前,您应该检查/proc/devices以确保所需的设备号没有被占用

3.cdev_init :用于初始化一个已经分配的cdev结构体。这个函数将file_operations结构体与cdev结构体关联起来,为设备提供必要的文件操作方法。

4.cdev_add:用于将一个cdev结构体添加到内核中,使得相应的字符设备立即可用。

5.class_create:用于创建一个新的设备类,这个类将出现在/sys/class目录下,成功调用class_create后,您可以在/sys/class/<name>下找到新创建的设备类。

6.device_create:用于在已创建的设备类下创建一个设备,并在/dev目录下自动创建相应的设备文件节点。

第4章:文件操作接口

字符设备驱动程序通常需要实现一系列文件操作接口,如openreleaseclose)、readwrite等。这些接口允许用户空间的程序通过设备文件与设备进行交互。我们将详细讨论这些接口的实现和它们在设备驱动中的作用。

ssize_t chr_write(struct file *fl, const char __user *buf, size_t len,
		      loff_t *off)
{
	struct i2c_msg msg;
	struct cdev *cdev = fl->f_path.dentry->d_inode->i_cdev;
	i2cDev_data_t *data = container_of(cdev, i2cDev_data_t, cdev);
	char *kbuf = NULL;
	int ret =0;
	char addr;
	struct i2c_client *cli = data->cli;
	kbuf = kzalloc(len+1, GFP_KERNEL);
	ret = copy_from_user(kbuf, buf, len);
	addr = atoi(kbuf);
	msgs.addr = cli->addr;
	msgs.flags = 0;
	msgs.len = 1;
	msgs.buf = &addr;

	ret = i2c_transfer(cli->adapter, &msgs, 1);
	if (ret < 0) {
		chr_DEBUG("%s error %d\n", __func__, ret);
	}
	kfree(kbuf);
	return len;
}

static long chr_ioctl(struct file *fl, unsigned int cmd, unsigned long arg)
{
	
	printk("chr_ioctl");
	return 0;
}

int chr_open(struct inode *ind, struct file *fl)
{
	printk("chr_open");
	return 0;
}

ssize_t chr_read(struct file *fl, char __user *buf, size_t len, loff_t *off)
{
	chr_DEBUG("chr_read audioValue=%d\n", audioValue);
	return audioValue;
}

struct file_operations fops = {
	.owner = THIS_MODULE,
	.read = chr_read,
	.open = chr_open,
	.write = chr_write,
	.unlocked_ioctl = chr_ioctl,
};

1.container_of:

container_of宏是一个非常有用的工具,它允许您通过结构体的一个成员的地址来获取整个结构体的地址。这在驱动开发和内核编程中非常常见,尤其是当您只有对结构体中某个成员的引用时。以下是container_of宏的一般用法:

#define container_of(ptr, type, member) ({          \
    const typeof( ((type *)0)->member ) *__mptr = (ptr); \
    (type *)( (char *)__mptr - offsetof(type, member) );})
  • tr:指向结构体中成员的指针。
  • type:结构体的类型。
  • member:结构体中的成员名称

  2.struct file_operations :

struct file_operations {
    struct module *owner;  // 指向模块所有者的指针
    loff_t (*llseek) (struct file *, loff_t, int);  // 改变文件读写位置的方法
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);  // 从设备读取数据的方法
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);  // 向设备写入数据的方法
    int (*open) (struct inode *, struct file *);  // 打开设备的方法
    int (*release) (struct inode *, struct file *);  // 释放设备的方法
    // ... 其他操作方法
};

这些方法对应于用户空间程序对设备文件执行的系统调用。例如,当用户程序调用`read()`系统调用时,内核会调用file_operations中的read方法来从设备读取数据

实际运用将一个I2C驱动添加字符设备接口

dtsi:
&i2c5 {
        status = "okay";
        chr_drive: chr_drive@44 {
	        status = "okay";
            compatible = "chr_drive";
            reg = <0x44>;
		};
};

chr_driver.c 

#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#define MYMA 301
#define COUNT 1

static unsigned char audioValue = 43;

typedef struct {
	struct cdev cdev;
	struct i2c_client *cli;
	struct class *cls;
} i2cDev_data_t;

static int atoi(const char *str)
{
	int result = 0;
	int sign = 0;
	if (str == NULL) {
		return -1;
	}
	while (*str == ' ' || *str == '\t' || *str == '\n')
		++str;
	if (*str == '-') {
		sign = 1;
		++str;
	} else if (*str == '+') {
		++str;
	}
	while (*str >= '0' && *str <= '9') {
		result = result * 10 + *str - '0';
		++str;
	}
	if (sign == 1)
		return -result;
	else
		return result;
}
ssize_t chr_write(struct file *fl, const char __user *buf, size_t len,
		      loff_t *off)
{
	struct i2c_msg msg;
	struct cdev *cdev = fl->f_path.dentry->d_inode->i_cdev;
	i2cDev_data_t *data = container_of(cdev, i2cDev_data_t, cdev);
	char *kbuf = NULL;
	int ret =0;
	char addr;
	struct i2c_client *cli = data->cli;
	kbuf = kzalloc(len+1, GFP_KERNEL);
	ret = copy_from_user(kbuf, buf, len);
	addr = atoi(kbuf);
	msg.addr = cli->addr;
	msg.flags = 0;
	msg.len = 1;
	msg.buf = &addr;
    printk("chr_write %d",addr);
	ret = i2c_transfer(cli->adapter, &msg, 1);
	if (ret < 0) {
		printk("%s error %d\n", __func__, ret);
	}
	
	kfree(kbuf);
	return len;
}

static long chr_ioctl(struct file *fl, unsigned int cmd, unsigned long arg)
{
	
	printk("chr_ioctl");
	return 0;
}

int chr_open(struct inode *ind, struct file *fl)
{
	printk("chr_open");
	return 0;
}

ssize_t chr_read(struct file *fl, char __user *buf, size_t len, loff_t *off)
{
	printk("chr_read");
	return 0;
}

struct file_operations fops = {
	.owner = THIS_MODULE,
	.read = chr_read,
	.open = chr_open,
	.write = chr_write,
	.unlocked_ioctl = chr_ioctl,
};

int myprobe(struct i2c_client *cli, const struct i2c_device_id *id)
{
	struct device_node *np;
	struct device *dev;
	int ret;
	i2cDev_data_t *data;
	static int mi = 0;

    dev_t devid;
    devid = MKDEV(MYMA, mi);
	ret = register_chrdev_region(devid, COUNT, cli->name);
	if (ret < 0)
		goto err0;
	data = kzalloc(sizeof(*data), GFP_KERNEL);
	if (NULL == data) {
		ret = -ENOMEM;
		goto err1;
	}

	cdev_init(&data->cdev, &fops);
	data->cdev.owner = THIS_MODULE;
	ret = cdev_add(&data->cdev, devid, COUNT);
	if (ret < 0)
		goto err2;
	data->cls = class_create(THIS_MODULE, cli->name);
	device_create(data->cls, NULL, devid, NULL, "%s.%d", cli->name, mi++);
	data->cli = cli;


	i2c_set_clientdata(cli, data);
	dev = &cli->dev;
	if (!dev)
		return -ENODEV;
	np = dev->of_node;

	return 0;
err2:
	kfree(data);
err1:
	unregister_chrdev_region(devid, COUNT);
err0:
	return ret;
}

int myremove(struct i2c_client *cli)
{
	i2cDev_data_t *data = i2c_get_clientdata(cli);
	device_destroy(data->cls, data->cdev.dev);
	class_destroy(data->cls);
	cdev_del(&data->cdev);
	unregister_chrdev_region(data->cdev.dev, COUNT);
	kfree(data);
	return 0;
}

struct i2c_device_id ids[] = {
	{ "chr_drive", 0 },
	{},
};

MODULE_DEVICE_TABLE(i2c, ids);

static const struct of_device_id chr_of_match[] = {
	{ .compatible = "chr_drive" },
	{},
};

struct i2c_driver mydrv = {
    .probe = myprobe,
    .remove = myremove,
    .driver = {
        .name = "chr_drive",
        .of_match_table = chr_of_match,
        .owner = THIS_MODULE,
    },

    .id_table = ids,
};

module_i2c_driver(mydrv);
MODULE_LICENSE("GPL");

注册成功会在dev下生成chr_drive节点

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1528872.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

u-boot-2017.09 make配置过程分析

概述 本文基于firefly RK3588Q SDK uboot配置过程进行分析&#xff0c;环境如下&#xff1a; 编译环境&#xff1a;Ubuntu 20.04 LTS 编译工具&#xff1a;aarch64-linux-gnu- 代码版本&#xff1a;u-boot v2017.09 配置文件&#xff1a;rk3588_defconfig Uboot配置单板 …

【数组、字符串】算法例题

每个题的【方法1】是自己的思路&#xff0c;【其他方法】是力扣上更优的解题思路 目录 一、数组、字符串 1. 合并两个有序数组 ① 2. 移除元素 ① 3. 删除有序数组中的重复项 ① 4. 删除有序数组中的重复项 II ② 5. 多数元素 ① 6. 轮转数组 ② 7. 买卖股票的最佳时机…

瑞_Redis_短信登录

文章目录 项目介绍1 短信登录1.1 项目准备1.1.1 导入SQL1.1.2 导入后端项目1.1.3 导入前端项目 1.2 基于Session实现登录流程1.2.1 功能流程介绍1.2.1.1 发送短信验证码1.2.1.2 短信验证码登录、注册1.2.1.3 校验登录状态 1.2.2 实现发送短信验证码功能1.2.2.1 页面流程1.2.2.2…

2024单商户微信小程序商城源码

2024单商户微信小程序商城源码 1.框架采用全新thinkphp6事件开发设计layuiuniapp进行设计&#xff0c;代码完全重构&#xff0c;支持百万级! 2.前端以layui uniapp模块化开发; 3.数据导出采用phpExce1,使数据更加直观&#xff0c;更方便于管理统计; 4.插件钩子机制&#xff0…

突发!半导体巨头关闭上海公司,重组中国区业务!

据韩媒消息&#xff0c;韩国最大的半导体巨头之一 SK 海力士正在重组中国区业务&#xff0c;计划关闭其在上海的子公司&#xff0c;该子公司成立于 2006 年。 根据发布的 2023 年审计报告&#xff0c;去年四季度以来该公司一直在清算其上海子公司&#xff0c;并计划将业务重心…

基于java的宠物信息交流平台设计(含源文件)

随着世界经济信息化、全球化的到来和互联网的飞速发展&#xff0c;推动了各行业的改革。若想达到安全&#xff0c;快捷的目的&#xff0c;就需要拥有信息化的组织和管理模式&#xff0c;建立一套合理、动态的、交互友好的、高效的“多鱼”旧物交易平台。当前的信息管理存在工作…

【第十二章】改进神经网络学习方式-过拟合与正则化

前言 诺贝尔奖获得者、物理学家恩里科费米曾被问及对一位同事提出的数学模型作为重要未解物理问题的解决方案的看法。该模型与实验结果非常吻合&#xff0c;但费米持怀疑态度。他询问该模型中有多少自由参数可供设置。答案是“四个”。费米回答道:“我记得我的朋友约翰尼冯诺伊…

基于Spring Boot+Vue的智慧图书管理系统

末尾获取源码作者介绍&#xff1a;大家好&#xff0c;我是墨韵&#xff0c;本人4年开发经验&#xff0c;专注定制项目开发 更多项目&#xff1a;CSDN主页YAML墨韵 学如逆水行舟&#xff0c;不进则退。学习如赶路&#xff0c;不能慢一步。 一、项目简介 如今社会上各行各业&…

力扣---验证二叉搜索树---前根/中根/后根遍历

题目解析参考&#xff1a;验证二叉搜索树_哔哩哔哩_bilibili 一开始做呢&#xff0c;就跟这位老兄一样&#xff1a; 因为没有考虑到5和3的比较 接下来走入整体&#xff1a; 先根遍历解法&#xff1a; 首先 每个点其实都有范围&#xff0c;比如根节点的范围在(-INF,INF)&…

Vue.js+SpringBoot开发企业项目合同信息系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 合同审批模块2.3 合同签订模块2.4 合同预警模块2.5 数据可视化模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 合同审批表3.2.2 合同签订表3.2.3 合同预警表 四、系统展示五、核心代码5.1 查询合同…

线性代数基础概念和在AI中的应用

基本概念 线性代数是数学的一个分支&#xff0c;专注于向量、向量空间&#xff08;也称为线性空间&#xff09;、线性变换和矩阵的研究。这些概念在数据科学、人工智能、工程学和物理学等多个领域都有广泛应用。以下是这些基本概念的详细解释和它们在数据处理和AI中的应用。 …

Java语言: JVM

1.1 内存管理 1.1.1 JVM内存区域 编号 名字 功能 备注 1 堆 主要用于存放新创建的对象 (所有对象都在这里分配内存) jdk1.8之后永久代被替换成为了元空间&#xff08;Metaspace&#xff09; 2 方法区(加、常、静、即) 被虚拟机加载的类信息(版本、字段、方法、接口…

Qt学习--多态(虚函数)

这次来分享多态的概念&#xff0c;这是比较重要的知识点 面向对象的三大特征&#xff1a;封装、继承、多态 首先&#xff1a;来点官方术语&#xff1a; 多态&#xff0c;通俗来讲就是多种形态&#xff0c;具体点就是去完成某个行为&#xff0c;当不同的对象去完成时会产生出…

深度解析 Android 系统属性

目录 Android系统属性 1.属性在哪里&#xff1f; 2.属性长什么样&#xff1f; 3.如何读写属性&#xff1a; 4.属性的作用 属性文件生成过程 如何添加系统属性 1.添加系统属性到 /system/build.prop 2.添加系统属性到 /vendor/build.prop 3.添加系统属性到 /product/b…

【C++】string 类---字符判断与大小写转换(超详细解析!)

目录 一、string 类的介绍 二、字符大小写转换与判断常用函数 &#x1f4a6; 字符大小写判断 ① isalpha() ② isalnum() ③ isdigit() ④ islower() ⑤ isupper() &#x1f4a6; 字符大小写转换 ① tolower() ✨方法一&#xff1a; ✨方法二&#xff1a; ② toupper() ✨方…

为什么说金融/财务人需要学Python??

Python 是财务人和金融人的多功能工具箱&#xff01;它有非常多的细分应用&#xff0c;接下来我将给你详细介绍为什么财务、金融方向需要学 Python 以及有哪些方向的细分应用&#xff01; 一、为什么金融/财务人需要 Python&#xff1f; 想在行业中崭露头角&#xff1f;那你可…

106 基于消息队列来做 mysql 大数据表数据的遍历处理

前言 最近有这样的一个需求, 我们存在一张 很大的 mysql 数据表, 数据量大概是在 六百万左右 然后 需要获取所有的记录, 将数据传输到 es 中 然后 当时 我就写了一个脚本来读取 这张大表, 然后 分页获取数据, 然后 按页进行数据处理 转换到 es 但是存在的问题是, 前面 还…

CTF题型 SSTI(2) Flask-SSTI典型题巩固

CTF题型 SSTI(2) Flask-SSTI典型题巩固 文章目录 CTF题型 SSTI(2) Flask-SSTI典型题巩固前记1.klf__sstiSSTI_Fuzz字典&#xff08;网上收集自己补充&#xff09; 2.klf_2数字问题如何解决了&#xff1f;|count |length都被禁&#xff1f; 3.klf_3 前记 从基础到自己构造paylo…

java String的深入了解

1、String 概述 &#xff08;1&#xff09;String 类在 java.lang 包下&#xff0c;所以使用的时候不需要导包。 &#xff08;2&#xff09;String 类代表字符串&#xff0c;Java程序中的所有字符串文字&#xff08;例如“abc”&#xff09;都被实现为此类的实例。也就是说&a…

【漏洞复现】用友U8Cloud base64 SQL注入漏洞

0x01 产品简介 用友U8 Cloud是用友推出的新一代云ERP&#xff0c;主要聚焦成长型、创新型企业&#xff0c;提供企业级云ERP整体解决方案。 0x02 漏洞概述 用友U8 Cloud 存在SQL注入漏洞&#xff0c;未授权的攻击者可通过此漏洞获取数据库权限&#xff0c;从而盗取用户数据&a…