18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * linux/drivers/char/raw.c 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Front-end raw character devices. These can be bound to any block 68c2ecf20Sopenharmony_ci * devices to provide genuine Unix raw character device semantics. 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * We reserve minor number 0 for a control interface. ioctl()s on this 98c2ecf20Sopenharmony_ci * device are used to bind the other minor numbers to block devices. 108c2ecf20Sopenharmony_ci */ 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_ci#include <linux/init.h> 138c2ecf20Sopenharmony_ci#include <linux/fs.h> 148c2ecf20Sopenharmony_ci#include <linux/major.h> 158c2ecf20Sopenharmony_ci#include <linux/blkdev.h> 168c2ecf20Sopenharmony_ci#include <linux/backing-dev.h> 178c2ecf20Sopenharmony_ci#include <linux/module.h> 188c2ecf20Sopenharmony_ci#include <linux/raw.h> 198c2ecf20Sopenharmony_ci#include <linux/capability.h> 208c2ecf20Sopenharmony_ci#include <linux/uio.h> 218c2ecf20Sopenharmony_ci#include <linux/cdev.h> 228c2ecf20Sopenharmony_ci#include <linux/device.h> 238c2ecf20Sopenharmony_ci#include <linux/mutex.h> 248c2ecf20Sopenharmony_ci#include <linux/gfp.h> 258c2ecf20Sopenharmony_ci#include <linux/compat.h> 268c2ecf20Sopenharmony_ci#include <linux/vmalloc.h> 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci#include <linux/uaccess.h> 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_cistruct raw_device_data { 318c2ecf20Sopenharmony_ci dev_t binding; 328c2ecf20Sopenharmony_ci struct block_device *bdev; 338c2ecf20Sopenharmony_ci int inuse; 348c2ecf20Sopenharmony_ci}; 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_cistatic struct class *raw_class; 378c2ecf20Sopenharmony_cistatic struct raw_device_data *raw_devices; 388c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(raw_mutex); 398c2ecf20Sopenharmony_cistatic const struct file_operations raw_ctl_fops; /* forward declaration */ 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_cistatic int max_raw_minors = CONFIG_MAX_RAW_DEVS; 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_cimodule_param(max_raw_minors, int, 0); 448c2ecf20Sopenharmony_ciMODULE_PARM_DESC(max_raw_minors, "Maximum number of raw devices (1-65536)"); 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci/* 478c2ecf20Sopenharmony_ci * Open/close code for raw IO. 488c2ecf20Sopenharmony_ci * 498c2ecf20Sopenharmony_ci * We just rewrite the i_mapping for the /dev/raw/rawN file descriptor to 508c2ecf20Sopenharmony_ci * point at the blockdev's address_space and set the file handle to use 518c2ecf20Sopenharmony_ci * O_DIRECT. 528c2ecf20Sopenharmony_ci * 538c2ecf20Sopenharmony_ci * Set the device's soft blocksize to the minimum possible. This gives the 548c2ecf20Sopenharmony_ci * finest possible alignment and has no adverse impact on performance. 558c2ecf20Sopenharmony_ci */ 568c2ecf20Sopenharmony_cistatic int raw_open(struct inode *inode, struct file *filp) 578c2ecf20Sopenharmony_ci{ 588c2ecf20Sopenharmony_ci const int minor = iminor(inode); 598c2ecf20Sopenharmony_ci struct block_device *bdev; 608c2ecf20Sopenharmony_ci int err; 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci if (minor == 0) { /* It is the control device */ 638c2ecf20Sopenharmony_ci filp->f_op = &raw_ctl_fops; 648c2ecf20Sopenharmony_ci return 0; 658c2ecf20Sopenharmony_ci } 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci pr_warn_ratelimited( 688c2ecf20Sopenharmony_ci "process %s (pid %d) is using the deprecated raw device\n" 698c2ecf20Sopenharmony_ci "support will be removed in Linux 5.14.\n", 708c2ecf20Sopenharmony_ci current->comm, current->pid); 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci mutex_lock(&raw_mutex); 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci /* 758c2ecf20Sopenharmony_ci * All we need to do on open is check that the device is bound. 768c2ecf20Sopenharmony_ci */ 778c2ecf20Sopenharmony_ci err = -ENODEV; 788c2ecf20Sopenharmony_ci if (!raw_devices[minor].binding) 798c2ecf20Sopenharmony_ci goto out; 808c2ecf20Sopenharmony_ci bdev = blkdev_get_by_dev(raw_devices[minor].binding, 818c2ecf20Sopenharmony_ci filp->f_mode | FMODE_EXCL, raw_open); 828c2ecf20Sopenharmony_ci if (IS_ERR(bdev)) { 838c2ecf20Sopenharmony_ci err = PTR_ERR(bdev); 848c2ecf20Sopenharmony_ci goto out; 858c2ecf20Sopenharmony_ci } 868c2ecf20Sopenharmony_ci err = set_blocksize(bdev, bdev_logical_block_size(bdev)); 878c2ecf20Sopenharmony_ci if (err) 888c2ecf20Sopenharmony_ci goto out1; 898c2ecf20Sopenharmony_ci filp->f_flags |= O_DIRECT; 908c2ecf20Sopenharmony_ci filp->f_mapping = bdev->bd_inode->i_mapping; 918c2ecf20Sopenharmony_ci if (++raw_devices[minor].inuse == 1) 928c2ecf20Sopenharmony_ci file_inode(filp)->i_mapping = 938c2ecf20Sopenharmony_ci bdev->bd_inode->i_mapping; 948c2ecf20Sopenharmony_ci filp->private_data = bdev; 958c2ecf20Sopenharmony_ci raw_devices[minor].bdev = bdev; 968c2ecf20Sopenharmony_ci mutex_unlock(&raw_mutex); 978c2ecf20Sopenharmony_ci return 0; 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ciout1: 1008c2ecf20Sopenharmony_ci blkdev_put(bdev, filp->f_mode | FMODE_EXCL); 1018c2ecf20Sopenharmony_ciout: 1028c2ecf20Sopenharmony_ci mutex_unlock(&raw_mutex); 1038c2ecf20Sopenharmony_ci return err; 1048c2ecf20Sopenharmony_ci} 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci/* 1078c2ecf20Sopenharmony_ci * When the final fd which refers to this character-special node is closed, we 1088c2ecf20Sopenharmony_ci * make its ->mapping point back at its own i_data. 1098c2ecf20Sopenharmony_ci */ 1108c2ecf20Sopenharmony_cistatic int raw_release(struct inode *inode, struct file *filp) 1118c2ecf20Sopenharmony_ci{ 1128c2ecf20Sopenharmony_ci const int minor= iminor(inode); 1138c2ecf20Sopenharmony_ci struct block_device *bdev; 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci mutex_lock(&raw_mutex); 1168c2ecf20Sopenharmony_ci bdev = raw_devices[minor].bdev; 1178c2ecf20Sopenharmony_ci if (--raw_devices[minor].inuse == 0) 1188c2ecf20Sopenharmony_ci /* Here inode->i_mapping == bdev->bd_inode->i_mapping */ 1198c2ecf20Sopenharmony_ci inode->i_mapping = &inode->i_data; 1208c2ecf20Sopenharmony_ci mutex_unlock(&raw_mutex); 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci blkdev_put(bdev, filp->f_mode | FMODE_EXCL); 1238c2ecf20Sopenharmony_ci return 0; 1248c2ecf20Sopenharmony_ci} 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci/* 1278c2ecf20Sopenharmony_ci * Forward ioctls to the underlying block device. 1288c2ecf20Sopenharmony_ci */ 1298c2ecf20Sopenharmony_cistatic long 1308c2ecf20Sopenharmony_ciraw_ioctl(struct file *filp, unsigned int command, unsigned long arg) 1318c2ecf20Sopenharmony_ci{ 1328c2ecf20Sopenharmony_ci struct block_device *bdev = filp->private_data; 1338c2ecf20Sopenharmony_ci return blkdev_ioctl(bdev, 0, command, arg); 1348c2ecf20Sopenharmony_ci} 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_cistatic int bind_set(int number, u64 major, u64 minor) 1378c2ecf20Sopenharmony_ci{ 1388c2ecf20Sopenharmony_ci dev_t dev = MKDEV(major, minor); 1398c2ecf20Sopenharmony_ci dev_t raw = MKDEV(RAW_MAJOR, number); 1408c2ecf20Sopenharmony_ci struct raw_device_data *rawdev; 1418c2ecf20Sopenharmony_ci int err = 0; 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci if (number <= 0 || number >= max_raw_minors) 1448c2ecf20Sopenharmony_ci return -EINVAL; 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci if (MAJOR(dev) != major || MINOR(dev) != minor) 1478c2ecf20Sopenharmony_ci return -EINVAL; 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci rawdev = &raw_devices[number]; 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci /* 1528c2ecf20Sopenharmony_ci * This is like making block devices, so demand the 1538c2ecf20Sopenharmony_ci * same capability 1548c2ecf20Sopenharmony_ci */ 1558c2ecf20Sopenharmony_ci if (!capable(CAP_SYS_ADMIN)) 1568c2ecf20Sopenharmony_ci return -EPERM; 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci /* 1598c2ecf20Sopenharmony_ci * For now, we don't need to check that the underlying 1608c2ecf20Sopenharmony_ci * block device is present or not: we can do that when 1618c2ecf20Sopenharmony_ci * the raw device is opened. Just check that the 1628c2ecf20Sopenharmony_ci * major/minor numbers make sense. 1638c2ecf20Sopenharmony_ci */ 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci if (MAJOR(dev) == 0 && dev != 0) 1668c2ecf20Sopenharmony_ci return -EINVAL; 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci mutex_lock(&raw_mutex); 1698c2ecf20Sopenharmony_ci if (rawdev->inuse) { 1708c2ecf20Sopenharmony_ci mutex_unlock(&raw_mutex); 1718c2ecf20Sopenharmony_ci return -EBUSY; 1728c2ecf20Sopenharmony_ci } 1738c2ecf20Sopenharmony_ci if (rawdev->binding) 1748c2ecf20Sopenharmony_ci module_put(THIS_MODULE); 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci rawdev->binding = dev; 1778c2ecf20Sopenharmony_ci if (!dev) { 1788c2ecf20Sopenharmony_ci /* unbind */ 1798c2ecf20Sopenharmony_ci device_destroy(raw_class, raw); 1808c2ecf20Sopenharmony_ci } else { 1818c2ecf20Sopenharmony_ci __module_get(THIS_MODULE); 1828c2ecf20Sopenharmony_ci device_destroy(raw_class, raw); 1838c2ecf20Sopenharmony_ci device_create(raw_class, NULL, raw, NULL, "raw%d", number); 1848c2ecf20Sopenharmony_ci } 1858c2ecf20Sopenharmony_ci mutex_unlock(&raw_mutex); 1868c2ecf20Sopenharmony_ci return err; 1878c2ecf20Sopenharmony_ci} 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_cistatic int bind_get(int number, dev_t *dev) 1908c2ecf20Sopenharmony_ci{ 1918c2ecf20Sopenharmony_ci if (number <= 0 || number >= max_raw_minors) 1928c2ecf20Sopenharmony_ci return -EINVAL; 1938c2ecf20Sopenharmony_ci *dev = raw_devices[number].binding; 1948c2ecf20Sopenharmony_ci return 0; 1958c2ecf20Sopenharmony_ci} 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci/* 1988c2ecf20Sopenharmony_ci * Deal with ioctls against the raw-device control interface, to bind 1998c2ecf20Sopenharmony_ci * and unbind other raw devices. 2008c2ecf20Sopenharmony_ci */ 2018c2ecf20Sopenharmony_cistatic long raw_ctl_ioctl(struct file *filp, unsigned int command, 2028c2ecf20Sopenharmony_ci unsigned long arg) 2038c2ecf20Sopenharmony_ci{ 2048c2ecf20Sopenharmony_ci struct raw_config_request rq; 2058c2ecf20Sopenharmony_ci dev_t dev; 2068c2ecf20Sopenharmony_ci int err; 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci switch (command) { 2098c2ecf20Sopenharmony_ci case RAW_SETBIND: 2108c2ecf20Sopenharmony_ci if (copy_from_user(&rq, (void __user *) arg, sizeof(rq))) 2118c2ecf20Sopenharmony_ci return -EFAULT; 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci return bind_set(rq.raw_minor, rq.block_major, rq.block_minor); 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci case RAW_GETBIND: 2168c2ecf20Sopenharmony_ci if (copy_from_user(&rq, (void __user *) arg, sizeof(rq))) 2178c2ecf20Sopenharmony_ci return -EFAULT; 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci err = bind_get(rq.raw_minor, &dev); 2208c2ecf20Sopenharmony_ci if (err) 2218c2ecf20Sopenharmony_ci return err; 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci rq.block_major = MAJOR(dev); 2248c2ecf20Sopenharmony_ci rq.block_minor = MINOR(dev); 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci if (copy_to_user((void __user *)arg, &rq, sizeof(rq))) 2278c2ecf20Sopenharmony_ci return -EFAULT; 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci return 0; 2308c2ecf20Sopenharmony_ci } 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci return -EINVAL; 2338c2ecf20Sopenharmony_ci} 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_ci#ifdef CONFIG_COMPAT 2368c2ecf20Sopenharmony_cistruct raw32_config_request { 2378c2ecf20Sopenharmony_ci compat_int_t raw_minor; 2388c2ecf20Sopenharmony_ci compat_u64 block_major; 2398c2ecf20Sopenharmony_ci compat_u64 block_minor; 2408c2ecf20Sopenharmony_ci}; 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_cistatic long raw_ctl_compat_ioctl(struct file *file, unsigned int cmd, 2438c2ecf20Sopenharmony_ci unsigned long arg) 2448c2ecf20Sopenharmony_ci{ 2458c2ecf20Sopenharmony_ci struct raw32_config_request __user *user_req = compat_ptr(arg); 2468c2ecf20Sopenharmony_ci struct raw32_config_request rq; 2478c2ecf20Sopenharmony_ci dev_t dev; 2488c2ecf20Sopenharmony_ci int err = 0; 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_ci switch (cmd) { 2518c2ecf20Sopenharmony_ci case RAW_SETBIND: 2528c2ecf20Sopenharmony_ci if (copy_from_user(&rq, user_req, sizeof(rq))) 2538c2ecf20Sopenharmony_ci return -EFAULT; 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_ci return bind_set(rq.raw_minor, rq.block_major, rq.block_minor); 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_ci case RAW_GETBIND: 2588c2ecf20Sopenharmony_ci if (copy_from_user(&rq, user_req, sizeof(rq))) 2598c2ecf20Sopenharmony_ci return -EFAULT; 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_ci err = bind_get(rq.raw_minor, &dev); 2628c2ecf20Sopenharmony_ci if (err) 2638c2ecf20Sopenharmony_ci return err; 2648c2ecf20Sopenharmony_ci 2658c2ecf20Sopenharmony_ci rq.block_major = MAJOR(dev); 2668c2ecf20Sopenharmony_ci rq.block_minor = MINOR(dev); 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_ci if (copy_to_user(user_req, &rq, sizeof(rq))) 2698c2ecf20Sopenharmony_ci return -EFAULT; 2708c2ecf20Sopenharmony_ci 2718c2ecf20Sopenharmony_ci return 0; 2728c2ecf20Sopenharmony_ci } 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_ci return -EINVAL; 2758c2ecf20Sopenharmony_ci} 2768c2ecf20Sopenharmony_ci#endif 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_cistatic const struct file_operations raw_fops = { 2798c2ecf20Sopenharmony_ci .read_iter = blkdev_read_iter, 2808c2ecf20Sopenharmony_ci .write_iter = blkdev_write_iter, 2818c2ecf20Sopenharmony_ci .fsync = blkdev_fsync, 2828c2ecf20Sopenharmony_ci .open = raw_open, 2838c2ecf20Sopenharmony_ci .release = raw_release, 2848c2ecf20Sopenharmony_ci .unlocked_ioctl = raw_ioctl, 2858c2ecf20Sopenharmony_ci .llseek = default_llseek, 2868c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 2878c2ecf20Sopenharmony_ci}; 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_cistatic const struct file_operations raw_ctl_fops = { 2908c2ecf20Sopenharmony_ci .unlocked_ioctl = raw_ctl_ioctl, 2918c2ecf20Sopenharmony_ci#ifdef CONFIG_COMPAT 2928c2ecf20Sopenharmony_ci .compat_ioctl = raw_ctl_compat_ioctl, 2938c2ecf20Sopenharmony_ci#endif 2948c2ecf20Sopenharmony_ci .open = raw_open, 2958c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 2968c2ecf20Sopenharmony_ci .llseek = noop_llseek, 2978c2ecf20Sopenharmony_ci}; 2988c2ecf20Sopenharmony_ci 2998c2ecf20Sopenharmony_cistatic struct cdev raw_cdev; 3008c2ecf20Sopenharmony_ci 3018c2ecf20Sopenharmony_cistatic char *raw_devnode(struct device *dev, umode_t *mode) 3028c2ecf20Sopenharmony_ci{ 3038c2ecf20Sopenharmony_ci return kasprintf(GFP_KERNEL, "raw/%s", dev_name(dev)); 3048c2ecf20Sopenharmony_ci} 3058c2ecf20Sopenharmony_ci 3068c2ecf20Sopenharmony_cistatic int __init raw_init(void) 3078c2ecf20Sopenharmony_ci{ 3088c2ecf20Sopenharmony_ci dev_t dev = MKDEV(RAW_MAJOR, 0); 3098c2ecf20Sopenharmony_ci int ret; 3108c2ecf20Sopenharmony_ci 3118c2ecf20Sopenharmony_ci if (max_raw_minors < 1 || max_raw_minors > 65536) { 3128c2ecf20Sopenharmony_ci pr_warn("raw: invalid max_raw_minors (must be between 1 and 65536), using %d\n", 3138c2ecf20Sopenharmony_ci CONFIG_MAX_RAW_DEVS); 3148c2ecf20Sopenharmony_ci max_raw_minors = CONFIG_MAX_RAW_DEVS; 3158c2ecf20Sopenharmony_ci } 3168c2ecf20Sopenharmony_ci 3178c2ecf20Sopenharmony_ci raw_devices = vzalloc(array_size(max_raw_minors, 3188c2ecf20Sopenharmony_ci sizeof(struct raw_device_data))); 3198c2ecf20Sopenharmony_ci if (!raw_devices) { 3208c2ecf20Sopenharmony_ci printk(KERN_ERR "Not enough memory for raw device structures\n"); 3218c2ecf20Sopenharmony_ci ret = -ENOMEM; 3228c2ecf20Sopenharmony_ci goto error; 3238c2ecf20Sopenharmony_ci } 3248c2ecf20Sopenharmony_ci 3258c2ecf20Sopenharmony_ci ret = register_chrdev_region(dev, max_raw_minors, "raw"); 3268c2ecf20Sopenharmony_ci if (ret) 3278c2ecf20Sopenharmony_ci goto error; 3288c2ecf20Sopenharmony_ci 3298c2ecf20Sopenharmony_ci cdev_init(&raw_cdev, &raw_fops); 3308c2ecf20Sopenharmony_ci ret = cdev_add(&raw_cdev, dev, max_raw_minors); 3318c2ecf20Sopenharmony_ci if (ret) 3328c2ecf20Sopenharmony_ci goto error_region; 3338c2ecf20Sopenharmony_ci raw_class = class_create(THIS_MODULE, "raw"); 3348c2ecf20Sopenharmony_ci if (IS_ERR(raw_class)) { 3358c2ecf20Sopenharmony_ci printk(KERN_ERR "Error creating raw class.\n"); 3368c2ecf20Sopenharmony_ci cdev_del(&raw_cdev); 3378c2ecf20Sopenharmony_ci ret = PTR_ERR(raw_class); 3388c2ecf20Sopenharmony_ci goto error_region; 3398c2ecf20Sopenharmony_ci } 3408c2ecf20Sopenharmony_ci raw_class->devnode = raw_devnode; 3418c2ecf20Sopenharmony_ci device_create(raw_class, NULL, MKDEV(RAW_MAJOR, 0), NULL, "rawctl"); 3428c2ecf20Sopenharmony_ci 3438c2ecf20Sopenharmony_ci return 0; 3448c2ecf20Sopenharmony_ci 3458c2ecf20Sopenharmony_cierror_region: 3468c2ecf20Sopenharmony_ci unregister_chrdev_region(dev, max_raw_minors); 3478c2ecf20Sopenharmony_cierror: 3488c2ecf20Sopenharmony_ci vfree(raw_devices); 3498c2ecf20Sopenharmony_ci return ret; 3508c2ecf20Sopenharmony_ci} 3518c2ecf20Sopenharmony_ci 3528c2ecf20Sopenharmony_cistatic void __exit raw_exit(void) 3538c2ecf20Sopenharmony_ci{ 3548c2ecf20Sopenharmony_ci device_destroy(raw_class, MKDEV(RAW_MAJOR, 0)); 3558c2ecf20Sopenharmony_ci class_destroy(raw_class); 3568c2ecf20Sopenharmony_ci cdev_del(&raw_cdev); 3578c2ecf20Sopenharmony_ci unregister_chrdev_region(MKDEV(RAW_MAJOR, 0), max_raw_minors); 3588c2ecf20Sopenharmony_ci} 3598c2ecf20Sopenharmony_ci 3608c2ecf20Sopenharmony_cimodule_init(raw_init); 3618c2ecf20Sopenharmony_cimodule_exit(raw_exit); 3628c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 363