在正点原子的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;
}