This article is a continuation of the Series on Linux Device Drivers and carries the discussion on Linux device drivers and their implementation. The aim of this series is to provide easy and practical examples that anyone can understand. In our previous tutorial, we have seen the Workqueue in Linux using the Static method. This is the Workqueue in Linux (Dynamic Creation Method) – Linux Device Driver Tutorial Part 15.
You can also read Sysfs, Procfs, Workqueue, Completion, Softirq, and threaded IRQ in the Linux device driver.
Table of Contents
Workqueue in Linux
Initialize work using Dynamic Method
The below call (INIT_WORK) creates a workqueue in Linux by the name work
and the function that gets scheduled in the queue is work_fn
.
INIT_WORK(work,work_fn)
Where,
name
: The name of the “work_struct” structure that has to be created.
func
: The function to be scheduled in this workqueue.
Schedule work to the Workqueue
The below functions used to allocate the work to the queue.
Schedule_work
This function puts a job in the kernel-global workqueue if it was not already queued and leaves it in the same position on the kernel-global workqueue otherwise.
where,
Returns zero if |
Scheduled_delayed_work
After waiting for a given time this function puts a job in the kernel-global workqueue.
where,
|
Schedule_work_on
This puts a job on a specific CPU.
where,
|
Scheduled_delayed_work_on
After waiting for a given time this puts a job in the kernel-global workqueue on the specified CPU. int scheduled_delayed_work_on(int cpu, struct delayed_work *dwork, unsigned long delay ); where,
|
Delete work from workqueue
There are also a number of helper functions that you can use to flush or cancel work on work queues. To flush a particular work item and block until the work is complete, you can make a call to flush_work
. All work on a given work queue can be completed using a call to . In both cases, the caller blocks until the operation are complete. To flush the kernel-global work queue, call
flush_scheduled_work
.
int flush_work( struct work_struct *work );
void flush_scheduled_work( void );
Cancel Work from workqueue
You can cancel work if it is not already executing in a handler. A call to cancel_work_sync
will terminate the work in the queue or block until the callback has finished (if the work is already in progress in the handler). If the work is delayed, you can use a call to cancel_delayed_work_sync
.
int cancel_work_sync( struct work_struct *work );
int cancel_delayed_work_sync( struct delayed_work *dwork );
Check workqueue
Finally, you can find out whether a work item is pending (not yet executed by the handler) with a call to work_pending
or delayed_work_pending
.
work_pending( work );
delayed_work_pending( work );
Programming
Driver Source Code
In that source code, When we read the /dev/etx_device
, the interrupt will hit (To understand interrupts in Linux go to this tutorial). Whenever an interrupt hits, I’m scheduling the work to the workqueue. I’m not going to do any job in both interrupt handler and workqueue function since it is a tutorial post. But in real workqueue in Linux, this function can be used to carry out any operations that need to be scheduled.
[Get the source from GitHub]
/***************************************************************************//** * \file driver.c * * \details Simple Linux device driver (Global Workqueue - Dynamic method) * * \author EmbeTronicX * *******************************************************************************/ #include <linux/kernel.h> #include <linux/init.h> #include <linux/module.h> #include <linux/kdev_t.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/device.h> #include<linux/slab.h> //kmalloc() #include<linux/uaccess.h> //copy_to/from_user() #include<linux/sysfs.h> #include<linux/kobject.h> #include <linux/interrupt.h> #include <asm/io.h> #include <linux/workqueue.h> // Required for workqueues #include <linux/err.h> #define IRQ_NO 11 /* Work structure */ static struct work_struct workqueue; void workqueue_fn(struct work_struct *work); /*Workqueue Function*/ void workqueue_fn(struct work_struct *work) { printk(KERN_INFO "Executing Workqueue Function\n"); } //Interrupt handler for IRQ 11. static irqreturn_t irq_handler(int irq,void *dev_id) { printk(KERN_INFO "Shared IRQ: Interrupt Occurred"); /*Allocating work to queue*/ schedule_work(&workqueue); return IRQ_HANDLED; } volatile int etx_value = 0; dev_t dev = 0; static struct class *dev_class; static struct cdev etx_cdev; struct kobject *kobj_ref; /* ** Function Prototypes */ static int __init etx_driver_init(void); static void __exit etx_driver_exit(void); /*************** Driver Fuctions **********************/ static int etx_open(struct inode *inode, struct file *file); static int etx_release(struct inode *inode, struct file *file); static ssize_t etx_read(struct file *filp, char __user *buf, size_t len,loff_t * off); static ssize_t etx_write(struct file *filp, const char *buf, size_t len, loff_t * off); /*************** Sysfs Fuctions **********************/ static ssize_t sysfs_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf); static ssize_t sysfs_store(struct kobject *kobj, struct kobj_attribute *attr,const char *buf, size_t count); struct kobj_attribute etx_attr = __ATTR(etx_value, 0660, sysfs_show, sysfs_store); /* ** File operation sturcture */ static struct file_operations fops = { .owner = THIS_MODULE, .read = etx_read, .write = etx_write, .open = etx_open, .release = etx_release, }; /* ** This fuction will be called when we read the sysfs file */ static ssize_t sysfs_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { printk(KERN_INFO "Sysfs - Read!!!\n"); return sprintf(buf, "%d", etx_value); } /* ** This fuction will be called when we write the sysfsfs file */ static ssize_t sysfs_store(struct kobject *kobj, struct kobj_attribute *attr,const char *buf, size_t count) { printk(KERN_INFO "Sysfs - Write!!!\n"); sscanf(buf,"%d",&etx_value); return count; } /* ** This fuction will be called when we open the Device file */ static int etx_open(struct inode *inode, struct file *file) { printk(KERN_INFO "Device File Opened...!!!\n"); return 0; } /* ** This fuction will be called when we close the Device file */ static int etx_release(struct inode *inode, struct file *file) { printk(KERN_INFO "Device File Closed...!!!\n"); return 0; } /* ** This fuction will be called when we read the Device file */ static ssize_t etx_read(struct file *filp, char __user *buf, size_t len, loff_t *off) { printk(KERN_INFO "Read function\n"); asm("int $0x3B"); // Corresponding to irq 11 return 0; } /* ** This fuction will be called when we write the Device file */ static ssize_t etx_write(struct file *filp, const char __user *buf, size_t len, loff_t *off) { printk(KERN_INFO "Write Function\n"); return 0; } /* ** Module Init function */ static int __init etx_driver_init(void) { /*Allocating Major number*/ if((alloc_chrdev_region(&dev, 0, 1, "etx_Dev")) <0){ printk(KERN_INFO "Cannot allocate major number\n"); return -1; } printk(KERN_INFO "Major = %d Minor = %d \n",MAJOR(dev), MINOR(dev)); /*Creating cdev structure*/ cdev_init(&etx_cdev,&fops); /*Adding character device to the system*/ if((cdev_add(&etx_cdev,dev,1)) < 0){ printk(KERN_INFO "Cannot add the device to the system\n"); goto r_class; } /*Creating struct class*/ if(IS_ERR(dev_class = class_create(THIS_MODULE,"etx_class"))){ printk(KERN_INFO "Cannot create the struct class\n"); goto r_class; } /*Creating device*/ if(IS_ERR(device_create(dev_class,NULL,dev,NULL,"etx_device"))){ printk(KERN_INFO "Cannot create the Device 1\n"); goto r_device; } /*Creating a directory in /sys/kernel/ */ kobj_ref = kobject_create_and_add("etx_sysfs",kernel_kobj); /*Creating sysfs file for etx_value*/ if(sysfs_create_file(kobj_ref,&etx_attr.attr)){ printk(KERN_INFO"Cannot create sysfs file......\n"); goto r_sysfs; } if (request_irq(IRQ_NO, irq_handler, IRQF_SHARED, "etx_device", (void *)(irq_handler))) { printk(KERN_INFO "my_device: cannot register IRQ "); goto irq; } /*Creating work by Dynamic Method */ INIT_WORK(&workqueue,workqueue_fn); printk(KERN_INFO "Device Driver Insert...Done!!!\n"); return 0; irq: free_irq(IRQ_NO,(void *)(irq_handler)); r_sysfs: kobject_put(kobj_ref); sysfs_remove_file(kernel_kobj, &etx_attr.attr); r_device: class_destroy(dev_class); r_class: unregister_chrdev_region(dev,1); cdev_del(&etx_cdev); return -1; } /* ** Module exit function */ static void __exit etx_driver_exit(void) { free_irq(IRQ_NO,(void *)(irq_handler)); kobject_put(kobj_ref); sysfs_remove_file(kernel_kobj, &etx_attr.attr); device_destroy(dev_class,dev); class_destroy(dev_class); cdev_del(&etx_cdev); unregister_chrdev_region(dev, 1); printk(KERN_INFO "Device Driver Remove...Done!!!\n"); } module_init(etx_driver_init); module_exit(etx_driver_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("EmbeTronicX <[email protected]>"); MODULE_DESCRIPTION("Simple Linux device driver (Global Workqueue - Dynamic method)"); MODULE_VERSION("1.11");
MakeFile
obj-m += driver.o KDIR = /lib/modules/$(shell uname -r)/build all: make -C $(KDIR) M=$(shell pwd) modules clean: make -C $(KDIR) M=$(shell pwd) clean
Building and Testing Driver
- Build the driver by using Makefile (
sudo make
) - Load the driver using
sudo insmod driver.ko
- To trigger the interrupt read device file (
sudo cat /dev/etx_device
) - Now see the Dmesg (
dmesg
)
linux@embetronicx-VirtualBox: dmesg [11213.943071] Major = 246 Minor = 0 [11213.945181] Device Driver Insert...Done!!! [11217.255727] Device File Opened...!!! [11217.255747] Read function [11217.255783] Shared IRQ: Interrupt Occurred [11217.255845] Executing Workqueue Function [11217.255860] Device File Closed...!!!
- We can able to see the print “Shared IRQ: Interrupt Occurred“ and “Executing Workqueue Function“
- Unload the module using
sudo rmmod driver
In our next tutorial, we will discuss Workqueue using its own worker thread.
Please find the other Linux device driver tutorials here.
You can also read the below tutorials.
Embedded Software | Firmware | Linux Devic Deriver | RTOS
Hi, I’m SLR. I am a tech blogger and an Embedded Engineer. I am always eager to learn and explore tech-related concepts. And also, I wanted to share my knowledge with everyone in a more straightforward way with easy practical examples. I strongly believe that learning by doing is more powerful than just learning by reading. I love to do experiments. If you want to help or support me on my journey, consider sharing my articles, or Buy me a Coffee! Thank you for reading my blog! Happy learning!