326 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Media device node4 *5 * Copyright (C) 2010 Nokia Corporation6 *7 * Based on drivers/media/video/v4l2_dev.c code authored by8 * Mauro Carvalho Chehab <mchehab@kernel.org> (version 2)9 * Alan Cox, <alan@lxorguk.ukuu.org.uk> (version 1)10 *11 * Contacts: Laurent Pinchart <laurent.pinchart@ideasonboard.com>12 * Sakari Ailus <sakari.ailus@iki.fi>13 *14 * --15 *16 * Generic media device node infrastructure to register and unregister17 * character devices using a dynamic major number and proper reference18 * counting.19 */20 21#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt22 23#include <linux/errno.h>24#include <linux/init.h>25#include <linux/module.h>26#include <linux/kernel.h>27#include <linux/kmod.h>28#include <linux/slab.h>29#include <linux/mm.h>30#include <linux/string.h>31#include <linux/types.h>32#include <linux/uaccess.h>33 34#include <media/media-devnode.h>35#include <media/media-device.h>36 37#define MEDIA_NUM_DEVICES 25638#define MEDIA_NAME "media"39 40static dev_t media_dev_t;41 42/*43 * Active devices44 */45static DEFINE_MUTEX(media_devnode_lock);46static DECLARE_BITMAP(media_devnode_nums, MEDIA_NUM_DEVICES);47 48/* Called when the last user of the media device exits. */49static void media_devnode_release(struct device *cd)50{51 struct media_devnode *devnode = to_media_devnode(cd);52 53 mutex_lock(&media_devnode_lock);54 /* Mark device node number as free */55 clear_bit(devnode->minor, media_devnode_nums);56 mutex_unlock(&media_devnode_lock);57 58 /* Release media_devnode and perform other cleanups as needed. */59 if (devnode->release)60 devnode->release(devnode);61 62 kfree(devnode);63 pr_debug("%s: Media Devnode Deallocated\n", __func__);64}65 66static const struct bus_type media_bus_type = {67 .name = MEDIA_NAME,68};69 70static ssize_t media_read(struct file *filp, char __user *buf,71 size_t sz, loff_t *off)72{73 struct media_devnode *devnode = media_devnode_data(filp);74 75 if (!devnode->fops->read)76 return -EINVAL;77 if (!media_devnode_is_registered(devnode))78 return -EIO;79 return devnode->fops->read(filp, buf, sz, off);80}81 82static ssize_t media_write(struct file *filp, const char __user *buf,83 size_t sz, loff_t *off)84{85 struct media_devnode *devnode = media_devnode_data(filp);86 87 if (!devnode->fops->write)88 return -EINVAL;89 if (!media_devnode_is_registered(devnode))90 return -EIO;91 return devnode->fops->write(filp, buf, sz, off);92}93 94static __poll_t media_poll(struct file *filp,95 struct poll_table_struct *poll)96{97 struct media_devnode *devnode = media_devnode_data(filp);98 99 if (!media_devnode_is_registered(devnode))100 return EPOLLERR | EPOLLHUP;101 if (!devnode->fops->poll)102 return DEFAULT_POLLMASK;103 return devnode->fops->poll(filp, poll);104}105 106static long107__media_ioctl(struct file *filp, unsigned int cmd, unsigned long arg,108 long (*ioctl_func)(struct file *filp, unsigned int cmd,109 unsigned long arg))110{111 struct media_devnode *devnode = media_devnode_data(filp);112 113 if (!ioctl_func)114 return -ENOTTY;115 116 if (!media_devnode_is_registered(devnode))117 return -EIO;118 119 return ioctl_func(filp, cmd, arg);120}121 122static long media_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)123{124 struct media_devnode *devnode = media_devnode_data(filp);125 126 return __media_ioctl(filp, cmd, arg, devnode->fops->ioctl);127}128 129#ifdef CONFIG_COMPAT130 131static long media_compat_ioctl(struct file *filp, unsigned int cmd,132 unsigned long arg)133{134 struct media_devnode *devnode = media_devnode_data(filp);135 136 return __media_ioctl(filp, cmd, arg, devnode->fops->compat_ioctl);137}138 139#endif /* CONFIG_COMPAT */140 141/* Override for the open function */142static int media_open(struct inode *inode, struct file *filp)143{144 struct media_devnode *devnode;145 int ret;146 147 /* Check if the media device is available. This needs to be done with148 * the media_devnode_lock held to prevent an open/unregister race:149 * without the lock, the device could be unregistered and freed between150 * the media_devnode_is_registered() and get_device() calls, leading to151 * a crash.152 */153 mutex_lock(&media_devnode_lock);154 devnode = container_of(inode->i_cdev, struct media_devnode, cdev);155 /* return ENXIO if the media device has been removed156 already or if it is not registered anymore. */157 if (!media_devnode_is_registered(devnode)) {158 mutex_unlock(&media_devnode_lock);159 return -ENXIO;160 }161 /* and increase the device refcount */162 get_device(&devnode->dev);163 mutex_unlock(&media_devnode_lock);164 165 filp->private_data = devnode;166 167 if (devnode->fops->open) {168 ret = devnode->fops->open(filp);169 if (ret) {170 put_device(&devnode->dev);171 filp->private_data = NULL;172 return ret;173 }174 }175 176 return 0;177}178 179/* Override for the release function */180static int media_release(struct inode *inode, struct file *filp)181{182 struct media_devnode *devnode = media_devnode_data(filp);183 184 if (devnode->fops->release)185 devnode->fops->release(filp);186 187 filp->private_data = NULL;188 189 /* decrease the refcount unconditionally since the release()190 return value is ignored. */191 put_device(&devnode->dev);192 193 return 0;194}195 196static const struct file_operations media_devnode_fops = {197 .owner = THIS_MODULE,198 .read = media_read,199 .write = media_write,200 .open = media_open,201 .unlocked_ioctl = media_ioctl,202#ifdef CONFIG_COMPAT203 .compat_ioctl = media_compat_ioctl,204#endif /* CONFIG_COMPAT */205 .release = media_release,206 .poll = media_poll,207};208 209int __must_check media_devnode_register(struct media_device *mdev,210 struct media_devnode *devnode,211 struct module *owner)212{213 int minor;214 int ret;215 216 /* Part 1: Find a free minor number */217 mutex_lock(&media_devnode_lock);218 minor = find_first_zero_bit(media_devnode_nums, MEDIA_NUM_DEVICES);219 if (minor == MEDIA_NUM_DEVICES) {220 mutex_unlock(&media_devnode_lock);221 pr_err("could not get a free minor\n");222 kfree(devnode);223 return -ENFILE;224 }225 226 set_bit(minor, media_devnode_nums);227 mutex_unlock(&media_devnode_lock);228 229 devnode->minor = minor;230 devnode->media_dev = mdev;231 232 /* Part 1: Initialize dev now to use dev.kobj for cdev.kobj.parent */233 devnode->dev.bus = &media_bus_type;234 devnode->dev.devt = MKDEV(MAJOR(media_dev_t), devnode->minor);235 devnode->dev.release = media_devnode_release;236 if (devnode->parent)237 devnode->dev.parent = devnode->parent;238 dev_set_name(&devnode->dev, "media%d", devnode->minor);239 device_initialize(&devnode->dev);240 241 /* Part 2: Initialize the character device */242 cdev_init(&devnode->cdev, &media_devnode_fops);243 devnode->cdev.owner = owner;244 kobject_set_name(&devnode->cdev.kobj, "media%d", devnode->minor);245 246 /* Part 3: Add the media and char device */247 set_bit(MEDIA_FLAG_REGISTERED, &devnode->flags);248 ret = cdev_device_add(&devnode->cdev, &devnode->dev);249 if (ret < 0) {250 clear_bit(MEDIA_FLAG_REGISTERED, &devnode->flags);251 pr_err("%s: cdev_device_add failed\n", __func__);252 goto cdev_add_error;253 }254 255 return 0;256 257cdev_add_error:258 mutex_lock(&media_devnode_lock);259 clear_bit(devnode->minor, media_devnode_nums);260 devnode->media_dev = NULL;261 mutex_unlock(&media_devnode_lock);262 263 put_device(&devnode->dev);264 return ret;265}266 267void media_devnode_unregister_prepare(struct media_devnode *devnode)268{269 /* Check if devnode was ever registered at all */270 if (!media_devnode_is_registered(devnode))271 return;272 273 mutex_lock(&media_devnode_lock);274 clear_bit(MEDIA_FLAG_REGISTERED, &devnode->flags);275 mutex_unlock(&media_devnode_lock);276}277 278void media_devnode_unregister(struct media_devnode *devnode)279{280 mutex_lock(&media_devnode_lock);281 /* Delete the cdev on this minor as well */282 cdev_device_del(&devnode->cdev, &devnode->dev);283 devnode->media_dev = NULL;284 mutex_unlock(&media_devnode_lock);285 286 put_device(&devnode->dev);287}288 289/*290 * Initialise media for linux291 */292static int __init media_devnode_init(void)293{294 int ret;295 296 pr_info("Linux media interface: v0.10\n");297 ret = alloc_chrdev_region(&media_dev_t, 0, MEDIA_NUM_DEVICES,298 MEDIA_NAME);299 if (ret < 0) {300 pr_warn("unable to allocate major\n");301 return ret;302 }303 304 ret = bus_register(&media_bus_type);305 if (ret < 0) {306 unregister_chrdev_region(media_dev_t, MEDIA_NUM_DEVICES);307 pr_warn("bus_register failed\n");308 return -EIO;309 }310 311 return 0;312}313 314static void __exit media_devnode_exit(void)315{316 bus_unregister(&media_bus_type);317 unregister_chrdev_region(media_dev_t, MEDIA_NUM_DEVICES);318}319 320subsys_initcall(media_devnode_init);321module_exit(media_devnode_exit)322 323MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");324MODULE_DESCRIPTION("Device node registration for media drivers");325MODULE_LICENSE("GPL");326