驱动程序
#include "head.h"
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/device.h>
#include <linux/export.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/seqlock.h>
#include <linux/atomic.h>
#include <linux/wait.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/poll.h>
#include <linux/time.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>char kbuf[128] = {0}; // 用来接用户空间传递的数据// 定义指针接收映射成功的虚拟内存首地址
gpio_t *vir_led1;
gpio_t *vir_led2;
gpio_t *vir_led3;
unsigned int *vir_rcc;// 接收struct class/struct device 类型空间的首地址
struct class *cls;
struct device *dev;struct cdev *cdev; // 字符驱动设备空间地址
unsigned int major = 0; // 主设备号
unsigned int minor = 0; // 次设备号
dev_t devnum; // 设备号unsigned int condition=0; //定义有无数据判断值//定义等待队列头
wait_queue_head_t wq_head;// 设备节点指针
struct device_node *dnode;
// 中断号
unsigned int key_irqnum[3];
// gpio_desc对象指针
struct gpio_desc *desc[3];
unsigned number=0;// 定义中断处理函数
irqreturn_t key_handler(int irq, void *dev)
{number=!number;switch ((int)dev){case 0: // key1printk("key1 interrupt\n");gpiod_set_value(desc[0], !gpiod_get_value(desc[1]));break;case 1: //key2printk("key2 interrupt\n");gpiod_set_value(desc[1], !gpiod_get_value(desc[1]));break;case 2: //key3printk("key3 interrupt\n");gpiod_set_value(desc[2], !gpiod_get_value(desc[2]));break;}condition=1;wake_up_interruptible(&wq_head);return IRQ_HANDLED; // 中断正常处理
}// 封装操作方法,自己封装的函数
int mycdev_open(struct inode *inode, struct file *file)
{printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);return 0;
}
ssize_t mycdev_read(struct file *file, char *ubuf, size_t size, loff_t *lof)
{int ret;//把进程切换为休眠态wait_event_interruptible(wq_head,condition);//把准备好的硬件数据拷贝到用户空间ret=copy_to_user(ubuf,&number,size);if(ret){printk("cpoy_to_user error\n");return ret;}condition=0; //表示下次数据没有准备好printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);return 0;
}
ssize_t mycdev_write(struct file *file, const char *ubuf, size_t size, loff_t *lof)
{printk("%s:%s:%d:%s\n", __FILE__, __func__, __LINE__, kbuf);return 0;
}int mycdev_close(struct inode *inode, struct file *file)
{printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);return 0;
}// 定义操作方法结构体变量并初始化,让结构体变量指向自己封装的函数名
struct file_operations fops = {.open = mycdev_open,.read = mycdev_read,.write = mycdev_write,.release = mycdev_close,
};static int __init mycdev_init(void)
{int i; // 设备节点int ret;//初始化等待队列头init_waitqueue_head(&wq_head);// 1.分配字符设备驱动对象// 申请一个字符设备对象驱动空间cdev = cdev_alloc();if (cdev == NULL){printk("cdev_alloc error\n");ret = -EFAULT;goto OUT1;}printk("申请字符设备驱动成功\n");// 2.初始化字符设备驱动对象cdev_init(cdev, &fops);// 3.申请设备号if (major > 0) // 静态申请设备号{ret = register_chrdev_region(MKDEV(major, minor), 3, "mychrdev");if (ret){printk("静态申请设备号失败\n");goto OUT2;}printk("静态申请设备号成功\n");}else if (major == 0){ret = alloc_chrdev_region(&devnum, minor, 3, "mychrdev");if (ret){printk("动态申请设备号失败\n");goto OUT2;}printk("动态申请设备号成功\n");minor = MINOR(devnum); // 根据设备号获取次设备号major = MAJOR(devnum); // 根据设备号获取主设备号}// 4.注册字符设备驱动对象ret = cdev_add(cdev, MKDEV(major, minor), 3);if (ret){printk("注册字符设备驱动对象失败\n");goto OUT3;}printk("注册字符设备驱动对象成功\n");// 5.向上提交目录信息cls = class_create(THIS_MODULE, "mycdev");if (IS_ERR(cls)){printk("向上提交目录信息失败\n");ret = -PTR_ERR(cls);goto OUT4;}printk("向上提交目录信息成功\n");// 6.向上提交设备节点信息for (i = 0; i < 3; i++){dev = device_create(cls, NULL, MKDEV(major, i), NULL, "mycdev%d", i);if (IS_ERR(dev)){printk("向上提交设备节点信息失败\n");ret = -PTR_ERR(dev);goto OUT5;}}printk("向上提交设备节点信息成功\n");//-------------------------解析按键的设备树节点--------------------------dnode = of_find_compatible_node(NULL, NULL, "zmgh,myirq");if (dnode == NULL){printk("解析设备树节点失败\n");return -ENXIO;}printk("解析设备树节点成功\n");for (i = 0; i < 3; i++){// 根据按键的设备树节点解析按键1的软中断号key_irqnum[i] = irq_of_parse_and_map(dnode, i);if (!key_irqnum[i]){printk("解析按键1的软中断号失败\n");goto OUT6;}printk("解析按键1的软中断号成功 %d\n", key_irqnum[i]);// 注册中断,下降沿触发ret = request_irq(key_irqnum[i], key_handler, IRQF_TRIGGER_FALLING, "key", (void *)i);if (ret < 0){printk("注册中断失败\n");return -ENXIO;}}printk("注册中断成功\n");// ------------------------解析LED设备树节点,根据路径解析--------------------dnode = of_find_node_by_path("/leds");if (dnode == NULL){printk("解析设备树节点失败\n");return -ENXIO;}printk("解析设备树节点成功\n");// 申请gpio_desc对象并设置输出为低电平desc[0] = gpiod_get_from_of_node(dnode, "led-gpios", 0, GPIOD_OUT_LOW, NULL);desc[1] = gpiod_get_from_of_node(dnode, "led-gpios", 1, GPIOD_OUT_LOW, NULL);desc[2] = gpiod_get_from_of_node(dnode, "led-gpios", 2, GPIOD_OUT_LOW, NULL);for (i = 0; i < 3; i++){if (IS_ERR(desc[0])){printk("申请gpio对象失败\n");return -PTR_ERR(desc[i]);}}printk("申请gpio对象成功\n");return 0;
OUT6://注销软中断号for (--i; i >= 0; i--){// 注销中断free_irq(key_irqnum[i], NULL);}
OUT5:// 注销目录信息for (--i; i >= 0; i--){// 释放提交成功的设备节点信息device_destroy(cls, MKDEV(major, i));}class_destroy(cls); // 销毁目录
OUT4:// 注销字符设备驱动对象cdev_del(cdev);
OUT3:// 注销设备号unregister_chrdev_region(MKDEV(major, minor), 3);
OUT2:// 静态设备号申请失败,就要释放申请的字符驱动空间kfree(cdev);
OUT1:return ret;
}
static void __exit mycdev_exit(void)
{unsigned int i;//----------------------------------------销毁目录/设备信息-------------------------------//<1.设备销毁,现有目录后有设备,所以先销设备,后销目录for (i = 0; i < 3; i++){device_destroy(cls, MKDEV(major, i));}//<2.目录销毁class_destroy(cls);// 注销字符设备驱动对象cdev_del(cdev);// 释放设备号unregister_chrdev_region(MKDEV(major, minor), 3);// 释放对象空间kfree(cdev);//注销软中断号for (--i; i >= 0; i--){// 注销中断free_irq(key_irqnum[i], NULL);}// 灭灯for (i = 0; i < 3; i++){gpiod_set_value(desc[i], 0);// 注销GPIO编号gpiod_put(desc[i]);}
}module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
应用程序
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include "head.h"int main(int argc,const char * argv[])
{int fd;int number;fd = open("/dev/mycdev0",O_RDWR);if(fd < 0){perror("error open ");return -1;}printf("打开设备文件成功\n");while(1){read(fd,&number,sizeof(number));printf("number=%d\n",number);}return 0;
}