Embedded Freaks..

February 5, 2009

Linux Device Driver Programming for Java Programmer

Filed under: linux-device-driver — Tags: — kunilkuda @ 11:08 am

If you have used Java Swing Framework before (or any OOP framework for Java), learning linux device driver programming is not that difficult. Linux already has its device drivers framework. What you need to do is just overriding the ‘methods’ that you like, and registers your ‘class’ into the framework. It’s very easy, right ?

Here’s my experience with simple character device from Linux Kernel Module Programming Guide.First of all, you need to specify the ‘methods’ that your driver wants to handle. Take a look at struct file_operations in LKMPG Ch #4. It’s explanation can be viewed from LDD’s book Ch #3. I only interested in ‘open’ (handles the driver opening), ‘release’ (handles the driver closing), ‘read’ (handles data transfer from driver to user application), and ‘write’ (handles data transfer from user application to driver).

/**
* Simple mutex to avoid this device opened multiple times.
* WARNING! THIS IS NOT REAL MUTEX. OPENING THE DEVICE CONCURRENTLY MAY DESTROY
* THE MODULE
*/
static int simplemem_is_open;

/**
* This is the buffer, to be shared on device read/write
*/
static unsigned char simplemem_buffer[BUFFER_SIZE];

static int simplemem_open(struct inode *inode, struct file *file) {
// Prevent multiple opening at the same time
if (simplemem_is_open == TRUE) {
return -EBUSY;
}

simplemem_is_open = TRUE;

printk(KERN_ALERT MODULE_NAME " is opened\n");
return 0;
}

static int simplemem_release(struct inode *inode, struct file *file) {
simplemem_is_open = FALSE;

printk(KERN_ALERT MODULE_NAME " is closed\n");
return 0;
}

static ssize_t simplemem_read(struct file *filp,
char __user *buffer,
size_t length,
loff_t* offset)
{
ssize_t retval;
size_t readCount = (length < BUFFER_SIZE) ? length : BUFFER_SIZE;

if (copy_to_user(buffer, (void*)&simplemem_buffer, readCount)) {
retval = -EFAULT;
return retval;
}

*offset += readCount;
retval = readCount;

printk(KERN_ALERT MODULE_NAME " is read\n");
return retval;
}

static ssize_t simplemem_write(struct file *filp,
const char __user *buffer,
size_t length,
loff_t* offset)
{
ssize_t retval;
size_t writeCount = (length < BUFFER_SIZE) ? length : BUFFER_SIZE;

// WARNING ! This is not memset() from user's libc. This is kernel's memset.
memset((void*)&simplemem_buffer, 0x00, sizeof simplemem_buffer); //< Fill buffer with 0x00

if (copy_from_user((void*)&simplemem_buffer, buffer, writeCount)) {
retval = -EFAULT;
return retval;
}

*offset += writeCount;
retval = writeCount;

printk(KERN_ALERT MODULE_NAME " is written\n");
return retval;
}

After that, the overridden methods should be integrated into a ‘class’.

static struct file_operations simplemem_fops = {
.open = simplemem_open,
.release = simplemem_release,
.read = simplemem_read,
.write = simplemem_write
};

If you ever learn C++, the struct above is quite similar to C++’s virtual method table. Once your ‘class’ is done, it’s time to instantiate it.  Here’s how to do it:

cdev_init(&simplemem_cdev, &simplemem_fops);

In C language, ‘struct cdev simplemem_cdev’ is a structure with a lot of callback pointer members. But, you can view it as ‘object’ of ‘file_operations class’.

Finish with the object creation, you have to add your object to the framework, like this:

cdev_add(&simplemem_cdev, simplemem_devt, DEV_NR);

‘dev_t simplemem_devt’ is our device driver’s framework (similar to ‘Application’ class in Swing Framework).

The story about getting our ‘dev_t’ is not quite object oriented, so it’s better for you to read it by yourself from LKMPG Ch #2 and Ch #4.

If anyone interested, here’s my source code for simple memory char driver.

/**
 * simplemem.c
 *
 * Demonstrate simple driver to read/write kernel memory
 * Adapted from LDD Ch #3, and Linux Kernel Module Programming Guide 2.6
 *
 * Note that the driver should be intact in single file, to reduce namespace
 * pollution
 */
#include
	<linux/module.h>
#include
	<linux/moduleparam.h>
#include
	<linux/init.h>
#include
	<linux/kdev_t.h>
#include
	<linux/cdev.h>
#include
	<linux/fs.h>
#include <asm/uaccess.h>
#include
	<linux/errno.h>
#include
	<linux/string.h>
#include "simplemem.h"

/**
 * ----------------------------------------------------------------------------
 * File operations
 *
 */

/**
 * Simple mutex to avoid this device opened multiple times.
 * WARNING! THIS IS NOT REAL MUTEX. OPENING THE DEVICE CONCURRENTLY MAY DESTROY
 * THE MODULE
 */
static int simplemem_is_open;

/**
 * This is the buffer, to be shared on device read/write
 */
static unsigned char simplemem_buffer[BUFFER_SIZE];

static int simplemem_open(struct inode *inode, struct file *file) {
  // Prevent multiple opening at the same time
  if (simplemem_is_open == TRUE) {
    return -EBUSY;
  }

  simplemem_is_open = TRUE;

  printk(KERN_ALERT MODULE_NAME " is opened\n");
  return 0;
}

static int simplemem_release(struct inode *inode, struct file *file) {
  simplemem_is_open = FALSE;

  printk(KERN_ALERT MODULE_NAME " is closed\n");
  return 0;
}

static ssize_t simplemem_read(struct file *filp,
                              char __user *buffer,
                              size_t length,
                              loff_t* offset)
{
  ssize_t retval;
  size_t readCount = (length < BUFFER_SIZE) ? length : BUFFER_SIZE;

  if (copy_to_user(buffer, (void*)&simplemem_buffer, readCount)) {
    retval = -EFAULT;
    return retval;
  }

  *offset += readCount;
  retval = readCount;

  printk(KERN_ALERT MODULE_NAME " is read\n");
  return retval;
}

static ssize_t simplemem_write(struct file *filp,
                               const char __user *buffer,
                               size_t length,
                               loff_t* offset)
{
  ssize_t retval;
  size_t writeCount = (length < BUFFER_SIZE) ? length : BUFFER_SIZE;

  // WARNING ! This is not memset() from user's libc. This is kernel's memset.
  // Include
	<linux/string.h>, instead of <string.h>
  memset((void*)&simplemem_buffer, 0x00, sizeof simplemem_buffer); //< Fill buffer with 0x00

  if (copy_from_user((void*)&simplemem_buffer, buffer, writeCount)) {
    retval = -EFAULT;
    return retval;
  }

  *offset += writeCount;
  retval = writeCount;

  printk(KERN_ALERT MODULE_NAME " is written\n");
  return retval;
}

static struct file_operations simplemem_fops = {
  .open = simplemem_open,
  .release = simplemem_release,
  .read = simplemem_read,
  .write = simplemem_write
};

/**
 * ----------------------------------------------------------------------------
 * Module parameters
 *
 */
static int simplemem_major; //< By default it will ask for major '0'

/**
 * Register the param into the modules
 * Type: integer (32-bits), Permission: Read Only
 */
module_param(simplemem_major, int, S_IRUGO);
MODULE_PARM_DESC(simplemem_major, "Requested major device number");

/**
 * ----------------------------------------------------------------------------
 * Driver registrations
 *
 */

/**
 * The device handler for this module
 */
static dev_t simplemem_devt; //< Device's handler
static struct cdev simplemem_cdev; //< Char device handler

/**
 * Allocate device number for the module
 *
 * If user assigns a 'simplemem_major' (ie. 'simplemem_major' != 0), use it
 * as our device major number. Otherwise, ask kernel for our device's major
 * number
 *
 * @return negative value for error, 0 for success
 */
static int register_driver(void) {
  int retval = 0;
  if (simplemem_major != 0) {
    // Static major number allocation
    simplemem_devt = MKDEV(simplemem_major, DEV_MINOR);
    retval = register_chrdev_region(simplemem_devt, DEV_NR, MODULE_NAME);
  } else {
    // Dynamic major number allocation
    retval = alloc_chrdev_region(&simplemem_devt, DEV_MINOR, DEV_NR, MODULE_NAME);
    simplemem_major = MAJOR(simplemem_devt);
  }

  // If error happens, quit
  if (retval < 0) {
    return retval;
  }

  // Enable the char device
  cdev_init(&simplemem_cdev, &simplemem_fops);
  retval = cdev_add(&simplemem_cdev, simplemem_devt, DEV_NR);

  return retval;
}

static void unregister_driver(void) {
  cdev_del(&simplemem_cdev);
  unregister_chrdev_region(simplemem_major, DEV_NR);
}

/**
 * ----------------------------------------------------------------------------
 * Driver initialization
 *
 */
static int __init simplemem_init(void) {
  int retval = register_driver();
  if (retval < 0) {
    printk(KERN_ERR "Couldn't load driver. The error was %d\n",retval);
  }
  else {
    printk(KERN_ALERT MODULE_NAME " is loaded using major %d\n", simplemem_major);
  }

  return retval;
}

static void __exit simplemem_exit(void) {
  unregister_driver();
  printk(KERN_ALERT MODULE_NAME " is unloaded\n");
}

module_init(simplemem_init);
module_exit(simplemem_exit);

/**
 * ----------------------------------------------------------------------------
 * Driver infos
 *
 */
MODULE_AUTHOR("kunilkuda@gmail.com");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Accessing kernel's memory from user space");
MODULE_SUPPORTED_DEVICE("Any memory in the system");

Here’s its header file

/**
* simplemem.h
* Simplemem module header.
*/
#ifndef SIMPLEMEM_H
#define SIMPLEMEM_H

#define TRUE 0xFF
#define FALSE 0x00
#define BUFFER_SIZE 256

#define DEV_MINOR 0
#define DEV_NR 1

#endif

And the infamous Makefile to compile it

MODULE_NAME = simplemem
EXTRA_CFLAGS += -DMODULE_NAME=\"$(MODULE_NAME)\"

# If KERNELRELEASE is defined, we've been invoked from the
# kernel build system and can use its language.
ifneq ($(KERNELRELEASE),)
  obj-m := $(MODULE_NAME).o
  $(MODULE_NAME)-objs :=

# Otherwise we were called directly from the command
# line; invoke the kernel build system.

else
  KERNELDIR ?= /lib/modules/$(shell uname -r)/build
  PWD := $(shell pwd)

# kunil: The whitespace in front of '$(MAKE)' line should be tab
# (not spaces). Otherwise, the GNU make will not be able to run
# this Makefile
#
default:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif

clean:
	rm -rf *.o *.ko *.mod.c .*.cmd .tmp_versions Module.markers modules.order Module.symvers
Advertisements

1 Comment »

  1. iam Korea student…
    i have question..

    figure)
    java application
    |
    linux device driver
    |
    linux driver

    how to structure?

    quetion 1) linux device driver -> C language??? or java language??
    if java language…what API??

    sorry, english is very difficulty

    Comment by Yeo — October 15, 2010 @ 8:57 pm


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: