韦东山2440-学习笔记-字符设备驱动

button字符驱动

#include <linux/module.h>
#include <linux/spinlock.h>
#include <asm/atomic.h>
#include <linux/poll.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>

#define DEVICE_NAME "button"

static void button_timeout(unsigned long data);

dev_t dev;
struct cdev *cdev;
struct class *button_class;

static wait_queue_head_t button_press_wait;
static int button_press;
static unsigned int g_value;

static struct fasync_struct *button_async_queue;

static struct timer_list button_timer = TIMER_INITIALIZER(button_timeout, 0, 0); /* 用于消除按键抖动 */

// 定义原子变量,实现只能一个进程打开
static atomic_t can_open = ATOMIC_INIT(1);

struct button_desc {
        unsigned int pin;
        unsigned int val;
        unsigned int irq;
};
static struct button_desc button_desc_arr[3] = {
        { .pin = S3C2410_GPF0, .val = 0x01, .irq = IRQ_EINT0 },
        { .pin = S3C2410_GPF2, .val = 0x02, .irq = IRQ_EINT2 },
        { .pin = S3C2410_GPG3, .val = 0x03, .irq = IRQ_EINT11 },
};

static void
button_timeout(unsigned long data)
{
        int up;
        struct button_desc *bd = (struct button_desc *)data;

        // 从系统删除timer
        del_timer(&button_timer);
        button_timer.data = 0; // 重置

        g_value = bd->val;

        up = s3c2410_gpio_getpin(bd->pin);
        if (up)
                g_value |= 0x80;

        /* 唤醒button_press_wait 上的进程 */
        button_press = 1;
        wake_up_interruptible(&button_press_wait);

        /* 异步通知进程 */
        kill_fasync(&button_async_queue, SIGIO, POLL_IN);
}

static irqreturn_t
buttons_irq_handler(int irq, void *dev_id)
{
        if (button_timer.data != 0) // 避免多次中断导致重复添加定时器
                return IRQ_HANDLED;

        // 设置定时器,延迟读取按键
        button_timer.expires = jiffies + msecs_to_jiffies(100); // 100ms 后读取按键
        button_timer.data = (unsigned long)dev_id;
        mod_timer(&button_timer, button_timer.expires); // 将timer加入系统时间堆

        return IRQ_HANDLED;
}

static int
button_fasync(int fd, struct file *file, int on)
{
        return fasync_helper(fd, file, on, &button_async_queue);
}

static int
button_open (struct inode *inode, struct file *fp)
{
        unsigned int i;

        if (!atomic_dec_and_test(&can_open)) {
                atomic_inc(&can_open);
                return -EBUSY;
        }

        /* 设置gpio为中断模式 */
        s3c2410_gpio_cfgpin(S3C2410_GPF2, S3C2410_GPF2_EINT2);
        s3c2410_gpio_cfgpin(S3C2410_GPF0, S3C2410_GPF0_EINT0);
        s3c2410_gpio_cfgpin(S3C2410_GPG3, S3C2410_GPG3_EINT11);

        /* 注册中断, IRQT_BOTHEDGE : 双边缘触发 */
        for (i = 0; i < sizeof(button_desc_arr)/sizeof(*button_desc_arr); i++) {
                request_irq(button_desc_arr[i].irq, buttons_irq_handler,
                                IRQT_BOTHEDGE, DEVICE_NAME, button_desc_arr + i);
        }

        return 0;
}

static ssize_t
button_read (struct file *fp, char __user *user, size_t sz, loff_t *poffst)
{
        if (fp->f_flags & O_NONBLOCK) { /* 如果是非阻塞操作 */
                if (!button_press)
                        return -EAGAIN;
        }

        /* 若没有数据,button_press 为0,则挂起本进程到等待队列 button_press_wait */
        wait_event_interruptible(button_press_wait, button_press);
        /* 重置 button_press */
        button_press = 0;

        if (copy_to_user(user, &g_value, sz)) {
                printk(DEVICE_NAME " failed to copy_to_user");
                return -1;
        }

        return 0;
}


static int
button_close (struct inode *inode, struct file *fp)
{
        unsigned int i;

        atomic_inc(&can_open);

        for (i = 0; i < sizeof(button_desc_arr)/ sizeof(*button_desc_arr); i++) {
                free_irq(button_desc_arr[i].irq, button_desc_arr + i);
        }

        return 0;
}

static unsigned int
button_poll (struct file *file, struct poll_table_struct *wait)
{
        // 将本进程加入 button_press_wait 等待队列,但当前不会阻塞
        poll_wait(file, &button_press_wait, wait);

        if (button_press) // 这里不重置 button_press 因为 数据还没有读,在read后重置 button_press
                return POLLIN;

        return 0;
}

static const struct file_operations button_ops = {
        .owner = THIS_MODULE,
        .open = button_open,
        .read = button_read,
        .poll = button_poll,
        .release = button_close,
        .fasync = button_fasync,
};

static int __init
button_init(void)
{
        unsigned int major;

        /* 1. 字符设备驱动框架部分 */
        /* 1.1 申请设备号 */
        if (alloc_chrdev_region(&dev, 0, 1, DEVICE_NAME) < 0) {
                printk(DEVICE_NAME "failed to alloc dev\n");
                return -1;
        }

        /* 1.2 构建cdev,并绑定 file_operations */
        cdev = cdev_alloc();
        cdev->ops = &button_ops;
        cdev->owner = THIS_MODULE;

        /* 1.3 将cdev加到系统的哈希表中,如此才能通过设备号找到cdev */
        if (cdev_add(cdev, dev, 1) < 0) {
                printk(DEVICE_NAME "failed to cdev_add\n");
                return -1;
        }

        /* 2. 为了mdev自动创建设备节点 */
        /* 2.1 创建一个/sys/class/button 类 */
        button_class = class_create(THIS_MODULE, DEVICE_NAME);

        /* 2.2 创建/sys/class/button/buttons */
        major = MAJOR(dev);
        device_create(button_class, NULL, MKDEV(major, 0), "buttons");

        /* 3. 初始化等待队列,以实现阻塞操作 */
        init_waitqueue_head(&button_press_wait);

        init_timer(&button_timer);

        return 0;
}

static void __exit
button_exit(void)
{
        int major;

        major = MAJOR(dev);
        device_destroy(button_class, MKDEV(major, 0));
        class_destroy(button_class);

        cdev_del(cdev);
        unregister_chrdev_region(dev, 4);
}

module_init(button_init);
module_exit(button_exit);

MODULE_LICENSE("GPL");

poll

poll 机制分析

应用程序调用poll时,会遍历所有 fd对应 file->f_op->poll(),
驱动模块的poll将当前进程加入等待队列,并检查是否有事件,返回事件个数,
当没有事件时,陷入睡眠

sys_poll
        do_sys_poll(ufds, nfds, &timeout_jiffies) // timeout_jiffies 是输入输出参数,输入超时值,输出剩余的时间
                poll_initwait(&table);
                        init_poll_funcptr(&pwq->pt, __pollwait); // -> table->pt->qproc = __pollwait;

                i = nfds; while(i != 0) {...} // 从用户空间中将poll event参数拷贝到内核空间
                fdcount = do_poll(nfds, head, &table, timeout);
                        for (;;) {
                                if (do_pollfd(pfd, pt)) { // -> mask = file->f_op->poll(file, pwait);
                                        count++;          // 调用驱动的 poll函数,如果mask>0则 count++
                                        pt = NULL;
                                }


                                // 如果 count > 0 ,有事件,返回
                                // 如果 *timeout ==  0 ,超时,返回
                                // 有信号待处理,返回
                                if (count || !*timeout || signal_pending(current))
                                        break;

                                // 睡眠__timeout 时间, 可以被打断
                                __timeout = schedule_timeout(__timeout);

                        }
                        return count; // 返回事件数量

                while() {...} // 将发生的事件拷贝到用户空间

驱动的poll函数

static unsigned int evdev_poll(struct file *file, poll_table *wait)
{
        struct evdev_client *client = file->private_data;
        struct evdev *evdev = client->evdev;

        // 调用 poll_wait , 最终将当前进程加入 驱动构建的等待队列, 但当前不会阻塞,在do_poll中若没有事件,则调用 schedule_timeout 阻塞
        poll_wait(file, &evdev->wait, wait); // >  p->qproc(filp, wait_address, p);
                                             // > __pollwait(filp, wait_address, p);
                                                      init_waitqueue_entry(&entry->wait, current);
                                                      add_wait_queue(wait_address, &entry->wait);

        return ((client->head == client->tail) ? 0 : (POLLIN | POLLRDNORM)) |
                (evdev->exist ? 0 : (POLLHUP | POLLERR));  // 返回 POLLIN 等事件
}

等待队列的函数
wait_queue_head_t wait;
init_waitqueue_head(&wait);
wake_up_interruptible(&wait);

驱动如何使用poll机制 ?

1) file_operations 中定义 poll ,准备等待队列 waitq
2) poll(struct file *file, poll_table *wait_table) {
        poll_wait(file, &waitq, wait_table);  // 将本进程加入等待队列
        // 3) 判断释放有事件
        return event; // 无事件为0,有事件返回 POLLIN POLLOUT 等
}

poll 和 select 在驱动层面都是调用 f_op->poll ,所以驱动实现了poll,应用层可以调用poll和 select

给button驱动加上 poll

static unsigned int
button_poll (struct file *file, struct poll_table_struct *wait)
{
        // 将本进程加入 button_press_wait 等待队列,但当前不会阻塞
        poll_wait(file, &button_press_wait, wait);

        if (button_press) // 这里不重置 button_press 因为 数据还没有读,在read后重置 button_press
                return POLLIN;

        return 0;
}

异步通知

内核主动通知应用程序有事件,需要应用程序把自己的进程号注册到驱动模块的异步通知队列,
当事件发生时,驱动模块 kill_fasync 给所有已注册进程发信号 SIGIO

  • 应用程序调用 f_ops->fasync 将自己的pid注册进驱动的异步通知队列
      int olfags;
      fcntl(fd, F_SETOWN, getpid());
      olfags = fcntl(fd, F_GETFL);
      fcntl(fd, F_SETFL, olfags | FASYNC);

      signal(SIGIO, signal_io_handler);
  • 当事件发生时,驱动调用 kill_fasync给已注册的进程发送信号SIGIO
  static irqreturn_t
  buttons_irq_handler(int irq, void *dev_id)
  {
      struct button_desc *bd = dev_id;
      unsigned int up;

      g_value = bd->val;
      up = s3c2410_gpio_getpin(bd->pin);
      if (up)
          g_value |= 0x80;

      /* 唤醒button_press_wait 上的进程 */
      button_press = 1;
      wake_up_interruptible(&button_press_wait);

      kill_fasync(&button_async_queue, SIGIO, POLL_IN);

      return IRQ_HANDLED;
  }

  static int
  button_fasync(int fd, struct file *file, int on)
  {
      return fasync_helper(fd, file, on, &button_async_queue);
  }

  static const struct file_operations button_ops = {
      .owner = THIS_MODULE,
      .open = button_open,
      .read = button_read,
      .poll = button_poll,
      .release = button_close,
      .fasync = button_fasync,
  };

原子操作实现互斥

atomic_t v = ATOMIC_INIT(0)
atomic_dec_and_test(atomic_t *)
atomic_inc(atomic_t *);

使用原子操作实现互斥,
定义一个原子变量作为资源计数,
获得资源时需要 atomic_dec_and_test,如果没有资源则atomic_inc 并退出。
进程退出时要释放资源 atomic_inc
比如实现 button驱动只能一个进程打开

  // 定义原子变量,实现只能一个进程打开
  static atomic_t can_open = ATOMIC_INIT(1);

  static int
  button_open (struct inode *inode, struct file *fp)
  {
      unsigned int i;

      //   #define atomic_dec_and_test(v) (atomic_sub_return(1, (v)) == 0)
      // !(--can_open == 0)      
      if (!atomic_dec_and_test(&can_open)) {
          atomic_inc(&can_open);  // ++can_open
          return -EBUSY;
      }
      ...
  }

  static int
  button_close (struct inode *inode, struct file *fp)
  {
      atomic_inc(&can_open);
  }

信号量

信号量也能实现互斥,并且当获得资源失败时,进程可以睡在该信号量上,等待被唤醒。

定义信号量
struct semaphore sem;
初始化信号量
void sema_init(struct semaphore *sem, int val);
void init_MUTEX(struct semaphore *sem); // 初始值为0
static DECLARE_MUTEX(button_lock); // 定义互斥锁

获得信号量
void down(struct semaphore *sem); // 获得失败,则陷入 不可打断的休眠,直到其他进程 up信号量,或被 SIGKILL杀死
int down_interruptible(struct semaphore *sem); // 获得失败,则陷入可打断休眠,直到其他进程up信号量,获得被信号打断返回1,成功获得信号量返回0

      if (down_interruptible(&soft->algolock))
          return -ERESTARTSYS;

int down_trylock(struct semaphore *sem); // 获得失败,则返回 1,获得成功返回 0

      /* try to get control of the write buffer */
      if (down_trylock(&sd->sd_wbs)) {
          /* somebody else has it now;
           * if we're non-blocking, then exit...
           */
          if (file->f_flags & O_NONBLOCK) {
              return -EAGAIN;
          }
          /* ...or if we want to block, then do so here */
          if (down_interruptible(&sd->sd_wbs)) {
              /* something went wrong with wait */
              return -ERESTARTSYS;
          }
      }

释放信号量
void up(struct semaphore *sem);

阻塞和非阻塞

应用程序在读操作时,f_ops->read 检查是否有数据,无则 wait_event_interruptible 将本进程加入 事件队列,
驱动模块需注册中断,当中断发生时,wake_up_interruptible 唤醒队列上所有进程

判断非阻塞
驱动程序可以获得 file->f_flags & O_NONBLOCK 以判断是否非阻塞处理
如果是非阻塞,并没有资源,则返回 -EAGAIN

按键消抖和定时器

由于机械按键的回弹导致刚按下时电压来回跳动,导致触发多次中断,所以应收到第一次中断设置一个定时器延迟处理中断,期间的其他中断忽略
定时器操作

// 定义定时器
  static struct timer_list button_timer = TIMER_INITIALIZER(button_timeout, 0, 0); 
// 初始化定时器,但不会加入系统
      init_timer(&button_timer);

// 设置定时器并加入系统
      button_timer.expires = jiffies + msecs_to_jiffies(100); // 100ms 后读取按键
      button_timer.data = (unsigned long)dev_id;
      mod_timer(&button_timer, button_timer.expires); 

// 从系统中删除定时器
      del_timer(&button_timer);

posted on 2023-02-21 22:21  开心种树  阅读(44)  评论(0编辑  收藏  举报