/*
 * Implement the hello, world character device driver.
 * Sad, but true.
 * Create the device with:
 *
 * mknod /dev/hw c 42 0
 *
 * - Jon. Tombs Dec '93
 *
 * TODO:
 *  o- Ioctl to change the return string?
 *  o- Better error treatment.
 *  o- Support of minor numbers.
 *
 * Bjorn Ekwall added ugly typecast to avoid the dreaded "__moddi3" message...
 */

/* Kernel includes */

#include <linux/config.h>
#include <linux/module.h>

#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/major.h>
#include <asm/segment.h>
#include <linux/kernel.h>
#include <linux/signal.h>

/* the magic return string */
static char hw_reply[]	= "Hello World\n";

/* nice and high, but it is tunable: insmod drv_hello major=your_selection */
static int major = 42;

/*
 * The driver.
 */

/*
 * Read requests on the hello world device.
 * The magic string is copied byte by byte to user space.
 * Who cares about speed?
 */
static int hw_read(struct inode * node,struct file * file,char * buf,int count)
{
	int left;

	int hw_pos = ((int)file->f_pos) % (sizeof(hw_reply) - 1);
		   /* ^^^^^ Sorry (bj0rn faked this, but it works for f_pos < 2^32) */

	for (left = count; left > 0; left--) {
		put_fs_byte(hw_reply[hw_pos++],buf);
		buf++;
		if (hw_pos == (sizeof(hw_reply) - 1))
			hw_pos = 0;
	}
	file->f_pos+=count;
	return count;
}

/*
 * support seeks on the device
 */
static int hw_lseek(struct inode * inode, struct file * file, off_t offset, int orig)
{
	switch (orig) {
		case 0:
			file->f_pos = offset;
			return file->f_pos;
		case 1:
			file->f_pos += offset;
			return file->f_pos;

		default: /* how does the device have an end? */
			return -EINVAL;
	}
}


/*
 * Our special open code.
 * MOD_INC_USE_COUNT make sure that the driver memory is not freed
 * while the device is in use.
 */
static int
hw_open( struct inode* ino, struct file* filep)
{
	MOD_INC_USE_COUNT;
	return 0;   
}

/*
 * Now decrement the use count.
 */
static void
hw_close( struct inode* ino, struct file* filep)
{
	MOD_DEC_USE_COUNT;
}

static struct file_operations hw_fops = {
	hw_lseek,
	hw_read,
	NULL,
	NULL,		/* hw_readdir */
	NULL,		/* hw_select */
	NULL,		/* hw_ioctl */
	NULL,
	hw_open,
	hw_close,
	NULL		/* fsync */
};


/*
 * And now the modules code and kernel interface.
 */



#ifdef __cplusplus
extern "C" {
#endif

extern int printk(const char* fmt, ...);


int
init_module( void)
{
	printk( "drv_hello.c:  init_module called\n");
	if (register_chrdev(major, "hw", &hw_fops)) {
		printk("register_chrdev failed: goodbye world :-(\n");
		return -EIO;
	}
	else
		printk("Hello, world\n");

	return 0;
}

void
cleanup_module(void)
{
	printk("drv_hello.c:  cleanup_module called\n");

	if (unregister_chrdev(major, "hw") != 0)
		printk("cleanup_module failed\n");
	else
		printk("cleanup_module succeeded\n");
}

#ifdef __cplusplus
}
#endif
