2021-07-22 11:25:32 +08:00
|
|
|
/*
|
2021-08-08 01:24:59 +08:00
|
|
|
* ioctl.c
|
2021-07-22 11:25:32 +08:00
|
|
|
*/
|
2021-07-22 06:58:13 +08:00
|
|
|
#include <linux/cdev.h>
|
|
|
|
#include <linux/fs.h>
|
2021-07-22 06:35:24 +08:00
|
|
|
#include <linux/init.h>
|
2021-07-22 06:58:13 +08:00
|
|
|
#include <linux/ioctl.h>
|
2021-07-22 06:35:24 +08:00
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/uaccess.h>
|
2024-04-16 06:19:52 +08:00
|
|
|
#include <linux/version.h>
|
2021-07-22 06:35:24 +08:00
|
|
|
|
|
|
|
struct ioctl_arg {
|
|
|
|
unsigned int val;
|
|
|
|
};
|
|
|
|
|
2023-09-05 01:59:31 +08:00
|
|
|
/* Documentation/userspace-api/ioctl/ioctl-number.rst */
|
2021-07-22 06:35:24 +08:00
|
|
|
#define IOC_MAGIC '\x66'
|
|
|
|
|
2021-07-22 06:58:13 +08:00
|
|
|
#define IOCTL_VALSET _IOW(IOC_MAGIC, 0, struct ioctl_arg)
|
|
|
|
#define IOCTL_VALGET _IOR(IOC_MAGIC, 1, struct ioctl_arg)
|
|
|
|
#define IOCTL_VALGET_NUM _IOR(IOC_MAGIC, 2, int)
|
|
|
|
#define IOCTL_VALSET_NUM _IOW(IOC_MAGIC, 3, int)
|
2021-07-22 06:35:24 +08:00
|
|
|
|
|
|
|
#define IOCTL_VAL_MAXNR 3
|
|
|
|
#define DRIVER_NAME "ioctltest"
|
|
|
|
|
|
|
|
static unsigned int test_ioctl_major = 0;
|
|
|
|
static unsigned int num_of_dev = 1;
|
|
|
|
static struct cdev test_ioctl_cdev;
|
|
|
|
static int ioctl_num = 0;
|
|
|
|
|
|
|
|
struct test_ioctl_data {
|
|
|
|
unsigned char val;
|
|
|
|
rwlock_t lock;
|
|
|
|
};
|
|
|
|
|
2021-09-02 15:15:07 +08:00
|
|
|
static long test_ioctl_ioctl(struct file *filp, unsigned int cmd,
|
2021-07-22 06:58:13 +08:00
|
|
|
unsigned long arg)
|
|
|
|
{
|
|
|
|
struct test_ioctl_data *ioctl_data = filp->private_data;
|
2021-07-22 06:35:24 +08:00
|
|
|
int retval = 0;
|
|
|
|
unsigned char val;
|
|
|
|
struct ioctl_arg data;
|
|
|
|
memset(&data, 0, sizeof(data));
|
|
|
|
|
|
|
|
switch (cmd) {
|
|
|
|
case IOCTL_VALSET:
|
2021-09-02 15:15:07 +08:00
|
|
|
if (copy_from_user(&data, (int __user *)arg, sizeof(data))) {
|
2021-07-22 06:35:24 +08:00
|
|
|
retval = -EFAULT;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
pr_alert("IOCTL set val:%x .\n", data.val);
|
|
|
|
write_lock(&ioctl_data->lock);
|
|
|
|
ioctl_data->val = data.val;
|
|
|
|
write_unlock(&ioctl_data->lock);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case IOCTL_VALGET:
|
|
|
|
read_lock(&ioctl_data->lock);
|
|
|
|
val = ioctl_data->val;
|
|
|
|
read_unlock(&ioctl_data->lock);
|
|
|
|
data.val = val;
|
|
|
|
|
2021-09-02 15:15:07 +08:00
|
|
|
if (copy_to_user((int __user *)arg, &data, sizeof(data))) {
|
2021-07-22 06:35:24 +08:00
|
|
|
retval = -EFAULT;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case IOCTL_VALGET_NUM:
|
2021-09-02 15:15:07 +08:00
|
|
|
retval = __put_user(ioctl_num, (int __user *)arg);
|
2021-07-22 06:35:24 +08:00
|
|
|
break;
|
|
|
|
|
|
|
|
case IOCTL_VALSET_NUM:
|
|
|
|
ioctl_num = arg;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
retval = -ENOTTY;
|
|
|
|
}
|
|
|
|
|
|
|
|
done:
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
2021-09-04 17:53:29 +08:00
|
|
|
static ssize_t test_ioctl_read(struct file *filp, char __user *buf,
|
|
|
|
size_t count, loff_t *f_pos)
|
2021-07-22 06:58:13 +08:00
|
|
|
{
|
|
|
|
struct test_ioctl_data *ioctl_data = filp->private_data;
|
2021-07-22 06:35:24 +08:00
|
|
|
unsigned char val;
|
|
|
|
int retval;
|
|
|
|
int i = 0;
|
2021-09-02 15:15:07 +08:00
|
|
|
|
2021-07-22 06:35:24 +08:00
|
|
|
read_lock(&ioctl_data->lock);
|
|
|
|
val = ioctl_data->val;
|
|
|
|
read_unlock(&ioctl_data->lock);
|
|
|
|
|
2021-07-22 06:58:13 +08:00
|
|
|
for (; i < count; i++) {
|
2021-07-22 06:35:24 +08:00
|
|
|
if (copy_to_user(&buf[i], &val, 1)) {
|
|
|
|
retval = -EFAULT;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
retval = count;
|
|
|
|
out:
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
2021-07-22 06:58:13 +08:00
|
|
|
static int test_ioctl_close(struct inode *inode, struct file *filp)
|
|
|
|
{
|
2021-07-22 06:35:24 +08:00
|
|
|
pr_alert("%s call.\n", __func__);
|
|
|
|
|
|
|
|
if (filp->private_data) {
|
|
|
|
kfree(filp->private_data);
|
|
|
|
filp->private_data = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-07-22 06:58:13 +08:00
|
|
|
static int test_ioctl_open(struct inode *inode, struct file *filp)
|
|
|
|
{
|
|
|
|
struct test_ioctl_data *ioctl_data;
|
2021-09-02 15:15:07 +08:00
|
|
|
|
2021-07-22 06:35:24 +08:00
|
|
|
pr_alert("%s call.\n", __func__);
|
|
|
|
ioctl_data = kmalloc(sizeof(struct test_ioctl_data), GFP_KERNEL);
|
|
|
|
|
2021-09-02 15:15:07 +08:00
|
|
|
if (ioctl_data == NULL)
|
2021-07-22 06:35:24 +08:00
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
rwlock_init(&ioctl_data->lock);
|
|
|
|
ioctl_data->val = 0xFF;
|
|
|
|
filp->private_data = ioctl_data;
|
2021-09-02 15:15:07 +08:00
|
|
|
|
2021-07-22 06:35:24 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-09-04 17:53:29 +08:00
|
|
|
static struct file_operations fops = {
|
2024-04-16 06:19:52 +08:00
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0)
|
2021-07-22 06:35:24 +08:00
|
|
|
.owner = THIS_MODULE,
|
2024-04-16 06:19:52 +08:00
|
|
|
#endif
|
2021-07-22 06:35:24 +08:00
|
|
|
.open = test_ioctl_open,
|
|
|
|
.release = test_ioctl_close,
|
|
|
|
.read = test_ioctl_read,
|
|
|
|
.unlocked_ioctl = test_ioctl_ioctl,
|
|
|
|
};
|
|
|
|
|
2023-07-05 09:44:21 +08:00
|
|
|
static int __init ioctl_init(void)
|
2021-07-22 06:58:13 +08:00
|
|
|
{
|
2021-09-13 14:51:20 +08:00
|
|
|
dev_t dev;
|
Fix a logic error in examples/ioctl.c (#137)
Change the "alloc_ret" and "cdev_ret" initial values to non-zero.
According to the source code, "alloc_chrdev_region" and "cdev_add"
return zero on success, and negative code on failure.
So, if the "alloc_chrdev_region" failed, the if condition becomes true,
then we will jump to the label "error" by goto, checking each return
value whether is a success state from both functions mentioned above
and dealing with it properly for exiting the process.
However, it checks the success state by comparing the return value
with zero (means success), and we got "cdev_ret == 0" is true from
the initial value zero, while we didn't execute "cdev_add" yet.
Hence, there was a logic error when the initial value is zero.
Co-authored-by: NOVBobLee <defru04002@gamil.com>
2022-02-21 00:58:47 +08:00
|
|
|
int alloc_ret = -1;
|
|
|
|
int cdev_ret = -1;
|
2021-07-22 06:35:24 +08:00
|
|
|
alloc_ret = alloc_chrdev_region(&dev, 0, num_of_dev, DRIVER_NAME);
|
|
|
|
|
2021-09-02 15:15:07 +08:00
|
|
|
if (alloc_ret)
|
2021-07-22 06:35:24 +08:00
|
|
|
goto error;
|
|
|
|
|
|
|
|
test_ioctl_major = MAJOR(dev);
|
|
|
|
cdev_init(&test_ioctl_cdev, &fops);
|
|
|
|
cdev_ret = cdev_add(&test_ioctl_cdev, dev, num_of_dev);
|
|
|
|
|
2021-09-02 15:15:07 +08:00
|
|
|
if (cdev_ret)
|
2021-07-22 06:35:24 +08:00
|
|
|
goto error;
|
|
|
|
|
2021-07-22 06:58:13 +08:00
|
|
|
pr_alert("%s driver(major: %d) installed.\n", DRIVER_NAME,
|
|
|
|
test_ioctl_major);
|
2021-07-22 06:35:24 +08:00
|
|
|
return 0;
|
|
|
|
error:
|
2021-09-02 15:15:07 +08:00
|
|
|
if (cdev_ret == 0)
|
2021-07-22 06:35:24 +08:00
|
|
|
cdev_del(&test_ioctl_cdev);
|
2021-09-02 15:15:07 +08:00
|
|
|
if (alloc_ret == 0)
|
2021-07-22 06:35:24 +08:00
|
|
|
unregister_chrdev_region(dev, num_of_dev);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2023-07-05 09:44:21 +08:00
|
|
|
static void __exit ioctl_exit(void)
|
2021-07-22 06:58:13 +08:00
|
|
|
{
|
2021-07-22 06:35:24 +08:00
|
|
|
dev_t dev = MKDEV(test_ioctl_major, 0);
|
2021-09-02 15:15:07 +08:00
|
|
|
|
2021-07-22 06:35:24 +08:00
|
|
|
cdev_del(&test_ioctl_cdev);
|
|
|
|
unregister_chrdev_region(dev, num_of_dev);
|
|
|
|
pr_alert("%s driver removed.\n", DRIVER_NAME);
|
|
|
|
}
|
|
|
|
|
|
|
|
module_init(ioctl_init);
|
|
|
|
module_exit(ioctl_exit);
|
|
|
|
|
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
MODULE_DESCRIPTION("This is test_ioctl module");
|