目录
- 一、多节点概念
- 1、所用到的结构体说明
- 2、函数接口主要是read和write函数
- 2.1、把应用层的数据拷贝给底层
- 2.2、把应用层的数据拷贝给底层
- 3、应用层的read和write函数
- 4、底层的read和write函数
- 二、ioctl控制命令接口
- 1、概念
- 2、函数介绍应用层和驱动层
- 三、代码与现象
- 1.编写LED灯的多节点驱动实现流水灯
- 2.编写KEY按键驱动实现底层按键按下反馈给应用层
- 3.编写beep的驱动代码可以使用ioctl控制
学习目标:
1.编写LED灯的多节点驱动实现流水灯
2.编写KEY按键驱动实现底层按键按下反馈给应用层
3.编写beep的驱动代码可以使用ioctl控制
一、多节点概念
这里所谓的多节点驱动其实指的就是一个设备对应一个设备节点。比如我现在有 4 个 led 等,你怎么做到单独的去控制每一盏灯。此时如果你想单独的去操控一个 LED 灯,那么你就需要单独给他们每一个灯去申请注册一个设备节点。多节点对于一类设备他们的主设备号必然是一样的,只不过是一类设备当中的不同的子设备。
比如有四个灯,那么他们的设备编号如下:
LED1 crwx----------- 350 0 /dev/led1
LED2 crwx----------- 350 1 /dev/led2
LED3 crwx----------- 350 2 /dev/led3
LED4 crwx----------- 350 3 /dev/led4
此时你在去使用应用层的 open 去打开单独的一个硬件设备节点,这样你就可以单独的操作这个设备。
1、所用到的结构体说明
这里内核是通过你 open 打开对应节点的设备号去区分不同的设备的,靠的就是函数的参数。
int (*open) (struct inode *, struct file *);
struct inode {
i_rdev:如果索引节点代表设备文件,则表示设备的主设备号和次设备号
};
struct file{
void *private_data;
}
private_data:他是一个私有数据,他主要就是给其他函数使用的
2、函数接口主要是read和write函数
函数接口里主要讲解的就是 read 和 write是 对数据进行读写操作的。内核是不允许让应用层和底层进行直接数据交互的,就是为了保护内核的安全。那么内核层和应用能不能进行数据交互呢?肯定是可以的,但是必须要使用内核提供的函数,进行数据的交互。所谓的数据交互就是应用层把数据拷贝给内核层,内核层把数据拷贝给应用层这就是所谓的数据交互。上层的写函数和读函数是不能直接给底层的写函数和读函数进行数据,若想直接交互的话,这里就需要使用以下两个函数
2.1、把应用层的数据拷贝给底层
函数功能:把应用层的数据拷贝给底层
函数原型:unsigned long copy_from_user(void *to, const void __user *from,
unsigned long n)
函数头文件:#include <linux/uaccess.h>
函数参数:to:他就是你要保存应用层数据的位置,把要接受的数据保存到哪里
from:他就是底层写函数的里的 buf
n:就是数据的大小 — from
函数返回值:成功返回 0 失败返回负数
2.2、把应用层的数据拷贝给底层
函数功能:把内核层的数据拷贝给应用层
函数原型:unsigned long copy_to_user(void __user *to, const void *from,
unsigned long n)
函数头文件:#include <linux/uaccess.h>
函数参数:to:就是底层读函数里的 buf
from: 要拷贝底层的具体数据
n:就是数据的大小 即,from大小
函数返回值:成功返回 0 失败返回负数
3、应用层的read和write函数
ssize_t read(int fd, void *buf, size_t count)
参数:fd: — 打开文件的描述符
buf: — 读取数据保存的位置
count:---- 读取数据的大小
ssize_t write(int fd, const void *buf, size_t count)
参数:fd: — 打开文件的描述符
buf: — 你要写入文件的数据的保存位置
count:---- 写入数据的大小
4、底层的read和write函数
**ssize_t (*read) (struct file *fp, char __user buf, size_t size, loff_t offset)
参数:fp:保存文件信息的核心结构体
buf:保存底层给应用层读取数据的保存位置 ,里面就保存内核要给用层的数据。
size:就数据的大小 — 就是 buf 的大小
offset:当前文件光标的位置
**ssize_t (write) (struct file fp, const char __user buf, size_t size,loff_toffset)
参数:fp:保存文件信息的核心结构体
buf:保存应用层给底层写人数据保存位置
size:就数据的大小 — 就是 buf 的大小
offset:当前文件光标的位置
二、ioctl控制命令接口
1、概念
ioctl用来做一些控制命令接口 类似:msgctl、semctl、shmctl 等(ioctl 是设备
驱动程序中对设备的 I/O 通道进行管理的函数。所谓对 I/O 通道进行管理,就
是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等)
2、函数介绍应用层和驱动层
函数功能:做一些控制命令的接口
函数原型:int ioctl(int fd, unsigned long request, …)
函数头文件:#include <sys/ioctl.h>
函数参数:fd:就是 open 打开的文件描述符
request:cmd 就是控制的命令
函数返回值:成功返回 0 失败返回负数
函数功能:做控制的命令
函数原型:**long (unlocked_ioctl) (struct file fp, unsigned int cmd, unsigned long arg);
函数头文件:#include <linux/fs.h>
函数参数:fp:保存文件信息结构体
cmd:就是你应用层传递过来的命令 — request
arg:暂时不用管
函数返回值:成功返回 0 失败负数
三、代码与现象
1.编写LED灯的多节点驱动实现流水灯
内核层:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include <linux/gpio.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
int led[2]={0};
const char *devs_name[2]={"myqxjled1","myqxjled2"};
int i;
dev_t dev[2]={0};
struct cdev mydev;
struct class *myclass=NULL;
int myled_open (struct inode *inode, struct file *fp)
{
if(inode->i_rdev==dev[0])
{
gpio_set_value(led[0],1);
}
if(inode->i_rdev==dev[1])
{
gpio_set_value(led[1],1);
}
printk("myled open 正确打开\n");
return 0;
}
int myled_close (struct inode *inode, struct file *fp)
{
if(inode->i_rdev==dev[0])
{
gpio_set_value(led[0],0);
}
if(inode->i_rdev==dev[1])
{
gpio_set_value(led[1],0);
}
printk("myled close 关闭正确\n");
return 0;
}
struct file_operations myfops={
.open = myled_open,
.release = myled_close,
};
int myled_probe(struct platform_device *pdev)
{
printk("探测函数:设备端和驱动端匹配成功\n");
//led[0] led[1]返回的是gpio编口号
led[0]=of_get_named_gpio(pdev->dev.of_node,"leds-gpios",0);//获得设备树的属性
led[1]=of_get_named_gpio(pdev->dev.of_node,"leds-gpios",1);
gpio_request(led[0], "led1 pc5");//21 申请你要使用 gpio 口的资源
gpio_request(led[1], "led2 pc6");//22
gpio_direction_output(led[0],0);//配置 gpio 口的工作模式
gpio_direction_output(led[1],0);
alloc_chrdev_region(dev,0,2,"led");//动态申请设备号 linux2.6或杂项类型
dev[1]=dev[0]+1;
cdev_init(&mydev,&myfops);//初始化核心结构体
cdev_add(&mydev,dev[0],2);//向内核去申请 linux2.6 字符设备
myclass=class_create(THIS_MODULE,"class_led");//创建类
if(myclass == NULL)
{
printk("class_create error\n");
printk("class_create 类创建失败\n");
return -1;
}
for(i=0;i<=1;i++)
{
device_create(myclass,NULL,dev[i],NULL,devs_name[i]);//自动创建设备节点
}
return 0;
}
int myled_remove (struct platform_device *pdev)
{
printk("移除函数成功\n");
device_destroy(myclass,dev[0]);//销毁设备节点 在/dev/name ---device_create
device_destroy(myclass,dev[1]);
class_destroy(myclass);//销毁类 --class_create
cdev_del(&mydev);//释放申请的字符设备 --cdev_add
unregister_chrdev_region(dev[0],2);//释放申请的设备号 ---alloc_chrdev_region
gpio_free(led[0]);// 释放 gpio 口资源 ----gpio_request
gpio_free(led[1]);
return 0;
}
struct of_device_id mydev_node={
.compatible="xyd-led",
};
struct platform_driver drv={
.probe = myled_probe,
.remove = myled_remove,
.driver = {
.name = "myxyd_leds",//与设备端必须保持一致
.of_match_table = &mydev_node,
},
};
static int __init myled_init(void)
{
platform_driver_register(&drv);
return 0;
}
static void __exit myled_exit(void)
{
platform_driver_unregister(&drv);
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");
应用层:
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
int fd = 0;
while(1)
{
fd = open("/dev/myqxjled1",O_RDWR); // --- 底层的open函数
sleep(1);
close(fd);//底层的close
sleep(1);
fd=open("/dev/myqxjled2",O_RDWR); // --- 底层的open函数
sleep(1);
close(fd);//底层的close
sleep(1);
}
return 0;
}
编译:
obj-m += led_driver.o #最终生成模块的名字就是 led.ko
KDIR:=/home/stephen/RK3588S/kernel #他就是你现在rk3588s里内核的路径
CROSS_COMPILE_FLAG=/home/stephen/RK3588S/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-
#这是你的交叉编译器路径 --- 这里你也要替换成你自己的交叉编译工具的路径
all:
make -C $(KDIR) M=$(PWD) modules ARCH=arm64 CROSS_COMPILE=$(CROSS_COMPILE_FLAG)
aarch64-none-linux-gnu-gcc app.c -o app
#调用内核层 Makefile 编译目标为 modules->模块 文件在当前路径
# 架构 ARCH=arm64
clean:
rm -f *.o *.mod.o *.mod.c *.symvers *.markers *.order app *.mod
灯现象:
2.编写KEY按键驱动实现底层按键按下反馈给应用层
内核层:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include <linux/gpio.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <linux/uaccess.h>
int key[2]={0};
int beep = 0;
int key_value = 0;
int beep_value[2] = {0};
dev_t dev;
struct cdev mydev;
struct class *myclass=NULL;
int myled_open (struct inode *inode, struct file *fp)
{
printk("myled open 正确打开\n");
return 0;
}
int myled_close (struct inode *inode, struct file *fp)
{
printk("myled close 关闭正确\n");
return 0;
}
ssize_t mykey_read (struct file *fp, char __user *buf, size_t size, loff_t *offset)
{
unsigned long ret=0;
if(gpio_get_value(key[0])==0)
{
key_value=1;
printk("按键1被按下\n");
}
else if(gpio_get_value(key[1])==0)
{
key_value=2;
printk("按键2被按下\n");
}
ret=copy_to_user(buf,&key_value,4);
if(ret<0)
{
printk("copy_to_user 错误\n");
return -1;
}
key_value=0;
return 0;
}
ssize_t mykey_write (struct file *fp, const char __user *buf, size_t size, loff_t *offset)
{
unsigned long ret=0;
ret=copy_from_user(beep_value,buf,4);
if(ret<0)
{
printk("copy_from_user 错误\n");
return -1;
}
if(beep_value[0]==1)
{
gpio_set_value(beep,beep_value[0]);
}
else if(beep_value[1]==0)
{
gpio_set_value(beep,beep_value[1]);
}
return 0;
}
struct file_operations myfops={
.open = myled_open,
.release = myled_close,
.read = mykey_read,
.write = mykey_write,
};
int myled_probe(struct platform_device *pdev)
{
printk("探测函数:设备端和驱动端匹配成功\n");
//led[0] led[1]返回的是gpio编口号
key[0]=of_get_named_gpio(pdev->dev.of_node,"devices-gpios",2);//获得设备树的属性
key[1]=of_get_named_gpio(pdev->dev.of_node,"devices-gpios",3);
beep = of_get_named_gpio(pdev->dev.of_node,"devices-gpios",4);
gpio_request(key[0], "key1 pa7");//21 申请你要使用 gpio 口的资源
gpio_request(key[1], "key2 pb1");//22
gpio_request(beep, "beep pa4");//36
gpio_direction_input(key[0]);//配置 gpio 口的工作模式
gpio_direction_input(key[1]);
gpio_direction_output(beep,0);//高电平叫
alloc_chrdev_region(&dev,0,1,"led");//动态申请设备号 linux2.6或杂项类型
cdev_init(&mydev,&myfops);//初始化核心结构体
cdev_add(&mydev,dev,1);//向内核去申请 linux2.6 字符设备
myclass=class_create(THIS_MODULE,"class_led");//创建类
if(myclass == NULL)
{
printk("class_create error\n");
printk("class_create 类创建失败\n");
return -1;
}
device_create(myclass,NULL,dev,NULL,"mykey");//自动创建设备节点
return 0;
}
int myled_remove (struct platform_device *pdev)
{
printk("移除函数成功\n");
device_destroy(myclass,dev);//销毁设备节点 在/dev/name ---device_create
class_destroy(myclass);//销毁类 --class_create
cdev_del(&mydev);//释放申请的字符设备 --cdev_add
unregister_chrdev_region(dev,1);//释放申请的设备号 ---alloc_chrdev_region
gpio_free(key[0]);// 释放 gpio 口资源 ----gpio_request
gpio_free(key[1]);
gpio_free(beep);
return 0;
}
struct of_device_id mydev_node={
.compatible="xyd-device",
};
struct platform_driver drv={
.probe = myled_probe,
.remove = myled_remove,
.driver = {
.name = "xyd-device",//与设备端必须保持一致
.of_match_table = &mydev_node,
},
};
static int __init myled_init(void)
{
platform_driver_register(&drv);
return 0;
}
static void __exit myled_exit(void)
{
platform_driver_unregister(&drv);
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");
应用层:
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
int fd = 0;
int beep_value[2]={1,0};
int key_value=0;
fd = open("/dev/mykey",O_RDWR); // --- 底层的open函数
while(1)
{
write(fd,&beep_value[0],4);
usleep(500000);
write(fd,&beep_value[1],4);
usleep(500000);
read(fd,&key_value,4);
if(key_value==1)
{
printf("第%d个按键按下\n",key_value);
}
else if(key_value==2)
{
printf("第%d个按键按下\n",key_value);
}
usleep(500000);
}
close(fd);//底层的close
return 0;
}
编译:
obj-m += led_driver.o #最终生成模块的名字就是 led.ko
KDIR:=/home/stephen/RK3588S/kernel #他就是你现在rk3588s里内核的路径
CROSS_COMPILE_FLAG=/home/stephen/RK3588S/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-
#这是你的交叉编译器路径 --- 这里你也要替换成你自己的交叉编译工具的路径
all:
make -C $(KDIR) M=$(PWD) modules ARCH=arm64 CROSS_COMPILE=$(CROSS_COMPILE_FLAG)
aarch64-none-linux-gnu-gcc app.c -o app
#调用内核层 Makefile 编译目标为 modules->模块 文件在当前路径
# 架构 ARCH=arm64
clean:
rm -f *.o *.mod.o *.mod.c *.symvers *.markers *.order app *.mod
3.编写beep的驱动代码可以使用ioctl控制
驱动层:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include <linux/gpio.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <linux/uaccess.h>
int key[2]={0};
int beep = 0;
int key_value = 0;
int beep_value[2] = {0};
dev_t dev;
struct cdev mydev;
struct class *myclass=NULL;
int myled_open (struct inode *inode, struct file *fp)
{
printk("myled open 正确打开\n");
return 0;
}
int myled_close (struct inode *inode, struct file *fp)
{
printk("myled close 关闭正确\n");
return 0;
}
ssize_t mykey_read (struct file *fp, char __user *buf, size_t size, loff_t *offset)
{
unsigned long ret=0;
if(gpio_get_value(key[0])==0)
{
key_value=1;
printk("按键1被按下\n");
}
else if(gpio_get_value(key[1])==0)
{
key_value=2;
printk("按键2被按下\n");
}
ret=copy_to_user(buf,&key_value,4);
if(ret<0)
{
printk("copy_to_user 错误\n");
return -1;
}
key_value=0;
return 0;
}
ssize_t mykey_write (struct file *fp, const char __user *buf, size_t size, loff_t *offset)
{
unsigned long ret=0;
ret=copy_from_user(&beep_value,buf,4);
if(ret<0)
{
printk("copy_from_user 错误\n");
return -1;
}
if(beep_value[0]==1)
{
gpio_set_value(beep,beep_value[0]);
}
else if(beep_value[1]==0)
{
gpio_set_value(beep,beep_value[1]);
}
return 0;
}
long mybeep_ioctl (struct file *fp, unsigned int cmd, unsigned long arg)
{
if(cmd==1)
{
gpio_set_value(beep,1);
}
if(cmd==0)
{
gpio_set_value(beep,0);
}
return 0;
}
struct file_operations myfops={
.open = myled_open,
.release = myled_close,
.read = mykey_read,
.write = mykey_write,
.unlocked_ioctl=mybeep_ioctl,
};
int myled_probe(struct platform_device *pdev)
{
printk("探测函数:设备端和驱动端匹配成功\n");
//led[0] led[1]返回的是gpio编口号
key[0]=of_get_named_gpio(pdev->dev.of_node,"devices-gpios",2);//获得设备树的属性
key[1]=of_get_named_gpio(pdev->dev.of_node,"devices-gpios",3);
beep = of_get_named_gpio(pdev->dev.of_node,"devices-gpios",4);
gpio_request(key[0], "key1 pa7");//21 申请你要使用 gpio 口的资源
gpio_request(key[1], "key2 pb1");//22
gpio_request(beep, "beep pa4");//36
gpio_direction_input(key[0]);//配置 gpio 口的工作模式
gpio_direction_input(key[1]);
gpio_direction_output(beep,0);//高电平叫
alloc_chrdev_region(&dev,0,1,"led");//动态申请设备号 linux2.6或杂项类型
cdev_init(&mydev,&myfops);//初始化核心结构体
cdev_add(&mydev,dev,1);//向内核去申请 linux2.6 字符设备
myclass=class_create(THIS_MODULE,"class_led");//创建类
if(myclass == NULL)
{
printk("class_create error\n");
printk("class_create 类创建失败\n");
return -1;
}
device_create(myclass,NULL,dev,NULL,"mykey");//自动创建设备节点
return 0;
}
int myled_remove (struct platform_device *pdev)
{
printk("移除函数成功\n");
device_destroy(myclass,dev);//销毁设备节点 在/dev/name ---device_create
class_destroy(myclass);//销毁类 --class_create
cdev_del(&mydev);//释放申请的字符设备 --cdev_add
unregister_chrdev_region(dev,1);//释放申请的设备号 ---alloc_chrdev_region
gpio_free(key[0]);// 释放 gpio 口资源 ----gpio_request
gpio_free(key[1]);
gpio_free(beep);
return 0;
}
struct of_device_id mydev_node={
.compatible="xyd-device",
};
struct platform_driver drv={
.probe = myled_probe,
.remove = myled_remove,
.driver = {
.name = "xyd-device",//与设备端必须保持一致
.of_match_table = &mydev_node,
},
};
static int __init myled_init(void)
{
platform_driver_register(&drv);
return 0;
}
static void __exit myled_exit(void)
{
platform_driver_unregister(&drv);
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");
应用层:
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
int main(int argc,char *argv[])
{
int fd = 0;
fd = open("/dev/mykey",O_RDWR); // --- 底层的open函数
while(1)
{
ioctl(fd,1);
usleep(500000);
ioctl(fd,1);
usleep(500000);
}
close(fd);//底层的close
return 0;
}
编译:
obj-m += led_driver.o #最终生成模块的名字就是 led.ko
KDIR:=/home/stephen/RK3588S/kernel #他就是你现在rk3588s里内核的路径
CROSS_COMPILE_FLAG=/home/stephen/RK3588S/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-
#这是你的交叉编译器路径 --- 这里你也要替换成你自己的交叉编译工具的路径
all:
make -C $(KDIR) M=$(PWD) modules ARCH=arm64 CROSS_COMPILE=$(CROSS_COMPILE_FLAG)
aarch64-none-linux-gnu-gcc app.c -o app
#调用内核层 Makefile 编译目标为 modules->模块 文件在当前路径
# 架构 ARCH=arm64
clean:
rm -f *.o *.mod.o *.mod.c *.symvers *.markers *.order app *.mod