【Linux驱动开发】通过ioremap虚拟内存映射的寄存器操作驱动、C应用函数库开发 devmem命令测试(正点原子STM32MP135文档BUG)

【Linux驱动开发】通过ioremap虚拟内存映射的寄存器操作驱动、C应用函数库开发 devmem命令测试(正点原子STM32MP135文档BUG)

【Linux驱动开发】通过ioremap虚拟内存映射的寄存器操作驱动 devmem命令测试

gitee库:

html 复制代码
https://gitee.com/Mike_Zhou_Admin/Ctrl_Reg_Driver

后续更新以gitee为准

文章目录

Linux寄存器操作

devmem

Linux中可以通过devmem命令来进行物理寄存器操作

其命令格式为:

读:

c 复制代码
devmem [ADDR] [长度] 

写:

c 复制代码
devmem [ADDR] [长度] 内容

如:

c 复制代码
devmem 0x5000A000 32 0x12345678

如下:

但要注意的是 操作的寄存器不能有内核驱动/应用冲突

如果有冲突 被应用覆盖了 可能就会出现写入不了或者造成设备错误

如:

有的寄存器也可能写入以后过段时间才会被覆盖

所以尽量用没有用到的寄存器进行测试

在Linux C应用层 则通过打开/dev/devmem驱动

然后通过mmap函数来实现

ioremap虚拟内存映射

该虚拟内存操作函数在进行驱动开发时可以被调用

虽然Linux设备如今大多已支持直接从硬件地址读写 但不建议直接采用

对于安装了MMU的设备 可以通过MMU映射到虚拟内存地址 然后对虚拟内存读写后内核则进行物理地址操作
ioremap 函数用于获取指定物理地址空间对应的虚拟地址空间

c 复制代码
void __iomem *ioremap(resource_size_t res_cookie, size_t size);

卸载则用:

c 复制代码
void iounmap (volatile void __iomem *addr)

Linux设备最好是通过虚拟内存来访问 并且用以下的几组函数来操作内存

使用 ioremap 函数将寄存器的物理地址映射到虚拟地址以后,我们就可以直接通过指针访问这些地址,但是 Linux 内核不建议这么做,而是推荐使用一组操作函数来对映射后的内存进行读写操作。

读:

c 复制代码
 u8 readb(const volatile void __iomem *addr)
 u16 readw(const volatile void __iomem *addr)
 u32 readl(const volatile void __iomem *addr)

写:

c 复制代码
 void writeb(u8 value, volatile void __iomem *addr)
 void writew(u16 value, volatile void __iomem *addr)
 void writel(u32 value, volatile void __iomem *addr)

寄存器操作驱动开发

为了能够在应用层使用write read open等函数直接调用寄存器

而又不直接操作硬件地址 保证安全

通过新字符设备方式开发了一个驱动

该驱动通过lseek指定地址

通过write和read方法操作寄存器

全局、常量如下:

c 复制代码
#define Ctrl_Reg_Driver_CNT			1		  	/* 设备号个数 */
#define Ctrl_Reg_Driver_NAME			"Ctrl_Reg_Driver"	/* 名字 */

typedef struct
{
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;		/* 类 		*/
	struct device *device;	/* 设备 	 */
	int major;				/* 主设备号	  */
	int minor;				/* 次设备号   */
    unsigned long long int Current_Offset;
    void __iomem *Current_Reg;
}Ctrl_Reg_Driver_dev;

Ctrl_Reg_Driver_dev Ctrl_Reg_Driver;
static char readbuf[256];		/* 读缓冲区 */
static char writebuf[256];		/* 写缓冲区 */

其中Current_Offset为通过lseek设置的地址(不直接写入到文件)

另外Current_Reg为虚拟内存地址映射

打开设备时 将结构体写入到私有数据中

c 复制代码
((Ctrl_Reg_Driver_dev*)(filp->private_data))->Current_Offset = offt;

读写操作

读寄存器的函数注释如下:

c 复制代码
/*
 * @description		: 从设备读取数据 
 * @param - filp 	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 返回给用户空间的数据缓冲区
 * @param - cnt 	: 要读取的寄存器个数(32位寄存器最大值为64)
 * @param - offt 	: 要读取的物理地址
 * @return 			: 读取的字节数,如果为负值,表示读取失败
 */

由于缓冲区只给定了256 一个寄存器为32位 4字节

所以这里寄存器个数最大值为64

在进行操作时 通过for循环来进行cnt计数的地址偏移

c 复制代码
static ssize_t Ctrl_Reg_Driver_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int retvalue = 0;
    u32 reg_value=0;
    unsigned char i=0;
    unsigned long long int offset = ((Ctrl_Reg_Driver_dev*)(filp->private_data))->Current_Offset;
    unsigned long long int reg_addr=0;
    for(i=0;i<cnt;i++)
    {
        reg_addr = offset + i*4;
        ((Ctrl_Reg_Driver_dev*)(filp->private_data))->Current_Reg = ioremap(reg_addr, 4);    
        reg_value=readl(((Ctrl_Reg_Driver_dev*)(filp->private_data))->Current_Reg);
        memcpy(&readbuf[i*4], &reg_value, 4);        
        iounmap(((Ctrl_Reg_Driver_dev*)(filp->private_data))->Current_Reg);
    }
    retvalue = copy_to_user(buf, readbuf, cnt*4);
	return retvalue;
}

同理写入为:

c 复制代码
static ssize_t Ctrl_Reg_Driver_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    int retvalue = 0;
    u32 reg_value=0;
    unsigned char i=0;
    unsigned long long int offset = ((Ctrl_Reg_Driver_dev*)(filp->private_data))->Current_Offset;
    unsigned long long int reg_addr=0;

    retvalue = copy_from_user(writebuf, buf, cnt*4);    
    if(retvalue!=0)
    {
        return retvalue;
    }
    
    for(i=0;i<cnt;i++)
    {
        reg_addr = offset + i*4;
        ((Ctrl_Reg_Driver_dev*)(filp->private_data))->Current_Reg = ioremap(reg_addr, 4);    
        memcpy(&reg_value,&writebuf[i*4], 4);
        writel(reg_value, ((Ctrl_Reg_Driver_dev*)(filp->private_data))->Current_Reg); 
        iounmap(((Ctrl_Reg_Driver_dev*)(filp->private_data))->Current_Reg);
    }

	return retvalue;
}

注册并安装后即可使用

Linux C应用层函数库开发

虽然在驱动上支持多个地址读写

但是一般在应用层面也就只需要操作一个寄存器

也就是cnt为1

另外 在操作寄存器之前 需要通过lseek来指定寄存器

所以应用层可以做如下包装:

c 复制代码
Ctrl_Reg_Driver_Struct Ctrl_Reg_Driver_Read(Ctrl_Reg_Driver_Struct Stu)
{
    uint8_t databuf[4];
    uint32_t val=0;
    int retvalue = lseek(Ctrl_Reg_Driver_Num,Stu.ADDR,0);
    if(retvalue < 0){
		printf("Ctrl_Reg_Driver lseek failed!\r\n");
        Close_Ctrl_Reg_Driver();
        Stu.Error_Flag=-2;
		return Stu;
	}

    retvalue = read(Ctrl_Reg_Driver_Num, &databuf[0], 1);
	if(retvalue < 0){
		printf("Ctrl_Reg_Driver Read Failed!\r\n");
		Close_Ctrl_Reg_Driver();
        Stu.Error_Flag=-1;
		return Stu;
	}

    memcpy(&val,&databuf[0],4);
    Stu.Value=val;
    Stu.Error_Flag=0;
    return Stu;
}

Ctrl_Reg_Driver_Struct Ctrl_Reg_Driver_Write(Ctrl_Reg_Driver_Struct Stu)
{
    uint8_t databuf[4];
    uint32_t val = Stu.Value;
    int retvalue = lseek(Ctrl_Reg_Driver_Num,Stu.ADDR,0);
    if(retvalue < 0){
		printf("Ctrl_Reg_Driver lseek failed!\r\n");
        Close_Ctrl_Reg_Driver();
        Stu.Error_Flag=-2;
		return Stu;
	}

    memcpy(&databuf[0],&val,4);
    retvalue = write(Ctrl_Reg_Driver_Num, &databuf[0], 1);
	if(retvalue < 0){
		printf("Ctrl_Reg_Driver Write Failed!\r\n");
		Close_Ctrl_Reg_Driver();
        Stu.Error_Flag=-1;
		return Stu;
	}

    Stu.Error_Flag=0;
    return Stu;
}

Ctrl_Reg_Driver_Num为全局变量 是设备打开的序号标志

所有操作函数和定义如下:

c 复制代码
#define Ctrl_Reg_Driver_Name "/dev/Ctrl_Reg_Driver"

static int Ctrl_Reg_Driver_Num;

typedef struct 
{
    uint32_t ADDR;
    uint32_t Value;
    int Error_Flag;
}Ctrl_Reg_Driver_Struct;

int Init_Ctrl_Reg_Driver(void);
int Close_Ctrl_Reg_Driver(void);
Ctrl_Reg_Driver_Struct Ctrl_Reg_Driver_Read(Ctrl_Reg_Driver_Struct Stu);
Ctrl_Reg_Driver_Struct Ctrl_Reg_Driver_Write(Ctrl_Reg_Driver_Struct Stu);

调用测试:

c 复制代码
int main(int argc, char *argv[])
{

    uint32_t val=0;
    Ctrl_Reg_Driver_Struct Stu;
    Init_Ctrl_Reg_Driver();
    Stu.ADDR=0x50007000;
    val=Ctrl_Reg_Driver_Read(Stu).Value;
    printf("read 1: %X\n",val);
    
    val=0x12345678;    
    printf("write 1: %X\n",val);

    Stu.Value=val;
    Ctrl_Reg_Driver_Write(Stu);
	
    val=Ctrl_Reg_Driver_Read(Stu).Value;
    printf("read 2: %X\n",val);

    Stu.Value=0xFFFFFFFF;
    Ctrl_Reg_Driver_Write(Stu);
    val=Ctrl_Reg_Driver_Read(Stu).Value;
    printf("read 3: %X\n",val);

    Stu.Value=0x00000000;
    Ctrl_Reg_Driver_Write(Stu);
    val=Ctrl_Reg_Driver_Read(Stu).Value;
    printf("read 4: %X\n",val);

	Close_Ctrl_Reg_Driver();
	return 0;
}

完整代码

驱动

c 复制代码
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>

#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define Ctrl_Reg_Driver_CNT			1		  	/* 设备号个数 */
#define Ctrl_Reg_Driver_NAME			"Ctrl_Reg_Driver"	/* 名字 */

typedef struct
{
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;		/* 类 		*/
	struct device *device;	/* 设备 	 */
	int major;				/* 主设备号	  */
	int minor;				/* 次设备号   */
    unsigned long long int Current_Offset;
    void __iomem *Current_Reg;
}Ctrl_Reg_Driver_dev;

Ctrl_Reg_Driver_dev Ctrl_Reg_Driver;
static char readbuf[256];		/* 读缓冲区 */
static char writebuf[256];		/* 写缓冲区 */

/*
 * @description		: 从设备读取数据 
 * @param - filp 	: 要打开的设备文件(文件描述符)
 * @param - offt 	: 要读取的物理地址
 * @return 			: 读取的字节数,如果为负值,表示读取失败
 */
static loff_t Ctrl_Reg_Driver_llseek(struct file *filp, loff_t offt,  int cnt)
{
    ((Ctrl_Reg_Driver_dev*)(filp->private_data))->Current_Offset = offt;
	return 0;
}


/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int Ctrl_Reg_Driver_open(struct inode *inode, struct file *filp)
{
    Ctrl_Reg_Driver.Current_Offset=0;
	filp->private_data = &Ctrl_Reg_Driver; /* 设置私有数据 */
	return 0;
}


/*
 * @description		: 从设备读取数据 
 * @param - filp 	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 返回给用户空间的数据缓冲区
 * @param - cnt 	: 要读取的寄存器个数(32位寄存器最大值为64)
 * @param - offt 	: 要读取的物理地址
 * @return 			: 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t Ctrl_Reg_Driver_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int retvalue = 0;
    u32 reg_value=0;
    unsigned char i=0;
    unsigned long long int offset = ((Ctrl_Reg_Driver_dev*)(filp->private_data))->Current_Offset;
    unsigned long long int reg_addr=0;
    for(i=0;i<cnt;i++)
    {
        reg_addr = offset + i*4;
        ((Ctrl_Reg_Driver_dev*)(filp->private_data))->Current_Reg = ioremap(reg_addr, 4);    
        reg_value=readl(((Ctrl_Reg_Driver_dev*)(filp->private_data))->Current_Reg);
        memcpy(&readbuf[i*4], &reg_value, 4);        
        iounmap(((Ctrl_Reg_Driver_dev*)(filp->private_data))->Current_Reg);
    }
    retvalue = copy_to_user(buf, readbuf, cnt*4);
	return retvalue;
}

/*
 * @description		: 向设备写数据 
 * @param - filp 	: 设备文件,表示打开的文件描述符
 * @param - buf 	: 要写给设备写入的数据
 * @param - cnt 	: 要写入的寄存器个数(32位寄存器最大值为64)
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t Ctrl_Reg_Driver_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    int retvalue = 0;
    u32 reg_value=0;
    unsigned char i=0;
    unsigned long long int offset = ((Ctrl_Reg_Driver_dev*)(filp->private_data))->Current_Offset;
    unsigned long long int reg_addr=0;

    retvalue = copy_from_user(writebuf, buf, cnt*4);    
    if(retvalue!=0)
    {
        return retvalue;
    }
    
    for(i=0;i<cnt;i++)
    {
        reg_addr = offset + i*4;
        ((Ctrl_Reg_Driver_dev*)(filp->private_data))->Current_Reg = ioremap(reg_addr, 4);    
        memcpy(&reg_value,&writebuf[i*4], 4);
        writel(reg_value, ((Ctrl_Reg_Driver_dev*)(filp->private_data))->Current_Reg); 
        iounmap(((Ctrl_Reg_Driver_dev*)(filp->private_data))->Current_Reg);
    }

	return retvalue;
}


/*
 * @description		: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int Ctrl_Reg_Driver_release(struct inode *inode, struct file *filp)
{
    // iounmap(((Ctrl_Reg_Driver_dev*)(filp->private_data))->Current_Reg);
	return 0;
}

/* 设备操作函数 */
static struct file_operations Ctrl_Reg_Driver_fops = {
	.owner = THIS_MODULE,
	.open = Ctrl_Reg_Driver_open,
	.read = Ctrl_Reg_Driver_read,
	.write = Ctrl_Reg_Driver_write,
	.release = 	Ctrl_Reg_Driver_release,
    .llseek = Ctrl_Reg_Driver_llseek,
};

static int __init Ctrl_Reg_Driver_init(void)
{
    int ret;

    ret = alloc_chrdev_region(&Ctrl_Reg_Driver.devid, 0, Ctrl_Reg_Driver_CNT, Ctrl_Reg_Driver_NAME);	/* 申请设备号 */
    if(ret < 0) {
        pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", Ctrl_Reg_Driver_NAME, ret);
        // iounmap(Ctrl_Reg_Driver.Current_Reg);
        return -EIO;
    }
    Ctrl_Reg_Driver.major = MAJOR(Ctrl_Reg_Driver.devid);	/* 获取分配号的主设备号 */
    Ctrl_Reg_Driver.minor = MINOR(Ctrl_Reg_Driver.devid);	/* 获取分配号的次设备号 */

    printk("Ctrl_Reg_Driver major=%d,minor=%d\r\n",Ctrl_Reg_Driver.major, Ctrl_Reg_Driver.minor);
    Ctrl_Reg_Driver.cdev.owner = THIS_MODULE;
    cdev_init(&Ctrl_Reg_Driver.cdev, &Ctrl_Reg_Driver_fops);

    ret = cdev_add(&Ctrl_Reg_Driver.cdev, Ctrl_Reg_Driver.devid, Ctrl_Reg_Driver_CNT);
	if(ret < 0)
    {
		unregister_chrdev_region(Ctrl_Reg_Driver.devid, Ctrl_Reg_Driver_CNT);
        // iounmap(Ctrl_Reg_Driver.Current_Reg);
        return -EIO;
    }

    Ctrl_Reg_Driver.class = class_create(THIS_MODULE, Ctrl_Reg_Driver_NAME);
	if (IS_ERR(Ctrl_Reg_Driver.class)) 
    {
		cdev_del(&Ctrl_Reg_Driver.cdev);
        unregister_chrdev_region(Ctrl_Reg_Driver.devid, Ctrl_Reg_Driver_CNT);
        // iounmap(Ctrl_Reg_Driver.Current_Reg);
        return -EIO;
	}

    Ctrl_Reg_Driver.device = device_create(Ctrl_Reg_Driver.class, NULL, Ctrl_Reg_Driver.devid, NULL, Ctrl_Reg_Driver_NAME);
	if (IS_ERR(Ctrl_Reg_Driver.device)) 
    {
		class_destroy(Ctrl_Reg_Driver.class);
        cdev_del(&Ctrl_Reg_Driver.cdev);
        unregister_chrdev_region(Ctrl_Reg_Driver.devid, Ctrl_Reg_Driver_CNT);
        // iounmap(Ctrl_Reg_Driver.Current_Reg);
        return -EIO;
	}

    return 0;
}

static void __exit Ctrl_Reg_Driver_exit(void)
{
    // iounmap(Ctrl_Reg_Driver.Current_Reg);
    cdev_del(&Ctrl_Reg_Driver.cdev);/*  删除cdev */
	unregister_chrdev_region(Ctrl_Reg_Driver.devid, Ctrl_Reg_Driver_CNT); /* 注销设备号 */
	device_destroy(Ctrl_Reg_Driver.class, Ctrl_Reg_Driver.devid);
	class_destroy(Ctrl_Reg_Driver.class);
}

module_init(Ctrl_Reg_Driver_init);
module_exit(Ctrl_Reg_Driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("MIKE_ZHOU");
MODULE_INFO(intree, "Y");

应用库

c 复制代码
#include "Ctrl_Reg_Driver_App.h"

static int Ctrl_Reg_Driver_Num=0;

int Init_Ctrl_Reg_Driver(void)
{
    Ctrl_Reg_Driver_Num = open(Ctrl_Reg_Driver_Name, O_RDWR);
	if(Ctrl_Reg_Driver_Num < 0){
		printf("Ctrl_Reg_Driver open failed!\r\n");
	}
    return Ctrl_Reg_Driver_Num;
}

int Close_Ctrl_Reg_Driver(void)
{
    int retvalue = close(Ctrl_Reg_Driver_Num); /* 关闭文件 */
	if(retvalue < 0){
		printf("Ctrl_Reg_Driver close failed!\r\n");
	}
    return retvalue;
}

Ctrl_Reg_Driver_Struct Ctrl_Reg_Driver_Read(Ctrl_Reg_Driver_Struct Stu)
{
    uint8_t databuf[4];
    uint32_t val=0;
    int retvalue = lseek(Ctrl_Reg_Driver_Num,Stu.ADDR,0);
    if(retvalue < 0){
		printf("Ctrl_Reg_Driver lseek failed!\r\n");
        Close_Ctrl_Reg_Driver();
        Stu.Error_Flag=-2;
		return Stu;
	}

    retvalue = read(Ctrl_Reg_Driver_Num, &databuf[0], 1);
	if(retvalue < 0){
		printf("Ctrl_Reg_Driver Read Failed!\r\n");
		Close_Ctrl_Reg_Driver();
        Stu.Error_Flag=-1;
		return Stu;
	}

    memcpy(&val,&databuf[0],4);
    Stu.Value=val;
    Stu.Error_Flag=0;
    return Stu;
}

Ctrl_Reg_Driver_Struct Ctrl_Reg_Driver_Write(Ctrl_Reg_Driver_Struct Stu)
{
    uint8_t databuf[4];
    uint32_t val = Stu.Value;
    int retvalue = lseek(Ctrl_Reg_Driver_Num,Stu.ADDR,0);
    if(retvalue < 0){
		printf("Ctrl_Reg_Driver lseek failed!\r\n");
        Close_Ctrl_Reg_Driver();
        Stu.Error_Flag=-2;
		return Stu;
	}

    memcpy(&databuf[0],&val,4);
    retvalue = write(Ctrl_Reg_Driver_Num, &databuf[0], 1);
	if(retvalue < 0){
		printf("Ctrl_Reg_Driver Write Failed!\r\n");
		Close_Ctrl_Reg_Driver();
        Stu.Error_Flag=-1;
		return Stu;
	}

    Stu.Error_Flag=0;
    return Stu;
}
c 复制代码
#ifndef __Ctrl_Reg_Driver_App_H__
#define __Ctrl_Reg_Driver_App_H__

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#define Ctrl_Reg_Driver_Name "/dev/Ctrl_Reg_Driver"

static int Ctrl_Reg_Driver_Num;

typedef struct 
{
    uint32_t ADDR;
    uint32_t Value;
    int Error_Flag;
}Ctrl_Reg_Driver_Struct;

int Init_Ctrl_Reg_Driver(void);
int Close_Ctrl_Reg_Driver(void);
Ctrl_Reg_Driver_Struct Ctrl_Reg_Driver_Read(Ctrl_Reg_Driver_Struct Stu);
Ctrl_Reg_Driver_Struct Ctrl_Reg_Driver_Write(Ctrl_Reg_Driver_Struct Stu);

#endif

正点原子STM32MP135文档BUG

在进行开发时 遇到了我上述提到的寄存器无法写入的BUG

于是去看正点原子的文档 发现他们的问题:

他们在操作PI3时 都是左移3位 实际上应该是6位

另外 在寄存器无法写入时 正点原子技术支持如是说:(st那边的问题)

而我实测其实是内核应用冲突的问题

附录:嵌入式Linux驱动开发基本步骤

开发环境

首先需要交叉编译器和Linux环境

这里如果是ARM内核 则需要采用ARM的交叉编译器编译器:

c 复制代码
arm-none-linux-gnueabihf-gcc

同时需要目标ARM板子的Linux系统内核环境

并编译内核:

c 复制代码
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- stm32mp1_atk_defconfig
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- uImage vmlinux dtbs LOADADDR=0xC2000040 -j4
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- stm32mp1_atk_defconfig
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- modules -j4

如果是第一次编译 则可能有所不同 需要根据实际手册来

以下是我编译好 打包好的虚拟机

c 复制代码
通过百度网盘分享的文件:适用于STM32MP135开发板的开发环境虚拟机
链接:https://pan.baidu.com/s/1Sf_wk2gEPj0JlQ7X_rpQcg 
提取码:d9sj

驱动文件

对于已完成的驱动开发 需要进行编译后进行安装

所有驱动文件在开发上都需要进行驱动入口和出口开发

譬如需要编写驱动入口和退出函数

c 复制代码
static int __init xxx_init(void)
static void __exit xxx_exit(void)

然后再模块注册 需要调用到以下函数:

c 复制代码
module_init(xxx_init); //注册模块加载函数
module_exit(xxx_exit); //注册模块卸载函数

最后在结尾添加作者和许可信息

c 复制代码
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");
MODULE_INFO(intree, "Y");

为了欺骗内核,给本驱动添加 intree 标记,如果不加就会有"loading out-of-tree module taints kernel."这个警告。

然后才能编译驱动

编译驱动

编译前要配置环境变量:

cpp 复制代码
source /etc/profile

需要先在此文件中 指定环境所在目录

Makefile

c 复制代码
KERNELDIR := /home/alientek/linux/atk-mp135/linux/my_linux/linux-5.15.24
CURRENT_PATH := $(shell pwd)

obj-m := test.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules

clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
cpp 复制代码
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf-

安装驱动

将编译好的驱动推荐放置到ARM板子的/lib/modules/<kernel-version>目录下

加载驱动:
insmod test.komodprobe test

建议用modprobe 原因是可以解决依赖关系

查看已安装的模块:

使用lsmodcat /proc/devices查看 其中 还能看到已安装的驱动设备号(新安装的不能重复)

创建设备节点文件:(如果自动创建就不需要)

c 复制代码
mknod /dev/test c 200 0

查看节点文件:

c 复制代码
ls /dev/test -l

最后如果不需要了 则卸载

卸载模块:
rmmod testmodprobe -r test

自动创建设备节点文件

使用udevmdev即可实现自动创建

如果要使用 则在驱动开发中写入到驱动入口函数中

(一般在 cdev_add 函数后面添加自动创建设备节点相关代码 一些具体的变量和说明见后文新字符驱动开发)

完成开发后 安装驱动时就自动帮你创建好驱动设备节点文件

否则就需要手动去添加

首先要创建一个 class 类,class 是个结构体,定义在文件include/linux/device/class.h 里面。class_create 是类创建函数,class_create 是个宏定义

c 复制代码
struct class *class_create (struct module *owner, const char *name)

class_create 一共有两个参数,参数 owner 一般为 THIS_MODULE,参数 name 是类名字

卸载驱动程序的时候需要删除掉类,类删除函数为 class_destroy,函数原型如下:

c 复制代码
void class_destroy(struct class *cls);

然后使用 device_create 函数在类下面创建设备

c 复制代码
device_create(struct class *cls,
 struct device *parent,
 dev_t devt,
 void *drvdata,
 const char *fmt, ...);

参数 cls 就是设备要创建哪个类下面;参数 parent 是父设备,一般为 NULL,也就是没有父设备;参数 devt 是设备号;参数 drvdata 是设备可能会使用的一些数据,一般为 NULL;参数 fmt 是设备名字,如果设置 fmt=xxx 的话,就会生成/dev/xxx 这个设备文件。

卸载则调用:

c 复制代码
void device_destroy(struct class *cls, dev_t devt);

如在已知设备号的情况下进行注册:

c 复制代码
struct class *class; /* 类 */ 
struct device *device; /* 设备 */
dev_t devid; /* 设备号 */ 

/* 驱动入口函数 */
 static int __init xxx_init(void)
{
 /* 创建类 */
class = class_create(THIS_MODULE, "xxx");
/* 创建设备 */
device = device_create(class, NULL, devid, NULL, "xxx");
return 0;
}

/* 驱动出口函数 */
 static void __exit led_exit(void)
{
 /* 删除设备 */
 device_destroy(newchrled.class, newchrled.devid);
 /* 删除类 */
class_destroy(newchrled.class);
 }

module_init(led_init);
module_exit(led_exit);

以上这些设备号、类、驱动等变量太多 可以用一个结构体来表示

c 复制代码
/* 设备结构体 */
struct test_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
};

通过将此结构体写入到驱动文件的私有变量中 即可使开发变得安全、规范

如:

c 复制代码
struct test_dev testdev;

 /* open 函数 */
static int test_open(struct inode *inode, struct file *filp)
{
filp->private_data = &testdev; /* 设置私有数据 */
return 0;
}

驱动开发

通过开发字符驱动等设备 编译成驱动*.ko文件 然后安装后即可调用

驱动设备号

驱动主要有主设备号 次设备号和驱动名

可以自定义 也可以自动申请

自定义的话 主设备号不能用冲突

查看已安装的模块:

使用lsmodcat /proc/devices查看 其中 还能看到已安装的驱动设备号(新安装的不能重复)

如果不采用分配的方式进行 直接自定义的话 就不需要看这一节下面的内容了

但如果要分配设备号的话 这里引入dev_t类型的设备号变量:

动态分配则用以下函数申请:

c 复制代码
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

函数 alloc_chrdev_region 用于申请设备号,此函数有 4 个参数:

dev:保存申请到的设备号。

baseminor:次设备号起始地址,alloc_chrdev_region 可以申请一段连续的多个设备号,这

些设备号的主设备号一样,但是次设备号不同,次设备号以 baseminor 为起始地址地址开始递

增。一般 baseminor 为 0,也就是说次设备号从 0 开始。

count:要申请的设备号数量。

name:设备名字。

注销字符设备之后要释放掉设备号,设备号释放函数如下:

c 复制代码
void unregister_chrdev_region(dev_t from, unsigned count)

或者采用以下两个函数都能来进行申请 第二个函数首先得是确定了主设备号的

c 复制代码
//无设备号
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
//给定了设备号
int register_chrdev_region(dev_t from, unsigned count, const char *name)

如:

c 复制代码
int major; /* 主设备号 */
int minor; /* 次设备号 */
dev_t devid; /* 设备号 */
 
 if (major) { /* 定义了主设备号 */
 devid = MKDEV(major, 0); /* 大部分驱动次设备号都选择 0*/
register_chrdev_region(devid, 1, "test");
} else { /* 没有定义设备号 */
alloc_chrdev_region(&devid, 0, 1, "test"); /* 申请设备号 */
 major = MAJOR(devid); /* 获取分配号的主设备号 */
 minor = MINOR(devid); /* 获取分配号的次设备号 */
 }

如果 major 有效的话就使用 MKDEV 来构建设备号,次设备号选择 0。

如果 major 无效,那就表示没有给定设备号。此时就要使用 alloc_chrdev_region

函数来申请设备号。设备号申请成功以后使用 MAJOR 和 MINOR 来提取出主设备号和次设备

注销字符设备之后要释放掉设备号 则是调用:

c 复制代码
void unregister_chrdev_region(dev_t from, unsigned count)

直接传入设备号数量即可

地址映射,虚拟内存和硬件内存地址

Linux设备如果最后要操作寄存器进行开发的话 不可避免的会使用内核寄存器

Linux设备如今大多已支持直接从硬件地址读写 但不建议直接采用

对于安装了MMU的设备 可以通过MMU映射到虚拟内存地址 然后对虚拟内存读写后内核则进行物理地址操作
ioremap 函数用于获取指定物理地址空间对应的虚拟地址空间

c 复制代码
void __iomem *ioremap(resource_size_t res_cookie, size_t size);

卸载则用:

c 复制代码
void iounmap (volatile void __iomem *addr)

Linux设备最好是通过虚拟内存来访问 并且用以下的几组函数来操作内存

使用 ioremap 函数将寄存器的物理地址映射到虚拟地址以后,我们就可以直接通过指针访问这些地址,但是 Linux 内核不建议这么做,而是推荐使用一组操作函数来对映射后的内存进行读写操作。

读:

c 复制代码
 u8 readb(const volatile void __iomem *addr)
 u16 readw(const volatile void __iomem *addr)
 u32 readl(const volatile void __iomem *addr)

写:

c 复制代码
 void writeb(u8 value, volatile void __iomem *addr)
 void writew(u16 value, volatile void __iomem *addr)
 void writel(u32 value, volatile void __iomem *addr)

字符驱动

其中 所有的外设、驱动等 都可以用字符驱动来开发 但不一定方便

因为字符驱动只能进行简单的打开 销毁 读写等

虽然本质上驱动的开发也是寄存器的读写 但用字符设备还是限制性很大

字符驱动可以实现open close write read等操作

另外字符驱动的文件结构体file中

有一个private_data变量 也就是私有变量 可以在初始化时将一些外部参数初始化成该变量存入

设置好好以后 就可以在在 write、read、close 等函数中直接读取 private_data即可得到设备结构体

旧字符驱动

字符驱动就是file文件驱动 在应用层用open read write close等函数来操作

字符驱动注册和注销需要:

c 复制代码
static inline int register_chrdev(unsigned int major, 
const char *name,
const struct file_operations *fops)
static inline void unregister_chrdev(unsigned int major, 
const char *name)

需要编写驱动入口和退出函数

c 复制代码
static int __init xxx_init(void)
static void __exit xxx_exit(void)

然后再模块注册 需要调用到以下函数:

c 复制代码
module_init(xxx_init); //注册模块加载函数
module_exit(xxx_exit); //注册模块卸载函数

在驱动入口和退出函数中调用register_chrdevunregister_chrdev函数进行字符驱动的注册与注销

其中 注册时需要传参设备号、名称和file_operations结构体

结构体中需要指定函数名称 该结构体下全是回调函数(函数指针)但也不是全部都要写 不过必须得几项必须要填

如:

c 复制代码
static struct file_operations test_fops = {
 .owner = THIS_MODULE, 
.open = chrtest_open,
.read = chrtest_read,
 .write = chrtest_write,
.release = chrtest_release,
};

另外 在write和read函数中 用户不得直接访问内存空间 所以要借助copy_from_usercopy_to_user来进行操作

最后在结尾添加作者和许可信息

c 复制代码
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");
MODULE_INFO(intree, "Y");

为了欺骗内核,给本驱动添加 intree 标记,如果不加就会有"loading out-of-tree module taints kernel."这个警告。

完整的代码如:

c 复制代码
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/init.h>
#include <linux/module.h>

/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名		: chrdevbase.c
作者	  	: 正点原子
版本	   	: V1.0
描述	   	: chrdevbase驱动文件。
其他	   	: 无
论坛 	   	: www.openedv.com
日志	   	: 初版V1.0 2020/12/26 正点原子创建
***************************************************************/

#define CHRDEVBASE_MAJOR	200				/* 主设备号 */
#define CHRDEVBASE_NAME		"chrdevbase" 	/* 设备名     */

static char readbuf[100];		/* 读缓冲区 */
static char writebuf[100];		/* 写缓冲区 */
static char kerneldata[] = {"kernel data!"};

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int chrdevbase_open(struct inode *inode, struct file *filp)
{
	//printk("chrdevbase open!\r\n");
	return 0;
}

/*
 * @description		: 从设备读取数据 
 * @param - filp 	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 返回给用户空间的数据缓冲区
 * @param - cnt 	: 要读取的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	int retvalue = 0;
	
	/* 向用户空间发送数据 */
	memcpy(readbuf, kerneldata, sizeof(kerneldata));
	retvalue = copy_to_user(buf, readbuf, cnt);
	if(retvalue == 0){
		printk("kernel senddata ok!\r\n");
	}else{
		printk("kernel senddata failed!\r\n");
	}
	
	//printk("chrdevbase read!\r\n");
	return 0;
}

/*
 * @description		: 向设备写数据 
 * @param - filp 	: 设备文件,表示打开的文件描述符
 * @param - buf 	: 要写给设备写入的数据
 * @param - cnt 	: 要写入的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
	int retvalue = 0;
	/* 接收用户空间传递给内核的数据并且打印出来 */
	retvalue = copy_from_user(writebuf, buf, cnt);
	if(retvalue == 0){
		printk("kernel recevdata:%s\r\n", writebuf);
	}else{
		printk("kernel recevdata failed!\r\n");
	}
	
	//printk("chrdevbase write!\r\n");
	return 0;
}

/*
 * @description		: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int chrdevbase_release(struct inode *inode, struct file *filp)
{
	//printk("chrdevbase release!\r\n");
	return 0;
}

/*
 * 设备操作函数结构体
 */
static struct file_operations chrdevbase_fops = {
	.owner = THIS_MODULE,	
	.open = chrdevbase_open,
	.read = chrdevbase_read,
	.write = chrdevbase_write,
	.release = chrdevbase_release,
};

/*
 * @description	: 驱动入口函数 
 * @param 		: 无
 * @return 		: 0 成功;其他 失败
 */
static int __init chrdevbase_init(void)
{
	int retvalue = 0;

	/* 注册字符设备驱动 */
	retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);
	if(retvalue < 0){
		printk("chrdevbase driver register failed\r\n");
	}
	printk("chrdevbase init!\r\n");
	return 0;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit chrdevbase_exit(void)
{
	/* 注销字符设备驱动 */
	unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
	printk("chrdevbase exit!\r\n");
}

/* 
 * 将上面两个函数指定为驱动的入口和出口函数 
 */
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);

/* 
 * LICENSE和作者信息
 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALIENTEK");
MODULE_INFO(intree, "Y");

然后就可以开始编译

新字符驱动

新字符驱动可以自动生成设备树文件等 比较方便 开发的方式大同小异

在 Linux 中使用 cdev 结构体表示一个字符设备,cdev 结构体在 include/linux/cdev.h 文件中

的定义如下:

示例代码 9.1.2.1 cdev 结构体

c 复制代码
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
} __randomize_layout;

可以看到 里面包含了file_operations 结构体 以及dev_t 变量等等

定义了cdev变量后 需要进行初始化

c 复制代码
void cdev_init(struct cdev *cdev, const struct file_operations *fops)

这里就需要传参file_operations变量了

这两个结构体的.owner都要为THIS_MODULE

如:

c 复制代码
 struct cdev testcdev;

/* 设备操作函数 */
static struct file_operations test_fops = {
 .owner = THIS_MODULE,
 /* 其他具体的初始项 */
 };
 
testcdev.owner = THIS_MODULE;
cdev_init(&testcdev, &test_fops); 
cdev_add(&testcdev, devid, 1);

初始化后 使用以下函数往cdev中添加dev设备号变量

这里要注意 虽然cdev中有dev变量 但不能直接赋值 需要使用cdev_add函数来添加

事实上 无论是写入dev还是读取dev 都不可直接在cdev中进行操作

(如果是C++ 就可以规定私有属性了 但C语言这里不行)

c 复制代码
int cdev_add(struct cdev *p, dev_t dev, unsigned count)

卸载时则需要删除cdev

c 复制代码
void cdev_del(struct cdev *p)

同时也要用unregister_chrdev_region函数去注销外部的dev变量

加上自动创建设备树等功能 则完整代码为:

c 复制代码
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>

#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名		: newchrled.c
作者	  	: 正点原子
版本	   	: V1.0
描述	   	: LED驱动文件。
其他	   	: 无
论坛 	   	: www.openedv.com
日志	   	: 初版V1.0 2020/11/24 正点原子团队创建
***************************************************************/
#define NEWCHRLED_CNT			1		  	/* 设备号个数 */
#define NEWCHRLED_NAME			"newchrled"	/* 名字 */
#define LEDOFF 					0			/* 关灯 */
#define LEDON 					1			/* 开灯 */
 
/* 寄存器物理地址 */
#define PERIPH_BASE     		     	(0x40000000)
#define MPU_AHB4_PERIPH_BASE			(PERIPH_BASE + 0x10000000)
#define RCC_BASE        		    	(MPU_AHB4_PERIPH_BASE + 0x0000)	
#define RCC_MP_AHB4ENSETR				(RCC_BASE + 0XA28)
#define GPIOI_BASE						(MPU_AHB4_PERIPH_BASE + 0xA000)	
#define GPIOI_MODER      			    (GPIOI_BASE + 0x0000)	
#define GPIOI_OTYPER      			    (GPIOI_BASE + 0x0004)	
#define GPIOI_OSPEEDR      			    (GPIOI_BASE + 0x0008)	
#define GPIOI_PUPDR      			    (GPIOI_BASE + 0x000C)	
#define GPIOI_BSRR      			    (GPIOI_BASE + 0x0018)

/* 映射后的寄存器虚拟地址指针 */
static void __iomem *MPU_AHB4_PERIPH_RCC_PI;
static void __iomem *GPIOI_MODER_PI;
static void __iomem *GPIOI_OTYPER_PI;
static void __iomem *GPIOI_OSPEEDR_PI;
static void __iomem *GPIOI_PUPDR_PI;
static void __iomem *GPIOI_BSRR_PI;

/* newchrled设备结构体 */
struct newchrled_dev{
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;		/* 类 		*/
	struct device *device;	/* 设备 	 */
	int major;				/* 主设备号	  */
	int minor;				/* 次设备号   */
};

struct newchrled_dev newchrled;	/* led设备 */

/*
 * @description		: LED打开/关闭
 * @param - sta 	: LEDON(0) 打开LED,LEDOFF(1) 关闭LED
 * @return 			: 无
 */
void led_switch(u8 sta)
{
	u32 val = 0;
	if(sta == LEDON) {
		val = readl(GPIOI_BSRR_PI);
		val |= (1 << 19);	
		writel(val, GPIOI_BSRR_PI);
	}else if(sta == LEDOFF) {
		val = readl(GPIOI_BSRR_PI);
		val|= (1 << 3);	
		writel(val, GPIOI_BSRR_PI);
	}	
}

/*
 * @description		: 取消映射
 * @return 			: 无
 */
void led_unmap(void)
{
		/* 取消映射 */
	iounmap(MPU_AHB4_PERIPH_RCC_PI);
	iounmap(GPIOI_MODER_PI);
	iounmap(GPIOI_OTYPER_PI);
	iounmap(GPIOI_OSPEEDR_PI);
	iounmap(GPIOI_PUPDR_PI);
	iounmap(GPIOI_BSRR_PI);
}

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int led_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &newchrled; /* 设置私有数据 */
	return 0;
}

/*
 * @description		: 从设备读取数据 
 * @param - filp 	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 返回给用户空间的数据缓冲区
 * @param - cnt 	: 要读取的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	return 0;
}

/*
 * @description		: 向设备写数据 
 * @param - filp 	: 设备文件,表示打开的文件描述符
 * @param - buf 	: 要写给设备写入的数据
 * @param - cnt 	: 要写入的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
	int retvalue;
	unsigned char databuf[1];
	unsigned char ledstat;

	retvalue = copy_from_user(databuf, buf, cnt);
	if(retvalue < 0) {
		printk("kernel write failed!\r\n");
		return -EFAULT;
	}

	ledstat = databuf[0];		/* 获取状态值 */

	if(ledstat == LEDON) {	
		led_switch(LEDON);		/* 打开LED灯 */
	} else if(ledstat == LEDOFF) {
		led_switch(LEDOFF);	/* 关闭LED灯 */
	}
	return 0;
}

/*
 * @description		: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int led_release(struct inode *inode, struct file *filp)
{
	return 0;
}

/* 设备操作函数 */
static struct file_operations newchrled_fops = {
	.owner = THIS_MODULE,
	.open = led_open,
	.read = led_read,
	.write = led_write,
	.release = 	led_release,
};

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init led_init(void)
{
	u32 val = 0;
	int ret;

	/* 初始化LED */
	/* 1、寄存器地址映射 */
    MPU_AHB4_PERIPH_RCC_PI = ioremap(RCC_MP_AHB4ENSETR, 4);
    GPIOI_MODER_PI = ioremap(GPIOI_MODER, 4);
    GPIOI_OTYPER_PI = ioremap(GPIOI_OTYPER, 4);
    GPIOI_OSPEEDR_PI = ioremap(GPIOI_OSPEEDR, 4);
    GPIOI_PUPDR_PI = ioremap(GPIOI_PUPDR, 4);
    GPIOI_BSRR_PI = ioremap(GPIOI_BSRR, 4);

    /* 2、使能PI时钟 */
    val = readl(MPU_AHB4_PERIPH_RCC_PI);
    val &= ~(0X1 << 8); /* 清除以前的设置 */
    val |= (0X1 << 8);  /* 设置新值 */
    writel(val, MPU_AHB4_PERIPH_RCC_PI);

    /* 3、设置PI3通用的输出模式。*/
    val = readl(GPIOI_MODER_PI);
    val &= ~(0X3 << 3); /* bit0:1清零 */
    val |= (0X1 << 3);  /* bit0:1设置01 */
    writel(val, GPIOI_MODER_PI);

    /* 3、设置PI3为推挽模式。*/
    val = readl(GPIOI_OTYPER_PI);
    val &= ~(0X1 << 3); /* bit0清零,设置为上拉*/
    writel(val, GPIOI_OTYPER_PI);

    /* 4、设置PI3为高速。*/
    val = readl(GPIOI_OSPEEDR_PI);
    val &= ~(0X3 << 3); /* bit0:1 清零 */
    val |= (0x2 << 3); /* bit0:1 设置为10*/
    writel(val, GPIOI_OSPEEDR_PI);

    /* 5、设置PI3为上拉。*/
    val = readl(GPIOI_PUPDR_PI);
    val &= ~(0X3 << 3); /* bit0:1 清零*/
    val |= (0x1 << 3); /*bit0:1 设置为01*/
    writel(val,GPIOI_PUPDR_PI);

    /* 6、默认关闭LED */
    val = readl(GPIOI_BSRR_PI);
    val |= (0x1 << 3);
    writel(val, GPIOI_BSRR_PI);


	/* 注册字符设备驱动 */
	/* 1、创建设备号 */
	if (newchrled.major) {		/*  定义了设备号 */
		newchrled.devid = MKDEV(newchrled.major, 0);
		ret = register_chrdev_region(newchrled.devid, NEWCHRLED_CNT, NEWCHRLED_NAME);
		if(ret < 0) {
			pr_err("cannot register %s char driver [ret=%d]\n",NEWCHRLED_NAME, NEWCHRLED_CNT);
			goto fail_map;
		}
	} else {						/* 没有定义设备号 */
		ret = alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_CNT, NEWCHRLED_NAME);	/* 申请设备号 */
		if(ret < 0) {
			pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", NEWCHRLED_NAME, ret);
			goto fail_map;
		}
		newchrled.major = MAJOR(newchrled.devid);	/* 获取分配号的主设备号 */
		newchrled.minor = MINOR(newchrled.devid);	/* 获取分配号的次设备号 */
	}
	printk("newcheled major=%d,minor=%d\r\n",newchrled.major, newchrled.minor);	
	
	/* 2、初始化cdev */
	newchrled.cdev.owner = THIS_MODULE;
	cdev_init(&newchrled.cdev, &newchrled_fops);
	
	/* 3、添加一个cdev */
	ret = cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_CNT);
	if(ret < 0)
		goto del_unregister;
		
	/* 4、创建类 */
	newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);
	if (IS_ERR(newchrled.class)) {
		goto del_cdev;
	}

	/* 5、创建设备 */
	newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME);
	if (IS_ERR(newchrled.device)) {
		goto destroy_class;
	}
	
	return 0;

destroy_class:
	class_destroy(newchrled.class);
del_cdev:
	cdev_del(&newchrled.cdev);
del_unregister:
	unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT);
fail_map:
	led_unmap();
	return -EIO;

}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit led_exit(void)
{
	/* 取消映射 */
   led_unmap();
   
	/* 注销字符设备驱动 */
	cdev_del(&newchrled.cdev);/*  删除cdev */
	unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT); /* 注销设备号 */

	device_destroy(newchrled.class, newchrled.devid);
	class_destroy(newchrled.class);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALIENTEK");
MODULE_INFO(intree, "Y");

然后就可以去编译了

应用程序开发

所谓应用程序 就是调用驱动就行各种任务 这里是Linux C应用开发

当然 如果你用Python啥的去调用驱动也可以

应用程序可以对/dev/下的驱动进行读写等操作 前提是已经安装了驱动

开发后 使用一条简单的命令即可编译

测试的应用程序采用open等函数进行驱动操作 写好后执行编译

c 复制代码
arm-none-linux-gnueabihf-gcc test_app.c -o test_app

最后进行测试即可

相关推荐
Wangshanjie_9831 分钟前
【STM32】-串口通讯
stm32
许白掰1 小时前
【stm32】HAL库开发——CubeMX配置RTC,单片机工作模式和看门狗
stm32·单片机·嵌入式硬件·学习·实时音视频
即将头秃的程序媛2 小时前
centos 7.9安装tomcat,并实现开机自启
linux·运维·centos
fangeqin2 小时前
ubuntu源码安装python3.13遇到Could not build the ssl module!解决方法
linux·python·ubuntu·openssl
DIY机器人工房2 小时前
0.96寸OLED显示屏 江协科技学习笔记(36个知识点)
笔记·科技·stm32·单片机·嵌入式硬件·学习·江协科技
爱奥尼欧3 小时前
【Linux 系统】基础IO——Linux中对文件的理解
linux·服务器·microsoft
超喜欢下雨天4 小时前
服务器安装 ros2时遇到底层库依赖冲突的问题
linux·运维·服务器·ros2
Natsume17104 小时前
嵌入式开发:GPIO、UART、SPI、I2C 驱动开发详解与实战案例
c语言·驱动开发·stm32·嵌入式硬件·mcu·架构·github
tan77º4 小时前
【Linux网络编程】网络基础
linux·服务器·网络
shaun20014 小时前
华为c编程规范
c语言