在struct file_operations结构体中获取struct cdev,struct device以及自定义数据结构

  • Post author:
  • Post category:其他


在正点原子的linux驱动教程中,dev_t设备号、struct cdev、struct class、struct device等结构体都是存放在一个自定义结构体中的,并不是定义成全局变量。这个自定义结构体所占用的内存则是在probe函数中由kzalloc函数分配,例如:

struct test_data
{
	struct gpio_desc *gpios[4];
	dev_t devid;
	u8 devid_valid;
	struct cdev cdev;
	struct class *class;
	struct device *device;
};

struct test_data *data = devm_kzalloc(&pdev->dev, sizeof(struct test_data), GFP_KERNEL);

正点原子说这样做才显得“专业”,然而正点原子并没有告诉我们,在struct file_operations的open、release、read、write函数中进行文件操作的时候,如何获取这个自定义结构体以及其中的成员变量。

struct file_operations *fops是由cdev_init的第二个参数设置的。根据Linux官方文档

Character device drivers — The Linux Kernel documentation (linux-kernel-labs.github.io)

可知,open函数有一个struct inode *inode参数,我们可以从这个参数下手,先通过inode->i_cdev访问到struct cdev *结构体,而这个cdev刚好是我们struct test_data自定义结构体的成员变量,因此可以由container_of宏获取struct test_data:

static int test_open(struct inode *inode, struct file *file)
{
	struct test_data *data;
	
	data = container_of(inode->i_cdev, struct test_data, cdev);
	file->private_data = data;
	return 0;
}

struct file_operations的read和write函数只有struct file *没有struct inode *参数,因此我们必须在open函数中将struct test_data *自定义结构体的指针赋给file->private_data保存起来,这样read和write函数就能获取了。

static ssize_t test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
	struct test_data *data = file->private_data;
	
	return size;
}

static ssize_t test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
	struct test_data *data = file->private_data;
	
	return size;
}

注意:linux中存在两套GPIO API函数:(基于GPIO number的)GPIO legacy API和(基于GPIO descriptor的)GPIOD API。正点原子讲的是已经淘汰的GPIO legacy API(以gpio_开头的函数),但在实际项目中最好使用最新的GPIOD API(以gpiod_开头的函数,头文件为<linux/gpio/consumer.h>)。

【示例程序】

test.c:

(提示:必须使用platform驱动模型,使compatible属性与设备树匹配上后,pinctrl-names和pinctrl-0属性才能生效!正点原子的那个程序只用了module_init,没有使用platform_driver模型,所以pinctrl-names和pinctrl-0属性无效!这点一定要注意)

/* 参考文档: https://linux-kernel-labs.github.io/refs/heads/master/labs/device_drivers.html */
#include <asm/uaccess.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/of.h>
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>

struct test_data
{
	struct gpio_desc *gpios[4];
	u8 gpios_state[4]; // imx6ull平台无法通过gpiod_get_value读取输出的电平
	dev_t devid;
	struct cdev cdev;
	struct class *class;
	struct device *device;
};

struct test_file
{
	struct test_data *data;
	char msg[100];
	int len;
};

static int test_open(struct inode *inode, struct file *file)
{
	char part[20];
	int i;
	struct test_file *p;
	
	p = kzalloc(sizeof(struct test_file), GFP_KERNEL);
	if (p == NULL)
		return -ENOMEM;
	file->private_data = p;
	
	p->data = container_of(inode->i_cdev, struct test_data, cdev);
	for (i = 0; i < ARRAY_SIZE(p->data->gpios); i++)
	{
		snprintf(part, sizeof(part), "GPIO%d: %c\n", i + 1, p->data->gpios_state[i] ? 'H' : 'L');
		strlcat(p->msg, part, sizeof(p->msg));
	}
	p->len = strlen(p->msg);
	return 0;
}

static int test_release(struct inode *inode, struct file *file)
{
	if (file->private_data != NULL)
		kfree(file->private_data);
	return 0;
}

static ssize_t test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
	int ret;
	struct test_file *p = file->private_data;
	
	if (*off + size > p->len)
		size = p->len - *off;
	if (size > 0)
	{
		ret = copy_to_user(buf, p->msg, size);
		if (ret != 0)
			return -EFAULT;
		*off += size;
	}
	return size;
}

static ssize_t test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
	char id, level;
	int i, j;
	struct test_file *p = file->private_data;
	
	pr_err("%s: size=%d\n", __FUNCTION__, size);
	for (i = 0; i + 1 < size; i += 2)
	{
		get_user(id, &buf[i]);
		get_user(level, &buf[i + 1]);
		if (id >= '1' && id <= '4')
		{
			j = id - '1';
			if (level == 'H' || level == 'h')
			{
				pr_err("GPIO%c: H\n", id);
				gpiod_set_value(p->data->gpios[j], 1);
				p->data->gpios_state[j] = 1;
			}
			else if (level == 'L' || level == 'l')
			{
				pr_err("GPIO%c: L\n", id);
				gpiod_set_value(p->data->gpios[j], 0);
				p->data->gpios_state[j] = 0;
			}
		}
	}
	return size;
}

static long test_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	return 0;
}

static const struct file_operations test_fops = {
	.owner = THIS_MODULE,
	.open = test_open,
	.release = test_release,
	.read = test_read,
	.write = test_write,
	.unlocked_ioctl = test_unlocked_ioctl
};

static int test_probe(struct platform_device *pdev)
{
	int i, ret;
	struct test_data *data;
	
	/* 创建结构体 */
	pr_err("test_probe(0x%p);\n", pdev);
	data = devm_kzalloc(&pdev->dev, sizeof(struct test_data), GFP_KERNEL);
	if (data == NULL)
	{
		pr_err("devm_kzalloc() failed\n");
		return -ENOMEM;
	}
	platform_set_drvdata(pdev, data);
	
	/* 创建设备 */
	ret = alloc_chrdev_region(&data->devid, 100, 1, "mydeviceid");
	if (ret != 0)
	{
		pr_err("alloc_chrdev_region() failed\n");
		return ret;
	}
	pr_err("Device ID: %d,%d\n", MAJOR(data->devid), MINOR(data->devid));
	cdev_init(&data->cdev, &test_fops);
	ret = cdev_add(&data->cdev, data->devid, 1);
	if (ret != 0)
	{
		pr_err("cdev_add() failed\n");
		goto err_chrdev;
	}
	
	/* 创建设备文件 */
	data->class = class_create(THIS_MODULE, "classname");
	if (IS_ERR(data->class))
	{
		pr_err("class_create() failed\n");
		ret = PTR_ERR(data->class);
		goto err_chrdev;
	}
	data->device = device_create(data->class, NULL, data->devid, NULL, "devicename");
	if (IS_ERR(data->device))
	{
		pr_err("device_create() failed\n");
		ret = PTR_ERR(data->device);
		goto err_class;
	}
	
	/* 获取GPIO端口 */
	for (i = 0; i < ARRAY_SIZE(data->gpios); i++)
	{
		data->gpios[i] = devm_gpiod_get_index(&pdev->dev, NULL, i, GPIOD_OUT_LOW);
		if (IS_ERR(data->gpios[i]))
		{
			pr_err("devm_gpiod_get_index() failed when i=%d\n", i);
			ret = PTR_ERR(data->gpios[i]);
			goto err_device;
		}
	}
	
	return 0;
	
err_device:
	device_destroy(data->class, data->devid);
err_class:
	class_destroy(data->class);
err_chrdev:
	cdev_del(&data->cdev);
	unregister_chrdev_region(data->devid, 1);
	platform_set_drvdata(pdev, NULL);
	return ret;
}

static int test_remove(struct platform_device *pdev)
{
	struct test_data *data;
	
	pr_err("test_remove(0x%p);\n", pdev);
	data = platform_get_drvdata(pdev);
	if (data != NULL)
	{
		device_destroy(data->class, data->devid);
		class_destroy(data->class);
		cdev_del(&data->cdev);
		unregister_chrdev_region(data->devid, 1);
		platform_set_drvdata(pdev, NULL);
	}
	return 0;
}

static const struct of_device_id test_match_ids[] = {
	{.compatible = "mygpiotest,myboard2"},
	{}
};

MODULE_DEVICE_TABLE(of, test_match_ids);

static struct platform_driver test_driver = {
	.driver = {
		.name = "mytestdriver",
		.of_match_table = of_match_ptr(test_match_ids)
	},
	.probe = test_probe,
	.remove = test_remove
};

module_platform_driver(test_driver);

MODULE_AUTHOR("Oct1158");
MODULE_LICENSE("GPL");

设备树(arch/arm/boot/dts/myboard2.dts):

(提示:使用GPIOD API时,GPIO是高电平有效还是低电平有效,只需在设备树里面设置,C代码里面不需要关心)

#include "imx6ull-14x14-emmc-4.3-800x480-c.dts"

/ {
	mygpiotest {
		compatible = "mygpiotest,myboard2";
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_mygpiotest>;
		gpios = <&gpio4 26 GPIO_ACTIVE_HIGH /* 引脚名: CSI_DATA05, 板上丝印: CAMERA D5  */
		         &gpio4 25 GPIO_ACTIVE_HIGH /* 引脚名: CSI_DATA04, 板上丝印: CAMERA D4 */
				 &gpio4 28 GPIO_ACTIVE_LOW /* 引脚名: CSI_DATA07, 板上丝印: CAMERA D7 */
				 &gpio4 27 GPIO_ACTIVE_LOW /* 引脚名: CSI_DATA06, 板上丝印: CAMERA D6 */
		>;
	};
};

&iomuxc {
	imx6ul-evk {
		/delete-node/csi1grp;
		
		pinctrl_mygpiotest: mygpiotestgrp {
			fsl,pins = <
				MX6UL_PAD_CSI_DATA05__GPIO4_IO26 0x10b0 /* 引脚名为CSI_DATA05的引脚复用为GPIO4_IO26 */
				MX6UL_PAD_CSI_DATA04__GPIO4_IO25 0x10b0
				MX6UL_PAD_CSI_DATA07__GPIO4_IO28 0x10b0
				MX6UL_PAD_CSI_DATA06__GPIO4_IO27 0x10b0
			>;
		};
	};
};

/* 删除不需要的节点 */
&i2c2 {
	/delete-node/ov5640@3c;
};
&csi {
	status = "disabled";
	/delete-node/port;
};

【程序运行结果】

Ubuntu 18.04.6 LTS alientek ttymxc0

alientek login: oct1158
Password:
Last login: Sun May 22 10:24:56 CST 2022 on ttymxc0
Welcome to Ubuntu 18.04.6 LTS (GNU/Linux 4.1.15-g06f53e4 armv7l)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage
This system has been minimized by removing packages and content that are
not required on a system that users do not log into.

To restore this content, you can run the 'unminimize' command.
oct1158@alientek:~$ cd learn_drivers/dts_myboard2/gpiospi_test/
oct1158@alientek:~/learn_drivers/dts_myboard2/gpiospi_test$ make
make -C /home/oct1158/learn_drivers/kernel M=/home/oct1158/learn_drivers/dts_myboard2/gpiospi_test EXTRA_CFLAGS=-fno-pic modules
make[1]: Entering directory '/home/oct1158/learn_drivers/kernel'
  CC [M]  /home/oct1158/learn_drivers/dts_myboard2/gpiospi_test/test.o
  Building modules, stage 2.
  MODPOST 1 modules
  LD [M]  /home/oct1158/learn_drivers/dts_myboard2/gpiospi_test/test.ko
make[1]: Leaving directory '/home/oct1158/learn_drivers/kernel'
oct1158@alientek:~/learn_drivers/dts_myboard2/gpiospi_test$ sudo insmod test.ko
[sudo] password for oct1158:
[  111.169447] test_probe(0x940ffe00);
[  111.172970] Device ID: 248,100
oct1158@alientek:~/learn_drivers/dts_myboard2/gpiospi_test$ sudo cat /dev/devicename
GPIO1: L
GPIO2: L
GPIO3: L
GPIO4: L
oct1158@alientek:~/learn_drivers/dts_myboard2/gpiospi_test$ sudo bash -c 'echo -n 1H2H3H4H > /dev/devicename'
[  140.189943] test_write: size=8
[  140.193029] GPIO1: H
[  140.195225] GPIO2: H
[  140.199770] GPIO3: H
[  140.201988] GPIO4: H
oct1158@alientek:~/learn_drivers/dts_myboard2/gpiospi_test$ sudo cat /dev/devicename
GPIO1: H
GPIO2: H
GPIO3: H
GPIO4: H
oct1158@alientek:~/learn_drivers/dts_myboard2/gpiospi_test$ sudo bash -c 'echo -n 1L2L3H4H > /dev/devicename'
[  156.230123] test_write: size=8
[  156.233207] GPIO1: L
[  156.235401] GPIO2: L
[  156.237591] GPIO3: H
[  156.242095] GPIO4: H
oct1158@alientek:~/learn_drivers/dts_myboard2/gpiospi_test$ sudo cat /dev/devicename
GPIO1: L
GPIO2: L
GPIO3: H
GPIO4: H
oct1158@alientek:~/learn_drivers/dts_myboard2/gpiospi_test$ sudo bash -c 'echo -n 1H2H3L4L > /dev/devicename'
[  167.522299] test_write: size=8
[  167.525384] GPIO1: H
[  167.527579] GPIO2: H
[  167.531759] GPIO3: L
[  167.533975] GPIO4: L
oct1158@alientek:~/learn_drivers/dts_myboard2/gpiospi_test$ sudo cat /dev/devicename
GPIO1: H
GPIO2: H
GPIO3: L
GPIO4: L
oct1158@alientek:~/learn_drivers/dts_myboard2/gpiospi_test$ lsmod
Module                  Size  Used by
test                    3736  0
oct1158@alientek:~/learn_drivers/dts_myboard2/gpiospi_test$ sudo rmmod test
[  181.098802] test_remove(0x940ffe00);
oct1158@alientek:~/learn_drivers/dts_myboard2/gpiospi_test$

那如果是使用miscdevice驱动框架的程序呢?在这种程序里,因为要在module_init和module_exit的函数中分别进行misc_register和misc_deregister操作,所以struct miscdevice通常是一个全局变量,这个时候就没有必要采用这种所谓的“专业方法”了。因为在struct file_operations的open函数里,只能通过file->private_data获取到struct miscdevice *的指针,毫无意义。struct miscdevice和struct file_operations这两个结构体里面也没有专门的地方存放用户数据指针。

static int test_open(struct inode *inode, struct file *file)
{
	struct miscdevice *mdev = file->private_data;
	
	pr_err("mdev=0x%p\n", mdev);
	return 0;
}



版权声明:本文为ZLK1214原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。