Linux第74步_“设备树”下的LED驱动

使用新字符设备驱动的一般模板,以及设备树,驱动LED。

1、添加"stm32mp1_led"节点

打开虚拟机上"VSCode",点击"文件",点击"打开文件夹",点击"zgq",点击"linux",点击"atk-mp1",点击"linux",点击"my_linux",点击"stm32mp157d-atk.dts"。

stm32mp157d-atk.dts文件如下:

/dts-v1/;

#include "stm32mp157.dtsi"

#include "stm32mp15xd.dtsi"

#include "stm32mp15-pinctrl.dtsi"

#include "stm32mp15xxaa-pinctrl.dtsi"

#include "stm32mp157-m4-srm.dtsi"

#include "stm32mp157-m4-srm-pinctrl.dtsi"

#include "stm32mp157d-atk.dtsi"

/ {

model = "STMicroelectronics STM32MP157D eval daughter";

compatible = "st,stm32mp157d-ed1", "st,stm32mp157";

chosen {

stdout-path = "serial0:115200n8";

};

aliases {

serial0 = &uart4;

};

reserved-memory {

gpu_reserved: gpu@f6000000 {

reg = <0xf6000000 0x8000000>;

no-map;

};

optee_memory: optee@fe000000 {

reg = <0xfe000000 0x02000000>;

no-map;

};

};

};

&cpu1{

cpu-supply = <&vddcore>;

};

&gpu {

contiguous-area = <&gpu_reserved>;

status = "okay";

};

&optee {

status = "okay";

};

修改后的stm32mp157d-atk.dts文件如下:

/dts-v1/;

#include "stm32mp157.dtsi"

#include "stm32mp15xd.dtsi"

#include "stm32mp15-pinctrl.dtsi"

#include "stm32mp15xxaa-pinctrl.dtsi"

#include "stm32mp157-m4-srm.dtsi"

#include "stm32mp157-m4-srm-pinctrl.dtsi"

#include "stm32mp157d-atk.dtsi"

/ {

model = "STMicroelectronics STM32MP157D eval daughter";

compatible = "st,stm32mp157d-ed1", "st,stm32mp157";

chosen {

stdout-path = "serial0:115200n8";

};

aliases {

serial0 = &uart4;

};

reserved-memory {

gpu_reserved: gpu@f6000000 {

reg = <0xf6000000 0x8000000>;

no-map;

};

optee_memory: optee@fe000000 {

reg = <0xfe000000 0x02000000>;

no-map;

};

};

stm32mp1_led {

compatible = "atkstm32mp1-led";

status = "okay";

reg = <0X50000A28 0X04 /* RCC_MP_AHB4ENSETR */

0X5000A000 0X04 /* GPIOI_MODER */

0X5000A004 0X04 /* GPIOI_OTYPER */

0X5000A008 0X04 /* GPIOI_OSPEEDR */

0X5000A00C 0X04 /* GPIOI_PUPDR */

0X5000A018 0X04 >; /* GPIOI_BSRR */

};

};

&cpu1{

cpu-supply = <&vddcore>;

};

&gpu {

contiguous-area = <&gpu_reserved>;

status = "okay";

};

&optee {

status = "okay";

};

见下图:

2、编译设备树

1)、在VSCode终端,输入"make dtbs回车",执行编译设备树

2)、输入"ls arch/arm/boot/uImage -l"

查看是否生成了新的"uImage"文件

3)、输入"ls arch/arm/boot/dts/stm32mp157d-atk.dtb -l"

查看是否生成了新的"stm32mp157d-atk.dtb"文件

拷贝输出的文件:

4)、输入"cp arch/arm/boot/uImage /home/zgq/linux/atk-mp1/linux/bootfs/ -f回车",执行文件拷贝,准备烧录到EMMC;

5)、输入"cp arch/arm/boot/dts/stm32mp157d-atk.dtb /home/zgq/linux/atk-mp1/linux/bootfs/ -f回车",执行文件拷贝,准备烧录到EMMC

6)、输入"cp arch/arm/boot/uImage /home/zgq/linux/tftpboot/ -f回车",执行文件拷贝,准备从tftp下载;

7)、输入"cp arch/arm/boot/dts/stm32mp157d-atk.dtb /home/zgq/linux/tftpboot/ -f回车",执行文件拷贝,准备从tftp下载;

8)、输入"ls -l /home/zgq/linux/atk-mp1/linux/bootfs/回车",查看"/home/zgq/linux/atk-mp1/linux/bootfs/"目录下的所有文件和文件夹

9)、输入"ls -l /home/zgq/linux/tftpboot/回车",查看"/home/zgq/linux/tftpboot/"目录下的所有文件和文件夹

输入"chmod 777 /home/zgq/linux/tftpboot/stm32mp157d-atk.dtb回车"

给"stm32mp157d-atk.dtb"文件赋予可执行权限

输入"chmod 777 /home/zgq/linux/tftpboot/uImage回车" ,给"uImage"文件赋予可执行权限

输入"ls /home/zgq/linux/tftpboot/回车",查看"/home/zgq/linux/tftpboot/"目录下的所有文件和文件夹

3、查看"stm32mp1_led"节点

启动开发板,从网络下载程序

输入"root"

输入"cd /proc/device-tree/回车"

切换到"/sys/firmware/devicetree/base"目录

输入"ls"查看"stm32mp1_led"是否存在

输入"cd stm32mp1_led/回车"

输入"ls"查看"/sys/firmware/devicetree/base/stm32mp1_led"目录下的文件和文件夹

输入"cat compatible回车"

输入"cat name回车"

输入"cat status回车"

4、创建MyDtsLED目录

输入"cd /home/zgq/linux/Linux_Drivers/回车"

切换到"/home/zgq/linux/Linux_Drivers/"

输入"ls回车",查看"/home/zgq/linux/Linux_Drivers/"

输入"mkdir MyDtsLED回车",创建"MyDtsLED"目录

输入"ls回车",查看"/home/zgq/linux/Linux_Drivers/"

5、LED.c文件如下:

#include "LED.h"

#include <linux/of_address.h> //使能of_iomap()

struct MyDtsLED_dev strMyDtsLED;

/* 映射后的寄存器虚拟地址指针 */

static void __iomem *MPU_AHB4_PERIPH_RCC_PI;

/*RCC_MP_AHB4ENSETR寄存器*/

static void __iomem *GPIOI_MODER_PI; /*GPIOx_MODER寄存器,x=A to K, Z*/

static void __iomem *GPIOI_OTYPER_PI;/*GPIOx_OTYPER,x=A to K,Z*/

static void __iomem *GPIOI_OSPEEDR_PI;/*GPIOx_OSPEEDR,x=A to K, Z*/

static void __iomem *GPIOI_PUPDR_PI; /*GPIOx_PUPDR,x=A to K, Z*/

static void __iomem *GPIOI_BSRR_PI;/*GPIOx_BSRR,x=A to K, Z*/

int led_ioremap(void);

void led_iounmap(void);

void led_Pin_Init(void);

void led_switch(u8 sta);

/* 寄存器地址映射 */

int led_ioremap(void)

{

int ret;

u32 regdata12;

const char *str;

struct property *proper;

/* 获取设备树中的属性数据 */

/* 1、获取设备节点:stm32mp1_led */

strMyDtsLED.nd = of_find_node_by_path("/stm32mp1_led");

//通过全路径"/stm32mp1_led"来查找stm32mp1_led节点

//path=/stm32mp1_led,"/stm32mp1_led"就是stm32mp1_led这个节点的全路径。

//返回值:返回找到的节点,如果为NULL,表示查找失败。

if(strMyDtsLED.nd == NULL) {

printk("stm32mp1_led node nost find!\r\n");

return -EINVAL;

}

else {

printk("stm32mp1_lcd node find!\r\n");

}

/* 2、获取compatible属性内容 */

proper = of_find_property(strMyDtsLED.nd, "compatible", NULL);

//查找指定的属性compatible

//np=strMyDtsLED.nd,指定设备节点

//name="compatible",指定属性名字。

//lenp=NULL,不指定属性值的字节数

//返回值:返回找到的属性。

if(proper == NULL) {

printk("compatible property find failed\r\n");

}

else {

printk("compatible = %s\r\n", (char*)proper->value);

}

/* 3、获取status属性内容 */

ret = of_property_read_string(strMyDtsLED.nd, "status", &str);

//读取指定属性status的值

//np=strMyDtsLED.nd,指定设备节点

//proname="status",指定属性的名字

//out_string=str,返回读到的属性值

//返回值:0,读取成功,负值,读取失败。

if(ret < 0){

printk("status read failed!\r\n");

}

else {

printk("status = %s\r\n",str);

}

/* 4、获取reg属性内容 */

ret = of_property_read_u32_array(strMyDtsLED.nd, "reg", regdata, 12);

//读取指定设备节点的属性值,保存到regdata\[\]中

//np=strMyDtsLED.nd,指定设备节点

//proname="reg",指定属性的名字

//out_value=regdata\[\],用来返回属性值,数据类型为u32

//sz=12,表示要读取的数组元素数量为12个

//返回值:0,读取成功,负值,读取失败,其中"-EINVAL"表示属性不存在,"-ENODATA"表示没有要读取的数据,-EOVERFLOW 表示属性值列表太小。

if(ret < 0) {

printk("reg property read failed!\r\n");

}

else {

u8 i = 0;

printk("reg data:\r\n");

for(i = 0; i < 12; i++)

printk("%#X ", regdatai);

printk("\r\n");

}

MPU_AHB4_PERIPH_RCC_PI = of_iomap(strMyDtsLED.nd, 0);

//将"reg属性的第(0+1)段地址信息"转换为"虚拟地址"

//np=strMyDtsLED.nd,指定设备节点

//index=0表示读取reg属性的第(0+1)段

//返回值:经过内存映射后的虚拟内存首地址,如果为NULL的话,则表示内存映射失败;

GPIOI_MODER_PI = of_iomap(strMyDtsLED.nd, 1);

//将"reg属性的第(1+1)段地址信息"转换为"虚拟地址"

//np=strMyDtsLED.nd,指定设备节点

//index=1表示读取reg属性的第(1+1)段

//返回值:经过内存映射后的虚拟内存首地址,如果为NULL的话,则表示内存映射失败;

GPIOI_OTYPER_PI = of_iomap(strMyDtsLED.nd, 2);

//将"reg属性的第(2+1)段地址信息"转换为"虚拟地址"

//np=strMyDtsLED.nd,指定设备节点

//index=2表示读取reg属性的第(2+1)段

//返回值:经过内存映射后的虚拟内存首地址,如果为NULL的话,则表示内存映射失败;

GPIOI_OSPEEDR_PI = of_iomap(strMyDtsLED.nd, 3);

//将"reg属性的第(3+1)段地址信息"转换为"虚拟地址"

//np=strMyDtsLED.nd,指定设备节点

//index=3表示读取reg属性的第(3+1)段

//返回值:经过内存映射后的虚拟内存首地址,如果为NULL的话,则表示内存映射失败;

GPIOI_PUPDR_PI = of_iomap(strMyDtsLED.nd, 4);

//将"reg属性的第(4+1)段地址信息"转换为"虚拟地址"

//np=strMyDtsLED.nd,指定设备节点

//index=4表示读取reg属性的第(4+1)段

//返回值:经过内存映射后的虚拟内存首地址,如果为NULL的话,则表示内存映射失败;

GPIOI_BSRR_PI = of_iomap(strMyDtsLED.nd, 5);

//将"reg属性的第(5+1)段地址信息"转换为"虚拟地址"

//np=strMyDtsLED.nd,指定设备节点

//index=5表示读取reg属性的第(5+1)段

//返回值:经过内存映射后的虚拟内存首地址,如果为NULL的话,则表示内存映射失败;

return 0;

}

/*取消"寄存器地址映射"*/

void led_iounmap(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);

}

void led_Pin_Init(void)

{

u32 val = 0;

/* 2、使能RCC时钟 */

val = readl(MPU_AHB4_PERIPH_RCC_PI);/* 读RCC_MP_AHB4ENSETR寄存器 */

val &= ~(0X1 << 8);/* 清除以前的bit8设置 */

val |= (0X1 << 8); /* 设置新的bit8值 */

writel(val, MPU_AHB4_PERIPH_RCC_PI);

/* 将val的值写入RCC_MP_AHB4ENSETR寄存器 */

/* 3、将PI0输出引脚。*/

val = readl(GPIOI_MODER_PI);/*读GPIOI_MODER寄存器*/

val &= ~(0X3 << 0); /* bit0:1清零 */

val |= (0X1 << 0); /* bit0:1设置01,配置为输出模式 */

writel(val, GPIOI_MODER_PI);

/* 将val的值写入GPIOI_MODER寄存器 */

/* 4、设置PI0为推挽模式 */

val = readl(GPIOI_OTYPER_PI);/*读GPIOI_OTYPER寄存器*/

val &= ~(0X1 << 0); /* bit0清零,设置为上拉*/

writel(val, GPIOI_OTYPER_PI);

/* 将val的值写入GPIOI_OTYPER寄存器 */

/* 5、设置PI0为极高速 */

val = readl(GPIOI_OSPEEDR_PI);/*读GPIOI_OSPEEDR寄存器*/

val &= ~(0X3 << 0); /* bit0:1 清零 */

val |= (0x3 << 0); /* bit0:1 设置为11,极高速*/

writel(val, GPIOI_OSPEEDR_PI);

/* 将val的值写入GPIOI_OSPEEDR寄存器 */

/* 6、设置PI0为上拉。*/

val = readl(GPIOI_PUPDR_PI);/*读GPIOI_PUPDR寄存器*/

val &= ~(0X3 << 0); /* bit0:1 清零*/

val |= (0x1 << 0); /*bit0:1 设置为01,配置为上拉*/

writel(val,GPIOI_PUPDR_PI);

/* 将val的值写入GPIOI_PUPDR寄存器 */

/* 6、默认打开LED,PI0=0 */

val = readl(GPIOI_BSRR_PI);/*读GPIOI_BSRR寄存器*/

val &= ~(0X1 << 16); /* bit16 清零*/

val |= (0x1 << 16); /*bit16 设置为1,令PI0输出低电平*/

writel(val, GPIOI_BSRR_PI);

/* 将val的值写入GPIOI_BSRR寄存器 */

/* 6、默认关闭LED,PI0=1 */

val = readl(GPIOI_BSRR_PI);/*读GPIOI_BSRR寄存器*/

val &= ~(0X1 << 0); /* bit0 清零*/

val |= (0x1 << 0);/*bit0 设置为1,令PI0输出高电平*/

writel(val, GPIOI_BSRR_PI);

/* 将val的值写入GPIOI_BSRR寄存器 */

}

void led_switch(u8 sta)

{

u32 val = 0;

if(sta == LEDON) {

val = readl(GPIOI_BSRR_PI);/*读GPIOI_BSRR寄存器*/

val &= ~(0X1 << 16); /* bit16 清零*/

val |= (0x1 << 16); /*bit16 设置为1,令PI0输出低电平*/

writel(val, GPIOI_BSRR_PI);

/* 将val的值写入GPIOI_BSRR寄存器 */

}

else if(sta == LEDOFF) {

val = readl(GPIOI_BSRR_PI);/*读GPIOI_BSRR寄存器*/

val &= ~(0X1 << 0); /* bit0 清零*/

val |= (0x1 << 0);/*bit0 设置为1,令PI0输出高电平*/

writel(val, GPIOI_BSRR_PI);

/* 将val的值写入GPIOI_BSRR寄存器 */

}

}

6、LED.h程序如下:

#ifndef __LED_H

#define __LED_H

#include <linux/types.h>

/*

数据类型重命名

使能bool,u8,u16,u32,u64, uint8_t, uint16_t, uint32_t, uint64_t

使能s8,s16,s32,s64,int8_t,int16_t,int32_t,int64_t

*/

#include <linux/cdev.h> //使能cdev结构

#include <linux/device.h>//使能class结构和device结构

#include <linux/of.h> //使能device_node结构

#define LEDOFF 0 /* 关灯 */

#define LEDON 1 /* 开灯 */

struct MyDtsLED_dev{

dev_t devid; /*声明32位变量devid用来给保存设备号 */

int major; /* 主设备号 */

int minor; /* 次设备号 */

struct cdev cdev; /*字符设备结构变量cdev */

struct class *class; /* 类 */

struct device *device;/*设备*/

struct device_node *nd;/* 设备节点 */

};

extern struct MyDtsLED_dev strMyDtsLED;

extern int led_ioremap(void);

extern void led_iounmap(void);

extern void led_Pin_Init(void);

extern void led_switch(u8 sta);

#endif

7、LEDInterface.c程序如下:

#include "LED.h"

#include <linux/types.h>

//数据类型重命名

//使能bool,u8,u16,u32,u64, uint8_t, uint16_t, uint32_t, uint64_t

//使能s8,s16,s32,s64,int8_t,int16_t,int32_t,int64_t

#include <linux/kernel.h>

#include <linux/delay.h>

#include <linux/ide.h>

#include <linux/init.h>

#include <linux/module.h>

#include <linux/string.h>

#include <linux/cdev.h> //使用字符设备结构

#include <linux/mdev.h>

#define MyDtsLED_CNT 1 //定义设备数量为1

#define MyDtsLED_NAME "MyDtsLEDName"//定义设备的名字

/* 打开设备 */

static int MyDtsLED_open(struct inode *inode, struct file *filp)

{

filp->private_data = &strMyDtsLED; /* 设置私有数据 */

printk("MyDtsLED_open!\r\n");

return 0;

}

/* 从设备读取数据,保存到首地址为buf的数据块中,长度为cnt个字节 */

//file结构指针变量flip表示要打开的设备文件

//buf表示用户数据块的首地址

//cnt表示用户数据的长度,单位为字节

//loff_t结构指针变量offt表示"相对于文件首地址的偏移"

static ssize_t MyDtsLED_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)

{

return 0;

}

/* 向设备写数据,将数据块首地址为buf的数据,长度为cnt个字节,发送给用户 */

//file结构指针变量flip表示要打开的设备文件

//buf表示用户数据块的首地址

//cnt表示用户数据的长度,单位为字节

//loff_t结构指针变量offt表示"相对于文件首地址的偏移"

static ssize_t MyDtsLED_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)

{

int ret = 0;

unsigned char databuf1;

unsigned char ledstat;

ret = copy_from_user(databuf, buf, cnt);

if(ret <0){

printk("kernel write failed!\r\n");

ret = -EFAULT;

}

ledstat = databuf0;/*获取到应用传递进来的开关灯状态*/

led_switch(ledstat);/*执行开灯或执行关灯*/

return ret;

}

/* 关闭/释放设备 */

static int MyDtsLED_release(struct inode *inode, struct file *filp)

{

/* 用户实现具体功能 */

printk("MyDtsLED_release!\r\n");

return 0;

}

/*声明file_operations结构变量MyCharDevice_fops*/

/*它是指向设备的操作函数集合变量*/

const struct file_operations MyDtsLED_fops = {

.owner = THIS_MODULE,

.open = MyDtsLED_open,

.read = MyDtsLED_read,

.write = MyDtsLED_write,

.release = MyDtsLED_release,

};

/*驱动入口函数 */

static int __init MyDtsLED_init(void)

{

int ret;

/* 1、寄存器地址映射 */

led_ioremap();//寄存器地址映射

led_Pin_Init();//LED灯引脚初始化

/*2、申请设备号*/

strMyDtsLED.major=0;

if(strMyDtsLED.major)/*如果指定了主设备号*/

{

strMyDtsLED.devid = MKDEV(strMyDtsLED.major, 0);

//输入参数strMyDtsLED.major为"主设备号"

//输入参数0为"次设备号",大部分驱动次设备号都选择0

//将strMyDtsLED.major左移20位,再与0相或,就得到"Linux设备号"

ret=register_chrdev_region(strMyDtsLED.devid, MyDtsLED_CNT, MyDtsLED_NAME);

//strMyDtsLED.devid表示起始设备号

//MyDtsLED_CNT表示次设备号的数量

//MyDtsLED_NAME表示设备名

if(ret < 0)

goto fail_map;

}

else

{ /* 没有定义设备号 */

ret=alloc_chrdev_region(&strMyDtsLED.devid, 0, MyDtsLED_CNT,MyDtsLED_NAME);

/* 申请设备号 */

//strMyDtsLED.devid:保存申请到的设备号

//0:次设备号的起始地址

//MyDtsLED_CNT:要申请的次设备号数量;

//MyDtsLED_NAME:表示"设备名字"

if(ret < 0)

goto fail_map;

strMyDtsLED.major = MAJOR(strMyDtsLED.devid);

/* 获取分配号的主设备号 */

//输入参数strMyDtsLED.devid为"Linux设备号"

//将strMyDtsLED.devid右移20位得到"主设备号"

strMyDtsLED.minor = MINOR(strMyDtsLED.devid);

/* 获取分配号的次设备号 */

//输入参数strMyDtsLED.devid为"Linux设备号"

//将strMyDtsLED.devid与0xFFFFF相与后得到"次设备号"

}

/*3、注册字符设备*/

strMyDtsLED.cdev.owner = THIS_MODULE;

//使用THIS_MODULE将owner指针指向当前这个模块

cdev_init(&strMyDtsLED.cdev,&MyDtsLED_fops);

//注册字符设备,初始化"字符设备结构变量strMyDtsLED.cdev"

//strMyDtsLED.cdev是等待初始化的结构体变量

//MyDtsLED_fops就是字符设备文件操作函数集合

/*4、添加字符设备*/

ret=cdev_add(&strMyDtsLED.cdev,strMyDtsLED.devid,MyDtsLED_CNT);

//添加字符设备

/*&strMyDtsLED.cdev表示指向要添加的字符设备,即字符设备结构strMyDtsLED.cdev变量*/

//strMyDtsLED.devid表示设备号

//MyDtsLED_CNT表示需要添加的设备数量

if(ret < 0 ) //添加字符设备失败

goto del_register;

printk("dev id major = %d,minor = %d\r\n", strMyDtsLED.major, strMyDtsLED.minor);

printk("MyDtsLED_init is ok!!!\r\n");

/*5、自动创建设备节点 */

strMyDtsLED.class =class_create(THIS_MODULE, MyDtsLED_NAME);

if (IS_ERR(strMyDtsLED.class)){

goto del_cdev;

}

/*6、创建设备 */

strMyDtsLED.device = device_create(strMyDtsLED.class, NULL, strMyDtsLED.devid, NULL, MyDtsLED_NAME);

//创建设备

//设备要创建在strMyDtsLED.class类下面

//NULL表示没有父设备

//strMyDtsLED.devid是设备号;

//参数drvdata=NULL,设备没有使用数据

//MyDtsLED_NAME是设备名字

//如果设置fmt=MyDtsLED_NAME 的话,就会生成/dev/MyDtsLED_NAME设备文件。

//返回值就是创建好的设备。

if (IS_ERR(strMyDtsLED.device)){

goto destroy_class;

}

return 0;

destroy_class:

class_destroy(strMyDtsLED.class);

//删除类

//strMyDtsLED.class就是要删除的类

del_cdev:

cdev_del(&strMyDtsLED.cdev);

//删除字符设备

//&strMyDtsLED.cdev表示指向需要删除的字符设备,即字符设备结构strMyDtsLED.cdev变量

del_register:

unregister_chrdev_region(strMyDtsLED.devid, MyDtsLED_CNT);

/* 释放设备号 */

//strMyDtsLED.devid:需要释放的起始设备号

//MyDtsLED_CNT:需要释放的次设备号数量;

fail_map://申请设备号失败

/*若有释放的内存,则释放内存*/

led_iounmap();

return -EIO;

}

/*驱动出口函数 */

static void __exit MyDtsLED_exit(void)

{

/*1、释放内存*/

led_iounmap();

/*2、 释放设备号 */

unregister_chrdev_region(strMyDtsLED.devid, MyDtsLED_CNT);

/*释放设备号 */

//strMyDtsLED.devid:需要释放的起始设备号

//MyDtsLED_CNT:需要释放的次设备号数;

/*3、删除字符设备*/

cdev_del(&strMyDtsLED.cdev);

/*删除字符设备*/

/*&strMyDtsLED.cdev表示指向需要删除的字符设备,即字符设备结构strMyDtsLED.cdev变量*/

/*4、 删除设备 */

device_destroy(strMyDtsLED.class, strMyDtsLED.devid);

//删除创建的设备

//newchrled.class是要删除的设备所处的类

//newchrled.devid是要删除的设备号

/*5、删除类*/

class_destroy(strMyDtsLED.class);

//删除类

//strMyDtsLED.class就是要删除的类

}

module_init(MyDtsLED_init);

//指定MyDtsLED_init()为驱动入口函数

module_exit(MyDtsLED_exit);

//指定MyDtsLED_exit()为驱动出口函数

MODULE_AUTHOR("Zhanggong");//添加作者名字

MODULE_LICENSE("GPL");//LICENSE采用"GPL协议"

MODULE_INFO(intree,"Y");

//去除显示"loading out-of-tree module taints kernel."

8、LED_APP.c文件如下:

#include "stdio.h"

#include "unistd.h"

#include "sys/types.h"

#include "sys/stat.h"

#include "fcntl.h"

#include "stdlib.h"

#include "string.h"

//APP运行命令:./LED_APP filename <1>|<0>如果是1表示打开LED,如果是0表示关闭LED

#define LEDOFF 0 /* 关灯 */

#define LEDON 1 /* 开灯 */

/*

参数argc: argv\[\]数组元素个数

参数argv\[\]:是一个指针数组

返回值: 0 成功;其他 失败

*/

int main(int argc, char *argv\[\])

{

int fd, retvalue;

char *filename;

unsigned char databuf1;

if(argc != 3)

{

printf("Error Usage!\r\n");

return -1;

}

//argv\[\]是指向输入参数"./LED_App" "/dev/LMyNewLEDName" "1"

filename = argv1;

//argv1指向字符串"/dev/MyNewLEDName"

fd = open(filename, O_RDWR);

//如果打开"/dev/MyNewLEDName"文件成功,则fd为"文件描述符"

//fd=0表示关灯; fd=1表示开灯;

if(fd < 0)

{

printf("Can't open file %s\r\n", filename);

return -1;

}

databuf0= atoi(argv2); /* 写入的数据,是数字的,表示打开或关闭 */

retvalue = write(fd, databuf, 1);

//将databuf\[\]中前1个字节发送给用户

//返回值大于0表示写入的字节数;

//返回值等于0表示没有写入任何数据;

//返回值小于0表示写入失败

if(retvalue < 0)

{

printf("write file %s failed!\r\n", filename);

close(fd);

//fd表示要关闭的"文件描述符"

//返回值等于0表示关闭成功

//返回值小于0表示关闭失败

return -1;

}

/* 关闭设备 */

retvalue = close(fd);

//fd表示要关闭的"文件描述符"

//返回值等于0表示关闭成功

//返回值小于0表示关闭失败

if(retvalue < 0)

{

printf("Can't close file %s\r\n", filename);

return -1;

}

return 0;

}

9、Makefile文件如下:

KERNELDIR := /home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31

#使用":="将其后面的字符串赋值给KERNELDIR

CURRENT_PATH := $(shell pwd)

#采用"shell pwd"获取当前打开的路径

#使用"$(变量名)"引用"变量的值"

MyAPP := LED_APP

MyDtsLED_Module-objs = LEDInterface.o LED.o

obj-m := MyDtsLED_Module.o

CC := arm-none-linux-gnueabihf-gcc

drv:

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

app:

(CC) (MyAPP).c -o $(MyAPP)

clean:

(MAKE) -C (KERNELDIR) M=$(CURRENT_PATH) clean

rm $(MyAPP)

install:

sudo cp *.ko $(MyAPP) /home/zgq/linux/nfs/rootfs/lib/modules/5.4.31/ -f

10、添加"c_cpp_properties.json"

按下"Ctrl+Shift+P",打开VSCode控制台,然后输入"C/C++:Edit Configurations(JSON)",打开以后会自动在".vscode "目录下生成一个名为"c_cpp_properties.json" 的文件。

修改c_cpp_properties.json内容如下所示:

{

"configurations": [

{

"name": "Linux",

"includePath": [

"${workspaceFolder}/**",

"/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31",

"/home/zgq/linux/Linux_Drivers/MyDtsLED",

"/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/arch/arm/include",

"/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/include",

"/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/arch/arm/include/generated"

],

"defines": \[\],

"compilerPath": "/usr/bin/gcc",

"cStandard": "gnu11",

"cppStandard": "gnu++14",

"intelliSenseMode": "gcc-x64"

}

],

"version": 4

}

11、编译

输入"make clean回车"

输入"make drv回车"

输入"make app回车"

输入"make install回车"

输入"ls /home/zgq/linux/nfs/rootfs/lib/modules/5.4.31/ -l回车"产看是存在"LED_APP和MyDtsLED_Module.ko"

12、测试

启动开发板,从网络下载程序

输入"root"

输入"cd /lib/modules/5.4.31/回车"

切换到"/lib/modules/5.4.31/"目录

注意:"lib/modules/5.4.31/"在虚拟机中是位于"/home/zgq/linux/nfs/rootfs/"目录下,但在开发板中,却是位于根目录中。

输入"ls -l"查看"MyDtsLED_Module.ko和LED_APP"是否存在

输入"depmod",驱动在第一次执行时,需要运行"depmod"

输入"modprobe MyDtsLED_Module.ko",加载"MyDtsLED_Module.ko"模块

输入"lsmod"查看有哪些驱动在工作

输入"ls /dev/MyDtsLEDName -l回车",发现节点文件"/dev/MyDtsLEDName"

输入"./LED_APP /dev/MyDtsLEDName 1回车"执行开灯

输入"./LED_APP /dev/MyDtsLEDName 0回车"执行关灯

输入"rmmod MyDtsLED_Module.ko",卸载"MyDtsLED_Module.ko"模块

注意:输入"rmmod MyDtsLED_Module"也可以卸载"MyDtsLED_Module.ko"模块

输入"lsmod"查看有哪些驱动在工作。

输入"ls /dev/MyDtsLEDName -l回车",查询节点文件"/dev/MyDtsLEDName"是否存在

相关推荐
顺风尿一寸12 小时前
Java Socket 内核之旅:从 SocketChannel.read() 到 tcp_recvmsg 与 epoll 的完整调用链路
linux
XIAOHEZIcode18 小时前
Ubuntu 终端美化全栈指南:Bash 到 Kitty 踩坑实录
linux·ubuntu·命令行
唐青枫20 小时前
别再只会用 cron:Linux systemd Timer 定时任务实战详解
linux
努力的小雨1 天前
我用 QClaw 做了个 Web3 陪学助手,专治 Java 程序员的“概念劝退”
经验分享·ai智能
AlfredZhao3 天前
生产环境里,为什么不建议把普通端口直接暴露到公网?
linux·https·443·80
戴为沐4 天前
Linux内存扩容指南
linux
zylyehuo4 天前
Linux 彻底且安全地删除文件
linux
用户805533698035 天前
主线 U-Boot 上 RK3506:和闭源 rkbin 拔河的三个隐性契约
linux·嵌入式
用户034095297915 天前
linux fcitx 5 雾凇拼音 设置在中文输入法下仍然输入英文标点
linux
Web3探索者7 天前
可视化服务器管理和传统命令行区别是什么?新手教程:Linux 运维到底该用图形界面还是 SSH 命令行?
linux·ssh