#FORMAT Plain
/*
 * tyler@agat.net
 *
 * Hello World Driver (intended to Linux Kernel 2.6(.12))
 *
 * Driver for the hello device. This driver is intended to show
 * and teach some basic kernel mechanics :
 * _ Linux Kernel Module programming
 * _ Driver programming
 * _ Debugfs operations
 * _ Semaphores Locking
 * _ Wait queues
 *
 * All that in a simple module. This module isn't very useful and I don't
 * know if someone will be interested.
 * However, to test it, you have to :
 * _ compile the module (I don't provide the Makefile, do it yourself :p)
 * _ load it
 * _ at the module loading, you will see a message : 
 *    "Major number X has been assigned to device hello"
 *    If you don't see it, have a look in the file /var/log/messages
 *    You now have to create the device node :
 *    mknod /dev/hello c X 0
 * _ you can now test the device driver and the hello device !:)   
 *
 * How to use the hello device ?
 * Once you've loaded the module, there is "Nothing" in the device. If you try to read
 * it, the process will block.
 * It will block until another process write some data in the device.
 * You can see the device's data by reading the device or by the debugfs file system.
 * You can write to the data by writing to the device or by the debugfs interface.
 * To reset the device, write "Nothing\n" in the device.
 *
 * I provide two userland programm to read and write to the hello device :
 * _ read_to_hello (which can only read 256 bytes)
 * _ write_to_hello
 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/poll.h>
#include <linux/errno.h>
#include <linux/debugfs.h>

#define DRIVER_AUTHOR "tyler@agat.net"
#define DRIVER_DESC "Hello World Driver"
#define DEVICE_NAME "hello"

#define NOTH_LEN 9

static int major;
static int count_user;
static char *hello_msg; 
static struct semaphore hello_sem;
static wait_queue_head_t attente;
static wait_queue_head_t inq;
static struct dentry *hello_dir;
static struct dentry *hello_dentry;

static int hello_open(struct inode *inode, struct file *file)
{
	down(&hello_sem);
	count_user++;
	up(&hello_sem);

	return 0;
}	

static int hello_release(struct inode *inode, struct file *file)
{
	down(&hello_sem);
	count_user--;
	up(&hello_sem);

	return 0;
}	

static ssize_t hello_read(struct file *filp, char __user *user_buf, size_t len, loff_t *offset)
{
	int bytes = 0;

	down(&hello_sem);
	while (strncmp(hello_msg, "Nothing\n", NOTH_LEN) == 0) {
		up(&hello_sem);
		printk(KERN_INFO "Process sleeps.\n");
		interruptible_sleep_on(&attente);
		printk(KERN_INFO "Process wakes up.\n");
		down(&hello_sem);
	}	
	bytes = copy_to_user(user_buf, hello_msg, len);
	if (bytes < 0) {
		up(&hello_sem);
		return -EFAULT;
	}
	up(&hello_sem);

	return bytes;
}	

static ssize_t hello_write(struct file *filp, const char __user *user_buf, size_t len, loff_t *offset)
{
	int bytes = 0;

	down(&hello_sem);
	if (hello_msg) {
		kfree(hello_msg);
		hello_msg = kmalloc(len+1, GFP_KERNEL);
		if (!hello_msg) {
			up(&hello_sem);
			return -ENOMEM;
		}
	}	 
	
      bytes = copy_from_user(hello_msg, user_buf, len);
	if (bytes < 0) {
		up(&hello_sem);
		return -EFAULT;
	}
	hello_msg[len] = '\0';
	up(&hello_sem);
	wake_up_interruptible(&attente);

	return bytes;
}

static unsigned int hello_poll(struct file *filp, poll_table *wait) 
{
	unsigned int mask = 0;

	poll_wait(filp, &inq, wait);
	if (strncmp(hello_msg, "Nothing\n", NOTH_LEN) != 0)
		mask |= POLLIN | POLLRDNORM;
	mask |= POLLOUT | POLLWRNORM;

	return mask;
}	

static loff_t hello_llseek(struct file *filp, loff_t off, int origin)  
{
	switch (origin) {
		default:
			off += filp->f_pos;
			break;
	}

	if (off < 0) {
		return -EINVAL;
	}

	filp->f_pos = off;

	return filp->f_pos;
}	

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

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

static ssize_t debug_hello_read(struct file *file, char __user *user_buf, size_t count, loff_t *off)
{
	int len;

	down(&hello_sem);
	len = copy_to_user(user_buf,hello_msg,count);
	up(&hello_sem);

	if (len < 0) {
		return -EFAULT;
	}

	return len;
}

static struct file_operations fops = {
	read: 	hello_read,
	write: 	hello_write,
	open: 	hello_open,
	release: hello_release,
	poll: 	hello_poll,	
	llseek: hello_llseek,
};		 

static struct file_operations debug_fops = {
	read:	debug_hello_read,
	open:	debug_hello_open,
	release: debug_hello_release,
};

int __init loading(void)
{      
        hello_msg = kmalloc(NOTH_LEN, GFP_KERNEL);
        if (!hello_msg) {
		return -ENOMEM;
        }
       
	sprintf(hello_msg, "Nothing\n");
       
	
        major = register_chrdev(major, DEVICE_NAME, &fops);

	if (major < 0) {
		printk(KERN_ERR "Cannot allocate a major number.\n");
		return major;
	}	

	printk(KERN_INFO "Major number %d has been assigned to device %s.\n", major, DEVICE_NAME);

        sema_init(&hello_sem, 1);
	init_waitqueue_head(&attente);
	init_waitqueue_head(&inq);

	hello_dir = debugfs_create_dir("hello_dir", NULL);
	hello_dentry = debugfs_create_file("hello", 0644, hello_dir, NULL, &debug_fops);

	return 0; 
}

void __exit unloading(void)
{
	int ret = 0;
	ret = unregister_chrdev(major, DEVICE_NAME);

	if (ret < 0) {
		printk(KERN_ERR "unregister_chrdev() error.\n");
	}	

	debugfs_remove(hello_dentry);
	debugfs_remove(hello_dir);

	kfree(hello_msg);
}	

module_init(loading);
module_exit(unloading);

MODULE_LICENSE("GPL");
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);