Linux learning notes (17.9) -- processing the follow-up work of keys in the work queue

Posted by jkrettek on Mon, 07 Mar 2022 22:37:57 +0100

  1. Work queue

    Soft interrupts run in the context of interrupts, so they cannot block and sleep. Tasklets are implemented with soft interrupts, and of course they cannot block and sleep. But what if a delay handler needs sleep or blocking? Never mind, the work queue will be as you want.
    The delayed task is called work, and its data structure is work_struct, these work queues are organized into work queues with the data structure of workqueue_struct, and the worker thread is responsible for executing the work in the work queue. The default worker thread is events.
    Work queues are another form of pushing work back for execution. Work queues can postpone work and hand it over to a kernel thread for execution - the lower half is always executed in the process context, but because it is a kernel thread, it cannot access user space. The most important feature is that work queues allow rescheduling and even sleep.
    In general, it's easy to choose between work queues and soft interrupts / tasklet s. The following rules can be used:

    • If the delayed task needs sleep, you can only select the work queue.
    • If the task to be executed later needs to be delayed for a specified time before triggering, work queue is used because it can take advantage of timer delay (implemented by kernel timer).
    • If the task to be executed later needs to be processed within a tick, use soft interrupt or tasklet, because it can preempt ordinary processes and kernel threads and can not sleep at the same time.
    • If the delayed task has no requirements for the delayed time, the work queue is used, which is usually an insignificant task.
      In fact, the essence of work queue is to hand over the work to the kernel thread, so it can be replaced by the kernel thread. However, the creation and destruction of kernel threads have high requirements for programmers, and work queue realizes the encapsulation of kernel threads, which is not easy to make mistakes, so we also recommend using work queue.
  2. Work queue related data structure

    In fact, the essence of work queue is to hand over the work to the kernel thread, so it can be replaced by the kernel thread. However, the creation and destruction of kernel threads have high requirements for programmers, and work queue realizes the encapsulation of kernel threads, which is not easy to make mistakes, so we also recommend using work queue.

    • Working structure

      struct work_struct {
          atomic_long_t data; 		/* Parameters passed to the working function */
      #define WORK_STRUCT_PENDING 0   /* T if work item pending execution */
      #define WORK_STRUCT_FLAG_MASK (3UL)
      #define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)
          struct list_head entry; 	/* Linked list structure, linking work on the same work queue. */
          work_func_t func; 			/* Working function, user-defined implementation */
      #ifdef CONFIG_LOCKDEP
          struct lockdep_map lockdep_map;
      #endif
      };
      
      /* Prototype of work queue execution function: 
       * This function is executed by a worker thread, so it can sleep or interrupt in the context of the process. But it can only run in the kernel,
       * Unable to access user space
       */
      void (*work_func_t) (struct work_struct *work);
      
    • Delay work structure (the implementation of delay is to delay the insertion of the corresponding work queue during scheduling)

      struct delayed_work {
          struct work_struct work;
          struct timer_list timer; /* Timer for delay processing */
      };
      
    • Work queue structure

      struct workqueue_struct {
          struct cpu_workqueue_struct *cpu_wq; /* Pointer array, each element of which is a per CPU work queue */
          struct list_head list;
          const char *name;
          int singlethread; 	/* Flag whether to create only one worker thread */
          int freezeable;     /* Freeze threads during suspend */
          int rt;
      #ifdef CONFIG_LOCKDEP
          struct lockdep_map lockdep_map;
      #endif
      };
      
    • cpu core work queue (each cpu core corresponds to a worker thread)

       struct cpu_workqueue_struct {
          spinlock_t lock;
          struct list_head worklist;
          wait_queue_head_t more_work;
          struct work_struct *current_work;
          struct workqueue_struct *wq;
          struct task_struct *thread;
      } cacheline_aligned;
      
    • Default work queue API

      // Static creation 
      DECLARE_WORK(name,function); 		/* Define normally executed work items */
      DECLARE_DELAYED_WORK(name,function);/* Define work items to be executed later */
      
      // Dynamic creation
      INIT_WORK(_work, _func) 			/* Create normally executed work items */
      INIT_DELAYED_WORK(_work, _func)		/* Create work items that are deferred */
      
      // Schedule default work queue
      int schedule_work(struct work_struct *work)
      
      /* Schedule the normally executed work, that is, submit the processing function of the given work to the default work queue and worker thread. Worker threads are essentially
      * Is an ordinary kernel thread. By default, each CPU core has a worker thread of type "events". When schedule is called_ work
      * When, the worker thread will be awakened to perform all the work on the work linked list.
      * The default work queue name is keventd_wq, the default worker thread is called events/n, where n is the number of the processor, at each location
      * The manager corresponds to a thread. For example, a single processor system has only one thread, events/0. The dual processor system will have one more events/1
      * Thread.
      * The default work queues and worker threads are created when the kernel initializes:
      */
      start_kernel()-->rest_init-->do_basic_setup-->init_workqueues
      
      // Scheduling delayed work
      int schedule_delayed_work(struct delayed_work *dwork,unsigned long delay)
      
      // Refresh default work queue
      void flush_scheduled_work(void)     /* This function waits until all work in the queue is executed */
      
      /* Cancel delayed work */
      static inline int cancel_delayed_work(struct delayed_work *work)
      // flush_scheduled_work does not cancel any delayed work, so if you want to cancel the delayed work, you should call cancel_delayed_work. 
      

      The advantage of the above is that it is too easy for us to use the kernel to create the work queue. The disadvantage is that it is too easy for us to use the default thread to implement the work queue, which is very inefficient.

    • Custom work queue

      // The return value of the macro definition is the work queue, and name is the name of the worker thread. Create a new work queue and corresponding worker thread. Name is used to name the kernel thread.
      create_workqueue(name) 
      
      // Similar to schedule_work, the difference is queue_work submits the given work to the created work queue wq instead of the default queue.
      int queue_work(struct workqueue_struct *wq, struct work_struct *work)
      
      // Schedule delayed work.
      int queue_delayed_work(struct workqueue_struct *wq,struct delayed_work *dwork, unsigned long delay)
      
      // Refreshes the specified work queue.
      void flush_workqueue(struct workqueue_struct *wq)
      
      // Release the created work queue.
      void destroy_workqueue(struct workqueue_struct *wq)
      
    • Organization of work queues
      workqueue_struct,cpu_workqueue_struct and work_struct.
      A work queue corresponds to a work queue_ queue_ Struct, the work queue of each cpu in the work queue is controlled by the cpu_workqueue_struct, while work_struct is the specific work on it.
      The relationship is shown in the figure below:

    • Work process of work queue

    • application

      Netdev needs to be notified of the status (up/down) messages of various linux interfaces_ Interested modules on the chain report user space messages at the same time. Work queues are used here. The specific flow chart is as follows:

  3. Programming practice with keys

    3.1 key device driver file
    In button_drv.c in the document,

    • When loading a module, BTN_ hw_ drv_ Calling INIT_ in probe function Work will the key work processing function button_ workqueue_ Fu is given to func member;
    • gpio_btn_isr key interrupt service program (upper part of interrupt) calls schedule_work function, press the key Put wq into the queue (linked list);
    • After the execution of the upper and lower parts of the interrupt is completed, wake up the corresponding kernel thread and put the work from the queue_ Take out the struct structure and execute the function button inside_ workqueue_ fun;
/**
 * Files: button_drv.c
 * Author: glen  
 * Description: button driver file
 */
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/poll.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/fs.h>
#include <linux/time.h>
#include <linux/timer.h>
#include <linux/proc_fs.h>
#include <linux/stat.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/workqueue.h>

#define KEY_BUF_LEN 16
#define NEXT_POS(x) ((x+1) % KEY_BUF_LEN)

struct gbtn_irq {
    int gpio;
    struct gpio_desc *gpiod;
    int flag;
    int irq;
    int idx;
    char kval[KEY_BUF_LEN];
    int r, w;
    struct fasync_struct *fp;
    struct timer_list key_timer;
    struct tasklet_struct tasklet;
    struct work_struct wq;
};

struct button_drv {
    struct class *class;
    struct gbtn_irq *gbtn_irq;
    
    char  *name;
    int count;
    int major;
};

static struct button_drv *btn_drv;

static int is_key_buf_empty(void *arg)
{
    struct gbtn_irq *p = (struct gbtn_irq *)arg;

    return (p->r == p->w);
}

static int is_key_buf_full(void *arg)
{
    struct gbtn_irq *p = (struct gbtn_irq *)arg;

    return (p->r == NEXT_POS(p->w));
}

static void put_key(char key, void *arg)
{
    struct gbtn_irq *p = (struct gbtn_irq *)arg;

    if (!is_key_buf_full(arg)) {
        p->kval[p->w] = key;
        p->w = NEXT_POS(p->w);
    }
}

static char get_key(void *arg)
{
    char key = 'N';
    struct gbtn_irq *p = (struct gbtn_irq *)arg;

    if (!is_key_buf_full(arg)) {
        key = p->kval[p->r];
        p->r = NEXT_POS(p->r);
    }

    return key;
}

/* Waiting for static initialization of queue header */
static DECLARE_WAIT_QUEUE_HEAD(gpio_button_wait);

static void key_timer_expire(unsigned long data)
{
    int val;
    char key;
    struct gbtn_irq *ops = (struct gbtn_irq *)data;

    /* Read the value of the key */
    val = gpiod_get_value(ops->gpiod);

    printk("button%d %d %d\n", ops->idx, ops->gpio, val);
    key = (ops->gpio << 4) | val;

    put_key(key, ops);

    /* Wake up waiting queue */
    wake_up_interruptible(&gpio_button_wait);

    kill_fasync(&ops->fp, SIGIO, POLL_IN);

    /* enable btn*/
    enable_irq(ops->irq);
}

/* Implementation file_operations struct member read function */
ssize_t button_drv_read (struct file *filp, char __user *buf, size_t size, loff_t *pos)
{
    int minor = iminor(filp->f_inode);
    struct gbtn_irq *ops = (struct gbtn_irq *)filp->private_data;
    char kval;

    size = (size >= 1) ? 1 : 0;
    if (ops == NULL) {
        printk("Please register button instance %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
        return -EIO;
    }

    if (is_key_buf_empty(ops) && (filp->f_flags & O_NONBLOCK))
        return -EAGAIN;

    wait_event_interruptible(gpio_button_wait, !is_key_buf_empty(ops));
    kval = get_key(ops);

    if (copy_to_user(buf, &kval, size))
        return -EFAULT;

    printk("Read button%d value successfully:", minor);
    return size;
}

/* Implementation file_operations struct member open function */
int button_drv_open(struct inode *nd, struct file *filp)
{
    int ret;
    int minor = iminor(nd);
    struct gbtn_irq *ops; 
    
    if (btn_drv == NULL) {
        printk("Please register button instance %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
        return -EIO;
    }

    ops = &btn_drv->gbtn_irq[minor];

    ret = gpiod_direction_input(ops->gpiod);
    if (ret) 
        printk("Set the button pin as input error %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
    else 
        printk("Set the button%d pin as input successfully!\n", minor);

    filp->private_data = ops;

    return 0;
}

/* Implementation file_operations struct member release function */
int button_drv_release (struct inode *nd, struct file *filp)
{
    struct gbtn_irq *ops = (struct gbtn_irq *)filp->private_data;

    if (ops == NULL) {
        printk("Please register button instance %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
        return -EIO;
    }

    filp->private_data = NULL;

    return 0;
}

/* Implementation file_ poll operation member structure */
unsigned int button_drv_poll (struct file *filp, struct poll_table_struct * wait)
{
    int minor = iminor(filp->f_inode);

    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
    poll_wait(filp, &gpio_button_wait, wait);
    return (is_key_buf_empty(&btn_drv->gbtn_irq[minor]) ? 0 : POLLIN | POLLRDNORM);
}

static int button_drv_fasync(int fd, struct file *filp, int on)
{
    struct gbtn_irq *ops = (struct gbtn_irq *)filp->private_data;

	if (fasync_helper(fd, filp, on, &ops->fp) >= 0)
		return 0;
	else
		return -EIO;
}

/**
 * 1. Construct file_operations structure 
 */
static struct file_operations button_drv_ops = {
    .owner   = THIS_MODULE,
    .read    = button_drv_read,
    .open    = button_drv_open,
    .release = button_drv_release,
    .poll    = button_drv_poll,
    .fasync  = button_drv_fasync,
};

/* Interrupt service function */
static irqreturn_t gpio_btn_isr (int irq, void *dev_id)
{
    struct gbtn_irq *ops = dev_id;

    // printk("gpio_btn_isr key %d irq happened\n", ops->gpio);
    tasklet_schedule(&ops->tasklet);
    mod_timer(&ops->key_timer, jiffies + HZ / 50);
    schedule_work(&ops->wq);
    disable_irq_nosync(irq);
    
    return IRQ_HANDLED;
}

/* tasklet action function */
static void button_tasklet_func (unsigned long data)
{
    int val;
    struct gbtn_irq *ops = (struct gbtn_irq *)data;

    /* Read the value of the key */
    val = gpiod_get_value(ops->gpiod);

    printk("button_tasklet_func key%d %d %d\n", ops->idx, ops->gpio, val);

}

static void button_work_func (struct work_struct *work)
{
    int val;
    struct gbtn_irq *ops = container_of(work, struct gbtn_irq, wq);

    /* Read the value of the key */
    val = gpiod_get_value(ops->gpiod);
    printk("button_work_func: the process is %s pid %d\n", current->comm, current->pid);
    printk("button_work_func key%d %d %d\n", ops->idx, ops->gpio, val);
}

/* platform_driver Implementation of probe member function of structure */
int btn_hw_drv_probe (struct platform_device *pdev)
{
    int i;
    int ret;
    int count;
    // enum of_gpio_flags flag;
    struct device_node *node = pdev->dev.of_node;

    /* Get gpio quantity from device node */
    count = of_gpio_count(node);
    if (!count) {
        printk("%s %s line %d, there isn't any gpio available!\n", __FILE__, __FUNCTION__, __LINE__);
        return -EIO;
    }
    
    btn_drv = kzalloc(sizeof(struct button_drv), GFP_KERNEL);
    if (btn_drv == NULL) 
        return -ENOMEM;

    btn_drv->gbtn_irq = kzalloc(sizeof(struct gbtn_irq) * count, GFP_KERNEL);
    if (btn_drv->gbtn_irq == NULL)
        return -ENOMEM;

    for (i = 0; i < count; i++) {
        btn_drv->gbtn_irq[i].gpiod = gpiod_get_index_optional(&pdev->dev, NULL, i, GPIOD_ASIS);
        if (btn_drv->gbtn_irq[i].gpiod == NULL) {
            printk("%s %s line %d, gpiod_get_index_optional failed!\n", __FILE__, __FUNCTION__, __LINE__);
            return -EIO;
        }

        btn_drv->gbtn_irq[i].irq = gpiod_to_irq(btn_drv->gbtn_irq[i].gpiod);
        btn_drv->gbtn_irq[i].gpio = desc_to_gpio(btn_drv->gbtn_irq[i].gpiod);

        setup_timer(&btn_drv->gbtn_irq[i].key_timer, key_timer_expire, &btn_drv->gbtn_irq[i]);
        btn_drv->gbtn_irq[i].key_timer.expires = ~0;
        add_timer(&btn_drv->gbtn_irq[i].key_timer);

        tasklet_init(&btn_drv->gbtn_irq[i].tasklet, button_tasklet_func, &btn_drv->gbtn_irq[i]);

        INIT_WORK(&btn_drv->gbtn_irq[i].wq, button_work_func);

        btn_drv->gbtn_irq[i].idx = i;
    }

    for (i = 0; i < count; i++) 
        /* Apply for irq interrupt and register the interrupt service program in the upper half */
        ret = request_irq(btn_drv->gbtn_irq[i].irq, gpio_btn_isr, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, 
                          "gpio_btn", &btn_drv->gbtn_irq[i]);

    /* Register file_operationss structure object -- button_drv_ops  */
    btn_drv->major = register_chrdev(btn_drv->major, "gbtn", &button_drv_ops);
    btn_drv->class = class_create(THIS_MODULE, "gbtn");
    if (IS_ERR(btn_drv->class)) {
        printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
        unregister_chrdev(btn_drv->major, "gbtn");
        return PTR_ERR(btn_drv->class);
    }

    for (i = 0; i < count; i++)
        device_create(btn_drv->class, NULL, MKDEV(btn_drv->major, i), NULL, "gbtn%d", i);

    btn_drv->count = count;
        
    return 0;
}

/* platform_driver Implementation of remove member function of struct */
int btn_hw_drv_remove(struct platform_device *pdev)
{
    int i;
    struct device_node *node = pdev->dev.of_node;
    int count = of_gpio_count(node);

    for (i = 0; i < count; i++) {
        device_destroy(btn_drv->class, MKDEV(btn_drv->major, i));
        free_irq(btn_drv->gbtn_irq[i].irq, &btn_drv->gbtn_irq[i]);
        del_timer(&btn_drv->gbtn_irq[i].key_timer);
        tasklet_kill(&btn_drv->gbtn_irq[i].tasklet);
    }
    class_destroy(btn_drv->class);
    unregister_chrdev(btn_drv->major, "gbtn");

    kfree(btn_drv);
    return 0;
}

/* Construct device properties for configuration */
static const struct of_device_id gbtns_id[] = {
    {.compatible = "glen,gbtn"},
    { },
};

/* Construct (initialize) file_operations structure */
static struct platform_driver btn_hw_drv = {
    .driver = {
        .name = "gbtn",
        .of_match_table = gbtns_id,
    },
    .probe = btn_hw_drv_probe,
    .remove = btn_hw_drv_remove,
};

/* initialization */
static int __init button_drv_init(void)
{
    int ret;
    ret = platform_driver_register(&btn_hw_drv);
    if (ret)
        pr_err("Unable to initialize button driver\n");
    else
        pr_info("The button driver is registered.\n");

    
    return 0;
}
module_init(button_drv_init);

static void __exit button_drv_exit(void)
{
    platform_driver_unregister(&btn_hw_drv);
    printk(" %s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
}
module_exit(button_drv_exit);

/* insert author information for module */
MODULE_AUTHOR("glen");
/* insert license for module */
MODULE_LICENSE("GPL");
 

The probe function first obtains the number of key nodes, and then reads them respectively gpio Descriptor and obtain gpio and irq numbers through it, and apply for registration of interrupt service program.

3.2 equipment tree file (no change)

 		pinctrl_btn0:btn0 {
 			fsl,pins = <
 				MX6UL_PAD_UART1_CTS_B__GPIO1_IO18	0xF080	/* KEY0 */ 
 			>;
 		};
 
 		pinctrl_btn1:btn1 {
 			fsl,pins = <
                 MX6UL_PAD_GPIO1_IO03__GPIO1_IO03    0xF080	/* KEY1  This key does not exist */
 			>;
 		};
     /* Add a pinctrl based gbtns device node under the root node */
     gbtns {
         compatible = "glen,gbtn";
         #address-cells = <1>;
 
         pinctrl-names = "default";
         pinctrl-0 = <&pinctrl_btn0 
 		             &pinctrl_btn1>;
 
         gpio-controller;
         #gpio-cells = <2>;
         gpios = <&gpio1 18 GPIO_ACTIVE_LOW /* button0 */
                  &gpio1 3 GPIO_ACTIVE_LOW>;   /* button1 */
 
     };
 
  • The gpios prefix "xxx -" is canceled. Accordingly, when the driver uses the gpiod_get_index_optional function to obtain the gpio descriptor, the second formal parameter "const char *con_id" can be passed NULL;

  • Change the property values of pinctrl-0 and gpios from "< >, < >;" Change to "<, same effect"

3.3 application

Application file button_drv_test.c provide:

  • Define sig_fun signal processing function and register the signal. In the future, when APP receives SIGIO signal, this function will be called automatically;
  • fcntl(fd, F_SETOWN, getpid()); Tell the PID (process ID) of the APP to the driver. This call does not involve the driver and records the PID at the file system level of the kernel;
  • oflags = fcntl(fd, F_GETFL); Read driver file oflags
  • fcntl(fd, F_SETFL, oflags | FASYNC); Set the fasync bit in oflags to 1: when the fasync bit changes, the driver's fasync will be called
 /*
  * File name: button_drv_test.c
  * Author: glen
  * Description: button_drv application
  */
 
 #include "stdio.h"
 #include "sys/types.h"
 #include "sys/stat.h"
 #include "stdlib.h"
 #include "string.h"
 #include "poll.h"
 #include <signal.h>
 #include <unistd.h>
 #include <fcntl.h>
 
 int fd;
 
 static void sig_fun(int sig)
 {
     char kval;
     read(fd, &kval, 1);
     printf("The glen button value is: %d!\n", kval);
 }
 
 /**
  * @brief   : main function
  * @par     : argc  argv Number of array elements
  *            argv  Parameter array
  * @retval  : 0 Success other failures
  */
 int main(int argc, char *argv[])
 {
     int ret;
     int oflags;
     char *filename;
 
     if (argc != 2) {
         printf("Error Usage!\r\n");
         return -1;
     }
 
     signal(SIGIO, sig_fun);
 
     filename = argv[1];
 
     /* Open driver file */
     fd = open(filename, O_RDWR);
     if (fd < 0) {
         printf("Can't open file %s\r\n", filename);
         return -1;
     }
 
     fcntl(fd, F_SETOWN, getpid());
     oflags = fcntl(fd, F_GETFL);
     fcntl(fd, F_SETFL, oflags | FASYNC);
 
     while (1) {
         sleep(2);
         printf("Read the glen button in sleepping!\n");
     }
 
     /* Close file */
     ret = close(fd);
     if (ret < 0) {
         printf("file %s close failed!\r\n", argv[1]);
         return -1;
     }
     return 0;
 }
 

3.4 in alientek_ linux_ The measured verification of alpha development board is as follows:

/drv_module # insmod button_drv.ko
The button driver is registered.
/drv_module # ./btn_drv_test /dev/gbtn0
./btn_drv_test: line 1: syntax error: unexpected "("
/drv_module # button_tasklet_func key0 18 0
button_work_func: the process is kworker/0:2 pid 45
button_work_func key0 18 1
button0 18 1
button_tasklet_func key0 18 1
button_work_func: the process is kworker/0:2 pid 45
button_work_func key0 18 1
button0 18 1
button_tasklet_func key0 18 0
button_work_func: the process is kworker/0:2 pid 45
button_work_func key0 18 0
button0 18 0
button_tasklet_func key0 18 1
button_work_func: the process is kworker/0:2 pid 45
button_work_func key0 18 1
button0 18 1
button_tasklet_func key0 18 0
button_work_func: the process is kworker/0:2 pid 45
button_work_func key0 18 0
button0 18 0
button_tasklet_func key0 18 1
button_work_func: the process is kworker/0:2 pid 45
button_work_func key0 18 1
button0 18 1
button_tasklet_func key0 18 0
button_work_func: the process is kworker/0:2 pid 45
button_work_func key0 18 0
button0 18 0

Topics: Linux Operation & Maintenance server