Kernel Module 2 Char Device - FrankBau/meta-marsboard-bsp GitHub Wiki

This example extends Kernel Module 1 - Hello World by exposing a device /dev/cse3 to user mode which can be used to communicate to my_module. The type of device used is called a character device, because its form of communication are two streams of (ASCII) characters, one for input, and one for output. One of the oldest and most common character devices is a serial console like /dev/tty or /dev/ttyUSB0 the serial link that is used to connect a MarS Board to a host PC.

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h> 
#include <linux/fs.h>
#include <asm/uaccess.h>

static char command[256];
static char response[256];
int have_command = 0;

int my_read( struct file *filep, char *buffer, size_t count, loff_t *offp ) 
{
    int ret;

    if( 0==have_command )
        return 0; // we already have sent a response and wait for a "write command" operation

    if( count > strlen(response) )
        count = strlen(response);

    ret = copy_to_user( buffer, response, strlen(response) );
    if( ret != 0 )
        return -EINVAL;

    have_command = 0;
    return count;
}

int my_write( struct file *filep, const char *buffer, size_t count, loff_t *offp )
{
    int ret;

    if( count > sizeof(command)-1 )
        count = sizeof(command)-1;

    ret = copy_from_user( command, buffer, count );
    if( ret != 0 )
        return -EINVAL;
    
    command[count] = '\0';

    // parse command here and execute it
    switch( command[0] )
    {
        case 's': 
            // TODO: set led
            strcpy( response, "OKd\n" );
            break;
        case 'c': 
            // TODO: clear led
            strcpy( response, "OK\n" );
            break;
        default: 
            strcpy( response, "ERROR: unknown command\n" ); 
    }

    have_command = 1;
    return count;
} 

static struct file_operations my_fops =
{
    .read = my_read,
    .write = my_write,
};

static int            my_major_number;
static struct class*  my_device_class;
static struct device* my_device;

static int __init my_init(void)
{
    printk(KERN_INFO "my module: init\n");

    my_major_number = register_chrdev(0, "cse3", &my_fops);
    if( my_major_number < 0 )
    {
        printk( KERN_ERR "register_chrdev failed, error %d\n", my_major_number );
        return  my_major_number;
    }

    my_device_class = class_create( THIS_MODULE, "cse3class" );
    if( IS_ERR(my_device_class) )
    {
        printk( KERN_ERR "class_create failed, error %ld\n", PTR_ERR(my_device_class) );
        unregister_chrdev( my_major_number, "cse3" );
        return PTR_ERR(my_device_class);
    }  

    my_device = device_create( my_device_class, NULL, MKDEV(my_major_number, 0), NULL, "cse3" );
    if (IS_ERR(my_device))
    {
        printk( KERN_ERR "device_create failed, error %ld\n", PTR_ERR(my_device) );
        class_destroy( my_device_cdlass );
        unregister_chrdev( my_major_number, "cse3" );
        return PTR_ERR(my_device);
    }

    return 0;
}

static void __exit my_exit(void)
{
    device_destroy( my_device_class, MKDEV(my_major_number,0) );
    class_destroy( my_device_class );
    unregister_chrdev( my_major_number, "cse3" );
    printk(KERN_INFO "my module: exit\n" );
}

module_init(my_init);
module_exit(my_exit);

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("F.B.");
MODULE_DESCRIPTION("my char device driver");

Test the kernel module by building it (as above) and transferring it to the target, say /home/root/my_module.ko

Login as root on the target and enter:

root@marsboard:~# insmod my_module.ko 
my module: init

root@marsboard:~# lsmod
Module                  Size  Used by
my_module               1807  0
...

root@marsboard:~# ls -l /dev/cse3 
crw------- 1 root root 246, 0 May 16 01:23 /dev/cse3

root@marsboard:~# echo "s" > /dev/cse3 
root@marsboard:~# cat /dev/cse3 
OK

root@marsboard:~# echo "x" > /dev/cse3
root@marsboard:~# cat /dev/cse3
ERROR: unknown command

root@marsboard:~# rmmod my_module
my module: exit

This simple module has some limitations:

  • open and release functions are not implemented, so the device can be openend serveral times or by several processes. This might be confusing. Hint: implement both functions such that the device can be opened at most once at a time.
  • my_read returns 0 which means "end of file". This is good for cat, because cat reads as much as it can. One might prefer a different implementation of my_read when the response is read by a user mode app.
  • There is no protection against race conditions like if my_read is called during my_write. So you might want to use a mutex for protection.
  • Often, kernel modules implement ioctl calls to set/get binary data like a C struct

Kernel Module 3 - GPIO shows how actually to toggle a GPIO pin whenever a read is executed.

⚠️ **GitHub.com Fallback** ⚠️