文章目录
- 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 0x01c20800
static 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-ON
int 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-on
switch(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-on
switch(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 clean
rm -rf modules.order test_led_app
OrangePiPlus_leds-y := led_driver.o orangepi_plus_led1.o orangepi_plus_led2.o
obj-m += OrangePiPlus_leds.o