目录
1.如何编写驱动程序
2.编写驱动程序
①确定主设备号
register_chrdev函数
②file_operations结构体
③实现对应的函数,填入结构体
copy_from_user函数
copy_to_user函数
④注册驱动程序
方式一
方式二
⑤入口函数
⑥出口函数
⑦提供设备信息,创建设备节点
class_create()函数
device_creat()函数
3.驱动程序源码
4.测试程序源码
5.Makefile
6.APP使用驱动的4种方式
7.中断引入
1.如何编写驱动程序
2.编写驱动程序
①确定主设备号
register_chrdev函数
函数原型:
int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
函数功能:
为字符设备注册一个主号码。
入参:
1. unsigned int major:用于动态分配的主要设备号或0,如果给0内核回自动分配主设备号
2. const char *name:这一系列设备的名称
3. const struct file_operations:与此设备相关联的文件操作
回参:
返回主设备号
②file_operations结构体
static void __init hello_exit(void)
{
class_destroy(hello_class);
device_destroy(hello_class, MKDEV(major, 0));
unregister_chrdev(major,"hello");
}
③实现对应的函数,填入结构体
copy_from_user函数
函数原型:
copy_from_user(void *to, const void __user *from, unsigned long n)
作用:
将@form地址中的数据拷贝到@to地址中去,拷贝长度是n
入参:
1. to 将数据拷贝到内核的地址
2. from 需要拷贝数据的地址
3. n 拷贝数据的长度(字节)
回参:
失败返回没有被拷贝的字节数,成功返回0.
copy_to_user函数
函数原型:
long copy_to_user(void __user *to, const void *from, unsigned long n);
作用:
将内核空间的数据复制到用户空间。
入参:
1. void __user *to:这是用户空间的目标地址,数据将被复制到这个地址。
2. const void *from:这是内核空间的源地址,数据将从这个地址被复制。
3. unsigned long n:要复制的字节数。
回参:
返回值是一个 long 类型;
如果复制成功,返回 0;
如果在复制过程中发生错误,返回一个负数,表示未能成功复制的字节数。
④注册驱动程序
方式一
register_chrdev函数
函数原型:
int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
函数功能:
为字符设备注册一个主号码。
入参:
1. unsigned int major:用于动态分配的主要设备号或0
2. const char *name:这一系列设备的名称
3. const struct file_operations:与此设备相关联的文件操作
方式二
alloc_chrdev_region函数
函数原型:
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
函数功能:
alloc_chrdev_region 更简便、更智能的方法是让内核给我们自动分配一个主设备号
入参:
1. dev_t *dev : 输出型参数 ,就是要分配的主 次 设备号
2. unsigned baseminor : 次设备号的 起始 号
3. unsigned count : 几个 次设备号
4. const char *name : 设备名字
回参:
小于<0 代表错误
函数使用:
int ret; //记录返回值
static dev_t mydev; /*这里用来接收 一个主次 设备号 , */
dev_t 类型的变量 !!!
ret = alloc_chrdev_region(&mydev, 12, MYCNT, MYNAME); /* 自动分配 主次设备号*/
12: 代表次设备号 从 12 开始
cdv_init函数
函数原型:
int cdv_init(struct device *dev, const struct device_driver *drv, void *data);
函数功能:
cdv_init 函数主要负责完成以下任务:
初始化设备相关的硬件资源,如寄存器配置、中断设置等。
将设备与对应的驱动进行关联和注册。
分配和初始化与设备驱动相关的内存和数据结构。
入参:
1. struct device *dev:指向要初始化的设备结构体的指针,包含了设备的相关信息,如设备的名称、属 性等。
2. const struct device_driver *drv:指向设备驱动结构体的指针,包含了驱动的相关信息,如驱动的名称、操作函数等。
3. void *data:一个通用的指针,用于传递特定于驱动或设备的私有数据。
回参:
返回 0 表示初始化成功。
返回非零值(通常是负数)表示初始化过程中出现错误,不同的非零值可能代表不同的错误码。
cdv_add函数
函数原型:
int cdv_add(struct some_struct *param1, int param2, void *param3);
函数功能:
“cdv_add”函数用于在 Linux 驱动中执行添加某个对象、资源或配置的操作。
入参:
1. struct some_struct *param1:可能是一个指向特定数据结构的指针,该数据结构包含了与要添加的对象相关的详细信息。
2. int param2:可能是一个标志位、索引值或其他整数类型的参数,用于指定添加操作的某些特定条件或选项。
3. void *param3:通用的指针,可能用于传递额外的自定义数据。
回参:
返回 0 表示添加成功。
返回负数表示添加过程中出现错误,不同的负数可能代表不同的错误类型。
⑤入口函数
static int __init hello_init(void)
写好入口函数后,需要使用module_init告诉内核这是入口函数
module_init(hello_init);
⑥出口函数
static void __init hello_exit(void)
同理,写好出口函数后,需要使用module_exit告诉内核这是出口函数
module_exit(hello_init);
⑦提供设备信息,创建设备节点
class_create()函数
函数原型:
#define class_create(owner, name) \
({ \
static struct lock_class_key __key; \
__class_create(owner, name, &__key); \
})
函数功能:
宏class_create()用于动态创建设备的逻辑类,并完成部分字段的初始化,然后将其添加进Linux内核系统中。此函数的执行效果就是在/sys/class/目录下创建一个新的文件夹,此文件夹的名字为此函数的第二个输入参数,但此文件夹是空的。宏class_create()在实现时,调用了函数__class_create()。
入参:
1. owner:一个struct module结构体类型的指针,指向函数__class_create()即将创建的、“拥有”这个struct class的模块。一般赋值为THIS_MODULE,此结构体的详细定义见文件include/linux/module.h。
2. name:char类型的指针,代表即将创建的struct class变量的名字,用于给struct class的name字段赋值。通俗地说,就是指向struct class名称的字符串的指针。
device_creat()函数
函数原型:
struct device *device_create(struct class *cls, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...);
函数功能:
函数device_create()用于动态地创建逻辑设备,并对新的逻辑设备类进行相应的初始化,\将其与此函数的第一个参数所代表的逻辑类关联起来,然后将此逻辑设备加到Linux内核系统的设备驱动程序模型中。\函数能够自动地在/sys/devices/virtual目录下创建新的逻辑设备目录,在/dev目录下创建与逻辑类对应的设备文件。
入参:
1.struct class *cls:输入参数代表与即将创建的逻辑设备相关的逻辑类。
2.struct device *parent:输入参数代表即将创建的逻辑设备的父设备的指针,子设备与父设备的关系是:当父设备不可用时,子设备不可用,子设备依赖父设备,父设备不依赖子设备。
3.dev_t devt:输入参数是逻辑设备的设备号。
4.void *drvdata:输入参数是void类型的指针,代表回调函数的输入参数。
5.const char *fmt:输入参数是逻辑设备的设备名,即在目录/sys/devices/virtual/和/dev创建的逻辑设备目录的目录名。
3.驱动程序源码
#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>
static int major;
char kernel_buf[1024];
#define MIN(a,b) a > b ? a : b
static struct class *hello_class;
static int hello_open (struct inode *node, struct file *file)
{
return 0;
}
static ssize_t hello_write (struct file *file, const char __user *buf, size_t size, loff_t *setoff)
{
int err;
err = copy_from_user(kernel_buf, buf, MIN(1024, size));
return MIN(1024, size);
}
static ssize_t hello_read (struct file *file, char __user *buf, size_t size, loff_t *setoff)
{
int err;
err = copy_to_user(buf, kernel_buf, MIN(1024, size));
return MIN(1024, size);
}
static int hello_release (struct inode *node, struct file *file)
{
return 0;
}
static const struct file_operations hello_drv = {
.owner = THIS_MODULE,
.open = hello_open,
.read = hello_read,
.write = hello_write,
.release = hello_release,
};
static int __init hello_init(void)
{
int err;
major = register_chrdev(0,"hello",&hello_drv);
hello_class = class_create(THIS_MODULE, "hello");
err = PTR_ERR(hello_class);
if (IS_ERR(hello_class))
{
unregister_chrdev(major, "hello");
return -1;
}
device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello");
return 0;
}
static void __init hello_exit(void)
{
class_destroy(hello_class);
device_destroy(hello_class, MKDEV(major, 0));
unregister_chrdev(major,"hello");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
4.测试程序源码
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char **argv)
{
int fd;
int len;
char buf[1024];
if(argc < 2)
{
printf("Usage : %s -w <string>", argv[0]);
printf(" %s -r\n", argv[0]);
return -1;
}
fd = open("/dev/hello", O_RDWR);
if (fd == -1)
{
printf("can not open file /dev/hello\n");
return -1;
}
if ((strcmp(argv[1], "-w")) == 0 && (argc == 3))
{
len = strlen(argv[2]) + 1;
len = len < 1024 ? len : 1024;
write(fd, argv[2], len);
}
else
{
len = read(fd, buf, 1024);
buf[1023] = '\0';
printf("APP read : %s\n", buf);
}
close(fd);
return 0;
}
5.Makefile
# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH, 比如: export ARCH=arm64
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
# 2.3 PATH, 比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
# 请参考各开发板的高级用户使用手册
KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88
all:
make -C $(KERN_DIR) M=`pwd` modules
$(CROSS_COMPILE)gcc -o hello_drv_test hello_drv_test.c
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
rm -f hello_drv_test
obj-m += hello_drv.o
使用Makefile编译通过后,将.ko文件和可执行程序放到开发板上,先使用insmod装载驱动,然后再执行程序,完成后通过rmmod卸载驱动。
6.APP使用驱动的4种方式
驱动程序:提供能力,不提供策略。
1.阻塞(休眠唤醒):条件不满足时休眠,条件满足由别人唤醒。
2.非阻塞(查询):不断的查询。
3.poll(定个闹钟)
4.异步通知
7.中断引入
中断处理流程:
①保存现场
②分辨中断源
③调用中断处理函数
④恢复现场
课程链接:
12_Hello驱动上机实验_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1XK411D7wK?p=13&spm_id_from=pageDriver&vd_source=3a9afee9fda50350a1c881b4325e007d