본문 바로가기
device driver

Waiting queues

by jsh91 2023. 6. 1.
#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_interruptible - sleep until a condition gets true
 * @wq_head: the waitqueue to wait on
 * @condition: a C expression for the event to wait for
 *
 * The process is put to sleep (TASK_INTERRUPTIBLE) until the
 * @condition evaluates to true or a signal is received.
 * The @condition is checked each time the waitqueue @wq_head is woken up.
 *
 * wake_up() has to be called after changing any variable that could
 * change the result of the wait condition.
 *
 * The function will return -ERESTARTSYS if it was interrupted by a
 * signal and 0 if @condition evaluated to true.
 */

wait_event를 사용하여 대기 상대가 되면 wake_up 함수를 사용하여 대기 상태를 해제가 가낭한지 condition을 보고 true이면 해제한다. 이 시나리오가 waitqueue의 핵심 동작이다

 

 

---

참고

1. https://linux-kernel-labs.github.io/refs/heads/master/labs/device_drivers.html?highlight=copy_from_user#waiting-queues 

 

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

2. https://docs.kernel.org/kernel-hacking/hacking.html?highlight=init_waitqueue_head#wait-queues-include-linux-wait-h 

 

댓글