Linux学习笔记(20)——linux异步通信设备驱动

  1. 设备树文件仍与18节的保持不变
  2. 编写阻塞设备驱动程序,编译拷贝到NFS网络文件系统的/lib/modules/4.1.15目录下
/* 
 * 文件名   : asyncnoti.c
 * 作者     : glen  
 * 描述     : asyncnoti驱动文件
 */
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/string.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fcntl.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define IRQ_CNT         1       /* 设备号个数 */
#define IRQ_NAME        "asyncnoti"   /* 名字 */
#define KEY0_PRESS      0x01    /* KEY0按键按下 */
#define KEY0_RELEASE    0x81    /* KEY0按键释放 */
#define KEY_NUM         1       /* 按键数量 */

/* 中断IO描述结构体
 */
struct irq_key {
    int gpio;                           /* gpio */
    unsigned int irq_num;               /* 中断号 */
    unsigned char val;                  /* 按键对应的键值 */
    char name[10];                      /* 名字 */
    irqreturn_t (*handler)(int, void *);/* 中断服务函数 */
};

/* asyncnoti设备结构体
 */
struct irq_dev {
    dev_t   devid;              /* 设备ID */
    struct cdev cdev;           /* cdev */
    struct class *class;        /* 类 */
    struct device *device;      /* 设备 */
    int major;                  /* 主设备号 */
    int minor;                  /* 次设备号 */
    struct device_node *nd;     /* 设备节点 */
    atomic_t key_val;           /* 有效按键值 */
    atomic_t release_key;       /* 标记是否一次完成的按键 */
    struct timer_list timer;    /* 定义一个定时器 */
    struct irq_key key[KEY_NUM];/* 按键描述数组 */
    unsigned char cur_key;      /* 当前按键 */

    wait_queue_head_t wait_h;   /* 读等待队列头 */
    struct fasync_struct *async_queue;  /* 异步相关结构体 */
};

struct irq_dev irqdev;          /* asyncnoti设备 */

/**
 * \brief   中断服务函数,开启定时器,延时10ms用于按键消抖
 * \param   irq 中断号
 *          dev_id  设备结构
 * \retval  中断执行结果
 */
static irqreturn_t key0_handler(int asyncnoti, void *dev_id)
{
    unsigned char num;
    struct irq_dev *dev = (struct irq_dev *)dev_id;

    dev->cur_key = 0;
    dev->timer.data = (volatile long)dev_id;
    
    num = dev->cur_key;
    if (gpio_get_value(dev->key[num].gpio) == 0) {
        atomic_set(&dev->key_val, KEY0_PRESS);          /* 按键按下 */
        printk("key %d falling edge!\r\n", num);
    } else {
        atomic_set(&dev->key_val, KEY0_RELEASE);        /* 按键松开 */
        printk("key %d rising edge!\r\n", num);
    }
    atomic_set(&dev->release_key, 0);                   /* 无按键 */

    /* 启动定时器 */
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));
    
    return IRQ_RETVAL(IRQ_HANDLED);
}

/**
 * \brief   定时器服务函数, 用于按键消抖, 定时器到了以后再次读取按键值, 如果
 *          按键还是处理于按下状态就表示按有效。
 * \param   arg 设备结构变量
 * \retval  无
 */
void timer_function(unsigned long arg)
{
    unsigned char val;
    unsigned char num;
    struct irq_key *pkey; 
    struct irq_dev *dev = (struct irq_dev *)arg;

    num = dev->cur_key;
    pkey = &dev->key[num];

    val = gpio_get_value(pkey->gpio);           /* 读取IO值 */
    if (val == 0) {
        if (atomic_read(&dev->key_val) == KEY0_PRESS) {
            atomic_set(&dev->key_val, KEY0_PRESS);   /* 按键按下 */
            atomic_set(&dev->release_key, 1);
            printk("key %d low level!\r\n", num);
        } else {
            atomic_set(&dev->key_val, 0);
        }

    } else {
        if (atomic_read(&dev->key_val) == KEY0_RELEASE) {
            atomic_set(&dev->key_val, KEY0_RELEASE);    /* 按键松开 */
            atomic_set(&dev->release_key, 1);
            printk("key %d high level!\r\n", num);
        } else {
            atomic_set(&dev->key_val, 0);
        }
    }

    if (atomic_read(&dev->release_key)) {               /* 一次完整的按键过程 */
        if (dev->async_queue) {
            kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
        }
    }
}

/**
 * \brief   按键IO初始化
 * \param   无
 * \retval  无
 */
static int key_io_init(void) 
{
    unsigned char i = 0;
    char name[10];
    int ret = 0;

    irqdev.nd = of_find_node_by_path("/key");
    if (irqdev.nd == NULL) {
        printk("Key node is not found!\r\n");
        return -EINVAL;
    }

    for (i = 0; i < KEY_NUM; i++) {
        /* 提取GPIO */
        irqdev.key[i].gpio = of_get_named_gpio(irqdev.nd, "key-gpio", i);

        if (irqdev.key[i].gpio < 0) {
            printk("Can't get key %d\r\n", i);
        }
    }

    for (i=0; i<KEY_NUM; i++) {
        /* 初始化key所使用的IO,并设置成中断模式 */
        memset(irqdev.key[i].name, 0, sizeof(name));
        sprintf(irqdev.key[i].name, "KEY%d", i);
        gpio_request(irqdev.key[i].gpio, name);
        gpio_direction_input(irqdev.key[i].gpio);
        irqdev.key[i].irq_num = irq_of_parse_and_map(irqdev.nd, i);
        printk("key%d:gpio=%d, irq_num=%d\r\n", i, irqdev.key[i].gpio, irqdev.key[i].irq_num);
    }

    /* 申请中断 */
    irqdev.key[0].handler = key0_handler;
    irqdev.key[0].val = KEY0_RELEASE;

    for (i=0; i<KEY_NUM; i++) {
        ret = request_irq(irqdev.key[i].irq_num, irqdev.key[i].handler, 
                          IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
                          irqdev.key[i].name, &irqdev);
        if (ret < 0) {
            printk("irq %d request failed!\r\n", irqdev.key[i].irq_num);
            return -EFAULT;
        }
    }

    /* 创建定时器 */
    init_timer(&irqdev.timer);
    irqdev.timer.function = timer_function;

    /* 初始化等待队列头 */
    init_waitqueue_head(&irqdev.wait_h);

    return 0;
}

/**
 * \brief   打开设备
 * \param   inode   传给驱动的inode
 *          filp    设备文件
 * \retval  0 成功  其它  失败
 */
static int irqdev_open(struct inode *inode, struct file *filp)
{
    /* 设置私有数据 */
    filp->private_data = &irqdev;
    return 0;
}

/**
 * \brief   从设备读取数据
 * \param   filp    要打开的设备文件(文件描述符)
 *          buf     返回给用户空间的数据缓冲区
 *          cnt     要读取的数据长度
 *          offt    相对于文件首地址的偏移
 * \retval  读取的字节数, 如果为负值, 表示读取失败
 */
static ssize_t irqdev_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int ret = 0;
    unsigned char key_val = 0;
    unsigned char release_key = 0;
    struct irq_dev *dev = (struct irq_dev *)filp->private_data;

    if (filp->f_flags & O_NONBLOCK) {               /* 非阻塞访问 */
        if (atomic_read(&dev->release_key) == 0) {  /* 按键无效 */
            return -EAGAIN;
        }
    } else {
        /* 加入等等队列,等待有按键时被唤醒 */
        ret = wait_event_interruptible(dev->wait_h, atomic_read(&dev->release_key));
        if (ret) {
            goto wait_error;
        }
    }

    key_val = atomic_read(&dev->key_val);
    release_key = atomic_read(&dev->release_key);

    if (release_key) {
        if (key_val == KEY0_PRESS || key_val == KEY0_RELEASE) {
            ret = copy_to_user(buf, &key_val, sizeof(key_val));
            printk("Key val translation complete!\r\n");
        } 
        atomic_set(&dev->release_key, 0);   /* 按下标志清零 */
        
    } else {
        goto data_error;
    }

    return 0;
wait_error:
    return ret;

data_error:
    return -EINVAL;
}

/**
 * \brief   poll函数,用于处理非阻塞访问
 * \param   filp    要打开的设备文件(文件描述符)
 *          wait    等待列表(poll_table)
 * \retval  设备或者资源状态
 */
unsigned int irqdev_poll(struct file *filp, struct poll_table_struct *wait)
{
    unsigned int mask = 0;
    struct irq_dev *dev = (struct irq_dev *)filp->private_data;

    poll_wait(filp, &dev->wait_h, wait);

    if (atomic_read(&dev->release_key)) {       /* 按键按下 */
        mask = POLLIN | POLLRDNORM;             /* 返回PLLIN */
    }

    return mask;
}

/**
 * \brief   fasync函数,用于处理异步通知
 * \param   fd  文件描述符
 *          filp 要打开的劥文件(文件描述符)
 *          on  模式
 * \retval  负数表示函数执行失败
 */
static int irqdev_fasync(int fd, struct file *filp, int on) 
{
    struct irq_dev *dev = (struct irq_dev *)filp->private_data;

    return fasync_helper(fd, filp, on, &dev->async_queue);
}

/**
 * \brief   release函数,应用程序调用close关闭驱动文件的时候会执行
 * \param   inode   inode节点
 *          filp    要打开的设备文件(文件描述符)
 * \retval  负数表示函数执行失败
 */
static int irqdev_release(struct inode *inode, struct file *filp) 
{
    return irqdev_fasync(-1, filp, 0);
}

/* 设备操作函数
 */
static  struct file_operations irqdev_fops = {
    .owner = THIS_MODULE,
    .open = irqdev_open,
    .read = irqdev_read,
    .poll = irqdev_poll,
    .fasync = irqdev_fasync,
    .release = irqdev_release,
};

/**
 * \brief   驱动入口函数
 * \param   无
 * \retval  无
 */
static int __init irqdev_init(void)
{
    /* 创建设备号 */
    if (irqdev.major) {
        /* 主备号已分配 */

        /* 根据设备号向内核获取设备ID */
        irqdev.devid = MKDEV(irqdev.major, 0);

        /* 向内核注册设备 */
        register_chrdev_region(irqdev.devid, IRQ_CNT, IRQ_NAME);
    } else {
        /* 向内核获取设备ID,主设备号,名称 */
        alloc_chrdev_region(&irqdev.devid, 0, IRQ_CNT, IRQ_NAME);
        irqdev.major = MAJOR(irqdev.devid);
        irqdev.minor = MINOR(irqdev.devid);
    }

    /* 注册字符设备 */
    /* 初始化字符设备 */
    cdev_init(&irqdev.cdev, &irqdev_fops);

    /* 创建字符设备 */
    cdev_add(&irqdev.cdev, irqdev.devid, IRQ_CNT);

    /* 创建类 */
    irqdev.class = class_create(THIS_MODULE, IRQ_NAME);
    if (IS_ERR(irqdev.class)) {
        return PTR_ERR(irqdev.class);
    }

    /* 创建设备 */
    irqdev.device = device_create(irqdev.class, NULL, irqdev.devid, NULL, IRQ_NAME);
    if (IS_ERR(irqdev.device)) {
        return PTR_ERR(irqdev.device);
    }

    /* 初始化按键 */
    atomic_set(&irqdev.key_val, KEY0_RELEASE);
    atomic_set(&irqdev.release_key, 0);

    key_io_init();

    return 0;
}

/**
 * \brief   驱动入口函数
 * \param   无
 * \retval  无
 */
static void __exit irqdev_exit(void)
{
    unsigned int i = 0;

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

    /* 释放中断 */
    for (i=0; i<KEY_NUM; i++) {
        free_irq(irqdev.key[i].irq_num, &irqdev);
    }

    cdev_del(&irqdev.cdev);
    unregister_chrdev_region(irqdev.devid, IRQ_CNT);
    device_destroy(irqdev.class, irqdev.devid);
    class_destroy(irqdev.class);
}

/* 设备注册入口, 展开后
 * static initcall_t \
 *        __initcall_irqdev_init6 \
 *        __used \
 *        __attribute__((__section__(".initcall6.init"))) \
 *        = irqdev_init;
 */
module_init(irqdev_init);

/* 设备注册出口, 展开后
 * static exitcall_t \
 *        __exitcall_irqdev_exit \
 *        __exit_call \
 *        = irqdev_exit;
 */
module_exit(irqdev_exit);

/* 模块的许可证声明, 展开后
 * static const char __UNIQUE_ID_license__COUNTER__[] \
 *   __used __attribute__((section(".modinfo"), unused, aligned(1))) \
 *  = "license=GPL";
 */
MODULE_LICENSE("GPL");

/* 模块的作者声明, 展开后
 * static const char __UNIQUE_ID_author__COUNTER__[]					  \
 * __used __attribute__((section(".modinfo"), unused, aligned(1)))	  \
 * = "author=glen_cao"
 */
MODULE_AUTHOR("glen");
  1. 编写驱动测试程序,编译拷贝到NFS网络文件系统的/lib/modules/4.1.15目录下
/*
 * 文件名   :  asyncnoti_test.c
 * 作者     :  glen
 * 描述     :  asyncnoti测试程序
 */

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "poll.h"
#include "sys/select.h"
#include "sys/time.h"
#include "linux/ioctl.h"
#include "signal.h"

static int fd = 0;  /* 文件描述符 */

/**
 * \brief   SIGIO信号处理函数
 * \param   signum  信号值
 * \retval  无
 */
static void sigio_signal_fun(int signum)
{
    int err = 0;

    unsigned int keyval = 0;

    err = read(fd, &keyval, sizeof(keyval));
    if (err < 0) {
        printf("read key filure!\r\n");
    } else {
        printf("sigio signal! key value=%#x\r\n", keyval);
    }
}

/**
 * @brief   : main函数
 * @par     : argc  argv数组元素的个数
 *            argv  参数数组
 * @retval  : 0 成功    其它 失败
 */
int main(int argc, char *argv[])
{
    int ret = 0;
    int flags = 0;
    char *filename;
    
    if (argc != 2) {
        printf("Error Usage!\r\n");
        return -1;
    }

    filename = argv[1];

    /* 打开驱动文件 */
    fd = open(filename, O_RDWR);       /* 阻塞访问 */
    if (fd < 0) {
        printf("Can't open file %s\r\n", filename);
        return -1;
    }

    /* 设置信号SIGIO的处理函数 */
    signal(SIGIO, sigio_signal_fun);

    fcntl(fd, F_SETOWN, getpid());      /* 将当前进程的进程号告诉内核 */
    flags = fcntl(fd, F_GETFD);         /* 获取当前的进程状态 */
    fcntl(fd, F_SETFL, flags | FASYNC); /* 设置进程启用异步通知功能 */

    while(1) {
        sleep(2);
    }
    
    /* 关闭文件 */
    ret = close(fd);
    if (ret < 0) {
        printf("file %s close failed!\r\n", argv[1]);
        return -1;
    }
    return 0;
}

拷贝到NFS网络文件系统的/lib/modules/4.1.15目录下

glen@ubuntu:~/linux/imx6ull/linux/driver/14_asyncnoti$ sudo cp asyncnoti.ko asyncnoti_test ~/linux/nfs/rootfs/lib/modules/4.1.15 -f
  1. 执行应用程序,并按下目标板的按键KEY0,打印信息如所示
/lib/modules/4.1.15 # insmod asyncnoti.ko
key0:gpio=18, irq_num=49
/lib/modules/4.1.15 # random: nonblocking pool is initialized
/lib/modules/4.1.15 # ./asyncnoti_test /dev/asyncnoti &
/lib/modules/4.1.15 # key 0 falling edge!
key 0 low level!
Key val translation complete!
sigio signal! key value=0x1
key 0 rising edge!
key 0 high level!
Key val translation complete!
sigio signal! key value=0x81
key 0 falling edge!
key 0 low level!
Key val translation complete!
sigio signal! key value=0x1
key 0 rising edge!
key 0 falling edge!
key 0 low level!
Key val translation complete!
sigio signal! key value=0x1
key 0 rising edge!
key 0 high level!
Key val translation complete!
sigio signal! key value=0x81
key 0 falling edge!
key 0 low level!
Key val translation complete!
sigio signal! key value=0x1
key 0 rising edge!
key 0 high level!
Key val translation complete!
sigio signal! key value=0x81
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值