Device node creation without using “mknod”

In my last post, where i showed how to write a character gpio driver, i had used mknod for device node creation. Without mknod the device files would not have been created under /dev. So, we had to manually create the device node under /dev using mknod. Now, cannot this manual work be done away with? Of course, it can be done!!.

The automatic creation of device files can be handled with udev. One has to ensure that the major and minor numbers assigned to a device controlled by the driver are exported to user space through the sysfs interface. To know more about this, read “The Linux Device Module” chapter from the Linux Device Drivers book.

Below i am posting the source code for the driver module, the user space application and the Makefile. The user space application and Makefile remain the same. I have only changed the name of the device node under /dev from gpio to gpio_drv. So, the user space application code accordingly reflects this.

The Driver Module:


#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <asm/uaccess.h>
#include <linux/version.h>
#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/device.h>
#include <linux/cdev.h>

#define GPIO_NUMBER    149     //User LED 0. GPIO number 149. Page 71 of BB-xM Sys Ref Manual.

static dev_t first;         // Global variable for the first device number
static struct cdev c_dev;     // Global variable for the character device structure
static struct class *cl;     // Global variable for the device class

static int init_result;

static ssize_t gpio_read( struct file* F, char *buf, size_t count, loff_t *f_pos )
{
    char buffer[10];

    int temp = gpio_get_value(GPIO_NUMBER);

    sprintf( buffer, "%1d" , temp );

    count = sizeof( buffer );

    if( copy_to_user( buf, buffer, count ) )
    {
        return -EFAULT;
    }

    if( *f_pos == 0 )
    {
        *f_pos += 1;
        return 1;
    }
    else
    {
        return 0;
    }
}

static ssize_t gpio_write( struct file* F, const char *buf, size_t count, loff_t *f_pos )
{

    printk(KERN_INFO "Executing WRITE.\n");

    switch( buf[0] )
    {
        case '0':
        gpio_set_value(GPIO_NUMBER, 0);
        break;

        case '1':
        gpio_set_value(GPIO_NUMBER, 1);
        break;

        default:
        printk("Wrong option.\n");
        break;
    }
    return count;
}

static int gpio_open( struct inode *inode, struct file *file )
{
    return 0;
}

static int gpio_close( struct inode *inode, struct file *file )
{
    return 0;
}

static struct file_operations FileOps =
{
    .owner        = THIS_MODULE,
    .open         = gpio_open,
    .read         = gpio_read,
    .write        = gpio_write,
    .release      = gpio_close,
};

static int init_gpio(void)
{
    //init_result = register_chrdev( 0, "gpio", &FileOps );

    init_result = alloc_chrdev_region( &first, 0, 1, "gpio_drv" );

    if( 0 > init_result )
    {
        printk( KERN_ALERT "Device Registration failed\n" );
        return -1;
    }
    //else
    //{
    //    printk( KERN_ALERT "Major number is: %d\n",init_result );
    //    return 0;
    //}

    if ( (cl = class_create( THIS_MODULE, "chardev" ) ) == NULL )
    {
        printk( KERN_ALERT "Class creation failed\n" );
        unregister_chrdev_region( first, 1 );
        return -1;
    }

    if( device_create( cl, NULL, first, NULL, "gpio_drv" ) == NULL )
    {
        printk( KERN_ALERT "Device creation failed\n" );
        class_destroy(cl);
        unregister_chrdev_region( first, 1 );
        return -1;
    }

    cdev_init( &c_dev, &FileOps );

    if( cdev_add( &c_dev, first, 1 ) == -1)
    {
        printk( KERN_ALERT "Device addition failed\n" );
        device_destroy( cl, first );
        class_destroy( cl );
        unregister_chrdev_region( first, 1 );
        return -1;
    }

    return 0;
}

void cleanup_gpio(void)
{
    //unregister_chrdev( init_result, "gpio" );

    cdev_del( &c_dev );
    device_destroy( cl, first );
    class_destroy( cl );
    unregister_chrdev_region( first, 1 );

    printk(KERN_ALERT "Device unregistered\n");
}

module_init(init_gpio);
module_exit(cleanup_gpio);

MODULE_AUTHOR("Sanchayan");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Beagleboard-xM GPIO Driver");

The User Space Application:


#include <stdio.h>
#include <fcntl.h>

int main(void)
{
    int fd;
    char gpio_buffer[10];
    char choice[10];

    fd = open( "/dev/gpio_drv", O_RDWR );

    printf( "Value of fd is: %d", fd );

    if( fd < 0 )
    {
        printf("Cannot open device \t");
        printf(" fd = %d \n",fd);
        return 0;
     }

     printf("\nPlease enter choice: \t");
     scanf( "%s", choice );
     printf("Your choice is: %s \n", choice );
     write( fd, choice, 1 );
     read( fd, gpio_buffer, 1);
     printf("GPIO value is: %s \n", gpio_buffer );

     if( 0 != close(fd) )
     {
         printf("Could not close device\n");
     }

     return 0;
}

Makefile:

# Cross compilation Makefile for ARM

KERN_SRC=/home/vm/buildroot/output/build/linux-3.2.8
KERN_COMPILER=/opt/CodeSourcery/Sourcery_CodeBench_Lite_for_ARM_GNU_Linux/bin

obj-m := gpio.o

all:
make -C $(KERN_SRC) ARCH=arm CROSS_COMPILE=$(KERN_COMPILER)/arm-none-linux-gnueabi- M=`pwd` modules

clean:
make -C $(KERN_SRC) ARCH=arm CROSS_COMPILE=$(KERN_COMPILER)/arm-none-linux-gnueabi- M=`pwd` clean

The path for your kernel source and path for the CodeSourcery toolchain can and mostly will be different. So change them, as per your environment.

After this, compile your kernel driver module by typing “sudo make” at the command line prompt. After this, cross compile your user space application by typing

arm-none-linux-gnueabi-gcc   gpio_app.c   -o   gpio_app

I have assumed that the cross compiler path has been added to your path environment variable.

You will now have a gpio.ko file and a gpio_app executable. Transfer these files to your beagleboard. I use the “scp” command to transfer files to my board over the ssh connection.

On the command prompt of your beagleboard, do “insmod gpio.ko”.

This time, you won’t have to do the mknod for device node creation under /dev. You can cd to /sys/class and you will find the chardev entry there. Do ls /dev and you will find gpio_drv already present without having run mknod.

By default, the user led’s 0 and 1 which are connected on GPIO 149 and 150, are used for indicating mmc card access activity and heartbeat.

You can turn these dafault behaviour off by first entering the /sys/class/gpio/beagleboard::usr0 and /sys/class/gpio/beagleboard::usr1 directory. After this, do “cat trigger”. You should see the default behaviour marked.

Now, change the default behaviour for both by doing “echo none > trigger” in their respective directories.

Now, run the user space application by typing ./gpio_app on the command line prompt. You will be prompted to enter the value “0″ or “1″ to turn off or turn on the led. You can observe the state of the led pin on your board after this.

Refer the Linux Device Drivers book by Corbet, Rubini and Greg Kroah Hartman. The second, third and fourteenth chapters are important for this tutorial.

Refer this link http://www.linuxforu.com/2011/04/character-device-files-creation-operations/.  as well. Thanks to Anil Pugalia for his wonderful series on Linux Device Drivers available on http://www.linuxforu.com/tag/linux-device-drivers-series/.

Advertisements