嵌入式设备驱动(实战)

1. 字符设备驱动框架

1.1 框架简介

下图所展现的就是整个字符设备驱动开发的整体框架简而言之,就是将驱动程序编入内核中,然后在用户态下即可像使用系统调用函数一样,通过传入相关参数就能够控制硬件设备。
在这里插入图片描述

1.2 如何实现自己的驱动

在做嵌入式驱动开发时,我们所需要明白的就是站在“巨人的肩膀上”,比如学习并理解Linux内核中驱动程序是如何运作的,参考其内核源码,然后去编写自己的驱动程序,这样在针对不同的芯片或者是开发板,便可以自己写出一套适应自己的驱动(重点是多参照Linux内核源码)。

2. 字符设备开发实验

2.1 环境搭建

  1. 开发板:I.mx6uLL
  2. 编译过后的Linux4.1.15源码
  3. Uboot 4.6源码、Linux4.1.15的镜像、根文件系统rootfs。
  4. Ubuntu16.04,vscode,nfs服务器,mobaXterm终端,tftp。
    重点:上面的环境搭建所需要的均会放到Github以及百度网盘中,请参考下面链接,可自行下载。
    链接:GitHub、百度网盘。

2.2 驱动实验1

2.2.1 需求

要求:
1. 实现open、read、write、release函数;
2. 在用户态下输入数据,传送到内核态下;
3. 将内核态的数据发送到用户态。

2.2.2 字符驱动源文件

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#define CHRDEVBASE_MAJOR    200             // 设备号
#define CHRDEVBASE_NAME     "chrdevbase"    // 设备名

static char readbuf[100];
static char writebuf[100];
static char kerneldata[] = {"kernel data"};

static int chrdevbase_open(struct inode *inode, struct file *file) {
    return 0;
}

static int chrdevbase_release(struct inode *inode, struct file *file) {
    return 0;
}

static ssize_t chrdevbase_read(struct file *filp, char __user *ubuf,
						size_t len, loff_t *offset) {
    int ret = 0;
    memcpy(readbuf, kerneldata, sizeof(kerneldata));
    ret = copy_to_user(ubuf, readbuf, sizeof(kerneldata));	// 将用户态数据拷贝到内核中
    if(ret < 0) {
        printk("copy data to user failed ...\r\n");
    }
    return 0;
}

static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, 
                        size_t count, loff_t *ppos) {
    int ret = 0;
    ret = copy_from_user(writebuf, buf, count);		// 将内核态数据拷贝到用户态中
    printk("data from user is %s\r\n", writebuf);
    if(ret < 0) {
        printk("copy data to kernel failed ...\r\n");
    }
    return 0;
}

// 操作函数集合
static const struct file_operations chrdev_base_fops = {
    .owner		= THIS_MODULE,
    .open       = chrdevbase_open,
    .release    = chrdevbase_release,
    .read       = chrdevbase_read,
    .write      = chrdevbase_write,  
};

/* 
 *驱动入口
*/
static int __init chrdevbase_init(void) {
    int ret = 0;
    // 设备注册
    ret = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME,
				    &chrdev_base_fops);
    if(ret < 0) {
        printk("chrdev register failed ...\r\n");
    }
    return 0;
}

/*
 *驱动出口
*/
static void __exit chrdevbase_exit(void) {
    unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
}

module_init(chrdevbase_init);
module_exit(chrdevbase_exit);

MODULE_LICENSE("GPL");		// 表明认可证书
MODULE_AUTHOR("wangyu");	// 作者

2.2.3 驱动测试程序

#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define READDATA        1   // 读数据
#define WRITEDATA       2   // 写数据   

static char readbuf[100];
static char writebuf[100];
static char userdata[100];

int main(int argc, char* argv[]) {
    int ret = 0;   
    int fd = 0;
    const char* filename = argv[1];		// 驱动文件路径
    int value = atoi(argv[2]);			// 控制读/写数据
    if(argc != 3) {
        return -1;
    }
    fd = open(filename, O_RDWR);    // 获得文件描述符
    if(value == 1) {      // 读取数据
        ret = read(fd, readbuf, sizeof(readbuf));
        printf("read from kernel's data is %s\r\n", readbuf);
        if(ret < 0) {
            printf("read data failed ...\r\n");
        }
    }
    if(value == 2) {      // 写数据
        printf("please input data ...\r\n");
        scanf("%[^\n]", userdata);  
        ret = write(fd, userdata, sizeof(userdata));
        if(ret < 0) {
            printf("write data failed ...\r\n");
        }
    }

    return 0;
}

驱动测试程序需要用到交叉编译工具,在linux中下载交叉编译工具链即可。

2.2.4 Makefile

KERNELDIR := /home/wy/linux/IMX6ULL/linux/alientek_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek

CURRENT_PATH := $(shell pwd)
obj-m := chrdevbase.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

用于将上面的字符驱动源文件编译为.ko模块。

2.2.5 json配置文件

{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/**",
                "/home/wy/linux/IMX6ULL/linux/alientek_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/include", 
                "/home/wy/linux/IMX6ULL/linux/alientek_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include", 
                "/home/wy/linux/IMX6ULL/linux/alientek_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include/generated/"
            ],
            "defines": [],
            "compilerPath": "/usr/bin/clang",
            "cStandard": "c11",
            "cppStandard": "c++17",
            "intelliSenseMode": "clang-x64"
        }
    ],
    "version": 4
}

由于我开发使用的是Vscode,所以需要在.vscode中加入一个json文件用于导入l之前编译的Linux内核源码的路径。

2.3 驱动实验2

2.3.1 需求

要求:
1. 实现控制开发板上的LED灯;
2. 在用户态下调用write函数可以控制开发板上的LED灯。

2.3.2 LED驱动源文件

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#define LED_MAJOR       200     // 设备号
#define LED_NAME        "led"   // 设备名

/* 寄存器物理地址 */
#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 LEDOFF   0      /* 关闭 */
#define LEDON    1      /* 打开 */

/* LED灯打开/关闭 */
static void led_switch(s8 sta) {
    u32 val = 0;
    if(sta == LEDON) {
        val = readl(GPIO1_DR);
        val &= ~(1 << 3);   // bit3清零,打开LED灯
        writel(val, GPIO1_DR);
    } else if(sta == LEDOFF) {
        val = readl(GPIO1_DR);
        val |= 1 << 3;   // bit3清零,关闭LED灯
        writel(val, GPIO1_DR);
    }
}

static int led_open(struct inode *inode, struct file *file) {
    return 0;
}

static int led_release(struct inode *inode, struct file *file) {
    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 count, loff_t * ppos) {
    int retvalue = 0;
    unsigned char databuf[1];
    retvalue = copy_from_user(databuf, buf, count);
    if(retvalue < 0) {
        printk("kernel write falied ...\r\n");
        return -EFAULT;
    }
    /* 判断是开灯还是关灯 */
    led_switch(databuf[0]);

    return 0;
}

static struct file_operations led_fops = {
	.owner = THIS_MODULE,
	.write = led_write,
	.open  = led_open,
	.release = led_release,
    .read = led_read,
};

// 驱动入口
static int __init led_init(void) {
    int ret = 0;
    unsigned int val = 0;
    /* 初始化led灯,地址映射 */
    IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
    SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
    SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
    GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
    GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);

    /* 初始化 */
    val = readl(IMX6U_CCM_CCGR1);
    val &= ~(3 << 26);
    val |= 3 << 26;
    writel(val, IMX6U_CCM_CCGR1);

    writel(0x5, SW_MUX_GPIO1_IO03);     // 设置复用
    writel(0x10B0, SW_PAD_GPIO1_IO03);     // 设置电气属性

    val = readl(GPIO1_GDIR);
    val |= 1 << 3;      // bit3置1,设置为输出
    writel(val, GPIO1_GDIR);

    led_switch(LEDOFF);      // 默认LED灯处于关闭状态

    /* 注册字符设备 */
    ret = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);
    if(ret < 0) {
        printk("register_chrdev falied ...\r\n");
        return -EIO;
    }
    printk("led_init ...\r\n");
    return 0;
}

// 驱动出口
static void __exit led_exit(void) {
    led_switch(LEDOFF);

    /* 注销地址映射 */
    iounmap(IMX6U_CCM_CCGR1);
    iounmap(SW_MUX_GPIO1_IO03);
    iounmap(SW_PAD_GPIO1_IO03);
    iounmap(GPIO1_DR);
    iounmap(GPIO1_GDIR);

    /* 注销字符设备 */
    unregister_chrdev(LED_MAJOR, LED_NAME);
    printk("led_exit ...\r\n");
}


/* 注册驱动加载和卸载 */
module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("wangyu");

LED驱动源文件中所实现的是对寄存器的操作,通过操作寄存器来控制LED灯的亮灭。

2.3.3 LED驱动测试程序

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>

/*
 * argc: 应用程序参数个数
 * argv[]: 具体的参数内容字符串形式
 * ./ledAPP  <filename> <0/1> 0表示关灯,1表示开灯
 * ./ledAPP /dev/led 0    关灯
 * ./ledAPP /dev/led 1    开灯
*/

#define LEDOFF      0
#define LEDON       1

int main(int argc, char* argv[]) {
    int ret = 0;
    int fd = 0;
    const char* filename = argv[1];
    unsigned char databuf[1];
    if(argc != 3) {
        printf("Error usage!");
        return -1;
    }

    fd = open(filename, O_RDWR);
    if(fd < 0) {
        printf("%s open failed ...\r\n", filename);
        return -1;
    }

    databuf[0] = atoi(argv[2]);     // 将字符转换为数字
    ret = write(fd, databuf, sizeof(databuf));
    if(ret < 0) {
        printf("LED control failed ...\r\n");
        close(fd);
        return -1;
    }

    close(fd);

    return 0;
}

测试写的LED驱动代码。

2.4 学习设备树

2.4.1 设备树基础

如下图所示,设备树简单理解就是用来标识不同的设备信息,这些设备信息可以是片上信息,亦可以是外设。
在这里插入图片描述

2.4.2 设备树实验

要求:在原来的设备树即开发板所对应的.dts文件中加入自己的设备树节点,,然后从编写驱动程序来读取节点的属性信息并打印出来。

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>

#if 0
backlight {
    compatible = "pwm-backlight";
    pwms = <&pwm1 0 5000000>;
    brightness-levels = <0 4 8 16 32 64 128 255>;
    default-brightness-level = <7>;
    status = "okay";
};
#endif


/*  
 * 模块入口
 */
static int __init dtsof_init(void) {
    int ret = 0;
    struct device_node *bl_nd = NULL;      // 节点
    struct property    *comppro = NULL;    // 属性
    const char *str;
    u32 def_value = 0;
    u32 elemsize = 0;
    u32 *brival;
    u8 i = 0;

    /* 找到backlight节点 */
    bl_nd = of_find_node_by_path("/backlight");   
    if(bl_nd == NULL) {
        ret = -EINVAL;
        goto fail_findnd;
    }

    /* 获取属性 */
    comppro = of_find_property(bl_nd, "compatible", NULL);
    if(comppro == NULL) {
        ret = -EINVAL;
        goto fail_findpro;
    } else {
        printk("compatible = %s\r\n", (char*)comppro->value);
    }

    ret = of_property_read_string(bl_nd, "status", &str);
    if(ret < 0) {
        goto fail_rs;
    } else {
        printk("status = %s\r\n", str);
    }

    /* 读取数字属性值 */ 
    ret = of_property_read_u32(bl_nd, "default-brightness-level", &def_value);
    if(ret < 0) {
        goto fail_read32;
    } else {
        printk("default-brightness-level = %d\r\n", def_value);
    }

    /* 获取数组类型的属性 */
    elemsize = of_property_count_elems_of_size(bl_nd, "brightness-levels", sizeof(u32));
    if(elemsize < 0) {
        goto fail_readele;
    } else {
        printk("brightness-level = %d\r\n", ret);
    }
    /* 申请内存 */
    brival = kmalloc(elemsize * sizeof(u32), GFP_KERNEL);
    if(!brival) {
        ret = -EINVAL;
        goto fail_mem;
    } 
    /* 获取数组 */
    ret = of_property_read_u32_array(bl_nd, "brightness-levels", brival, elemsize);
    if(ret < 0) {
        goto fail_read32array;
    } else {
        for(i = 0; i < elemsize; i++) {
            printk("brightness-levels[%d] = %d\r\n", i, *(brival + i));
        }
    }
    kfree(brival);

    return 0;
fail_read32array:
    kfree(brival);
fail_mem:
fail_readele:
fail_read32:
fail_rs:
fail_findpro:
fail_findnd:
    return ret;
}

/*
 * 模块出口
 */
static void __exit dtsof_exit(void) {

}

/* 注册模块入口和出口 */
module_init(dtsof_init);
module_exit(dtsof_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wangyu");

2.5 驱动实验3

2.5.1 需求

前面所实现的LED驱动程序都是通过的自定义的宏来实现的,现在我们需要的就是通过在设备树中添加与LED相关的寄存器地址信息,然后去控制LED灯。

2.5.2 驱动源程序

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>

#define DTSLED_CNT      1       // 设备号个数
#define DTSLED_NAME  "dtsled"   // 名字
#define LEDOFF 			0		// 关灯
#define LEDON 		    1		// 开灯

/* 映射后的寄存器虚拟地址指针 */
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;

#if 0
	alphaled {
		#address-cells = <1>;
		#size-cells = <1>;
		compatible = "atkalpha, alphaled";
		status = "okay";
		reg = < 0X020C406C 0X04		/* CCM_CCGR1_BAE 			*/
				0X020E0068 0X04		/* SW_MUX_GPIO1_IO03_BASE 	*/
				0X020E02F4 0X04		/* SW_PAD_GPIO1_IO03_BASE	*/
				0X0209C000 0X04		/* GPIO1_DR_BASE 			*/
				0X0209C004 0X04 >;	/* GPIO1_GDIR_BASE 			*/
	};
#endif

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

struct dtsled_dev dtsled;   // led设备

void led_switch(u8 sta) {
	u32 val = 0;
	if(sta == LEDON) {
		val = readl(GPIO1_DR);
		val &= ~(1 << 3);	
		writel(val, GPIO1_DR);
	}else if(sta == LEDOFF) {
		val = readl(GPIO1_DR);
		val|= (1 << 3);	
		writel(val, GPIO1_DR);
	}	
}

static int dtsled_open(struct inode *inode, struct file *filp) {
    filp->private_data = &dtsled;
    return 0;
}

static int dtsled_release(struct inode *inode, struct file *filp) {
    // struct dtsled_dev *dev = (struct dtsled_dev*)filp->private_data;

	return 0;
}

static ssize_t dtsled_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) {
    // struct dtsled_dev *dev = (struct dtsled_dev*)filp->private_data;

    int retvalue;
	unsigned char databuf[1];
	unsigned char ledstat;

	retvalue = copy_from_user(databuf, buf, cnt);
	if(retvalue < 0) {
		printk("kernel write failed!\r\n");
		return -EFAULT;
	}

	ledstat = databuf[0];		/* 获取状态值 */

	if(ledstat == LEDON) {	
		led_switch(LEDON);		/* 打开LED灯 */
	} else if(ledstat == LEDOFF) {
		led_switch(LEDOFF);	/* 关闭LED灯 */
	}

    return 0;
}

/* dtsled字符设备操作集合 */
static const struct file_operations dtsled_fops = {
    .owner = THIS_MODULE,
    .write = dtsled_write,
    .open = dtsled_open,
    .release = dtsled_release,
};

/* 入口 */
static int __init dtsled_init(void) {
    int ret = 0;
    unsigned int val = 0;
    const char* str;
    u32 regdata[10];
    u8 i = 0;

    /* 注册字符设备 */
    // 申请设备号
    dtsled.major = 0;       // 内核分配设备号
    if(dtsled.major) {
        dtsled.devid = MKDEV(dtsled.major, 0);
        ret = register_chrdev_region(dtsled.devid, DTSLED_CNT, DTSLED_NAME);
    } else {        // 未给定设备号
        ret = alloc_chrdev_region(&dtsled.devid, 0, DTSLED_CNT, DTSLED_NAME);
        dtsled.major = MAJOR(dtsled.devid);
        dtsled.minor = MINOR(dtsled.devid);
    }
    if(ret < 0) {
        goto fail_devid;
    }

    /* 添加字符设备 */
    dtsled.cdev.owner = THIS_MODULE;
    cdev_init(&dtsled.cdev, &dtsled_fops);
    ret = cdev_add(&dtsled.cdev, dtsled.devid, DTSLED_CNT);
    if(ret < 0) {
        goto fail_cdev;
    }

    /* 自动创建设备节点 */
    dtsled.class = class_create(THIS_MODULE, DTSLED_NAME);
    if(IS_ERR(dtsled.class)) {
        ret = PTR_ERR(dtsled.class);
        goto fail_class;
    }

    dtsled.device = device_create(dtsled.class, NULL, dtsled.devid, NULL, DTSLED_NAME);
    if(IS_ERR(dtsled.device)) {
        ret = PTR_ERR(dtsled.device);
        goto fail_device;
    }

    /* 获取设备树属性内容 */
    dtsled.nd = of_find_node_by_path("/alphaled");
    if(dtsled.nd == NULL) {
        ret = -EINVAL;
        goto fail_findnd;
    }
    
#if 0
    /* 获取属性 */
    ret = of_property_read_string(dtsled.nd, "status", &str);
    if(ret < 0) {
        goto fail_rs;
    } else {
        printk("status = %s\r\n", str);
    }

    ret = of_property_read_string(dtsled.nd, "compatible", &str);
    if(ret < 0) {
        goto fail_rs;
    } else {
        printk("compatible = %s\r\n", str);
    }
    
    ret = of_property_read_u32_array(dtsled.nd, "reg", regdata, 10);
    if(ret < 0) {
        goto fail_rs;
    } else {
        printk("regdata: \r\n");
        for(i = 0; i < 10; i++) {
            printk("%#X ", regdata[i]);
        }
        printk("\r\n");
    }

    /* LED灯初始化 */
	/* 1、寄存器地址映射 */
    IMX6U_CCM_CCGR1 = ioremap(regdata[0], regdata[1]);
    SW_MUX_GPIO1_IO03 = ioremap(regdata[2], regdata[3]);
    SW_PAD_GPIO1_IO03 = ioremap(regdata[4], regdata[5]);
    GPIO1_DR = ioremap(regdata[6], regdata[7]);
    GPIO1_GDIR = ioremap(regdata[8], regdata[9]);
#endif
    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);

    /* 2、使能GPIO1时钟 */
	val = readl(IMX6U_CCM_CCGR1);
	val &= ~(3 << 26);	/* 清楚以前的设置 */
	val |= (3 << 26);	/* 设置新值 */
	writel(val, IMX6U_CCM_CCGR1);

    writel(5, SW_MUX_GPIO1_IO03);   // 设置复用
    writel(0x10B0, SW_PAD_GPIO1_IO03);  // 设置电气属性

    /* 设置GPIO1_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);

    return 0;

fail_rs:
fail_findnd:
    device_destroy(dtsled.class, dtsled.devid);
fail_device:
    class_destroy(dtsled.class);
fail_class:
    cdev_del(&dtsled.cdev);
fail_cdev:
    unregister_chrdev_region(dtsled.devid, DTSLED_CNT);
fail_devid:
    return ret;
}

/* 出口 */
static void __exit dtsled_exit(void) {
    unsigned int val = 0;
    val = readl(GPIO1_DR);
    val |= (1 << 3);
    writel(val, GPIO1_DR);

    /* 释放内存映射 */
    iounmap(IMX6U_CCM_CCGR1);
	iounmap(SW_MUX_GPIO1_IO03);
	iounmap(SW_PAD_GPIO1_IO03);
	iounmap(GPIO1_DR);
	iounmap(GPIO1_GDIR);

    /* 删除字符设备 */
    cdev_del(&dtsled.cdev);

    /* 释放设备号 */
    unregister_chrdev_region(dtsled.devid, DTSLED_CNT);

    /* 摧毁设备 */
    device_destroy(dtsled.class, dtsled.devid);

    /* 摧毁类 */
    class_destroy(dtsled.class);
}

/* 注册和卸载驱动 */
module_init(dtsled_init);
module_exit(dtsled_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wangyu");

源程序实现的是两种方法来控制LED灯,一种是通过从设备树获取属性来,另一种则是通过使用of函数来实现物理地址到虚拟地址的映射,同时也获取到设备信息。

2.6 驱动实验4(pinctrl和gpio子系统)

2.6.1 需求

上一个实验我们用的是在设备树中添加相关的gpio的属性值,也就是相关的寄存器的地址,然后再通过of函数去获取属性值,进而去向寄存器中写入值来实现相应功能,但实际上,我们很少直接去操作寄存器,而是通过pinctrl和gpio子系统去实现功能。

2.6.2 驱动程序(控制LED灯和蜂鸣器)

简介:通过向设备树中加入gpioled标签和pinctrl标签。![在这里插入图片描述](https://img-blog.csdnimg.cn/72a4393d462d44068e741801349df342.png
在这里插入图片描述

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>

#define GPIOLED_CNT         1
#define GPIOLED_NAME    "gpioled"
#define LEDON               1
#define LEDOFF              0

/* gpioled设备结构体 */
struct gpioled_dev {
    dev_t devid;    // 设备号
    int major;      // 主设备号
    int minor;      // 次设备号
    struct cdev cdev;
    struct class *class;
    struct device *device;
    struct device_node *nd;
    int led_gpio;
};

struct gpioled_dev gpioled;     /* LED */

static int led_open(struct inode *inode, struct file *filp) {
    filp->private_data = &gpioled;

    return 0;
}

static int led_release(struct inode *inode, struct file *filp) {
    // struct gpioled_dev *dev = (struct dtsled_dev*)filp->private_data;

	return 0;
}

static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) {
    int ret;
    unsigned char databuf[1];
    struct gpioled_dev *dev = (struct gpioled_dev*)filp->private_data;
    ret = copy_from_user(databuf, buf, cnt);
    if(ret < 0) {
        return -EINVAL;
    }
    if(databuf[0] == LEDON) {
        gpio_set_value(dev->led_gpio, 0);
    } else {
        gpio_set_value(dev->led_gpio, 1);
    }

    return 0;
}


/* 操作集合 */
static const struct file_operations led_fops = {
	.owner = THIS_MODULE,
	.open = led_open,
	.release = led_release,
	.write = led_write,
};

/* 驱动入口函数 */
static int __init led_init(void) {
    int ret = 0;

    /* 1. 注册字符设备驱动 */
    gpioled.major = 0;
    if(gpioled.major) {     // 给定主设备号
        gpioled.devid = MKDEV(gpioled.major, 0);
        ret = register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
    } else {        // 未给定设备号
        ret = alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);
        gpioled.major = MAJOR(gpioled.devid);
        gpioled.minor = MINOR(gpioled.devid);
    }
    if(ret < 0) {
        goto fail_devid;
    }
    printk("gpioled major = %d, minor = %d\r\n", gpioled.major, gpioled.minor);

    /* 2. 初始化cdev */
    gpioled.cdev.owner = THIS_MODULE;
    cdev_init(&gpioled.cdev, &led_fops);
    
    /* 3. 添加cdev */
    ret = cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);    // 自己去进行错误处理
    if(ret < 0) {
        goto fail_cdev;
    }

    /* 4. 创建类 */
    gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
    if(IS_ERR(gpioled.class)) {
        ret = PTR_ERR(gpioled.class);
        goto fail_class;
    }

    /* 5. 创建设备 */
    gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
    if(IS_ERR(gpioled.device)) {
        ret = PTR_ERR(gpioled.device);
        goto fail_device;
    }

    /* 1. 获取设备节点 */
    gpioled.nd = of_find_node_by_path("/gpioled");
    if(gpioled.nd == NULL) {
        ret = -EINVAL;
        goto fail_findnode;
    }

    /* 2. 获取节点LED所对应的GPIO */
    gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpios", 0);
    if(gpioled.led_gpio < 0) {
        printk("can't find led gpio\r\n");
        ret = -EINVAL;
        goto fail_findnode;
    }
    printk("led gpio num = %d\r\n", gpioled.led_gpio);

    /* 3. 申请IO */
    ret = gpio_request(gpioled.led_gpio, "led-gpios");
    if(ret < 0) {
        printk("Failed to request the led gpio\r\n");
        ret = -EINVAL;
        goto fail_findnode;
    }

    /* 4. 使用IO,设置为输出 */
    ret = gpio_direction_output(gpioled.led_gpio, 1);
    if(ret < 0) {
        goto fail_setoutput;
    }

    /* 5. 输出低电平,点亮LED */
    gpio_set_value(gpioled.led_gpio, 0);

    return 0;

fail_setoutput:
    gpio_free(gpioled.led_gpio);
fail_findnode:
fail_device:
    class_destroy(gpioled.class);
fail_class:
    cdev_del(&gpioled.cdev);
fail_cdev:
    unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
fail_devid:
    return ret;
}


/* 驱动出口函数 */
static void __exit led_exit(void) {
    /* 关灯 */
    gpio_set_value(gpioled.led_gpio, 1);

    /* 注销字符设备驱动 */
    cdev_del(&gpioled.cdev);
    unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);

    device_destroy(gpioled.class, gpioled.devid);
    class_destroy(gpioled.class);

    /* 释放IO */
    gpio_free(gpioled.led_gpio);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wangyu");

注意:在编写这个驱动的时候我们要做好异常处理,同时在编写属性值的时候需要参照内核所编写好了,仿照它们的去编写。

2.7 驱动实验5(并发与竞争)

2.7.1 需求

学习Linux中的并发与竞争,知道什么是原子操作,掌握自旋锁、信号量、互斥锁等相关的锁。

2.7.2 驱动程序

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/atomic.h>

#define GPIOLED_CNT         1
#define GPIOLED_NAME    "gpioled"
#define LEDON               1
#define LEDOFF              0

/* gpioled设备结构体 */
struct gpioled_dev {
    dev_t devid;    // 设备号
    int major;      // 主设备号
    int minor;      // 次设备号
    struct cdev cdev;
    struct class *class;
    struct device *device;
    struct device_node *nd;
    int led_gpio;
    int dev_status;     // 0表示设备可以使用,大于1表示不可使用
    spinlock_t lock;
};

struct gpioled_dev gpioled;     /* LED */

static int led_open(struct inode *inode, struct file *filp) {
    unsigned long flag;
    filp->private_data = &gpioled;

    // spin_lock(&gpioled.lock);
    spin_lock_irqsave(&gpioled.lock, flag);
    if(gpioled.dev_status) {    // 驱动不能使用
        spin_unlock_irqrestore(&gpioled.lock, flag);
        return -EBUSY;
    }

    gpioled.dev_status++;   // 标记被使用
    // spin_unlock(&gpioled.lock);
    spin_unlock_irqrestore(&gpioled.lock, flag);

    return 0;
}

static int led_release(struct inode *inode, struct file *filp) {
    unsigned long flag;
    struct gpioled_dev *dev = (struct gpioled_dev*)filp->private_data;
    // spin_lock(&dev->lock);
    spin_lock_irqsave(&dev->lock, flag);
    if(dev->dev_status) {
        gpioled.dev_status--;       // 标记驱动可以使用
    }

    // spin_unlock(&dev->lock);
    spin_unlock_irqrestore(&dev->lock, flag);

	return 0;
}

static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) {
    int ret;
    unsigned char databuf[1];
    struct gpioled_dev *dev = (struct gpioled_dev*)filp->private_data;
    ret = copy_from_user(databuf, buf, cnt);
    if(ret < 0) {
        return -EINVAL;
    }
    if(databuf[0] == LEDON) {
        gpio_set_value(dev->led_gpio, 0);
    } else {
        gpio_set_value(dev->led_gpio, 1);
    }

    return 0;
}


/* 操作函数集合 */
static const struct file_operations led_fops = {
	.owner = THIS_MODULE,
	.open = led_open,
	.release = led_release,
	.write = led_write,
};
/* 驱动入口函数 */
static int __init led_init(void) {
    int ret = 0;

    /* 初始化自旋锁 */
    spin_lock_init(&gpioled.lock);
    gpioled.dev_status = 0;

    /* 1. 注册字符设备驱动 */
    gpioled.major = 0;
    if(gpioled.major) {     // 给定主设备号
        gpioled.devid = MKDEV(gpioled.major, 0);
        ret = register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
    } else {        // 未给定设备号
        ret = alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);
        gpioled.major = MAJOR(gpioled.devid);
        gpioled.minor = MINOR(gpioled.devid);
    }
    if(ret < 0) {
        goto fail_devid;
    }
    printk("gpioled major = %d, minor = %d\r\n", gpioled.major, gpioled.minor);

    /* 2. 初始化cdev */
    gpioled.cdev.owner = THIS_MODULE;
    cdev_init(&gpioled.cdev, &led_fops);
    
    /* 3. 添加cdev */
    ret = cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);    // 自己去进行错误处理
    if(ret < 0) {
        goto fail_cdev;
    }

    /* 4. 创建类 */
    gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
    if(IS_ERR(gpioled.class)) {
        ret = PTR_ERR(gpioled.class);
        goto fail_class;
    }

    /* 5. 创建设备 */
    gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
    if(IS_ERR(gpioled.device)) {
        ret = PTR_ERR(gpioled.device);
        goto fail_device;
    }

    /* 1. 获取设备节点 */
    gpioled.nd = of_find_node_by_path("/gpioled");
    if(gpioled.nd == NULL) {
        ret = -EINVAL;
        goto fail_findnode;
    }

    /* 2. 获取节点LED所对应的GPIO */
    gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpios", 0);
    if(gpioled.led_gpio < 0) {
        printk("can't find led gpio\r\n");
        ret = -EINVAL;
        goto fail_findnode;
    }
    printk("led gpio num = %d\r\n", gpioled.led_gpio);

    /* 3. 申请IO */
    ret = gpio_request(gpioled.led_gpio, "led-gpios");
    if(ret < 0) {
        printk("Failed to request the led gpio\r\n");
        ret = -EINVAL;
        goto fail_findnode;
    }

    /* 4. 使用IO,设置为输出 */
    ret = gpio_direction_output(gpioled.led_gpio, 1);
    if(ret < 0) {
        goto fail_setoutput;
    }

    /* 5. 输出低电平,点亮LED */
    gpio_set_value(gpioled.led_gpio, 0);

    return 0;

fail_setoutput:
    gpio_free(gpioled.led_gpio);
fail_findnode:
fail_device:
    class_destroy(gpioled.class);
fail_class:
    cdev_del(&gpioled.cdev);
fail_cdev:
    unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
fail_devid:
    return ret;
}


/* 驱动出口函数 */
static void __exit led_exit(void) {
    /* 关灯 */
    gpio_set_value(gpioled.led_gpio, 1);

    /* 注销字符设备驱动 */
    cdev_del(&gpioled.cdev);
    unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);

    device_destroy(gpioled.class, gpioled.devid);
    class_destroy(gpioled.class);

    /* 释放IO */
    gpio_free(gpioled.led_gpio);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wangyu");

2.8 驱动实验6(定时器)

2.8.1 定时器版本1

2.8.1.1 需求

通过使用Linux内核中所实现的定时器来控制开发板上的LED灯,初步了解定时器的使用。

2.8.1.2 驱动程序
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/atomic.h>
#include <linux/timer.h>
#include <linux/jiffies.h>


#define TIMER_CNT         1
#define TIMER_NAME     "timer"

/* timer设备结构体 */
struct timer_dev {
    dev_t devid;        // 设备号
    int major;          // 主设备号
    int minor;          // 次设备号
    struct cdev cdev;   // 字符设备
    struct class *class;
    struct device *device;
    struct device_node *nd;
    struct timer_list timer;  // 定时器
    int led_gpio;   // led 的GPIO
};

struct timer_dev timerdev;      /* 定时器 */

static int timer_open(struct inode *inode, struct file *filp) {
    filp->private_data = &timerdev;
    
    return 0;
}

static int timer_release(struct inode *inode, struct file *filp) {
    // sstruct timer_dev *dev = (struct timer_dev*)filp->private_data;
    
    return 0;
}

static ssize_t timer_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) {
    // sstruct timer_dev *dev = (struct timer_dev*)filp->private_data;

    return 0;
}


/* 操作集合 */
static const struct file_operations timer_fops = {
    .owner = THIS_MODULE,
    .open = timer_open,
    .write = timer_write,
    .release = timer_release,
};

/* 定时器处理函数 */
static void timer_func(unsigned long arg) {
    struct timer_dev *dev = (struct timer_dev*)arg;
    static int sta = 1;

    sta = !sta;
    gpio_set_value(dev->led_gpio, sta);

    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(500));
}

/* 初始化LED */
static int led_init(struct timer_dev *dev) {
    int ret = 0;
    dev->nd = of_find_node_by_path("/gpioled");
    if(dev->nd == NULL) {
        ret = -EINVAL;
        goto fail_fd;
    }

    dev->led_gpio = of_get_named_gpio(dev->nd, "led-gpios", 0);
    if(dev->led_gpio < 0) {
        ret = -EINVAL;
        goto fail_gpio;
    }

    ret = gpio_request(dev->led_gpio, "led");
    if(ret) {
        ret = -EBUSY;
        printk("IO %d can't request!\r\n", dev->led_gpio);
        goto fail_request;
    }

    ret = gpio_direction_output(dev->led_gpio, 1);      // 默认关灯
    if(ret < 0) {
        ret = -EINVAL;
        goto fail_gpioset;
    }

    return 0;

fail_gpioset:
    gpio_free(dev->led_gpio);
fail_request:
fail_gpio:
fail_fd:
    return ret;
}

/* 驱动函数入口 */
static int __init timer_init(void) {
    int ret = 0;
    /* 1.注册字符设备驱动 */
    timerdev.major = 0;
    if(timerdev.major) {   // 指定了主设备号
        timerdev.devid = MKDEV(timerdev.major, 0);
        ret = register_chrdev_region(timerdev.devid, TIMER_CNT, TIMER_NAME);
    } else {
        ret = alloc_chrdev_region(&timerdev.devid, 0, TIMER_CNT, TIMER_NAME);
        timerdev.major = MAJOR(timerdev.devid);
        timerdev.minor = MINOR(timerdev.minor);
    }
    if(ret < 0) {
        goto fail_devid;
    }
    printk("timer major = %d, minor = %d\r\n", timerdev.major, timerdev.minor);

    /* 2.初始化cdev */
    timerdev.cdev.owner = THIS_MODULE;
    cdev_init(&timerdev.cdev, &timer_fops);

    /* 3.添加cdev */
    ret = cdev_add(&timerdev.cdev, timerdev.devid, TIMER_CNT);
    if(ret < 0) {
        goto fail_cdev;
    }

    /* 4.创建类 */
    timerdev.class = class_create(THIS_MODULE, TIMER_NAME);
    if(IS_ERR(timerdev.class)) {
        ret = PTR_ERR(timerdev.class);
        goto fail_class;
    }

    /* 5.创建设备 */
    timerdev.device = device_create(timerdev.class, NULL, timerdev.devid, NULL, TIMER_NAME);
    if(IS_ERR(timerdev.device)) {
        ret = PTR_ERR(timerdev.device);
        goto fail_device;
    }

    /* 6.初始化LED */
    ret = led_init(&timerdev);
    if(ret < 0) {
        goto fail_ledinit;
    }

    /* 7.初始化定时器 */
    init_timer(&timerdev.timer);
    
    timerdev.timer.function = timer_func;
    timerdev.timer.expires = jiffies + msecs_to_jiffies(500);
    timerdev.timer.data = (unsigned long)&timerdev;
    add_timer(&timerdev.timer);     // 添加到系统

    return 0;

fail_ledinit:
fail_device:
    class_destroy(timerdev.class);
fail_class:
    cdev_del(&timerdev.cdev);
fail_cdev:
    unregister_chrdev_region(timerdev.devid, TIMER_CNT);
fail_devid:
    return ret;
}


/* 驱动函数出口 */
static void __exit timer_exit(void) {
    /* 关灯 */
    gpio_set_value(timerdev.led_gpio, 1);

    /* 删除定时器 */
    del_timer(&timerdev.timer);

    /* 注销字符设备驱动 */
    cdev_del(&timerdev.cdev);
    unregister_chrdev_region(timerdev.devid, TIMER_CNT);

    device_destroy(timerdev.class, timerdev.devid);
    class_destroy(timerdev.class);

    /* 释放IO */
    gpio_free(timerdev.led_gpio);
}

module_init(timer_init);
module_exit(timer_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wangyu");

2.8.2 定时器版本2

2.8.2.1 需求

本次需要实现的就是用户程序通过使用ioctl函数来控制LED灯,同时还可以调节灯闪烁的时间和周期,同时结合前面所学的,需要用到自旋锁来保护共享资源。

2.8.2.2 驱动程序
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/atomic.h>
#include <linux/timer.h>
#include <linux/jiffies.h>

#define TIMER_CNT         1         // 设备个数
#define TIMER_NAME     "timer"      // 名字


#if 0
#define BINDER_WRITE_READ		_IOWR('b', 1, struct binder_write_read)
#define BINDER_SET_IDLE_TIMEOUT		_IOW('b', 3, __s64)
#define BINDER_SET_MAX_THREADS		_IOW('b', 5, __u32)
#define BINDER_SET_IDLE_PRIORITY	_IOW('b', 6, __s32)
#define BINDER_SET_CONTEXT_MGR		_IOW('b', 7, __s32)
#define BINDER_THREAD_EXIT		_IOW('b', 8, __s32)
#define BINDER_VERSION			_IOWR('b', 9, struct binder_version)
#endif

#define CLOSE_CMD              _IO(0xEF, 1)         // 关闭命令
#define OPEN_CMD               _IO(0xEF, 2)         // 打开命令
#define SETPERIOD_CMD          _IOW(0xEF, 3, int)   // 设置周期


/* timer设备结构体 */
struct timer_dev {
    dev_t devid;        // 设备号
    int major;          // 主设备号
    int minor;          // 次设备号
    struct cdev cdev;   // 字符设备
    struct class *class;   // 类
    struct device *device;  // 设备
    struct device_node *nd; // 节点
    struct timer_list timer;  // 定时器
    int timeperiod;     // 定时周期ms
    int led_gpio;       // led 的GPIO
    spinlock_t lock;    // 自旋锁
};

struct timer_dev timerdev;      /* 定时器 */

static int timer_open(struct inode *inode, struct file *filp) {
    filp->private_data = &timerdev;
    
    return 0;
}

static int timer_release(struct inode *inode, struct file *filp) {
    // sstruct timer_dev *dev = (struct timer_dev*)filp->private_data;
    
    return 0;
}


static long timer_ioctl(struct file *file, unsigned int cmd, unsigned long arg) {   // arg是应用传递给驱动的周期数据的首地址,占4字节
    int ret = 0;
    int value = 0;
    int timerperiod;
    unsigned long flags;
    struct timer_dev *dev = (struct timer_dev*)file->private_data;

    switch(cmd) {
    case CLOSE_CMD:
        del_timer_sync(&dev->timer);
        break;
    case OPEN_CMD: 
        spin_lock_irqsave(&dev->lock, flags);        // 上锁
        timerperiod = dev->timeperiod;
        spin_unlock_irqrestore(&dev->lock, flags);  // 解锁
        mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->timeperiod));
        break;
    case SETPERIOD_CMD:
        ret = copy_from_user(&value, (int *)arg, sizeof(int));
        if(ret < 0) {
            return -EFAULT;
        }
        spin_lock_irqsave(&dev->lock, flags);   
        dev->timeperiod = value;    // 修改周期
        spin_unlock_irqrestore(&dev->lock, flags);
        mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->timeperiod));
        break;
    default:
        break;
    }

    return 0;
}

/* 操作集合 */
static const struct file_operations timer_fops = {
    .owner = THIS_MODULE,
    .open = timer_open,     
    .unlocked_ioctl = timer_ioctl,
    .release = timer_release,
};

/* 定时器回调函数 */
static void timer_func(unsigned long arg) {
    int timerperiod;
    unsigned long flags;
    struct timer_dev *dev = (struct timer_dev*)arg;
    static int sta = 1;

    sta = !sta;
    gpio_set_value(dev->led_gpio, sta);

    spin_lock_irqsave(&dev->lock, flags);   // 上锁
    timerperiod = dev->timeperiod;
    spin_unlock_irqrestore(&dev->lock, flags); // 解锁
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->timeperiod));	// 重启定时器
}

/* 初始化LED */
static int led_init(struct timer_dev *dev) {
    int ret = 0;
    dev->nd = of_find_node_by_path("/gpioled");
    if(dev->nd == NULL) {
        ret = -EINVAL;
        goto fail_fd;
    }

    dev->led_gpio = of_get_named_gpio(dev->nd, "led-gpios", 0);
    if(dev->led_gpio < 0) {
        ret = -EINVAL;
        goto fail_gpio;
    }

    ret = gpio_request(dev->led_gpio, "led");
    if(ret) {
        ret = -EBUSY;
        printk("IO %d can't request!\r\n", dev->led_gpio);
        goto fail_request;
    }

    ret = gpio_direction_output(dev->led_gpio, 1);      // 默认关灯
    if(ret < 0) {
        ret = -EINVAL;
        goto fail_gpioset;
    }

    return 0;

fail_gpioset:
    gpio_free(dev->led_gpio);
fail_request:
fail_gpio:
fail_fd:
    return ret;
}

/* 驱动函数入口 */
static int __init timer_init(void) {
    int ret = 0;
    /* 1.注册字符设备驱动 */
    timerdev.major = 0;
    if(timerdev.major) {   // 指定了主设备号
        timerdev.devid = MKDEV(timerdev.major, 0);
        ret = register_chrdev_region(timerdev.devid, TIMER_CNT, TIMER_NAME);
    } else {
        ret = alloc_chrdev_region(&timerdev.devid, 0, TIMER_CNT, TIMER_NAME);
        timerdev.major = MAJOR(timerdev.devid);
        timerdev.minor = MINOR(timerdev.minor);
    }
    if(ret < 0) {
        goto fail_devid;
    }
    printk("timer major = %d, minor = %d\r\n", timerdev.major, timerdev.minor);

    /* 2.初始化cdev */
    timerdev.cdev.owner = THIS_MODULE;
    cdev_init(&timerdev.cdev, &timer_fops);

    /* 3.添加cdev */
    ret = cdev_add(&timerdev.cdev, timerdev.devid, TIMER_CNT);
    if(ret < 0) {
        goto fail_cdev;
    }

    /* 4.创建类 */
    timerdev.class = class_create(THIS_MODULE, TIMER_NAME);
    if(IS_ERR(timerdev.class)) {
        ret = PTR_ERR(timerdev.class);
        goto fail_class;
    }

    /* 5.创建设备 */
    timerdev.device = device_create(timerdev.class, NULL, timerdev.devid, NULL, TIMER_NAME);
    if(IS_ERR(timerdev.device)) {
        ret = PTR_ERR(timerdev.device);
        goto fail_device;
    }

    /* 6.初始化LED */
    ret = led_init(&timerdev);
    if(ret < 0) {
        goto fail_ledinit;
    }

    /* 7.初始化定时器 */
    init_timer(&timerdev.timer);
    
    timerdev.timeperiod = 500;
    timerdev.timer.function = timer_func;
    timerdev.timer.expires = jiffies + msecs_to_jiffies(timerdev.timeperiod);
    timerdev.timer.data = (unsigned long)&timerdev;
    add_timer(&timerdev.timer);     // 添加到系统

    return 0;

fail_ledinit:
fail_device:
    class_destroy(timerdev.class);
fail_class:
    cdev_del(&timerdev.cdev);
fail_cdev:
    unregister_chrdev_region(timerdev.devid, TIMER_CNT);
fail_devid:
    return ret;
}


/* 驱动函数出口 */
static void __exit timer_exit(void) {
    /* 关灯 */
    gpio_set_value(timerdev.led_gpio, 1);

    /* 删除定时器 */
    del_timer(&timerdev.timer);

    /* 注销字符设备驱动 */
    cdev_del(&timerdev.cdev);
    unregister_chrdev_region(timerdev.devid, TIMER_CNT);

    device_destroy(timerdev.class, timerdev.devid);
    class_destroy(timerdev.class);

    /* 释放IO */
    gpio_free(timerdev.led_gpio);
}

module_init(timer_init);
module_exit(timer_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wangyu");

该驱动程序是通过Linux内核的定时器来实现控制LED闪烁间隔不同。为了一直闪烁,则在定时器时间结束之后重启定时器。

2.8.2.3 用户测试程序

用户测试程序用来测试驱动程序的,用户只需要调用open、ioctl函数就可以控制LED灯。

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/*
 * argc: 应用程序参数个数
 * argv: 具体的参数内容,字符串形式
 * ./timerAPP <filename>
 * ./timeAPP /dev/timer
*/

#define CLOSE_CMD              _IO(0xEF, 1)         // 关闭命令
#define OPEN_CMD               _IO(0xEF, 2)         // 打开命令
#define SETPERIOD_CMD          _IOW(0xEF, 3, int)   // 设置周期

int main(int argc, char* argv[]) {
    int fd, retvalue;
    char *filename;
    unsigned char databuf[1];
    unsigned int cmd;
    unsigned int arg;
    unsigned char str[100];

    if(argc !=2) {
        printf("Error Usage!\r\n");
        return -1;
    }

    filename = argv[1];
    
    fd = open(filename, O_RDWR);
    if(fd < 0) {
        printf("file %s open failed!\r\n", filename);
        return -1;
    }

    while(1) {
        printf("Input cmd:");
        retvalue = scanf("%d", &cmd);
        if(retvalue != 1) {
            gets(str);  // 防止卡死
        }

        if(cmd == 1) {      // 关闭
            ioctl(fd, CLOSE_CMD, &arg);
        } else if(cmd == 2) {   // 打开
            ioctl(fd, OPEN_CMD, &arg);
        } else if(cmd == 3) {   // 设置周期
            printf("Input timer period:");
            retvalue = scanf("%d", &arg);
            if(retvalue != 1) {
                gets(str);
            }
            ioctl(fd, SETPERIOD_CMD, &arg);
        }
    }

    close(fd);

    return 0;
}

2.9 驱动实验7(中断)

2.9.1 基础知识

中断,简单来讲就是cpu停止正在做的事情去执行另外的任务,然后执行完之后回到中断前的状态继续执行任务,中断又分为硬件中断和软中断,软中断所针对的是SMP系统。

2.9.2 需求

本次实验的需求就是采用按键中断实验,分为三个版本,基础按键中断版本,定时器按键消抖,中断上下部分完善。

2.9.3 基础按键中断版本

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/atomic.h>
#include <linux/string.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/interrupt.h>

/*
 * timer_temp 文件是编写驱动的模板文件,以后均在这上面增加内容,
 * 这个字符驱动框架必须熟悉。
*/

#define IMX6UIRQ_CNT             1
#define IMX6UIRQ_NAME        "imx6uirq"
#define KEY_NUM                  1
#define KEY0VALUE               0x01
#define INVAKEY                 0xFF

/* key结构体 */
struct irq_keydesc {
    int gpio;       // io编号
    int irqnum;     // 中断号
    unsigned char value;    // 键值
    char name[10];      // 名字
    irqreturn_t (*handler) (int, void*);     // 中断处理函数
};

/* imx6uirq设备结构体 */
struct imx6uirq_dev {
    dev_t devid;        // 设备号
    int major;          // 主设备号
    int minor;          // 次设备号
    struct cdev cdev;   // 字符设备
    struct class *class;
    struct device *device;
    struct device_node *nd;
    struct irq_keydesc irqkey[KEY_NUM];
    struct timer_list timer;        // 定时器

    atomic_t keyvalue;
    atomic_t releasekey;
};

struct imx6uirq_dev imx6uirq;      /* 中断 */

static int imx6uirq_open(struct inode *inode, struct file *filp) {
    filp->private_data = &imx6uirq;
    
    return 0;
}

static int imx6uirq_release(struct inode *inode, struct file *filp) {
    // sstruct imx6uirq_dev *dev = (struct imx6uirq_dev*)filp->private_data;
    
    return 0;
}

static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) {
    int ret = 0;
    unsigned char keyvalue;
    unsigned char releasekey;
    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 ret;

data_error:
    return -EINVAL;
}


/* 操作集合 */
static const struct file_operations im6uirq_fops = {
    .owner = THIS_MODULE,
    .open = imx6uirq_open,
    .read = imx6uirq_read,
    .release = imx6uirq_release,
};

/* 按键中断处理函数 */
static irqreturn_t key0_handler(int irq, void *dev_id) {
    struct imx6uirq_dev *dev = dev_id;

    dev->timer.data = (volatile long)dev_id;
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20));     // 20ms定时

    return IRQ_HANDLED;
}

/* 定时器处理函数 */
static void timer_func(unsigned long arg) {
    int value = 0;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev*)arg;

    value = gpio_get_value(dev->irqkey[0].gpio);
    if(value == 0) {    // 按下
        atomic_set(&dev->keyvalue, dev->irqkey[0].value);
    } else if(value == 1) {     // 释放
        atomic_set(&dev->keyvalue, 0x80 | dev->irqkey[0].value);
        atomic_set(&dev->releasekey, 1);    // 完整的按键过程
    }
}

/* 按键初始化 */
static int keyio_init(struct imx6uirq_dev *dev) {
    int ret = 0;
    int i = 0;

    /* 1.按键初始化 */
    dev->nd = of_find_node_by_path("/key");
    if(dev->nd == NULL) {
        ret = -EINVAL;
        goto fail_nd;
    }

    for(i = 0; i < KEY_NUM; i++) {
        dev->irqkey[i].gpio = of_get_named_gpio(dev->nd, "key-gpios", i);   // 可以判断返回值
    }

    for(i = 0; i < KEY_NUM; i++) {
        memset(dev->irqkey[i].name, 0, sizeof(dev->irqkey[i].name));
        sprintf(dev->irqkey[i].name, "KEY%d", i);
        gpio_request(dev->irqkey[i].gpio, dev->irqkey[i].name);
        gpio_direction_input(dev->irqkey[i].gpio);

        dev->irqkey[i].irqnum = gpio_to_irq(dev->irqkey[i].gpio);   // 获取中断号
#if 0
        dev->irqkey[i].irqnum = irq_of_parse_and_map(dev->nd, i);   // 获取中断号(通用)
#endif

    }

    dev->irqkey[0].handler = key0_handler;  // 初始化
    dev->irqkey[0].value = KEY0VALUE;

    /* 2.按键中断初始化 */
    for(i = 0; i < KEY_NUM; i++) {
        ret = request_irq(dev->irqkey[i].irqnum, dev->irqkey[i].handler, 
                        IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, 
                        dev->irqkey[i].name, &imx6uirq);
        if(ret) {
            printk("irq %d request failed!\r\n", dev->irqkey[i].irqnum);
            goto fail_irq;
        }
    }

    /* 3.初始化定时器 */
    init_timer(&imx6uirq.timer);
    imx6uirq.timer.function = timer_func;

    return 0;

fail_irq:
    for(i = 0; i < KEY_NUM; i++) {
        gpio_free(dev->irqkey[i].gpio);
    }
fail_nd:
    return ret;
}

/* 驱动函数入口 */
static int __init timer_init(void) {
    int ret = 0;
    /* 1.注册字符设备驱动 */
    imx6uirq.major = 0;
    if(imx6uirq.major) {   // 指定了主设备号
        imx6uirq.devid = MKDEV(imx6uirq.major, 0);
        ret = register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
    } else {
        ret = alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
        imx6uirq.major = MAJOR(imx6uirq.devid);
        imx6uirq.minor = MINOR(imx6uirq.minor);
    }
    if(ret < 0) {
        goto fail_devid;
    }
    printk("timer major = %d, minor = %d\r\n", imx6uirq.major, imx6uirq.minor);

    /* 2.初始化cdev */
    imx6uirq.cdev.owner = THIS_MODULE;
    cdev_init(&imx6uirq.cdev, &im6uirq_fops);

    /* 3.添加cdev */
    ret = cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);
    if(ret < 0) {
        goto fail_cdev;
    }

    /* 4.创建类 */
    imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
    if(IS_ERR(imx6uirq.class)) {
        ret = PTR_ERR(imx6uirq.class);
        goto fail_class;
    }

    /* 5.创建设备 */
    imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME);
    if(IS_ERR(imx6uirq.device)) {
        ret = PTR_ERR(imx6uirq.device);
        goto fail_device;
    }

    /* 初始化IO */
    ret = keyio_init(&imx6uirq);
    if(ret < 0) {
        goto fail_keyinit;
    }

    /* 初始化原子变量(按键) */
    atomic_set(&imx6uirq.keyvalue, INVAKEY);
    atomic_set(&imx6uirq.releasekey, 0);

    return 0;

fail_keyinit:
fail_device:
    class_destroy(imx6uirq.class);
fail_class:
    cdev_del(&imx6uirq.cdev);
fail_cdev:
    unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
fail_devid:
    return ret;
}


/* 驱动函数出口 */
static void __exit timer_exit(void) {
    int i = 0;

    /* 1.释放中断 */
    for(i = 0; i < KEY_NUM; i++) {
        free_irq(imx6uirq.irqkey[i].irqnum, &imx6uirq);
    }

    /* 2.释放IO */
    for(i = 0; i < KEY_NUM; i++) {
        gpio_free(imx6uirq.irqkey[i].irqnum);
    }

    /* 3.删除定时器 */
    del_timer_sync(&imx6uirq.timer);

    /* 注销字符设备驱动 */
    cdev_del(&imx6uirq.cdev);
    unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);

    device_destroy(imx6uirq.class, imx6uirq.devid);
    class_destroy(imx6uirq.class);
}

module_init(timer_init);
module_exit(timer_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wangyu");

2.9.4 定时器按键消抖中断

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/atomic.h>
#include <linux/string.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/interrupt.h>

/*
 * timer_temp 文件是编写驱动的模板文件,以后均在这上面增加内容,
 * 这个字符驱动框架必须熟悉。
*/

#define IMX6UIRQ_CNT             1
#define IMX6UIRQ_NAME        "imx6uirq"
#define KEY_NUM                  1
#define KEY0VALUE               0x01
#define INVAKEY                 0xFF

/* key结构体 */
struct irq_keydesc {
    int gpio;       // io编号
    int irqnum;     // 中断号
    unsigned char value;    // 键值
    char name[10];      // 名字
    irqreturn_t (*handler) (int, void*);     // 中断处理函数
    struct tasklet_struct tasklet;
};

/* imx6uirq设备结构体 */
struct imx6uirq_dev {
    dev_t devid;        // 设备号
    int major;          // 主设备号
    int minor;          // 次设备号
    struct cdev cdev;   // 字符设备
    struct class *class;
    struct device *device;
    struct device_node *nd;
    struct irq_keydesc irqkey[KEY_NUM];
    struct timer_list timer;        // 定时器

    atomic_t keyvalue;
    atomic_t releasekey;
};

struct imx6uirq_dev imx6uirq;      /* 中断 */

static int imx6uirq_open(struct inode *inode, struct file *filp) {
    filp->private_data = &imx6uirq;
    
    return 0;
}

static int imx6uirq_release(struct inode *inode, struct file *filp) {
    // sstruct imx6uirq_dev *dev = (struct imx6uirq_dev*)filp->private_data;
    
    return 0;
}

static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) {
    int ret = 0;
    unsigned char keyvalue;
    unsigned char releasekey;
    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 ret;

data_error:
    return -EINVAL;
}


/* 操作集合 */
static const struct file_operations im6uirq_fops = {
    .owner = THIS_MODULE,
    .open = imx6uirq_open,
    .read = imx6uirq_read,
    .release = imx6uirq_release,
};

/* 按键中断处理函数 */
static irqreturn_t key0_handler(int irq, void *dev_id) {
    struct imx6uirq_dev *dev = dev_id;

    tasklet_schedule(&dev->irqkey[0].tasklet);

    return IRQ_HANDLED;
}

/* tasklet */
static void key_tasklet(unsigned long data) {
    struct imx6uirq_dev *dev = (struct imx6uirq_dev*)data;

    dev->timer.data = data;
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20));     // 20ms定时
}

/* 定时器处理函数 */
static void timer_func(unsigned long arg) {
    int value = 0;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev*)arg;

    value = gpio_get_value(dev->irqkey[0].gpio);
    if(value == 0) {    // 按下
        atomic_set(&dev->keyvalue, dev->irqkey[0].value);
    } else if(value == 1) {     // 释放
        atomic_set(&dev->keyvalue, 0x80 | dev->irqkey[0].value);
        atomic_set(&dev->releasekey, 1);    // 完整的按键过程
    }
}

/* 按键初始化 */
static int keyio_init(struct imx6uirq_dev *dev) {
    int ret = 0;
    int i = 0;

    /* 1.按键初始化 */
    dev->nd = of_find_node_by_path("/key");
    if(dev->nd == NULL) {
        ret = -EINVAL;
        goto fail_nd;
    }

    for(i = 0; i < KEY_NUM; i++) {
        dev->irqkey[i].gpio = of_get_named_gpio(dev->nd, "key-gpios", i);   // 可以判断返回值
    }

    for(i = 0; i < KEY_NUM; i++) {
        memset(dev->irqkey[i].name, 0, sizeof(dev->irqkey[i].name));
        sprintf(dev->irqkey[i].name, "KEY%d", i);
        gpio_request(dev->irqkey[i].gpio, dev->irqkey[i].name);
        gpio_direction_input(dev->irqkey[i].gpio);

        dev->irqkey[i].irqnum = gpio_to_irq(dev->irqkey[i].gpio);   // 获取中断号
#if 0
        dev->irqkey[i].irqnum = irq_of_parse_and_map(dev->nd, i);   // 获取中断号(通用)
#endif

    }

    dev->irqkey[0].handler = key0_handler;  // 初始化
    dev->irqkey[0].value = KEY0VALUE;

    /* 2.按键中断初始化 */
    for(i = 0; i < KEY_NUM; i++) {
        ret = request_irq(dev->irqkey[i].irqnum, dev->irqkey[i].handler, 
                        IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, 
                        dev->irqkey[i].name, &imx6uirq);
        if(ret) {
            printk("irq %d request failed!\r\n", dev->irqkey[i].irqnum);
            goto fail_irq;
        }

        tasklet_init(&dev->irqkey[i].tasklet, key_tasklet, (unsigned long)dev);
    }

    /* 3.初始化定时器 */
    init_timer(&imx6uirq.timer);
    imx6uirq.timer.function = timer_func;

    return 0;

fail_irq:
    for(i = 0; i < KEY_NUM; i++) {
        gpio_free(dev->irqkey[i].gpio);
    }
fail_nd:
    return ret;
}

/* 驱动函数入口 */
static int __init timer_init(void) {
    int ret = 0;
    /* 1.注册字符设备驱动 */
    imx6uirq.major = 0;
    if(imx6uirq.major) {   // 指定了主设备号
        imx6uirq.devid = MKDEV(imx6uirq.major, 0);
        ret = register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
    } else {
        ret = alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
        imx6uirq.major = MAJOR(imx6uirq.devid);
        imx6uirq.minor = MINOR(imx6uirq.minor);
    }
    if(ret < 0) {
        goto fail_devid;
    }
    printk("timer major = %d, minor = %d\r\n", imx6uirq.major, imx6uirq.minor);

    /* 2.初始化cdev */
    imx6uirq.cdev.owner = THIS_MODULE;
    cdev_init(&imx6uirq.cdev, &im6uirq_fops);

    /* 3.添加cdev */
    ret = cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);
    if(ret < 0) {
        goto fail_cdev;
    }

    /* 4.创建类 */
    imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
    if(IS_ERR(imx6uirq.class)) {
        ret = PTR_ERR(imx6uirq.class);
        goto fail_class;
    }

    /* 5.创建设备 */
    imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME);
    if(IS_ERR(imx6uirq.device)) {
        ret = PTR_ERR(imx6uirq.device);
        goto fail_device;
    }

    /* 初始化IO */
    ret = keyio_init(&imx6uirq);
    if(ret < 0) {
        goto fail_keyinit;
    }

    /* 初始化原子变量(按键) */
    atomic_set(&imx6uirq.keyvalue, INVAKEY);
    atomic_set(&imx6uirq.releasekey, 0);

    return 0;

fail_keyinit:
fail_device:
    class_destroy(imx6uirq.class);
fail_class:
    cdev_del(&imx6uirq.cdev);
fail_cdev:
    unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
fail_devid:
    return ret;
}


/* 驱动函数出口 */
static void __exit timer_exit(void) {
    int i = 0;

    /* 1.释放中断 */
    for(i = 0; i < KEY_NUM; i++) {
        free_irq(imx6uirq.irqkey[i].irqnum, &imx6uirq);
    }

    /* 2.释放IO */
    for(i = 0; i < KEY_NUM; i++) {
        gpio_free(imx6uirq.irqkey[i].irqnum);
    }

    /* 3.删除定时器 */
    del_timer_sync(&imx6uirq.timer);

    /* 注销字符设备驱动 */
    cdev_del(&imx6uirq.cdev);
    unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);

    device_destroy(imx6uirq.class, imx6uirq.devid);
    class_destroy(imx6uirq.class);
}

module_init(timer_init);
module_exit(timer_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wangyu");

2.9.4 完善按键中断上下部分

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/atomic.h>
#include <linux/string.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/interrupt.h>

/*
 * timer_temp 文件是编写驱动的模板文件,以后均在这上面增加内容,
 * 这个字符驱动框架必须熟悉。
*/

#define IMX6UIRQ_CNT             1
#define IMX6UIRQ_NAME        "imx6uirq"
#define KEY_NUM                  1
#define KEY0VALUE               0x01
#define INVAKEY                 0xFF

/* key结构体 */
struct irq_keydesc {
    int gpio;       // io编号
    int irqnum;     // 中断号
    unsigned char value;    // 键值
    char name[10];      // 名字
    irqreturn_t (*handler) (int, void*);     // 中断处理函数
    struct tasklet_struct tasklet;
};

/* imx6uirq设备结构体 */
struct imx6uirq_dev {
    dev_t devid;        // 设备号
    int major;          // 主设备号
    int minor;          // 次设备号
    struct cdev cdev;   // 字符设备
    struct class *class;
    struct device *device;
    struct device_node *nd;
    struct irq_keydesc irqkey[KEY_NUM];
    struct timer_list timer;        // 定时器

    atomic_t keyvalue;
    atomic_t releasekey;
    struct work_struct work;
};

struct imx6uirq_dev imx6uirq;      /* 中断 */

static int imx6uirq_open(struct inode *inode, struct file *filp) {
    filp->private_data = &imx6uirq;
    
    return 0;
}

static int imx6uirq_release(struct inode *inode, struct file *filp) {
    // sstruct imx6uirq_dev *dev = (struct imx6uirq_dev*)filp->private_data;
    
    return 0;
}

static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) {
    int ret = 0;
    unsigned char keyvalue;
    unsigned char releasekey;
    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 ret;

data_error:
    return -EINVAL;
}


/* 操作集合 */
static const struct file_operations im6uirq_fops = {
    .owner = THIS_MODULE,
    .open = imx6uirq_open,
    .read = imx6uirq_read,
    .release = imx6uirq_release,
};


/* 按键中断处理函数 */
static irqreturn_t key0_handler(int irq, void *dev_id) {
    struct imx6uirq_dev *dev = dev_id;

    // tasklet_schedule(&dev->irqkey[0].tasklet);
    
    schedule_work(&dev->work);

    return IRQ_HANDLED;
}


/* tasklet */
static void key_tasklet(unsigned long data) {
    struct imx6uirq_dev *dev = (struct imx6uirq_dev*)data;

    dev->timer.data = data;
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20));     // 20ms定时
}

/* work */
static void key_work(struct work_struct *work) {
    // struct imx6uirq_dev *dev = (struct imx6uirq_dev*)data;

    struct imx6uirq_dev *dev = container_of(work, struct imx6uirq_dev, work);

    dev->timer.data = (unsigned long)dev;
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20));     // 20ms定时
}


/* 定时器处理函数 */
static void timer_func(unsigned long arg) {
    int value = 0;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev*)arg;

    value = gpio_get_value(dev->irqkey[0].gpio);
    if(value == 0) {    // 按下
        atomic_set(&dev->keyvalue, dev->irqkey[0].value);
    } else if(value == 1) {     // 释放
        atomic_set(&dev->keyvalue, 0x80 | dev->irqkey[0].value);
        atomic_set(&dev->releasekey, 1);    // 完整的按键过程
    }
}

/* 按键初始化 */
static int keyio_init(struct imx6uirq_dev *dev) {
    int ret = 0;
    int i = 0;

    /* 1.按键初始化 */
    dev->nd = of_find_node_by_path("/key");
    if(dev->nd == NULL) {
        ret = -EINVAL;
        goto fail_nd;
    }

    for(i = 0; i < KEY_NUM; i++) {
        dev->irqkey[i].gpio = of_get_named_gpio(dev->nd, "key-gpios", i);   // 可以判断返回值
    }

    for(i = 0; i < KEY_NUM; i++) {
        memset(dev->irqkey[i].name, 0, sizeof(dev->irqkey[i].name));
        sprintf(dev->irqkey[i].name, "KEY%d", i);
        gpio_request(dev->irqkey[i].gpio, dev->irqkey[i].name);
        gpio_direction_input(dev->irqkey[i].gpio);

        dev->irqkey[i].irqnum = gpio_to_irq(dev->irqkey[i].gpio);   // 获取中断号
#if 0
        dev->irqkey[i].irqnum = irq_of_parse_and_map(dev->nd, i);   // 获取中断号(通用)
#endif

    }

    dev->irqkey[0].handler = key0_handler;  // 初始化
    dev->irqkey[0].value = KEY0VALUE;

    /* 2.按键中断初始化 */
    for(i = 0; i < KEY_NUM; i++) {
        ret = request_irq(dev->irqkey[i].irqnum, dev->irqkey[i].handler, 
                        IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, 
                        dev->irqkey[i].name, &imx6uirq);
        if(ret) {
            printk("irq %d request failed!\r\n", dev->irqkey[i].irqnum);
            goto fail_irq;
        }

        // tasklet_init(&dev->irqkey[i].tasklet, key_tasklet, (unsigned long)dev);
    }

    INIT_WORK(&dev->work, key_work);
    /* 3.初始化定时器 */
    init_timer(&imx6uirq.timer);
    imx6uirq.timer.function = timer_func;

    return 0;

fail_irq:
    for(i = 0; i < KEY_NUM; i++) {
        gpio_free(dev->irqkey[i].gpio);
    }
fail_nd:
    return ret;
}

/* 驱动函数入口 */
static int __init timer_init(void) {
    int ret = 0;
    /* 1.注册字符设备驱动 */
    imx6uirq.major = 0;
    if(imx6uirq.major) {   // 指定了主设备号
        imx6uirq.devid = MKDEV(imx6uirq.major, 0);
        ret = register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
    } else {
        ret = alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
        imx6uirq.major = MAJOR(imx6uirq.devid);
        imx6uirq.minor = MINOR(imx6uirq.minor);
    }
    if(ret < 0) {
        goto fail_devid;
    }
    printk("timer major = %d, minor = %d\r\n", imx6uirq.major, imx6uirq.minor);

    /* 2.初始化cdev */
    imx6uirq.cdev.owner = THIS_MODULE;
    cdev_init(&imx6uirq.cdev, &im6uirq_fops);

    /* 3.添加cdev */
    ret = cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);
    if(ret < 0) {
        goto fail_cdev;
    }

    /* 4.创建类 */
    imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
    if(IS_ERR(imx6uirq.class)) {
        ret = PTR_ERR(imx6uirq.class);
        goto fail_class;
    }

    /* 5.创建设备 */
    imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME);
    if(IS_ERR(imx6uirq.device)) {
        ret = PTR_ERR(imx6uirq.device);
        goto fail_device;
    }

    /* 初始化IO */
    ret = keyio_init(&imx6uirq);
    if(ret < 0) {
        goto fail_keyinit;
    }

    /* 初始化原子变量(按键) */
    atomic_set(&imx6uirq.keyvalue, INVAKEY);
    atomic_set(&imx6uirq.releasekey, 0);

    return 0;

fail_keyinit:
fail_device:
    class_destroy(imx6uirq.class);
fail_class:
    cdev_del(&imx6uirq.cdev);
fail_cdev:
    unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
fail_devid:
    return ret;
}


/* 驱动函数出口 */
static void __exit timer_exit(void) {
    int i = 0;

    /* 1.释放中断 */
    for(i = 0; i < KEY_NUM; i++) {
        free_irq(imx6uirq.irqkey[i].irqnum, &imx6uirq);
    }

    /* 2.释放IO */
    for(i = 0; i < KEY_NUM; i++) {
        gpio_free(imx6uirq.irqkey[i].irqnum);
    }

    /* 3.删除定时器 */
    del_timer_sync(&imx6uirq.timer);

    /* 注销字符设备驱动 */
    cdev_del(&imx6uirq.cdev);
    unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);

    device_destroy(imx6uirq.class, imx6uirq.devid);
    class_destroy(imx6uirq.class);
}

module_init(timer_init);
module_exit(timer_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wangyu");
  • 4
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值