char driver IOCTL - MarekBykowski/readme GitHub Wiki

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/poll.h>
#include <linux/mutex.h>
#include <linux/wait.h>
#include <linux/version.h>

#define DEVICE_NAME "mychardev"
#define CLASS_NAME  "mychar"

#define MY_IOCTL_RESET _IO('M', 0x01)

struct mydev {
	struct cdev cdev;
	struct mutex lock;
	wait_queue_head_t wq;

	char buffer[256];
	size_t len;
	bool data_ready;
};

static dev_t devno;
static struct class *myclass;
static struct mydev mydevice;

/* ---------- file operations ---------- */

static int my_open(struct inode *inode, struct file *file)
{
	struct mydev *d = container_of(inode->i_cdev, struct mydev, cdev);
	file->private_data = d;
	return 0;
}

static ssize_t my_read(struct file *file, char __user *buf,
		       size_t count, loff_t *ppos)
{
	struct mydev *d = file->private_data;
	ssize_t ret;

	if (*ppos > 0)
		return 0;

	if (wait_event_interruptible(d->wq,
	    READ_ONCE(d->data_ready)))
		return -ERESTARTSYS;

	mutex_lock(&d->lock);

	if (count > d->len)
		count = d->len;

	if (copy_to_user(buf, d->buffer, count)) {
		ret = -EFAULT;
		goto out;
	}

	d->data_ready = false;
	*ppos += count;
	ret = count;

out:
	mutex_unlock(&d->lock);
	return ret;
}

static ssize_t my_write(struct file *file, const char __user *buf,
			size_t count, loff_t *ppos)
{
	struct mydev *d = file->private_data;

	if (count > sizeof(d->buffer))
		count = sizeof(d->buffer);

	mutex_lock(&d->lock);

	if (copy_from_user(d->buffer, buf, count)) {
		mutex_unlock(&d->lock);
		return -EFAULT;
	}

	d->len = count;
	d->data_ready = true;

	mutex_unlock(&d->lock);
	wake_up_interruptible(&d->wq);

	return count;
}

static __poll_t my_poll(struct file *file, poll_table *wait)
{
	struct mydev *d = file->private_data;
	__poll_t mask = 0;

	poll_wait(file, &d->wq, wait);

	if (READ_ONCE(d->data_ready))
		mask |= POLLIN | POLLRDNORM;

	return mask;
}

static long my_ioctl(struct file *file, unsigned int cmd,
		     unsigned long arg)
{
	struct mydev *d = file->private_data;

	switch (cmd) {
	case MY_IOCTL_RESET:
		mutex_lock(&d->lock);
		d->len = 0;
		d->data_ready = false;
		mutex_unlock(&d->lock);
		return 0;
	default:
		return -ENOTTY;
	}
}

static const struct file_operations my_fops = {
	.owner          = THIS_MODULE,
	.open           = my_open,
	.read           = my_read,
	.write          = my_write,
	.poll           = my_poll,
	.unlocked_ioctl = my_ioctl,
	.llseek         = no_llseek,
};

/* ---------- module init / exit ---------- */

static int __init my_init(void)
{
	int ret;

	ret = alloc_chrdev_region(&devno, 0, 1, DEVICE_NAME);
	if (ret)
		return ret;

	cdev_init(&mydevice.cdev, &my_fops);
	mydevice.cdev.owner = THIS_MODULE;

	mutex_init(&mydevice.lock);
	init_waitqueue_head(&mydevice.wq);

	ret = cdev_add(&mydevice.cdev, devno, 1);
	if (ret)
		goto err_unregister;

#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 0, 0)
	myclass = class_create(THIS_MODULE, CLASS_NAME);
#else
	myclass = class_create(CLASS_NAME);
#endif
	if (IS_ERR(myclass)) {
		ret = PTR_ERR(myclass);
		goto err_cdev;
	}

	if (IS_ERR(device_create(myclass, NULL, devno, NULL,
				  DEVICE_NAME "0"))) {
		ret = -ENOMEM;
		goto err_class;
	}

	pr_info("mychardev: loaded (%d:%d)\n",
		MAJOR(devno), MINOR(devno));
	return 0;

err_class:
	class_destroy(myclass);
err_cdev:
	cdev_del(&mydevice.cdev);
err_unregister:
	unregister_chrdev_region(devno, 1);
	return ret;
}

static void __exit my_exit(void)
{
	device_destroy(myclass, devno);
	class_destroy(myclass);
	cdev_del(&mydevice.cdev);
	unregister_chrdev_region(devno, 1);
	pr_info("mychardev: unloaded\n");
}

module_init(my_init);
module_exit(my_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("example");
MODULE_DESCRIPTION("Polished character device with ioctl (LKM)");
⚠️ **GitHub.com Fallback** ⚠️