Monthly Archives: October 2015

Waiting / Blocking in Linux Driver

<< Previous Article

Continuing our journey with Linux kernel internals, the next few articles in this series will focus on wait mechanisms in kernel. Now, you might be wondering, why do we need to wait in Linux driver? Well, there can be quite a lot of reasons to wait in driver. Let’s say you are interfacing with hardware such as LCD, which requires you to wait for 5ms before sending a subsequent command. Another example is say you want to read the data from disk, and since disk is a slower device, it may require you to wait until valid data is available. In these scenarios, we have no option, but to wait. One of the simplest way to implement the wait is a busy loop, but it might not be efficient way of waiting. So, does kernel provide any efficient mechanisms to wait? Yes, of course, kernel does provide a variety of mechanisms for waiting. Read on to get the crux of waiting in Linux kernel.

Process States in Linux

Before moving on to the wait mechanisms, it would be worthwhile to understand the process states in Linux. At any point of time, a process can be in any of the below mentioned states:

  • TASK_RUNNING :- Process is in run queue or is running
  • TASK_STOPPED :- Process stopped by debugger
  • TASK_INTERRUPTIBLE :- Process is waiting for some event, but can be woken up by signal
  • TASK_UNINTERRUPTIBLE :- Similar to TASK_INTERRUPTIBLE, but can’t be woken up by signal
  • TASK_ZOMBIE :- Process is terminated, but not cleaned up yet

For a process to be scheduled, it needs to be in TASK_RUNNING state, while TASK_INTERRUPTIBLE and TASK_INTERRUPTIBLE states correspond to a waiting process.

Wait Mechanism in Linux Kernel

API schedule() provides the basic wait mechanism in the linux kernel. Invoking this API yields the processor and invokes the scheduler to schedule any other process in run queue. Below is the programming example for the same:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/delay.h>

#define FIRST_MINOR 0
#define MINOR_CNT 1

static dev_t dev;
static struct cdev c_dev;
static struct class *cl;

int open(struct inode *inode, struct file *filp)
{
	printk(KERN_INFO "Inside open\n");
	return 0;
}

int release(struct inode *inode, struct file *filp)
{
	printk(KERN_INFO "Inside close\n");
	return 0;
}

ssize_t read(struct file *filp, char *buff, size_t count, loff_t *offp)
{
	printk(KERN_INFO "Inside read\n");
	printk(KERN_INFO "Scheduling out\n");
	schedule();
	printk(KERN_INFO "Woken up\n");
	return 0;
}

ssize_t write(struct file *filp, const char *buff, size_t count, loff_t *offp)
{
	printk(KERN_INFO "Inside Write\n");
	return 0;
}

struct file_operations fops =
{
	.read = read,
	.write = write,
	.open = open,
	.release = release
};

int schd_init (void)
{
	int ret;
	struct device *dev_ret;

	if ((ret = alloc_chrdev_region(&dev, FIRST_MINOR, MINOR_CNT, "wqd")) < 0)
	{
		return ret;
	}
	printk("Major Nr: %d\n", MAJOR(dev));

	cdev_init(&c_dev, &fops);

	if ((ret = cdev_add(&c_dev, dev, MINOR_CNT)) < 0)
	{
		unregister_chrdev_region(dev, MINOR_CNT);
		return ret;
	}

	if (IS_ERR(cl = class_create(THIS_MODULE, "chardrv")))
	{
		cdev_del(&c_dev);
		unregister_chrdev_region(dev, MINOR_CNT);
		return PTR_ERR(cl);
	}
	if (IS_ERR(dev_ret = device_create(cl, NULL, dev, NULL, "mychar%d", 0)))
	{
		class_destroy(cl);
		cdev_del(&c_dev);
		unregister_chrdev_region(dev, MINOR_CNT);
		return PTR_ERR(dev_ret);
	}
	return 0;
}

void schd_cleanup(void)
{
	printk(KERN_INFO " Inside cleanup_module\n");
	device_destroy(cl, dev);
	class_destroy(cl);
	cdev_del(&c_dev);
	unregister_chrdev_region(dev, MINOR_CNT);
}

module_init(schd_init);
module_exit(schd_cleanup);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Pradeep Tewani");
MODULE_DESCRIPTION("Waiting Process Demo");

Example above is a simple character driver demonstrating the use of schedule() API. In read() function, we invoke schedule() to yield the processor. Below is the sample run, assuming that the above program is compiled as sched.ko:

$ insmod sched.ko
Major Nr: 244
$ cat /dev/mychar0
Inside open
Inside read
Scheduling out
Woken up

So, what do we get? Does the usage of schedule() API serves the purpose of waiting? Not really. Why is it so?  Well, if you recall the definition of schedule(), it states that the process invoking this API voluntarily yields the processor, but only yielding the processor is not enough. Process is still in run queue and as long as process is in run queue, it would be scheduled again to run. This is exactly what happens in the above example, which makes our process to come out of wait quite immediately. So, the pre-condition for performing the wait with schedule() is to first move the process out of the run queue. How do we achieve this? For this, we have a API called set_current_state(). Below is the modified code snippet for the read() function:

ssize_t read(struct file *filp, char *buff, size_t count, loff_t *offp)
{
	printk(KERN_INFO "Inside read\n");
	printk(KERN_INFO "Scheduling out\n");
	set_current_state(TASK_INTERRUPTIBLE);
	schedule();
	printk(KERN_INFO "Woken up\n");
	return 0;
}

In the above example, before invoking the schedule() API, we are setting the state of the process to TASK_INTERRUPTIBLE. This will move the process out of run queue and hence it won’t be scheduled again to run. Below is the sample run for the modified example:

$ insmod sched.ko
Major Nr: 250
$ cat /dev/mychar0
Inside open
Inside read
Scheduling out

Conclusion

So, finally we are able to get the process blocked. But do you see the problem with this code? This process is indefinitely blocked. When and who will wake this process up? Another thing worth noting is that in real life scenarios, process always waits on some event, but our process is put to an unconditional sleep. How do we make a process wait on an event? To find out the answer to these questions, stay tuned to my next article. Till then, Happy Waiting!

Next Article >>

   Send article as PDF   

Micro-controller Programming on a Bread Board

This 9th article in the series of “Do It Yourself: Electronics”, programs a micro-controller without a hardware programmer.

<< Previous Article

In playing around with DIY electronics, Pugs has developed enough confidence to share his knowledge with his juniors. So, in one such occasion, he decided to give a try to program a micro-controller, as part of the electronics hobby club. There have been many hobbyist micro-controllers, like 8051, PIC, AVR, … and an equivalent or more varieties of hardware programmers to program them. However, Pugs’ goal was different – how can a DIY electronics learner, one as he is, do program a micro-controller in the simplest possible way with no unknown pieces of hardware, meaning no external hardware programmers. First fundamental question was if that was even possible.

“Hey Pugs, seems like it can be achieved with AVR controllers – they have a simple serial programming mechanism using their MOSI, MISO, SCK lines”, exclaimed his junior Vinay, while going through the AVR ATmega16 datasheet pg 273-277.

“Yes, seems possible, at least on the AVR side – we may just have to figure out, how to control these lines from a laptop”, asserted Pugs, reviewing the same.

“Can’t we use serial?”, ask Vinay.

“Yes, but our laptops don’t have a serial – hopefully USB to Serial converters would work”, replied Pugs.

“If it works, it would be great. We can then just connect the various serial port lines to the corresponding ATmega16 lines, and then write an application on laptop to download a ‘blink led’ program into the ATmega16”, supported Vinay.

“Regarding the application, we may not have to write one, as there is already one open source application called avrdude, specially for downloading or flashing programs into AVRs. We may just have to configure it properly”, replied Pugs.

“O! That’s good.”

“However, connecting the lines of ATmega16 to serial port may not be straight forward.”

“Why? That looks simpler than the flashing part.”

“Ya! but the challenge is that serial port lines operate on +/-12V – +12V being logic 0 and -12V being logic 1. And, micro-controllers understand 0/+5V – 0 being logic 0V and +5V being logic 1.”

“Oh! I didn’t know that there are things where 0 and 1 are not just 0V and 5V. Then, it might not be possible to connect them, right?”

“Don’t give up that easy. Where there is a problem, there would be a solution. Possibly there would be some way to do the proper voltage translations.”

So, they explored further about the same and figured out that ICs like MAX232 are meant exactly for such purposes. MAX232 datasheet gave them the connection details. Using that, they set up the ATmega16 and MAX232 connections, as shown in the schematic and breadboard diagram below. They also connected an LED through a resistor to port pin B0 for “blink led” program. Also, they set up the reset circuitry using the pull-up resistor and the jumper J1, as reset needs to be pulled low for downloading the program into ATmega16, and needs to be set high for running the program. So, J1 would be shorted, before starting the programming, and opened for running the flashed program.

AVR Programming Schematic

AVR Programming Schematic

AVR Programming Bread Board Connections

AVR Programming Bread Board Connections

“Aha! That’s cool. So, now we have the jumper J2 to connect our ATmega16 to our laptop over the serial port. But how do we decide, which lines to connect to what?”, doubted Vinay.

“That should be simpler. Let’s open the avrdude‘s configuration file, and look for ponyser section, which is the mode we are going to use for flashing our program”, suggested Pugs.

The following is what they obtained from the avrdude.conf file (typically located under /etc/avrdude/ in Linux):

programmer
  id    = "ponyser";
  desc  = "design ponyprog serial, reset=!txd sck=rts mosi=dtr miso=cts";
  type  = "serbb";
  connection_type = serial;
  reset = ~3;
  sck   = 7;
  mosi  = 4;
  miso  = 8;
;

Based on this, they figured out and connected the following serial port line connections with the jumper J2 from left to right in the schematic: CTS (pin 8), RTS (pin 7), GND (pin 5), DTR (pin 4), using the jumper cables. And, finally powered the whole circuitry with 5V from an LM7805 & 9V battery, as shown in the schematic and breadboard diagram above.

Vinay got the following blink_led.c program coded in C:

/* Toggles the LED connected at PB0 at 1Hz */

#include <avr/io.h>
#include <util/delay.h>

void init_io(void)
{
	// 1 = output, 0 = input
	DDRB |= (1 << DDB0);
}

int main(void)
{
	init_io();

	while (1)
	{
		PORTB |= (1 << PB0);
		_delay_ms(500);
		PORTB &= ~(1 << PB0);
		_delay_ms(500);
	}

	return 0;
}

Alongwith, he installed the AVR toolchain and compiled the program as follows:

$ avr-gcc -mmcu=atmega16 -DF_CPU=1000000 -Os blink_led.c -o blink_led.elf
$ avr-objcopy -O ihex blink_led.elf blink_led.hex

Why the F_CPU=1000000? As Vinay figured out from the ATmega16 datasheet pg 260-261, that with the default fuse settings of the ATmega16, it runs on a 1MHz clock.

And finally, they downloaded the blink_led.hex into the ATmega16 (with J1 shorted), using the following command:

$ avrdude -c ponyser -P /dev/ttyUSB0 -p m16 -U flash:w:blink_led.hex:i

“Hey Pugs, avrdude says programmed successfully. But no LED blink. What could be wrong?”

“Did you remove the short from J1?”

“Aha! No. So, it is still in downloading mode.”

Vinay removes the short and viola LED connected to port pin B0 starts blinking with a 1Hz frequency.

Enhancement

Interestingly, on his later explorations, Pugs figured out that you don’t even need the MAX232 & related circuitry to do the flashing of an AVR. One can directly connect the MISO line to the CTS pin, as this is input to the serial. And, the other two lines (MOSI to DTR, SCK to RTS) can be connected each through a 22K resistor, thus limiting the voltage into the ATmega16. See the schematic and breadboard diagram below.

AVR Programming Simplified Schematic

AVR Programming Simplified Schematic

AVR Programming Simplified Bread Board Connections

AVR Programming Simplified Bread Board Connections

But now, the logic is reversed on all the 3 lines, and hence an another entry, with values inverted from the ponyser entry, say ponyseri, has to be added in the avrdude.conf, as follows:

programmer
  id    = "ponyseri";
  desc  = "design ponyprog serial, reset=txd sck=!rts mosi=!dtr miso=!cts";
  type  = "serbb";
  connection_type = serial;
  reset = 3;
  sck   = ~7;
  mosi  = ~4;
  miso  = ~8;
;

And, then to be used in the avrdude command as follows:

$ avrdude -c ponyseri -P /dev/ttyUSB0 -p m16 -U flash:w:blink_led.hex:i

Next Article >>

   Send article as PDF