#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <asm/atomic.h>

MODULE_LICENSE("GPL");
dev_t device_number;
bool dynamic = true;
struct class *my_class;
static struct cdev my_cdev;
#define MAX_SIZE	1024
char buffer[MAX_SIZE];
static atomic_t device_available = ATOMIC_INIT(1);

static int msgdevice_open(struct inode *inode, struct file *file)
{
	pr_info("%s\n", __func__);
	//Returns 1 if the result is zero, else 0
	if (!atomic_dec_and_test(&device_available)) {
		atomic_inc(&device_available);
		return -EBUSY;
	}
	return 0;
}

static int msgdevice_release(struct inode *inode, struct file *file)
{
	pr_info("%s\n", __func__);
	atomic_inc(&device_available);
	return 0;
}

ssize_t msgdevice_read(struct file *file, char __user *user_buffer,
		size_t count, loff_t *offset)
{
	int retval = 0;
	size_t bytes;

	bytes = strlen(buffer) - (*offset); //how many bytes not yet sent?
	bytes = bytes > count ? count: bytes; 

	if (bytes) {
		retval = copy_to_user(user_buffer, buffer, strlen(buffer));
		if (!retval) {
			*offset = bytes;
		}
		else
			return retval;
	}
	return bytes;
}

ssize_t msgdevice_write(struct file *file, const char __user *user_buffer,
		size_t count, loff_t *offset)
{
	int retval;
	size_t bytes;

	bytes = count;
	if (bytes > (MAX_SIZE - 1))
		bytes = MAX_SIZE - 1;

	memset(buffer, 0, sizeof(buffer));
	retval = copy_from_user(buffer, user_buffer, bytes);
	if (!retval) {
		buffer[bytes] = '\0'; 
		pr_info("%s Data written:%s\n", __func__, buffer);
	}
	else {
		pr_info("%s: copy_from_user failed\t retval:%d\n",
				__func__, retval);
		return retval;
	}
	return bytes;
}

struct file_operations fops = {
	.owner = THIS_MODULE,
	.open = msgdevice_open,
	.release = msgdevice_release,
	.read = msgdevice_read,
	.write = msgdevice_write
};

static int msg_device_init(void)
{
	int retval;

	pr_info("%s: In init\n", __func__);
	if (dynamic) {
		retval = alloc_chrdev_region(&device_number, 0, 1, "embedded");
	}
	else {
		device_number = MKDEV(180, 0);
		retval = register_chrdev_region(device_number, 1, "embedded");
	}
	if (!retval) {
		pr_info("%s: Major Number:%d\t Minor Number:%d\n",
				__func__, MAJOR(device_number), MINOR(device_number));
		my_class = class_create(THIS_MODULE, "my_driver_class");
		cdev_init(&my_cdev, &fops);
		retval = cdev_add(&my_cdev, device_number, 1);
		if (retval) {
			pr_info("%s: Failed in adding cdev to subsystem "
					"retval:%d\n", __func__, retval);
		}
		else {
			device_create(my_class, NULL, device_number, NULL, "msg");
		}
	}
	else
		pr_err("%s: Failed in allocating device number "
				"Error:%d\n", __func__, retval);
	return retval;
}

static void msg_device_exit(void)
{
	cdev_del(&my_cdev);
	device_destroy(my_class, device_number);
	class_destroy(my_class);
	unregister_chrdev_region(device_number, 5);
	pr_info("%s: In exit\n", __func__);
}

module_init(msg_device_init);
module_exit(msg_device_exit);