// SPDX-License-Identifier: GPL-2.0

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>

static int major;
static char text[64];

static ssize_t my_read (struct file *filp, char __user *user_buf, size_t length, loff_t *offset)
{
	/* if len + off is less than text size then size is = len, else it is size - offset */
	int not_copied, delta, to_copy = (length + *offset) < sizeof(text) ? length : (sizeof(text) - *offset);

	pr_info("hello_cdev - Read is called, we want to read %u bytes, but actually only coping %d bytes. The offset is %lld\n", length, to_copy, *offset);

	if(*offset >= sizeof(text))
		return 0;

	not_copied = copy_to_user(user_buf, &text[*offset], to_copy);
	delta = to_copy - not_copied;
	if(not_copied)
		pr_warn("hello_cdev - Could only copy %d bytes\n", delta);

	*offset += delta;
	return delta;
}

static ssize_t my_write (struct file *filp, const char __user *user_buf, size_t length, loff_t *offset)
{
	/* if len + off is less than text size then size is = len, else it is size - offset */
	int not_copied, delta, to_copy = (length + *offset) < sizeof(text) ? length : (sizeof(text) - *offset);

	pr_info("hello_cdev - Write is called, we want to write %u bytes, but actually only coping %d bytes. The offset is %lld\n", length, to_copy, *offset);

	if(*offset >= sizeof(text))
		return 0;

	not_copied = copy_from_user(&text[*offset], user_buf, to_copy);
	delta = to_copy - not_copied;
	if(not_copied)
		pr_warn("hello_cdev - Could only copy %d bytes\n", delta);

	*offset += delta;
	return delta;
}

static struct file_operations fops = {
	.read = my_read,
	.write = my_write
};

/**
 * @brief This function is called when the module is loaded into the kernel
 */
static int __init my_init(void)
{
	/**
	 * First argument 0 auto allocates major device number
	 * returns device number
	 * Enter a number eg 64 to allocate all minor num 0 - 255
	 * 0 returned if successfull
	 */

	major = register_chrdev(0, "hello_cdev", &fops);
	if (major < 0)
	{
		pr_err("hello_cdev - Error registering chrdev\n");
		return major;
	}
	pr_info("hello_cdev - Major device number: %d\n", major);
	return 0;
}

/**
 * @brief This function is called when the module is removed from the kernel
 */
static void __exit my_exit(void)
{
	pr_info("Goodbye, Kernel\n");
	unregister_chrdev(major, "hello_cdev");
}

module_init(my_init);
module_exit(my_exit);

/* Meta Information */
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("David D'Ulisse");
MODULE_DESCRIPTION("A simple character device driver");

