嵌入式LINUX驱动学习心得--基于正点原子IMX6ULL

嵌入式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() //添加模块 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;
    /*注册chrdevYqx设备*/
    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);//chrdevYqx模块加载函数
module_exit(chrdevYqx_exit);//chrdevYqx模块卸载函数

/*作者信息*/
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);
//宏 SW_MUX_GPIO1_IO03_BASE 是寄存器物理地址, SW_MUX_GPIO1_IO03 是
//映射后的虚拟地址。对于 I.MX6ULL 来说一个寄存器是 4 字节(32 位)的,因
//此映射的内存长度为 4。映射完成以后直接对 SW_MUX_GPIO1_IO03 进行读写操作即可。

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)
//readb、 readw 和 readl 这三个函数分别对应 8bit、 16bit 和 32bit 读
//操作,参数 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)
//writeb、 writew 和 writel 这三个函数分别对应 8bit、 16bit 和 32bit 
//写操作,参数 value 是要写入的数值, 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; //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;//将私有数据定义到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);

    /*使能GPIO1时钟*/
    val = readl(IMX6U_CCM_CCGR1);
    val &= ~(3<<26);//清除过去时钟设置
    val |= (3<<26);//使能GPIO1时钟
    writel(val,IMX6U_CCM_CCGR1);

    /*设置GPIO1——IO03端口属性*/

    writel(5,SW_MUX_GPIO1_IO03);//GPIO3IO模式

    writel(0X10B0,SW_PAD_GPIO1_IO03);//设置IO口电气属性

    val = readl(GPIO1_GDIR);
    val &= ~(1<<3);//清除以前的设置
    val |= (1<<3) ;//设置为输出
    writel(val,GPIO1_GDIR);

    /*初始默认关闭IO3端口*/
    val = readl(GPIO1_DR);
    val |= (1<<3);
    writel(val,GPIO1_DR);

    /*注册字符设备驱动*/
    if(newchrdev.major){//给定了主设备号,手动申请设备号
        newchrdev.devid = MKDEV(newchrdev.major,0);//使用MKDEV()构建设备号,次设备号选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;注册设备*/
    cdev_init(&newchrdev.cdev,&newchrdev_fops);
    cdev_add(&newchrdev.cdev,newchrdev.devid,NEWCHRDEV_CNT);

    /*自动创建设备节点*/
    /*1.创建类*/
    newchrdev.class = class_create(THIS_MODULE,NEWCHRDEV_NAME);
    if(IS_ERR(newchrdev.class)){
        return PTR_ERR(newchrdev.class);
    }
    /*2.创建设备*/
    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);
    /*1.删除设备*/
    device_destroy(newchrdev.class,newchrdev.devid);
    /*2.删除类*/
    class_destroy(newchrdev.class);
    /*3.删除cdev*/
    cdev_del(&newchrdev.cdev);
    /*4.删除设备号*/
    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文件中加入设备节点
/*YQX 2022/4/21*/
	yqxled {
		#address-cells = <1>;
		#size-cells = <1>;
		compatible = "yqx,yqxLed";
		status = "okay";
		reg = < 0X020C406C 0x04 /*CCM_CCR1_BASE*/
				0X020E0068 0x04 /*SW_MUX_GPIO1_IO03_BASE*/
				0X020E02F4 0x04 /*SW_PAD_GPIO1_IO03_BASE*/
				0X0209C000 0x04 /*GPIO1_DR_BASE*/
				0X0209C004 0x04>; /*GPIO1_GDIR_BASE*/
	};
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;

//dtsled设备结构体
typedef struct dtsled_dev{
    dev_t devid;//设备号
    struct cdev cdev;//cdev
    struct class *class;//类
    struct device *device;//节点设备
    int major;//主设备号
    int minor; //次设备号
    struct device_node *nd;//设备树节点
} dtsled_dev;

dtsled_dev dtsled;//led设备


void led_switch(int led_flag){
    u32 val;
    if(led_flag == 1){ //开LED
        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;
}

//dtsled设备操作函数集初始化
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);//读取status属性
    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);

    /*初始化IO复用功能*/
    writel(5,SW_MUX_GPIO1_IO03);

    /*设置IO电气属性*/
    writel(0x10b0,SW_PAD_GPIO1_IO03);

    /*设置IO03为输出*/
    val = readl(GPIO1_GDIR);
    val &= ~(1<<3);
    val |= (1<<3);
    writel(val,GPIO1_GDIR);

    /*默认关闭led,电位高电位*/
    val = readl(GPIO1_DR);
    val |=(1<<3);
    writel(val,GPIO1_DR);

    /*注册字符设备驱动*/
    /*注册字符设备驱动*/
    if(dtsled.major){//给定了主设备号,手动申请设备号
        dtsled.devid = MKDEV(dtsled.major,0);//使用MKDEV()构建设备号,次设备号选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;注册设备*/
    cdev_init(&dtsled.cdev,&dtsled_fops);
    cdev_add(&dtsled.cdev,dtsled.devid,DEVICE_CNT);

    /*自动创建设备节点*/
    /*1.创建类*/
    dtsled.class = class_create(THIS_MODULE,DEVICE_NAME);
    if(IS_ERR(dtsled.class)){
        return PTR_ERR(dtsled.class);
    }
    /*2.创建设备*/
    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;
     /*释放内存前关闭led*/
    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;//cdev设备
    struct class* class;//类
    struct device* device;//设备
    int major;//主设备号
    int minor; //副设备号
    struct device_node *nd; //设备树节点
    int led_gpio;//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;
    /*设置LED所使用的GPIO*/
    /*1.获取设备节点:gpioled*/
    yqxgpioled.nd = of_find_node_by_path("/yqxgpioled");
    if(yqxgpioled.nd == NULL){
        printk("访问gpio节点失败\r\n");
        return -EINVAL;
    } else {
        printk("gpio设备节点已找到\r\n");
    }

    /*获取gpio编号*/
    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);

    /*设置gpio1_io03为输出*/
    ret = gpio_direction_output(yqxgpioled.led_gpio,1);
    if(ret < 0){
        printk("gpio1_3设置IO输出失败\r\n");
    }
    /*1.分配设备号*/
    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);

    /*2.注册设备*/
    cdev_init(&yqxgpioled.cdev,&gpioled_fops);
    cdev_add(&yqxgpioled.cdev,yqxgpioled.devid,DEV_CNT);

    /*3.自动创建设备节点*/
    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);//释放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;//cdev设备
    struct class* class;//类
    struct device* device;//设备
    int major;//主设备号
    int minor; //副设备号
    struct device_node *nd; //设备树节点
    int led_gpio;//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);//初始化原子量,置1
    /*设置LED所使用的GPIO*/
    /*1.获取设备节点:gpioled*/
    yqxgpioled.nd = of_find_node_by_path("/yqxgpioled");
    if(yqxgpioled.nd == NULL){
        printk("访问gpio节点失败\r\n");
        return -EINVAL;
    } else {
        printk("gpio设备节点已找到\r\n");
    }

    /*获取gpio编号*/
    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);

    /*设置gpio1_io03为输出*/
    ret = gpio_direction_output(yqxgpioled.led_gpio,1);
    if(ret < 0){
        printk("gpio1_3设置IO输出失败\r\n");
    }
    /*1.分配设备号*/
    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);

    /*2.注册设备*/
    cdev_init(&yqxgpioled.cdev,&gpioled_fops);
    cdev_add(&yqxgpioled.cdev,yqxgpioled.devid,DEV_CNT);

    /*3.自动创建设备节点*/
    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);//释放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;//cdev设备
    struct class* class;//类
    struct device* device;//设备
    int major;//主设备号
    int minor; //副设备号
    struct device_node *nd; //设备树节点
    int led_gpio;//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);//初始化自旋锁
    /*设置LED所使用的GPIO*/
    /*1.获取设备节点:gpioled*/
    yqxgpioled.nd = of_find_node_by_path("/yqxgpioled");
    if(yqxgpioled.nd == NULL){
        printk("访问gpio节点失败\r\n");
        return -EINVAL;
    } else {
        printk("gpio设备节点已找到\r\n");
    }

    /*获取gpio编号*/
    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);

    /*设置gpio1_io03为输出*/
    ret = gpio_direction_output(yqxgpioled.led_gpio,1);
    if(ret < 0){
        printk("gpio1_3设置IO输出失败\r\n");
    }
    /*1.分配设备号*/
    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);

    /*2.注册设备*/
    cdev_init(&yqxgpioled.cdev,&gpioled_fops);
    cdev_add(&yqxgpioled.cdev,yqxgpioled.devid,DEV_CNT);

    /*3.自动创建设备节点*/
    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);//释放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;//cdev设备
    struct class* class;//类
    struct device* device;//设备
    int major;//主设备号
    int minor; //副设备号
    struct device_node *nd; //设备树节点
    int led_gpio;//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);//信号量值为2
    /*设置LED所使用的GPIO*/
    /*1.获取设备节点:gpioled*/
    yqxgpioled.nd = of_find_node_by_path("/yqxgpioled");
    if(yqxgpioled.nd == NULL){
        printk("访问gpio节点失败\r\n");
        return -EINVAL;
    } else {
        printk("gpio设备节点已找到\r\n");
    }

    /*获取gpio编号*/
    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);

    /*设置gpio1_io03为输出*/
    ret = gpio_direction_output(yqxgpioled.led_gpio,1);
    if(ret < 0){
        printk("gpio1_3设置IO输出失败\r\n");
    }
    /*1.分配设备号*/
    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);

    /*2.注册设备*/
    cdev_init(&yqxgpioled.cdev,&gpioled_fops);
    cdev_add(&yqxgpioled.cdev,yqxgpioled.devid,DEV_CNT);

    /*3.自动创建设备节点*/
    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);//释放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;//cdev设备
    struct class* class;//类
    struct device* device;//设备
    int major;//主设备号
    int minor; //副设备号
    struct device_node *nd; //设备树节点
    int led_gpio;//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);//信号量值为2
    /*设置LED所使用的GPIO*/
    /*1.获取设备节点:gpioled*/
    yqxgpioled.nd = of_find_node_by_path("/yqxgpioled");
    if(yqxgpioled.nd == NULL){
        printk("访问gpio节点失败\r\n");
        return -EINVAL;
    } else {
        printk("gpio设备节点已找到\r\n");
    }

    /*获取gpio编号*/
    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);

    /*设置gpio1_io03为输出*/
    ret = gpio_direction_output(yqxgpioled.led_gpio,1);
    if(ret < 0){
        printk("gpio1_3设置IO输出失败\r\n");
    }
    /*1.分配设备号*/
    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);

    /*2.注册设备*/
    cdev_init(&yqxgpioled.cdev,&gpioled_fops);
    cdev_add(&yqxgpioled.cdev,yqxgpioled.devid,DEV_CNT);

    /*3.自动创建设备节点*/
    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);//释放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;//cdev设备
    struct class *class;//类指针
    struct device *device;//设备
    int major;//主设备号
    int minor;//子设备号
    struct device_node *nd;//设备树节点
    int led_gpio;//gpio编号
    struct timer_list timer;//定义一个定时器
    int timedata;//定时时间
    spinlock_t lock;//定义自旋锁
}time_dev;

time_dev timeryqx;

static int led_init(void){//初始化led
    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;
    //printk("进入自旋锁1\r\n");
    spin_lock_irqsave(&dev->lock,flags);
    timeperiod = dev->timedata;
    spin_unlock_irqrestore(&dev->lock,flags);
    //printk("退出自旋锁1\r\n");
    sta= !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:
            //printk("进入自旋锁2\r\n");
            spin_lock_irqsave(&dev->lock,flags);
            timeperiod = dev->timedata;
            spin_unlock_irqrestore(&dev->lock,flags);
            //printk("退出自旋锁2\r\n");
            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;
            }
            //printk("进入自旋锁3\r\n");
            spin_lock_irqsave(&dev->lock,flags);
            dev->timedata = timeperiod;
            spin_unlock_irqrestore(&dev->lock,flags);
            //printk("退出自旋锁3\r\n");
            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);//关闭led
    gpio_free(timeryqx.led_gpio);//释放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);
    /*向linux添加设备*/
    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);

    /*初始化timer*/
    init_timer(&timeryqx.timer);//初始化定时器
    timeryqx.timer.function = timer_function;
    timeryqx.timer.data = (unsigned long)& timeryqx;//将timeryqx结构体变量的地址传递给timerfunction
    return 0;
}

static void __exit timeyqx_exit(void){
    gpio_set_value(timeryqx.led_gpio,1);//关闭led
    gpio_free(timeryqx.led_gpio);//释放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;
                //ioctl(fd,cmdyqx,&argyqx);//实际上TIMER_OPEN,TIMER_CLOSE,调用时,
                //最后一个参数&argyqx无意义,因为不传递值进入,只有TIMER_SET中有写命名,最后一个参数才有意义
                break;
            case 2:
                printf("定时器关闭\r\n");
                cmdyqx = TIMER_CLOSE;
                //ioctl(fd,cmdyqx,&argyqx);//实际上TIMER_OPEN,TIMER_CLOSE,调用时,
                //最后一个参数&argyqx无意义,因为不传递值进入,只有TIMER_SET中有写命名,最后一个参数才有意义
                break;
            case 3:
                cmdyqx = TIMER_SET;
                printf("please input timerValue:\r\n");
                scanf("%d",&argyqx);
                if(retvalue !=1){
                    gets(str);//教程老师经验,输入不符清除一行缓存,防止卡死
                }
                //ioctl(fd,cmdyqx,&argyqx);//实际上TIMER_OPEN,TIMER_CLOSE,调用时,
                //最后一个参数&argyqx无意义,因为不传递值进入,只有TIMER_SET中有写命名,最后一个参数才有意义
                break;
            default:
                printf("退出命令\r\n");
                exit_yqx = 1;
                break;
        }
        if(exit_yqx){
            break;
        }
        ioctl(fd,cmdyqx,&argyqx);//实际上TIMER_OPEN,TIMER_CLOSE,调用时,
                //最后一个参数&argyqx无意义,因为不传递值进入,只有TIMER_SET中有写命名,最后一个参数才有意义
        
    }

    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.设备树
/*yqx key*/
	/*2022/5/1*/
	/*2022/5/4 irqhandle*/
	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 //KEY0按键值
#define KEY_NUM 1 //按键数量
/*中断IO描述结构体*/
struct irq_keydesc {
    int gpio;//中断所用gpio编号
    int irqnum;//中断号
    unsigned char value;//按键对应的键值
    char name[10];//中断名字
    irqreturn_t (*irqhandleyqx)(int,void *);//中断服务函数
};

/*irqim6ull设备结构体*/
struct imx6uirq_dev{
    dev_t devid;//设备号
    struct cdev 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));//定时器定时10ms
    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);//读取IO值
     if(value == 0){//按键按下
         atomic_set(&dev->keyvalue,keydesc->value);
     } else {//按键松开
         atomic_set(&dev->keyvalue,0x80|keydesc->value);
         atomic_set(&dev->releasekey,1);
     }
}

/*io中断初始化*/
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;
    }

    /*初始化io*/
    for(i=0;i<KEY_NUM;i++){
        irqdevyqx.irqkeydesc[i].gpio = of_get_named_gpio(irqdevyqx.nd,"key-gpios",i);//获取gpio编号
        if(irqdevyqx.irqkeydesc[i].gpio<0){
            printk("can't get key%d\r\n",i);
            return -EINVAL;
        }

    }
    /*提取key所使用的IO的中断模式*/
    for(i=0;i<KEY_NUM;i++){
        memset(irqdevyqx.irqkeydesc[i].name,0,sizeof(irqdevyqx.irqkeydesc[i].name));//初始化IO中断名,全部清0
        sprintf(irqdevyqx.irqkeydesc[i].name,"KEY%d",i);//设置中断名
        gpio_request(irqdevyqx.irqkeydesc[i].gpio,irqdevyqx.irqkeydesc[i].name);//申请对应中断io
        gpio_direction_input(irqdevyqx.irqkeydesc[i].gpio);//设置中断IO为输入
        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;//设置key0中断服务函数
    irqdevyqx.irqkeydesc[0].value = KEY0VALUE;//key0对应的键值
    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);
    /*向linux添加设备*/
    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);//初始化key0按键值
    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 //KEY0按键值
#define KEY_NUM 1 //按键数量
/*中断IO描述结构体*/
struct irq_keydesc {
    int gpio;//中断所用gpio编号
    int irqnum;//中断号
    unsigned char value;//按键对应的键值
    char name[10];//中断名字
    irqreturn_t (*irqhandleyqx)(int,void *);//中断服务函数
};

/*irqim6ull设备结构体*/
struct imx6uirq_dev{
    dev_t devid;//设备号
    struct cdev 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));//定时器定时10ms
    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);//读取IO值
     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));//唤醒进程
     }
}

/*io中断初始化*/
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;
    }

    /*初始化io*/
    for(i=0;i<KEY_NUM;i++){
        irqdevyqx.irqkeydesc[i].gpio = of_get_named_gpio(irqdevyqx.nd,"key-gpios",i);//获取gpio编号
        if(irqdevyqx.irqkeydesc[i].gpio<0){
            printk("can't get key%d\r\n",i);
            return -EINVAL;
        }

    }
    /*提取key所使用的IO的中断模式*/
    for(i=0;i<KEY_NUM;i++){
        memset(irqdevyqx.irqkeydesc[i].name,0,sizeof(irqdevyqx.irqkeydesc[i].name));//初始化IO中断名,全部清0
        sprintf(irqdevyqx.irqkeydesc[i].name,"KEY%d",i);//设置中断名
        gpio_request(irqdevyqx.irqkeydesc[i].gpio,irqdevyqx.irqkeydesc[i].name);//申请对应中断io
        gpio_direction_input(irqdevyqx.irqkeydesc[i].gpio);//设置中断IO为输入
        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;//设置key0中断服务函数
    irqdevyqx.irqkeydesc[0].value = KEY0VALUE;//key0对应的键值
    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;
    //DECLARE_WAITQUEUE(q_wait,current);//定义等待队列项,该等待队列项属于当前进程
    #if 0
    wait_event_interruptible(dev->h_wait,atomic_read(&dev->releasekey));//将该设备等待队列头加入等待事件中
    //使用等待队列来完成阻塞
    #endif
    /*使用等待队列来完成阻塞事件*/
    printk("开始阻塞\r\n");
    DECLARE_WAITQUEUE(q_wait,current);//创建并初始化一个等待队列项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);
    /*向linux添加设备*/
    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);//初始化key0按键值
    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 //KEY0按键值
#define KEY_NUM 1 //按键数量
/*中断IO描述结构体*/
struct irq_keydesc {
    int gpio;//中断所用gpio编号
    int irqnum;//中断号
    unsigned char value;//按键对应的键值
    char name[10];//中断名字
    irqreturn_t (*irqhandleyqx)(int,void *);//中断服务函数
};

/*irqim6ull设备结构体*/
struct imx6uirq_dev{
    dev_t devid;//设备号
    struct cdev 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));//定时器定时10ms
    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);//读取IO值
     if(value == 0){//按键按下
         atomic_set(&dev->keyvalue,keydesc->value);
     } else {//按键松开
         atomic_set(&dev->keyvalue,0x80|keydesc->value);
         atomic_set(&dev->releasekey,1);
        
     }
}

/*io中断初始化*/
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;
    }

    /*初始化io*/
    for(i=0;i<KEY_NUM;i++){
        irqdevyqx.irqkeydesc[i].gpio = of_get_named_gpio(irqdevyqx.nd,"key-gpios",i);//获取gpio编号
        if(irqdevyqx.irqkeydesc[i].gpio<0){
            printk("can't get key%d\r\n",i);
            return -EINVAL;
        }

    }
    /*提取key所使用的IO的中断模式*/
    for(i=0;i<KEY_NUM;i++){
        memset(irqdevyqx.irqkeydesc[i].name,0,sizeof(irqdevyqx.irqkeydesc[i].name));//初始化IO中断名,全部清0
        sprintf(irqdevyqx.irqkeydesc[i].name,"KEY%d",i);//设置中断名
        gpio_request(irqdevyqx.irqkeydesc[i].gpio,irqdevyqx.irqkeydesc[i].name);//申请对应中断io
        gpio_direction_input(irqdevyqx.irqkeydesc[i].gpio);//设置中断IO为输入
        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;//设置key0中断服务函数
    irqdevyqx.irqkeydesc[0].value = KEY0VALUE;//key0对应的键值
    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;
   
    //printk("开始阻塞\r\n");
    
    //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;

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;
    //DECLARE_WAITQUEUE(q_wait,current);//添加一个等待队列
    poll_wait(filp,&dev->h_wait,wait);//将当前等待队列放入等待队列头中

    if(atomic_read(&dev->releasekey)){//按键按下
        mask = POLLIN|POLLRDNORM;//返回PLLIN
    }
    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);
    /*向linux添加设备*/
    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);//初始化key0按键值
    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;
    //fd_set readfds;
    //struct timeval timeout;
    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;//500ms
        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)){//判断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 //KEY0按键值
#define KEY_NUM 1 //按键数量
/*中断IO描述结构体*/
struct irq_keydesc {
    int gpio;//中断所用gpio编号
    int irqnum;//中断号
    unsigned char value;//按键对应的键值
    char name[10];//中断名字
    irqreturn_t (*irqhandleyqx)(int,void *);//中断服务函数
};

/*irqim6ull设备结构体*/
struct imx6uirq_dev{
    dev_t devid;//设备号
    struct cdev 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));//定时器定时10ms
    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);//读取IO值
     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);//驱动程序向应用发信号
        }

     }
}

/*io中断初始化*/
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;
    }

    /*初始化io*/
    for(i=0;i<KEY_NUM;i++){
        irqdevyqx.irqkeydesc[i].gpio = of_get_named_gpio(irqdevyqx.nd,"key-gpios",i);//获取gpio编号
        if(irqdevyqx.irqkeydesc[i].gpio<0){
            printk("can't get key%d\r\n",i);
            return -EINVAL;
        }

    }
    /*提取key所使用的IO的中断模式*/
    for(i=0;i<KEY_NUM;i++){
        memset(irqdevyqx.irqkeydesc[i].name,0,sizeof(irqdevyqx.irqkeydesc[i].name));//初始化IO中断名,全部清0
        sprintf(irqdevyqx.irqkeydesc[i].name,"KEY%d",i);//设置中断名
        gpio_request(irqdevyqx.irqkeydesc[i].gpio,irqdevyqx.irqkeydesc[i].name);//申请对应中断io
        gpio_direction_input(irqdevyqx.irqkeydesc[i].gpio);//设置中断IO为输入
        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;//设置key0中断服务函数
    irqdevyqx.irqkeydesc[0].value = KEY0VALUE;//key0对应的键值
    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;

}

/*fasync函数*/
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);
    /*向linux添加设备*/
    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);//初始化key0按键值
    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;
    }
    /*设置信号SIGIO的处理函数*/
    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

/*leddev设备结构体*/
struct leddev_dev{
    dev_t devid;//设备号
    struct cdev cdev;//cdev
    struct class *class;//类
    struct device *device;
    int major;
    int minor;
    struct device_node *node;//设备树节点
    int led0; //gpioled编号
};

struct leddev_dev leddev;//led设备

/*ledswitch函数*/
void led0_switch(u8 sta){
    if(sta == LEDON){
        gpio_set_value(leddev.led0,0);
    } else if (sta == LEDOFF){
        gpio_set_value(leddev.led0,1);
    }
}

/*release函数*/
static int led_release(struct inode *inode,struct file *filp){
    return 0;
}

/*write函数*/
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;
}
/*open函数*/
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,
};

/*platform系统probe函数,驱动与设备相匹配后执行*/
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);
    }

    /*初始化IO*/
    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);//关闭led
    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"},
    { /*Sentinel*/ } //固定空白行
};
/*platform驱动结构体*/
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);//注册platform平台驱动
}

/*模块卸载函数*/
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

/*leddev设备结构体*/
struct leddev_dev{
    dev_t devid;//设备号
    struct cdev cdev;//cdev
    struct class *class;//类
    struct device *device;
    int major;
    int minor;
    struct device_node *node;//设备树节点
    int led0; //gpioled编号
};

struct leddev_dev leddev;//led设备

/*ledswitch函数*/
void led0_switch(u8 sta){
    if(sta == LEDON){
        gpio_set_value(leddev.led0,0);
    } else if (sta == LEDOFF){
        gpio_set_value(leddev.led0,1);
    }
}

/*release函数*/
static int led_release(struct inode *inode,struct file *filp){
    return 0;
}

/*write函数*/
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;
}
/*open函数*/
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,
};

/*MISC设备结构体*/
static struct miscdevice miscbeep0 = {
    .minor = MISCBEEP_MINOR,
    .name = DEV_NAME,
    .fops = &led_fops,

};

/*platform系统probe函数,驱动与设备相匹配后执行*/
static int led_probe(struct platform_device *dev){
    int retvalue;
    printk("driver and device was matched!\r\n");
    /*初始化IO*/
    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);//关闭led
    misc_deregister(&miscbeep0);

    return 0;
}

/*匹配列表*/
static const struct of_device_id led_of_match[] = {
    { .compatible = "alientek,beep"},
    { /*Sentinel*/ } //固定空白行
};
/*platform驱动结构体*/
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);//注册platform平台驱动
}

/*模块卸载函数*/
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 //KEY0按键值
#define KEY_NUM 1 //按键数量
/*中断IO描述结构体*/
struct irq_keydesc {
    int gpio;//中断所用gpio编号
    int irqnum;//中断号
    unsigned char value;//按键对应的键值
    char name[10];//中断名字
    irqreturn_t (*irqhandleyqx)(int,void *);//中断服务函数
};

/*irqim6ull设备结构体*/
struct imx6uirq_dev{
    dev_t devid;//设备号
    struct cdev 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));//定时器定时10ms
    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);//读取IO值
     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);
     }
}

/*io中断初始化*/
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;
    }

    /*初始化io*/
    for(i=0;i<KEY_NUM;i++){
        irqdevyqx.irqkeydesc[i].gpio = of_get_named_gpio(irqdevyqx.nd,"key-gpios",i);//获取gpio编号
        if(irqdevyqx.irqkeydesc[i].gpio<0){
            printk("can't get key%d\r\n",i);
            return -EINVAL;
        }

    }
    /*提取key所使用的IO的中断模式*/
    for(i=0;i<KEY_NUM;i++){
        memset(irqdevyqx.irqkeydesc[i].name,0,sizeof(irqdevyqx.irqkeydesc[i].name));//初始化IO中断名,全部清0
        sprintf(irqdevyqx.irqkeydesc[i].name,"KEY%d",i);//设置中断名
        gpio_request(irqdevyqx.irqkeydesc[i].gpio,irqdevyqx.irqkeydesc[i].name);//申请对应中断io
        gpio_direction_input(irqdevyqx.irqkeydesc[i].gpio);//设置中断IO为输入
        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;//设置key0中断服务函数
    irqdevyqx.irqkeydesc[0].value = KEY_0;//key0对应的键值
    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;

    /*申请input_dev*/
    irqdevyqx.inputdev = input_allocate_device();//注册input子系统结构体
    irqdevyqx.inputdev->name = DEV_NAME;

    /*初始化input_dev,设置那些事件*/
    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);//初始化key0按键值
    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_dev*/
   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;
    }
    //printf("打开设备文件失败\r");
    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;		/* 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;

/*
 * @description	: 从ap3216c读取多个寄存器数据
 * @param - dev:  ap3216c设备
 * @param - reg:  要读取的寄存器首地址
 * @param - val:  读取到的数据
 * @param - len:  要读取的数据长度
 * @return 		: 操作结果
 */
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]为发送要读取的首地址 */
	msg[0].addr = client->addr;			/* ap3216c地址 */
	msg[0].flags = 0;					/* 标记为发送数据 */
	msg[0].buf = &reg;					/* 读取的首地址 */
	msg[0].len = 1;						/* reg长度*/

	/* msg[1]读取数据 */
	msg[1].addr = client->addr;			/* ap3216c地址 */
	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;
}

/*
 * @description	: 向ap3216c多个寄存器写入数据
 * @param - dev:  ap3216c设备
 * @param - reg:  要写入的寄存器首地址
 * @param - val:  要写入的数据缓冲区
 * @param - len:  要写入的数据长度
 * @return 	  :   操作结果
 */
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);		/* 将要写入的数据拷贝到数组b里面 */
		
	msg.addr = client->addr;	/* ap3216c地址 */
	msg.flags = 0;				/* 标记为写数据 */

	msg.buf = b;				/* 要写入的数据缓冲区 */
	msg.len = len + 1;			/* 要写入的数据长度 */

	return i2c_transfer(client->adapter, &msg, 1);
}

/*
 * @description	: 读取ap3216c指定寄存器值,读取一个寄存器
 * @param - dev:  ap3216c设备
 * @param - reg:  要读取的寄存器
 * @return 	  :   读取到的寄存器值
 */
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
}

/*
 * @description	: 向ap3216c指定寄存器写入指定的值,写一个寄存器
 * @param - dev:  ap3216c设备
 * @param - reg:  要写的寄存器
 * @param - data: 要写入的值
 * @return   :    无
 */
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);
}

/*
 * @description	: 读取AP3216C的数据,读取原始数据,包括ALS,PS和IR, 注意!
 *				: 如果同时打开ALS,IR+PS的话两次数据读取的时间间隔要大于112.5ms
 * @param - ir	: ir数据
 * @param - ps 	: ps数据
 * @param - ps 	: als数据 
 * @return 		: 无。
 */
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) 	/* IR_OF位为1,则数据无效 */
		dev->ir = 0;					
	else 				/* 读取IR传感器的数据   		*/
		dev->ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03); 			
	
	dev->als = ((unsigned short)buf[3] << 8) | buf[2];	/* 读取ALS传感器的数据 			 */  
	
    if(buf[4] & 0x40)	/* IR_OF位为1,则数据无效 			*/
		dev->ps = 0;    													
	else 				/* 读取PS传感器的数据    */
		dev->ps = ((unsigned short)(buf[5] & 0X3F) << 4) | (buf[4] & 0X0F); 
}

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

	/* 初始化AP3216C */
	ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x04);		/* 复位AP3216C 			*/
	mdelay(50);														/* AP3216C复位最少10ms 	*/
	ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0X03);		/* 开启ALS、PS+IR 		*/
	return 0;
}

/*
 * @description		: 从设备读取数据 
 * @param - filp 	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 返回给用户空间的数据缓冲区
 * @param - cnt 	: 要读取的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 读取的字节数,如果为负值,表示读取失败
 */
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;
}

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

/* AP3216C操作函数 */
static const struct file_operations ap3216c_ops = {
	.owner = THIS_MODULE,
	.open = ap3216c_open,
	.read = ap3216c_read,
	.release = ap3216c_release,
};

 /*
  * @description     : i2c驱动的probe函数,当驱动与
  *                    设备匹配以后此函数就会执行
  * @param - client  : i2c设备
  * @param - id      : i2c设备ID
  * @return          : 0,成功;其他负值,失败
  */
static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	/* 1、构建设备号 */
	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);
	}

	/* 2、注册设备 */
	cdev_init(&ap3216cdev.cdev, &ap3216c_ops);
	cdev_add(&ap3216cdev.cdev, ap3216cdev.devid, AP3216C_CNT);

	/* 3、创建类 */
	ap3216cdev.class = class_create(THIS_MODULE, AP3216C_NAME);
	if (IS_ERR(ap3216cdev.class)) {
		return PTR_ERR(ap3216cdev.class);
	}

	/* 4、创建设备 */
	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;
}

/*
 * @description     : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行
 * @param - client 	: i2c设备
 * @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;
}

/* 传统匹配方式ID列表 */
static const struct i2c_device_id ap3216c_id[] = {
	{"alientek,ap3216c", 0},  
	{}
};

/* 设备树匹配列表 */
static const struct of_device_id ap3216c_of_match[] = {
	{ .compatible = "alientek,ap3216c" },
	{ /* Sentinel */ }
};

/* i2c驱动结构体 */	
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,
};
		   
/*
 * @description	: 驱动入口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init ap3216c_init(void)
{
	int ret = 0;

	ret = i2c_add_driver(&ap3216c_driver);
	return ret;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit ap3216c_exit(void)
{
	i2c_del_driver(&ap3216c_driver);
}

/* module_i2c_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>


/*
 * @description		: main主程序
 * @param - argc 	: argv数组元素个数
 * @param - argv 	: 具体参数
 * @return 			: 0 成功;其他 失败
 */
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]; 	/* ir传感器数据 */
			als = databuf[1]; 	/* als传感器数据 */
			ps =  databuf[2]; 	/* ps传感器数据 */
			printf("ir = %d, als = %d, ps = %d\r\n", ir, als, ps);
		}
		usleep(200000); /*100ms */
	}
	close(fd);	/* 关闭文件 */	
	return 0;
}


十四.SPI驱动

SPI 驱动框架和 I2C 很类似,都分为主机控制器驱动和设备驱动,主机控制器也就是 SOC的 SPI 控制器接口。,我们编写好 SPI 控制器驱动以后就可以直接使用了,不管是什么 SPI 设备, SPI 控制器部分的驱动都是一样,我们的重点就落在了种类繁多的 SPI 设备驱动。

实验程序
1.设备树:
/*yqx SPI*/
		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;			/* cdev 	*/
	struct class *class;		/* 类 		*/
	struct device *device;		/* 设备 	 */
	struct device_node	*nd; 	/* 设备节点 */
	int major;					/* 主设备号 */
	void *private_data;			/* 私有数据 		*/
	int cs_gpio;				/* 片选所使用的GPIO编号		*/
	signed int gyro_x_adc;		/* 陀螺仪X轴原始值 	 */
	signed int gyro_y_adc;		/* 陀螺仪Y轴原始值		*/
	signed int gyro_z_adc;		/* 陀螺仪Z轴原始值 		*/
	signed int accel_x_adc;		/* 加速度计X轴原始值 	*/
	signed int accel_y_adc;		/* 加速度计Y轴原始值	*/
	signed int accel_z_adc;		/* 加速度计Z轴原始值 	*/
	signed int temp_adc;		/* 温度原始值 			*/
};

static struct icm20608_dev icm20608dev;

/*
 * @description	: 从icm20608读取多个寄存器数据
 * @param - dev:  icm20608设备
 * @param - reg:  要读取的寄存器首地址
 * @param - val:  读取到的数据
 * @param - len:  要读取的数据长度
 * @return 		: 操作结果
 */
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);				/* 片选拉低,选中ICM20608 */
	t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);	/* 申请内存 */

	/* 第1次,发送要读取的寄存地址 */
	txdata[0] = reg | 0x80;		/* 写数据的时候寄存器地址bit8要置1 */
	t->tx_buf = txdata;			/* 要发送的数据 */
	t->len = 1;					/* 1个字节 */
	spi_message_init(&m);		/* 初始化spi_message */
	spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
	ret = spi_sync(spi, &m);	/* 同步发送 */

	/* 第2次,读取数据 */
	txdata[0] = 0xff;			/* 随便一个值,此处无意义 */
	t->rx_buf = buf;			/* 读取到的数据 */
	t->len = len;				/* 要读取的数据长度 */
	spi_message_init(&m);		/* 初始化spi_message */
	spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
	ret = spi_sync(spi, &m);	/* 同步发送 */

	kfree(t);									/* 释放内存 */
	gpio_set_value(dev->cs_gpio, 1);			/* 片选拉高,释放ICM20608 */

	return ret;
}

/*
 * @description	: 向icm20608多个寄存器写入数据
 * @param - dev:  icm20608设备
 * @param - reg:  要写入的寄存器首地址
 * @param - val:  要写入的数据缓冲区
 * @param - len:  要写入的数据长度
 * @return 	  :   操作结果
 */
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);			/* 片选拉低 */

	/* 第1次,发送要读取的寄存地址 */
	txdata[0] = reg & ~0x80;	/* 写数据的时候寄存器地址bit8要清零 */
	t->tx_buf = txdata;			/* 要发送的数据 */
	t->len = 1;					/* 1个字节 */
	spi_message_init(&m);		/* 初始化spi_message */
	spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
	ret = spi_sync(spi, &m);	/* 同步发送 */

	/* 第2次,发送要写入的数据 */
	t->tx_buf = buf;			/* 要写入的数据 */
	t->len = len;				/* 写入的字节数 */
	spi_message_init(&m);		/* 初始化spi_message */
	spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
	ret = spi_sync(spi, &m);	/* 同步发送 */

	kfree(t);					/* 释放内存 */
	gpio_set_value(dev->cs_gpio, 1);/* 片选拉高,释放ICM20608 */
	return ret;
}

/*
 * @description	: 读取icm20608指定寄存器值,读取一个寄存器
 * @param - dev:  icm20608设备
 * @param - reg:  要读取的寄存器
 * @return 	  :   读取到的寄存器值
 */
static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg)
{
	u8 data = 0;
	icm20608_read_regs(dev, reg, &data, 1);
	return data;
}

/*
 * @description	: 向icm20608指定寄存器写入指定的值,写一个寄存器
 * @param - dev:  icm20608设备
 * @param - reg:  要写的寄存器
 * @param - data: 要写入的值
 * @return   :    无
 */	

static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value)
{
	u8 buf = value;
	icm20608_write_regs(dev, reg, &buf, 1);
}

/*
 * @description	: 读取ICM20608的数据,读取原始数据,包括三轴陀螺仪、
 * 				: 三轴加速度计和内部温度。
 * @param - dev	: ICM20608设备
 * @return 		: 无。
 */
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]);
}

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

/*
 * @description		: 从设备读取数据 
 * @param - filp 	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 返回给用户空间的数据缓冲区
 * @param - cnt 	: 要读取的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 读取的字节数,如果为负值,表示读取失败
 */
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;
}

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

/* icm20608操作函数 */
static const struct file_operations icm20608_ops = {
	.owner = THIS_MODULE,
	.open = icm20608_open,
	.read = icm20608_read,
	.release = icm20608_release,
};

/*
 * ICM20608内部寄存器初始化函数 
 * @param  	: 无
 * @return 	: 无
 */
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); 	/* 陀螺仪±2000dps量程 				*/
	icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG, 0x18); 	/* 加速度计±16G量程 					*/
	icm20608_write_onereg(&icm20608dev, ICM20_CONFIG, 0x04); 		/* 陀螺仪低通滤波BW=20Hz 				*/
	icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG2, 0x04); /* 加速度计低通滤波BW=21.2Hz 			*/
	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);		/* 关闭FIFO						*/
}

 /*
  * @description     : spi驱动的probe函数,当驱动与
  *                    设备匹配以后此函数就会执行
  * @param - client  : spi设备
  * @param - id      : spi设备ID
  * 
  */	
static int icm20608_probe(struct spi_device *spi)
{
	int ret = 0;

	/* 1、构建设备号 */
	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);
	}

	/* 2、注册设备 */
	cdev_init(&icm20608dev.cdev, &icm20608_ops);
	cdev_add(&icm20608dev.cdev, icm20608dev.devid, ICM20608_CNT);

	/* 3、创建类 */
	icm20608dev.class = class_create(THIS_MODULE, ICM20608_NAME);
	if (IS_ERR(icm20608dev.class)) {
		return PTR_ERR(icm20608dev.class);
	}

	/* 4、创建设备 */
	icm20608dev.device = device_create(icm20608dev.class, NULL, icm20608dev.devid, NULL, ICM20608_NAME);
	if (IS_ERR(icm20608dev.device)) {
		return PTR_ERR(icm20608dev.device);
	}

	/* 获取设备树中cs片选信号 */
	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;
	} 

	/* 2、 获取设备树中的gpio属性,得到BEEP所使用的BEEP编号 */
	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;
	}

	/* 3、设置GPIO1_IO20为输出,并且输出高电平 */
	ret = gpio_direction_output(icm20608dev.cs_gpio, 1);
	if(ret < 0) {
		printk("can't set gpio!\r\n");
	}

	/*初始化spi_device */
	spi->mode = SPI_MODE_0;	/*MODE0,CPOL=0,CPHA=0*/
	spi_setup(spi);
	icm20608dev.private_data = spi; /* 设置私有数据 */

	/* 初始化ICM20608内部寄存器 */
	icm20608_reginit();		
	return 0;
}

/*
 * @description     : spi驱动的remove函数,移除spi驱动的时候此函数会执行
 * @param - client 	: spi设备
 * @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;
}

/* 传统匹配方式ID列表 */
static const struct spi_device_id icm20608_id[] = {
	{"alientek,icm20608", 0},  
	{}
};

/* 设备树匹配列表 */
static const struct of_device_id icm20608_of_match[] = {
	{ .compatible = "alientek,icm20608" },
	{ /* Sentinel */ }
};

/* SPI驱动结构体 */	
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,
};
		   
/*
 * @description	: 驱动入口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init icm20608_init(void)
{
	return spi_register_driver(&icm20608_driver);
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
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>

/*
 * @description		: main主程序
 * @param - argc 	: argv数组元素个数
 * @param - argv 	: 具体参数
 * @return 			: 0 成功;其他 失败
 */
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); /*100ms */
	}
	close(fd);	/* 关闭文件 */	
	return 0;
}

尾言

有关驱动开发内容就这些,本文绝大多出自I.MX6U 嵌入式 Linux 驱动开发指南 V1.5.1中,关于驱动开发,笔者最大的经验就是多写代码,多做有关的项目应用。

  • 5
    点赞
  • 64
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值