Monthly Archives: November 2015

Waiting / Blocking in Linux Driver Part – 2

<< Previous Article

In the last article, we managed to get our process blocked. As stated then, the code had couple of problems. One of them being unblocking the process. There was no one to wake our process up. Sleeping process is of no use. Another flaw was that our process was sleeping unconditionally. However, in real life scenarios, process never goes to sleep unconditionally. Read on to get the further understanding of wait mechanisms in the kernel.

Waking up the Process

We have the wake_up_process() API as shown below for waking up the process.

void wake_up_process(task_struct *ts);
ts - pointer to the task_struct of the waiting process

As our process would be blocked, we need some other process to invoke this API. Below is code snippet which demonstrates the usage of this API.

#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;
static struct task_struct *sleeping_task;

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");
	sleeping_task = current;
	set_current_state(TASK_INTERRUPTIBLE);
	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");
	wake_up_process(sleeping_task);
	return count;
}

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(KERN_INFO "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");

In the above example, we are using the global variable sleeping_task to hold the task_struct of the sleeping process. This variable is updated in read() function. In write() function, we use the sleeping_task as a parameter to the wake_up_process() API.

Below is the sample run for the above example. Assuming that the above module is compiled as sched.ko:

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

The above output is same as that of example from the last article. Now, comes the interesting part of waking up the process. For this, open another shell and execute the command as below:

$ echo 1 > /dev/mychar0
Inside open
Inside write
Woken up
Inside close
Inside close

When we execute the echo command, write operation gets invoked, which invokes the wake_up_process() to wake up the blocked process.

Waiting on an event

What we saw in the above example was the basic mechanism to block and unblock the process. However, as discussed earlier, the process always waits on some event. The event can be some specified amount of time, waiting for some resource or it can well be waiting for some data to arrive. Below is the modified version of above program to wait for an event.

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");
	sleeping_task = current;
slp:
	if (flag != 'y') 
	{
		set_current_state(TASK_INTERRUPTIBLE);
		schedule();
	}
	if (flag == 'y')
		printk(KERN_INFO "Woken Up\n");
	else 
	{
		printk(KERN_INFO "Interrupted by signal\n");
		goto slp;
	}
	flag = 'n';
	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");
	ret = __get_user(flag, buffer);
	printk(KERN_INFO "%c", flag);
	wake_up_process(sleeping_task);
	return count;
}

Here, we use the global variable flag to signal the condition and the event for waking up is the flag being set to ‘y’. This flag is updated in write() function as per the data from the user space. Below is the sample run of the above program, assuming that the module is compiled as sched.ko:

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

This gets our process blocked. Open another shell to wake up the process:

$ echo 1 > /dev/mychar0
Inside open
Inside write
Interrupted by signal
Inside close

Unlike earlier program, this doesn’t unblock the process. The process wakes up and again goes to sleep, since the condition for waking up is not satisfied. The process will wake up only if the flag is set to ‘y’. Let’s execute the echo as below:

$ echo 'y' > /dev/mychar0
Inside open
Inside write
Woken up
Inside close
Inside close

As seen above, this will wake up the process, since the condition of flag being ‘y’ is satisfied.

Conclusion

In this article, we implemented the basic wait mechanism in the driver. This was more like a manual waiting where everything needs to be taken care by driver writer and as such is prone to some synchronization issues. So, this kind of manual waiting is rarely used. However, kernel does provide some robust mechanism to implement the waiting. So, stay tuned to my next article to learn more about the waiting in Linux driver.

Next Article >>

www.pdf24.org    Send article as PDF   

Digital to Analog Conversion using a Micro-controller

This 10th article in the series of “Do It Yourself: Electronics”, demonstrates a digital to analog conversion (DAC) approximation using pulse width modulation (PWM) of a micro-controller.

<< Previous Article

Once the fundamental setup for programming the micro-controller ATmega16 was up, the club members were on a spree to try out all kind of micro-controller programming based electronics experiments. If you have not yet got the setup, check out the previous article.

“With Diwali approaching and LED blinking just done, can we do LED dimming using a micro-controller”, queried Rohit.

“Should be possible. Basically we need to have a varying (analog) voltage between 0V & 5V, instead of just 0V & 5V as in the blinking of LED”, replied Pugs.

“But a micro-controller is all about digital electronics, right? How do we get analog voltage output?”

“We may not get perfect analog voltages but we definitely can do approximations, which would be practically good enough for cases like LED dimming.”

“What kind of approximations & how?”

“ATmega16 have pulse width modulation (PWM) support. We can use that for the purpose.”

“What is PWM?”

“It is a kind of timer mechanism, using which we can keep on generating square wave of desired frequency with desired duty cycle.”

“Meaning we can change the width of the square pulse on the fly as desired.”

“Yes. And that’s were the name pulse width modulation.”

“But, isn’t it still a digital waveform?”

“Yes. But, if the frequency is high enough, the voltage value averages out and the net voltage appears to be the average of the 0V & 5V, weighted by the duty cycle.”

“You mean to say, if I measure it as a DC voltage on multimeter, it would show me a stable averaged voltage.”

“Yes. And for many other practical purposes like LED dimming this is good enough an analog voltage.”

“That’s a great technique. So, I guess details of programming it would be in the datasheet.”

“Correct. Look into the timer section, say Timer0.”

Rohit and Pugs figure out the following about Timer0 from the ATmega16 datasheet pg 71-88:

  • Output of the PWM waveform would be on pin OC0/PB3, i.e Pin 4
  • For high frequency wave generation, there is a fast PWM mode, enabled by setting each of the 2 bits of WGM0 to 1 in TCCR0 register
  • Timer0 has a 8-bit counter TCNT0, which in fast PWM mode increments continuously from 0 to 255 and back to 0
  • The counter incrementing frequency is to be set by the 3-bit clock select CS0 in TCCR0 register
  • During the increment, it compares TCNT0 with OCR0, and updates the waveform output as per the settings of the 2-bit compare output mode COM0 in TCCR0 register
  • If COM0 is set to 2, pin OC0 is set to 0 on match with OCR0 and back to 1 on counter TCNT0 wrapping around to 0, generating a pulse during counting from 0 to OCR0
  • Thus values of 0 to 255 for OCR0 controls the duty cycle of the square wave between 0% to 100%, thus giving 0% to 100% of 5V averaged output

In a way, it can also be viewed as an 8-bit DAC converting the digital value 0 to 255 (in OCR0) to analog voltage 0V to 5V (on the pin PB3).

As the output waveform is pre-determined on PB3, Pugs connected the LED with a resistor (as in the previous article) but with the pin PB3 instead of PB0.

Rohit got the following dac.c program coded in C:

/*
 * DAC is basically averaging out PWM to analog voltages.
 * This example uses Fast PWM mode of 8-bit Timer/Counter0 for DAC output on
 * OC0/PB3/AIN1 (Pin 4)
 */

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

void set_digital(uint8_t digital)
{
	OCR0 = digital;
}
void init_pwm(uint8_t digital)
{
	set_digital(digital);

	/*
	 * Setting the Timer/Counter0 in Fast PWM for averaging DAC with highest
	 * possible frequency, i.e. clock select (CS0) as 1.
	 * Getting pulse during up-counting till it matches OCR0.
	 * Output would come on OC0/PB3 (Pin 4).
	 */
	TCCR0 = (1 << WGM01) | (1 << WGM00) | (0b10 << COM00);
	TCCR0 |= (0b001 << CS00);

	DDRB |= (1 << PB3); // OC0 is PB3. Setting it as output
}

int main(void)
{
	uint8_t digital = 0xFF; // Full output (5V)

	init_pwm(digital);

	while (1)
	{
		_delay_ms(10);
		if (--digital == 0x00) // Decrementing upto almost 0V
		{
			digital = 0xFF;
		}
		set_digital(digital);
	}

	return 0;
}

Then, Rohit compiled the program as follows (using the AVR toolchain already installed as in the previous article):

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

And finally, downloaded the dac.hex into the ATmega16 with J1 shorted (same as in the previous article), using the following command:

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

And … removing the jumper J1, wow! they see the LED dimming and then bright again, dimming and bright again, …

Next Article >>

www.pdf24.org    Send article as PDF