Linux Device Driver

Linux Device Driver(multi-reader-chardev)

    A. First Edition

    This is the assignment of comp444 which I act as TA and I am also learning from it.

    ¡¡

    B.The problem

In this programming assignment, your task is to write a kernel module for Linux. Your kernel module will implement a driver for the (pseudo) ASCII device (you can call it /dev/ASCII if one does not already exist) as defined in the following.

The module will respond to the standard open (), read (), write (), and close () file-system calls. If the first use after an open () is a read (), it will return "hello world" or a part of it, depending on the number of bytes requested. A process may write any set of characters to the device up to a limit of 100. Reads subsequent to a write will return the requested number of bytes, all of which are the same characters written by the previous write. The first read will return the first character stored by the previous write; the second read returns the second character, and so on, until a new write is made onto the device.

¡¡

¡¡

C.The idea of program
¡¡

Most of programmers may feel a bit dizzy when they don't know what device driver is. But if given enough, correct information, it is no more than other program except it is more difficult to debug and trace.

On big question is whether you allow multiple users to use your device at the same time? How do you handle their concurrent access. This little program demo one simple solution of multiple reading pointer for each opening session.

¡¡

The following is the developing process:

1. Environment setting. It is strongly recommended to use a standard "makefile" to ease configuration because you need to find the correct version of source code and correct header files because some headers should not be used by kernel developing.

    # If KERNELRELEASE is defined, we've been invoked from the
    # kernel build system and can use its language.
    ifneq ($(KERNELRELEASE),)
    	obj-m := chardev.o
    # 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)
    default:
    	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
    endif

    clean:
    	$(RM) .*.cmd *.o *.ko -r .tmp*

¡¡

There is several thing to notice.

i) shell uname -r will give you the folder name of current kernel source.

ii) this makefile first goes the kernel directory so that your source code "include" refers the header file in kernel source directory instead of "/usr/include/*".

iii) you should change the "chardev.o" to "mysource.o'.

iv) the final kernal module is "mysource.ko".

¡¡

2. Initialize and uninitialize function.

The int init_module(void); and void cleanup_module(void); are the starting and ending entry of your module.

You can write your initializing and uninitializing logic in them, like kfree etc.

¡¡

3. Install module and remove module.

Using "insmod mysource.ko" to install. And "rmmod mysource" to remove. "lsmod" will list all modules.

4. implement all the generic device functions like "open", "read' etc.

5. You should use the "major" number returned from "register_chardev" as a key to create your mounted device in "/dev/mysource".

mknod "/dev/mysource" -c major minor

6. Something you should remember that kernel address and user address cannot be directly referred. "put_user" and "copy_from_user" should be used. Others like printf by printk, malloc by kmalloc, free by kfree.

7. You should decide whether you allow multiple user access or not. If not, try to use a simple "flag" to prevent multiple access, but it is not perfect because of possible race condition. So, the best strategy is to make it thread-safe.

8. You should realize the device file is like standard file.

9. You cannot see the "printk" message from X window and you should open a new console by "ctl+alt+F6". If you access from internet by SSH, you can check "/var/???" (I forget!!!)

OK, thanks to my friend who answers my email about the path of log message and it is /var/log/messages. What a simple question!

cat /var/log/messages
¡¡

    D.The major functions

    ¡¡

    E.Further improvement

    ¡¡

    F.File listing

    1. chardev.c

    2. test.c

    ¡¡

    ¡¡

    ¡¡

    ¡¡

    file name: chardev.c

    /*
     *  chardev.c: Creates a read-only char device that says how many times
     *  you've read from the dev file
     */

    #include <linux/kernel.h>
    #include <linux/module.h>
    #include <linux/fs.h>
    #include <asm/uaccess.h>	/* for put_user */

    # define SEEK_SET       0       /* Seek from beginning of file.  */
    # define SEEK_CUR       1       /* Seek from current position.  */
    # define SEEK_END       2       /* Seek from end of file.  */

    #define SUCCESS 0
    #define DEVICE_NAME "chardev"	/* Dev name as it appears in /proc/devices   */
    #define BUF_LEN 100		/* Max length of the message from the device */

    /*
     *  Prototypes - this would normally go in a .h file
     */
    int init_module(void);
    void cleanup_module(void);

    static int device_open(struct inode *, struct file *);
    static int device_release(struct inode *, struct file *);
    static ssize_t device_read(struct file *, char *, size_t, loff_t *);
    static ssize_t device_write(struct file *, const char *, size_t, loff_t *);
    static loff_t device_seek(struct file* filp, loff_t offset, int flag);

    /*
     * Global variables are declared as static, so are global within the file.
     */

    static int Major;		/* Major number assigned to our device driver */
    static int Device_Open = 0;	/* Is device open?
    				 * Used to prevent multiple access to device */
    static char msg[BUF_LEN];	/* The msg the device will give when asked */
    static char *msg_Ptr;
    static int counter = 0;

    static const char* GreetingMsg=	"Hello world!\n";

    static struct file_operations fops = {
    	.read = device_read,
    	.write = device_write,
    	.open = device_open,
    	.release = device_release,
    	.llseek = device_seek
    };
    //static int open_counter=0;
    static int table[2]={0,1};

    /*
     * This function is called when the module is loaded
     */
    int init_module(void)
    {
            Major = register_chrdev(0, DEVICE_NAME, &fops);

    	if (Major < 0) {
    	  printk(KERN_ALERT "Registering char device failed with %d\n", Major);
    	  return Major;
    	}
    	printk(KERN_ALERT "Hello, world\n");
    	printk(KERN_ALERT "I was assigned major number %d. To talk to\n", Major);
    	printk(KERN_ALERT "the driver, create a dev file with\n");
    	printk(KERN_ALERT "'mknod /dev/%s c %d 0'.\n", DEVICE_NAME, Major);
    	printk(KERN_ALERT "Try various minor numbers. Try to cat and echo to\n");
    	printk(KERN_ALERT "the device file.\n");
    	printk(KERN_ALERT "Remove the device file and module when done.\n");

    	return SUCCESS;
    }

    /*
     * This function is called when the module is unloaded
     */
    void cleanup_module(void)
    {
    	/*
    	 * Unregister the device
    	 */
    	int ret = unregister_chrdev(Major, DEVICE_NAME);
    	if (ret < 0)
    		printk(KERN_ALERT "Error in unregister_chrdev: %d\n", ret);
    }

    /*
     * Methods
     */

    /*
     * Called when a process tries to open the device file, like
     * "cat /dev/mycharfile"
     */
    static int device_open(struct inode *inode, struct file *file)
    {
    	int i;
    	int *ptr;
    	printk(KERN_ALERT "I am in kernal open function.\n");

    	//if (Device_Open)
    	//	return -EBUSY;
    	//counter=0;

    	file->private_data=(void*)kmalloc(sizeof(int), GFP_KERNEL);
    	ptr =(int*)(file->private_data);
    	//file->f_pos
    	if (Device_Open==0)
    	{
    		sprintf(msg, GreetingMsg);
    		counter=strlen(GreetingMsg);
    	}
    	*ptr=counter;
    	printk(KERN_ALERT "*ptr=%d\n", *ptr);

    	//msg_Ptr = msg;
    	try_module_get(THIS_MODULE);
    	Device_Open++;

    	return SUCCESS;
    }

    static loff_t device_seek(struct file* filp, loff_t offset, int flag)
    {
    	int*ptr;
    	ptr=(int*)(filp->private_data)	;

    	switch (flag)
    	{
    	case SEEK_SET:
    		*ptr=counter-offset;
    		if (*ptr<0)
    		{
    			*ptr=0;
    		}
    		break;
    	case SEEK_CUR:
    		*ptr-=offset;
    		if (*ptr<0)
    		{
    			*ptr=0;
    		}
    		if (*ptr>counter)
    		{
    			*ptr=counter;
    		}
    		break;

    	case SEEK_END:
    		*ptr=offset;
    		if (*ptr>counter)
    		{
    			*ptr=counter;
    		}
    		break;
    	}
    	return	counter-*ptr;
    }

    /*
     * Called when a process closes the device file.
     */
    static int device_release(struct inode *inode, struct file *file)
    {
    	Device_Open--;		/* We're now ready for our next caller */
    	printk(KERN_ALERT "device release successful\n");

    	kfree(file->private_data);
    	/*
    	 * Decrement the usage count, or else once you opened the file, you'll
    	 * never get get rid of the module.
    	 */
    	module_put(THIS_MODULE);

    	return 0;
    }

    /*
     * Called when a process, which already opened the dev file, attempts to
     * read from it.
     */
    static ssize_t device_read(struct file *filp,	/* see include/linux/fs.h   */
    			   char *buffer,	/* buffer to fill with data */
    			   size_t length,	/* length of the buffer     */
    			   loff_t * offset)
    {
    	/*
    	 * Number of bytes actually written to the buffer
    	 */
    	int*ptr;
    	int bytes_read = 0;
    	printk(KERN_ALERT "I am in kernal read function and private=%d.\n", *((int*)filp->private_data));

    	/*
    	 * If we're at the end of the message,
    	 * return 0 signifying end of file
    	 */
    	ptr=(int*)filp->private_data;

    	printk(KERN_ALERT "*inside read *ptr=%d\n", *ptr);

    	/*
    	 * Actually put the data into the buffer
    	 */

    	if (counter==0)
    	{
    		return 0;
    	}

    	if (*ptr==0)
    	{
    		return 0;
    	}

    /*
    	while (length && counter && *msg_Ptr) {

    		/*
    		 * The buffer is in the user data segment, not the kernel
    		 * segment so "*" assignment won't work.  We have to use
    		 * put_user which copies data from the kernel data segment to
    		 * the user data segment.
    		 */
    /*
    		put_user(*(msg_Ptr++), buffer++);
    		length--;
    		counter--;
    		bytes_read++;
    	}
    */

    	msg_Ptr=msg+counter-(*ptr);
    	while (length && (*ptr) && *msg_Ptr) {

    		/*
    		 * The buffer is in the user data segment, not the kernel
    		 * segment so "*" assignment won't work.  We have to use
    		 * put_user which copies data from the kernel data segment to
    		 * the user data segment.
    		 */
    		put_user(*(msg_Ptr++), buffer++);
    		length--;
    		(*ptr)--;
    		bytes_read++;
    	}

    	/*
    	 * Most read functions return the number of bytes put into the buffer
    	 */
    	return bytes_read;
    }

    static ssize_t
    device_write(struct file *filp, const char *buff, size_t len, loff_t * off)
    {
    	//printk(KERN_ALERT "Sorry, this operation isn't supported.\n");
    	//return -EINVAL;
    	int*ptr;
    /*
    	msg_Ptr = buff;
    	try_module_get(THIS_MODULE);

    	return -EINVAL;
    */
    	printk(KERN_ALERT "I am in kernal write function.\n");
    	copy_from_user(msg, buff, len);

    	//msg_Ptr=msg;
    	counter=len;
    	ptr=(int*)(filp->private_data);
    	*ptr=len;
    	counter=len;
    	return len;

    }

    ¡¡

    ¡¡

    file name: test.c

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <linux/ext2_fs.h>
    #include <math.h>

    void main(){
      int fd,len;
      char buf[100];
      char cmd[100];

      if ((fd = open("/dev/chardev",O_RDWR))<0) printf("open disk error!");
      printf("my fd=%d\n", fd);
      int tempLen;
      while(1)
       {

            len=read(STDIN_FILENO, cmd, 100);
            cmd[len-1]='\0';

    	if (strcmp(cmd, "lseek")==0)
    	{
    	     len=read(STDIN_FILENO, cmd, 100);
                 cmd[len-1]='\0';
                 sscanf(cmd, "%d",&len);
    	     lseek(fd, len, SEEK_SET);
    	}

          	if (strcmp(cmd, "open")==0)
    	{
    		//only first print "hello".
    		tempLen=read(fd, &buf, 100);
    		buf[tempLen]='\0';
            	printf("%s\n", buf);
    	}
    	if (strcmp(cmd, "read")==0)
    	{
    	        len=read(STDIN_FILENO, cmd, 100);
            	cmd[len-1]='\0';
    		sscanf(cmd, "%d",&len);
    		//memset(buf, 0, 100);
    		buf[0]='\0';
    		tempLen=read(fd, buf, len);
    		buf[tempLen]='\0';

            	printf("readsize=%d:%s\n",tempLen, buf);
    	}
    	if (strcmp(cmd, "write")==0)
    	{
    	        len=read(STDIN_FILENO, buf, 100);//read from keyboard
            	buf[len-1]='\0';
    		//printf("%s len=%d\n", buf, len-1);
    		tempLen=write(fd, buf, len-1);
    		//tempLen=write(fd, buf, 5);
    		//read(fd, &buf, 100);
            	printf("write returns %d\n",tempLen);

    	}
            if (strcmp(cmd, "close")==0)
    	{
    		close(fd);
    		break;

    	}
    	buf[0]='\0';
    	//memset(buf, 0, 100);
       }
    }

    //Running result:

    ¡¡

    ¡¡

    ¡¡

    ¡¡

    ¡¡