飞腾派使用内核态编程完成LED20控制操作

发布时间 2023-11-01 20:27:44作者: p1cky

1 基础知识

在该程序设计过程中我们首先需要学习如何在内核态编程。

1.1 内核态编程

在内核态中编写C语言程序和在用户态中编写C语言程序不同,在用户态中编写C语言程序,我们可以使用libc库,通过系统调用访问内核态的相关操作。

基础的内核态程序如下:

#include<linux/init.h>
#include<linux/module.h>
static int test_init(void)
{
    printk("hello kernel\n");
    return 0;
}
static void test_exit(void)
{
    printk("bye\n");
}

module_init(test_init);
module_exit(test_exit);

MODULE_LICENSE("GPL");

首先是加载和卸载模块。

加载模块我们使用的是函数module_init,该函数的作用是告诉内核你编写模块程序是从哪里开始执行,module_init()中的参数就是入口函数的函数名。

卸载模块我们使用的函数时module_exit,该函数的作用是告诉内核你编写模块程序是从哪里离开,module_exit()中的参数名就是卸载函数的函数名。

在进行内核态编程的过程中,头文件为

#include<linux/init.h> //init&exit相关宏
#include<linux/module.h> //所有模块都需要的头文件

由于内核态编程和用户层编程所使用的库函数不一样,所以它的头文件也和我们在用户层编写程序时所使用的头文件不一样。

内核层头文件的位置:/usr/src/linux-x.y.z/include/

用户层头文件的位置:/usr/include/

printk是内核信息打印函数,功能和标准C库的printf类似。

MODULE_LICENSE("GPL") 模块声明,描述内核模块的许可权限。如果不声明LICENSE,模块被加载时,将收到内核的警告。

1.2 Makefile

obj-m :=file.o

all:
	make -C 你的Linux环境中Makefile所在的文件的位置路径 M=$(PWD) modules
clean:
	make -C 你的Linux环境中Makefile所在的文件的位置路径 M=$(PWD) modules clean

all和clean下面的命令要严格使用“tab”。

obj-m 后面的文件是你想要编译的文件,表示编译连接后将生成的file.ko模块。

因为在内核态编译过程中使用Makefile文件,实际上你在使用make的过程中,时经历了两次make。第一次make是你自己编写的Makefile文件,你使用了该文件进行了一次make;第二次make是Linux系统环境中自带的Makefile,你使用了该文件进行了第二次make。

-C 指定内核Kbuild Makefile所在路径。(Linux环境下Makefile路径)

M=指定模块所在路径。(自己编写的Makefile路径)

make modules:编译模块

make clean:清楚模块编译产生的文件

1.3 内核模块加载命令

sudo insmod hello.ko //加载模块
sudo rmmod hello //卸载模块
dmesg //查看日志(查看printk的输出)
//模块相关命令
lsmod //查看本机模块
modinfo //查看模块信息

rmmod后面要加用lsmod查看到的模块名字“hello”,而不是“hello.ko"。

1.4 硬件驱动原理

image-20231101195503588

我们通过程序控制LED20灯的流程如下:

我们写出代码后放入硬盘中,程序在执行过程中被调入内存,在CPU中执行。CPU通过执行程序中的命令来控制GPIO控制器,通过GPIO控制器控制我们想要控制的外设。

在编写程序的硬件驱动流程如下:
我们首先来看我们使用的LED灯的硬件:
image-20231101195740333

可以发现,如果我们想要控制灯灭的话,我们需要向CPU_RUN输入低电平信号,从而使得路径上没有电势差导致没有电流,最后灯灭。

如果我们想要控制CPU_RUN输入低电平信号的话,我们需要查看其在SOC中对应的引脚。

image-20231101195926306

我们可以发现,CPU_RUN对应的引脚是E37,所以我们接下来要看E37引脚。

image-20231101200009094

我们发现如果我们要向CPU_RUN输入低电平的话,我们需要使得E37引脚使用功能6:GPIO1_8。

那么我们就要使用PAD控制器,该控制器控制引脚的复用。

PAD复用有两个知识点:
1.用户可以通过配置控制寄存器来完成复用。

2.在PAD的内存空间中(也就是寄存器中)[2:0]低三位的空间用于配置复用功能。

所以我们首先需要找到PAD寄存器的基地址,然后找到E37引脚的偏移地址,就可以锁定该引脚的位置。然后对该引脚的数据进行更改,低三位的数值变为110,从而使用功能6。

image-20231101200353122

image-20231101200402227

然后我们控制GPIO控制器。

想让GPIO输出低电平需要控制两个寄存器:
1.配置端口方向控制寄存器GPIO_SWPORT_DDR方向为输出。

2.配置端口输出寄存器GPIO_SWPORT_DR。

首先我们需要找到这两个寄存器的地址。

image-20231101200547653

然后我们先配置方向控制寄存器为”输出“。

image-20231101200633695

配置GPIO1_8为输出方向,则修改GPIO_SWPORT_DDR第8位为“1”。

然后配置输出寄存器输出”低电平“。

image-20231101200725233

配置GPIO1_8输出低电平,则修改GPIO_SWPORT_DR第8位为“0”。

1.5 IO资源访问

在Linux内核下,所有的物理地址都会通过MMU(Memory Management Unit 内存管理单元)进行映射得到一个虚拟地址。

那么我们如果想要操作物理地址的话,就需要地址映射函数(ioremap)和解除映射函数(iounmap),帮助我们将物理地址映射为虚拟地址,然后对虚拟地址进行相关的操作。

函数演示如下:

#include<linux/io.h>
#include<linux/init.h>
#include<linux/module.h>
void __iomen* ioGPIO5_15_DR;
static int drv_led_init(void)
{
    //将物理地址与虚拟地址映射
    ioGPIO5_15_DR=ioremap(0x28039000,4);
    printk("%p",ioGPIO5_15_DR); //输出得到的虚拟地址
    return 0;
}
static void drv_led_exit(void)
{
    //解除映射
    iounmap(ioGPIO5_15_DR);
}

module_init(drv_led_init);
module_exit(drv_led_exit);
MODULE_LICENSE("GPL");

IO读写操作我们使用的函数时writel()和readl()。

writel()往内存映射的I/O空间上写数据,writel()I/O上写入32位数据(4字节)。

readl()往内存映射的I/O空间上读数据,readl()I/O上读取32位数据(4字节)。

典型的寄存器操作(读-改-写)

//定义临时变量
int val=0;
//读:获取GPIO5的数据寄存器的原值
val=readl(ioGPIO5_15_DR);
//改:将第15位修改位1
val |=(1<<15) //用与或操作修改数值
//写:将新值写入GPIO5的数据寄存器
writel(val,ioGPIO5_15_DR);

2 程序

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/io.h>
#include <linux/uaccess.h>

#define E37_reg0 (0x32B30000+0x01F8)
#define GPIO1_8_DDR (0x28035000+0x04)
#define GPIO1_8_DR (0x28035000+0x00)

void __iomem* ioE37_reg0;
void __iomem* ioGPIO1_8_DDR;
void __iomem* ioGPIO1_8_DR;

void pad_set(void)
{
    int val = 0;
     
    val = readl(ioE37_reg0);
    
    val &= ~(0x7 << 0);
    val |= (0x6 << 0);
    
    writel(val, ioE37_reg0);
}

void gpio_set(void)
{
    int val = 0;
     
    val = readl(ioGPIO1_8_DDR);
    
    val |= (0x1 << 8);
    
    writel(val, ioGPIO1_8_DDR);
}

void led_off(void)
{
    int val = 0;
     
    val = readl(ioGPIO1_8_DR);
    
    val &= ~(0x1 << 8);
    
    writel(val, ioGPIO1_8_DR);
}

static int drv_led_init(void)
{
    ioE37_reg0 = ioremap(E37_reg0, 4);
    ioGPIO1_8_DDR = ioremap(GPIO1_8_DDR, 4);
    ioGPIO1_8_DR = ioremap(GPIO1_8_DR, 4);
    
    pad_set(); 
    gpio_set(); 
    
    led_off();
    
    return 0;
}
 
static void drv_led_exit(void)
{
    iounmap(ioGPIO1_8_DR);
    iounmap(ioGPIO1_8_DDR);
    iounmap(ioE37_reg0);
}
 
module_init(drv_led_init);
module_exit(drv_led_exit);

MODULE_LICENSE("GPL");