一、新老设备驱动写法差异
设备驱动基本写法可以参见之前的文章:Linux字符设备驱动基本写法
register_chrdev 和 unregister_chrdev 这两个函数是老版本驱动使用的函数,现在新的字符设备驱动已经不再使用这两个函数。
主要原因是使用这两个函数我们需要确定哪些主设备号没有使用,而且注册的时候会将这个主设备号下面的所有次设备号都使用掉,而实际我们并没有这么多设备也不需要将所有次设备号都占住,这就造成了次设备号的浪费。
Linux 社区推荐使用动态分配设备号,在注册字符设备之前先申请一个设备号,系统会自动给你一个没有被使用的设备号,这样就避免了冲突。
设备号的申请函数如下:
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
dev:保存申请到的设备号。
baseminor: 次设备号起始地址, alloc_chrdev_region 可以申请一段连续的多个设备号,这些设备号的主设备号一样,但是次设备号不同,次设备号以 baseminor 为起始地址地址开始递增。一般 baseminor 为 0,也就是说次设备号从 0 开始。
count: 要申请的设备号数量。
name:设备名字。
对于主设备号已经确定,可以使用如下函数进行申请,dev_t from为所需设备号范围中的第一个,必须包括主设备号。
int register_chrdev_region(dev_t from, unsigned count, const char *name)
注销字符设备之后要释放掉设备号,设备号释放函数如下:
void unregister_chrdev_region(dev_t from, unsigned count)
from:要释放的设备号。
count: 表示从 from 开始,要释放的设备号数量
二、新驱动实例代码
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#define NEWCHRLED_CNT 1
#define NEWCHRLED_NAME "my_leddrv"
static struct gpio_desc *led_gpio;
static int led_drv_open(struct inode *node, struct file *file)
{
printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
gpiod_direction_output(led_gpio, 0);
return 0;
}
static int led_drv_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static int led_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
int result;
char status;
printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
result = copy_from_user(&status, buf, 1);
gpiod_set_value(led_gpio, status);
return 1;
}
static int led_drv_release(struct inode *node, struct file *file)
{
printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static struct file_operations led_drv_fops =
{
.owner = THIS_MODULE,
.open = led_drv_open,
.read = led_drv_read,
.write = led_drv_write,
.release = led_drv_release,
};
struct newchar_dev
{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct class *class; /* 类 */
struct device *device; /* 设备 */
};
struct newchar_dev myled;
static int led_gpio_probe(struct platform_device *pdev)
{
printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
led_gpio = gpiod_get(&pdev->dev, "led", 0);
if(IS_ERR(led_gpio))
{
printk(KERN_ERR "gpiod_get is err\r\n");
return -1;
}
/*
major = register_chrdev(0, "my_leddrv", &led_drv);
led_class = class_create(THIS_MODULE, "my_led_class");
if(IS_ERR(led_class))
{
printk(KERN_ERR "class_create is err\r\n");
}
device_create(led_class, NULL, MKDEV(major, 0), NULL, "my_led%d", 0);
*/
/* 注册字符设备驱动 */
/* 1、创建设备号 */
alloc_chrdev_region(&myled.devid, 0, NEWCHRLED_CNT, NEWCHRLED_NAME); /* 申请设备号 */
myled.major = MAJOR(myled.devid); /* 获取主设备号 */
myled.minor = MINOR(myled.devid); /* 获取次设备号 */
printk("myled major is %d, minor is %d\r\n",myled.major, myled.minor);
/* 2、初始化 cdev */
myled.cdev.owner = THIS_MODULE;
cdev_init(&myled.cdev, &led_drv_fops);
/* 3、添加一个 cdev */
cdev_add(&myled.cdev, myled.devid, NEWCHRLED_CNT);
/* 4、创建类 */
myled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);
/* 5、创建设备 */
myled.device = device_create(myled.class, NULL,myled.devid, NULL, NEWCHRLED_NAME);
return 0;
}
static int led_gpio_remove(struct platform_device *pdev)
{
printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
/* 注销字符设备 */
cdev_del(&myled.cdev);/* 删除 cdev */
unregister_chrdev_region(myled.devid, NEWCHRLED_CNT);
/* 删除设备文件和对应类 */
device_destroy(myled.class, myled.devid);
class_destroy(myled.class);
gpiod_put(led_gpio);
return 0;
}
static const struct of_device_id my_led[] =
{
{.compatible = "my,led_driver"},
{},
};
static struct platform_driver led_gpio_driver =
{
.probe = led_gpio_probe,
.remove = led_gpio_remove,
.driver =
{
.name = "led_gpio",
.of_match_table = my_led,
},
};
static int __init led_init(void)
{
int result;
printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
result = platform_driver_register(&led_gpio_driver);
return result;
}
static void __exit led_exit(void)
{
printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
platform_driver_unregister(&led_gpio_driver);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
上面的例子是用之前写的老led驱动程序改写的,主要看下probe函数新老差异,老的代码可以看下被注释掉的部分。
三、实例测试
手动加载驱动ko文件,可以看到/dev/目录下生成了主设备号为240,次设备号为0的字符设备,与我们在probe函数加的内核主次设备号打印相匹配,程序符合预期。
[root:/mnt]# insmod myleddrv_cdev.ko
[ 495.380082] /home/book/code/test/myleddrv_cdev.c led_init line is 161
[ 495.396821] /home/book/code/test/myleddrv_cdev.c led_gpio_probe line is 77
[root:/mnt]#
[root:/mnt]# ls -al /dev/my_leddrv
crw------- 1 root root 240, 0 Jan 1 00:08 /dev/my_leddrv
[root:/mnt]# echo 0 > /dev/my_leddrv
[ 615.510401] /home/book/code/test/myleddrv_cdev.c led_drv_open line is 27
[ 615.518102] /home/book/code/test/myleddrv_cdev.c led_drv_write line is 42
[ 615.527225] /home/book/code/test/myleddrv_cdev.c led_drv_write line is 42
[ 615.535099] /home/book/code/test/myleddrv_cdev.c led_drv_release line is 50
[root:/mnt]# echo 1 > /dev/my_leddrv
[ 622.995421] /home/book/code/test/myleddrv_cdev.c led_drv_open line is 27
[ 623.005806] /home/book/code/test/myleddrv_cdev.c led_drv_write line is 42
[ 623.013132] /home/book/code/test/myleddrv_cdev.c led_drv_write line is 42
[ 623.022285] /home/book/code/test/myleddrv_cdev.c led_drv_release line is 50
加载驱动之后,内核dmesg打印如下:
[ 495.380082] /home/book/code/test/myleddrv_cdev.c led_init line is 161
[ 495.396821] /home/book/code/test/myleddrv_cdev.c led_gpio_probe line is 77
[ 495.404145] myled major is 240, minor is 0
与我们在/dev/目录下查询到的一致。