嵌入式LINUX学习心得–基于正点原子IMX6ULL
观前提醒:用电脑带目录看,此文更多是介绍性和作者自我小结用。
一.前言
笔者基于正点原子IMX6ULL来学习嵌入式LINUX驱动开发,嵌入式LINUX开发大致可分为两个大方向:1.应用开发;2.驱动开发。其中应用开发主要是使用已经编写好的驱动来完成具体的项目应用,对芯片底层相关知识要求较低,程序运行于用户态;驱动开发顾名思义,主要是为应用开发提供各个功能模块的接口,开发者需要对芯片的硬件性能和LINUX内核有一定了解。由于笔者目前只学习了驱动开发故只介绍驱动开发相关知识。 本文绝大多出自I.MX6U 嵌入式 Linux 驱动开发指南 V1.5.1中,关于驱动开发,笔者最大的经验就是多写代码,多做有关的项目应用。
二.驱动开发前预备内容
1.LINUX系统相关命令学习
这部分主要包括LINUX的一些常用基本命令,VIM编辑器使用,以及shell编程的内容,这部分上手较快,网上有很多资料,笔者不再赘述。
2.LINUX系统移植
1.U-BOOT系统
Linux 系统要启动就必须需要一个 bootloader 程序,也就说芯片上电以后先运行一段bootloader程序。这段bootloader程序会先初始化DDR等外设,然后将Linux内核从flash(NAND,NOR FLASH, SD, MMC 等)拷贝到 DDR 中,最后启动 Linux 内核。当然了, bootloader 的实际工作要复杂的多,但是它最主要的工作就是启动 Linux 内核, bootloader 和 Linux 内核的关系,就跟 PC 上的 BIOS 和 Windows 的关系一样, bootloader 就相当于 BIOS。所以我们要先搞定bootloader,很庆幸,有很多现成的 bootloader 软件可以使用,比如 U-Boot、 vivi、 RedBoot 等等,其中以 U-Boot 使用最为广泛。
uboot启动过程的具体行为 第一阶段: 初始化时钟,关闭看门狗,关中断,启动ICACHE,关闭DCACHE和TLB,关闭MMU,初始化NAD FLASH,重定位。 第二阶段: 初始化一个串口,检测系统内存映射,将内核映像和根文件系统从Flash读到SDRAM空间中,为内核设置启动参数,调用内核。
uboot与内核如何完成参数传递? uboot启动后已完成基本的硬件初始化(内存,串口),接下来,主要任务是加载Linux内核到开发板的内存,然后跳转到LINUX内核所在地址运行。 TIPS:面经,只要问到uboot,面试官必问uboot和内核的参数传递。 具体如何跳转?直接修改PC寄存器的值为LINUX内核所在的地址,这样CPU就会从LINUX内核所在地址去取指令,从而执行代码。 跳转到内核之前,uboot需要做的三件事情: 1.CPU寄存器的设置 R0=0; R1=机器类型ID R2=启动参数标记列表在RAM中起始基地址
2.CPU工作模式 必须静止中断(IRQs和FIQs) CPU必须为SVC模式 3.cache和MMU的设置 MMU必须关闭 指令Cache可以打开也可以关闭 数据Cache必须关闭
其中上面第一步CPU寄存器的设置中,就是通过R0,R1,R2三个参数给内核传递参数的。
uboot系统移植 见正点原子I.MX6U 嵌入式 Linux 驱动开发指南 V1.5.1
2.LINUX内核移植
见正点原子I.MX6U 嵌入式 Linux 驱动开发指南 V1.5.1
3.根文件系统构建
Linux 中的根文件系统更像是一个文件夹或者叫做目录(在我看来就是一个文件夹,只不过是特殊的文件夹),在这个目录里面会有很多的子目录。根目录下和子目录中会有很多的文件,这些文件是 Linux 运行所必须的,比如库、常用的软件和命令、设备文件、配置文件等等。以后我们说到文件系统,如果不特别指明,统一表示根文件系统。 根文件系统首先是内核启动时所 mount(挂载)的第一个文件系统,内核代码映像文件保存在根文件系统中,而系统引导启动程序会在根文件系统挂载之后从中把一些基本的初始化脚本和服务等加载到内存中去运行。
根文件系统构建方法 见正点原子I.MX6U 嵌入式 Linux 驱动开发指南 V1.5.1
三.驱动开发相关知识点
驱动开发设备大致分为三类:字符设备驱动,块设备驱动,网络设备驱动,笔者主要学习字符设备驱动
一.字符驱动开发步骤
1.驱动模块的加载和卸载
module_init(xxx_init);
module_exit(xxx_exit);
2.字符设备注册与注销
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)
3.实现设备的具体操作函数
1588 struct file_operations {
1589 struct module *owner;
1590 loff_t (*llseek) (struct file *, loff_t, int);
1591 ssize_t (*read) (struct file *, char __user *, size_t, loff_t
*);
1592 ssize_t (*write) (struct file *, const char __user *, size_t,
loff_t *);
1593 ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
1594 ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
1595 int (*iterate) (struct file *, struct dir_context *);
1596 unsigned int (*poll) (struct file *, struct poll_table_struct
*);
1597 long (*unlocked_ioctl) (struct file *, unsigned int, unsigned
long);
1598 long (*compat_ioctl) (struct file *, unsigned int, unsigned
long);
1599 int (*mmap) (struct file *, struct vm_area_struct *);
1600 int (*mremap)(struct file *, struct vm_area_struct *);
1601 int (*open) (struct inode *, struct file *);
1602 int (*flush) (struct file *, fl_owner_t id);
1603 int (*release) (struct inode *, struct file *);
1604 int (*fsync) (struct file *, loff_t, loff_t, int datasync);
1605 int (*aio_fsync) (struct kiocb *, int datasync);
1606 int (*fasync) (int, struct file *, int);
1607 int (*lock) (struct file *, int, struct file_lock *);
1608 ssize_t (*sendpage) (struct file *, struct page *, int, size_t,
loff_t *, int);
1609 unsigned long (*get_unmapped_area)(struct file *, unsigned long,
unsigned long, unsigned long, unsigned long);
1610 int (*check_flags)(int);
1611 int (*flock) (struct file *, int, struct file_lock *);
1612 ssize_t (*splice_write)(struct pipe_inode_info *, struct file *,
loff_t *, size_t, unsigned int);
1613 ssize_t (*splice_read)(struct file *, loff_t *, struct
pipe_inode_info *, size_t, unsigned int);
1614 int (*setlease)(struct file *, long, struct file_lock **, void
**);
1615 long (*fallocate)(struct file *file, int mode, loff_t offset,
1616 loff_t len);
1617 void (*show_fdinfo)(struct seq_file *m, struct file *f);
1618 #ifndef CONFIG_MMU
1619 unsigned (*mmap_capabilities)(struct file *);
1620 #endif
1621 };
4.添加LICENSE和作者信息
MODULE_LICENSE()
MODULE_AUTHOR()
Linux设备号 Linux 中每个设备都有一个设备号,设备号由主设备号和次设备号两部分组成,主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备。
实验代码:
1.驱动代码:
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/module.h>
#include<linux/ide.h>
#include <linux/init.h>
#define CHRDEVYQX_MAJOR 233
#define CHRDEVYQX_NAME "chrdevYqx"
char readbuf[100];
char writebuf[100];
char kerneldata[]= "hello,I'm kenerdata";
static int chrdevYqx_open(struct inode *inode,struct file *flip){
printk("chrdevYqx is opened\r\n");
return 0;
}
static ssize_t chrdevYqx_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("send data is sucessful\r\n");
} else {
printk("send data is failed\r\n");
}
return 0;
}
static ssize_t chrdevYqx_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("receive DATA:%s\r\n",writebuf);
} else {
printk("receive DATA is failed\r\n");
}
return 0;
}
static int chrdevYqx_release(struct inode *inode,struct file *filp){
return 0;
}
static struct file_operations chrdevYqx_fops = {
.owner=THIS_MODULE,
.open=chrdevYqx_open,
.read=chrdevYqx_read,
.write=chrdevYqx_write,
.release=chrdevYqx_release,
};
static int __init chrdevYqx_init(void){
int retvalue = 0;
retvalue = register_chrdev(CHRDEVYQX_MAJOR,CHRDEVYQX_NAME,&chrdevYqx_fops);
if(retvalue<0){
printk("chrdevYqx is failed\r\n");
}
printk("chrdevYqx_init\r\n");
return 0;
}
static void __exit chrdevYqx_exit(void){
unregister_chrdev(CHRDEVYQX_MAJOR,CHRDEVYQX_NAME);
}
module_init(chrdevYqx_init);
module_exit(chrdevYqx_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("yqx");
二.新字符设备驱动开发实验
与上个字符设备驱动开发实验相比,此字符设备驱动开发可以自动向内核申请设备号,并且可以自动创建设备节点。
1.地址映射
在编写驱动之前,我们需要先简单了解一下 MMU 这个神器, MMU 全称叫做 Memory Manage Unit,也就是内存管理单元。在老版本的 Linux 中要求处理器必须有 MMU,但是现在Linux 内核已经支持无 MMU 的处理器了。 MMU 主要完成的功能如下: ①、完成虚拟空间到物理空间的映射。 ②、内存保护,设置存储器的访问权限,设置虚拟存储空间的缓冲特性。 我们重点来看一下第①点,也就是虚拟空间到物理空间的映射,也叫做地址映射。首先了解两个地址概念:虚拟地址(VA,Virtual Address)、物理地址(PA, Physcical Address)。 Linux 内核启动的时候会初始化 MMU,设置好内存映射,设置好以后 CPU访问的都是虚拟 地 址 。 这里就涉及到了物理内存和虚拟内存之间的转换,需要用到两个函数:ioremap 和 iounmap。 1.1、 ioremap 函数 ioremap 函 数 用 于 获 取 指 定 物 理 地 址 空 间 对 应 的 虚 拟 地 址 空 间 , 定 义 在arch/arm/include/asm/io.h 文件中
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
static void __iomem* SW_MUX_GPIO1_IO03;
SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
1.2.iounmap 函数 卸载驱动的时候需要使用 iounmap 函数释放掉 ioremap 函数所做的映射, iounmap 函数原型如下:
void iounmap (volatile void __iomem *addr)
iounmap(SW_MUX_GPIO1_IO03);
1.3.I/O内存访问函数
1.读操作函数
1 u8 readb(const volatile void __iomem *addr)
2 u16 readw(const volatile void __iomem *addr)
3 u32 readl(const volatile void __iomem *addr)
2.写操作函数
1 void writeb(u8 value, volatile void __iomem *addr)
2 void writew(u16 value, volatile void __iomem *addr)
3 void writel(u32 value, volatile void __iomem *addr)
2.自动创建设备节点
1.分配和注册设备号 解决这两个问题最好的方法就是要使用设备号的时候向 Linux 内核申请,需要几个就申请几个,由 Linux 内核分配设备可以使用的设备号。这个就是我们在 讲解的设备号的分配,如果没有指定设备号的话就使用如下函数来申请设备号。
如果没有指定设备号的话就使用如下函数来申请设备号:
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)
注 销 字 符 设 备 之 后 要 释 放 掉 设 备 号 , 不 管 是 通 过
alloc_chrdev_region 函 数 还 是register_chrdev_region 函数申请的设备
号,统一使用如下释放函数:
void unregister_chrdev_region(dev_t from, unsigned count)
3.cdev 在 Linux 中使用 cdev 结构体表示一个字符设备
在 cdev 中有两个重要的成员变量: ops 和 dev,这两个就是字符设备文件操作
函数集合file_operations 以及设备号 dev_t。编写字符设备驱动之前需要定义
一个 cdev 结构体变量,这个变量就表示一个字符设备,如下所示:
struct cdev test_cdev;
定义好 cdev 变量以后就要使用 cdev_init 函数对其进行初始化, cdev_init
函数原型如下:
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
cdev_add 函数用于向 Linux 系统添加字符设备(cdev 结构体变量),首先使用
cdev_init 函数完成对 cdev 结构体变量的初始化,然后使用 cdev_add 函数向
Linux 系统添加这个字符设备。
cdev_add 函数原型如下:
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
卸载驱动的时候一定要使用 cdev_del 函数从 Linux 内核中删除相应的字符设
备, cdev_del函数原型如下:
void cdev_del(struct cdev *p)
2.mdev机制 udev 是一个用户程序,在 Linux 下通过 udev 来实现设备文件的创建与删除, udev 可以检测系统中硬件设备状态,可以根据系统中硬件设备状态来创建或者删除设备文件。busybox 会创建一个 udev 的简化版本—mdev,所以在嵌入式 Linux 中我们使用mdev 来实现设备节点文件的自动创建与删除。
3.创建和删除类
创建类
struct class *class_create (struct module *owner, const char *name)
删除类:
卸载驱动程序的时候需要删除掉类,类删除函数为 class_destroy,函数原型如下:
void class_destroy(struct class *cls);
4.创建设备 上一小节创建好类以后还不能实现自动创建设备节点,我们还需要在这个类下创建一个设备。使用 device_create 函数在类下面创建设备,
device_create 函数原型如下:
struct device *device_create(struct class *class,
struct device *parent,
dev_t devt,
void *drvdata,
const char *fmt, ...)
同样的,卸载驱动的时候需要删除掉创建的设备,设备删除函数为device_destroy,函数原型如下:
void device_destroy(struct class *class, dev_t devt)
3.设置文件私有数据
4.实验代码:
1.驱动代码
#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<asm/mach/map.h>
#include<asm/io.h>
#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_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;
#define NEWCHRDEV_NAME "newchrdev"
#define NEWCHRDEV_CNT 1
#define LED_ON 1
#define LED_OFF 0
struct newchrdev_dev{
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device ;
int major;
int minor;
};
struct newchrdev_dev newchrdev;
void led_switch(u8 ledstat){
u32 val=0;
switch(ledstat){
case 1:
val = readl(GPIO1_DR);
val &= ~(1<<3);
writel(val,GPIO1_DR);
break;
case 0:
val = readl(GPIO1_DR);
val |= (1<<3);
writel(val,GPIO1_DR);
break;
}
}
static int newchrdev_open(struct inode *inode,struct file *filp){
filp->private_data = &newchrdev;
return 0;
}
static ssize_t newchrdev_read(struct file *filp,char __user *buf,size_t cnt,loff_t *offt){
return 0;
}
static ssize_t newchrdev_write(struct file *filp,const char __user *buf,size_t cnt,loff_t *offt){
int retvalue=0;
unsigned char databuf[1];
unsigned char ledsta;
retvalue = copy_from_user(databuf,buf,cnt);
if(retvalue < 0){
printk("用户写入数据失败!\r\n");
}
ledsta = databuf[0];
switch(ledsta){
case 1:
led_switch(LED_ON);
break;
case 0:
led_switch(LED_OFF);
break;
default:
printk("输入错误,请重新输入!\r\n");
break;
}
return 0;
}
static int newchrdev_release(struct inode *inode,struct file *filp){
return 0;
}
static struct file_operations newchrdev_fops = {
.owner = THIS_MODULE,
.open = newchrdev_open,
.release = newchrdev_release,
.read = newchrdev_read,
.write = newchrdev_write,
};
static int __init newchrdev_init(void){
u32 val = 0;
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);
val = readl(IMX6U_CCM_CCGR1);
val &= ~(3<<26);
val |= (3<<26);
writel(val,IMX6U_CCM_CCGR1);
writel(5,SW_MUX_GPIO1_IO03);
writel(0X10B0,SW_PAD_GPIO1_IO03);
val = readl(GPIO1_GDIR);
val &= ~(1<<3);
val |= (1<<3) ;
writel(val,GPIO1_GDIR);
val = readl(GPIO1_DR);
val |= (1<<3);
writel(val,GPIO1_DR);
if(newchrdev.major){
newchrdev.devid = MKDEV(newchrdev.major,0);
register_chrdev_region(newchrdev.devid,NEWCHRDEV_CNT,NEWCHRDEV_NAME);
} else {
alloc_chrdev_region(&newchrdev.devid,0,NEWCHRDEV_CNT,NEWCHRDEV_NAME);
newchrdev.major = MAJOR(newchrdev.devid);
newchrdev.minor = MINOR(newchrdev.minor);
}
printk("设备号成功申请:MAJOR:%d,MINOR:%d\r\n",newchrdev.major,newchrdev.minor);
cdev_init(&newchrdev.cdev,&newchrdev_fops);
cdev_add(&newchrdev.cdev,newchrdev.devid,NEWCHRDEV_CNT);
newchrdev.class = class_create(THIS_MODULE,NEWCHRDEV_NAME);
if(IS_ERR(newchrdev.class)){
return PTR_ERR(newchrdev.class);
}
newchrdev.device = device_create(newchrdev.class,NULL,newchrdev.devid,NULL,NEWCHRDEV_NAME);
if(IS_ERR(newchrdev.device)){
return PTR_ERR(newchrdev.device);
}
return 0;
}
static void __exit newchrdev_exit(void){
iounmap(IMX6U_CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
device_destroy(newchrdev.class,newchrdev.devid);
class_destroy(newchrdev.class);
cdev_del(&newchrdev.cdev);
unregister_chrdev_region(newchrdev.devid,NEWCHRDEV_CNT);
}
module_init(newchrdev_init);
module_exit(newchrdev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("yqx");
三.设备树相关知识
前面一个实验是通过直接使用MMU来操作物理地址和虚拟地址直接对寄存器进行访问,这种技术现在已经不是主流,现在大多使用设备树技术进行操作。用户将需要使用的设备的节点以及其相关信息,比如寄存器地址等信息写入设备树文件中,然后LINUX内核读取设备树文件,驱动文件在使用设备树里面保存好的设备信息,从而达到控制设备的目的,此项技术大大减少了冗余重复代码的出现。 本章通过实验来学习设备树,具体原理读者可自行翻阅其它书籍。
实验例程
1.在dts文件中加入设备节点
yqxled {
#address-cells = <1>;
#size-cells = <1>;
compatible = "yqx,yqxLed";
status = "okay";
reg = < 0X020C406C 0x04
0X020E0068 0x04
0X020E02F4 0x04
0X0209C000 0x04
0X0209C004 0x04>;
};
2.驱动程序编写
#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>
#define DEVICE_NAME "dtsled"
#define DEVICE_CNT 1
#define LED_ON 1
#define LED_OFF 0
static void __iomem* IMX6U_CCM_CCGR1;
static void __iomem* SW_MUX_GPIO1_IO03;
static void __iomem* SW_PAD_GPIO1_IO03;
static void __iomem* GPIO1_DR;
static void __iomem* GPIO1_GDIR;
typedef struct dtsled_dev{
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
int major;
int minor;
struct device_node *nd;
} dtsled_dev;
dtsled_dev dtsled;
void led_switch(int led_flag){
u32 val;
if(led_flag == 1){
val = readl(GPIO1_DR);
val &= ~(1<<3);
writel(val,GPIO1_DR);
} else {
val = readl(GPIO1_DR);
val |= (1<<3);
writel(val,GPIO1_DR);
}
}
static int led_open(struct inode *inode,struct file *filp){
filp->private_data = &dtsled;
return 0;
}
static ssize_t led_write(struct file *filp,const char __user *buf,size_t cnt,loff_t *offt){
char writebuf[1];
int retvalue;
dtsled_dev *tempdata = filp->private_data;
printk("MAJOR:%d,MINOR:%d",tempdata->major,tempdata->minor);
retvalue = copy_from_user(writebuf,buf,cnt);
if(retvalue < 0){
printk("kerneldata error!\r\n");
}
if(writebuf[0]==1){
led_switch(LED_ON);
}
if(writebuf[0]== 0){
led_switch(LED_OFF);
}
return 0;
}
static ssize_t led_read(struct file *filp,char __user* buf,size_t cnt,loff_t *offt){
return 0;
}
static int led_release(struct inode *inode,struct file *filp){
return 0;
}
static struct file_operations dtsled_fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release
};
static int __init led_init(void){
struct property *ledproperty;
const char* str;
u32 val;
u32 regdata[14];
int retvalue;
dtsled.nd = of_find_node_by_path("/yqxled");
if(dtsled.nd == NULL){
printk("节点无法找到\r\n");
return -EINVAL;
} else {
printk("成功找到节点\r\n");
}
ledproperty = of_find_property(dtsled.nd,"compatible",NULL);
if(ledproperty == NULL){
printk("设备兼容性属性无法找到\r\n");
} else {
printk("设备兼容性属性为:%s\r\n",(char *)ledproperty->value);
}
retvalue = of_property_read_string(dtsled.nd,"status",&str);
if(retvalue < 0){
printk("数据读取失败\r\n");
} else {
printk("status = %s\r\n",str);
}
retvalue = of_property_read_u32_array(dtsled.nd,"reg",regdata,10);
if(retvalue<0){
printk("读取reg属性失败\r\n");
return -EINVAL;
} else {
printk("读取reg属性成功\r\n");
}
IMX6U_CCM_CCGR1 = of_iomap(dtsled.nd,0);
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);
val = readl(IMX6U_CCM_CCGR1);
val &=~(3<<26);
val |= (3<<26);
writel(val,IMX6U_CCM_CCGR1);
writel(5,SW_MUX_GPIO1_IO03);
writel(0x10b0,SW_PAD_GPIO1_IO03);
val = readl(GPIO1_GDIR);
val &= ~(1<<3);
val |= (1<<3);
writel(val,GPIO1_GDIR);
val = readl(GPIO1_DR);
val |=(1<<3);
writel(val,GPIO1_DR);
if(dtsled.major){
dtsled.devid = MKDEV(dtsled.major,0);
register_chrdev_region(dtsled.devid,DEVICE_CNT,DEVICE_NAME);
} else {
alloc_chrdev_region(&dtsled.devid,0,DEVICE_CNT,DEVICE_NAME);
dtsled.major = MAJOR(dtsled.devid);
dtsled.minor = MINOR(dtsled.minor);
}
printk("设备号成功申请:MAJOR:%d,MINOR:%d\r\n",dtsled.major,dtsled.minor);
cdev_init(&dtsled.cdev,&dtsled_fops);
cdev_add(&dtsled.cdev,dtsled.devid,DEVICE_CNT);
dtsled.class = class_create(THIS_MODULE,DEVICE_NAME);
if(IS_ERR(dtsled.class)){
return PTR_ERR(dtsled.class);
}
dtsled.device = device_create(dtsled.class,NULL,dtsled.devid,NULL,DEVICE_NAME);
if(IS_ERR(dtsled.device)){
return PTR_ERR(dtsled.device);
}
return 0;
}
static void __exit led_exit(void){
u32 val;
val = readl(GPIO1_DR);
val |=(1<<3);
writel(val,GPIO1_DR);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(IMX6U_CCM_CCGR1);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_GDIR);
iounmap(GPIO1_DR);
device_destroy(dtsled.class,dtsled.devid);
class_destroy(dtsled.class);
cdev_del(&dtsled.cdev);
unregister_chrdev_region(dtsled.devid,DEVICE_CNT);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("yqx");
四.pinctrl 和 gpio 子系统实验
上一章我们编写了基于设备树的 LED 驱动,但是驱动的本质还是没变,都是配置 LED 灯所使用的 GPIO 寄存器,驱动开发方式和裸机基本没啥区别。 Linux 是一个庞大而完善的系统,尤其是驱动框架,像 GPIO 这种最基本的驱动不可能采用“原始”的裸机驱动开发方式,否则就相当于你买了一辆车,结果每天推着车去上班。 Linux 内核提供了 pinctrl 和 gpio 子系统用于GPIO 驱动,本章我们就来学习一下如何借助 pinctrl 和 gpio 子系统来简化 GPIO 驱动开发。 其本质是使用NXP中为我们写好的linux内核中的GPIO框架来操控GPIO端口
1.pinctrl子系统
大多数 SOC 的 pin 都是支持复用的,比如 I.MX6ULL 的 GPIO1_IO03 既可以作为普通的GPIO 使用,也可以作为 I2C1 的 SDA 等等。此外我们还需要置 pin 的电气特性,比如上/下拉、速度、驱动能力等等。传统的配置 pin 的方式就是直接操作相应的寄存器,但是这种配置方式比较繁琐、而且容易出问题(比如 pin 功能冲突)。 pinctrl 子系统就是为了解决这个问题而引入的, pinctrl子系统主要工作内容如下: ①、获取设备树中 pin 信息。 ②、根据获取到的 pin 信息来设置 pin 的复用功能 ③、根据获取到的 pin 信息来设置 pin 的电气特性,比如上/下拉、速度、驱动能力等。 对于我们使用者来讲,只需要在设备树里面设置好某个 pin 的相关属性即可,其他的初始化工作均由 pinctrl 子系统来完成, pinctrl 子系统源码目录为drivers/pinctrl。
2.gpio子系统
上一小节讲解了 pinctrl 子系统, pinctrl 子系统重点是设置 PIN(有的 SOC 叫做 PAD)的复用和电气属性,如果 pinctrl 子系统将一个 PIN 复用为 GPIO 的话,那么接下来就要用到 gpio 子系统了。 gpio 子系统顾名思义,就是用于初始化 GPIO 并且提供相应的 API 函数,比如设置 GPIO为输入输出,读取GPIO 的值等。 gpio 子系统的主要目的就是方便驱动开发者使用 gpio,驱动开发者在设备树中添加 gpio 相关信息,然后就可以在驱动程序中使用 gpio 子系统提供的 API函数来操作 GPIO, Linux 内核向驱动开发者屏蔽掉了 GPIO的设置过程,极大的方便了驱动开发者使用 GPIO。
1.gpio子系统API函数:
1、 gpio_request 函数
gpio_request 函数用于申请一个 GPIO 管脚,在使用一个 GPIO 之前一定要使
用 gpio_request进行申请,函数原型如下:
int gpio_request(unsigned gpio, const char *label)
2、 gpio_free 函数
如果不使用某个 GPIO 了,那么就可以调用 gpio_free 函数进行释放。函数原型如下:
void gpio_free(unsigned gpio)
3、 gpio_direction_input 函数
int gpio_direction_input(unsigned gpio)
4、 gpio_direction_output 函数
int gpio_direction_output(unsigned gpio, int value)
5、 gpio_get_value 函数
#define gpio_get_value __gpio_get_value
int __gpio_get_value(unsigned gpio)
6、 gpio_set_value 函数
#define gpio_set_value __gpio_set_value
void __gpio_set_value(unsigned gpio, int value)
2.与gpio子系统相关的of函数
1、 of_gpio_named_count 函数
of_gpio_named_count 函数用于获取设备树某个属性里面定义了几个 GPIO 信
息,要注意的是空的 GPIO 信息也会被统计到
int of_gpio_named_count(struct device_node *np, const char *propname)
2、 of_gpio_count 函数
和 of_gpio_named_count 函数一样,但是不同的地方在于,此函数统计的是“gpios”这个属性的 GPIO 数量,而 of_gpio_named_count 函数可以统计任意属性的 GPIO 信息,函数原型如下所示:
int of_gpio_count(struct device_node *np)
3、 of_get_named_gpio 函数
此函数获取 GPIO 编号,因为 Linux 内核中关于 GPIO 的 API 函数都要使用
GPIO 编号,此函数会将设备树中类似<&gpio5 7 GPIO_ACTIVE_LOW>的属性信息
转换为对应的 GPIO 编号,此函数在驱动中使用很频繁!函数原型如下:
int of_get_named_gpio(struct device_node *np,const char *propname,int index)
3.实验编写
1.设备树:
yqxgpioled {
#address-cells = <1>;
#size-cells = <1>;
compatible = "yqx,yqxgpioled";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_yqxgpioled>;
led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
status = "okay";
};
2.驱动程序编写
#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 <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define DEV_NAME "yqxgpioled"
#define DEV_CNT 1
#define LED_ON 1
#define LED_OFF 0
typedef struct gpioled{
dev_t devid;
struct cdev cdev;
struct class* class;
struct device* device;
int major;
int minor;
struct device_node *nd;
int led_gpio;
} gpioled;
gpioled yqxgpioled;
static int led_open(struct inode *inode,struct file *filp){
filp->private_data = &yqxgpioled;
return 0;
}
static int led_close(struct inode *inode,struct file *filp){
return 0;
}
static ssize_t led_read(struct file *filp,char __user *buf,size_t cnt,loff_t *offt){
return 0;
}
static ssize_t led_write(struct file *filp,const char __user *buf,size_t cnt,loff_t *offt){
gpioled *yqx_dev = filp->private_data;
unsigned char databuf[1];
unsigned char ledstat;
int retvalue;
retvalue = copy_from_user(databuf,buf,cnt);
if(retvalue < 0){
printk("访问内核失败\r\n");
return -EINVAL;
}
ledstat = databuf[0];
if(ledstat == LED_ON){
gpio_set_value(yqx_dev->led_gpio,0);
}else {
gpio_set_value(yqx_dev->led_gpio,1);
}
return 0;
}
static struct file_operations gpioled_fops = {
.owner = THIS_MODULE,
.open = led_open,
.release = led_close,
.write = led_write,
.read = led_read,
};
static int __init led_init(void){
int ret = 0;
yqxgpioled.nd = of_find_node_by_path("/yqxgpioled");
if(yqxgpioled.nd == NULL){
printk("访问gpio节点失败\r\n");
return -EINVAL;
} else {
printk("gpio设备节点已找到\r\n");
}
yqxgpioled.led_gpio = of_get_named_gpio(yqxgpioled.nd,"led-gpio",0);
if(yqxgpioled.led_gpio< 0){
printk("获取gpio编号失败\r\n");
return -EINVAL;
}
printk("gpio设备编号为:%d\r\n",yqxgpioled.led_gpio);
ret = gpio_direction_output(yqxgpioled.led_gpio,1);
if(ret < 0){
printk("gpio1_3设置IO输出失败\r\n");
}
if(yqxgpioled.major){
yqxgpioled.devid=MKDEV(yqxgpioled.major,0);
register_chrdev_region(yqxgpioled.devid,DEV_CNT,DEV_NAME);
} else {
alloc_chrdev_region(&yqxgpioled.devid,0,DEV_CNT,DEV_NAME);
yqxgpioled.major = MAJOR(yqxgpioled.devid);
yqxgpioled.minor = MINOR(yqxgpioled.devid);
}
printk("major:%d,minor:%d\r\n",yqxgpioled.major,yqxgpioled.minor);
cdev_init(&yqxgpioled.cdev,&gpioled_fops);
cdev_add(&yqxgpioled.cdev,yqxgpioled.devid,DEV_CNT);
yqxgpioled.class = class_create(THIS_MODULE,DEV_NAME);
if(IS_ERR(yqxgpioled.class)){
printk("创建类失败\r\n");
return PTR_ERR(yqxgpioled.class);
}
yqxgpioled.device = device_create(yqxgpioled.class,NULL,yqxgpioled.devid,NULL,DEV_NAME);
if(IS_ERR(yqxgpioled.device)){
printk("创建设备节点失败");
return PTR_ERR(yqxgpioled.device);
}
return 0;
}
static void __exit led_exit(void){
gpio_set_value(yqxgpioled.led_gpio,1);
gpio_free(yqxgpioled.led_gpio);
device_destroy(yqxgpioled.class,yqxgpioled.devid);
class_destroy(yqxgpioled.class);
cdev_del(&yqxgpioled.cdev);
unregister_chrdev_region(yqxgpioled.devid,1);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("YQX");
五.Linux并发与竞争实验
Linux是一个多任务操作系统,肯定会存在多个任务共同操作同一段内存或者设备的情况,多个任务甚至中断都能访问的资源叫做共享资源,就和共享单车一样。在驱动开发中要注意对共享资源的保护,也就是要处理对共享资源的并发访问。比如共享单车,大家按照谁扫谁骑走的原则来共用这个单车,如果没有这个并发访问共享单车的原则存在,只怕到时候为了一辆单车要打起来了。在 Linux 驱动编写过程中对于并发控制的管理非常重要,本章我们就来学习一下如何在 Linux 驱动中处理并发。
1.原子操作
首先看一下原子操作,原子操作就是指不能再进一步分割的操作,一般原子操作用于变量或者位操作。假如现在要对无符号整形变量 a 赋值,值为 3,对于 C 语言来讲很简单。 实验程序:
1.驱动程序
#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 <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define DEV_NAME "yqxgpioled"
#define DEV_CNT 1
#define LED_ON 1
#define LED_OFF 0
typedef struct gpioled{
dev_t devid;
struct cdev cdev;
struct class* class;
struct device* device;
int major;
int minor;
struct device_node *nd;
int led_gpio;
atomic_t flag;
} gpioled;
gpioled yqxgpioled;
static int led_open(struct inode *inode,struct file *filp){
filp->private_data = &yqxgpioled;
if(!atomic_dec_and_test(&yqxgpioled.flag)){
atomic_inc(&yqxgpioled.flag);
printk("设备正在备占用,请稍候再使用\r\n");
return -EBUSY;
}
return 0;
}
static int led_close(struct inode *inode,struct file *filp){
gpioled *yqx_dev = filp->private_data;
atomic_set(&yqx_dev->flag,1);
return 0;
}
static ssize_t led_read(struct file *filp,char __user *buf,size_t cnt,loff_t *offt){
return 0;
}
static ssize_t led_write(struct file *filp,const char __user *buf,size_t cnt,loff_t *offt){
gpioled *yqx_dev = filp->private_data;
unsigned char databuf[1];
unsigned char ledstat;
int retvalue;
retvalue = copy_from_user(databuf,buf,cnt);
if(retvalue < 0){
printk("访问内核失败\r\n");
return -EINVAL;
}
ledstat = databuf[0];
if(ledstat == LED_ON){
gpio_set_value(yqx_dev->led_gpio,0);
}else {
gpio_set_value(yqx_dev->led_gpio,1);
}
return 0;
}
static struct file_operations gpioled_fops = {
.owner = THIS_MODULE,
.open = led_open,
.release = led_close,
.write = led_write,
.read = led_read,
};
static int __init led_init(void){
int ret = 0;
atomic_set(&yqxgpioled.flag,1);
yqxgpioled.nd = of_find_node_by_path("/yqxgpioled");
if(yqxgpioled.nd == NULL){
printk("访问gpio节点失败\r\n");
return -EINVAL;
} else {
printk("gpio设备节点已找到\r\n");
}
yqxgpioled.led_gpio = of_get_named_gpio(yqxgpioled.nd,"led-gpio",0);
if(yqxgpioled.led_gpio< 0){
printk("获取gpio编号失败\r\n");
return -EINVAL;
}
printk("gpio设备编号为:%d\r\n",yqxgpioled.led_gpio);
ret = gpio_direction_output(yqxgpioled.led_gpio,1);
if(ret < 0){
printk("gpio1_3设置IO输出失败\r\n");
}
if(yqxgpioled.major){
yqxgpioled.devid=MKDEV(yqxgpioled.major,0);
register_chrdev_region(yqxgpioled.devid,DEV_CNT,DEV_NAME);
} else {
alloc_chrdev_region(&yqxgpioled.devid,0,DEV_CNT,DEV_NAME);
yqxgpioled.major = MAJOR(yqxgpioled.devid);
yqxgpioled.minor = MINOR(yqxgpioled.devid);
}
printk("major:%d,minor:%d\r\n",yqxgpioled.major,yqxgpioled.minor);
cdev_init(&yqxgpioled.cdev,&gpioled_fops);
cdev_add(&yqxgpioled.cdev,yqxgpioled.devid,DEV_CNT);
yqxgpioled.class = class_create(THIS_MODULE,DEV_NAME);
if(IS_ERR(yqxgpioled.class)){
printk("创建类失败\r\n");
return PTR_ERR(yqxgpioled.class);
}
yqxgpioled.device = device_create(yqxgpioled.class,NULL,yqxgpioled.devid,NULL,DEV_NAME);
if(IS_ERR(yqxgpioled.device)){
printk("创建设备节点失败");
return PTR_ERR(yqxgpioled.device);
}
return 0;
}
static void __exit led_exit(void){
gpio_set_value(yqxgpioled.led_gpio,1);
gpio_free(yqxgpioled.led_gpio);
device_destroy(yqxgpioled.class,yqxgpioled.devid);
class_destroy(yqxgpioled.class);
cdev_del(&yqxgpioled.cdev);
unregister_chrdev_region(yqxgpioled.devid,1);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("YQX");
2.应用程序
include<stdio.h>
#include "unistd.h"
#include "sys/types.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#define LED_ON 1
#define LED_OFF 0
int main(int argc,char * argv[]){
int fd,retvalue;
unsigned char databuf[1];
char *filename;
int time_cnt=5;
retvalue = -1;
if(argc != 3){
printf("输入参数错误,请重新输入\r\n");
return -1;
}
filename = argv[1];
databuf[0] = atoi(argv[2]);
fd = open(filename,O_RDWR);
if(fd < 0){
printf("打开设备文件失败\r\n");
return -1;
}
if(databuf[0] == LED_ON)
retvalue = write(fd, databuf,sizeof(databuf));
else if(databuf[0] == LED_OFF)
retvalue = write(fd, databuf,sizeof(databuf));
else
printf("输入非法,请重新输入\r\n");
if(retvalue < 0){
printf("向内核写入数据失败\r\n");
close(fd);
return -1;
}
while(1){
if(time_cnt<=0){
break;
}
printf("APP runing time:%d\r\n",time_cnt);
sleep(5);
time_cnt--;
}
retvalue = close(fd);
if(retvalue < 0){
printf("关闭文件失败\r\n");
return -1;
}
return 0;
}
2.自旋锁
原子操作只能对整形变量或者位进行保护,但是,在实际的使用环境中怎么可能只有整形变量或位这么简单的临界区。举个最简单的例子,设备结构体变量就不是整型变量,我们对于结构体中成员变量的操作也要保证原子性,在线程 A 对结构体变量使用期间,应该禁止其他的线程来访问此结构体变量,这些工作原子操作都不能胜任,需要本节要讲的锁机制,在 Linux内核中就是自旋锁。 自旋锁的“自旋”也就是“原地打转”的意思,“原地打转”的目的是为了等待自旋锁可以用,可以访问共享资源。把自旋锁比作一个变量 a,变量 a=1 的时候表示共享资源可用,当 a=0的时候表示共享资源不可用。现在线程 A 要访问共享资源,发现 a=0(自旋锁被其他线程持有),那么线程 A 就会不断的查询 a 的值,直到 a=1。从这里我们可以看到自旋锁的一个缺点:那就等待自旋锁的线程会一直处于自旋状态,这样会浪费处理器时间,降低系统性能,所以自旋锁的持有时间不能太长。所以自旋锁适用于短时期的轻量级加锁,如果遇到需要长时间持有锁的场景那就需要换其他的方法了,这个我们后面会讲解。 自旋锁API函数适用于SMP或支持抢占的单CPU下线程之间的并发访问, 也就是用于线程与线程之间,被自旋锁保护的临界区一定不能调用任何能够引起睡眠和阻塞的API 函数,否则的话会可能会导致死锁现象的发生。自旋锁会自动禁止抢占,也就说当线程 A得到锁以后会暂时禁止内核抢占。如果线程 A 在持有锁期间进入了休眠状态,那么线程 A 会自动放弃 CPU 使用权。线程 B 开始运行,线程 B 也想要获取锁,但是此时锁被 A 线程持有,而且内核抢占还被禁止了!线程 B 无法被调度出去,那么线程 A 就无法运行,锁也就无法释放,好了,死锁发生了! 如果此时中断也要插一脚,中断也想访问共享资源,那该怎么办呢?首先可以肯定的是,中断里面可以使用自旋锁,但是在中断里面使用自旋锁的时候,在获取锁之前一定要先禁止本地中断(也就是本 CPU 中断,对于多核 SOC来说会有多个 CPU 核),否则可能导致锁死现象的发生。
实验程序:
1.驱动代码:
#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 <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define DEV_NAME "yqxgpioled"
#define DEV_CNT 1
#define LED_ON 1
#define LED_OFF 0
typedef struct gpioled{
dev_t devid;
struct cdev cdev;
struct class* class;
struct device* device;
int major;
int minor;
struct device_node *nd;
int led_gpio;
int dev_flag;
spinlock_t lock;
} gpioled;
gpioled yqxgpioled;
static int led_open(struct inode *inode,struct file *filp){
unsigned long irq_flags;
filp->private_data = &yqxgpioled;
spin_lock_irqsave(&yqxgpioled.lock,irq_flags);
if(yqxgpioled.dev_flag){
printk("设备正忙碌\r\n");
spin_unlock_irqrestore(&yqxgpioled.lock,irq_flags);
return -EBUSY;
}
yqxgpioled.dev_flag++;
spin_unlock_irqrestore(&yqxgpioled.lock,irq_flags);
return 0;
}
static int led_close(struct inode *inode,struct file *filp){
unsigned long irq_flags;
gpioled *yqx_dev = filp->private_data;
spin_lock_irqsave(&yqxgpioled.lock,irq_flags);
yqx_dev->dev_flag = 0;
spin_unlock_irqrestore(&yqxgpioled.lock,irq_flags);
return 0;
}
static ssize_t led_read(struct file *filp,char __user *buf,size_t cnt,loff_t *offt){
return 0;
}
static ssize_t led_write(struct file *filp,const char __user *buf,size_t cnt,loff_t *offt){
gpioled *yqx_dev = filp->private_data;
unsigned char databuf[1];
unsigned char ledstat;
int retvalue;
retvalue = copy_from_user(databuf,buf,cnt);
if(retvalue < 0){
printk("访问内核失败\r\n");
return -EINVAL;
}
ledstat = databuf[0];
if(ledstat == LED_ON){
gpio_set_value(yqx_dev->led_gpio,0);
}else {
gpio_set_value(yqx_dev->led_gpio,1);
}
return 0;
}
static struct file_operations gpioled_fops = {
.owner = THIS_MODULE,
.open = led_open,
.release = led_close,
.write = led_write,
.read = led_read,
};
static int __init led_init(void){
int ret = 0;
yqxgpioled.dev_flag = 0;
spin_lock_init(&yqxgpioled.lock);
yqxgpioled.nd = of_find_node_by_path("/yqxgpioled");
if(yqxgpioled.nd == NULL){
printk("访问gpio节点失败\r\n");
return -EINVAL;
} else {
printk("gpio设备节点已找到\r\n");
}
yqxgpioled.led_gpio = of_get_named_gpio(yqxgpioled.nd,"led-gpio",0);
if(yqxgpioled.led_gpio< 0){
printk("获取gpio编号失败\r\n");
return -EINVAL;
}
printk("gpio设备编号为:%d\r\n",yqxgpioled.led_gpio);
ret = gpio_direction_output(yqxgpioled.led_gpio,1);
if(ret < 0){
printk("gpio1_3设置IO输出失败\r\n");
}
if(yqxgpioled.major){
yqxgpioled.devid=MKDEV(yqxgpioled.major,0);
register_chrdev_region(yqxgpioled.devid,DEV_CNT,DEV_NAME);
} else {
alloc_chrdev_region(&yqxgpioled.devid,0,DEV_CNT,DEV_NAME);
yqxgpioled.major = MAJOR(yqxgpioled.devid);
yqxgpioled.minor = MINOR(yqxgpioled.devid);
}
printk("major:%d,minor:%d\r\n",yqxgpioled.major,yqxgpioled.minor);
cdev_init(&yqxgpioled.cdev,&gpioled_fops);
cdev_add(&yqxgpioled.cdev,yqxgpioled.devid,DEV_CNT);
yqxgpioled.class = class_create(THIS_MODULE,DEV_NAME);
if(IS_ERR(yqxgpioled.class)){
printk("创建类失败\r\n");
return PTR_ERR(yqxgpioled.class);
}
yqxgpioled.device = device_create(yqxgpioled.class,NULL,yqxgpioled.devid,NULL,DEV_NAME);
if(IS_ERR(yqxgpioled.device)){
printk("创建设备节点失败");
return PTR_ERR(yqxgpioled.device);
}
return 0;
}
static void __exit led_exit(void){
gpio_set_value(yqxgpioled.led_gpio,1);
gpio_free(yqxgpioled.led_gpio);
device_destroy(yqxgpioled.class,yqxgpioled.devid);
class_destroy(yqxgpioled.class);
cdev_del(&yqxgpioled.cdev);
unregister_chrdev_region(yqxgpioled.devid,1);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("YQX");
2.应用程序:
#include<stdio.h>
#include "unistd.h"
#include "sys/types.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#define LED_ON 1
#define LED_OFF 0
int main(int argc,char * argv[]){
int fd,retvalue;
unsigned char databuf[1];
char *filename;
int time_cnt=5;
retvalue = -1;
if(argc != 3){
printf("输入参数错误,请重新输入\r\n");
return -1;
}
filename = argv[1];
databuf[0] = atoi(argv[2]);
fd = open(filename,O_RDWR);
if(fd < 0){
printf("打开设备文件失败\r\n");
return -1;
}
if(databuf[0] == LED_ON)
retvalue = write(fd, databuf,sizeof(databuf));
else if(databuf[0] == LED_OFF)
retvalue = write(fd, databuf,sizeof(databuf));
else
printf("输入非法,请重新输入\r\n");
if(retvalue < 0){
printf("向内核写入数据失败\r\n");
close(fd);
return -1;
}
while(1){
if(time_cnt<=0){
break;
}
printf("APP runing time:%d\r\n",time_cnt);
sleep(5);
time_cnt--;
}
retvalue = close(fd);
if(retvalue < 0){
printf("关闭文件失败\r\n");
return -1;
}
return 0;
}
3.信号量
大家如果有学习过 FreeRTOS 或者 UCOS 的话就应该对信号量很熟悉,因为信号量是同步的一种方式。 Linux 内核也提供了信号量机制,信号量常常用于控制对共享资源的访问。举一个很常见的例子,某个停车场有 100 个停车位,这 100 个停车位大家都可以用,对于大家来说这100 个停车位就是共享资源。假设现在这个停车场正常运行,你要把车停到这个这个停车场肯定要先看一下现在停了多少车了?还有没有停车位?当前停车数量就是一个信号量,具体的停车数量就是这个信号量值,当这个值到 100 的时候说明停车满了。停车场满的时你可以等一会看看有没有其他的车开出停车场,当有车开出停车场的时候停车数量就会减一,也就是说信号量减一,此时你就可以把车停进去了,你把车停进去以后停车数量就会加一,也就是信号量加一。这就是一个典型的使用信号量进行共享资源管理的案例,在这个案例中使用的就是计数型信号量。 相比于自旋锁,信号量可以使线程进入休眠状态,比如 A 与 B、 C 合租了一套房子,这个房子只有一个厕所,一次只能一个人使用。某一天早上 A 去上厕所了,过了一会 B 也想用厕所,因为 A 在厕所里面,所以 B 只能等到 A 用来了才能进去。 B 要么就一直在厕所门口等着,等 A 出来,这个时候就相当于自旋锁。 B 也可以告诉 A,让 A 出来以后通知他一下,然后 B 继续回房间睡觉,这个时候相当于信号量。可以看出,使用信号量会提高处理器的使用效率,毕竟不用一直傻乎乎的在那里“自旋”等待。但是,信号量的开销要比自旋锁大,因为信号量使线程进入休眠状态以后会切换线程,切换线程就会有开销。总结一下信号量的特点: ①、因为信号量可以使等待资源线程进入休眠状态,因此适用于那些占用资源比较久的场合。 ②、因此信号量不能用于中断中,因为信号量会引起休眠,中断不能休眠。 ③、如果共享资源的持有时间比较短,那就不适合使用信号量了,因为频繁的休眠、切换线程引起的开销要远大于信号量带来的那点优势。
信号量有一个信号量值,相当于一个房子有 10 把钥匙,这 10 把钥匙就相当于信号量值为10。因此,可以通过信号量来控制访问共享资源的访问数量,如果要想进房间,那就要先获取一把钥匙,信号量值减 1,直到 10 把钥匙都被拿走,信号量值为 0,这个时候就不允许任何人进入房间了,因为没钥匙了。如果有人从房间出来,那他要归还他所持有的那把钥匙,信号量值加 1,此时有 1 把钥匙了,那么可以允许进去一个人。相当于通过信号量控制访问资源的线程数,在初始化的时候将信号量值设置的大于 1,那么这个信号量就是计数型信号量,计数型信号量不能用于互斥访问,因为它允许多个线程同时访问共享资源。如果要互斥的访问共享资源那么信号量的值就不能大于 1,此时的信号量就是一个二值信号量。
实验程序
1.驱动程序:
#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 <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/semaphore.h>
#define DEV_NAME "yqxgpioled"
#define DEV_CNT 1
#define LED_ON 1
#define LED_OFF 0
typedef struct gpioled{
dev_t devid;
struct cdev cdev;
struct class* class;
struct device* device;
int major;
int minor;
struct device_node *nd;
int led_gpio;
int sem_flag;
struct semaphore sem;
} gpioled;
gpioled yqxgpioled;
static int led_open(struct inode *inode,struct file *filp){
filp->private_data = &yqxgpioled;
if(down_interruptible(&yqxgpioled.sem)){
printk("设备正忙碌信号量\r\n");
return -EBUSY;
}
yqxgpioled.sem_flag++;
printk("当前已获取信号量数为%d\r\n",yqxgpioled.sem_flag);
return 0;
}
static int led_close(struct inode *inode,struct file *filp){
gpioled *yqx_dev = filp->private_data;
up(&yqx_dev->sem);
printk("释放一个信号量\r\n");
yqx_dev->sem_flag--;
return 0;
}
static ssize_t led_read(struct file *filp,char __user *buf,size_t cnt,loff_t *offt){
return 0;
}
static ssize_t led_write(struct file *filp,const char __user *buf,size_t cnt,loff_t *offt){
gpioled *yqx_dev = filp->private_data;
unsigned char databuf[1];
unsigned char ledstat;
int retvalue;
retvalue = copy_from_user(databuf,buf,cnt);
if(retvalue < 0){
printk("访问内核失败\r\n");
return -EINVAL;
}
ledstat = databuf[0];
if(ledstat == LED_ON){
gpio_set_value(yqx_dev->led_gpio,0);
}else {
gpio_set_value(yqx_dev->led_gpio,1);
}
return 0;
}
static struct file_operations gpioled_fops = {
.owner = THIS_MODULE,
.open = led_open,
.release = led_close,
.write = led_write,
.read = led_read,
};
static int __init led_init(void){
int ret = 0;
sema_init(&yqxgpioled.sem,2);
yqxgpioled.nd = of_find_node_by_path("/yqxgpioled");
if(yqxgpioled.nd == NULL){
printk("访问gpio节点失败\r\n");
return -EINVAL;
} else {
printk("gpio设备节点已找到\r\n");
}
yqxgpioled.led_gpio = of_get_named_gpio(yqxgpioled.nd,"led-gpio",0);
if(yqxgpioled.led_gpio< 0){
printk("获取gpio编号失败\r\n");
return -EINVAL;
}
printk("gpio设备编号为:%d\r\n",yqxgpioled.led_gpio);
ret = gpio_direction_output(yqxgpioled.led_gpio,1);
if(ret < 0){
printk("gpio1_3设置IO输出失败\r\n");
}
if(yqxgpioled.major){
yqxgpioled.devid=MKDEV(yqxgpioled.major,0);
register_chrdev_region(yqxgpioled.devid,DEV_CNT,DEV_NAME);
} else {
alloc_chrdev_region(&yqxgpioled.devid,0,DEV_CNT,DEV_NAME);
yqxgpioled.major = MAJOR(yqxgpioled.devid);
yqxgpioled.minor = MINOR(yqxgpioled.devid);
}
printk("major:%d,minor:%d\r\n",yqxgpioled.major,yqxgpioled.minor);
cdev_init(&yqxgpioled.cdev,&gpioled_fops);
cdev_add(&yqxgpioled.cdev,yqxgpioled.devid,DEV_CNT);
yqxgpioled.class = class_create(THIS_MODULE,DEV_NAME);
if(IS_ERR(yqxgpioled.class)){
printk("创建类失败\r\n");
return PTR_ERR(yqxgpioled.class);
}
yqxgpioled.device = device_create(yqxgpioled.class,NULL,yqxgpioled.devid,NULL,DEV_NAME);
if(IS_ERR(yqxgpioled.device)){
printk("创建设备节点失败");
return PTR_ERR(yqxgpioled.device);
}
return 0;
}
static void __exit led_exit(void){
gpio_set_value(yqxgpioled.led_gpio,1);
gpio_free(yqxgpioled.led_gpio);
device_destroy(yqxgpioled.class,yqxgpioled.devid);
class_destroy(yqxgpioled.class);
cdev_del(&yqxgpioled.cdev);
unregister_chrdev_region(yqxgpioled.devid,1);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("YQX");
2.应用程序
include<stdio.h>
#include "unistd.h"
#include "sys/types.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#define LED_ON 1
#define LED_OFF 0
int main(int argc,char * argv[]){
int fd,retvalue;
unsigned char databuf[1];
char *filename;
int time_cnt=5;
retvalue = -1;
if(argc != 3){
printf("输入参数错误,请重新输入\r\n");
return -1;
}
filename = argv[1];
databuf[0] = atoi(argv[2]);
fd = open(filename,O_RDWR);
if(fd < 0){
printf("打开设备文件失败\r\n");
return -1;
}
if(databuf[0] == LED_ON)
retvalue = write(fd, databuf,sizeof(databuf));
else if(databuf[0] == LED_OFF)
retvalue = write(fd, databuf,sizeof(databuf));
else
printf("输入非法,请重新输入\r\n");
if(retvalue < 0){
printf("向内核写入数据失败\r\n");
close(fd);
return -1;
}
while(1){
if(time_cnt<=0){
break;
}
printf("APP runing time:%d\r\n",time_cnt);
sleep(5);
time_cnt--;
}
retvalue = close(fd);
if(retvalue < 0){
printf("关闭文件失败\r\n");
return -1;
}
return 0;
}
4.互斥体
在 FreeRTOS 和 UCOS 中也有互斥体,将信号量的值设置为 1 就可以使用信号量进行互斥访问了,虽然可以通过信号量实现互斥,但是 Linux 提供了一个比信号量更专业的机制来进行互斥,它就是互斥体—mutex。互斥访问表示一次只有一个线程可以访问共享资源,不能递归申请互斥体。在我们编写 Linux 驱动的时候遇到需要互斥访问的地方建议使用 mutex。
实验程序
1.驱动程序
#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 <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/semaphore.h>
#define DEV_NAME "yqxgpioled"
#define DEV_CNT 1
#define LED_ON 1
#define LED_OFF 0
typedef struct gpioled{
dev_t devid;
struct cdev cdev;
struct class* class;
struct device* device;
int major;
int minor;
struct device_node *nd;
int led_gpio;
int sem_flag;
struct mutex mutexyqx;
} gpioled;
gpioled yqxgpioled;
static int led_open(struct inode *inode,struct file *filp){
filp->private_data = &yqxgpioled;
if(mutex_lock_interruptible(&yqxgpioled.mutexyqx)){
printk("设备正忙碌信号量\r\n");
return -EBUSY;
}
yqxgpioled.sem_flag++;
printk("当前已获取信号量数为%d\r\n",yqxgpioled.sem_flag);
return 0;
}
static int led_close(struct inode *inode,struct file *filp){
gpioled *yqx_dev = filp->private_data;
mutex_unlock(&yqx_dev->mutexyqx);
printk("释放一个信号量\r\n");
yqx_dev->sem_flag--;
return 0;
}
static ssize_t led_read(struct file *filp,char __user *buf,size_t cnt,loff_t *offt){
return 0;
}
static ssize_t led_write(struct file *filp,const char __user *buf,size_t cnt,loff_t *offt){
gpioled *yqx_dev = filp->private_data;
unsigned char databuf[1];
unsigned char ledstat;
int retvalue;
retvalue = copy_from_user(databuf,buf,cnt);
if(retvalue < 0){
printk("访问内核失败\r\n");
return -EINVAL;
}
ledstat = databuf[0];
if(ledstat == LED_ON){
gpio_set_value(yqx_dev->led_gpio,0);
}else {
gpio_set_value(yqx_dev->led_gpio,1);
}
return 0;
}
static struct file_operations gpioled_fops = {
.owner = THIS_MODULE,
.open = led_open,
.release = led_close,
.write = led_write,
.read = led_read,
};
static int __init led_init(void){
int ret = 0;
mutex_init(&yqxgpioled.mutexyqx);
yqxgpioled.nd = of_find_node_by_path("/yqxgpioled");
if(yqxgpioled.nd == NULL){
printk("访问gpio节点失败\r\n");
return -EINVAL;
} else {
printk("gpio设备节点已找到\r\n");
}
yqxgpioled.led_gpio = of_get_named_gpio(yqxgpioled.nd,"led-gpio",0);
if(yqxgpioled.led_gpio< 0){
printk("获取gpio编号失败\r\n");
return -EINVAL;
}
printk("gpio设备编号为:%d\r\n",yqxgpioled.led_gpio);
ret = gpio_direction_output(yqxgpioled.led_gpio,1);
if(ret < 0){
printk("gpio1_3设置IO输出失败\r\n");
}
if(yqxgpioled.major){
yqxgpioled.devid=MKDEV(yqxgpioled.major,0);
register_chrdev_region(yqxgpioled.devid,DEV_CNT,DEV_NAME);
} else {
alloc_chrdev_region(&yqxgpioled.devid,0,DEV_CNT,DEV_NAME);
yqxgpioled.major = MAJOR(yqxgpioled.devid);
yqxgpioled.minor = MINOR(yqxgpioled.devid);
}
printk("major:%d,minor:%d\r\n",yqxgpioled.major,yqxgpioled.minor);
cdev_init(&yqxgpioled.cdev,&gpioled_fops);
cdev_add(&yqxgpioled.cdev,yqxgpioled.devid,DEV_CNT);
yqxgpioled.class = class_create(THIS_MODULE,DEV_NAME);
if(IS_ERR(yqxgpioled.class)){
printk("创建类失败\r\n");
return PTR_ERR(yqxgpioled.class);
}
yqxgpioled.device = device_create(yqxgpioled.class,NULL,yqxgpioled.devid,NULL,DEV_NAME);
if(IS_ERR(yqxgpioled.device)){
printk("创建设备节点失败");
return PTR_ERR(yqxgpioled.device);
}
return 0;
}
static void __exit led_exit(void){
gpio_set_value(yqxgpioled.led_gpio,1);
gpio_free(yqxgpioled.led_gpio);
device_destroy(yqxgpioled.class,yqxgpioled.devid);
class_destroy(yqxgpioled.class);
cdev_del(&yqxgpioled.cdev);
unregister_chrdev_region(yqxgpioled.devid,1);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("YQX");
2.应用程序
include<stdio.h>
#include "unistd.h"
#include "sys/types.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#define LED_ON 1
#define LED_OFF 0
int main(int argc,char * argv[]){
int fd,retvalue;
unsigned char databuf[1];
char *filename;
int time_cnt=5;
retvalue = -1;
if(argc != 3){
printf("输入参数错误,请重新输入\r\n");
return -1;
}
filename = argv[1];
databuf[0] = atoi(argv[2]);
fd = open(filename,O_RDWR);
if(fd < 0){
printf("打开设备文件失败\r\n");
return -1;
}
if(databuf[0] == LED_ON)
retvalue = write(fd, databuf,sizeof(databuf));
else if(databuf[0] == LED_OFF)
retvalue = write(fd, databuf,sizeof(databuf));
else
printf("输入非法,请重新输入\r\n");
if(retvalue < 0){
printf("向内核写入数据失败\r\n");
close(fd);
return -1;
}
while(1){
if(time_cnt<=0){
break;
}
printf("APP runing time:%d\r\n",time_cnt);
sleep(5);
time_cnt--;
}
retvalue = close(fd);
if(retvalue < 0){
printf("关闭文件失败\r\n");
return -1;
}
return 0;
}
六.Linux内核定时器实验
时器是我们最常用到的功能,一般用来完成定时功能,本章我们就来学习一下 Linux 内核提供的定时器 API 函数,通过这些定时器 API 函数我们可以完成很多要求定时的应用。 Linux内核也提供了短延时函数,比如微秒、纳秒、毫秒延时函数,本章我们就来学习一下这些和时间有关的功能。
实验程序
1.驱动程序编写
#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 <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define DEV_NAME "timeyqx"
#define DEV_CNT 1
#define LED_OPEN (_IO(0XEF,0X1))
#define LED_CLOSE (_IO(0XEF,0X2))
#define TIME_SET (_IOW(0XEF,0X3,int))
typedef struct time_dev{
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
int major;
int minor;
struct device_node *nd;
int led_gpio;
struct timer_list timer;
int timedata;
spinlock_t lock;
}time_dev;
time_dev timeryqx;
static int led_init(void){
int retvalue = 0;
timeryqx.nd = of_find_node_by_path("/yqxgpioled");
if(timeryqx.led_gpio < 0){
printk("cna't get led!\r\n");
return -EINVAL;
}
timeryqx.led_gpio = of_get_named_gpio(timeryqx.nd,"led-gpio",0);
retvalue = gpio_request(timeryqx.led_gpio,"ledyqx");
if(retvalue){
printk("设备申请失败\r\n");
return -EINVAL;
}
retvalue = gpio_direction_output(timeryqx.led_gpio,1);
if(retvalue<0){
printk("设备设置输出失败失败\r\n");
return -EINVAL;
}
return 0;
}
void timer_function(unsigned long arg){
time_dev *dev = (time_dev* )arg;
static int sta = 1;
int timeperiod;
unsigned long flags;
spin_lock_irqsave(&dev->lock,flags);
timeperiod = dev->timedata;
spin_unlock_irqrestore(&dev->lock,flags);
sta= !sta;
timeperiod = jiffies+msecs_to_jiffies(dev->timedata);
gpio_set_value(dev->led_gpio,sta);
mod_timer(&(dev->timer),timeperiod);
}
static int timer_open(struct inode *inode,struct file *filp){
int retvalue =0;
filp->private_data = &timeryqx;
retvalue = led_init();
if(retvalue<0){
printk("led初始化失败\r\n");
return -EINVAL;
}
timeryqx.timedata = 1000;
return 0;
}
static long timer_unlocked_ioctl(struct file *filp,unsigned int cmd,unsigned long arg){
time_dev *dev = (time_dev *)filp->private_data;
int timeperiod = 0;
unsigned long flags;
int retvalue = 0;
switch(cmd){
case LED_OPEN:
spin_lock_irqsave(&dev->lock,flags);
timeperiod = dev->timedata;
spin_unlock_irqrestore(&dev->lock,flags);
mod_timer(&(dev->timer),jiffies+msecs_to_jiffies(timeperiod));
break;
case LED_CLOSE:
del_timer_sync(&(dev->timer));
break;
case TIME_SET:
retvalue = copy_from_user(&timeperiod,(int *)arg,sizeof(int));
if(retvalue < 0){
printk("用户传递数据失败\r\n");
return -EINVAL;
}
spin_lock_irqsave(&dev->lock,flags);
dev->timedata = timeperiod;
spin_unlock_irqrestore(&dev->lock,flags);
mod_timer(&(dev->timer),jiffies+msecs_to_jiffies(dev->timedata));
break;
default:
printk("无此命令k!\r\n");
break;
}
return 0;
}
static int timer_release(struct inode *inode,struct file *filp){
gpio_set_value(timeryqx.led_gpio,1);
gpio_free(timeryqx.led_gpio);
del_timer_sync(&(timeryqx.timer));
return 0;
}
static struct file_operations timer_fops = {
.owner = THIS_MODULE,
.open = timer_open,
.release = timer_release,
.unlocked_ioctl = timer_unlocked_ioctl
};
static int __init timeyqx_init(void){
if(timeryqx.major){
timeryqx.devid = MKDEV(timeryqx.devid,0);
register_chrdev_region(timeryqx.devid,DEV_CNT,DEV_NAME);
} else {
alloc_chrdev_region(&timeryqx.devid,0,DEV_CNT,DEV_NAME);
timeryqx.major = MAJOR(timeryqx.devid);
timeryqx.minor = MINOR(timeryqx.minor);
}
printk("MAJOR:%d,MINOR:%d\r\n",timeryqx.major,timeryqx.minor);
cdev_init(&timeryqx.cdev,&timer_fops);
cdev_add(&timeryqx.cdev,timeryqx.devid,DEV_CNT);
timeryqx.class = class_create(THIS_MODULE,DEV_NAME);
if(IS_ERR(timeryqx.class)){
printk("class create is fail\r\n");
return PTR_ERR(timeryqx.class);
}
timeryqx.device = device_create(timeryqx.class,NULL,timeryqx.devid,NULL,DEV_NAME);
if(IS_ERR(timeryqx.device )){
printk("device create is fail\r\n");
return PTR_ERR(timeryqx.device);
}
spin_lock_init(&timeryqx.lock);
init_timer(&timeryqx.timer);
timeryqx.timer.function = timer_function;
timeryqx.timer.data = (unsigned long)& timeryqx;
return 0;
}
static void __exit timeyqx_exit(void){
gpio_set_value(timeryqx.led_gpio,1);
gpio_free(timeryqx.led_gpio);
del_timer_sync(&(timeryqx.timer));
device_destroy(timeryqx.class,timeryqx.devid);
class_destroy(timeryqx.class);
cdev_del(&timeryqx.cdev);
unregister_chrdev_region(timeryqx.devid,DEV_CNT);
}
module_init(timeyqx_init);
module_exit(timeyqx_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("YQX");
2.应用程序编写
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "linux/ioctl.h"
#define TIMER_OPEN (_IO(0XEF,0X1))
#define TIMER_CLOSE (_IO(0XEF,0X2))
#define TIMER_SET (_IOW(0XEF,0X3,int))
int main(int argc,char *argv[]){
int fd;
int retvalue;
int status;
int exit_yqx=0;
char *filename;
unsigned int cmdyqx;
unsigned int argyqx;
unsigned char str[100];
if(argc != 3){
printf("程序参数个数不符\r\n");
return -1;
}
filename = argv[1];
fd = open(filename,O_RDWR);
if(retvalue<0){
printf("设备打开失败\r\n");
return -1;
}
status = atoi(argv[2]);
while(1){
printf("please input cmd(1:打开,2:关闭,3:设置定时器值)\r\n:");
retvalue = scanf("%d",&status);
if(retvalue !=1){
gets(str);
}
switch(status){
case 1:
printf("定时器打开\r\n");
cmdyqx = TIMER_OPEN;
break;
case 2:
printf("定时器关闭\r\n");
cmdyqx = TIMER_CLOSE;
break;
case 3:
cmdyqx = TIMER_SET;
printf("please input timerValue:\r\n");
scanf("%d",&argyqx);
if(retvalue !=1){
gets(str);
}
break;
default:
printf("退出命令\r\n");
exit_yqx = 1;
break;
}
if(exit_yqx){
break;
}
ioctl(fd,cmdyqx,&argyqx);
}
close(fd);
printf("退出\r\n");
return 0;
}
七.Linux中断实验
不管是裸机实验还是 Linux 下的驱动实验,中断都是频繁使用的功能,关于 I.MX6U 的中断原理已经在第十七章做了详细的讲解,在裸机中使用中断我们需要做一大堆的工作,比如配置寄存器,使能 IRQ 等等。 Linux 内核提供了完善的中断框架,我们只需要申请中断,然后注册中断处理函数即可,使用非常方便,不需要一系列复杂的寄存器配置。本章我们就来学习一下如何在 Linux 下使用中断。
1.API函数
1.中断号
每个中断都有一个中断号,通过中断号即可区分不同的中断,有的资料也把中断号
叫做中断线。在 Linux 内核中使用一个 int 变量表示中断号,关于中断号我们已
经在第十七章讲解过了。
2.request_irq 函数
在 Linux 内核中要想使用某个中断是需要申请的, request_irq 函数用于申请
中断, request_irq函数可能会导致睡眠,因此不能在中断上下文或者其他禁止睡
眠的代码段中使用 request_irq 函数。 request_irq 函数会激活(使能)中断,所以不需要我们手动去使能中断
3.free_irq 函数
使用中断的时候需要通过 request_irq 函数申请,使用完成以后就要通free_irq
函数释放掉相应的中断。如果中断不是共享的,那么 free_irq 会删除中断处理函
数并且禁止中断。
4.中断处理函数
使用 request_irq 函数申请中断的时候需要设置中断处理函数,中断处理函数格
式如下所示:
irqreturn_t (*irq_handler_t) (int, void *)
5.中断使能与禁止函数
void enable_irq(unsigned int irq)
void disable_irq(unsigned int irq)
6.关闭当前处理器的整个中断系统
local_irq_enable()
local_irq_disable()
而是将中断状态恢复到以前的状态,要考虑到别的任务的感受,此时就要用到下
面两个函数:
local_irq_save(flags)
local_irq_restore(flags)
2.上半部和下半部
在有些资料中也将上半部和下半部称为顶半部和底半部,都是一个意思。我们在使用request_irq 申请中断的时候注册的中断服务函数属于中断处理的上半部,只要中断触发,那么中断处理函数就会执行。我们都知道中断处理函数一定要快点执行完毕,越短越好,但是现实往往是残酷的,有些中断处理过程就是比较费时间,我们必须要对其进行处理,缩小中断处理函数的执行时间。比如电容触摸屏通过中断通知 SOC 有触摸事件发生, SOC 响应中断,然后通过 IIC 接口读取触摸坐标值并将其上报给系统。但是我们都知道 IIC 的速度最高也只有400Kbit/S,所以在中断中通过 IIC 读取数据就会浪费时间。我们可以将通过 IIC 读取触摸数据的操作暂后执行,中断处理函数仅仅相应中断,然后清除中断标志位即可。这个时候中断处理过程就分为了两部分:
上半部:上半部就是中断处理函数,那些处理过程比较快,不会占用很长时间的处理就可以放在上半部完成。 下半部:如果中断处理过程比较耗时,那么就将这些比较耗时的代码提出来,交给下半部去执行,这样中断处理函数就会快进快出。
因此, Linux 内核将中断分为上半部和下半部的主要目的就是实现中断处理函数的快进快出,那些对时间敏感、执行速度快的操作可以放到中断处理函数中,也就是上半部。剩下的所有工作都可以放到下半部去执行,比如在上半部将数据拷贝到内存中,关于数据的具体处理就可以放到下半部去执行。至于哪些代码属于上半部,哪些代码属于下半部并没有明确的规定,一切根据实际使用情况去判断,这个就很考验驱动编写人员的功底了。这里有一些可以借鉴的参考点:
①、如果要处理的内容不希望被其他中断打断,那么可以放到上半部。 ②、如果要处理的任务对时间敏感,可以放到上半部。 ③、如果要处理的任务与硬件有关,可以放到上半部 ④、除了上述三点以外的其他任务,优先考虑放到下半部
上半部处理很简单,直接编写中断处理函数就行了,关键是下半部该怎么做呢? Linux 内核提供了多种下半部机制,接下来我们来学习一下这些下半部机制。 1、软中断 2、 tasklet 3、工作队列
实验程序
1.设备树
key {
compatible = "alientek,key";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_key>;
key-gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;
status = "okay";
interrupt-parent = <&gpio1>;
interrupts = <18 IRQ_TYPE_EDGE_BOTH>;
};
2.驱动程序
#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/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/io.h>
#define DEV_NAME "irqioyqx"
#define DEV_CNT 1
#define INVAKEY 0XFF
#define KEY0VALUE 0X01
#define KEY_NUM 1
struct irq_keydesc {
int gpio;
int irqnum;
unsigned char value;
char name[10];
irqreturn_t (*irqhandleyqx)(int,void *);
};
struct imx6uirq_dev{
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
int major;
int minor;
struct device_node *nd;
atomic_t keyvalue;
atomic_t releasekey;
struct timer_list timer;
struct irq_keydesc irqkeydesc[KEY_NUM];
unsigned char curkeynum;
};
struct imx6uirq_dev irqdevyqx;
static irqreturn_t key0_irqhandle(int irq,void *dev_id){
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;
dev->curkeynum = 0;
dev->timer.data= (volatile long)dev_id;
mod_timer(&dev->timer,jiffies+msecs_to_jiffies(10));
return IRQ_RETVAL(IRQ_HANDLED);
}
void timer_function(unsigned long arg){
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;
unsigned char value;
unsigned char num;
struct irq_keydesc *keydesc;
num = dev->curkeynum;
keydesc = &dev->irqkeydesc[num];
value = gpio_get_value(keydesc->gpio);
if(value == 0){
atomic_set(&dev->keyvalue,keydesc->value);
} else {
atomic_set(&dev->keyvalue,0x80|keydesc->value);
atomic_set(&dev->releasekey,1);
}
}
static int keyio_init(void){
unsigned char i =0;
int retvalue = 0;
irqdevyqx.nd = of_find_node_by_path("/key");
if(irqdevyqx.nd == NULL){
printk("key node not find!\r\n");
return -EINVAL;
}
for(i=0;i<KEY_NUM;i++){
irqdevyqx.irqkeydesc[i].gpio = of_get_named_gpio(irqdevyqx.nd,"key-gpios",i);
if(irqdevyqx.irqkeydesc[i].gpio<0){
printk("can't get key%d\r\n",i);
return -EINVAL;
}
}
for(i=0;i<KEY_NUM;i++){
memset(irqdevyqx.irqkeydesc[i].name,0,sizeof(irqdevyqx.irqkeydesc[i].name));
sprintf(irqdevyqx.irqkeydesc[i].name,"KEY%d",i);
gpio_request(irqdevyqx.irqkeydesc[i].gpio,irqdevyqx.irqkeydesc[i].name);
gpio_direction_input(irqdevyqx.irqkeydesc[i].gpio);
irqdevyqx.irqkeydesc[i].irqnum = irq_of_parse_and_map(irqdevyqx.nd,i);
printk("key%d:gpio=%d,irqnum=%d\r\n",i,irqdevyqx.irqkeydesc[i].gpio,irqdevyqx.irqkeydesc[i].irqnum);
}
irqdevyqx.irqkeydesc[0].irqhandleyqx = key0_irqhandle;
irqdevyqx.irqkeydesc[0].value = KEY0VALUE;
for(i=0;i<KEY_NUM;i++){
retvalue = request_irq(irqdevyqx.irqkeydesc[i].irqnum,irqdevyqx.irqkeydesc[i].irqhandleyqx,
IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
irqdevyqx.irqkeydesc[i].name,&irqdevyqx);
if(retvalue<0){
printk("irq %d request failed!\r\n",irqdevyqx.irqkeydesc[i].irqnum);
return -EINVAL;
}
}
init_timer(&irqdevyqx.timer);
irqdevyqx.timer.function = timer_function;
return 0;
}
static int irqyqx_open(struct inode *inode,struct file *filp){
filp->private_data = &irqdevyqx;
return 0;
}
static ssize_t irqyqx_read(struct file *filp,char __user *buf,size_t cnt,loff_t *offt){
int ret = 0;
unsigned char keyvalue = 0;
unsigned char releasekey = 0;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
keyvalue = atomic_read(&dev->keyvalue);
releasekey = atomic_read(&dev->releasekey);
if(releasekey){
if(keyvalue&0x80){
keyvalue &= ~0x80;
ret = copy_to_user(buf,&keyvalue,sizeof(keyvalue));
}else {
goto data_error;
}
atomic_set(&dev->releasekey,0);
} else {
goto data_error;
}
return 0;
data_error:
return -EINVAL;
}
static int irqyqx_release(struct inode *inode,struct file *filp){
return 0;
}
struct file_operations irqyqx_fops = {
.owner = THIS_MODULE,
.open = irqyqx_open,
.release = irqyqx_release,
.read = irqyqx_read,
};
static int irqim6ull_init(void){
if(irqdevyqx.major){
irqdevyqx.devid = MKDEV(irqdevyqx.devid,0);
register_chrdev_region(irqdevyqx.devid,DEV_CNT,DEV_NAME);
} else {
alloc_chrdev_region(&irqdevyqx.devid,0,DEV_CNT,DEV_NAME);
irqdevyqx.major = MAJOR(irqdevyqx.devid);
irqdevyqx.minor = MINOR(irqdevyqx.minor);
}
printk("MAJOR:%d,MINOR:%d\r\n",irqdevyqx.major,irqdevyqx.minor);
cdev_init(&irqdevyqx.cdev,&irqyqx_fops);
cdev_add(&irqdevyqx.cdev,irqdevyqx.devid,DEV_CNT);
irqdevyqx.class = class_create(THIS_MODULE,DEV_NAME);
if(IS_ERR(irqdevyqx.class)){
printk("class create is fail\r\n");
return PTR_ERR(irqdevyqx.class);
}
irqdevyqx.device = device_create(irqdevyqx.class,NULL,irqdevyqx.devid,NULL,DEV_NAME);
if(IS_ERR(irqdevyqx.device )){
printk("device create is fail\r\n");
return PTR_ERR(irqdevyqx.device);
}
atomic_set(&irqdevyqx.keyvalue,INVAKEY);
atomic_set(&irqdevyqx.releasekey,0);
keyio_init();
return 0;
}
static void irqim6ull_exit(void){
unsigned int i =0;
del_timer_sync(&irqdevyqx.timer);
for(i=0;i<KEY_NUM;i++){
free_irq(irqdevyqx.irqkeydesc[i].irqnum,&irqdevyqx);
gpio_free(irqdevyqx.irqkeydesc[i].gpio);
}
device_destroy(irqdevyqx.class,irqdevyqx.devid);
class_destroy(irqdevyqx.class);
cdev_del(&irqdevyqx.cdev);
unregister_chrdev_region(irqdevyqx.devid,DEV_CNT);
}
module_init(irqim6ull_init);
module_exit(irqim6ull_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("yqx");
3.应用程序
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "linux/ioctl.h"
int main(int argc,char *argv[]){
int fd;
int ret =0;
char *filename;
unsigned char data;
if(argc !=2){
printf("输入参数个数非法\r\n");
return -1;
}
filename = argv[1];
fd = open(filename,O_RDWR);
if(fd < 0){
printf("无法打开文件\r\n");
return -1;
}
while(1){
ret = read(fd,&data,sizeof(data));
if(ret < 0){
} else {
if(data){
printf("key value = %#X\r\n",data);
}
}
}
close(fd);
return ret;
}
八.Linux 阻塞和非阻塞 IO 实验
阻塞和非阻塞 IO 是 Linux 驱动开发里面很常见的两种设备访问模式,在编写驱动的时候一定要考虑到阻塞和非阻塞。 这里的“IO”并不是我们学习 STM32 或者其他单片机的时候所说的“GPIO”(也就是引脚)。这里的 IO 指的是 Input/Output,也就是输入/输出,是应用程序对驱动设备的输入/输出操作。当应用程序对设备驱动进行操作的时候,如果不能获取到设备资源,那么阻塞式 IO 就会将应用程序对应的线程挂起,直到设备资源可以获取为止。对于非阻塞 IO,应用程序对应的线程不会挂起,它要么一直轮询等待,直到设备资源可以使用,要么就直接放弃。阻塞式 IO 如图 所示: 
图 52.1.1.1 中应用程序调用 read 函数从设备中读取数据,当设备不可用或数据未准备好的时候就会进入到休眠态。等设备可用的时候就会从休眠态唤醒,然后从设备中读取数据返回给应用程序。非阻塞 IO 如图 52.1.2 所示: 
1.等待队列(阻塞型IO)
1.等待队列头 2、等待队列项
实验程序
1.驱动程序
#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/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/io.h>
#define DEV_NAME "blockio"
#define DEV_CNT 1
#define INVAKEY 0XFF
#define KEY0VALUE 0X01
#define KEY_NUM 1
struct irq_keydesc {
int gpio;
int irqnum;
unsigned char value;
char name[10];
irqreturn_t (*irqhandleyqx)(int,void *);
};
struct imx6uirq_dev{
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
int major;
int minor;
struct device_node *nd;
atomic_t keyvalue;
atomic_t releasekey;
struct timer_list timer;
struct irq_keydesc irqkeydesc[KEY_NUM];
unsigned char curkeynum;
wait_queue_head_t h_wait;
};
struct imx6uirq_dev irqdevyqx;
static irqreturn_t key0_irqhandle(int irq,void *dev_id){
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;
dev->curkeynum = 0;
dev->timer.data= (volatile long)dev_id;
mod_timer(&dev->timer,jiffies+msecs_to_jiffies(10));
return IRQ_RETVAL(IRQ_HANDLED);
}
void timer_function(unsigned long arg){
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;
unsigned char value;
unsigned char num;
struct irq_keydesc *keydesc;
num = dev->curkeynum;
keydesc = &dev->irqkeydesc[num];
value = gpio_get_value(keydesc->gpio);
if(value == 0){
atomic_set(&dev->keyvalue,keydesc->value);
} else {
atomic_set(&dev->keyvalue,0x80|keydesc->value);
atomic_set(&dev->releasekey,1);
wake_up(&(dev->h_wait));
}
}
static int keyio_init(void){
unsigned char i =0;
int retvalue = 0;
irqdevyqx.nd = of_find_node_by_path("/key");
if(irqdevyqx.nd == NULL){
printk("key node not find!\r\n");
return -EINVAL;
}
for(i=0;i<KEY_NUM;i++){
irqdevyqx.irqkeydesc[i].gpio = of_get_named_gpio(irqdevyqx.nd,"key-gpios",i);
if(irqdevyqx.irqkeydesc[i].gpio<0){
printk("can't get key%d\r\n",i);
return -EINVAL;
}
}
for(i=0;i<KEY_NUM;i++){
memset(irqdevyqx.irqkeydesc[i].name,0,sizeof(irqdevyqx.irqkeydesc[i].name));
sprintf(irqdevyqx.irqkeydesc[i].name,"KEY%d",i);
gpio_request(irqdevyqx.irqkeydesc[i].gpio,irqdevyqx.irqkeydesc[i].name);
gpio_direction_input(irqdevyqx.irqkeydesc[i].gpio);
irqdevyqx.irqkeydesc[i].irqnum = irq_of_parse_and_map(irqdevyqx.nd,i);
printk("key%d:gpio=%d,irqnum=%d\r\n",i,irqdevyqx.irqkeydesc[i].gpio,irqdevyqx.irqkeydesc[i].irqnum);
}
irqdevyqx.irqkeydesc[0].irqhandleyqx = key0_irqhandle;
irqdevyqx.irqkeydesc[0].value = KEY0VALUE;
for(i=0;i<KEY_NUM;i++){
retvalue = request_irq(irqdevyqx.irqkeydesc[i].irqnum,irqdevyqx.irqkeydesc[i].irqhandleyqx,
IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
irqdevyqx.irqkeydesc[i].name,&irqdevyqx);
if(retvalue<0){
printk("irq %d request failed!\r\n",irqdevyqx.irqkeydesc[i].irqnum);
return -EINVAL;
}
}
init_timer(&irqdevyqx.timer);
irqdevyqx.timer.function = timer_function;
init_waitqueue_head(&(irqdevyqx.h_wait));
return 0;
}
static int irqyqx_open(struct inode *inode,struct file *filp){
filp->private_data = &irqdevyqx;
return 0;
}
static ssize_t irqyqx_read(struct file *filp,char __user *buf,size_t cnt,loff_t *offt){
int ret = 0;
unsigned char keyvalue = 0;
unsigned char releasekey = 0;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
#if 0
wait_event_interruptible(dev->h_wait,atomic_read(&dev->releasekey));
#endif
printk("开始阻塞\r\n");
DECLARE_WAITQUEUE(q_wait,current);
if(atomic_read(&dev->releasekey)==0){
add_wait_queue(&dev->h_wait,&q_wait);
__set_current_state(TASK_INTERRUPTIBLE);
schedule();
if(signal_pending(current)){
ret = -ERESTARTSYS;
goto wait_error;
}
__set_current_state(TASK_RUNNING);
remove_wait_queue(&dev->h_wait,&q_wait);
}
printk("结束阻塞\r\n");
keyvalue = atomic_read(&dev->keyvalue);
releasekey = atomic_read(&dev->releasekey);
if(releasekey){
if(keyvalue&0x80){
keyvalue &= ~0x80;
ret = copy_to_user(buf,&keyvalue,sizeof(keyvalue));
}else {
goto data_error;
}
atomic_set(&dev->releasekey,0);
} else {
goto data_error;
}
return 0;
wait_error:
__set_current_state(TASK_RUNNING);
remove_wait_queue(&dev->h_wait,&q_wait);
printk("信号\r\n");
return ret;
data_error:
return -EINVAL;
}
static int irqyqx_release(struct inode *inode,struct file *filp){
return 0;
}
struct file_operations irqyqx_fops = {
.owner = THIS_MODULE,
.open = irqyqx_open,
.release = irqyqx_release,
.read = irqyqx_read,
};
static int irqim6ull_init(void){
if(irqdevyqx.major){
irqdevyqx.devid = MKDEV(irqdevyqx.devid,0);
register_chrdev_region(irqdevyqx.devid,DEV_CNT,DEV_NAME);
} else {
alloc_chrdev_region(&irqdevyqx.devid,0,DEV_CNT,DEV_NAME);
irqdevyqx.major = MAJOR(irqdevyqx.devid);
irqdevyqx.minor = MINOR(irqdevyqx.minor);
}
printk("MAJOR:%d,MINOR:%d\r\n",irqdevyqx.major,irqdevyqx.minor);
cdev_init(&irqdevyqx.cdev,&irqyqx_fops);
cdev_add(&irqdevyqx.cdev,irqdevyqx.devid,DEV_CNT);
irqdevyqx.class = class_create(THIS_MODULE,DEV_NAME);
if(IS_ERR(irqdevyqx.class)){
printk("class create is fail\r\n");
return PTR_ERR(irqdevyqx.class);
}
irqdevyqx.device = device_create(irqdevyqx.class,NULL,irqdevyqx.devid,NULL,DEV_NAME);
if(IS_ERR(irqdevyqx.device )){
printk("device create is fail\r\n");
return PTR_ERR(irqdevyqx.device);
}
atomic_set(&irqdevyqx.keyvalue,INVAKEY);
atomic_set(&irqdevyqx.releasekey,0);
keyio_init();
return 0;
}
static void irqim6ull_exit(void){
unsigned int i =0;
del_timer_sync(&irqdevyqx.timer);
for(i=0;i<KEY_NUM;i++){
free_irq(irqdevyqx.irqkeydesc[i].irqnum,&irqdevyqx);
gpio_free(irqdevyqx.irqkeydesc[i].gpio);
}
device_destroy(irqdevyqx.class,irqdevyqx.devid);
class_destroy(irqdevyqx.class);
cdev_del(&irqdevyqx.cdev);
unregister_chrdev_region(irqdevyqx.devid,DEV_CNT);
}
module_init(irqim6ull_init);
module_exit(irqim6ull_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("yqx");
2.应用程序
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "linux/ioctl.h"
int main(int argc,char *argv[]){
int fd;
int ret =0;
char *filename;
unsigned char data;
if(argc !=2){
printf("输入参数个数非法\r\n");
return -1;
}
filename = argv[1];
fd = open(filename,O_RDWR);
if(fd < 0){
printf("无法打开文件\r\n");
return -1;
}
while(1){
ret = read(fd,&data,sizeof(data));
if(ret < 0){
} else {
if(data){
printf("\r\nkey value = %#X\r\n",data);
}
}
}
close(fd);
return ret;
}
2.轮询(非阻塞IO)
如果用户应用程序以非阻塞的方式访问设备,设备驱动程序就要提供非阻塞的处理方式,也就是轮询。 poll、 epoll 和 select 可以用于处理轮询,应用程序通过 select、 epoll 或 poll 函数来查询设备是否可以操作,如果可以操作的话就从设备读取或者向设备写入数据。当应用程序调用 select、 epoll 或 poll 函数的时候设备驱动程序中的 poll 函数就会执行,因此需要在设备驱动程序中编写 poll 函数。 1.select 函数 2.poll 函数 3.epoll 函数
实验程序
1.驱动程序:
#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/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/poll.h>
#define DEV_NAME "noblockio"
#define DEV_CNT 1
#define INVAKEY 0XFF
#define KEY0VALUE 0X01
#define KEY_NUM 1
struct irq_keydesc {
int gpio;
int irqnum;
unsigned char value;
char name[10];
irqreturn_t (*irqhandleyqx)(int,void *);
};
struct imx6uirq_dev{
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
int major;
int minor;
struct device_node *nd;
atomic_t keyvalue;
atomic_t releasekey;
struct timer_list timer;
struct irq_keydesc irqkeydesc[KEY_NUM];
unsigned char curkeynum;
wait_queue_head_t h_wait;
};
struct imx6uirq_dev irqdevyqx;
static irqreturn_t key0_irqhandle(int irq,void *dev_id){
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;
dev->curkeynum = 0;
dev->timer.data= (volatile long)dev_id;
mod_timer(&dev->timer,jiffies+msecs_to_jiffies(10));
return IRQ_RETVAL(IRQ_HANDLED);
}
void timer_function(unsigned long arg){
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;
unsigned char value;
unsigned char num;
struct irq_keydesc *keydesc;
num = dev->curkeynum;
keydesc = &dev->irqkeydesc[num];
value = gpio_get_value(keydesc->gpio);
if(value == 0){
atomic_set(&dev->keyvalue,keydesc->value);
} else {
atomic_set(&dev->keyvalue,0x80|keydesc->value);
atomic_set(&dev->releasekey,1);
}
}
static int keyio_init(void){
unsigned char i =0;
int retvalue = 0;
irqdevyqx.nd = of_find_node_by_path("/key");
if(irqdevyqx.nd == NULL){
printk("key node not find!\r\n");
return -EINVAL;
}
for(i=0;i<KEY_NUM;i++){
irqdevyqx.irqkeydesc[i].gpio = of_get_named_gpio(irqdevyqx.nd,"key-gpios",i);
if(irqdevyqx.irqkeydesc[i].gpio<0){
printk("can't get key%d\r\n",i);
return -EINVAL;
}
}
for(i=0;i<KEY_NUM;i++){
memset(irqdevyqx.irqkeydesc[i].name,0,sizeof(irqdevyqx.irqkeydesc[i].name));
sprintf(irqdevyqx.irqkeydesc[i].name,"KEY%d",i);
gpio_request(irqdevyqx.irqkeydesc[i].gpio,irqdevyqx.irqkeydesc[i].name);
gpio_direction_input(irqdevyqx.irqkeydesc[i].gpio);
irqdevyqx.irqkeydesc[i].irqnum = irq_of_parse_and_map(irqdevyqx.nd,i);
printk("key%d:gpio=%d,irqnum=%d\r\n",i,irqdevyqx.irqkeydesc[i].gpio,irqdevyqx.irqkeydesc[i].irqnum);
}
irqdevyqx.irqkeydesc[0].irqhandleyqx = key0_irqhandle;
irqdevyqx.irqkeydesc[0].value = KEY0VALUE;
for(i=0;i<KEY_NUM;i++){
retvalue = request_irq(irqdevyqx.irqkeydesc[i].irqnum,irqdevyqx.irqkeydesc[i].irqhandleyqx,
IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
irqdevyqx.irqkeydesc[i].name,&irqdevyqx);
if(retvalue<0){
printk("irq %d request failed!\r\n",irqdevyqx.irqkeydesc[i].irqnum);
return -EINVAL;
}
}
init_timer(&irqdevyqx.timer);
irqdevyqx.timer.function = timer_function;
init_waitqueue_head(&(irqdevyqx.h_wait));
return 0;
}
static int irqyqx_open(struct inode *inode,struct file *filp){
filp->private_data = &irqdevyqx;
return 0;
}
static ssize_t irqyqx_read(struct file *filp,char __user *buf,size_t cnt,loff_t *offt){
int ret = 0;
unsigned char keyvalue = 0;
unsigned char releasekey = 0;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
keyvalue = atomic_read(&dev->keyvalue);
releasekey = atomic_read(&dev->releasekey);
if(releasekey){
if(keyvalue&0x80){
keyvalue &= ~0x80;
ret = copy_to_user(buf,&keyvalue,sizeof(keyvalue));
}else {
goto data_error;
}
atomic_set(&dev->releasekey,0);
} else {
goto data_error;
}
return 0;
data_error:
return -EINVAL;
}
static int irqyqx_release(struct inode *inode,struct file *filp){
return 0;
}
static unsigned int noblock_poll(struct file *filp,struct poll_table_struct *wait){
unsigned int mask = 0;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
poll_wait(filp,&dev->h_wait,wait);
if(atomic_read(&dev->releasekey)){
mask = POLLIN|POLLRDNORM;
}
return mask;
}
struct file_operations irqyqx_fops = {
.owner = THIS_MODULE,
.open = irqyqx_open,
.release = irqyqx_release,
.read = irqyqx_read,
.poll = noblock_poll,
};
static int irqim6ull_init(void){
if(irqdevyqx.major){
irqdevyqx.devid = MKDEV(irqdevyqx.devid,0);
register_chrdev_region(irqdevyqx.devid,DEV_CNT,DEV_NAME);
} else {
alloc_chrdev_region(&irqdevyqx.devid,0,DEV_CNT,DEV_NAME);
irqdevyqx.major = MAJOR(irqdevyqx.devid);
irqdevyqx.minor = MINOR(irqdevyqx.minor);
}
printk("MAJOR:%d,MINOR:%d\r\n",irqdevyqx.major,irqdevyqx.minor);
cdev_init(&irqdevyqx.cdev,&irqyqx_fops);
cdev_add(&irqdevyqx.cdev,irqdevyqx.devid,DEV_CNT);
irqdevyqx.class = class_create(THIS_MODULE,DEV_NAME);
if(IS_ERR(irqdevyqx.class)){
printk("class create is fail\r\n");
return PTR_ERR(irqdevyqx.class);
}
irqdevyqx.device = device_create(irqdevyqx.class,NULL,irqdevyqx.devid,NULL,DEV_NAME);
if(IS_ERR(irqdevyqx.device )){
printk("device create is fail\r\n");
return PTR_ERR(irqdevyqx.device);
}
atomic_set(&irqdevyqx.keyvalue,INVAKEY);
atomic_set(&irqdevyqx.releasekey,0);
keyio_init();
return 0;
}
static void irqim6ull_exit(void){
unsigned int i =0;
del_timer_sync(&irqdevyqx.timer);
for(i=0;i<KEY_NUM;i++){
free_irq(irqdevyqx.irqkeydesc[i].irqnum,&irqdevyqx);
gpio_free(irqdevyqx.irqkeydesc[i].gpio);
}
device_destroy(irqdevyqx.class,irqdevyqx.devid);
class_destroy(irqdevyqx.class);
cdev_del(&irqdevyqx.cdev);
unregister_chrdev_region(irqdevyqx.devid,DEV_CNT);
}
module_init(irqim6ull_init);
module_exit(irqim6ull_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("yqx");
2.应用程序
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "linux/ioctl.h"
#include "poll.h"
#include "sys/time.h"
int main(int argc,char *argv[]){
int fd;
int ret =0;
char *filename;
struct pollfd fds;
unsigned char data;
if(argc !=2){
printf("输入参数个数非法\r\n");
return -1;
}
filename = argv[1];
fd = open(filename,O_RDWR|O_NONBLOCK);
if(fd < 0){
printf("无法打开文件\r\n");
return -1;
}
fds.fd = fd;
fds.events = POLLIN;
while(1){
ret = poll(&fds,1,3000);
switch(ret){
case -1:
printf("出现错误\r\n");
break;
case 0:
printf("3s超时\r\n");
break;
default:
ret = read(fd,&data,sizeof(data));
if(ret < 0){
printf("读取错误\r\n");
return -1;
}else {
printf("key value = %#X\r\n",data);
}
break;
}
}
#if 0
while(1){
FD_ZERO(&readfds);
FD_SET(fd,&readfds);
timeout.tv_sec = 0;
timeout.tv_usec = 500000;
ret = select(fd+1,&readfds,NULL,NULL,&timeout);
switch(ret){
case 0:
printf("超时!\r\n");
break;
case -1:
printf("读取错误\r\n");
break;
default:
if(FD_ISSET(fd,&readfds)){
ret = read(fd,&data,sizeof(data));
if(ret < 0){
printf("读取错误\r\n");
return -1;
}else {
printf("key value = %#X\r\n",data);
}
}
break;
}
}
#endif
close(fd);
return ret;
}
九.异步通知实验
我们首先来回顾一下“中断”,中断是处理器提供的一种异步机制,我们配置好中断以后就可以让处理器去处理其他的事情了,当中断发生以后会触发我们事先设置好的中断服务函数,在中断服务函数中做具体的处理。比如我们在裸机篇里面编写的 GPIO 按键中断实验,我们通过按键去开关蜂鸣器,采用中断以后处理器就不需要时刻的去查看按键有没有被按下,因为按键按下以后会自动触发中断。同样的, Linux 应用程序可以通过阻塞或者非阻塞这两种方式来访问驱动设备,通过阻塞方式访问的话应用程序会处于休眠态,等待驱动设备可以使用,非阻塞方式的话会通过 poll 函数来不断的轮询,查看驱动设备文件是否可以使用。这两种方式都需要应用程序主动的去查询设备的使用情况,如果能提供一种类似中断的机制,当驱动程序可以访问的时候主动告诉应用程序那就最好了。 “信号”为此应运而生,信号类似于我们硬件上使用的“中断”,只不过信号是软件层次上的。算是在软件层次上对中断的一种模拟,驱动可以通过主动向应用程序发送信号的方式来报告自己可以访问了,应用程序获取到信号以后就可以从驱动设备中读取或者写入数据了。整个过程就相当于应用程序收到了驱动发送过来了的一个中断,然后应用程序去响应这个中断,在整个处理过程中应用程序并没有去查询驱动设备是否可以访问,一切都是由驱动设备自己告诉给应用程序的。阻塞、非阻塞、异步通知,这三种是针对不同的场合提出来的不同的解决方法,没有优劣之分,在实际的工作和学习中,根据自己的实际需求选择合适的处理方法即可。
1.驱动中的信号处理
1、 fasync_struct 结构体 首先我们需要在驱动程序中定义一个 fasync_struct 结构体指针变量 2.fasync 函数 如果要使用异步通知,需要在设备驱动中实现 file_operations 操作集中的fasync 函数,此函数格式如下所示:
int (*fasync) (int fd, struct file *filp, int on)
fasync 函数里面一般通过调用 fasync_helper 函数来初始化前面定义的fasync_struct 结构体指针, fasync_helper 函数原型如下:
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
3.kill_fasync 函数 当设备可以访问的时候,驱动程序需要向应用程序发出信号,相当于产生“中断”。 kill_fasync函数负责发送指定的信号, kill_fasync 函数原型如下所示:
void kill_fasync(struct fasync_struct **fp, int sig, int band)
2.应用程序对异步通知的处理
1、注册信号处理函数 应用程序根据驱动程序所使用的信号来设置信号的处理函数,应用程序使用 signal 函数来设置信号的处理函数。前面已经详细的讲过了,这里就不细讲了。
2.将本应用程序的进程号告诉给内核 使用 fcntl(fd, F_SETOWN, getpid())将本应用程序的进程号告诉给内核。
3.开启异步通知 使用如下两行程序开启异步通知:
flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | FASYNC);
重点就是通过 fcntl 函数设置进程状态为 FASYNC,经过这一步,驱动程序中的 fasync 函数就会执行。
实验程序
1.驱动程序
#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/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/poll.h>
#include <linux/fcntl.h>
#define DEV_NAME "asyyqx"
#define DEV_CNT 1
#define INVAKEY 0XFF
#define KEY0VALUE 0X01
#define KEY_NUM 1
struct irq_keydesc {
int gpio;
int irqnum;
unsigned char value;
char name[10];
irqreturn_t (*irqhandleyqx)(int,void *);
};
struct imx6uirq_dev{
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
int major;
int minor;
struct device_node *nd;
atomic_t keyvalue;
atomic_t releasekey;
struct timer_list timer;
struct irq_keydesc irqkeydesc[KEY_NUM];
unsigned char curkeynum;
struct fasync_struct *async_yqx;
};
struct imx6uirq_dev irqdevyqx;
static irqreturn_t key0_irqhandle(int irq,void *dev_id){
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;
dev->curkeynum = 0;
dev->timer.data= (volatile long)dev_id;
mod_timer(&dev->timer,jiffies+msecs_to_jiffies(10));
return IRQ_RETVAL(IRQ_HANDLED);
}
void timer_function(unsigned long arg){
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;
unsigned char value;
unsigned char num;
struct irq_keydesc *keydesc;
num = dev->curkeynum;
keydesc = &dev->irqkeydesc[num];
value = gpio_get_value(keydesc->gpio);
if(value == 0){
atomic_set(&dev->keyvalue,keydesc->value);
} else {
atomic_set(&dev->keyvalue,0x80|keydesc->value);
atomic_set(&dev->releasekey,1);
}
if(atomic_read(&dev->releasekey)){
if(dev->async_yqx){
kill_fasync(&dev->async_yqx,SIGIO,POLL_IN);
}
}
}
static int keyio_init(void){
unsigned char i =0;
int retvalue = 0;
irqdevyqx.nd = of_find_node_by_path("/key");
if(irqdevyqx.nd == NULL){
printk("key node not find!\r\n");
return -EINVAL;
}
for(i=0;i<KEY_NUM;i++){
irqdevyqx.irqkeydesc[i].gpio = of_get_named_gpio(irqdevyqx.nd,"key-gpios",i);
if(irqdevyqx.irqkeydesc[i].gpio<0){
printk("can't get key%d\r\n",i);
return -EINVAL;
}
}
for(i=0;i<KEY_NUM;i++){
memset(irqdevyqx.irqkeydesc[i].name,0,sizeof(irqdevyqx.irqkeydesc[i].name));
sprintf(irqdevyqx.irqkeydesc[i].name,"KEY%d",i);
gpio_request(irqdevyqx.irqkeydesc[i].gpio,irqdevyqx.irqkeydesc[i].name);
gpio_direction_input(irqdevyqx.irqkeydesc[i].gpio);
irqdevyqx.irqkeydesc[i].irqnum = irq_of_parse_and_map(irqdevyqx.nd,i);
printk("key%d:gpio=%d,irqnum=%d\r\n",i,irqdevyqx.irqkeydesc[i].gpio,irqdevyqx.irqkeydesc[i].irqnum);
}
irqdevyqx.irqkeydesc[0].irqhandleyqx = key0_irqhandle;
irqdevyqx.irqkeydesc[0].value = KEY0VALUE;
for(i=0;i<KEY_NUM;i++){
retvalue = request_irq(irqdevyqx.irqkeydesc[i].irqnum,irqdevyqx.irqkeydesc[i].irqhandleyqx,
IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
irqdevyqx.irqkeydesc[i].name,&irqdevyqx);
if(retvalue<0){
printk("irq %d request failed!\r\n",irqdevyqx.irqkeydesc[i].irqnum);
return -EINVAL;
}
}
init_timer(&irqdevyqx.timer);
irqdevyqx.timer.function = timer_function;
return 0;
}
static int irqyqx_open(struct inode *inode,struct file *filp){
filp->private_data = &irqdevyqx;
return 0;
}
static ssize_t irqyqx_read(struct file *filp,char __user *buf,size_t cnt,loff_t *offt){
int ret = 0;
unsigned char keyvalue = 0;
unsigned char releasekey = 0;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
keyvalue = atomic_read(&dev->keyvalue);
releasekey = atomic_read(&dev->releasekey);
if(releasekey){
if(keyvalue&0x80){
keyvalue &= ~0x80;
ret = copy_to_user(buf,&keyvalue,sizeof(keyvalue));
}else {
goto data_error;
}
atomic_set(&dev->releasekey,0);
} else {
goto data_error;
}
return 0;
data_error:
return -EINVAL;
}
static int irqyqx_fasync(int fd,struct file *filp,int on){
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
if(fasync_helper(fd,filp,on,&dev->async_yqx)<0){
return -EIO;
}
return 0;
}
static int irqyqx_release(struct inode *inode,struct file *filp){
irqyqx_fasync(-1,filp,0);
return 0;
}
struct file_operations irqyqx_fops = {
.owner = THIS_MODULE,
.open = irqyqx_open,
.release = irqyqx_release,
.read = irqyqx_read,
.fasync = irqyqx_fasync,
};
static int irqim6ull_init(void){
if(irqdevyqx.major){
irqdevyqx.devid = MKDEV(irqdevyqx.devid,0);
register_chrdev_region(irqdevyqx.devid,DEV_CNT,DEV_NAME);
} else {
alloc_chrdev_region(&irqdevyqx.devid,0,DEV_CNT,DEV_NAME);
irqdevyqx.major = MAJOR(irqdevyqx.devid);
irqdevyqx.minor = MINOR(irqdevyqx.minor);
}
printk("MAJOR:%d,MINOR:%d\r\n",irqdevyqx.major,irqdevyqx.minor);
cdev_init(&irqdevyqx.cdev,&irqyqx_fops);
cdev_add(&irqdevyqx.cdev,irqdevyqx.devid,DEV_CNT);
irqdevyqx.class = class_create(THIS_MODULE,DEV_NAME);
if(IS_ERR(irqdevyqx.class)){
printk("class create is fail\r\n");
return PTR_ERR(irqdevyqx.class);
}
irqdevyqx.device = device_create(irqdevyqx.class,NULL,irqdevyqx.devid,NULL,DEV_NAME);
if(IS_ERR(irqdevyqx.device )){
printk("device create is fail\r\n");
return PTR_ERR(irqdevyqx.device);
}
atomic_set(&irqdevyqx.keyvalue,INVAKEY);
atomic_set(&irqdevyqx.releasekey,0);
keyio_init();
return 0;
}
static void irqim6ull_exit(void){
unsigned int i =0;
del_timer_sync(&irqdevyqx.timer);
for(i=0;i<KEY_NUM;i++){
free_irq(irqdevyqx.irqkeydesc[i].irqnum,&irqdevyqx);
gpio_free(irqdevyqx.irqkeydesc[i].gpio);
}
device_destroy(irqdevyqx.class,irqdevyqx.devid);
class_destroy(irqdevyqx.class);
cdev_del(&irqdevyqx.cdev);
unregister_chrdev_region(irqdevyqx.devid,DEV_CNT);
}
module_init(irqim6ull_init);
module_exit(irqim6ull_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("yqx");
2.应用程序
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "linux/ioctl.h"
#include "poll.h"
#include "sys/time.h"
#include "signal.h"
int fd = 0;
static void sigio_signal_func(int signum){
int err = 0;
unsigned char data;
err = read(fd,&data,sizeof(data));
if(err<0){
printf("读取错误\r\n");
} else {
printf("sigio signal! key value = %d\r\n",data);
}
}
int main(int argc,char *argv[]){
int ret =0;
char *filename;
unsigned char data;
int flags = 0;
if(argc !=2){
printf("输入参数个数非法\r\n");
return -1;
}
filename = argv[1];
fd = open(filename,O_RDWR);
if(fd < 0){
printf("无法打开文件\r\n");
return -1;
}
signal(SIGIO,sigio_signal_func);
fcntl(fd,F_SETOWN,getpid());
flags = fcntl(fd,F_GETFL);
fcntl(fd,F_SETFL,flags|FASYNC);
while(1){
sleep(2);
}
close(fd);
return ret;
}
十.platform设备驱动实验
1.Linux驱动的分离与分层
1.驱动的分隔与分离
驱动的分隔,也就是将主机驱动和设备驱动分隔开来,比如 I2C、 SPI 等等都会采用驱动分隔的方式来简化驱动的开发。在实际的驱动开发中,一般 I2C 主机控制器驱动已经由半导体厂家编写好了,而设备驱动一般也由设备器件的厂家编写好了,我们只需要提供设备信息即可,比如 I2C 设备的话提供设备连接到了哪个 I2C 接口上, I2C 的速度是多少等等。相当于将设备信息从设备驱动中剥离开来,驱动使用标准方法去获取到设备信息(比如从设备树中获取到设备信息),然后根据获取到的设备信息来初始化设备。 这样就相当于驱动只负责驱动,设备只负责设备,想办法将两者进行匹配即可。这个就是 Linux 中的总线(bus)、驱动(driver)和设备(device)模型,也就是常说的驱动分离。总线就是驱动和设备信息的月老,负责给两者牵线搭桥,如图 54.1.1.4 所示:  当我们向系统注册一个驱动的时候,总线就会在右侧的设备中查找,看看有没有与之匹配的设备,如果有的话就将两者联系起来。同样的,当向系统中注册一个设备的时候,总线就会在左侧的驱动中查找看有没有与之匹配的设备,有的话也联系起来。 Linux 内核中大量的驱动程序都采用总线、驱动和设备模式,我们一会要重点讲解的 platform 驱动就是这一思想下的产物。
2.驱动的分层
上一小节讲了驱动的分隔与分离,本节我们来简单看一下驱动的分层,大家应该听说过网络的 7 层模型,不同的层负责不同的内容。同样的, Linux 下的驱动往往也是分层的,分层的目的也是为了在不同的层处理不同的内容。以其他书籍或者资料常常使用到的input(输入子系统,后面会有专门的章节详细的讲解)为例,简单介绍一下驱动的分层。 input 子系统负责管理所有跟输入有关的驱动,包括键盘、鼠标、触摸等,最底层的就是设备原始驱动,负责获取输入设备的原始值,获取到的输入事件上报给 input 核心层。 input 核心层会处理各种 IO 模型,并且提供 file_operations 操作集合。我们在编写输入设备驱动的时候只需要处理好输入事件的上报即可,至于如何处理这些上报的输入事件那是上层去考虑的,我们不用管。可以看出借助分层模型可以极大的简化我们的驱动编写,对于驱动编写来说非常的友好。
2.platform 平台驱动模型简介(基于设备树)
前面我们讲了设备驱动的分离,并且引出了总线(bus)、驱动(driver)和设备(device)模型,比如 I2C、 SPI、 USB 等总线。但是在 SOC 中有些外设是没有总线这个概念的,但是又要使用总线、驱动和设备模型该怎么办呢?为了解决此问题, Linux 提出了 platform 这个虚拟总线,相应的就有 platform_driver 和 platform_device。 1. platform 总线 2. platform 驱动 3. platform 设备
实验程序:
1.驱动程序
#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_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define LEDDEV_CNT 1
#define DEV_NAME "dtsplatled"
#define LEDOFF 0
#define LEDON 1
struct leddev_dev{
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
int major;
int minor;
struct device_node *node;
int led0;
};
struct leddev_dev leddev;
void led0_switch(u8 sta){
if(sta == LEDON){
gpio_set_value(leddev.led0,0);
} else if (sta == LEDOFF){
gpio_set_value(leddev.led0,1);
}
}
static int led_release(struct inode *inode,struct file *filp){
return 0;
}
static ssize_t led_write(struct file *filp,const char __user *buf,size_t cnt,loff_t *offt){
int retvalue;
unsigned char data;
unsigned char ledflag;
retvalue = copy_from_user(&data,buf,cnt);
if(retvalue <0){
printk("kernel write failed\r\n");
return -EINVAL;
}
ledflag = data;
if(ledflag == LEDON){
led0_switch(LEDON);
} else if(ledflag == LEDOFF){
led0_switch(LEDOFF);
} else {
printk("kernel:输入非法\r\n");
}
return 0;
}
static int led_open(struct inode *inode,struct file *filp){
filp->private_data = &leddev;
return 0;
}
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
.release = led_release,
};
static int led_probe(struct platform_device *dev){
printk("driver and device was matched!\r\n");
if(leddev.major){
leddev.devid = MKDEV(leddev.major,0);
register_chrdev_region(leddev.devid,LEDDEV_CNT,DEV_NAME);
} else {
alloc_chrdev_region(&leddev.devid,0,LEDDEV_CNT,DEV_NAME);
leddev.major = MAJOR(leddev.devid);
}
printk("MAJOR:%d,MINOR:%d\r\n",leddev.major,leddev.minor);
cdev_init(&leddev.cdev,&led_fops);
cdev_add(&leddev.cdev,leddev.devid,LEDDEV_CNT);
leddev.class = class_create(THIS_MODULE,DEV_NAME);
if(IS_ERR(leddev.class)){
return PTR_ERR(leddev.class);
}
leddev.device = device_create(leddev.class,NULL,leddev.devid,NULL,DEV_NAME);
if(IS_ERR(leddev.device)){
return PTR_ERR(leddev.device);
}
leddev.node = of_find_node_by_path("/yqxgpioled");
if(leddev.node == NULL){
printk("gpioled node not find!\r\n");
return -EINVAL;
}
leddev.led0 = of_get_named_gpio(leddev.node,"led-gpio",0);
gpio_request(leddev.led0,"led0");
gpio_direction_output(leddev.led0,1);
return 0;
}
static int led_remove(struct platform_device *dev){
gpio_set_value(leddev.led0,1);
gpio_free(leddev.led0);
device_destroy(leddev.class,leddev.devid);
class_destroy(leddev.class);
cdev_del(&leddev.cdev);
unregister_chrdev_region(leddev.devid,LEDDEV_CNT);
return 0;
}
static const struct of_device_id led_of_match[] = {
{ .compatible = "yqx,yqxgpioled"},
{ }
};
static struct platform_driver led_driver = {
.driver = {
.name = "imx6ul-ledy",
.of_match_table = led_of_match,
},
.probe = led_probe,
.remove = led_remove,
};
static int __init leddriver_init(void){
return platform_driver_register(&led_driver);
}
static void __exit leddriver_exit(void){
platform_driver_unregister(&led_driver);
}
module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("yqx");
2.应用程序
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#define LED0N 1
#define LEDOFF 0
int main(int argc,char *argv[]){
int fd,retvalue;
char *filename;
unsigned char data;
if(argc != 3){
printf("输入不符合格式\r\n");
return -1;
}
filename = argv[1];
fd = open(filename,O_RDWR);
if(fd<0){
printf("打开文件失败\r\n");
return -1;
}
data = atoi(argv[2]);
retvalue = write(fd,&data,sizeof(data));
if(retvalue <0){
printf("LED Control Failed\r\n");
close(fd);
return -1;
}
retvalue = close(fd);
if(retvalue < 0){
printf("关闭文件失败\r\n");
return -1;
}
return 0;
}
十一.Linux MISC 驱动实验
misc 的意思是混合、杂项的,因此 MISC 驱动也叫做杂项驱动,也就是当我们板子上的某些外设无法进行分类的时候就可以使用 MISC 驱动。 MISC 驱动其实就是最简单的字符设备驱动,通常嵌套在 platform 总线驱动中,实现复杂的驱动,本章我们就来学习一下 MISC 驱动的编写。 所有的 MISC 设备驱动的主设备号都为 10,不同的设备使用不同的从设备号。随着 Linux字符设备驱动的不断增加,设备号变得越来越紧张,尤其是主设备号, MISC 设备驱动就用于解决此问题。 MISC 设备会自动创建 cdev,不需要像我们以前那样手动创建,因此采用 MISC 设备驱动可以简化字符设备驱动的编写。我们需要向 Linux 注册一个 miscdevice 设备, miscdevice 是一个结构体。
实验程序
1.驱动程序
#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_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/miscdevice.h>
#define LEDDEV_CNT 1
#define DEV_NAME "miscbeep"
#define LEDOFF 0
#define LEDON 1
#define MISCBEEP_MINOR 144
struct leddev_dev{
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
int major;
int minor;
struct device_node *node;
int led0;
};
struct leddev_dev leddev;
void led0_switch(u8 sta){
if(sta == LEDON){
gpio_set_value(leddev.led0,0);
} else if (sta == LEDOFF){
gpio_set_value(leddev.led0,1);
}
}
static int led_release(struct inode *inode,struct file *filp){
return 0;
}
static ssize_t led_write(struct file *filp,const char __user *buf,size_t cnt,loff_t *offt){
int retvalue;
unsigned char data;
unsigned char ledflag;
retvalue = copy_from_user(&data,buf,cnt);
if(retvalue <0){
printk("kernel write failed\r\n");
return -EINVAL;
}
ledflag = data;
if(ledflag == LEDON){
led0_switch(LEDON);
} else if(ledflag == LEDOFF){
led0_switch(LEDOFF);
} else {
printk("kernel:输入非法\r\n");
}
return 0;
}
static int led_open(struct inode *inode,struct file *filp){
filp->private_data = &leddev;
return 0;
}
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
.release = led_release,
};
static struct miscdevice miscbeep0 = {
.minor = MISCBEEP_MINOR,
.name = DEV_NAME,
.fops = &led_fops,
};
static int led_probe(struct platform_device *dev){
int retvalue;
printk("driver and device was matched!\r\n");
leddev.node = of_find_node_by_path("/beep");
if(leddev.node == NULL){
printk("gpioled node not find!\r\n");
return -EINVAL;
}
leddev.led0 = of_get_named_gpio(leddev.node,"beep-gpios",0);
gpio_request(leddev.led0,"beep0");
gpio_direction_output(leddev.led0,1);
retvalue = misc_register(&miscbeep0);
if(retvalue <0){
printk("misc device register failed!\r\n");
return -EINVAL;
}
return 0;
}
static int led_remove(struct platform_device *dev){
gpio_set_value(leddev.led0,1);
misc_deregister(&miscbeep0);
return 0;
}
static const struct of_device_id led_of_match[] = {
{ .compatible = "alientek,beep"},
{ }
};
static struct platform_driver led_driver = {
.driver = {
.name = "imx6ul-ledy",
.of_match_table = led_of_match,
},
.probe = led_probe,
.remove = led_remove,
};
static int __init leddriver_init(void){
return platform_driver_register(&led_driver);
}
static void __exit leddriver_exit(void){
platform_driver_unregister(&led_driver);
}
module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("yqx");
2.应用程序
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#define BEEPOFF 0
#define BEEPON 1
int main(int argc,char* argv[]){
char *filename;
unsigned char databuf[1];
int fd,retvalue;
if(argc != 3){
printf("输入格式非法\r\n");
return -1;
}
filename = argv[1];
databuf[0] = atoi(argv[2]);
fd = open(filename,O_RDWR);
if(fd<0){
printf("打开文件失败\r\n");
return -1;
}
if(databuf[0] == BEEPON){
retvalue = write(fd,databuf,sizeof(databuf));
if(retvalue<0){
printf("写入数据失败\r\n");
close(fd);
return -1;
}
} else if(databuf[0] == BEEPOFF){
retvalue = write(fd,databuf,sizeof(databuf));
if(retvalue<0){
printf("写入数据失败\r\n");
close(fd);
return -1;
}
}else {
printf("输入数据错误\r\n");
}
retvalue = close(fd);
if(retvalue<0){
printf("关闭文件失败\r\n");
return -1;
}
printf("关闭文件成功\r\n");
return 0;
}
十二.Linux INPUT 子系统实验
按键、鼠标、键盘、触摸屏等都属于输入(input)设备, Linux 内核为此专门做了一个叫做 input子系统的框架来处理输入事件。输入设备本质上还是字符设备,只是在此基础上套上了 input 框架,用户只需要负责上报输入事件,比如按键值、坐标等信息, input 核心层负责处理这些事件。本章我们就来学习一下 Linux 内核中的 input 子系统。
input 就是输入的意思,因此 input 子系统就是管理输入的子系统,和 pinctrl、 gpio 子系统一样,都是 Linux 内核针对某一类设备而创建的框架。比如按键输入、键盘、鼠标、触摸屏等等这些都属于输入设备,不同的输入设备所代表的含义不同,按键和键盘就是代表按键信息,鼠标和触摸屏代表坐标信息,因此在应用层的处理就不同,对于驱动编写者而言不需要去关心 应用层的事情,我们只需要按照要求上报这些输入事件即可。为此 input 子系统分为 input 驱动层、 input 核心层、 input 事件处理层,最终给用户空间提供可访问的设备节点, input 子系统框架如图 58.1.1.1 所示:  图 58.1.1.1 中左边就是最底层的具体设备,比如按键、 USB 键盘/鼠标等,中间部分属于Linux 内核空间,分为驱动层、核心层和事件层,最右边的就是用户空间,所有的输入设备以文件的形式供用户应用程序使用。可以看出 input 子系统用到了我们前面讲解的驱动分层模型,我们编写驱动程序的时候只需要关注中间的驱动层、核心层和事件层,这三个层的分工如下: 驱动层:输入设备的具体驱动程序,比如按键驱动程序,向内核层报告输入内容。 核心层:承上启下,为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行处理。 事件层:主要和用户空间进行交互。
实验程序
1.驱动程序
#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/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/input.h>
#define DEV_NAME "inputyqx"
#define DEV_CNT 1
#define INVAKEY 0XFF
#define KEY0VALUE 0X01
#define KEY_NUM 1
struct irq_keydesc {
int gpio;
int irqnum;
unsigned char value;
char name[10];
irqreturn_t (*irqhandleyqx)(int,void *);
};
struct imx6uirq_dev{
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
int major;
int minor;
struct device_node *nd;
atomic_t keyvalue;
atomic_t releasekey;
struct timer_list timer;
struct irq_keydesc irqkeydesc[KEY_NUM];
unsigned char curkeynum;
struct input_dev *inputdev;
};
struct imx6uirq_dev irqdevyqx;
static irqreturn_t key0_irqhandle(int irq,void *dev_id){
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;
dev->curkeynum = 0;
dev->timer.data= (volatile long)dev_id;
mod_timer(&dev->timer,jiffies+msecs_to_jiffies(10));
return IRQ_RETVAL(IRQ_HANDLED);
}
void timer_function(unsigned long arg){
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;
unsigned char value;
unsigned char num;
struct irq_keydesc *keydesc;
num = dev->curkeynum;
keydesc = &dev->irqkeydesc[num];
value = gpio_get_value(keydesc->gpio);
if(value == 0){
input_report_key(dev->inputdev,keydesc->value,1);
input_sync(dev->inputdev);
} else {
input_report_key(dev->inputdev,keydesc->value,0);
input_sync(dev->inputdev);
}
}
static int keyio_init(void){
unsigned char i =0;
int retvalue = 0;
irqdevyqx.nd = of_find_node_by_path("/key");
if(irqdevyqx.nd == NULL){
printk("key node not find!\r\n");
return -EINVAL;
}
for(i=0;i<KEY_NUM;i++){
irqdevyqx.irqkeydesc[i].gpio = of_get_named_gpio(irqdevyqx.nd,"key-gpios",i);
if(irqdevyqx.irqkeydesc[i].gpio<0){
printk("can't get key%d\r\n",i);
return -EINVAL;
}
}
for(i=0;i<KEY_NUM;i++){
memset(irqdevyqx.irqkeydesc[i].name,0,sizeof(irqdevyqx.irqkeydesc[i].name));
sprintf(irqdevyqx.irqkeydesc[i].name,"KEY%d",i);
gpio_request(irqdevyqx.irqkeydesc[i].gpio,irqdevyqx.irqkeydesc[i].name);
gpio_direction_input(irqdevyqx.irqkeydesc[i].gpio);
irqdevyqx.irqkeydesc[i].irqnum = irq_of_parse_and_map(irqdevyqx.nd,i);
printk("key%d:gpio=%d,irqnum=%d\r\n",i,irqdevyqx.irqkeydesc[i].gpio,irqdevyqx.irqkeydesc[i].irqnum);
}
irqdevyqx.irqkeydesc[0].irqhandleyqx = key0_irqhandle;
irqdevyqx.irqkeydesc[0].value = KEY_0;
for(i=0;i<KEY_NUM;i++){
retvalue = request_irq(irqdevyqx.irqkeydesc[i].irqnum,irqdevyqx.irqkeydesc[i].irqhandleyqx,
IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
irqdevyqx.irqkeydesc[i].name,&irqdevyqx);
if(retvalue<0){
printk("irq %d request failed!\r\n",irqdevyqx.irqkeydesc[i].irqnum);
return -EINVAL;
}
}
init_timer(&irqdevyqx.timer);
irqdevyqx.timer.function = timer_function;
irqdevyqx.inputdev = input_allocate_device();
irqdevyqx.inputdev->name = DEV_NAME;
irqdevyqx.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
input_set_capability(irqdevyqx.inputdev,EV_KEY,KEY_0);
retvalue = input_register_device(irqdevyqx.inputdev);
if(retvalue){
printk("register input device failed!\r\n");
return retvalue;
}
return 0;
}
static int irqim6ull_init(void){
atomic_set(&irqdevyqx.keyvalue,INVAKEY);
atomic_set(&irqdevyqx.releasekey,0);
keyio_init();
return 0;
}
static void irqim6ull_exit(void){
unsigned int i =0;
del_timer_sync(&irqdevyqx.timer);
for(i=0;i<KEY_NUM;i++){
free_irq(irqdevyqx.irqkeydesc[i].irqnum,&irqdevyqx);
gpio_free(irqdevyqx.irqkeydesc[i].gpio);
}
input_unregister_device(irqdevyqx.inputdev);
input_free_device(irqdevyqx.inputdev);
}
module_init(irqim6ull_init);
module_exit(irqim6ull_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("yqx");
2.应用程序
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>
#include <linux/input.h>
#define KEYYQXVALUE 0XF0
struct input_event inputeventy;
int main(int argc,char * argv[]){
int fd,retvalue;
char *filename;
unsigned char keyvalue;
retvalue = -1;
if(argc != 2){
printf("输入参数错误,请重新输入12\r");
return -1;
}
filename = argv[1];
fd = open(filename,O_RDWR);
if(fd < 0){
printf("打开设备文件失败\r\n");
return -1;
}
while(1){
retvalue = read(fd,&inputeventy,sizeof(inputeventy));
if(retvalue>0){
switch(inputeventy.type){
case EV_KEY:
if(inputeventy.code < BTN_MISC){
printf("key %d %s\r\n",inputeventy.code,inputeventy.value?"press":"release");
} else {
printf("buttton %d %s\r\n",inputeventy.code,inputeventy.value?"press":"release");
}
break;
case EV_REL:
break;
case EV_ABS:
break;
case EV_MSC:
break;
}
} else {
printf("读取数据失败\r\n");
}
}
retvalue = close(fd);
if(retvalue < 0){
printf("关闭文件失败\r\n");
return -1;
}
return 0;
}
十三.I2C驱动
首先来看一下 I2C 总线,在讲 platform 的时候就说过, platform 是虚拟出来的一条总线,目的是为了实现总线、设备、驱动框架。对于 I2C 而言,不需要虚拟出一条总线,直接使用 I2C总线即可。 I2C 总线驱动重点是 I2C 适配器(也就是 SOC 的 I2C 接口控制器)驱动,这里要用到两个重要的数据结构: i2c_adapter 和 i2c_algorithm, Linux 内核将 SOC 的 I2C 适配器(控制器)
实验程序
1.设备树
&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
ap3216c@1e {
compatible = "alientek,ap3216c";
reg = <0x1e>;
};
};
2.驱动程序
#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_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "ap3216creg.h"
#define AP3216C_CNT 1
#define AP3216C_NAME "ap3216c"
struct ap3216c_dev {
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
struct device_node *nd;
int major;
void *private_data;
unsigned short ir, als, ps;
};
static struct ap3216c_dev ap3216cdev;
static int ap3216c_read_regs(struct ap3216c_dev *dev, u8 reg, void *val, int len)
{
int ret;
struct i2c_msg msg[2];
struct i2c_client *client = (struct i2c_client *)dev->private_data;
msg[0].addr = client->addr;
msg[0].flags = 0;
msg[0].buf = ®
msg[0].len = 1;
msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD;
msg[1].buf = val;
msg[1].len = len;
ret = i2c_transfer(client->adapter, msg, 2);
if(ret == 2) {
ret = 0;
} else {
printk("i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len);
ret = -EREMOTEIO;
}
return ret;
}
static s32 ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg, u8 *buf, u8 len)
{
u8 b[256];
struct i2c_msg msg;
struct i2c_client *client = (struct i2c_client *)dev->private_data;
b[0] = reg;
memcpy(&b[1],buf,len);
msg.addr = client->addr;
msg.flags = 0;
msg.buf = b;
msg.len = len + 1;
return i2c_transfer(client->adapter, &msg, 1);
}
static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev, u8 reg)
{
u8 data = 0;
ap3216c_read_regs(dev, reg, &data, 1);
return data;
#if 0
struct i2c_client *client = (struct i2c_client *)dev->private_data;
return i2c_smbus_read_byte_data(client, reg);
#endif
}
static void ap3216c_write_reg(struct ap3216c_dev *dev, u8 reg, u8 data)
{
u8 buf = 0;
buf = data;
ap3216c_write_regs(dev, reg, &buf, 1);
}
void ap3216c_readdata(struct ap3216c_dev *dev)
{
unsigned char i =0;
unsigned char buf[6];
for(i = 0; i < 6; i++)
{
buf[i] = ap3216c_read_reg(dev, AP3216C_IRDATALOW + i);
}
if(buf[0] & 0X80)
dev->ir = 0;
else
dev->ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03);
dev->als = ((unsigned short)buf[3] << 8) | buf[2];
if(buf[4] & 0x40)
dev->ps = 0;
else
dev->ps = ((unsigned short)(buf[5] & 0X3F) << 4) | (buf[4] & 0X0F);
}
static int ap3216c_open(struct inode *inode, struct file *filp)
{
filp->private_data = &ap3216cdev;
ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x04);
mdelay(50);
ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0X03);
return 0;
}
static ssize_t ap3216c_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
short data[3];
long err = 0;
struct ap3216c_dev *dev = (struct ap3216c_dev *)filp->private_data;
ap3216c_readdata(dev);
data[0] = dev->ir;
data[1] = dev->als;
data[2] = dev->ps;
err = copy_to_user(buf, data, sizeof(data));
return 0;
}
static int ap3216c_release(struct inode *inode, struct file *filp)
{
return 0;
}
static const struct file_operations ap3216c_ops = {
.owner = THIS_MODULE,
.open = ap3216c_open,
.read = ap3216c_read,
.release = ap3216c_release,
};
static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
if (ap3216cdev.major) {
ap3216cdev.devid = MKDEV(ap3216cdev.major, 0);
register_chrdev_region(ap3216cdev.devid, AP3216C_CNT, AP3216C_NAME);
} else {
alloc_chrdev_region(&ap3216cdev.devid, 0, AP3216C_CNT, AP3216C_NAME);
ap3216cdev.major = MAJOR(ap3216cdev.devid);
}
cdev_init(&ap3216cdev.cdev, &ap3216c_ops);
cdev_add(&ap3216cdev.cdev, ap3216cdev.devid, AP3216C_CNT);
ap3216cdev.class = class_create(THIS_MODULE, AP3216C_NAME);
if (IS_ERR(ap3216cdev.class)) {
return PTR_ERR(ap3216cdev.class);
}
ap3216cdev.device = device_create(ap3216cdev.class, NULL, ap3216cdev.devid, NULL, AP3216C_NAME);
if (IS_ERR(ap3216cdev.device)) {
return PTR_ERR(ap3216cdev.device);
}
ap3216cdev.private_data = client;
return 0;
}
static int ap3216c_remove(struct i2c_client *client)
{
cdev_del(&ap3216cdev.cdev);
unregister_chrdev_region(ap3216cdev.devid, AP3216C_CNT);
device_destroy(ap3216cdev.class, ap3216cdev.devid);
class_destroy(ap3216cdev.class);
return 0;
}
static const struct i2c_device_id ap3216c_id[] = {
{"alientek,ap3216c", 0},
{}
};
static const struct of_device_id ap3216c_of_match[] = {
{ .compatible = "alientek,ap3216c" },
{ }
};
static struct i2c_driver ap3216c_driver = {
.probe = ap3216c_probe,
.remove = ap3216c_remove,
.driver = {
.owner = THIS_MODULE,
.name = "ap3216c",
.of_match_table = ap3216c_of_match,
},
.id_table = ap3216c_id,
};
static int __init ap3216c_init(void)
{
int ret = 0;
ret = i2c_add_driver(&ap3216c_driver);
return ret;
}
static void __exit ap3216c_exit(void)
{
i2c_del_driver(&ap3216c_driver);
}
module_init(ap3216c_init);
module_exit(ap3216c_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");
3.应用程序
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int fd;
char *filename;
unsigned short databuf[3];
unsigned short ir, als, ps;
int ret = 0;
if (argc != 2) {
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;
}
while (1) {
ret = read(fd, databuf, sizeof(databuf));
if(ret == 0) {
ir = databuf[0];
als = databuf[1];
ps = databuf[2];
printf("ir = %d, als = %d, ps = %d\r\n", ir, als, ps);
}
usleep(200000);
}
close(fd);
return 0;
}
十四.SPI驱动
SPI 驱动框架和 I2C 很类似,都分为主机控制器驱动和设备驱动,主机控制器也就是 SOC的 SPI 控制器接口。,我们编写好 SPI 控制器驱动以后就可以直接使用了,不管是什么 SPI 设备, SPI 控制器部分的驱动都是一样,我们的重点就落在了种类繁多的 SPI 设备驱动。
实验程序
1.设备树:
pinctrl_ecspi3: ecspi3grp {
fsl,pins = <
MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20 0x10B0
MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK 0X10B1
MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI 0x10B1
MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO 0x10B1
>;
};
2.驱动程序
#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_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <linux/spi/spi.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "icm20608reg.h"
#define ICM20608_CNT 1
#define ICM20608_NAME "icm20608"
struct icm20608_dev {
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
struct device_node *nd;
int major;
void *private_data;
int cs_gpio;
signed int gyro_x_adc;
signed int gyro_y_adc;
signed int gyro_z_adc;
signed int accel_x_adc;
signed int accel_y_adc;
signed int accel_z_adc;
signed int temp_adc;
};
static struct icm20608_dev icm20608dev;
static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len)
{
int ret;
unsigned char txdata[len];
struct spi_message m;
struct spi_transfer *t;
struct spi_device *spi = (struct spi_device *)dev->private_data;
gpio_set_value(dev->cs_gpio, 0);
t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
txdata[0] = reg | 0x80;
t->tx_buf = txdata;
t->len = 1;
spi_message_init(&m);
spi_message_add_tail(t, &m);
ret = spi_sync(spi, &m);
txdata[0] = 0xff;
t->rx_buf = buf;
t->len = len;
spi_message_init(&m);
spi_message_add_tail(t, &m);
ret = spi_sync(spi, &m);
kfree(t);
gpio_set_value(dev->cs_gpio, 1);
return ret;
}
static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, u8 len)
{
int ret;
unsigned char txdata[len];
struct spi_message m;
struct spi_transfer *t;
struct spi_device *spi = (struct spi_device *)dev->private_data;
t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
gpio_set_value(dev->cs_gpio, 0);
txdata[0] = reg & ~0x80;
t->tx_buf = txdata;
t->len = 1;
spi_message_init(&m);
spi_message_add_tail(t, &m);
ret = spi_sync(spi, &m);
t->tx_buf = buf;
t->len = len;
spi_message_init(&m);
spi_message_add_tail(t, &m);
ret = spi_sync(spi, &m);
kfree(t);
gpio_set_value(dev->cs_gpio, 1);
return ret;
}
static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg)
{
u8 data = 0;
icm20608_read_regs(dev, reg, &data, 1);
return data;
}
static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value)
{
u8 buf = value;
icm20608_write_regs(dev, reg, &buf, 1);
}
void icm20608_readdata(struct icm20608_dev *dev)
{
unsigned char data[14];
icm20608_read_regs(dev, ICM20_ACCEL_XOUT_H, data, 14);
dev->accel_x_adc = (signed short)((data[0] << 8) | data[1]);
dev->accel_y_adc = (signed short)((data[2] << 8) | data[3]);
dev->accel_z_adc = (signed short)((data[4] << 8) | data[5]);
dev->temp_adc = (signed short)((data[6] << 8) | data[7]);
dev->gyro_x_adc = (signed short)((data[8] << 8) | data[9]);
dev->gyro_y_adc = (signed short)((data[10] << 8) | data[11]);
dev->gyro_z_adc = (signed short)((data[12] << 8) | data[13]);
}
static int icm20608_open(struct inode *inode, struct file *filp)
{
filp->private_data = &icm20608dev;
return 0;
}
static ssize_t icm20608_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
signed int data[7];
long err = 0;
struct icm20608_dev *dev = (struct icm20608_dev *)filp->private_data;
icm20608_readdata(dev);
data[0] = dev->gyro_x_adc;
data[1] = dev->gyro_y_adc;
data[2] = dev->gyro_z_adc;
data[3] = dev->accel_x_adc;
data[4] = dev->accel_y_adc;
data[5] = dev->accel_z_adc;
data[6] = dev->temp_adc;
err = copy_to_user(buf, data, sizeof(data));
return 0;
}
static int icm20608_release(struct inode *inode, struct file *filp)
{
return 0;
}
static const struct file_operations icm20608_ops = {
.owner = THIS_MODULE,
.open = icm20608_open,
.read = icm20608_read,
.release = icm20608_release,
};
void icm20608_reginit(void)
{
u8 value = 0;
icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x80);
mdelay(50);
icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x01);
mdelay(50);
value = icm20608_read_onereg(&icm20608dev, ICM20_WHO_AM_I);
printk("ICM20608 ID = %#X\r\n", value);
icm20608_write_onereg(&icm20608dev, ICM20_SMPLRT_DIV, 0x00);
icm20608_write_onereg(&icm20608dev, ICM20_GYRO_CONFIG, 0x18);
icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG, 0x18);
icm20608_write_onereg(&icm20608dev, ICM20_CONFIG, 0x04);
icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG2, 0x04);
icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_2, 0x00);
icm20608_write_onereg(&icm20608dev, ICM20_LP_MODE_CFG, 0x00);
icm20608_write_onereg(&icm20608dev, ICM20_FIFO_EN, 0x00);
}
static int icm20608_probe(struct spi_device *spi)
{
int ret = 0;
if (icm20608dev.major) {
icm20608dev.devid = MKDEV(icm20608dev.major, 0);
register_chrdev_region(icm20608dev.devid, ICM20608_CNT, ICM20608_NAME);
} else {
alloc_chrdev_region(&icm20608dev.devid, 0, ICM20608_CNT, ICM20608_NAME);
icm20608dev.major = MAJOR(icm20608dev.devid);
}
cdev_init(&icm20608dev.cdev, &icm20608_ops);
cdev_add(&icm20608dev.cdev, icm20608dev.devid, ICM20608_CNT);
icm20608dev.class = class_create(THIS_MODULE, ICM20608_NAME);
if (IS_ERR(icm20608dev.class)) {
return PTR_ERR(icm20608dev.class);
}
icm20608dev.device = device_create(icm20608dev.class, NULL, icm20608dev.devid, NULL, ICM20608_NAME);
if (IS_ERR(icm20608dev.device)) {
return PTR_ERR(icm20608dev.device);
}
icm20608dev.nd = of_find_node_by_path("/soc/aips-bus@02000000/spba-bus@02000000/ecspi@02010000");
if(icm20608dev.nd == NULL) {
printk("ecspi3 node not find!\r\n");
return -EINVAL;
}
icm20608dev.cs_gpio = of_get_named_gpio(icm20608dev.nd, "cs-gpio", 0);
if(icm20608dev.cs_gpio < 0) {
printk("can't get cs-gpio");
return -EINVAL;
}
ret = gpio_direction_output(icm20608dev.cs_gpio, 1);
if(ret < 0) {
printk("can't set gpio!\r\n");
}
spi->mode = SPI_MODE_0;
spi_setup(spi);
icm20608dev.private_data = spi;
icm20608_reginit();
return 0;
}
static int icm20608_remove(struct spi_device *spi)
{
cdev_del(&icm20608dev.cdev);
unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT);
device_destroy(icm20608dev.class, icm20608dev.devid);
class_destroy(icm20608dev.class);
return 0;
}
static const struct spi_device_id icm20608_id[] = {
{"alientek,icm20608", 0},
{}
};
static const struct of_device_id icm20608_of_match[] = {
{ .compatible = "alientek,icm20608" },
{ }
};
static struct spi_driver icm20608_driver = {
.probe = icm20608_probe,
.remove = icm20608_remove,
.driver = {
.owner = THIS_MODULE,
.name = "icm20608",
.of_match_table = icm20608_of_match,
},
.id_table = icm20608_id,
};
static int __init icm20608_init(void)
{
return spi_register_driver(&icm20608_driver);
}
static void __exit icm20608_exit(void)
{
spi_unregister_driver(&icm20608_driver);
}
module_init(icm20608_init);
module_exit(icm20608_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("yqx");
3.应用程序:
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int fd;
char *filename;
signed int databuf[7];
unsigned char data[14];
signed int gyro_x_adc, gyro_y_adc, gyro_z_adc;
signed int accel_x_adc, accel_y_adc, accel_z_adc;
signed int temp_adc;
float gyro_x_act, gyro_y_act, gyro_z_act;
float accel_x_act, accel_y_act, accel_z_act;
float temp_act;
int ret = 0;
if (argc != 2) {
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;
}
while (1) {
ret = read(fd, databuf, sizeof(databuf));
if(ret == 0) {
gyro_x_adc = databuf[0];
gyro_y_adc = databuf[1];
gyro_z_adc = databuf[2];
accel_x_adc = databuf[3];
accel_y_adc = databuf[4];
accel_z_adc = databuf[5];
temp_adc = databuf[6];
gyro_x_act = (float)(gyro_x_adc) / 16.4;
gyro_y_act = (float)(gyro_y_adc) / 16.4;
gyro_z_act = (float)(gyro_z_adc) / 16.4;
accel_x_act = (float)(accel_x_adc) / 2048;
accel_y_act = (float)(accel_y_adc) / 2048;
accel_z_act = (float)(accel_z_adc) / 2048;
temp_act = ((float)(temp_adc) - 25 ) / 326.8 + 25;
printf("\r\n原始值:\r\n");
printf("gx = %d, gy = %d, gz = %d\r\n", gyro_x_adc, gyro_y_adc, gyro_z_adc);
printf("ax = %d, ay = %d, az = %d\r\n", accel_x_adc, accel_y_adc, accel_z_adc);
printf("temp = %d\r\n", temp_adc);
printf("实际值:");
printf("act gx = %.2f°/S, act gy = %.2f°/S, act gz = %.2f°/S\r\n", gyro_x_act, gyro_y_act, gyro_z_act);
printf("act ax = %.2fg, act ay = %.2fg, act az = %.2fg\r\n", accel_x_act, accel_y_act, accel_z_act);
printf("act temp = %.2f°C\r\n", temp_act);
}
usleep(100000);
}
close(fd);
return 0;
}
尾言
有关驱动开发内容就这些,本文绝大多出自I.MX6U 嵌入式 Linux 驱动开发指南 V1.5.1中,关于驱动开发,笔者最大的经验就是多写代码,多做有关的项目应用。
|