基于正点原子IMX6UL开发板的笔记
40 字符设备驱动开发
c_cpp_properties.json
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**",
"/home/yinliang/linux/kernel/nxp_linux/include",
"/home/yinliang/linux/kernel/nxp_linux/arch/arm/include",
"/home/yinliang/linux/kernel/nxp_linux/arch/arm/include/generated"
],
"defines": [],
"compilerPath": "/usr/bin/gcc",
"cStandard": "c11",
"cppStandard": "c++17",
"intelliSenseMode": "clang-x64"
}
],
"version": 4
}
chrdevbase.c
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
/***************************************************************
文件名 : chrdevbase.c
作者 : junluoyu
版本 : V1.0
描述 : chrdevbase驱动文件。
其他 : 无
日志 : 初版 V1.0 2022/11/28
***************************************************************/
#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 - buf :返回给用户空间的数据缓冲区
* @param - filp :要打开的设备文件(文件描述符)
* @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 - buf :要写给设备写入的数据
* @param - filp :设备文件,表示打开的文件描述符
* @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 recvdata: %s\r\n", writebuf);
}else{
printk("kernel recvdata 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 - filp :无
* @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 - filp :无
* @return :无
*/
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("junluoyu");
chrdevbaseApp.c
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include <string.h>
/***************************************************************
文件名 : chrdevbaseApp.c
作者 : junluoyu
版本 : V1.0
描述 : chrdevbase驱动测试APP
其他 : 无
日志 : 初版 V1.0 2022/11/28
其他 : ./chrdevbaseApp /dev/chrdevbase 1(读文件)|2(写文件)
***************************************************************/
static char usrdata[] = {"usr data!"};
/*
* @description : main主程序
* @param -argc :argv数组元素长度
* @param -argv :具体参数
*/
int main(int argc, char *argv[])
{
int fd, retvalue;
char *filename;
char readbuf[100], writebuf[100];
if (argc != 3) {
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
/* 打开驱动文件 */
fd = open(filename, O_RDWR);
if (fd < 0) {
printf("Can't open file %s \r\n", filename);
return -1;
}
if (atoi(argv[2]) == 1) { /* 从驱动文件读取数据 */
retvalue = read(fd, readbuf, 50);
if (retvalue < 0) {
printf("read file %s failed!\r\n", filename);
} else {
/* 读取成功,打印出读取成功的数据*/
printf("read data:%s \r\n", readbuf);
}
}
if (atoi(argv[2]) == 2) { /* 向驱动文件写数据 */
memcpy(writebuf, usrdata, sizeof(usrdata));
retvalue = write(fd, writebuf, 50);
if (retvalue < 0) {
printf("write file %s failed!\r\n", filename);
}
}
/* 关闭设备 */
retvalue = close(fd);
if (retvalue < 0) {
printf("Can't close file %s failed!\r\n", filename);
return -1;
}
return 0;
}
Makefile
KERNELDIR := /home/yinliang/linux/kernel/nxp_linux
CURERENT_PATH := $(shell pwd)
obj-m := chrdevbase.o
build: kernel_moduls
kernel_moduls:
$(MAKE) -C $(KERNELDIR) M=$(CURERENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURERENT_PATH) clean
arm-linux-gnueabihf-gcc chrdevbaseApp.c -o chrdevbaseApp
file chrdevbaseApp
复制chrdevbase.ko chrdevbaseApp 到 lib/modules/4.1.15/
sudo cp chrdevbase.ko chrdevbaseApp /home/yinliang/linux/nfs/rootfs/lib/modules/4.1.15/ -f
启动板卡测试
cd /lib/modules/4.1.15
1.安装驱动模块
insmod chrdevbase.ko
modprobe chrdevbase.ko #会安装依赖文件
2.挂载
cat /proc/devices #看有没有设备
mknod /dev/chrdevbase c 200 0 #mknod创建节点命令,/dev/chrdevbase是创建的节点文件,c表示是字符设备,200是主设备号,0表示设备的次设备号
ls /dev/chrdevbase -l
3.测试
chmod 777 /dev/chrdevbase
./chrdevbaseApp /dev/chedevbase 1
./chrdevbaseApp /dev/chedevbase 2
4.卸载驱动模块
rmmod chrdevbase.ko
modprobe -r chrdevbase.ko #会卸载依赖文件
41 Linux LED驱动开发实验
ioremap iounmap 物理地址映射到虚拟地址
IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE ,4);
// 物理地址映射到虚拟地址 4代表4个字节
iounmap(IMX6U_CCM_CCGR1 );
// 取消映射关系
led.c
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
/***************************************************************
文件名 : led.c
作者 : junluoyu
版本 : V1.0
描述 : led驱动文件。
其他 : 无
日志 : 初版 V1.0 2022/11/20
***************************************************************/
/* 寄存器物理地址 */
#define CCM_CCGR1_BASE (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define GPIO1_DR_BASE (0X0209C000)
#define GPIO1_GDIR_BASE (0X0209C004)
/* 映射后的寄存器虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;
#define LED_MAJOR 200 /* 主设备号 */
#define LED_NAME "led" /* 设备名 */
#define LEDOFF 0 //关灯
#define LEDON 1 //开灯
/*
* @description : led打开/关闭
* @param - sta :LED0N 0 打开LED LEDOFF 1 关闭LED
* @return :无
*/
void led_switch(u8 sta)
{
u32 val = 0;
if(sta == LEDON) {
val = readl(GPIO1_DR);
val &= ~(1<<3);
writel(val,GPIO1_DR);
}
else if (sta == LEDOFF) {
val = readl(GPIO1_DR);
val |= (1<<3);
writel(val,GPIO1_DR);
}
}
/*
* @description : 打开设备
* @param - inode :传递给驱动的inode
* @param - filp :设备文件,file结构体有给叫做private_data的成员变量
* ,一般再open的时候将private_data指向设备的结构体
* @return :0成功;其他失败
*/
static int led_open(struct inode *inode, struct file *filp)
{
return 0;
}
/*
* @description : 从设备读取数据
* @param - buf :返回给用户空间的数据缓冲区
* @param - filp :要打开的设备文件(文件描述符)
* @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 - buf :要写给设备写入的数据
* @param - filp :设备文件,表示打开的文件描述符
* @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 = 0;
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)
{
// printk("led release! \r\n");
return 0;
}
/*
* 设备操作结构体
*/
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};
/*
* @description : 驱动入口函数
* @param - filp :无
* @return :0成功; 其他 失败
*/
static int __init led_init(void)
{
int retvalue = 0;
u32 val = 0;
/* 初始化LED */
/* 1.寄存器地址映射 */
IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE ,4);
SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE ,4);
SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE ,4);
GPIO1_DR = ioremap(GPIO1_DR_BASE ,4);
GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE ,4);
/* 2.使能GPIO1时钟 */
val = readl(IMX6U_CCM_CCGR1);
val &= ~(3<<26); /* 清除以前的设置 */
val |= (3<<26); /* 设置新值 */
writel(val, IMX6U_CCM_CCGR1);
/* 3.设置GPIO1_IO03的复用功能,将其复用为GPIO01_IO03,最后设置IO属性 */
writel(5, SW_MUX_GPIO1_IO03); //mux复用功能为5
writel(0X10B0, SW_PAD_GPIO1_IO03); //PAD设置IO属性
/* 4.设置GPIO1_IO03为输出功能*/
val = readl(GPIO1_GDIR);
val &= ~(1<<3); /* 清除以前的设置 */
val |= (1<<3); /* 设置为输出 */
writel(val,GPIO1_GDIR);
/* 5.默认关闭LED*/
val = readl(GPIO1_DR);
val |= (1<<3); //默认为关,1电平
writel(val, GPIO1_DR);
/* 6.注册字符设备驱动 */
retvalue = register_chrdev(LED_MAJOR, LED_NAME,
&led_fops);
if (retvalue < 0)
{
printk("register led failed\r\n");
return -EIO;
}
return 0;
}
/*
* @description : 驱动出口函数
* @param - filp :无
* @return :无
*/
void __exit led_exit(void)
{
/* 取消映射 */
iounmap(IMX6U_CCM_CCGR1 );
iounmap(SW_PAD_GPIO1_IO03 );
iounmap(SW_MUX_GPIO1_IO03 );
iounmap(GPIO1_DR );
iounmap(GPIO1_GDIR );
/* 注销字符设备驱动 */
unregister_chrdev(LED_MAJOR,LED_NAME);
}
/*
* 将上面两个函数指定为驱动的入口和出口函数
*/
module_init(led_init);
module_exit(led_exit);
/*
* LICENSE和作者信息
*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("junluoyu");
ledApp.c
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
/***************************************************************
文件名 : ledApp.c
作者 : junluoyu
版本 : V1.0
描述 : led驱动测试APP
其他 : 无
日志 : 初版 V1.0 2022/11/28
其他 : ./ledApp /dev/led 1(打开LED)|0(关闭LED)
***************************************************************/
#define LEDOFF 0 //关灯
#define LEDON 1 //开灯
/*
* @description : main主程序
* @param -argc :argv数组元素长度
* @param -argv :具体参数
*/
int main(int argc, char *argv[])
{
int fd, retvalue;
char *filename;
unsigned char databuf[1];
if (argc != 3) {
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
/* 打开驱动文件 */
fd = open(filename, O_RDWR);
if (fd < 0) {
printf("Can't open file %s \r\n", filename);
return -1;
}
databuf[0] = atoi(argv[2]);
/* 向/dev/led文件写入数据 */
retvalue = write(fd, databuf,sizeof(databuf));
if (retvalue < 0) {
printf("led control failed!\r\n");
close(fd);
return -1;
}
/* 关闭led设备 */
retvalue = close(fd);
if (retvalue < 0) {
printf("file %s close failed!\r\n", argv[1]);
return -1;
}
return 0;
}
Makefile
KERNELDIR := /home/yinliang/linux/kernel/nxp_linux
CURERENT_PATH := $(shell pwd)
obj-m := led.o
build: kernel_moduls
kernel_moduls:
$(MAKE) -C $(KERNELDIR) M=$(CURERENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURERENT_PATH) clean
make -j16
arm-linux-gnueabihf-gcc led.c -o ledApp
file chrdevbaseApp
复制led.ko ledApp 到 lib/modules/4.1.15/
sudo cp led.ko ledApp /home/yinliang/linux/nfs/rootfs/lib/modules/4.1.15/ -f
启动板卡测试
cd /lib/modules/4.1.15
1.安装驱动模块 第一次加载先使用depmod
insmod led.ko
modprobe led.ko #会安装依赖文件
2.挂载
cat /proc/devices #看有没有设备
mknod /dev/led c 200 0 #mknod创建节点命令,/dev/led是创建的节点文件,c表示是字符设备,200是主设备号,0表示设备的次设备号
ls /dev/led -l
3.测试
chmod 777 /dev/led
./ledApp /dev/led 1 //开灯
./ledApp /dev/led 2 //关灯
4.卸载驱动模块
rmmod ledbase.ko
modprobe -r ledbase.ko #会卸载依赖文件
42 新字符设备驱动开发实验
不再需要自己去挂载设备,自动生成设备号并且挂载上去。
newchrled.c
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
/***************************************************************
文件名 : led.c
作者 : junluoyu
版本 : V1.0
描述 : led驱动文件 新版
其他 : 无
日志 : 初版 V1.0 2022/11/20
***************************************************************/
#define NEWCHRLED_CNT 1 /*设备号个数*/
#define NEWCHRLED_NAME "newchrled" /* 名字 */
/* 寄存器物理地址 */
#define CCM_CCGR1_BASE (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define GPIO1_DR_BASE (0X0209C000)
#define GPIO1_GDIR_BASE (0X0209C004)
/* 映射后的寄存器虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;
#define LEDOFF 0 //关灯
#define LEDON 1 //开灯
/*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 :LED0N 0 打开LED LEDOFF 1 关闭LED
* @return :无
*/
void led_switch(u8 sta)
{
u32 val = 0;
if(sta == LEDON) {
val = readl(GPIO1_DR);
val &= ~(1<<3);
writel(val,GPIO1_DR);
}
else if (sta == LEDOFF) {
val = readl(GPIO1_DR);
val |= (1<<3);
writel(val,GPIO1_DR);
}
}
/*
* @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 - buf :返回给用户空间的数据缓冲区
* @param - filp :要打开的设备文件(文件描述符)
* @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 - buf :要写给设备写入的数据
* @param - filp :设备文件,表示打开的文件描述符
* @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 = 0;
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)
{
// printk("led release! \r\n");
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 - filp :无
* @return :0成功; 其他 失败
*/
static int __init led_init(void)
{
u32 val = 0;
/* 初始化LED */
/* 1.寄存器地址映射 */
IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE ,4);
SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE ,4);
SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE ,4);
GPIO1_DR = ioremap(GPIO1_DR_BASE ,4);
GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE ,4);
/* 2.使能GPIO1时钟 */
val = readl(IMX6U_CCM_CCGR1);
val &= ~(3<<26); /* 清除以前的设置 */
val |= (3<<26); /* 设置新值 */
writel(val, IMX6U_CCM_CCGR1);
/* 3.设置GPIO1_IO03的复用功能,将其复用为GPIO01_IO03,最后设置IO属性 */
writel(5, SW_MUX_GPIO1_IO03); //mux复用功能为5
writel(0X10B0, SW_PAD_GPIO1_IO03); //PAD设置IO属性
/* 4.设置GPIO1_IO03为输出功能*/
val = readl(GPIO1_GDIR);
val &= ~(1<<3); /* 清除以前的设置 */
val |= (1<<3); /* 设置为输出 */
writel(val,GPIO1_GDIR);
/* 5.默认关闭LED*/
val = readl(GPIO1_DR);
val |= (1<<3); //默认为关,1电平
writel(val, GPIO1_DR);
/* 注册字符设备驱动 */
// retvalue = register_chrdev(LED_MAJOR, LED_NAME,
// &led_fops);
/* 1.创建设备号 */
if(newchrled.major) { /* 定义了设备号 */
newchrled.devid = MKDEV(newchrled.major, 0);
register_chrdev_region(newchrled.devid, NEWCHRLED_CNT,NEWCHRLED_NAME);
}
else { /* 没有定义设备号 */
alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_CNT, NEWCHRLED_NAME); /*动态申请设备号*/
newchrled.major = MAJOR(newchrled.devid); /* 获取主设备号 */
newchrled.minor = MINOR(newchrled.devid); /* 获取次设备号*/
}
printk("newchrled 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 */
cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_CNT);
/* 4.创建类 */
newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);
if(IS_ERR(newchrled.class)) {
return PTR_ERR(newchrled.class);
}
/* 5.创建设备 */
newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME);
if(IS_ERR(newchrled.device)) {
return PTR_ERR(newchrled.device);
}
return 0;
}
/*
* @description : 驱动出口函数
* @param - filp :无
* @return :无
*/
void __exit led_exit(void)
{
/* 取消映射 */
iounmap(IMX6U_CCM_CCGR1 );
iounmap(SW_PAD_GPIO1_IO03 );
iounmap(SW_MUX_GPIO1_IO03 );
iounmap(GPIO1_DR );
iounmap(GPIO1_GDIR );
/* 注销字符设备驱动 */
//unregister_chrdev(LED_MAJOR,LED_NAME);
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);
/*
* LICENSE和作者信息
*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("junluoyu");
newchrledApp.c
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
/***************************************************************
文件名 : ledApp.c
作者 : junluoyu
版本 : V1.0
描述 : led驱动测试APP
其他 : 无
日志 : 初版 V1.0 2022/11/28
其他 : ./ledApp /dev/led 1(打开LED)|0(关闭LED)
***************************************************************/
#define LEDOFF 0 //关灯
#define LEDON 1 //开灯
/*
* @description : main主程序
* @param -argc :argv数组元素长度
* @param -argv :具体参数
*/
int main(int argc, char *argv[])
{
int fd, retvalue;
char *filename;
unsigned char databuf[1];
if (argc != 3) {
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
/* 打开驱动文件 */
fd = open(filename, O_RDWR);
if (fd < 0) {
printf("Can't open file %s \r\n", filename);
return -1;
}
databuf[0] = atoi(argv[2]);
/* 向/dev/led文件写入数据 */
retvalue = write(fd, databuf,sizeof(databuf));
if (retvalue < 0) {
printf("led control failed!\r\n");
close(fd);
return -1;
}
/* 关闭led设备 */
retvalue = close(fd);
if (retvalue < 0) {
printf("file %s close failed!\r\n", argv[1]);
return -1;
}
return 0;
}
Makefile
KERNELDIR := /home/yinliang/linux/kernel/nxp_linux
CURERENT_PATH := $(shell pwd)
obj-m := newchrled.o
build: kernel_moduls
kernel_moduls:
$(MAKE) -C $(KERNELDIR) M=$(CURERENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURERENT_PATH) clean
复制newchrled.ko newchrledApp 到 lib/modules/4.1.15/
make -j16
arm-linux-gnueabihf-gcc newchrledApp.c -o newchrledApp
cp newchrled.ko newchrledApp /home/yinliang/linux/nfs/rootfs/lib/modules/4.1.15/
启动板卡测试
cd /lib/modules/4.1.15
1.安装驱动模块 第一次加载先使用depmod
insmod newchrled.ko
modprobe newchrled.ko #会安装依赖文件
2.自动挂载
ls /dev/newchrled -l
3.测试
./newchrledApp /dev/newchrled 1 //开灯
./newchrledApp /dev/newchrled 0 //关灯
4.卸载驱动模块
rmmod newchrled.ko
modprobe -r newchrled.ko #会卸载依赖文件
43 linux设备树
imx6ull-alientek-emmc.dts在 make dtbs后产生 imx6ull-alientek-emmc.dtb
myfirst.dts
//①、I.MX6ULL这个 Cortex-A7架构的 32位 CPU。
//②、I.MX6ULL内部 ocram,起始地址 0x00900000,大小为 128KB(0x20000)。
//③、I.MX6ULL内部 aips1域下的 ecspi1外设控制器,寄存器起始地址为 0x02008000,大小为 0x4000。
//④、I.MX6ULL内部 aips2域下的 usbotg1外设控制器,寄存器起始地址为 0x02184000,大小为 0x4000。
//⑤、I.MX6ULL内部 aips3域下的 rngb外设控制器,寄存器起始地址为 0x02284000,大小为 0x4000。
/ {
compatible = "fsl, imx6ull-alientek-evk", "fsl, imx6ull";
cpus {
#address-cells = <1>;
#size-cells = <0>;
//cpu0节点
cpu0: cpu@0 {
compatible = "arm, cortex-a7";
device_type = "cpu";
reg = <0>;
};
};
//soc节点
soc {
#address-cells = <1>;
#size-cells = <1>;
compatible = "simple-bus";
ranges; //ranges属性为空,表示子空间和父空间地址范围相同
//ocram节点
ocram:sram@00900000 {
compatible = "fsl,lpm-sram";
reg = <0x00900000, 0x20000>;
};
//aips1节点
aips1:aips-bus@02000000 {
compatible = "fsl,aips-bus", "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
reg = <0x02000000, 0x100000>;
ranges;
//ecspil节点
ecspil:aips-bus@02008000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,imx6ul-ecspi", "fsl,imx51-ecspi";
reg = <0x02008000, 0x4000>;
status = "disabled";
};
};
//aips2节点
aips2:aips-bus@02100000 {
compatible = "fsl,aips-bus", "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
reg = <0x02100000, 0x100000>;
ranges;
//usbotg1节点
usbotg1:usb@02184000 {
compatible = "fsl,imx6ul-usb", "fsl,imx27-usb";
reg = <0x02184000, 0x4000>;
status = "disabled";
};
};
//aips3节点
aips3:aips-bus@02200000 {
compatible = "fsl,aips-bus", "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
reg = <0x02200000, 0x100000>;
ranges;
//rgnb节点
rgnb:rgnb@02284000 {
compatible = "fsl,imx6sl-rng", "fsl,imx-rng", "imx-rng";
reg = <0x02284000, 0x4000>;
};
};
};
}
dts语法(待补充)
44 设备树下的 LED驱动实验
修改设备树 imx6ull-alientek-emmc.dts
/ {
model = "Freescale i.MX6 ULL 14x14 EVK Board";
compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
/* 4_dtsled alphaled--------------------------------------------------*/
alphaled {
#address-cells = <1>;
#size-cells = <1>;
compatible = "atkalpha-led";
status = "okay";
reg = < 0X020C406C 0X04 /* CCM_CCGR1_BASE */
0X020E0068 0X04 /* SW_MUX_GPIO1_IO03_BASE */
0X020E02F4 0X04 /* SW_PAD_GPIO1_IO03_BASE */
0X0209C000 0X04 /* GPIO1_DR_BASE */
0X0209C004 0X04 >; /* GPIO1_GDIR_BASE */
};
/* 4_dtsled alphaled---------------------------------------------------*/
make dtbs
cp /home/yinliang/linux/kernel/nxp_linux/arch/arm/boot/dts/imx6ull-alientek-emmc.dtb /home/yinliang/linux/tftpboot
dtsled.c
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.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 <linux/of.h>
#include <linux/of_address.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
/***************************************************************
文件名 : led.c
作者 : junluoyu
版本 : V1.0
描述 : led驱动文件 新版
其他 : 无
日志 : 初版 V1.0 2022/11/20
***************************************************************/
#define DTSLED_CNT 1 /*设备号个数*/
#define DTSLED_NAME "dtsled" /* 名字 */
// /* 寄存器物理地址 */
// #define CCM_CCGR1_BASE (0X020C406C)
// #define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
// #define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
// #define GPIO1_DR_BASE (0X0209C000)
// #define GPIO1_GDIR_BASE (0X0209C004)
/* 映射后的寄存器虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;
#define LEDOFF 0 //关灯
#define LEDON 1 //开灯
/*dtsled设备结构体*/
struct dtsled_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct device_node *nd; /* 设备节点 */
};
struct dtsled_dev dtsled; /*led设备 */
/*
* @description : led打开/关闭
* @param - sta :LED0N 0 打开LED LEDOFF 1 关闭LED
* @return :无
*/
void led_switch(u8 sta)
{
u32 val = 0;
if(sta == LEDON) {
val = readl(GPIO1_DR);
val &= ~(1<<3);
writel(val,GPIO1_DR);
}
else if (sta == LEDOFF) {
val = readl(GPIO1_DR);
val |= (1<<3);
writel(val,GPIO1_DR);
}
}
/*
* @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 = &dtsled; /*设置私有数据*/
return 0;
}
/*
* @description : 从设备读取数据
* @param - buf :返回给用户空间的数据缓冲区
* @param - filp :要打开的设备文件(文件描述符)
* @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 - buf :要写给设备写入的数据
* @param - filp :设备文件,表示打开的文件描述符
* @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 = 0;
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)
{
// printk("led release! \r\n");
return 0;
}
/*
* 设备操作结构体
*/
static struct file_operations dtsled_fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};
/*
* @description : 驱动入口函数
* @param - filp :无
* @return :0成功; 其他 失败
*/
static int __init led_init(void)
{
u32 val = 0;
int ret = 0;
u32 regdata[14];
const char *str;
struct property *proper;
/* 获取设备树中的属性数据 */
/* 1.获取设备节点 alphaled */
dtsled.nd = of_find_node_by_path("/alphaled"); //of_find_node_by_path得到alphaled节点
if(dtsled.nd == NULL) {
printk("alphaled node can not found !\r\n");
return -EINVAL;
} else {
printk("alphaled node has been found !\r\n");
}
/* 2.获取compatible属性*/
proper = of_find_property(dtsled.nd, "compatible", NULL); //of_find_property获取compatible属性,返回property结构体类型指针,property的成员变量value表示属性值
if(proper == NULL) {
printk("compatible property find failed!\r\n");
return -EINVAL;
} else {
printk("compatible = %s\r\n", (char*)proper->value);
}
/* 3.获取status属性*/
ret = of_property_read_string(dtsled.nd, "status", &str);//of_property_read_string获得status属性值
if(ret < 0) {
printk("status read failed!\r\n");
return -EINVAL;
} else {
printk("status = %s\r\n", str);
}
/* 3.获取status属性*/
ret = of_property_read_u32_array(dtsled.nd, "reg", regdata, 10);//of_property_read_u32_array得到reg属性值,保存到regdata数组
if(ret < 0) {
printk("reg property read failed!\r\n");
return -EINVAL;
} else {
u8 i = 0;
printk("reg data:\r\n");
for( i = 0; i < 10; i++)
printk("%#x", regdata[i]);
printk("\r\n");
}
/* 初始化LED */
#if 0
/* 1.寄存器地址映射 */
IMX6U_CCM_CCGR1 = ioremap(regdata[0], regdata[1]);
SW_MUX_GPIO1_IO03 = ioremap(regdata[2], regdata[3]);
SW_PAD_GPIO1_IO03 = ioremap(regdata[4], regdata[5]);
GPIO1_DR = ioremap(regdata[6], regdata[7]);
GPIO1_GDIR = ioremap(regdata[8], regdata[9]);
#else
IMX6U_CCM_CCGR1 = of_iomap(dtsled.nd, 0); //of_iomap一次性完成读取reg属性以及内存映射,是设备树推荐的OF函数
SW_MUX_GPIO1_IO03 = of_iomap(dtsled.nd, 1);
SW_PAD_GPIO1_IO03 = of_iomap(dtsled.nd, 2);
GPIO1_DR = of_iomap(dtsled.nd, 3);
GPIO1_GDIR = of_iomap(dtsled.nd, 4);
#endif
/* 2.使能GPIO1时钟 */
val = readl(IMX6U_CCM_CCGR1);
val &= ~(3<<26); /* 清除以前的设置 */
val |= (3<<26); /* 设置新值 */
writel(val, IMX6U_CCM_CCGR1);
/* 3.设置GPIO1_IO03的复用功能,将其复用为GPIO01_IO03,最后设置IO属性 */
writel(5, SW_MUX_GPIO1_IO03); //mux复用功能为5
writel(0X10B0, SW_PAD_GPIO1_IO03); //PAD设置IO属性
/* 4.设置GPIO1_IO03为输出功能*/
val = readl(GPIO1_GDIR);
val &= ~(1<<3); /* 清除以前的设置 */
val |= (1<<3); /* 设置为输出 */
writel(val,GPIO1_GDIR);
/* 5.默认关闭LED*/
val = readl(GPIO1_DR);
val |= (1<<3); //默认为关,1电平
writel(val, GPIO1_DR);
/* 注册字符设备驱动 */
// retvalue = register_chrdev(LED_MAJOR, LED_NAME,
// &led_fops);
/* 1.创建设备号 */
if(dtsled.major) { /* 定义了设备号 */
dtsled.devid = MKDEV(dtsled.major, 0);
register_chrdev_region(dtsled.devid, DTSLED_CNT,DTSLED_NAME);
}
else {/* 没有定义设备号 */
alloc_chrdev_region(&dtsled.devid, 0, DTSLED_CNT, DTSLED_NAME);/*动态申请设备号*/
dtsled.major = MAJOR(dtsled.devid); /* 获取主设备号 */
dtsled.minor = MINOR(dtsled.devid); /* 获取次设备号*/
}
printk("dtsled major=%d, minor=%d\r\n", dtsled.major, dtsled.minor);
/* 2.初始化cdev */
dtsled.cdev.owner = THIS_MODULE;
cdev_init(&dtsled.cdev, &dtsled_fops);
/* 3.添加一个cdev */
cdev_add(&dtsled.cdev, dtsled.devid, DTSLED_CNT);
/* 4.创建类 */
dtsled.class = class_create(THIS_MODULE, DTSLED_NAME);
if(IS_ERR(dtsled.class)) {
return PTR_ERR(dtsled.class);
}
/* 5.创建设备 */
dtsled.device = device_create(dtsled.class, NULL, dtsled.devid, NULL, DTSLED_NAME);
if(IS_ERR(dtsled.device)) {
return PTR_ERR(dtsled.device);
}
return 0;
}
/*
* @description : 驱动出口函数
* @param - filp :无
* @return :无
*/
static void __exit led_exit(void)
{
/* 取消映射 */
iounmap(IMX6U_CCM_CCGR1 );
iounmap(SW_PAD_GPIO1_IO03 );
iounmap(SW_MUX_GPIO1_IO03 );
iounmap(GPIO1_DR );
iounmap(GPIO1_GDIR );
/* 注销字符设备驱动 */
//unregister_chrdev(LED_MAJOR,LED_NAME);
cdev_del(&dtsled.cdev); /* 删除cdev */
unregister_chrdev_region(dtsled.devid, DTSLED_CNT);/*注销设备号*/
device_destroy(dtsled.class, dtsled.devid);
class_destroy(dtsled.class);
}
/*
* 将上面两个函数指定为驱动的入口和出口函数
*/
module_init(led_init);
module_exit(led_exit);
/*
* LICENSE和作者信息
*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("junluoyu");
dtsledApp.c
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
/***************************************************************
文件名 : ledApp.c
作者 : junluoyu
版本 : V1.0
描述 : led驱动测试APP
其他 : 无
日志 : 初版 V1.0 2022/11/28
其他 : ./ledApp /dev/led 1(打开LED)|0(关闭LED)
***************************************************************/
#define LEDOFF 0 //关灯
#define LEDON 1 //开灯
/*
* @description : main主程序
* @param -argc :argv数组元素长度
* @param -argv :具体参数
*/
int main(int argc, char *argv[])
{
int fd, retvalue;
char *filename;
unsigned char databuf[1];
if (argc != 3) {
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
/* 打开驱动文件 */
fd = open(filename, O_RDWR);
if (fd < 0) {
printf("Can't open file %s \r\n", filename);
return -1;
}
databuf[0] = atoi(argv[2]);
/* 向/dev/led文件写入数据 */
retvalue = write(fd, databuf,sizeof(databuf));
if (retvalue < 0) {
printf("led control failed!\r\n");
close(fd);
return -1;
}
/* 关闭led设备 */
retvalue = close(fd);
if (retvalue < 0) {
printf("file %s close failed!\r\n", argv[1]);
return -1;
}
return 0;
}
Makefile
KERNELDIR := /home/yinliang/linux/kernel/nxp_linux
CURERENT_PATH := $(shell pwd)
obj-m := dtsled.o
build: kernel_moduls
kernel_moduls:
$(MAKE) -C $(KERNELDIR) M=$(CURERENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURERENT_PATH) clean
复制dtsled.ko dtsledApp 到 lib/modules/4.1.15/
make -j16
arm-linux-gnueabihf-gcc dtsledApp.c -o dtsledApp
cp dtsled.ko dtsledApp /home/yinliang/linux/nfs/rootfs/lib/modules/4.1.15/
启动板卡测试
cd /lib/modules/4.1.15
1.安装驱动模块 第一次加载先使用depmod
insmod dtsled.ko
modprobe dtsled.ko #会安装依赖文件
2.自动挂载
ls /dev/dtsled -l
3.测试
./dtsledApp /dev/dtsled 1 //开灯
./dtsledApp /dev/dtsled 0 //关灯
4.卸载驱动模块
rmmod dtsled.ko
modprobe -r dtsled.ko #会卸载依赖文件