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

引言

在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驱动添加字符设备接口

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

chr_driver.c

cpp 复制代码
#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节点

相关推荐
AlfredZhao6 小时前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户97183563346612 小时前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪14 小时前
linux 拷贝文件或目录到指定的位置
linux
摇滚侠1 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
bush41 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5201 天前
Linux 11 动态监控指令top
linux
不会C语言的男孩1 天前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言
古城小栈1 天前
Unix 与 Linux 异同小叙
linux·服务器·unix
凡人叶枫1 天前
Effective C++ 条款42:了解 typename 的双重意义
java·linux·服务器·c++
2601_961875241 天前
决战申论100题2026|最新|范文
linux·容器·centos·debian·ssh·fabric·vagrant