定时器去抖:
#### 数据结构
struct gpio_key {
int gpio;
struct gpio_desc *gpiod;
int flag;
int irq;
struct timer_list key_timer;
};
每个按键独立一个定时器,设计合理。
##### 定时器初始化
/* 原代码 */
setup_timer(&gpio_keys_100ask[i].key_timer,
key_timer_expire,
&gpio_keys_100ask[i]);
gpio_keys_100ask[i].key_timer.expires = ~0;
add_timer(&gpio_keys_100ask[i].key_timer);
`setup_timer` 是内核 4.15 之前的旧接口,新内核已废弃。回调函数参数是 `unsigned long`,类型不安全。
**改进:**
/* 使用新接口 */
timer_setup(&gpio_keys_100ask[i].key_timer,
key_timer_expire, 0);
gpio_keys_100ask[i].key_timer.expires = ~0;
add_timer(&gpio_keys_100ask[i].key_timer);
/* 回调函数改为 */
static void key_timer_expire(struct timer_list *t)
{
struct gpio_key *gpio_key = from_timer(gpio_key, t, key_timer);
...
}
##### 中断处理
/* 原代码 */
static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{
struct gpio_key *gpio_key = dev_id;
printk("gpio_key_isr key %d irq happened\n", gpio_key->gpio);
mod_timer(&gpio_key->key_timer, jiffies + HZ/5);
return IRQ_HANDLED;
}
`HZ/5 = 200ms` 去抖时间太长,正常按键响应会有明显延迟。`printk` 在中断里频繁打印影响性能。
**改进:**
static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{
struct gpio_key *gpio_key = dev_id;
/* 去抖时间改为 20ms */
mod_timer(&gpio_key->key_timer, jiffies + msecs_to_jiffies(20));
return IRQ_HANDLED;
}
##### 定时器回调
/* 原代码 */
static void key_timer_expire(unsigned long data)
{
struct gpio_key *gpio_key = data;
int val;
int key;
val = gpiod_get_value(gpio_key->gpiod);
printk("key_timer_expire key %d %d\n", gpio_key->gpio, val);
key = (gpio_key->gpio << 8) | val;
put_key(key);
wake_up_interruptible(&gpio_key_wait);
kill_fasync(&button_fasync, SIGIO, POLL_IN);
}
使用旧接口 `unsigned long data` 接收参数,不安全。没有区分按下和松开,两个事件都会放入缓冲区,用户需要自己判断。
**改进:**
static void key_timer_expire(struct timer_list *t)
{
struct gpio_key *gpio_key = from_timer(gpio_key, t, key_timer);
int val;
int key;
val = gpiod_get_value(gpio_key->gpiod);
/* 低电平有效:val=0 按下,val=1 松开,更直观 */
key = (gpio_key->gpio << 8) | (val ? 0 : 1);
put_key(key);
wake_up_interruptible(&gpio_key_wait);
kill_fasync(&button_fasync, SIGIO, POLL_IN);
}
##### read 函数
/* 原代码 */
static ssize_t gpio_key_drv_read(struct file *file,
char __user *buf,
size_t size, loff_t *offset)
{
int err;
int key;
if (is_key_buf_empty() && (file->f_flags & O_NONBLOCK))
return -EAGAIN;
wait_event_interruptible(gpio_key_wait, !is_key_buf_empty());
key = get_key();
err = copy_to_user(buf, &key, 4);
return 4;
}
`wait_event_interruptible` 返回值没有判断,被信号打断时应返回 `-EINTR`。`copy_to_user` 返回值没有判断,拷贝失败时应报错。`size` 参数没有检查,用户传入小于4字节时会越界。
**改进:**
static ssize_t gpio_key_drv_read(struct file *file,
char __user *buf,
size_t size, loff_t *offset)
{
int err;
int key;
/* 检查size */
if (size < 4)
return -EINVAL;
if (is_key_buf_empty() && (file->f_flags & O_NONBLOCK))
return -EAGAIN;
/* 判断是否被信号打断 */
err = wait_event_interruptible(gpio_key_wait, !is_key_buf_empty());
if (err)
return -EINTR;
key = get_key();
/* 判断拷贝是否成功 */
if (copy_to_user(buf, &key, 4))
return -EFAULT;
return 4;
}
##### remove 函数
/* 原代码 */
static int gpio_key_remove(struct platform_device *pdev)
{
...
for (i = 0; i < count; i++) {
free_irq(gpio_keys_100ask[i].irq, &gpio_keys_100ask[i]);
del_timer(&gpio_keys_100ask[i].key_timer); // 不安全
}
kfree(gpio_keys_100ask);
return 0;
}
`del_timer` 不等待回调执行完就删除,可能导致回调还在执行时内存已被释放。
**改进:**
static int gpio_key_remove(struct platform_device *pdev)
{
...
for (i = 0; i < count; i++) {
free_irq(gpio_keys_100ask[i].irq, &gpio_keys_100ask[i]);
del_timer_sync(&gpio_keys_100ask[i].key_timer); // 等回调结束再删
}
kfree(gpio_keys_100ask);
return 0;
}