#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)");