文章目录

  • 1. 字符设备驱动框架
    • 1.1 框架简介
    • 1.2 如何实现自己的驱动
  • 2. 字符设备开发实验
    • 2.1 环境搭建
    • 2.2 驱动实验1
      • 2.2.1 需求
      • 2.2.2 字符驱动源文件
      • 2.2.3 驱动测试程序
      • 2.2.4 Makefile
      • 2.2.5 json配置文件
    • 2.3 驱动实验2
      • 2.3.1 需求
      • 2.3.2 LED驱动源文件
      • 2.3.3 LED驱动测试程序
    • 2.4 学习设备树
      • 2.4.1 设备树基础
      • 2.4.2 设备树实验
    • 2.5 驱动实验3
      • 2.5.1 需求
      • 2.5.2 驱动源程序
    • 2.6 驱动实验4(pinctrl和gpio子系统)
      • 2.6.1 需求
      • 2.6.2 驱动程序(控制LED灯和蜂鸣器)
    • 2.7 驱动实验5(并发与竞争)
      • 2.7.1 需求
      • 2.7.2 驱动程序
    • 2.8 驱动实验6(定时器)
      • 2.8.1 定时器版本1
        • 2.8.1.1 需求
        • 2.8.1.2 驱动程序
      • 2.8.2 定时器版本2
        • 2.8.2.1 需求
        • 2.8.2.2 驱动程序
        • 2.8.2.3 用户测试程序
    • 2.9 驱动实验7(中断)
      • 2.9.1 基础知识
      • 2.9.2 需求
      • 2.9.3 基础按键中断版本
      • 2.9.4 定时器按键消抖中断
      • 2.9.4 完善按键中断上下部分

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_alientekCURRENT_PATH := $(shell pwd)
obj-m := chrdevbase.obuild: kernel_moduleskernel_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       1int 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 0alphaled {#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]);
#endifIMX6U_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;     // 定时周期msint led_gpio;       // led 的GPIOspinlock_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 0dev->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 0dev->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 0dev->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");

嵌入式设备驱动(实战)相关推荐

  1. 《嵌入式设备驱动开发精解》——导读

    前言 嵌入式设备驱动开发精解 本书的编写主要是针对从事嵌入式软件开发人员.本书的内容主要涵盖ARM CPU以及各种常用外部设备驱动开发的方方面面,包括各种硬件接口.硬件接口协议说明以及各种外设的使用及 ...

  2. 转载:谢谢原作者:块设备驱动实战基础篇四 (逐渐成型,加入ioctl通信机制)

    1.6介绍一种内核与用户空间通信的方法-misc设备ioctl机制 块设备驱动开发中往往需要配合用户态的管理程序工具,管理我们的块设备,此时我们需要涉及用户空间程序与块设备通信的方法,ioctl机制就 ...

  3. 转载:谢谢原作者: 块设备驱动实战基础篇二 (继续完善170行过滤驱动代码至200行)

    1.3块设备驱动关键数据结构及函数API详细剖析 经过上节四个步骤我们已经熟悉并实战了一个最基本的过滤块设备驱动的设计技巧,我们这一节先不继续实战,我们本节把上节170行代码中接触到的块设备核心数据结 ...

  4. 转载:谢谢原作者:块设备驱动实战基础篇一 (170行代码构建一个逻辑块设备驱动)

    1   内核块设备驱动基础学习与实战 1.1 设备驱动IO架构初探 操作系统是如何将数据读到缓冲区的,发生了什么?我们带着这样的问题,粗略走一下read调用系统过程,希望这个初探,可以唤起大家研究操作 ...

  5. linux 字符设备驱动实战

    文章目录 字符设备 1.加载卸载模块 2.字符设备相关结构体 2.1.file_operations 结构体 2.2.cdev 结构体 2.3.cdev 系列函数 常使用模板如下 3.注册设备号 常使 ...

  6. 概述嵌入式设备驱动,教你怎么“玩”转嵌入式开发

    一.设备驱动的作用     对设备驱动最通俗的解释就是"驱使硬件设备行动".设备驱动与底层硬件直接打交道,按照硬件设备的具体工作方式读写设备寄存器,完成设备的轮询.中断处理.DMA ...

  7. 转载:谢谢原作者:块设备驱动实战基础篇三 (BIO请求回调机制)

    1.5 块设备请求返回处理回调机制 本节我们继续完善1.4节中的代码,在上节我们完成了请求的过滤转发,那么请求被磁盘处理完成后返回回来的路径处理是怎样的,本节我们继续带着这样的问题再一次完善我们的驱动 ...

  8. 块设备驱动、bio理解

    别人写过的内容,我就不写了.贴一下大佬的博客,写的非常好: 块设备驱动实战基础篇一 (170行代码构建一个逻辑块设备驱动) 块设备驱动实战基础篇二 (继续完善170行过滤驱动代码至200行) 块设备驱 ...

  9. linux MISC设备驱动

    系列文章 I.MX6ULL 手册查找使用方法 实战点亮LED(寄存器版) I.MX6ULL 手册查找使用方法 实战点亮LED(固件库版本) linux 字符设备驱动实战 linux LED设备驱动文件 ...

最新文章

  1. android 画布实现签名,Android 自定义View手写签名并保存图片
  2. 基于Chrome开源提取的界面开发框架 三
  3. redis的主从数据库复制功能
  4. 3 操作系统第二章 进程管理 进程定义、特征、组织、状态与转换
  5. spring配置讲解
  6. Android年月日选择,Android日期选择器实现年月日三级联动
  7. virtualbox macos_MacOS 终于可以完美使用 Podman 了!
  8. 动词ing基本用法_哪些动词后面只能接动名词背诵口诀
  9. 推荐12个非常不错而且免费的后台管理HTML模板
  10. python读取txt文件存储数组_python – 从文本文件中将数据读入numpy数组
  11. opencv 利用图像轮廓函数填充圆环内部 图像轮廓函数应用小例子
  12. Security+ 学习笔记12 安全编码实践
  13. mysql utf8 bom_UTF8的BOM
  14. 电动自动吞吐式IC卡RFID读写器EMV模块HX150进卡命令
  15. hustOJ使用问题
  16. 将图片放大如何保持图片的清晰度?
  17. centos7下安装mysql5.7(rpm)
  18. 1、51单片机——郭天祥课后习题解答
  19. C语言编程>第二周 ① 打印菱形图案
  20. springboot2.x 整合 elasticsearch 创建索引的方式

热门文章

  1. 声讨《完美安装mplayer手册!》一文
  2. 【计算机视觉】数字图像处理(六)—— 图像压缩
  3. php ifp,人人商城微擎的ifp ife ifpp的作用
  4. [汇编语言]-第四章可执行文件中的程序装入内存并运行的原理
  5. 统计自然语言处理基础-第三章 语言学基础(笔记)
  6. mysql 计算农历_MySQL 获取农历年函数
  7. [Spam]垃圾邮件者纲要(Spammer's Compendium)
  8. Android(三)原生开发基本知识
  9. 热图(Heatmap)绘制(matplotlib与seaborn)
  10. sql server误删数据恢复delete(低效版)