#include <linux/module.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/uaccess.h>
#include <linux/interrupt.h>
#include <linux/miscdevice.h>
#include <linux/wait.h> /* include wait queue */
#define MAX_KEY_STATES 256
static char *HELLO_KEYS_NAME = "PB_USER";
static char hello_keys_buf[MAX_KEY_STATES];
static int buf_rd, buf_wr;
struct key_priv {
struct device *dev;
struct gpio_desc *gpio;
struct miscdevice int_miscdevice;
wait_queue_head_t wq_data_available;
int irq;
};
static irqreturn_t hello_keys_isr(int irq, void *data)
{
int val;
struct key_priv *priv = data;
dev_info(priv->dev, "interrupt received. key: %s\n", HELLO_KEYS_NAME);
val = gpiod_get_value(priv->gpio);
/*
val = 1 -> 버튼이 눌림
val = 0 -> 버튼이 안눌림
*/
dev_info(priv->dev, "Button state: 0x%08X\n", val);
if (val == 1)
hello_keys_buf[buf_wr++] = 'P';
else
hello_keys_buf[buf_wr++] = 'R';
if (buf_wr >= MAX_KEY_STATES)
buf_wr = 0;
/* Wake up the process */
dev_info(priv->dev, " wake_up event start \n");
wake_up_interruptible(&priv->wq_data_available);
dev_info(priv->dev, " wake_up event end \n");
return IRQ_HANDLED;
}
static int my_dev_read(struct file *file, char __user *buff,
size_t count, loff_t *off)
{
int ret_val;
char ch[2];
struct key_priv *priv;
priv = container_of(file->private_data,
struct key_priv, int_miscdevice);
dev_info(priv->dev, "mydev_read_file entered\n");
/*
* Sleep the process
* The condition is checked each time the waitqueue is woken up
*
* buf_wr : 인터럽트가 발생하면 buf_wr 를 하나 증가
* buf_rd : /dev/mydev 를 읽으면 buf_rd 를 하나 증가
* condition이 true가 되면 대기를 멈춘다, 정상적으로 종료되면 0을 return
*/
dev_info(priv->dev, "wait_event start \n");
ret_val = wait_event_interruptible(priv->wq_data_available, buf_wr != buf_rd);
dev_info(priv->dev, "wait_event end \n");
if(ret_val)
return ret_val;
/* Send values to user application*/
ch[0] = hello_keys_buf[buf_rd];
ch[1] = '\n';
if(copy_to_user(buff, &ch, 2)){
return -EFAULT;
}
buf_rd++;
if(buf_rd >= MAX_KEY_STATES)
buf_rd = 0;
*off+=1;
/* cat /dev/mydev 하였을 경우 계속 읽기 위해 2를 return
만약 0을 return 하면은 cat은 종료된다.
*/
return 2;
}
static const struct file_operations my_dev_fops = {
.owner = THIS_MODULE,
.read = my_dev_read,
};
static int my_probe(struct platform_device *pdev)
{
int ret_val;
struct key_priv *priv;
struct device *dev = &pdev->dev;
dev_info(dev, "my_probe() function is called.\n");
/* Allocate new structure representing device */
priv = devm_kzalloc(dev, sizeof(struct key_priv), GFP_KERNEL);
priv->dev = dev;
platform_set_drvdata(pdev, priv);
/* Init the wait queue head */
init_waitqueue_head(&priv->wq_data_available);
///*
priv->gpio = devm_gpiod_get(dev, NULL, GPIOD_IN);
if (IS_ERR(priv->gpio)) {
dev_err(dev, "gpio get failed\n");
return PTR_ERR(priv->gpio);
}
priv->irq = gpiod_to_irq(priv->gpio);
if (priv->irq < 0)
return priv->irq;
dev_info(dev, "The IRQ number is: %d\n", priv->irq);
//*/
priv->irq = platform_get_irq(pdev, 0);
if (priv->irq < 0){
dev_err(dev, "irq is not available\n");
return priv->irq;
}
dev_info(dev, "IRQ_using_platform_get_irq: %d\n", priv->irq);
ret_val = devm_request_irq(dev, priv->irq, hello_keys_isr,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
HELLO_KEYS_NAME, priv);
if (ret_val) {
dev_err(dev, "Failed to request interrupt %d, error %d\n", priv->irq, ret_val);
return ret_val;
}
priv->int_miscdevice.name = "mydev";
priv->int_miscdevice.minor = MISC_DYNAMIC_MINOR;
priv->int_miscdevice.fops = &my_dev_fops;
ret_val = misc_register(&priv->int_miscdevice);
if (ret_val != 0)
{
dev_err(dev, "could not register the misc device mydev\n");
return ret_val;
}
dev_info(dev, "my_probe() function is exited.\n");
return 0;
}
static int my_remove(struct platform_device *pdev)
{
struct key_priv *priv = platform_get_drvdata(pdev);
dev_info(&pdev->dev, "my_remove() function is called.\n");
misc_deregister(&priv->int_miscdevice);
dev_info(&pdev->dev, "my_remove() function is exited.\n");
return 0;
}
static const struct of_device_id my_of_ids[] = {
{ .compatible = "arrow,wait"},
{},
};
MODULE_DEVICE_TABLE(of, my_of_ids);
static struct platform_driver my_platform_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "intkeywait",
.of_match_table = my_of_ids,
.owner = THIS_MODULE,
}
};
module_platform_driver(my_platform_driver);
MODULE_LICENSE("GPL");
key_pin: key_pin {
brcm,pins = <23>;
brcm,function = <0>; /* Input */
brcm,pull = <1>; /* Pull down */
};
int_key_wait {
compatible = "arrow,wait";
pinctrl-names = "default";
pinctrl-0 = <&key_pin>;
gpios = <&gpio 23 0>;
interrupts = <23 IRQ_TYPE_EDGE_BOTH>;
interrupt-parent = <&gpio>;
};
button을 push 할 경우
[ 513.510716] intkeywait soc:int_key_wait: interrupt received. key: PB_USER
[ 513.510738] intkeywait soc:int_key_wait: Button state: 0x00000001
[ 513.510747] intkeywait soc:int_key_wait: wake_up event start
[ 513.510763] intkeywait soc:int_key_wait: wake_up event end
[ 513.510828] intkeywait soc:int_key_wait: wait_event end
[ 513.510921] intkeywait soc:int_key_wait: mydev_read_file entered
[ 513.510940] intkeywait soc:int_key_wait: wait_event start
button을 release 할 경우
[ 535.167078] intkeywait soc:int_key_wait: interrupt received. key: PB_USER
[ 535.167102] intkeywait soc:int_key_wait: Button state: 0x00000000
[ 535.167111] intkeywait soc:int_key_wait: wake_up event start
[ 535.167126] intkeywait soc:int_key_wait: wake_up event end
[ 535.167187] intkeywait soc:int_key_wait: wait_event end
[ 535.167283] intkeywait soc:int_key_wait: mydev_read_file entered
[ 535.167297] intkeywait soc:int_key_wait: wait_event start
wait queue api 목록
#include <linux/wait.h>
DECLARE_WAIT_QUEUE_HEAD(wq_name);
void init_waitqueue_head(wait_queue_head_t *q);
int wait_event(wait_queue_head_t q, int condition);
int wait_event_interruptible(wait_queue_head_t q, int condition);
int wait_event_timeout(wait_queue_head_t q, int condition, int timeout);
int wait_event_interruptible_timeout(wait_queue_head_t q, int condition, int timeout);
void wake_up(wait_queue_head_t *q);
void wake_up_interruptible(wait_queue_head_t *q);
init_waitqueue_head()는 대기열을 초기화합니다. 컴파일 시간에 대기열을 초기화하려면 DECLARE_WAIT_QUEUE_HEAD 매크로를 사용할 수 있습니다
.
wait_event() 및 wait_event_interruptible()은 조건이 거짓인 동안 현재 스레드를 대기열에 추가하고 이를 TASK_UNINTERRUPTIBLE 또는 TASK_INTERRUPTIBLE로 설정하고 스케줄러를 호출하여 새 스레드를 예약합니다. 다른 스레드가 wake_up 함수를 호출하면 대기가 중단됩니다.
wait_event_timeout() 및 wait_event_interruptible_timeout()은 위의 함수와 동일한 효과를 가지며 매개변수로 받은 타임아웃이 끝날 때에만 대기를 중단할 수 있습니다.
wake_up()은 TASK_RUNNING 상태의 TASK_INTERRUPTIBLE 및 TASK_UNINTERRUPTIBLE 상태에서 모든 스레드를 해제합니다. 대기열에서 이러한 스레드를 제거합니다.
wake_up_interruptible() 같은 동작이지만 TASK_INTERRUPTIBLE 상태의 스레드만 깨어납니다.
wait_event_interruptible 함수 정의
wait_event를 사용하여 대기 상대가 되면 wake_up 함수를 사용하여 대기 상태를 해제가 가낭한지 condition을 보고 true이면 해제한다. 이 시나리오가 waitqueue의 핵심 동작이다
---
참고
Character device drivers — The Linux Kernel documentation
Overview In UNIX, hardware devices are accessed by the user through special device files. These files are grouped into the /dev directory, and system calls open, read, write, close, lseek, mmap etc. are redirected by the operating system to the device driv
linux-kernel-labs.github.io
'device driver' 카테고리의 다른 글
gpio contorl 전반적인 내용 (0) | 2023.06.15 |
---|---|
kernel linked list api 사용법 (0) | 2023.06.01 |
charter device driver - 8. interrupt platform driver 만들기 (0) | 2023.05.30 |
charter device driver - 7. i2c platform driver 만들기 (0) | 2023.05.28 |
charter device driver - 6. led_class_platform 만들기 (0) | 2023.05.26 |
댓글