文章目录
字符设备驱动
字符设备是 Linux 驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节流进行读写操作的设备,读写数据是分先后顺序的。比如我们最常见的点灯、按键、IIC、SPI,LCD 等等都是字符设备,这些设备的驱动就叫做字符设备驱动。
file_operations结构体
每一个系统调用,在驱动中都有与之对应的一个驱动函数,在 Linux 内核文件 include/linux/fs.h
中有个叫做 file_operations
的结构体,此结构体就是 Linux 内核驱动操作函数集合
struct file_operations {
struct module *owner; // 拥有该结构体模块的指针,一般设置为 THIS_MODULE
loff_t (*llseek) (struct file *, loff_t, int); // 修改文件读写位置函数
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); // 读
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); // 写
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iterate) (struct file *, struct dir_context *);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
// poll 轮询函数,用于查询设备是否可以进行非阻塞的读写
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
// unlocked_ioctl函数提供对于设备的控制功能,与应用程序中的ioctl函数对应。32位系统程序调用
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
// compat_ioctl 函数与 unlocked_ioctl 函数功能一样,区别在于这个是64位系统的程序调用
int (*mmap) (struct file *, struct vm_area_struct *);
// mmap 函数用于将将设备的内存映射到进程空间中(也就是用户空间)
int (*mremap)(struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *); // 打开设备
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
// release 函数用于释放(关闭)设备文件,与应用程序中的 close 函数对应
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
// aio_fsync 函数与 fasync 函数的功能类似,只是 aio_fsync 是异步刷新待处理的数据。
int (*fasync) (int, struct file *, int);
// fasync 函数用于刷新待处理的数据,用于将缓冲区中的数据刷新到磁盘中
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **, void **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
unsigned (*mmap_capabilities)(struct file *);
#endif
};
字符驱动开发步骤
驱动模块的加载与卸载
#include <linux/init.h> // module_init、module_exit函数的头文件所在地
/*驱动入口函数 当使用“insmod mydev.ko”命令或“modprobe mydev.ko”加载驱动的时候,
mydev_init 这个函数就会被调用*/
static int __init mydev_init(void)
{
return 0;
}
/*驱动出口函数 当使用“rmmod mydev.ko”命令或者“modprobe -r mydev.ko”卸载具体驱动的时候
mydev_exit 函数就会被调用*/
static void __exit mydev_exit(void)
{
}
module_init(mydev_init); // 注册加载函数
module_exit(mydev_exit); // 注册卸载函数
字符设备注册于注销
当驱动模块加载成功以后需要注册字符设备,同样,卸载驱动模块的时候也需要注销掉字符设备。在/linux/fs.h
中是这样定义的。
#include <linux/fs.h>
/*注册字符设备 返回值小于0表示注册失败*/
static inline int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)
{
return __register_chrdev(major, 0, 256, name, fops);
}
/*unregister_chrdev 函数用户注销字符设备*/
static inline void unregister_chrdev(unsigned int major, const char *name)
{
__unregister_chrdev(major, 0, 256, name);
}
// major:主设备号,Linux 下每个设备都有一个设备号,设备号分为主设备号和次设备号两部分
// 输入命令“cat /proc/devices”可以查看当前已经被使用掉的设备号
// name:设备名字,指向一串字符串。
// fops:结构体 file_operations 类型指针,指向设备的操作函数集合变量
#include <linux/init.h> // module_init、module_exit函数的头文件所在地
#include <linux/fs.h> //添加文件描述符,设备注册注销头文件
#define MYMAJOR 200
#define MYNAME "my_dev"
static struct file_operations my_fops;
/*驱动入口函数 当使用“insmod mydev.ko”命令或“modprobe mydev.ko”加载驱动的时候,
mydev_init 这个函数就会被调用*/
static int __init mydev_init(void)
{
int ret = 0;
ret = register_chrdev(MYMAJOR,MYNAME,&my_fops);
if(ret < 0)
{
printk(KERN_ERR "register_chrdev fail\n");
}
printk(KERN_INFO "register_chrdev success...\n");
return 0;
}
/*驱动出口函数 当使用“rmmod mydev.ko”命令或者“modprobe -r mydev.ko”卸载具体驱动的时候
mydev_exit 函数就会被调用*/
static void __exit mydev_exit(void)
{
printk(KERN_INFO "mydev_exit\n");
unregister_chrdev(MYMAJOR,MYNAME);
}
module_init(mydev_init); // 注册加载函数
module_exit(mydev_exit); // 注册卸载函数
添加设备具体操作函数
#include <linux/init.h> // module_init、module_exit函数的头文件所在地
#include <linux/fs.h> //添加文件描述符,设备注册注销头文件
#define MYMAJOR 200
#define MYNAME "my_dev"
/*打开设备*/
static int my_dev_open (struct inode *inode, struct file *file)
{
return 0;
}
/*读设备*/
static ssize_t my_dev_read (struct file *file, char __user *buf,
size_t cnt, loff_t * offt)
{
return 0;
}
/*写设备*/
static ssize_t my_dev_write (struct file *file, const char __user *buf,
size_t cnt, loff_t *offt)
{
return 0;
}
/*释放设备*/
static int my_dev_close (struct inode *inode, struct file *file)
{
return 0;
}
static struct file_operations my_fops = {
.owner = THIS_MODULE, // 惯例,直接写即可
.open = my_dev_open, // 打开设备
.read = my_dev_read, // 读设备
.write = my_dev_write, // 写设备
.release = my_dev_close, // 关闭设备
};
/*驱动入口函数 当使用“insmod mydev.ko”命令或“modprobe mydev.ko”加载驱动的时候,
mydev_init 这个函数就会被调用*/
static int __init mydev_init(void)
{
int ret = 0;
ret = register_chrdev(MYMAJOR,MYNAME,&my_fops);
if(ret < 0)
{
printk(KERN_ERR "register_chrdev fail\n");
}
printk(KERN_INFO "register_chrdev success...\n");
return 0;
}
/*驱动出口函数 当使用“rmmod mydev.ko”命令或者“modprobe -r mydev.ko”卸载具体驱动的时候
mydev_exit 函数就会被调用*/
static void __exit mydev_exit(void)
{
printk(KERN_INFO "mydev_exit\n");
unregister_chrdev(MYMAJOR,MYNAME);
}
module_init(mydev_init); // 注册加载函数
module_exit(mydev_exit); // 注册卸载函数
添加 LICENSE 和作者信息(字符设备模板)
最后我们需要在驱动中加入 LICENSE 信息和作者信息,其中 LICENSE 是必须添加的,否则的话编译的时候会报错,作者信息可以添加也可以不添加。此时最后就得到了一个字符设备的基础模板。
#include <linux/init.h> // module_init、module_exit函数的头文件所在地
#include <linux/fs.h> //添加文件描述符,设备注册注销头文件
#include <linux/module.h> // 添加module_XXX宏定义头文件
#define MYMAJOR 200
#define MYNAME "my_dev"
/*打开设备*/
static int my_dev_open (struct inode *inode, struct file *file)
{
return 0;
}
/*读设备*/
static ssize_t my_dev_read (struct file *file, char __user *buf,
size_t cnt, loff_t * offt)
{
return 0;
}
/*写设备*/
static ssize_t my_dev_write (struct file *file, const char __user *buf,
size_t cnt, loff_t *offt)
{
return 0;
}
/*释放设备*/
static int my_dev_close (struct inode *inode, struct file *file)
{
return 0;
}
static struct file_operations my_fops = {
.owner = THIS_MODULE, // 惯例,直接写即可
.open = my_dev_open, // 打开设备
.read = my_dev_read, // 读设备
.write = my_dev_write, // 写设备
.release = my_dev_close, // 关闭设备
};
/*驱动入口函数 当使用“insmod mydev.ko”命令或“modprobe mydev.ko”加载驱动的时候,
mydev_init 这个函数就会被调用*/
static int __init mydev_init(void)
{
int ret = 0;
ret = register_chrdev(MYMAJOR,MYNAME,&my_fops);
if(ret < 0)
{
printk(KERN_ERR "register_chrdev fail\n");
}
printk(KERN_INFO "register_chrdev success...\n");
return 0;
}
/*驱动出口函数 当使用“rmmod mydev.ko”命令或者“modprobe -r mydev.ko”卸载具体驱动的时候
mydev_exit 函数就会被调用*/
static void __exit mydev_exit(void)
{
printk(KERN_INFO "mydev_exit\n");
unregister_chrdev(MYMAJOR,MYNAME);
}
module_init(mydev_init); // 注册加载函数
module_exit(mydev_exit); // 注册卸载函数
// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("dongfang"); // 描述模块的作者
MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息
MODULE_ALIAS("------"); // 描述模块的别名信息
设备号
Linux 中每个设备都有一个设备号,设备号由主设备号和次设备号两部分组成,主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备。在/linux/types.h
中定义了typedef __u32 __kernel_dev_t;
和typedef __kernel_dev_t dev_t;
。dev_t
是32位的数据类型,其中高 12 位为主设备号,低 20 位为次设备号。因此 Linux系统中主设备号范围为 0~4095。
设备号组成
在文件 include/linux/kdev_t.h
中提供了几个关于设备号的操作函数(本质是宏)
#define MINORBITS 20 // 次设备号位数
#define MINORMASK ((1U << MINORBITS) - 1) // 次设备号掩码
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) // 主设备号
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) // 次设备号
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi)) // 组成设备号dev_t
设备号分配
-
静态分配
也就是上面模板的形式。一般要查看
cat /proc/devices
中已经使用的,不要冲突。// 静态申请设备号 int register_chrdev_region(dev_t from, unsigned count, const char *name) // from 起始设备号 // count 设备数量 // name 设备名字 // 成功返回0,失败返回负数 /*设备号的释放函数*/ void unregister_chrdev_region(dev_t from, unsigned count); // from:要释放的设备号。 // count:表示从 from 开始,要释放的设备号数量。
-
动态分配
在注册字设备之前先申请一个设备号,系统会自动给你一个没有被使用的设备号,这样就避免了冲突。卸载驱动的时候释放掉这个设备号即可。
#include <linux/fs.h> // 头文件包含 /*设备号的申请函数*/ int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name); // dev:保存申请到的设备号。传出参数 // baseminor:次设备号起始地址,alloc_chrdev_region 可以申请一段连续的多个设备号, // 这些设备号的主设备号一样,但是次设备号不同,次设备号以 baseminor 为起始地址地址开始递增。 // 一般 baseminor 为 0,也就是说次设备号从 0 开始。 // count:要申请的设备号数量。 // name:设备名字 /*设备号的释放函数*/ void unregister_chrdev_region(dev_t from, unsigned count); // from:要释放的设备号。 // count:表示从 from 开始,要释放的设备号数量。
字符设备驱动实验(模拟)
my_dev
设备有两个缓冲区,一个为读缓冲区,一个为写缓冲区,这两个缓冲区的大小都为 100 字节。在应用程序中可以向 my_dev
设备的写缓冲区中写入数据,从读缓冲区中读取数据。
my_dev 驱动代码编写
#include <linux/init.h> // module_init、module_exit函数的头文件所在地
#include <linux/fs.h> //添加文件描述符,设备注册注销头文件
#include <linux/module.h> // 添加module_XXX宏定义头文件
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#define MYMAJOR 200
#define MYNAME "my_dev"
static char read_buf[100]; // 读缓冲区
static char write_buf[100]; // 写缓冲区
static char kernel_data[] = {"kernel data!"};
/*打开设备*/
static int my_dev_open(struct inode *inode, struct file *file)
{
printk("chrdevbase open!\r\n");
return 0;
}
/*读设备*/
static ssize_t my_dev_read(struct file *file, char __user *buf,
size_t cnt, loff_t *offt)
{
int ret = 0;
/*向用户空间发送数据*/
memcpy(read_buf,kernel_data,sizeof(kernel_data));
ret = copy_to_user(buf,read_buf,cnt);
if(ret == 0)
{
printk(KERN_INFO "read success...\n");
}else
{
printk(KERN_INFO "read fail\n");
}
return 0;
}
/*写设备*/
static ssize_t my_dev_write(struct file *file, const char __user *buf,
size_t cnt, loff_t *offt)
{
int ret = 0;
/*接收用户空间的数据*/
ret = copy_from_user(write_buf,buf,cnt);
if(ret == 0)
{
/*将写入的数据打印出来*/
printk(KERN_INFO "write success %s ...\n",write_buf);
}else
{
printk(KERN_INFO "write fail\n");
}
return 0;
}
/*释放设备*/
static int my_dev_close(struct inode *inode, struct file *file)
{
printk(KERN_INFO "release dev \n");
return 0;
}
static struct file_operations my_fops = {
.owner = THIS_MODULE, // 惯例,直接写即可
.open = my_dev_open, // 打开设备
.read = my_dev_read, // 读设备
.write = my_dev_write, // 写设备
.release = my_dev_close, // 关闭设备
};
/*驱动入口函数 当使用“insmod my_dev.ko”命令或“modprobe my_dev.ko”加载驱动的时候,
mydev_init 这个函数就会被调用*/
static int __init mydev_init(void)
{
int ret = 0;
ret = register_chrdev(MYMAJOR,MYNAME,&my_fops);
if(ret < 0)
{
printk(KERN_ERR "register_chrdev fail\n");
}
printk(KERN_INFO "register_chrdev success...\n");
return 0;
}
/*驱动出口函数 当使用“rmmod my_dev.ko”命令或者“modprobe -r my_dev.ko”卸载具体驱动的时候
mydev_exit 函数就会被调用*/
static void __exit mydev_exit(void)
{
printk(KERN_INFO "mydev_exit\n");
unregister_chrdev(MYMAJOR,MYNAME);
}
module_init(mydev_init); // 注册加载函数
module_exit(mydev_exit); // 注册卸载函数
// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("dongfang"); // 描述模块的作者
MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息
MODULE_ALIAS("------"); // 描述模块的别名信息
Makefile编写
KERNELDIR := /home/haitu/Desktop/Linux/NXP/NXP_linux
CURRENT_PATH := $(shell pwd)
obj-m := my_dev.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
APP 代码编写
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
static char usr_data[] = {"usr data!"};
int main(int argc, char* argv[])
{
int fd, ret;
char* file_name;
char read_buf[100],write_buf[100];
if(argc != 3) // 命令行参数过少
{
printf("used error \n");
return -1;
}
file_name = argv[1];
fd = open(file_name,O_RDWR); // 打开设备文件
if(atoi(argv[2]) == 1) // 读取数据
{
ret = read(fd,read_buf,50);
if(ret < 0)
{
printf("read error \n");
}else
{
printf("read success %s \n",read_buf);
}
}
if(atoi(argv[2]) == 2) // 写数据
{
memcpy(write_buf,usr_data,sizeof(usr_data));
ret = write(fd,write_buf,50);
if(ret < 0)
{
printf("write file %s err \n",file_name);
}
}
ret = close(fd);
if(ret < 0)
{
printf("close error\n");
return -1;
}
return 0;
}
编译:arm-linux-gnueabihf-gcc APP.c -o APP
。file APP
显示的信息说明这个可执行文件是 32 位 LSB 格式,ARM 版本的,因此 APP 只能在 ARM 芯片下运行。执行APP文件./APP my_dev 1
。使用modprobe
加载驱动,需要手动创建设备节点,mknod /dev/my_dev c 200 0
。
关于printk
printk
相当于 printf
的孪生兄妹,printf
运行在用户态,printk
运行在内核态。printk
可以根据日志级别对消息进行分类,一共有 8 个消息级别,这 8 个消息级别定义在文件 include/linux/kern_levels.h
里面
#define KERN_EMERG KERN_SOH "0" /* 紧急事件,一般是内核崩溃*/
#define KERN_ALERT KERN_SOH "1" /* 必须立即采取行动*/
#define KERN_CRIT KERN_SOH "2" /* 临界条件,比如严重的软件或硬件错误*/
#define KERN_ERR KERN_SOH "3" /* 错误状态,一般设备驱动程序中使用KERN_ERR 报告硬件错误*/
#define KERN_WARNING KERN_SOH "4" /* 警告信息,不会对系统造成严重影响 */
#define KERN_NOTICE KERN_SOH "5" /* 有必要进行提示的一些信息 */
#define KERN_INFO KERN_SOH "6" /* 提示性的信息 */
#define KERN_DEBUG KERN_SOH "7" /* 调试信息 默认就是这个级别的 */
// 在linux/printk.h文件中定义如下,#define CONSOLE_LOGLEVEL_DEFAULT 7
printk("KERN_DEBUG : ------ ");
printk(KERN_INFO"KERN_INFO : ------ ");
LED 驱动实验(直接操作寄存器)
重点就是编写 Linux
下 I.MX6UL
引脚控制驱动。
地址映射
MMU
全称叫做 Memory Manage Unit
,也就是内存管理单元。在老版本的 Linux
中要求处理器必须有 MMU
,但是现在Linux
内核已经支持无 MMU
的处理器了。MMU
主要完成的功能如下:
①、完成虚拟空间到物理空间的映射。
②、内存保护,设置存储器的访问权限,设置虚拟存储空间的缓冲特性。
虚拟地址(VA,Virtual Address)
、物理地址(PA,PhyscicalAddress)
。对于 32
位的处理器来说,虚拟地址范围是 2^32=4GB
,我们的开发板上有 512MB
的 DDR3,这 512MB
的内存就是物理内存,经过 MMU
可以将其映射到整个 4GB 的虚拟空间。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z3FojHBU-1681260356942)(D:\Soft\Typora\Picture\image-20230410085909478.png)]
开启MMU后,配置GPIO就不能直接使用物理地址进行配置,而是要将物理地址转换为虚拟地址,在进行配置。
- ioremap
// 1、ioremap 函数
#define ioremap(cookie,size) __arm_ioremap((cookie), (size), MT_DEVICE)
void __iomem *__arm_ioremap(phys_addr_t phys_addr, size_t size,
unsigned int mtype)
{
return arch_ioremap_caller(phys_addr, size, mtype,
__builtin_return_address(0));
}
// phys_addr:要映射给的物理起始地址。
// size:要映射的内存空间大小。
// mtype:ioremap 的类型,可以选择 MT_DEVICE、MT_DEVICE_NONSHARED、
// MT_DEVICE_CACHED 和 MT_DEVICE_WC,ioremap 函数选择 MT_DEVICE。
// 返回值:__iomem 类型的指针,指向映射后的虚拟空间首地址。
#define MX53_DPLL1_BASE 0x63f80000
#define SZ_16K 0x00004000
static void __iomem *pll_base;
pll_base = ioremap(MX53_DPLL1_BASE, SZ_16K);
// MX53_DPLL1_BASE映射16K的虚拟地址,首地址为pll_base
- iounmap
// 2、iounmap 函数
void iounmap (volatile void __iomem *addr)
// iounmap 只有一个参数 addr,此参数就是要取消映射的虚拟地址空间首地址
iounmap(pll_base); // 取消虚拟地址pll_base
I/O 内存访问函数
使用 ioremap
函数将寄存器的物理地址映射到虚拟地址以后,我们就可以直接通过指针访问这些地址,但是 Linux
内核不建议这么做,而是推荐使用一组操作函数来对映射后的内存进行读写操作。
// 读操作函数
static inline u8 readb(const volatile void __iomem *addr);
static inline u16 readw(const volatile void __iomem *addr);
static inline u32 readl(const volatile void __iomem *addr);
// readb、readw 和 readl 这三个函数分别对应 8bit、16bit 和 32bit 读操作
// 参数 addr 就是要读取写内存地址
// 返回值就是读取到的数据
// 写操作函数
static inline void writeb(u8 value, volatile void __iomem *addr)
static inline void writew(u16 value, volatile void __iomem *addr)
static inline void writel(u32 value, volatile void __iomem *addr)
// writeb、writew 和 writel 这三个函数分别对应 8bit、16bit 和 32bit 写操作
// 参数 value 是要写入的数值,addr 是要写入的地址
LED 灯驱动程序编写
#include <linux/init.h> // module_init、module_exit函数的头文件所在地
#include <linux/fs.h> //添加文件描述符,设备注册注销头文件
#include <linux/module.h> // 添加module_XXX宏定义头文件
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <asm/io.h> // 包含readl等IO操作函数
#include <linux/gpio.h>
#include <linux/errno.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#define MYMAJOR 200
#define MYNAME "my_led"
#define LED_OFF 0 /* 关灯 */
#define LED_ON 1 /* 开灯 */
/* 寄存器物理地址 */
#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; // 设置IO时钟虚拟地址
static void __iomem *SW_MUX_GPIO1_IO03; // 设置IO复用虚拟地址
static void __iomem *SW_PAD_GPIO1_IO03; // 设置IO属性虚拟地址
static void __iomem *GPIO1_DR; // 设置IO输出功能虚拟地址
static void __iomem *GPIO1_GDIR; // led虚拟地址
void led_switch(u8 sta)
{
u32 val = 0;
if(sta == LED_ON) /*开灯*/
{
val = readl(GPIO1_DR);
val &= ~(1 << 3);
writel(val, GPIO1_DR);
}else if(sta == LED_OFF) /*关灯*/
{
val = readl(GPIO1_DR);
val|= (1 << 3);
writel(val, GPIO1_DR);
}
}
/*打开设备*/
static int my_dev_open(struct inode *inode, struct file *file)
{
printk("chrdevbase open!\r\n");
return 0;
}
/*读设备*/
static ssize_t my_dev_read(struct file *file, char __user *buf,
size_t cnt, loff_t *offt)
{
return 0;
}
/*写设备*/
static ssize_t my_dev_write(struct file *file, const char __user *buf,
size_t cnt, loff_t *offt)
{
int ret = 0;
unsigned char data_buf[1];
unsigned char led_stat;
/*接收用户空间的数据*/
ret = copy_from_user(data_buf,buf,cnt);
if(ret < 0)
{
printk(KERN_INFO "write fail ...\n");
return -EFAULT;
}
led_stat = data_buf[0]; /* 获取状态值 */
if(led_stat == LED_ON)
{
led_switch(LED_ON);
}else if(led_stat == LED_OFF)
{
led_switch(LED_OFF);
}
return 0;
}
/*释放设备*/
static int my_dev_close(struct inode *inode, struct file *file)
{
printk(KERN_INFO "release dev \n");
printk("------- %s -------\n", __FUNCTION__);
return 0;
}
static struct file_operations my_fops = {
.owner = THIS_MODULE, // 惯例,直接写即可
.open = my_dev_open, // 打开设备
.read = my_dev_read, // 读设备
.write = my_dev_write, // 写设备
.release = my_dev_close, // 关闭设备
};
/*驱动入口函数 当使用“insmod my_dev.ko”命令或“modprobe my_dev.ko”加载驱动的时候,
mydev_init 这个函数就会被调用*/
static int __init mydev_init(void)
{
int ret = 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 的复用功能,将其复用为
* GPIO1_IO03,最后设置 IO 属性。
*/
writel(5, SW_MUX_GPIO1_IO03);
/* 寄存器 SW_PAD_GPIO1_IO03 设置 IO 属性 */
writel(0x10B0, SW_PAD_GPIO1_IO03);
/* 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);
writel(val, GPIO1_DR);
/* 6、字符设备注册 */
ret = register_chrdev(MYMAJOR,MYNAME,&my_fops);
if(ret < 0)
{
printk(KERN_ERR "register_chrdev fail\n");
}
printk(KERN_INFO "register_chrdev success...\n");
return 0;
}
/*驱动出口函数 当使用“rmmod my_dev.ko”命令或者“modprobe -r my_dev.ko”卸载具体驱动的时候
mydev_exit 函数就会被调用*/
static void __exit mydev_exit(void)
{
/* 取消映射 */
iounmap(IMX6U_CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
printk(KERN_INFO "mydev_exit\n");
unregister_chrdev(MYMAJOR,MYNAME);
}
module_init(mydev_init); // 注册加载函数
module_exit(mydev_exit); // 注册卸载函数
// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("dongfang"); // 描述模块的作者
MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息
MODULE_ALIAS("------"); // 描述模块的别名信息
Makefile
KERNELDIR := /home/haitu/Desktop/Linux/NXP/NXP_linux
CURRENT_PATH := $(shell pwd)
obj-m := my_led.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
APP.c
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
int main(int argc, char* argv[])
{
int fd, ret;
char* file_name;
char data_buf[1];
if(argc != 3) // 命令行参数过少
{
printf("used error \n");
return -1;
}
file_name = argv[1];
fd = open(file_name,O_RDWR); // 打开设备文件
data_buf[0] = atoi(argv[2]); /* 要执行的操作:打开或关闭 */
ret = write(fd, data_buf, sizeof(data_buf));
if(ret < 0)
{
printf("write error \n");
close(fd);
return -1;
}
ret = close(fd);
if(ret < 0)
{
printf("close error\n");
return -1;
}
return 0;
}