[i.MX6ULL] driver development 10 - blocking & non blocking key detection

Posted by warpdesign on Thu, 09 Dec 2021 17:23:02 +0100

The previous article: introduced five I/O models in linux. In this article, we use blocking I/O and non blocking I/O to read keys, and compare the key programs detected by input capture and interrupt method to see whether the CPU utilization is reduced.

1 key detection of blocking I/O mode

1.1 waiting queue for blocking I/O

The biggest advantage of blocking access is that when the device file is inoperable, the process can enter the sleep state, which can free up CPU resources. However, when the device file can be operated, the process must be awakened. Generally, the wake-up work is completed in the interrupt function. The Linux kernel provides a wait queue to wake up the blocking process.

The wait queue header uses the structure wait_queue_head_t means:

struct __wait_queue_head { 
	spinlock_t       lock; 
	struct list_head task_list; 
}; 

typedef struct __wait_queue_head wait_queue_head_t; 

Using init_ waitqueue_ The head function initializes the waiting queue header:

/**
 * q: Wait queue header to initialize
 * return: nothing
 */
void init_waitqueue_head(wait_queue_head_t *q) 

When the device is unavailable, add the corresponding wait_queue_t to the wait queue:

struct __wait_queue { 
	unsigned int      flags; 
    void              *private; 
    wait_queue_func_t func; 
    struct list_head  task_list;
}; 

typedef struct __wait_queue wait_queue_t;

Using macro DECLARE_WAITQUEUE defines and initializes a waiting queue entry:

DECLARE_WAITQUEUE(name, tsk)

When the device is inaccessible, you need to add the wait queue item corresponding to the process to the previously created wait queue header:

/**
 * q: Waiting queue header to join
 * wait: Waiting queue item to join
 * return: nothing
 */
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait) 

When the device is accessible, delete the corresponding waiting queue item from the waiting queue header:

/**
 * q: Wait queue header to delete
 * wait: Waiting queue entry to delete
 * return: nothing
 */
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait) 

Wake up the process entering sleep state when the device is available:

void wake_up(wait_queue_head_t *q) 
void wake_up_interruptible(wait_queue_head_t *q) 

1.2 blocking I/O programming

Here is only the main difference from the previous key program.

1.2. 1 driver

The blocking read logic is as follows. First, define a waiting queue. When the key is not pressed, the waiting queue will be blocked (add the waiting queue to the waiting queue head), and then switch tasks once a row to hand over the CPU usage right. When waiting for a key to be pressed, a signal will wake up the waiting and return the key value to the application layer program.

static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int ret = 0;
    unsigned char keyvalue = 0;
    unsigned char releasekey = 0;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
    
    /* Define a waiting queue<-------------------------- */
    DECLARE_WAITQUEUE(wait, current);
    
    /* No key pressed<------------------------------ */
    if(atomic_read(&dev->releasekey) == 0)
    {
        /* Add wait queue to wait queue header<------------ */
        add_wait_queue(&dev->r_wait, &wait);
        
        /* Set task status<-------------------------- */
        __set_current_state(TASK_INTERRUPTIBLE);
        
        /* Make a task switch<---------------------- */
        schedule();
        
        /* Judge whether it is wake-up caused by signal<-------------- */
        if(signal_pending(current))
        {
            ret = -ERESTARTSYS;
            goto wait_error;
        }
        
        /* Set the current task to run<-------------- */
        __set_current_state(TASK_RUNNING);
        
        /* Delete the corresponding queue item from the waiting queue header<-------- */
        remove_wait_queue(&dev->r_wait, &wait);
    }

    keyvalue = atomic_read(&dev->keyvalue);
    releasekey = atomic_read(&dev->releasekey);

    /* A key is pressed */
    if (releasekey)
    {
        //printk("releasekey!\r\n");
        if (keyvalue & 0x80)
        {
            keyvalue &= ~0x80;
            ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
        }
        else
        {
            goto data_error;
        }
        atomic_set(&dev->releasekey, 0); /* Press the flag to clear */
    }
    else
    {
        goto data_error;
    }
    return 0;

wait_error:
    set_current_state(TASK_RUNNING);           /* Set the task to running status */
    remove_wait_queue(&dev->r_wait, &wait);    /* Remove waiting queue */
    return ret;
    
data_error:
    return -EINVAL;
}

In the key timer de dithering logic, wake-up is triggered after reading the key. Here, take one of the keys as an example, and its logic is as follows:

void timer1_function(unsigned long arg)
{
    unsigned char value;
    struct irq_keydesc *keydesc;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;

    keydesc = &dev->irqkeydesc[0];

    value = gpio_get_value(keydesc->gpio); /* Read IO value */
    if(value == 1) /* Press the key */
    {
        printk("get key1: high\r\n");
        atomic_set(&dev->keyvalue, keydesc->value);
    }
    else /* Key release */
    {
        printk("key1 release\r\n");
        atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
        atomic_set(&dev->releasekey, 1); /* Mark and release the key to complete a complete key pressing process */            
    }
    
    /* Wake up process */
    if(atomic_read(&dev->releasekey))
    {
        wake_up_interruptible(&dev->r_wait);
    }
}

1.2. 2 applications

The application does not need to be modified. It also uses the previous polling reading method. In order to see the difference between blocking and non blocking methods during the test, print before and after the read function. If the program runs normally, the print of the previous sentence of read will be printed first. The read function will not be blocked until a key is pressed, and the print of the next sentence of read will not be printed.

/* Cycle to read key value data! */
while(1)
{
    printf("[APP] read begin...\r\n");
    read(fd, &keyvalue, sizeof(keyvalue));
    printf("[APP] read end\r\n");
    if (keyvalue == KEY1VALUE)
    {
        printf("[APP] KEY1 Press, value = %#X\r\n", keyvalue);
    }
    else if (keyvalue == KEY2VALUE)
    {
        printf("[APP] KEY2 Press, value = %#X\r\n", keyvalue);
    }
}

1.2 experiment

As before, use Makefile to compile drivers and applications and copy them to the nfs root file system.

Start the test, as shown in the figure below. When no key is pressed, the application is blocked:

The keystroke program runs in the background. At this time, use the top command to check the CPU utilization. It can be found that the blocking keystroke drive mode has almost zero CPU utilization. Although the circular reading mode is still implemented in the keystroke application, the keystroke application is blocked and the CPU usage right is ceded because the keystroke value cannot be read at ordinary times, Naturally, CPU utilization is reduced.

2 key detection in non blocking I/O mode

The key application reads in a non blocking manner, and the key driver also returns immediately in a non blocking manner. Applications can use select, poll, or epoll functions to
Query whether the device can operate. The driver uses the poll function.

2.1 select/poll of non blocking I/O

  • select function prototype:
/**
 * nfs: In the set of three types of file descriptions to be monitored, the maximum file descriptor is increased by 1
 * readfds: Used to monitor read changes of the specified descriptor set
 * writefds: Used to monitor whether a file can be written
 * exceptfds: Exceptions for monitoring files
 * timeout: Timeout
 * return: 0 Timeout occurred, error occurred in - 1, and the number of file descriptors with other values that can be operated 
 */
int select(int    nfds,  
           fd_set *readfds,  
           fd_set *writefds, 
           fd_set *exceptfds,  
           struct timeval *timeout) 

The timeout time is represented by the structure timeval:

struct timeval { 
   long tv_sec;  /* second   */ 
   long tv_usec; /* subtle */  
}; 

When timeout is NULL, it means infinite waiting.

  • poll function prototype:
/**
 * fds: The collection of file descriptors to be monitored and the events to be monitored are an array
 * nfds: Number of file descriptors monitored
 * timeout: Timeout in ms
 * return: 0 Timeout occurred, error occurred in - 1, and the number of file descriptors with other values that can be operated 
 */
int poll(struct pollfd *fds,  
         nfds_t nfds,  
         nt     timeout) 

2.2 non blocking I/O programming

2.2. 1 driver

poll function processing part:

unsigned int imx6uirq_poll(struct file *filp, struct poll_table_struct *wait)
{
    unsigned int mask = 0;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;

    /* Add wait queue header to poll_ In table */
    poll_wait(filp, &dev->r_wait, wait);
    
    /* Press the key */
    if(atomic_read(&dev->releasekey))
    {
        mask = POLLIN | POLLRDNORM;            /* Return to PLLIN */
    }
    return mask;
}

/* Device operation function */
static struct file_operations imx6uirq_fops = {
    .owner = THIS_MODULE,
    .open = imx6uirq_open,
    .read = imx6uirq_read,
    .poll = imx6uirq_poll,
};

read function processing part:

static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int ret = 0;
    unsigned char keyvalue = 0;
    unsigned char releasekey = 0;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;

    /* Non blocking access */
    if (filp->f_flags & O_NONBLOCK)
    {
        /* If no key is pressed, return to - EAGAIN */
        if(atomic_read(&dev->releasekey) == 0)
        {
            return -EAGAIN;
        }
    }
    /* Blocking access */
    else
    {
        /* Join the waiting queue and wait to be awakened, that is, a key is pressed */
        ret = wait_event_interruptible(dev->r_wait, atomic_read(&dev->releasekey)); 
        if (ret)
        {
            goto wait_error;
        }
    }

    keyvalue = atomic_read(&dev->keyvalue);
    releasekey = atomic_read(&dev->releasekey);

    /* A key is pressed */
    if (releasekey)
    {
        //printk("releasekey!\r\n");
        if (keyvalue & 0x80)
        {
            keyvalue &= ~0x80;
            ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
        }
        else
        {
            goto data_error;
        }
        atomic_set(&dev->releasekey, 0); /* Press the flag to clear */
    }
    else
    {
        goto data_error;
    }
    return 0;

wait_error:
    return ret;
data_error:
    return -EINVAL;
}

2.2. 2 applications

2.2.2.1 poll mode reading

Note that the parameter of the open function is O_NONBLOCK, i.e. non blocking access. In order to see the difference between blocking reading and non blocking reading during the test, print is added before and after the poll function. If the program runs normally, the poll function will not be blocked. After the 500ms timeout, the key value is not read again. The actual effect is that there is always print output.

    filename = argv[1];
    fd = open(filename, O_RDWR | O_NONBLOCK);    /* Non blocking access */
    if (fd < 0)
    {
        printf("[APP] Can't open file %s\r\n", filename);
        return -1;
    }

    /* Structural body */
    fds.fd = fd;
    fds.events = POLLIN;
    while(1)
    {
        printf("[APP] poll begin... \r\n", data);
        ret = poll(&fds, 1, 500);
        printf("[APP] poll end \r\n", data);
        /* Data valid */
        if (ret > 0)
        {
            ret = read(fd, &data, sizeof(data));
            if(ret < 0)
            {
                /* Read error */
            }
            else
            {
                if(data)
                {
                    printf("[APP] key value = %d \r\n", data);
                }
            }     
        }
        /* overtime */
        else if (ret == 0)
        {
            /* User defined timeout processing */
        }
        /* error */
        else
        {
            /* User defined error handling */
        }
    }

2.2. 2.2 read in select mode

The select mode is similar to the poll mode. Both are non blocking reads, and the program is similar:

while(1)
{
    FD_ZERO(&readfds);
    FD_SET(fd, &readfds);
    /* Construction timeout */
    timeout.tv_sec = 0;
    timeout.tv_usec = 500000; /* 500ms */
    ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
    switch (ret)
    {
            /* overtime */
        case 0:
            /* User defined timeout processing */
            break;
            /* error */
        case -1:
            /* User defined error handling */
            break;
            /* Can read data */
        default:
            if(FD_ISSET(fd, &readfds))
            {
                ret = read(fd, &data, sizeof(data));
                if (ret < 0)
                {
                    /* Read error */
                }
                else
                {
                    if (data)
                    {
                        printf("key value=%d\r\n", data);
                    }
                }
            }
            break;
    }
}

2.3 experiment

2.3. 1. Read in poll mode

As before, use Makefile to compile drivers and applications and copy them to the nfs root file system.

Start the test, as shown in the figure below. When no key is pressed, the application is not blocked. It can be seen from the continuous printing that the application is running in a cycle. When a key is pressed, the corresponding key value can be read.

The keystroke program is running in the background. At this time, use the top command to check the CPU utilization. It can be found that the non blocking keystroke drive mode, and the CPU temporary utilization rate is almost 0. Although the circular reading mode is still implemented in the keystroke application, the poll function has a timeout setting of 500ms. During the timeout waiting time, the CPU usage right is also surrendered, So CPU utilization has also decreased.

2.3. 2. Read in select mode

Reading in select mode has the same effect as reading in poll mode.

Use the ps command to view the key sequence number in poll mode, kill the process with kill, and then run the key application in select mode:

select non blocking reading, and the CPU utilization rate is almost 0:

3 Summary

In this paper, two I/O models are used for key reading: blocking I/O and non blocking I/O. through actual experiments, the actual operation effects and main differences of the two methods are compared, and the CPU occupancy rate is checked. The CPU utilization rate of the two methods is almost 0.