文章目录
- Linux驱动基础(一)GPIO(上)LED驱动
- 一、开发环境准备
- 1.安装交叉编译工具+编译内核
- (1)安装交叉编译工具
- (2)修改Makefile指定编译器和架构
- (3)生成配置文件.config
- (4)编译内核
- 2.安装配置vscode
- 二、第一个驱动程序HelloWorld
- 1.简单的驱动框架使用
- 2.重要的几个宏/函数
- 三、第二个驱动程序点亮LED
- 1.阅读原理图和芯片手册
- (1)找到OrangePi PC+的两个LED
- (2)找到两个LED和CPU连接的引脚
- (3)在芯片手册找到PL10和PA15
- (4)找到对应的配置寄存器
- (5)找到对应的数据寄存器
- 2.从驱动开始点亮香橙派orangepi plus的LED
- 四、面向对象设计思想
Linux驱动基础(一)GPIO(上)LED驱动
一、开发环境准备
ubuntu22.04 + Vscode + OrangePi+
1.安装交叉编译工具+编译内核
(1)安装交叉编译工具
📌选择交叉编译工具链
以官网下载页面为例
📌下载交叉编译工具
交叉编译工具链官方下载地址(国外)
交叉编译工具链清华开源镜像站下载地址(国内)
📌解压、添加环境变量、测试安装结果
sudo tar -xvf arm-none-linux-gnueabihf-12.3-x86_64-.tar.xz -C /mnt # 解压到/mnt目录
# 添加环境变量,在原有的双引号前输入:后粘贴复制工具链的路径
# 例如这里是/mnt/arm-none-linux-gnueabihf-12.3-x86_64/bin
# 保存后重新登录或重启生效,输入arm-尝试tab能否补全命令以及arm-none-linux-gnueabihf-gcc -v进行测试
sudo vi /etc/environment
(2)修改Makefile指定编译器和架构
# 在内核源码顶层目录的Makefile,搜索CROSS_COMPILE或ARCH,添加以下内容
# 要配置上面步骤后,CROSS_COMPILE可以这样写;没有的话要写绝对路径
ARCH := arm
CROSS_COMPILE := arm-none-linux-gnueabihf-
(3)生成配置文件.config
# 生成默认的配置文件,也可以make menuconfig进行配置,最后会保存到.config
make defconfig
❓❓❓ 报错1:arch/arm是一个目录 ❓❓❓
解决:ARCH := arm (arm后面多了空格)
❓❓❓ 报错2:/bin/sh: 1: flex: not found ❓❓❓
sudo apt-get install flex
❓❓❓ 报错3:/bin/sh: 1: bison: not found ❓❓❓
sudo apt-get install bison
❓❓❓ 报错4:make menuconfig 打开失败❓❓❓
sudo apt-get install libncurses5-dev
(4)编译内核
# 建议这里可以将所有的CPU核心分配给虚拟机,提高速度
make -j24
❓❓❓ 报错5:scripts/extract-cert.c:21:10: fatal error: openssl/bio.h: 没有那个文件或目录❓❓❓
原因:没有安装libssl-dev或者安装版本过高
解决:(未安装)sudo apt-get install libssl-dev
(版本过高)sudo apt-get install aptitude(安装aptitude软件包管理器)
sudo aptitude install libssl-dev(使用aptitude安装libssl-dev)
选择不保持当前版本,出现提示输出n,确认y后降级
2.安装配置vscode
二、第一个驱动程序HelloWorld
1.简单的驱动框架使用
简单的驱动框架分为三步:①装载驱动、②操作驱动、③卸载驱动。个人认为这样划分容易理解和记忆!
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.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/err.h>#define DEV_NAME "hello"static struct class *hello_cls;
static int ERR;
static struct device *device;static int hello_open(struct inode *inode, struct file *file){printk("%s %s %d\n",__FILE__,__FUNCTION__ ,__LINE__);return 0;
}
static ssize_t hello_read (struct file *file, char __user *buf, size_t size, loff_t *offset){printk("%s %s %d\n",__FILE__,__FUNCTION__ ,__LINE__);return 0;
}static ssize_t hello_write (struct file *file, const char __user *buf, size_t size, loff_t * offset){printk("%s %s %d\n",__FILE__,__FUNCTION__ ,__LINE__);return 0;
}
static int hello_release(struct inode *inode, struct file *file){printk("%s %s %d\n",__FILE__,__FUNCTION__ ,__LINE__);return 0;
}static unsigned int major = 0;
static struct file_operations hello_fop = {.owner = THIS_MODULE,.open = hello_open,.read = hello_read,.write = hello_write,.release = hello_release
};/*** @brief 初始化驱动(注册设备register_chrdev()、创建设备节点device_create())* 注册设备需要:设备号、设备名、设备file_operaction* 创建设备节点需要:class、devtype-由设备号确定、设备节点名**/
static int __init hello_Driver_Init(void){printk("%s %s %d\n",__FILE__,__FUNCTION__ ,__LINE__);major = register_chrdev(major,DEV_NAME,&hello_fop);hello_cls = class_create(THIS_MODULE,DEV_NAME);if(IS_ERR(hello_cls)){ERR = PTR_ERR(hello_cls);pr_err("class create failed.(error code:%d)\n",ERR);unregister_chrdev(major,DEV_NAME);return ERR;}device = device_create(hello_cls,NULL, MKDEV(major,0),NULL,"hello");if(IS_ERR(device)){ERR = PTR_ERR(device);pr_err("device create failed.(error code:%d)\n",ERR);class_destroy(hello_cls);unregister_chrdev(major,DEV_NAME);return ERR;}return 1;
}/*** @brief 卸载驱动(删除设备节点device_destroy()、注销设备unregister_chrdev())**/
static void __exit hello_Driver_Exit(void){printk("%s %s %d\n",__FILE__,__FUNCTION__ ,__LINE__);device_destroy(hello_cls, MKDEV(major,0));class_destroy(hello_cls);unregister_chrdev(major,DEV_NAME);
}module_init(hello_Driver_Init);
module_exit(hello_Driver_Exit);
MODULE_LICENSE("GPL");
2.重要的几个宏/函数
2.1 错误指针判断IS_ERR( )、PTR_ERR( )和pr_err( )
2.2 用户和内核之间的数据拷贝copy_from_user( )、copy_to_user( )和get_user( )、put_user( )
三、第二个驱动程序点亮LED
1.阅读原理图和芯片手册
(1)找到OrangePi PC+的两个LED
📌在原理图中搜索"LED",找到关于OrangePi+ LED部分的原理图
- 从原理图关于LED的部分可以看到,OrangePi PC+有两个LED:PWR-LED(供电时闪烁LED)、STATUS-LED(开机运行时状态LED)。
(2)找到两个LED和CPU连接的引脚
- PWR-LED连接到PL10、STATUS_LED连接到PA15
(3)在芯片手册找到PL10和PA15
(4)找到对应的配置寄存器
(5)找到对应的数据寄存器
2.从驱动开始点亮香橙派orangepi plus的LED
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.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/err.h>
#include <asm/io.h>
#include <linux/types.h>
#include <linux/uaccess.h>#define PIO_BASE 0x01c20800static volatile unsigned int *PA_CFG1_REG;
static volatile unsigned int *PA_DATA_REG;
static unsigned int major = 0;
static struct class *led_class;static int led_open(struct inode *inode, struct file *file){/* output: bit30-28 = 001 */*PA_CFG1_REG &= ~(0x01 << 30);*PA_CFG1_REG &= ~(0x01 << 29);*PA_CFG1_REG |= (0x01 << 28);printk("======= led_open():PA_CFG_REG = 0x%x =========\n",*PA_CFG1_REG);return 0;
}
static int led_close(struct inode *inode, struct file *file){/* default: bit30-28 = 111 */*PA_CFG1_REG |= (0x01 << 30);*PA_CFG1_REG |= (0x01 << 29);*PA_CFG1_REG |= (0x01 << 28);printk("======= led_close():PA_CFG_REG = 0x%x =========\n",*PA_CFG1_REG);return 0;
}
static ssize_t led_read (struct file *file, char __user *buf, size_t size, loff_t *off){return 0;
}
static ssize_t led_write (struct file *file, const char __user *buf, size_t size, loff_t *off){char val; // 0-OFF 1-ONint ret = copy_from_user(&val,buf,1);ret = 3;if(val){*PA_DATA_REG |= 0x1<<15; //bit15 = 1}else{*PA_DATA_REG &= ~(0x1<<15); //bit15 = 0}return 1;
}
static struct file_operations led_ops = {.owner = THIS_MODULE,.open = led_open,.read = led_read,.write = led_write,.release = led_close,
};
static int __init led_driver_init(void){printk("======= %s %s %d ========\n",__FILE__,__FUNCTION__ ,__LINE__);major = register_chrdev(major,"led_driver",&led_ops);led_class = class_create(THIS_MODULE,"led_driver");device_create(led_class,NULL, MKDEV(major,0),NULL,"led1");PA_CFG1_REG = ioremap(PIO_BASE+0x04,4);PA_DATA_REG = ioremap(PIO_BASE+0x10,4);*PA_CFG1_REG &= ~(0x1<<30);*PA_CFG1_REG &= ~(0x1<<29);*PA_CFG1_REG |= 0x1<<28;return 0;
}static void __exit led_driver_exit(void){printk("======= %s %s %d ========\n",__FILE__,__FUNCTION__ ,__LINE__);iounmap(PA_CFG1_REG);iounmap(PA_DATA_REG);device_destroy(led_class, MKDEV(major,0));class_destroy(led_class);unregister_chrdev(major,"led_driver");}module_init(led_driver_init);
module_exit(led_driver_exit);
MODULE_LICENSE("GPL");
这里为了没有写应用层的测试程序,只简单粗暴点个LED,加载驱动后led亮,香橙派OrangePi Plus上是红灯
四、面向对象设计思想
上面所写的LED驱动程序,驱动和硬件操作绑定在一起,更换开发板平台所有的代码就完全不适用,不仅可维护性和可读性差,而且代码难以移植到其他平台,增加了代码冗余和维护的工作量。因此,内核驱动程序中采用面向对象的编程思想,将驱动和硬件操作的代码分离并进行合理的抽象和封装,通常是更好的选择
📌改写LED驱动,实现应用层程序控制两个LED亮灭
【led_test_app.c】
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>/** ./test_led_app /dev/led1 on **/
/** ./test_led_app /dev/led2 off **/int main(int argc,char **argv){char *buf = (char *)malloc(36);int nwrite = 0;if(argc < 3){printf("Usage:%s [dev-led1/led2] [status-on/off] \n",argv[0]);return -1;}int fd = open(argv[1],O_RDWR);if(fd < 0){printf("open device error\n");return -1;}int val_on = 1;int val_off = 0;if( 0 == strcmp(argv[2],"on")){nwrite = write(fd,&val_on,sizeof(val_on));}else if( 0 == strcmp(argv[2],"off")){nwrite = write(fd,&val_off,sizeof(val_off));}else{printf("syntax error\n");}close(fd);return 0;
}
【orangepi_plus_leds.h】
#ifndef _ORANGEPI_PLUS_LEDS_H
#define _ORANGEPI_PLUS_LEDS_H#define MIN(a,b) (a < b ? a : b)
#define PIN_GROUP(pin) (pin >> 22)
#define PIN_NUM(pin) (pin & 0x3FFFFF)struct GPIOx_PIN { /** bit[21:0] pin_num **//** bit[31:22] pin_group **/int pin; int count;volatile unsigned int *GPIOx_CFG_REG; //配置寄存器volatile unsigned int *GPIOx_DAT_REG; //数据寄存器int (*gpio_init)(int pin); //初始化函数int (*gpio_control)(int pin,int status); //控制函数struct GPIOx_PIN *next;
};extern struct GPIOx_PIN *get_GPIOA_PIN15(void);
extern struct GPIOx_PIN *get_GPIOL_PIN10(void);#endif
【orangepi_plus_led1.c】
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.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/err.h>
#include <asm/io.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include "orangepi_plus_leds.h"#define BASE_ADDR (0x01c20800)#define WHICH_CFG(pin_num) \((pin_num >= 0 && pin_num <= 7) ? 0x00 : \(pin_num >= 8 && pin_num <= 15) ? 0x04 : \(pin_num >= 15 && pin_num <= 21) ? 0x08 : 0x0c)int led1_init(int pin);
int led1_control(int pin,int status);static struct GPIOx_PIN GPIOA_PIN15 = {.pin = (0 << 22)|(15), //第0组第15pin(GPIOA_15).gpio_init = led1_init,.gpio_control = led1_control,.next = NULL
};int led1_init(int pin){int pin_num = PIN_NUM(pin);GPIOA_PIN15.GPIOx_CFG_REG = ioremap(BASE_ADDR + PIN_GROUP(pin)* 0x24 + WHICH_CFG(pin_num),4);GPIOA_PIN15.GPIOx_DAT_REG = ioremap(BASE_ADDR + PIN_GROUP(pin)* 0x24 + 0x10,4);if(GPIOA_PIN15.GPIOx_CFG_REG == NULL || GPIOA_PIN15.GPIOx_DAT_REG == NULL){ pr_err("======== %s:ioremap address error =======\n",__FUNCTION__);return -1;}*(GPIOA_PIN15.GPIOx_CFG_REG) &= ~(0x1<<29);*(GPIOA_PIN15.GPIOx_CFG_REG) &= ~(0x1<<30);*(GPIOA_PIN15.GPIOx_CFG_REG) |= (0x1<<28);return 0;
}int led1_control(int pin,int status){ //status 0-off 1-onswitch(status){case 0: //off*(GPIOA_PIN15.GPIOx_DAT_REG) &= ~(0x1 << PIN_NUM(pin));break;case 1: //on*(GPIOA_PIN15.GPIOx_DAT_REG) |= (0x1 << PIN_NUM(pin));break;default:break;}return 0;
}struct GPIOx_PIN *get_GPIOA_PIN15(void){return &GPIOA_PIN15;
}EXPORT_SYMBOL(get_GPIOL_PIN10);
MODULE_LICENSE("GPL");
【orangepi_plus_led2.c】
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.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/err.h>
#include <asm/io.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include "orangepi_plus_leds.h"#define BASE_ADDR (0x01f02c00)#define WHICH_CFG(pin_num) \((pin_num >= 0 && pin_num <= 7) ? 0x00 : \(pin_num >= 8 && pin_num <= 15) ? 0x04 : \(pin_num >= 15 && pin_num <= 21) ? 0x08 : 0x0c)int led2_init(int pin);
int led2_control(int pin,int status);static struct GPIOx_PIN GPIOL_PIN10 = {.pin = (0 << 22)|(10), //第0组第10pin(GPIOL_10).gpio_init = led2_init,.gpio_control = led2_control,.next = NULL
};int led2_init(int pin){int pin_num = PIN_NUM(pin);GPIOL_PIN10.GPIOx_CFG_REG = ioremap(BASE_ADDR + PIN_GROUP(pin)*0x24 + WHICH_CFG(pin_num),4);GPIOL_PIN10.GPIOx_DAT_REG = ioremap(BASE_ADDR + PIN_GROUP(pin)*0x24 + 0x10,4);if(GPIOL_PIN10.GPIOx_CFG_REG == NULL || GPIOL_PIN10.GPIOx_DAT_REG == NULL){pr_err("======= %s ioremap address error ========\n",__FUNCTION__);return -1;}*(GPIOL_PIN10.GPIOx_CFG_REG) &= ~(0x1<<29);*(GPIOL_PIN10.GPIOx_CFG_REG) &= ~(0x1<<30);*(GPIOL_PIN10.GPIOx_CFG_REG) |= (0x1<<28);return 0;
}int led2_control(int pin,int status){ //status 0-off 1-onswitch(status){case 0: //off*(GPIOL_PIN10.GPIOx_DAT_REG) &= ~(0x1<<PIN_NUM(pin));break;case 1: //on*(GPIOL_PIN10.GPIOx_DAT_REG) |= (0x1<<PIN_NUM(pin));break;default:break;}return 0;
}struct GPIOx_PIN *get_GPIOL_PIN10(void){return &GPIOL_PIN10;
}EXPORT_SYMBOL(get_GPIOL_PIN10);
MODULE_LICENSE("GPL");
【led_driver.c】
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.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/err.h>
#include <asm/io.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/string.h>
#include "orangepi_plus_leds.h"static int major = 0;
static int error_check = 0;
static int i = 0;
static struct device *device_check;
static unsigned int minor;
static int kernel_buf = 0;static volatile int *GPIOx_base_address;
static volatile int *PA_CFGx_REG;
static volatile int *PA_DATA_REG;static struct GPIOx_PIN *led = NULL;static int gpio_open(struct inode *inode, struct file *file){printk("========== %s %s %d ===============\n",__FILE__,__FUNCTION__ ,__LINE__);dev_t dev = inode->i_rdev; //应用层open文件的设备号minor = MINOR(dev); //获取次设备号 0-1 switch (minor){case 0:led = get_GPIOA_PIN15();break;case 1:led = get_GPIOL_PIN10();default:break;}led->gpio_init(led->pin);return 0;
}static ssize_t gpio_read (struct file *file, char __user *buf, size_t size, loff_t *offset){printk("========== %s %s %d =============\n",__FILE__,__FUNCTION__ ,__LINE__);return 0;
}static ssize_t gpio_write (struct file *file, const char __user *buf, size_t size, loff_t * offset){printk("========== %s %s %d =============\n",__FILE__,__FUNCTION__ ,__LINE__);if(copy_from_user(&kernel_buf,buf,4)){pr_err("copy_from_user error.(%s)\n",__FUNCTION__);return -1;}led->gpio_control(led->pin,kernel_buf);return 0;
}
static int gpio_close(struct inode *inode, struct file *file){printk("========== %s %s %d ===============\n",__FILE__,__FUNCTION__ ,__LINE__);iounmap(PA_DATA_REG);iounmap(PA_CFGx_REG);iounmap(GPIOx_base_address);return 0;
}static struct file_operations gpio_ops = {.owner = THIS_MODULE,.open = gpio_open,.release = gpio_close,.write = gpio_write,.read = gpio_read,
};static struct class *gpio_cls;static int __init gpio_driver_init(void){printk("============= %s %s %d ==================\n",__FILE__,__FUNCTION__ ,__LINE__);/* 1.注册字符设备 */major = register_chrdev(major,"GPIO",&gpio_ops);/* 2.创建设备节点 */gpio_cls = class_create(THIS_MODULE,"gpio_class");if(IS_ERR(gpio_cls)){error_check = PTR_ERR(gpio_cls);pr_err("class create failed.(error code:%d)\n",error_check);unregister_chrdev(major,"GPIO");return -1;}for(i=0;i<2;i++){device_check = device_create(gpio_cls,NULL,MKDEV(major,i),NULL,"led%d",i+1);if(IS_ERR(device_check)){error_check = PTR_ERR(device_check);pr_err("device create failed.(error code:%d)\n",error_check);class_destroy(gpio_cls);unregister_chrdev(major,"GPIO");return -1;}}return 0;
}static void __exit gpio_driver_exit(void){printk("============= %s %s %d ==================\n",__FILE__,__FUNCTION__ ,__LINE__);iounmap(PA_DATA_REG);iounmap(PA_CFGx_REG);iounmap(GPIOx_base_address);device_destroy(gpio_cls, MKDEV(major,0));device_destroy(gpio_cls, MKDEV(major,1));class_destroy(gpio_cls);unregister_chrdev(major,"GPIO");
}module_init(gpio_driver_init);
module_exit(gpio_driver_exit);
MODULE_LICENSE("GPL");
【Makefile】
# KERNEL_DIR = /usr/src/linux-headers-5.4.65-sunxi/
KERNEL_DIR =/home/socket/Desktop/linux-5.4-orangepi# CROSS_COMPILE = arm-linux-gnueabihf-
CROSS_COMPILE = arm-none-linux-gnueabihf-all:make -C $(KERNEL_DIR) M=`pwd` modules$(CROSS_COMPILE)gcc test_led_app.c -o test_led_app
clean:make -C $(KERNEL_DIR) M=`pwd` modules cleanrm -rf modules.order test_led_appOrangePiPlus_leds-y := led_driver.o orangepi_plus_led1.o orangepi_plus_led2.o
obj-m += OrangePiPlus_leds.o