驱动程序固定格式
- 依赖<linux/init.h>和<linux/module.h>
- 入口函数 module_init(fun_init) 是int静态函数
- 出口函数 module_exit(fun_exit) 是voidt静态函数
- 许可证 MODULE_LICCENSE("GPL")
固定格式
#include <linux/init.h>
#include <linux/module.h>
// 入口文件
static int __init fun_init(void){
return 0;
}
module_init(fun_init);
// 出口文件
static int __exit fun_exit(void){
return 0;
}
module_exit(fun_exit);
MODULE_LICCENSE("GPL");
通用编译文件
- 编译指令 make arch=x86/arm modename=1_hello(文件名不带后缀)
- 清除编译后文件 make clean
- 加载模块 sudo insmod 1_hello.ko
- 加载模块和依赖 sudo modprobe -a 1_hello.ko // -r卸载 -l列出已加载 -v显示详细信息 -a加载所有依赖 -F显示模块配置
- 卸载模块 sudo rmmod 1_hello.ko
- 查看模块信息 sudo lsmod 1_hello.ko
- 查看调试信息(后十条,后创建的在后) dmesg | tail
- 查看驱动程序是否在运行(前十条,后创建的在前) lsmod | head
#arch 如果是第一次出现 赋值 arm架构,如果不是 命令行有参数传进来 按照参数指定
arch ?= arm
modename ?= test
ifeq ($(arch),arm)
#源代码目录准备
#板载源代码
KERNELDIR:=/home/linux/quDong/linux-5.4.31
else
#乌班图源代码
KERNELDIR:=/lib/modules/5.4.0-26-generic/build
endif
#执行一个shell 命令
PWD:=$(shell pwd)
#换行 tab键
#模块位置
all:
make -C $(KERNELDIR) M=$(PWD) modules
#依赖 命令
clean:
make -C $(KERNELDIR) M=$(PWD) clean
#最后形成的模块名字是什么?
#指定要构建的模块名字 对应的 1_hello这个源文件
obj-m:=$(modename).o
驱动程序内打印信息
- 格式 1是级别1~7,参数二打印内容,参数参以后都是打印内容的值,参数一和参数二没有逗号
- printk(1"xxxx%d",6);
- 等级可以不写,不写等级默认为4
- 在黑白终端中,要打印级别高于4级才显示
- 基本上和c语言的printf一样,只是多了个等级
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
// 入口文件
static int __init fun_init(void)
{
printk(KERN_EMERG"1\n");
printk(KERN_ALERT "2\n");
printk(KERN_CRIT"3\n");
printk(KERN_ERR"4\n");
printk(KERN_WARNING"5\n");
printk(KERN_NOTICE"6\n");
printk(KERN_INFO"7\n");
printk(KERN_DEBUG"7\n");
return 0;
}
// 出口文件
static void __exit fun_exit(void)
{
printk("exit");
}
module_init(fun_init);
module_exit(fun_exit);
MODULE_LICENSE("GPL");
驱动程序传参
执行驱动程序时传递参数
- sudo insmod chuancan.ko a=10 b=500 // 指定上通过文件后x=值传递,可以传多个
- 只能传递基本类型(char,bool,int,long,short,byte,ushort,uint),数组array,字符串string(charp)
- 依赖头文件<linux/moduleparam.h>
- 接收基本类型 module_param
- 接收数组类型 module_param_array
- 接收字符串类型 module_param_string
- 接收函数的三个/四个参数 变量名称 变量类型 长度(基本类型没有) 权限(一般设置为0664)
- 写入参数说明MODULE_PARM_DESC(arr, "An array of integers")
- 查看参数说明指令 modinfo chuancan.ko
- 三种类型的参数指令 sudo insmod 4_params.ko a=123 b="hello" arr=10,20,30
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
int a = 100;
module_param(a, int, 0664);
MODULE_PARM_DESC(a, "a说明");
// 修改:字符串参数使用字符数组
char b[100] = "test";
module_param_string(b, b, sizeof(b), 0664);
MODULE_PARM_DESC(b, "b说明");
// 数组参数
int arr[10] = {0}; // 最多接收10个元素
int arr_len = 0;
module_param_array(arr, int, &arr_len, 0664);
MODULE_PARM_DESC(arr, "arr说明");
// 入口函数
static int __init fun_init(void)
{
int i;
printk(KERN_INFO "a = %d, str = %s\n", a, b);
for (i = 0; i < arr_len; i++) {
printk(KERN_INFO "arr[%d] = %d\n", i, arr[i]);
}
return 0;
}
// 退出函数
static void __exit fun_exit(void)
{
printk(KERN_INFO "Module exit.\n");
}
module_init(fun_init);
module_exit(fun_exit);
MODULE_LICENSE("GPL");
导出符号表(通过导出符号表,实现驱动模块和驱动模块之间的函数相互调用)
- 依赖头文件<linux/export.h>
- 导出符号表会有部分安全问题,例如:符号重定义、版本依赖、版权问题、安全漏洞等
- 导出函数方法EXPORT_SYMBOL_GPL(funadd);//funadd是要导出的函数
操作顺序
- 安装要先安装导出模块再安装使用模块,卸载要相反
安装顺序
- 安装导出模块 sudo insmod a.ko
- 查看 dmesg | tail
- 安装使用模块 sudo insmod b.ko
- 查看 dmesg | tail
卸载顺序
- 卸载导出模块 sudo rmmod b.ko
- 查看 dmesg | tail
- 卸载使用模块 sudo rmmod a.ko
- 查看 dmesg | tail
导出模块
#include <linux/init.h>
#include <linux/module.h>
// 导出符号表头文件
#include <linux/export.h>
// a + b 的和
int funadd(int a, int b)
{
return a + b;
}
// 导出符号表
EXPORT_SYMBOL_GPL(funadd);
// 入口文件
static int __init fun_init(void)
{
printk("这里导出符号表funadd\n");
return 0;
}
// 出口文件
static void __exit fun_exit(void)
{
printk("exit");
}
module_init(fun_init);
module_exit(fun_exit);
MODULE_LICENSE("GPL");
使用模块
#include <linux/init.h>
#include <linux/module.h>
int funadd(int a,int b);
// 入口文件
static int __init fun_init(void)
{
printk("这里使用符号表funadd\n");
printk("res = %d\n",funadd(100,200));
return 0;
}
// 出口文件
static void __exit fun_exit(void)
{
printk("exit");
}
module_init(fun_init);
module_exit(fun_exit);
MODULE_LICENSE("GPL");
应用程序调用字符设备驱动程序的函数
- 要给驱动程序分配一个主设备号major
- 分配设备号依赖头文件<linux/fs.h>
- 可以调用api生成,不会重复
- 入口程序生成字符设备驱动函数主设备号
- register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
- 出口函数释放设备号
- 创建文件操作结构体 struct file_operations {}
- 文件操作结构体有一大堆成员函数,常用的四个开关读写open、release、read、write
运行流程
- 编译驱动程序,编译程序里需要打印主设备号
- 安装驱动程序 sudo insmod cdev.ko
- 查看信息,看看主设备号是多少 dmesg | tail
- 根据主设备号手动创建设备节点(也可以在代码里创建) sudo mknod /dev/mycdev c 240 0
- 编译应用程序,在编译程序调用对应方法,看是否会执行驱动程序的代码(可以在驱动程序增加打印)
驱动程序源码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
int major; // 主设备号
#define CNAME "chardev" // 字符设备驱动名称
int myopen(struct inode *node, struct file *file)
{
printk("this is my %s test \n", __func__);
return 0;
}
int myclose(struct inode *node, struct file *file)
{
printk("this is my%s\n", __func__);
return 0;
}
ssize_t myread(struct file *file, char __user *ubuf, size_t size, loff_t *offs)
{
printk("this is my%s\n", __func__);
return 0;
}
ssize_t mywrite(struct file *file, const char __user *ubuf, size_t size, loff_t *offs)
{
printk("this is my%s\n", __func__);
return 0;
}
const struct file_operations fop = {
.open = myopen,
.release = myclose,
.write = mywrite,
.read = myread
};
static int __init fun_init(void)
{
printk("this is %s-%d test\n", __func__, __LINE__);
major = register_chrdev(0, CNAME, &fop);
if (major < 0)
{
printk("字符设备注册失败\n");
return -1;
}
else
{
printk("字符设备注册成功,major:%d\n", major); // 打印主设备号
}
return 0;
}
static void __exit fun_exit(void)
{
unregister_chrdev(major, CNAME);
printk("this is %s %d exit\n", __func__, __LINE__);
}
module_init(fun_init);
module_exit(fun_exit);
// 证书信息
MODULE_LICENSE("GPL");
应用程序源代码
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/fcntl.h>
int main()
{
int fd = open("/dev/mycdev", O_RDWR);
if (fd < 0)
{
perror("open error\n");
}
printf("open /dev/mycdevs success\n");
char buf[128] = "Write succ";
write(fd, buf, strlen(buf));
close(fd);
return 0;
}
驱动程序和应用程序相互传递数据
- 依赖头文件#include <linux/uaccess.h>
- 在驱动文件的write函数中调用copy_from_user函数将应用程序的数据传递到驱动程序
- 在驱动文件的read函数中调用copy_to_user函数将驱动程序的数据传递到应用程序
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
int major; // 主设备号
#define CNAME "chardev" // 字符设备驱动名称
// 内核缓冲区
char kbuf[128] = "";
int myopen(struct inode *node, struct file *file)
{
printk("this is my %s test \n", __func__);
return 0;
}
int myclose(struct inode *node, struct file *file)
{
printk("this is my%s\n", __func__);
return 0;
}
ssize_t myread(struct file *file, char __user *ubuf, size_t size, loff_t *offs)
{
int ret;
printk("this is my%s\n", __func__);
if (size > sizeof(kbuf))
{
size = sizeof(kbuf);
}
ret = copy_to_user(ubuf, kbuf, size);
if (ret < 0)
{
printk("copy to user error\n");
return -EIO;
}
return size;
}
ssize_t mywrite(struct file *file, const char __user *ubuf, size_t size, loff_t *offs)
{
int ret;
printk("this is my%s\n", __func__);
if (size > sizeof(kbuf))
{
size = sizeof(kbuf);
}
ret = copy_from_user(kbuf, ubuf, size);
if (ret < 0)
{
printk("copy from user error\n");
return -EIO;
}
return size;
}
const struct file_operations fop = {
.open = myopen,
.release = myclose,
.write = mywrite,
.read = myread};
static int __init fun_init(void)
{
printk("this is %s-%d test\n", __func__, __LINE__);
major = register_chrdev(0, CNAME, &fop);
if (major < 0)
{
printk("字符设备注册失败\n");
return -1;
}
else
{
printk("字符设备注册成功,major:%d\n", major);
}
return 0;
}
static void __exit fun_exit(void)
{
unregister_chrdev(major, CNAME);
printk("this is %s %d exit\n", __func__, __LINE__);
}
module_init(fun_init);
module_exit(fun_exit);
// 证书信息
MODULE_LICENSE("GPL");
代码中创建设备节点
- 依赖头文件<linux/device.h>和<linux/fs.h>
- 创建目录指针struct class *cls 和设备文件指针 struct device *dev
- 在入口函数中,先创建目录class_create(THIS_MODULE,CNAME)// 参数一当前内核模块(基本上固定是THIS_MODULE),参数二类名
- 生成32位综合设备号MKDEV(major,0);//参数1:设备号,参数2:次设备号
- 在入口函数中,再创建设备文件device_create(cls,NULL,MKDEV(major,0),NULL,CNAME),参数1:父目录指针,参数2:一般NULL,参数3:设备号,参数4:传递的数据,参数5:文件名称
- 设备文件创建失败要销毁类class_destroy(cls)
- 出口函数要先销毁设备再销毁类
- 应用程序open的设备文件名要和驱动程序用代码创建的文件名一致
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/errno.h>
int major; // 主设备号
#define CNAME "chardev" // 字符设备驱动名称
// 内核缓冲区
char kbuf[128] = "";
int myopen(struct inode *node, struct file *file)
{
printk("this is my %s test \n", __func__);
return 0;
}
int myclose(struct inode *node, struct file *file)
{
printk("this is my%s\n", __func__);
return 0;
}
ssize_t myread(struct file *file, char __user *ubuf, size_t size, loff_t *offs)
{
int ret;
printk("this is my%s\n", __func__);
if (size > sizeof(kbuf))
{
size = sizeof(kbuf);
}
ret = copy_to_user(ubuf, kbuf, size);
if (ret < 0)
{
printk("copy to user error\n");
return -EIO;
}
return size;
}
ssize_t mywrite(struct file *file, const char __user *ubuf, size_t size, loff_t *offs)
{
int ret;
printk("this is my%s\n", __func__);
if (size > sizeof(kbuf))
{
size = sizeof(kbuf);
}
ret = copy_from_user(kbuf, ubuf, size);
if (ret < 0)
{
printk("copy from user error\n");
return -EIO;
}
return size;
}
const struct file_operations fop = {
.open = myopen,
.release = myclose,
.write = mywrite,
.read = myread
};
// 创建设备目录指针
struct class *cls;
// 创建设备文件指针
struct device *dev;
static int __init fun_init(void)
{
printk("this is %s-%d test\n", __func__, __LINE__);
major = register_chrdev(0, CNAME, &fop);
if (major < 0)
{
printk("字符设备注册失败\n");
return -1;
}
printk("字符设备注册成功,major:%d\n", major);
cls = class_create(THIS_MODULE,CNAME);
if(IS_ERR(cls)){
printk("class create error\n");
return PTR_ERR(cls);
}
// 创建设备文件
dev= device_create(cls,NULL,MKDEV(major,0),NULL,CNAME);
if(IS_ERR(dev)){
// 销毁目录
class_destroy(cls);
// 注销
unregister_chrdev(major,CNAME);
printk("class create error\n");
return PTR_ERR(dev);
}
printk("设备目录和设备文件创建成功\n");
return 0;
}
static void __exit fun_exit(void)
{
unregister_chrdev(major, CNAME);
class_destroy(cls);
printk("this is %s %d exit\n", __func__, __LINE__);
}
module_init(fun_init);
module_exit(fun_exit);
// 证书信息
MODULE_LICENSE("GPL");
ioremap驱动程序操作寄存器
- 依赖头文件<linux/io.h>
- 通过void *ioremap(unsigned long phys_addr, unsigned long size)拿到寄存器指针,参数1物理地址,参数2大小
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/io.h>
// 内核缓冲区
char kbuf[128] = "";
#define RCC_BASE 0x50000000 // AHB4总线 GPIOE
#define AHB4_CLOCK_ENABLE (RCC_BASE+0xA28) // AHB4总线地址
// PE10 - LED1
#define GPIOE_BASE 0x50006000
#define GPIO_MODER (GPIOE_BASE+0x00) // 模式寄存器
#define GPIO_OPTYE (GPIOE_BASE+0x04) // 输出类型寄存器
#define GPIO_OUTDATA (GPIOE_BASE+0x14) // 输出数据寄存器
unsigned int *ahb4_clock_enable = NULL;
unsigned int *gpioe_mode = NULL;
unsigned int *gpioe_10_odr = NULL;
int major; // 主设备号
#define CNAME "leddev" // 字符设备驱动名称
int myopen(struct inode *node, struct file *file)
{
printk("this is my %s test \n", __func__);
// 在open函数中完成映射
// GPIOE时钟 ok
ahb4_clock_enable = (unsigned int *)ioremap(AHB4_CLOCK_ENABLE,sizeof(unsigned int));
if(ahb4_clock_enable == NULL){
printk("ioremap clock error");
return -1;
}
// 驱动第四个位置的GPIOE
*ahb4_clock_enable |= (1 << 4);
printk("rcc_ahb4_clockenable success %d\n",__LINE__);
// 在write函数中 完成控制
gpioe_mode = (unsigned int *)ioremap(GPIO_MODER,sizeof(unsigned int));
if(gpioe_mode == NULL ){
printk("gpioe_mode error\n");
return -1;
}
printk("gpioe_mode set success %d\n",__LINE__);
// 模式寄存器:01输出模式
*gpioe_mode &=~(0x11<<(2*10)); //清空初始化
// 赋值0x11
*gpioe_mode|=0x11<<(2*10);
// 输出数据寄存器
gpioe_10_odr = (unsigned int *)ioremap(GPIO_OUTDATA,sizeof(unsigned int));
if(gpioe_10_odr==NULL){
printk("gpioe_10_odr error\n");
return -1;
}
printk("gpioe_10_odr set success%d\n",__LINE__);
// 输出数据,默认给低电平
*gpioe_10_odr &= ~(0x1<<10); // 关灯
// *gpioe_10_odr |=(0x1 << 10);
return 0;
}
int myclose(struct inode *node, struct file *file)
{
printk("this is my%s\n", __func__);
return 0;
}
ssize_t myread(struct file *file, char __user *ubuf, size_t size, loff_t *offs)
{
int ret;
printk("this is my%s\n", __func__);
if (size > sizeof(kbuf))
{
size = sizeof(kbuf);
}
ret = copy_to_user(ubuf, kbuf, size);
if (ret < 0)
{
printk("copy to user error\n");
return -EIO;
}
return size;
}
ssize_t mywrite(struct file *file, const char __user *ubuf, size_t size, loff_t *offs)
{
int ret;
printk("this is my%s\n", __func__);
if (size > sizeof(kbuf))
{
size = sizeof(kbuf);
}
ret = copy_from_user(kbuf, ubuf, size);
if (ret < 0)
{
printk("copy from user error\n");
return -EIO;
}
// 判断kbuf当中是否有数据来开关led
if(kbuf[0]==1){
*gpioe_10_odr |=(0x1<<10);
}else{
*gpioe_10_odr &= ~(0x1 << 10);
}
return size;
}
const struct file_operations fop = {
.open = myopen,
.release = myclose,
.write = mywrite,
.read = myread
};
// 创建设备目录指针
struct class *cls;
// 创建设备文件指针
struct device *dev;
static int __init fun_init(void)
{
printk("this is %s-%d test\n", __func__, __LINE__);
major = register_chrdev(0, CNAME, &fop);
if (major < 0)
{
printk("字符设备注册失败\n");
return -1;
}
printk("字符设备注册成功,major:%d\n", major);
cls = class_create(THIS_MODULE,CNAME);
if(IS_ERR(cls)){
printk("class create error\n");
return PTR_ERR(cls);
}
// 创建设备文件
dev= device_create(cls,NULL,MKDEV(major,0),NULL,CNAME);
if(IS_ERR(dev)){
// 销毁目录
class_destroy(cls);
// 注销
unregister_chrdev(major,CNAME);
printk("class create error\n");
return PTR_ERR(dev);
}
printk("设备目录和设备文件创建成功\n");
return 0;
}
static void __exit fun_exit(void)
{
unregister_chrdev(major, CNAME);
class_destroy(cls);
printk("this is %s %d exit\n", __func__, __LINE__);
}
module_init(fun_init);
module_exit(fun_exit);
// 证书信息
MODULE_LICENSE("GPL");
ioctl驱动程序操作寄存器
- 依赖头文件,驱动程序是<linux/ioctl.h>,应用程序是<sys/ioctl.h>
- 最好写一个头文件,使用带参数的宏定义调用_IO('a',1);命令码的_IO方法生成设备控制指令标识符
- 设备文件指针major = register_chrdev(0, CNAME, &fop);的第三个参数的结构体,增加.unlocked_ioctl = myioclt
- 定义myiclt函数,参数一文件指针,参数二应用程序传过来的参数,函数内通过不同的参数,给寄存器设置不同的数据,函数执行完成后会通过设备文件自动将数据写入到寄存器
- 应用程序通过ioctl(fd, LED1_ON);调用业务程序,实现业务代码
h文件
#ifndef __IOCTL__
#define __IOCTL__
// 自定义命令码写好,16位命令类型,8位命令序号,8位参数类型
#define LED1_ON _IO('a', 1)
#define LED1_OFF _IO('a', 0)
#endif
c文件
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/io.h>
#include <linux/ioctl.h>
#include "10_ioctl.h"
// 内核缓冲区
char kbuf[128] = "";
#define RCC_BASE 0x50000000 // AHB4总线 GPIOE
#define AHB4_CLOCK_ENABLE (RCC_BASE + 0xA28) // AHB4总线地址
// PE10 - LED1
#define GPIOE_BASE 0x50006000
#define GPIO_MODER (GPIOE_BASE + 0x00) // 模式寄存器
#define GPIO_OPTYE (GPIOE_BASE + 0x04) // 输出类型寄存器
#define GPIO_OUTDATA (GPIOE_BASE + 0x14) // 输出数据寄存器
unsigned int *ahb4_clock_enable = NULL;
unsigned int *gpioe_mode = NULL;
unsigned int *gpioe_10_odr = NULL;
int major; // 主设备号
#define CNAME "leddev" // 字符设备驱动名称
int myopen(struct inode *node, struct file *file)
{
printk("this is my %s test \n", __func__);
// 在open函数中完成映射
// GPIOE时钟 ok
ahb4_clock_enable = (unsigned int *)ioremap(AHB4_CLOCK_ENABLE, sizeof(unsigned int));
if (ahb4_clock_enable == NULL)
{
printk("ioremap clock error");
return -1;
}
// 驱动第四个位置的GPIOE
*ahb4_clock_enable |= (1 << 4);
printk("rcc_ahb4_clockenable success %d\n", __LINE__);
// 在write函数中 完成控制
gpioe_mode = (unsigned int *)ioremap(GPIO_MODER, sizeof(unsigned int));
if (gpioe_mode == NULL)
{
printk("gpioe_mode error\n");
return -1;
}
printk("gpioe_mode set success %d\n", __LINE__);
// 模式寄存器:01输出模式
*gpioe_mode &= ~(0x03 << (2 * 10)); // 清空初始化
// 赋值0x11
*gpioe_mode |= 0x11 << (2 * 10);
// 输出数据寄存器
gpioe_10_odr = (unsigned int *)ioremap(GPIO_OUTDATA, sizeof(unsigned int));
if (gpioe_10_odr == NULL)
{
printk("gpioe_10_odr error\n");
return -1;
}
printk("gpioe_10_odr set success%d\n", __LINE__);
// 输出数据,默认给低电平
*gpioe_10_odr &= ~(0x1 << 10); // 关灯
// *gpioe_10_odr |=(0x1 << 10);
return 0;
}
int myclose(struct inode *node, struct file *file)
{
printk("this is my%s\n", __func__);
return 0;
}
ssize_t myread(struct file *file, char __user *ubuf, size_t size, loff_t *offs)
{
int ret;
printk("this is my%s\n", __func__);
if (size > sizeof(kbuf))
{
size = sizeof(kbuf);
}
ret = copy_to_user(ubuf, kbuf, size);
if (ret < 0)
{
printk("copy to user error\n");
return -EIO;
}
return size;
}
ssize_t mywrite(struct file *file, const char __user *ubuf, size_t size, loff_t *offs)
{
int ret;
printk("this is my%s\n", __func__);
if (size > sizeof(kbuf))
{
size = sizeof(kbuf);
}
ret = copy_from_user(kbuf, ubuf, size);
if (ret < 0)
{
printk("copy from user error\n");
return -EIO;
}
// 判断kbuf当中是否有数据来开关led
if (kbuf[0] == 1)
{
*gpioe_10_odr |= (0x1 << 10);
}
else
{
*gpioe_10_odr &= ~(0x1 << 10);
}
return size;
}
long myioclt(struct file *file, unsigned int cmd, unsigned long args)
{
switch (cmd)
{
case LED1_ON:
*gpioe_10_odr |= (0x1 << 10);
break;
case LED1_OFF:
*gpioe_10_odr &= ~(0x1 << 10);
break;
default:
break;
}
return 0;
};
const struct file_operations fop = {
.open = myopen,
.release = myclose,
.write = mywrite,
.read = myread,
.unlocked_ioctl = myioclt
};
// 创建设备目录指针
struct class *cls;
// 创建设备文件指针
struct device *dev;
static int __init fun_init(void)
{
printk("this is %s-%d test\n", __func__, __LINE__);
major = register_chrdev(0, CNAME, &fop);
if (major < 0)
{
printk("字符设备注册失败\n");
return -1;
}
printk("字符设备注册成功,major:%d\n", major);
cls = class_create(THIS_MODULE, CNAME);
if (IS_ERR(cls))
{
printk("class create error\n");
return PTR_ERR(cls);
}
// 创建设备文件
dev = device_create(cls, NULL, MKDEV(major, 0), NULL, CNAME);
if (IS_ERR(dev))
{
// 销毁目录
class_destroy(cls);
// 注销
unregister_chrdev(major, CNAME);
printk("class create error\n");
return PTR_ERR(dev);
}
printk("设备目录和设备文件创建成功\n");
return 0;
}
static void __exit fun_exit(void)
{
unregister_chrdev(major, CNAME);
class_destroy(cls);
printk("this is %s %d exit\n", __func__, __LINE__);
}
module_init(fun_init);
module_exit(fun_exit);
// 证书信息
MODULE_LICENSE("GPL");
应用程序
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/fcntl.h>
#include <sys/ioctl.h>
#include "10_ioctl.h"
#define CNAME "/dev/leddev"
int main()
{
int fd = open(CNAME, O_RDWR);
if (fd < 0)
{
perror("open error\n");
return -1;
}
printf("open success");
while (1)
{
ioctl(fd, LED1_ON);
sleep(1);
ioctl(fd, LED1_OFF);
sleep(1);
}
close(fd);
return 0;
}
内核中产生竞态
- 多个应用程序同时访问驱动程序的临界资源的时候,竞态就会产生。
- 竞态是由多个执行流(进程、线程或中断)对共享资源访问顺序的不确定,导致结果不可预测,例如多个线程同时访问和修改共享资源,就会造成数据错误等场景
- 通过中断屏蔽、自旋锁、信号量、互斥体、原子操作等方式
- 个人理解,多个线程同时修改一个变量,没办法保证执行顺序,用一些方法控制执行顺序
自旋锁
- 在驱动文件定义自旋锁变量 spinlock_t lock = {0}
- 在入口函数初始化自旋锁 spin_lock_init(&lock);
- 在打开文件的驱动函数中上锁 spin_lock(&lock);
- 在设备关闭函数中解锁 spin_unlock(&lock); 驱动程序
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/io.h>
// 内核缓冲区
char kbuf[128] = "";
#define RCC_BASE 0x50000000 // AHB4总线 GPIOE
#define AHB4_CLOCK_ENABLE (RCC_BASE + 0xA28) // AHB4总线地址
// PE10 - LED1
#define GPIOE_BASE 0x50006000
#define GPIO_MODER (GPIOE_BASE + 0x00) // 模式寄存器
#define GPIO_OPTYE (GPIOE_BASE + 0x04) // 输出类型寄存器
#define GPIO_OUTDATA (GPIOE_BASE + 0x14) // 输出数据寄存器
unsigned int *ahb4_clock_enable = NULL;
unsigned int *gpioe_mode = NULL;
unsigned int *gpioe_10_odr = NULL;
int major; // 主设备号
#define CNAME "leddev" // 字符设备驱动名称
spinlock_t lock = {0};
int myopen(struct inode * node, struct file *file)
{
printk("this is my %s test \n", __func__);
// 初始化自旋锁
spin_lock_init(&lock);
spin_lock(&lock);
printk("spin lock success\n");
return 0;
}
int myclose(struct inode *node, struct file *file)
{
printk("this is my%s\n", __func__);
return 0;
}
ssize_t myread(struct file *file, char __user *ubuf, size_t size, loff_t *offs)
{
int ret;
printk("this is my%s\n", __func__);
if (size > sizeof(kbuf))
{
size = sizeof(kbuf);
}
ret = copy_to_user(ubuf, kbuf, size);
if (ret < 0)
{
printk("copy to user error\n");
return -EIO;
}
spin_unlock(&lock);
printk("spin unlock success\n");
return size;
}
ssize_t mywrite(struct file *file, const char __user *ubuf, size_t size, loff_t *offs)
{
int ret;
printk("this is my%s\n", __func__);
if (size > sizeof(kbuf))
{
size = sizeof(kbuf);
}
ret = copy_from_user(kbuf, ubuf, size);
if (ret < 0)
{
printk("copy from user error\n");
return -EIO;
}
return size;
}
const struct file_operations fop = {
.open = myopen,
.release = myclose,
.write = mywrite,
.read = myread,
};
// 创建设备目录指针
struct class *cls;
// 创建设备文件指针
struct device *dev;
static int __init fun_init(void)
{
printk("this is %s-%d test\n", __func__, __LINE__);
major = register_chrdev(0, CNAME, &fop);
if (major < 0)
{
printk("字符设备注册失败\n");
return -1;
}
printk("字符设备注册成功,major:%d\n", major);
cls = class_create(THIS_MODULE, CNAME);
if (IS_ERR(cls))
{
printk("class create error\n");
return PTR_ERR(cls);
}
// 创建设备文件
dev = device_create(cls, NULL, MKDEV(major, 0), NULL, CNAME);
if (IS_ERR(dev))
{
// 销毁目录
class_destroy(cls);
// 注销
unregister_chrdev(major, CNAME);
printk("class create error\n");
return PTR_ERR(dev);
}
printk("设备目录和设备文件创建成功\n");
return 0;
}
static void __exit fun_exit(void)
{
unregister_chrdev(major, CNAME);
class_destroy(cls);
printk("this is %s %d exit\n", __func__, __LINE__);
}
module_init(fun_init);
module_exit(fun_exit);
// 证书信息
MODULE_LICENSE("GPL");
应用程序
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/fcntl.h>
#define CNAME "/dev/leddev"
int main()
{
int fd = open(CNAME, O_RDWR);
if (fd < 0)
{
perror("open error\n");
return -1;
}
printf("open success");
char *str = "demo";
write(fd, str, strlen(str));
sleep(100);
char buf[128] = "";
read(fd, buf, sizeof(buf));
close(fd);
return 0;
}
信号量
- 在驱动文件定义信号量 struct semaphore sem;
- 在入口函数初始化信号量 void sema_init(struct semaphore *sem, int val) //初始化信号量
- 在写函数上锁 void down(struct semaphore *sem); //上锁 阻塞方式 p操作
- 在读函数解锁 void up(struct semaphore *sem);
驱动程序
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/io.h>
// 内核缓冲区
char kbuf[128] = "";
int major; // 主设备号
#define CNAME "leddev" // 字符设备驱动名称
//定义一个信号量
struct semaphore sem;
int myopen(struct inode * node, struct file *file)
{
printk("this is my %s test \n", __func__);
return 0;
}
int myclose(struct inode *node, struct file *file)
{
printk("this is my%s\n", __func__);
return 0;
}
ssize_t myread(struct file *file, char __user *ubuf, size_t size, loff_t *offs)
{
int ret;
printk("this is my%s\n", __func__);
if (size > sizeof(kbuf))
{
size = sizeof(kbuf);
}
ret = copy_to_user(ubuf, kbuf, size);
if (ret < 0)
{
printk("copy to user error\n");
return -EIO;
}
//v操作
printk("解锁\n");
up(&sem);
return size;
}
ssize_t mywrite(struct file *file, const char __user *ubuf, size_t size, loff_t *offs)
{
int ret;
printk("this is my%s\n", __func__);
if (size > sizeof(kbuf))
{
size = sizeof(kbuf);
}
ret = copy_from_user(kbuf, ubuf, size);
if (ret < 0)
{
printk("copy from user error\n");
return -EIO;
}
//上锁
down(&sem);
printk("上锁\n");
return size;
}
const struct file_operations fop = {
.open = myopen,
.release = myclose,
.write = mywrite,
.read = myread,
};
// 创建设备目录指针
struct class *cls;
// 创建设备文件指针
struct device *dev;
static int __init fun_init(void)
{
printk("this is %s-%d test\n", __func__, __LINE__);
major = register_chrdev(0, CNAME, &fop);
if (major < 0)
{
printk("字符设备注册失败\n");
return -1;
}
printk("字符设备注册成功,major:%d\n", major);
cls = class_create(THIS_MODULE, CNAME);
if (IS_ERR(cls))
{
printk("class create error\n");
return PTR_ERR(cls);
}
// 创建设备文件
dev = device_create(cls, NULL, MKDEV(major, 0), NULL, CNAME);
if (IS_ERR(dev))
{
// 销毁目录
class_destroy(cls);
// 注销
unregister_chrdev(major, CNAME);
printk("class create error\n");
return PTR_ERR(dev);
}
printk("设备目录和设备文件创建成功\n");
//初始化信号量
sema_init(&sem,1);
printk("初始化信号量成功\n");
return 0;
}
static void __exit fun_exit(void)
{
unregister_chrdev(major, CNAME);
//销毁设备
device_destroy(cls,MKDEV(major,0));
class_destroy(cls);
printk("this is %s %d exit\n", __func__, __LINE__);
}
module_init(fun_init);
module_exit(fun_exit);
// 证书信息
MODULE_LICENSE("GPL");
应用程序
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/fcntl.h>
#define CNAME "/dev/leddev"
int main()
{
int fd = open(CNAME, O_RDWR);
if (fd < 0)
{
perror("open error\n");
return -1;
}
printf("open success\n");
char *str = "demo";
write(fd, str, strlen(str));
printf("p操作成功\n");
printf("正在使用临界资源\n");
sleep(20);
char buf[128] = "";
read(fd, buf, sizeof(buf));
printf("v操作成功\n");
close(fd);
return 0;
}
I/O模型
- IO模型决定了用户程序如何与内核交互获取数据的方式,特别是当数据没准备好不可用的状态下
阻塞I/O
- 阻塞I/O就是当数据未准备好时,进程会被挂起,也就是阻塞,一直等待数据准备好才返回
- 例如用read读数据的时候,如果没有数据,当前进程就会被挂起,内核不会返回,会一直等待,直到有数据可读
- 阻塞的时候cpu可以去调度其他任务
- 对应的方法有wait_event_interruptible(wq,condition);
- 一般用在网络通信、串口接收、中断过来的数据等
非阻塞I/O
- 非阻塞I/O是当数据未准备好时,不等待,立即返回一个错误或状态码。
- read调用立即返回,即使没有数据,用户程序需要自己判断是否成功读取到了数据,一般需要轮询加延时处理
I/O多路复用
- 使用一个线程,同时监听多个I/O通道,一旦其中某一个I/O就绪,就立刻处理,不必为每个I/O建立一个线程
- 一般用在网络服务器、多设备数据收集、实时系统监测多个输入源等场景
设备树
- Linux设备树(Device Tree)是一种描述硬件配置的数据结构,用于将硬件信息传递给操作系统内核
- 一般设备树用来配置io口、adc、spi等外设
- 依赖头文件#include <linux/of.h>
- 设备树文件目录 linux-5.4.31/arch/arm/boot/dts/stm32mp157a-fsmp1a.dts
- 增加节点在/{}内添加myleds{led1 = <&gpioe 10 0>;};
- 编辑完以后需要在linux-5.4.31文件夹目录下执行编译命令make dtbs
- 编译完成后,将arch/arm/boot/dts/stm32mp157a-fsmp1a.dtb文件拷到tftpboot共享文件夹
- 启动开发板,在/proc/device-tree/目录内ls,能看到自己增加的节点
- 驱动文件读取设备树文件的节点node = of_find_node_by_path("/lanbinNode@0x123456");
- 驱动文件读取属性of_property_read_string(node, "astring", &out_string);
- 不同类型属性有不同的读取方法of_property_read_u32_array、of_property_read_u8_array等
驱动文件
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/export.h>
// cp 拷贝相关函数
#include <linux/uaccess.h>
#include <linux/errno.h>
// 延时函数
#include <linux/delay.h>
#include <linux/of.h>
int major;
// 字符设备名称 + 驱动设备名称
#define CNAME "mycdev"
// 设备树节点指针
struct device_node *node;
// 属性指针
struct property *pp;
// 入口文件
static int __init fun_init(void)
{
int i = 0;
// 返回指针
const char *out_string;
// 这里不能做指针 要做一个数组
u32 out_values[2];
// 二进制数据有4组
uint8_t bindata[4];
// 1 通过路径获取节点
// 在双引号中填写 节点全称
// 有个"/""
node = of_find_node_by_path("/lanbinNode@0x123456");
if (node == NULL)
{
printk("of find node by path error\n");
return -EINVAL;
}
// 只能在开发板测试,因为上位机的设备数里面没有这个代码
printk("of find node by path success\n");
// 2 通过节点结构体 获取键值对
of_property_read_string(node, "astring", &out_string);
printk("out string : %s\n", out_string);
// 3 解析数组
// 设备节点的地址
// 属性名称
// 输出数据首地址
// 成员个数
of_property_read_u32_array(node, "uint", out_values, 2);
for (i = 0; i < 2; i++)
{
printk("uint:%#x\n", out_values[i]);
}
// 4 读数组 二进制数据 4个
// 设备节点指针
// 属性名称
// 输出数据首地址
// 成员个数
of_property_read_u8_array(node, "binddata", bindata, 4);
// 把二进制数据转换成大断?
for (i = 0; i < 4; i++)
{
// 格式化输出二进制数据 用16进制数据代替二进制数据输出
// 0 填充不足的位数
// 2 最小宽度为2个字符
// x小写16进制数据
printk("bindata:%02x\n", bindata[i]);
}
return 0;
}
// 出口文件
static void __exit fun_exit(void)
{
printk("exit");
}
module_init(fun_init);
module_exit(fun_exit);
MODULE_LICENSE("GPL");
设备树文件
/dts-v1/;
#include "stm32mp157.dtsi"
#include "stm32mp15xa.dtsi"
#include "stm32mp15-pinctrl.dtsi"
#include "stm32mp15xxaa-pinctrl.dtsi"
#include "stm32mp15xx-fsmp1x.dtsi"
/ {
model = "HQYJ STM32MP157 FSMP1A Discovery Board";
compatible = "st,stm32mp157a-chb", "hqyj,stm32mp157";
aliases {
serial0 = &uart4;
};
chosen {
stdout-path = "serial0:115200n8";
};
reserved-memory {
gpu_reserved: gpu@da000000 {
reg = <0xda000000 0x4000000>;
no-map;
};
optee_memory: optee@0xde000000 {
reg = <0xde000000 0x02000000>;
no-map;
};
};
lanbinNode@0x123456{
astring = "hello cs2502ban";
uint = <0x1234 0x5678>;
bindata = [01 02 34 66];
bindata2 = /bits/ 8 <0x01 0x02 0x34 0x66>;
};
myleds{
compatible = "linuxKaiFaBan,myleds";
led1 = <&gpioe 10 0>;
led2 = <&gpiof 10 0>;
led3 = <&gpioe 8 0>;
};
};
&optee {
status = "okay";
};
GPIO子系统
- linux内核的gpio子系统是一套用于管理和控制io口的软件框架。
- 依赖头文件<linux/of.h>和<linux/of_gpio.h>
- 通过在设备树上根据芯片数据手册,定义io口,例如led1=<&gpioe 10 0> //pe10 初始值为0
- 驱动程序中gpio_no = of_get_named_gpio(node,"led1",0);获取io口软件编号
- 驱动程序中gpio_request(gpio_no,NULL)申请使用编号
- 驱动程序中设置io口模式为输出gpio_direction_output(gpio_no,0);
- 设置io口值gpio_set_value(gpio_no,1);
- 释放io口gpio_free(gpio_no);
#include <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/export.h>
// 文件操作结构体的头文件不能少
#include <linux/fs.h>
// cp 拷贝相关函数
#include <linux/uaccess.h>
#include <linux/errno.h>
// 自动创建节点
#include <linux/device.h>
// 映射头文件
#include <linux/io.h>
// 延时函数
#include <linux/delay.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
int major;
// 字符设备名称 + 驱动设备名称
#define CNAME "mycdev"
// 设备树节点指针
struct device_node *node;
// 属性指针
struct property *pp;
// 目录文件夹结构体类型
struct class *cls = NULL;
// 设备节点结构体类型
struct device *dev = NULL;
int gpio_no, gpio_no2, gpio_no3;
// 打开
// int (*open) (struct inode *, struct file *);
int mycdev_open(struct inode *node, struct file *file)
{
printk("this is %s test\n", __func__);
return 0;
}
// 关闭
// int (*release) (struct inode *, struct file *);
int mycdev_close(struct inode *node, struct file *file)
{
printk("this is %s test\n", __func__);
return 0;
}
// 内核空间缓冲区
char kbuf[128] = "";
// 读
// 文件用户缓冲区地址 大小 偏移量
// 应用层的read函数调用到此函数
// ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t mycdev_read(struct file *file, char __user *ubuf, size_t size, loff_t *offs)
{
int ret;
printk("this is %s test\n", __func__);
if (size > sizeof(kbuf))
size = sizeof(kbuf);
// 用户空间 内核空间 大小
ret = copy_to_user(ubuf, kbuf, size);
if (ret < 0)
{
printk("copy to user error\n");
return -EIO;
}
// 返回实际写入的大小
return size;
}
// 写
// ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
// 应用层的wirite函数 调用到此函数
ssize_t mycdev_write(struct file *file, const char __user *ubuf, size_t size, loff_t *offs)
{
int ret;
printk("this is %s test\n", __func__);
// 如果空间大 则用最大空间
if (size > sizeof(kbuf))
size = sizeof(kbuf);
// 把用户空间数据写进内核空间
ret = copy_from_user(kbuf, ubuf, size);
if (ret < 0)
{
printk("copy form user error\n");
return -EIO;
}
if (kbuf[0] == 0)
{
gpio_set_value(gpio_no, 0);
gpio_set_value(gpio_no2, 0);
gpio_set_value(gpio_no3, 0);
}
if (kbuf[0] == 1)
{
gpio_set_value(gpio_no, 1);
gpio_set_value(gpio_no2, 1);
gpio_set_value(gpio_no3, 1);
}
// 返回实际写入的大小
return size;
}
// 文件操作结构体
const struct file_operations fops = {
.open = mycdev_open,
.release = mycdev_close,
.read = mycdev_read,
.write = mycdev_write};
// 入口文件
static int __init fun_init(void)
{
// 返回指针
const char *out_string;
// 在此处注册
// 系统分配 字符设备名字 结构体引用
major = register_chrdev(0, CNAME, &fops);
if (major < 0)
{
printk("register char dev error\n");
return -1;
}
printk("设备注册成功 %d\n", major);
// 提交目录
cls = class_create(THIS_MODULE, CNAME);
if (IS_ERR(cls))
{
printk("class create error\n");
return PTR_ERR(cls);
}
printk("classcreate ok\n");
// 创建设备文件
// 目录句柄 此设备的父设备 设备号 传递的数据 节点名称
// 主设备号与次设备号组成一个设备号
dev = device_create(cls, NULL, MKDEV(major, 0), NULL, CNAME);
if (IS_ERR(dev))
{
// 删除目录
class_destroy(cls);
// 注销
unregister_chrdev(major, CNAME);
// 提示信息
printk("device create error\n");
return PTR_ERR(dev);
}
printk("device create ok\n");
// 1 通过路径获取节点
// 在双引号中填写 节点全称
// 有个"/""
node = of_find_node_by_path("/myleds");
if (node == NULL)
{
printk("of find node by path error\n");
return -EINVAL;
}
// 只能在开发板测试,因为上位机的设备数里面没有这个代码
printk("of find node by path success\n");
// 2 通过节点结构体 获取键值对
of_property_read_string(node, "compatible", &out_string);
printk("out string : %s\n", out_string);
gpio_no = of_get_named_gpio(node, "led1", 0);
if (gpio_no < 0)
{
printk("of_named_gpio err\n");
}
printk("of_named_gpio %d \n", gpio_no);
if (gpio_request(gpio_no, NULL) < 0)
{
printk("gpio_request err\n");
}
gpio_no2 = of_get_named_gpio(node, "led2", 0);
if (gpio_no2 < 0)
{
printk("of_named_gpio2 err\n");
}
printk("of_named_gpio2 %d \n", gpio_no2);
if (gpio_request(gpio_no2, NULL) < 0)
{
printk("gpio_request2 err\n");
}
printk("gpio_request2 succ\n");
gpio_no3 = of_get_named_gpio(node, "led3", 0);
if (gpio_no3 < 0)
{
printk("of_named_gpio3 err\n");
}
printk("of_named_gpio3 %d \n", gpio_no3);
if (gpio_request(gpio_no3, NULL) <0)
{
printk("gpio_request3 err\n");
}
printk("gpio_request3 succ\n");
gpio_direction_output(gpio_no, 0); // 初始化gpio
gpio_direction_output(gpio_no2, 0); // 初始化gpio
gpio_direction_output(gpio_no3, 0); // 初始化gpio
return 0;
}
// 出口文件
static void __exit fun_exit(void)
{
printk("this is mycdev exit function\n");
// 在此处注销
// 主设备号 名字
unregister_chrdev(major, CNAME);
// 销毁设备 目录 设备号
device_destroy(cls, MKDEV(major, 0));
// 目录解除
class_destroy(cls);
gpio_set_value(gpio_no, 0);
gpio_free(gpio_no);
gpio_set_value(gpio_no2, 0);
gpio_free(gpio_no2);
gpio_set_value(gpio_no3, 0);
gpio_free(gpio_no3);
printk("exit");
}
module_init(fun_init);
module_exit(fun_exit);
MODULE_LICENSE("GPL");