1、字符设备传统开发模板
字符设备驱动框架,首先我们需要去用module_init这个宏去修饰整个驱动的入口函数,用module_exit去修饰整个驱动的出口函数,然后还需要用MODULE_LICENSE用于声明模块的许可证类型。
在入口函数里面我们需要注册字符设备,使用register_chrdev()注册字符设备,使用class_create来注册区分一个类,在用device_create来为这个类创造一个设备节点,供我们在linux根目录下的dev目录下给应用层程序访问。在register_chrdev注册时最重要的是要提供字符设备的结构体file_operations,这个结构体启动了内核和应用层交互数据的功能。需要实现file_operations结构体的open,write,read,release函数,对应于应用层的open,write,read,close
#include "asm-generic/errno-base.h"
#include "asm-generic/gpio.h"
#include "asm/uaccess.h"
#include <linux/module.h>
#include <linux/poll.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>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/timer.h>
struct gpios
{
int gpio;
int irq;
char *name;
};
static gpois gpios_s[2]={
{115,0,"sr51"},
};
static int major=0;
static struct class *cls=NULL;
static ssize_t sr51_read(struct file *file, char __user *buf, size_t size, loff_t * loff)
{
char ker_buf[2]={0};
int err=0;
err=copy_from_user(ker_buf, buf, 1);
if(err)
{
return -EINVAL;
}
ker_buf[1]=gpio_get_value(gpios_s[0].gpio);
copy_to_user(buf, ker_buf,2)
return 2;
}
static int sr51_release(struct inode *node, struct file *file)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
printk("fd close\n");
return 0;
}
static int sr51_open(struct inode *node, struct file *file)
{
printk("%s %s line %d\n",__FILE__,__FUNCTION__,__LINE__);
return 0;
}
ssize_t sr51_write(struct file *file, const char __user *buf , size_t size , loff_t * loff)
{
printk("%s %s line %d\n",__FILE__,__FUNCTION__,__LINE__);
return 0;
}
static struct file_operations sr51_driver={
.owner=THIS_MODULE,
.open=sr51_open,
.write=sr51_write,
.read=sr51_read,
.release=sr51_release,
};
static int __init sr51_driver_init(void)
{
int err;
err=gpio_request(gpios_s[0].gpio, gpios[0].name);
if (err < 0) {
printk("can not request gpio %s %d\n", gpios_s[0].gpio, gpios[0].name);
return -ENODEV;
}
gpio_direction_output(gpios[0].gpio, 1);
major=register_chrdev(0, "sr51",&sr51_driver);
cls=class_create(THIS_MODULE,"sr51_class");
if(IS_ERR(cls))
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
unregister_chrdev(major,"sr51");
return PTR_ERR(cls);
}
device_create(&cls, NULL, MKDEV(major, 0), NULL, "sr51");
return 0;
}
static void __exit sr51_driver_exit(void)
{
device_destroy(cls, MKDEV(major, 0));
unregister_chrdev(major,"sr51");
class_destroy(cls);
gpio_free(gpios_s[0].gpio);
}
module_init(sr51_driver_init);
module_exit(sr51_driver_exit);
MODULE_LICENSE("GPL");
2、不使用register_chrdev的另外一类驱动的注册方法
register_chrdev其实是cdev封装好的一个函数,他其实也会调用cdev_init(),cdev_add(),
初始化设备号的另一种方法
1、int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
1、dev_t *dev
这个参数是一个指向 dev_t 类型的指针。dev_t 是一个在 <sys/types.h> 中定义的数据类型,用于表示设备号。在调用 alloc_chrdev_region() 函数时,你需要传递一个 dev_t 类型的变量的地址给这个函数。如果函数成功地为你的字符设备驱动程序分配了一个设备号,那么它会将这个设备号存储在 dev 指针所指向的变量中。
2、unsigned baseminor次设备号
3、unsigned count几个次设备号
4、const char *name设备名称
2、void cdev_init(struct cdev *cdev, const struct file_operations *fops);
cdev设备号
fops字符设备结构体
3、int cdev_add(struct cdev *p, dev_t num, unsigned int count);
struct cdev *p:指向已经初始化的cdev结构体的指针。
dev_t num:分配给这个字符设备的设备号。这通常是通过alloc_chrdev_region()函数获得的。
unsigned int count:通常设置为1,除非你的字符设备支持多个次设备号。
3、当下用的最多的驱动开发,设备树加platform总线模型
设备树的作用:用于描述硬件的具体的功能。像gpio控制器,ii2c控制器,io多路复用控制器,spi控制器,中断控制器gic都需要使用设备树去描述这个硬件。
platform总线模型:他是一个虚拟的总线模型,内核里面使用bus_type这个结构体来描述这个虚拟总线,这个虚拟中心又分为左边一部分和右边一部分,左边一部分使用platform_device来描述,这个结构体它可以由程序员编写,也可以直接使用设备树自动生成(目前使用的是这个),而右边是使用的是platform_driver这个是由当下的驱动程序员来编写的,虚拟总线的功能就是让这两个设备相匹配,如果匹配到了,那么驱动就会被加载进内核当中。
3.1他们是怎么匹配到,然后加载进入内核的详细过程
1、设备树通常在系统引导阶段由引导加载器(如U-Boot)从预定义的配置文件加载,并传递给内核,它包含了硬件的层次结构和属性(如内存地址、中断号和GPIO配置等)。
2、设备树中的节点会被映射到内核中的platform_device结构体实例。每个设备节点通常包含compatible属性,此属性用于标识设备类型和需要加载的驱动。(第一次会比对)
3、设备通过platform_device_register函数在内核中注册。这个过程会为设备创建一个platform_device实例,并将其添加到platform_bus_type所代表的总线上。
设备注册后,内核会使用platform_bus_type中的.match函数来查找和该设备兼容的驱动程序。
4、驱动程序使用platform_driver_register函数注册自己,并提供一个platform_driver结构体,其中包含驱动程序的名称、匹配表、以及回调函数,如.probe和.remove。
.match回调函数确定一个驱动程序是否适用于某个特定的设备,这通常是基于设备的compatible属性进行匹配的。
5、当设备和驱动成功匹配时,驱动的.probe函数被调用,这是驱动初始化设备的地方。例如,驱动可能会配置设备使用的GPIO引脚,设置初始状态,或注册更高层的服务(如输入设备或网络接口)。
4、设备树的使用
设备树的常见属性:
compatible:用于在驱动匹配时使用的匹配字符串。
#address-cells:常见于父节点中用于规定子节点使用多少字长来描述硬件的起始地址。
#size-cells:常见于父节点中用于规定子节点使用多少字长来描述硬件的地址长度。
/ {
#address-cells = <1>;
#size-cells = <1>;
memory {
reg = <0x80000000 0x20000000>;
};
};
status:表示这个设备的状态
reg 节点:的本意是 register,用来表示寄存器地址。
但是在设备树里,它可以用来描述一段空间。反正对于 ARM 系统,寄存器和
内存是统一编址的,即访问寄存器时用某块地址,访问内存时用某块地址,在访
问方法上没有区别
chosen 节点:可以通过设备树文件给内核传入一些参数。
platform设备模型示例代码
#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>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
struct gpio_key{
int gpio;
struct gpio_desc *gpiod;
int flag;
int irq;
} ;
static struct gpio_key *gpio_keys_100ask;
static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{
struct gpio_key *gpio_key = dev_id;
int val;
val = gpiod_get_value(gpio_key->gpiod);
printk("key %d %d\n", gpio_key->gpio, val);
return IRQ_HANDLED;
}
/* 1. 从platform_device获得GPIO
* 2. gpio=>irq
* 3. request_irq
*/
static int gpio_key_probe(struct platform_device *pdev)
{
int err;
struct device_node *node = pdev->dev.of_node;//获取设备节点//来自于设备树就不是空的
int count;
int i;
enum of_gpio_flags flag;
unsigned flags = GPIOF_IN;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
count = of_gpio_count(node);//获取节点数量
if (!count)
{
printk("%s %s line %d, there isn't any gpio available\n", __FILE__, __FUNCTION__, __LINE__);
return -1;
}
gpio_keys_100ask = kzalloc(sizeof(struct gpio_key) * count, GFP_KERNEL);
for (i = 0; i < count; i++)
{
gpio_keys_100ask[i].gpio = of_get_gpio_flags(node, i, &flag);//得到gpio的状态
if (gpio_keys_100ask[i].gpio < 0)
{
printk("%s %s line %d, of_get_gpio_flags fail\n", __FILE__, __FUNCTION__, __LINE__);
return -1;
}
gpio_keys_100ask[i].gpiod = gpio_to_desc(gpio_keys_100ask[i].gpio);
gpio_keys_100ask[i].flag = flag & OF_GPIO_ACTIVE_LOW;
if (flag & OF_GPIO_ACTIVE_LOW)
flags |= GPIOF_ACTIVE_LOW;
err = devm_gpio_request_one(&pdev->dev, gpio_keys_100ask[i].gpio, flags, NULL);
gpio_keys_100ask[i].irq = gpio_to_irq(gpio_keys_100ask[i].gpio);
}
for (i = 0; i < count; i++)
{
err = request_irq(gpio_keys_100ask[i].irq, gpio_key_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "100ask_gpio_key", &gpio_keys_100ask[i]);//请求对应的中断
}
return 0;
}
static int gpio_key_remove(struct platform_device *pdev)
{
//int err;
struct device_node *node = pdev->dev.of_node;
int count;
int i;
count = of_gpio_count(node);
for (i = 0; i < count; i++)
{
free_irq(gpio_keys_100ask[i].irq, &gpio_keys_100ask[i]);
}
kfree(gpio_keys_100ask);
return 0;
}
static const struct of_device_id ask100_keys[] = {
{ .compatible = "100ask,gpio_key" },
{ },
};
/* 1. 定义platform_driver */
static struct platform_driver gpio_keys_driver = {
.probe = gpio_key_probe,
.remove = gpio_key_remove,
.driver = {
.name = "100ask_gpio_key",
.of_match_table = ask100_keys,
},
};
/* 2. 在入口函数注册platform_driver */
static int __init gpio_key_init(void)
{
int err;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
err = platform_driver_register(&gpio_keys_driver);
return err;
}
/* 3. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数
* 卸载platform_driver
*/
static void __exit gpio_key_exit(void)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
platform_driver_unregister(&gpio_keys_driver);
}
/* 7. 其他完善:提供设备信息,自动创建设备节点 */
module_init(gpio_key_init);
module_exit(gpio_key_exit);
MODULE_LICENSE("GPL");