From: Laurent Pinchart <laurent.pinchart+renesas@xxxxxxxxxxxxxxxx> The request API allows bundling media device parameters with request objects and applying them atomically, either synchronously or asynchronously. Signed-off-by: Laurent Pinchart <laurent.pinchart+renesas@xxxxxxxxxxxxxxxx> Signed-off-by: Sakari Ailus <sakari.ailus@xxxxxxxxxxxxxxx> --- drivers/media/media-device.c | 366 ++++++++++++++++++++++++++++++++++++++++++- include/media/media-device.h | 71 ++++++++- include/uapi/linux/media.h | 10 ++ 3 files changed, 441 insertions(+), 6 deletions(-) diff --git a/drivers/media/media-device.c b/drivers/media/media-device.c index da63da1..41ec5ac 100644 --- a/drivers/media/media-device.c +++ b/drivers/media/media-device.c @@ -19,11 +19,13 @@ /* We need to access legacy defines from linux/media.h */ #define __NEED_MEDIA_LEGACY_API +#include <linux/anon_inodes.h> #include <linux/compat.h> #include <linux/export.h> -#include <linux/idr.h> +#include <linux/file.h> #include <linux/ioctl.h> #include <linux/media.h> +#include <linux/sched.h> #include <linux/slab.h> #include <linux/types.h> #include <linux/pci.h> @@ -35,6 +37,349 @@ #ifdef CONFIG_MEDIA_CONTROLLER +static const char *__request_state[] = { + "IDLE", + "QUEUED", + "COMPLETED", +}; + +const char * +media_device_request_state_str(enum media_device_request_state state) +{ + if (state < ARRAY_SIZE(__request_state)) + return __request_state[state]; + + return "UNKNOWN"; +} + +/* ----------------------------------------------------------------------------- + * Requests + */ + +void media_device_request_get(struct media_device_request *req) +{ + kref_get(&req->kref); +} +EXPORT_SYMBOL_GPL(media_device_request_get); + +static void media_device_request_release(struct kref *kref) +{ + struct media_device_request *req = + container_of(kref, struct media_device_request, kref); + struct media_device *mdev = req->mdev; + unsigned long flags; + + dev_dbg(mdev->dev, "request: release %s\n", req->debug_str); + + spin_lock_irqsave(&mdev->req_lock, flags); + list_del(&req->list); + spin_unlock_irqrestore(&mdev->req_lock, flags); + + mdev->ops->req_free(mdev, req); +} + +void media_device_request_put(struct media_device_request *req) +{ + kref_put(&req->kref, media_device_request_release); +} +EXPORT_SYMBOL_GPL(media_device_request_put); + +static int media_device_request_close(struct inode *inode, struct file *filp) +{ + struct media_device_request *req = filp->private_data; + + media_device_request_put(req); + + return 0; +} + +static const struct file_operations request_fops = { + .owner = THIS_MODULE, + .release = media_device_request_close, +}; + +/** + * media_device_request_find - Find a request based on the file descriptor + * @mdev: The media device + * @request: The request file handle + * + * Find and return the request associated with the given file descriptor, or + * an error if no such request exists. + * + * When the function returns a request it increases its reference count. The + * caller is responsible for releasing the reference by calling + * media_device_request_put() on the request. + */ +struct media_device_request * +media_device_request_find(struct media_device *mdev, int request) +{ + struct file *filp; + struct media_device_request *req; + + filp = fget(request); + if (!filp) + return ERR_PTR(-ENOENT); + + if (filp->f_op != &request_fops) + goto err_fput; + req = filp->private_data; + media_device_request_get(req); + + if (req->mdev != mdev) + goto err_kref_put; + + fput(filp); + + return req; + +err_kref_put: + media_device_request_put(req); + +err_fput: + fput(filp); + + return ERR_PTR(-EBADF); +} +EXPORT_SYMBOL_GPL(media_device_request_find); + +static int media_device_request_alloc(struct media_device *mdev, + struct file *filp, + struct media_request_cmd *cmd) +{ + struct media_device_request *req; +#ifdef CONFIG_DYNAMIC_DEBUG + char comm[TASK_COMM_LEN]; +#endif + unsigned long flags; + int fd; + int ret; + + fd = get_unused_fd_flags(O_CLOEXEC); + if (fd < 0) + return fd; + + filp = anon_inode_getfile("request", &request_fops, NULL, O_CLOEXEC); + if (IS_ERR(filp)) { + ret = PTR_ERR(filp); + goto err_put_fd; + } + + req = mdev->ops->req_alloc(mdev); + if (!req) { + ret = -ENOMEM; + goto err_fput; + } + + filp->private_data = req; + req->mdev = mdev; + req->state = MEDIA_DEVICE_REQUEST_STATE_IDLE; + kref_init(&req->kref); + + spin_lock_irqsave(&mdev->req_lock, flags); + list_add_tail(&req->req_list, &mdev->req_idle); + spin_unlock_irqrestore(&mdev->req_lock, flags); + + cmd->request = fd; + +#ifdef CONFIG_DYNAMIC_DEBUG + get_task_comm(comm, current); + snprintf(req->debug_str, sizeof(req->debug_str), "%s:%d", + comm, fd); +#endif + + dev_dbg(mdev->dev, "request: allocated %s\n", req->debug_str); + fd_install(fd, filp); + + return 0; + +err_fput: + fput(filp); + +err_put_fd: + put_unused_fd(fd); + + return ret; +} + +void media_device_request_complete(struct media_device *mdev, + struct media_device_request *req) +{ + unsigned long flags; + + spin_lock_irqsave(&mdev->req_lock, flags); + + if (req->state == MEDIA_DEVICE_REQUEST_STATE_IDLE) { + dev_dbg(mdev->dev, + "request: not completing an idle request %s\n", + req->debug_str); + spin_unlock_irqrestore(&mdev->req_lock, flags); + return; + } + + if (WARN_ON(req->state != MEDIA_DEVICE_REQUEST_STATE_QUEUED)) { + dev_dbg(mdev->dev, "request: can't delete %s, state %s\n", + req->debug_str, + media_device_request_state_str(req->state)); + spin_unlock_irqrestore(&mdev->req_lock, flags); + return; + } + + req->state = MEDIA_DEVICE_REQUEST_STATE_COMPLETE; + + spin_unlock_irqrestore(&mdev->req_lock, flags); + + media_device_request_put(req); +} +EXPORT_SYMBOL_GPL(media_device_request_complete); + +static int media_device_request_queue( + struct media_device *mdev, struct media_device_request *req, bool try) +{ + char *str = try ? "try" : "queue"; + unsigned long flags; + int ret = 0; + + dev_dbg(mdev->dev, "request: %s %s\n", str, req->debug_str); + + /* + * Ensure the request that was just validated is the one that gets + * queued next. + */ + mutex_lock(&mdev->req_queue_mutex); + + spin_lock_irqsave(&mdev->req_lock, flags); + if (req->state != MEDIA_DEVICE_REQUEST_STATE_IDLE) { + ret = -EINVAL; + dev_dbg(mdev->dev, + "request: unable to %s %s, request in state %s\n", + str, req->debug_str, + media_device_request_state_str(req->state)); + } else { + req->state = MEDIA_DEVICE_REQUEST_STATE_QUEUED; + } + spin_unlock_irqrestore(&mdev->req_lock, flags); + + if (ret) + goto err_unlock; + + ret = mdev->ops->req_validate(mdev, req); + if (ret || try) { + if (ret) + dev_dbg(mdev->dev, + "request: %s failed validation (%d)\n", + req->debug_str, ret); + goto err_set_idle; + } + + /* + * Obtain a reference to the request, release once complete (or there's + * an error). + */ + media_device_request_get(req); + spin_lock_irqsave(&mdev->req_lock, flags); + list_move(&req->list, &mdev->req_queued); + spin_unlock_irqrestore(&mdev->req_lock, flags); + + ret = mdev->ops->req_queue(mdev, req); + if (ret) { + dev_dbg(mdev->dev, "request: can't queue %s (%d)\n", + req->debug_str, ret); + goto err_put; + } + + mutex_unlock(&mdev->req_queue_mutex); + + return 0; + +err_put: + media_device_request_put(req); + +err_set_idle: + spin_lock_irqsave(&mdev->req_lock, flags); + req->state = MEDIA_DEVICE_REQUEST_STATE_IDLE; + spin_unlock_irqrestore(&mdev->req_lock, flags); + +err_unlock: + mutex_unlock(&mdev->req_queue_mutex); + + return ret; + +} + +static long media_device_request_cmd(struct media_device *mdev, + struct file *filp, + struct media_request_cmd *cmd) +{ + struct media_device_request *req = NULL; + int ret; + + if (!mdev->ops || !mdev->ops->req_alloc || !mdev->ops->req_free || + !mdev->ops->req_validate || !mdev->ops->req_queue) + return -ENOTTY; + + if (cmd->cmd != MEDIA_REQ_CMD_ALLOC) { + req = media_device_request_find(mdev, cmd->request); + if (!req) + return -EINVAL; + } + + switch (cmd->cmd) { + case MEDIA_REQ_CMD_ALLOC: + ret = media_device_request_alloc(mdev, filp, cmd); + break; + + case MEDIA_REQ_CMD_QUEUE: + ret = media_device_request_queue(mdev, req, false); + break; + + case MEDIA_REQ_CMD_TRY: + ret = media_device_request_queue(mdev, req, true); + break; + + default: + ret = -EINVAL; + break; + } + + if (req) + media_device_request_put(req); + + return ret < 0 ? ret : 0; +} + +/** + * media_device_request_alloc_simple - Callback to allocate memory for media + * request + * + * @mdev: The media device for which the request is to be allocated + * + * Allocate memory for a media request. Drivers are free to implement their own + * callbacks if more than just the size of the @struct media_device_request + * itself is needed. Driver's own data comes after @struct media_device_request. + */ +struct media_device_request * +media_device_request_alloc_simple(struct media_device *mdev) +{ + return kzalloc(sizeof(struct media_device_request), GFP_KERNEL); +} +EXPORT_SYMBOL_GPL(media_device_request_alloc_simple); + +/** + * media_device_request_alloc_free - Callback to release media request memory + * + * @mdev: The media device + * @req: The request which is to be released + * + * Release the media request memory. If more than this is needed to clean up a + * media request, drivers are free to implement their own callbacks. + */ +void media_device_request_free_simple(struct media_device *mdev, + struct media_device_request *req) +{ + kfree(req); +} +EXPORT_SYMBOL_GPL(media_device_request_free_simple); + /* ----------------------------------------------------------------------------- * Userspace API */ @@ -55,6 +400,7 @@ static int media_device_close(struct file *filp) } static int media_device_get_info(struct media_device *dev, + struct file *filp, struct media_device_info *info) { memset(info, 0, sizeof(*info)); @@ -94,6 +440,7 @@ static struct media_entity *find_entity(struct media_device *mdev, u32 id) } static long media_device_enum_entities(struct media_device *mdev, + struct file *filp, struct media_entity_desc *entd) { struct media_entity *ent; @@ -147,6 +494,7 @@ static void media_device_kpad_to_upad(const struct media_pad *kpad, } static long media_device_enum_links(struct media_device *mdev, + struct file *filp, struct media_links_enum *links) { struct media_entity *entity; @@ -196,6 +544,7 @@ static long media_device_enum_links(struct media_device *mdev, } static long media_device_setup_link(struct media_device *mdev, + struct file *filp, struct media_link_desc *linkd) { struct media_link *link = NULL; @@ -226,6 +575,7 @@ static long media_device_setup_link(struct media_device *mdev, } static long media_device_get_topology(struct media_device *mdev, + struct file *filp, struct media_v2_topology *topo) { struct media_entity *entity; @@ -390,7 +740,8 @@ static long copy_arg_to_user(void __user *uarg, void *karg, unsigned int cmd) #define MEDIA_IOC_SZ_ARG(__cmd, func, fl, alt_sz, from_user, to_user) \ [_IOC_NR(MEDIA_IOC_##__cmd)] = { \ .cmd = MEDIA_IOC_##__cmd, \ - .fn = (long (*)(struct media_device *, void *))func, \ + .fn = (long (*)(struct media_device *, \ + struct file *, void *))func, \ .flags = fl, \ .alt_arg_sizes = alt_sz, \ .arg_from_user = from_user, \ @@ -417,7 +768,7 @@ struct media_ioctl_info { * pointer is NULL. */ const unsigned short *alt_arg_sizes; - long (*fn)(struct media_device *dev, void *arg); + long (*fn)(struct media_device *dev, struct file *file, void *arg); long (*arg_from_user)(void *karg, void __user *uarg, unsigned int cmd); long (*arg_to_user)(void __user *uarg, void *karg, unsigned int cmd); }; @@ -428,6 +779,7 @@ static const struct media_ioctl_info ioctl_info[] = { MEDIA_IOC(ENUM_LINKS, media_device_enum_links, MEDIA_IOC_FL_GRAPH_MUTEX), MEDIA_IOC(SETUP_LINK, media_device_setup_link, MEDIA_IOC_FL_GRAPH_MUTEX), MEDIA_IOC(G_TOPOLOGY, media_device_get_topology, MEDIA_IOC_FL_GRAPH_MUTEX), + MEDIA_IOC(REQUEST_CMD, media_device_request_cmd, 0), }; #define MASK_IOC_SIZE(cmd) \ @@ -500,7 +852,7 @@ static long media_device_ioctl(struct file *filp, unsigned int cmd, if (info->flags & MEDIA_IOC_FL_GRAPH_MUTEX) mutex_lock(&dev->graph_mutex); - ret = info->fn(dev, karg); + ret = info->fn(dev, filp, karg); if (info->flags & MEDIA_IOC_FL_GRAPH_MUTEX) mutex_unlock(&dev->graph_mutex); @@ -540,7 +892,7 @@ static long media_device_enum_links32(struct media_device *mdev, links.pads = compat_ptr(pads_ptr); links.links = compat_ptr(links_ptr); - return media_device_enum_links(mdev, &links); + return media_device_enum_links(mdev, NULL, &links); } #define MEDIA_IOC_ENUM_LINKS32 _IOWR('|', 0x02, struct media_links_enum32) @@ -765,6 +1117,10 @@ int __must_check __media_device_register(struct media_device *mdev, if (!devnode) return -ENOMEM; + spin_lock_init(&mdev->req_lock); + INIT_LIST_HEAD(&mdev->req_idle); + INIT_LIST_HEAD(&mdev->req_queued); + /* Register the device node. */ mdev->devnode = devnode; devnode->fops = &media_device_fops; diff --git a/include/media/media-device.h b/include/media/media-device.h index bcc6ec4..0f32ceb 100644 --- a/include/media/media-device.h +++ b/include/media/media-device.h @@ -19,14 +19,51 @@ #ifndef _MEDIA_DEVICE_H #define _MEDIA_DEVICE_H +#include <linux/kref.h> +#include <linux/idr.h> #include <linux/list.h> #include <linux/mutex.h> #include <media/media-devnode.h> #include <media/media-entity.h> -struct ida; struct device; +struct ida; +struct media_device; + +enum media_device_request_state { + MEDIA_DEVICE_REQUEST_STATE_IDLE, + MEDIA_DEVICE_REQUEST_STATE_QUEUED, + MEDIA_DEVICE_REQUEST_STATE_COMPLETE, +}; + +/** + * media_device_request_state_str - Convert media request state to a string + * + * @state: The state of the media request + * + * Returns the string corresponding to a media request state. + */ +const char * +media_device_request_state_str(enum media_device_request_state state); + +/** + * struct media_device_request - Media device request + * @mdev: Media device this request belongs to + * @kref: Reference count + * @list: List entry in the media device requests list + * @debug_prefix: Prefix for debug messages (process name:fd) + * @state: The state of the request + */ +struct media_device_request { + struct media_device *mdev; + struct kref kref; + struct list_head list; +#ifdef CONFIG_DYNAMIC_DEBUG + char debug_str[TASK_COMM_LEN + 6]; +#endif + enum media_device_request_state state; +}; /** * struct media_entity_notify - Media Entity Notify @@ -50,10 +87,21 @@ struct media_entity_notify { * struct media_device_ops - Media device operations * @link_notify: Link state change notification callback. This callback is * called with the graph_mutex held. + * @req_alloc: Allocate a request + * @req_free: Free a request + * @req_validate: Validate a request + * @req_queue: Queue a request */ struct media_device_ops { int (*link_notify)(struct media_link *link, u32 flags, unsigned int notification); + struct media_device_request *(*req_alloc)(struct media_device *mdev); + void (*req_free)(struct media_device *mdev, + struct media_device_request *req); + int (*req_validate)(struct media_device *mdev, + struct media_device_request *req); + int (*req_queue)(struct media_device *mdev, + struct media_device_request *req); }; /** @@ -88,6 +136,11 @@ struct media_device_ops { * @disable_source: Disable Source Handler function pointer * * @ops: Operation handler callbacks + * @req_lock: Serialise access to @requests + * @req_queue_mutex: Serialise validating and queueing requests + * @req_idle: List of idle requests (@struct media_request.list) + * @req_queued: List of queued (and completed) requests (@struct + * media_request.list) * * This structure represents an abstract high-level media device. It allows easy * access to entities and provides basic media device-level support. The @@ -158,6 +211,11 @@ struct media_device { void (*disable_source)(struct media_entity *entity); const struct media_device_ops *ops; + + spinlock_t req_lock; + struct mutex req_queue_mutex; + struct list_head req_queued; + struct list_head req_idle; }; /* We don't need to include pci.h or usb.h here */ @@ -475,4 +533,15 @@ static inline void __media_device_usb_init(struct media_device *mdev, #define media_device_usb_init(mdev, udev, name) \ __media_device_usb_init(mdev, udev, name, KBUILD_MODNAME) +struct media_device_request * +media_device_request_find(struct media_device *mdev, int request); +void media_device_request_get(struct media_device_request *req); +void media_device_request_put(struct media_device_request *req); +void media_device_request_complete(struct media_device *mdev, + struct media_device_request *req); + +struct media_device_request * +media_device_request_alloc_simple(struct media_device *mdev); +void media_device_request_free_simple(struct media_device *mdev, + struct media_device_request *req); #endif diff --git a/include/uapi/linux/media.h b/include/uapi/linux/media.h index c7e9a5c..9bc397b 100644 --- a/include/uapi/linux/media.h +++ b/include/uapi/linux/media.h @@ -342,11 +342,21 @@ struct media_v2_topology { /* ioctls */ +#define MEDIA_REQ_CMD_ALLOC 0 +#define MEDIA_REQ_CMD_TRY 1 +#define MEDIA_REQ_CMD_QUEUE 2 + +struct __attribute__ ((packed)) media_request_cmd { + __u32 cmd; + __s32 request; +}; + #define MEDIA_IOC_DEVICE_INFO _IOWR('|', 0x00, struct media_device_info) #define MEDIA_IOC_ENUM_ENTITIES _IOWR('|', 0x01, struct media_entity_desc) #define MEDIA_IOC_ENUM_LINKS _IOWR('|', 0x02, struct media_links_enum) #define MEDIA_IOC_SETUP_LINK _IOWR('|', 0x03, struct media_link_desc) #define MEDIA_IOC_G_TOPOLOGY _IOWR('|', 0x04, struct media_v2_topology) +#define MEDIA_IOC_REQUEST_CMD _IOWR('|', 0x05, struct media_request_cmd) #if !defined(__KERNEL__) || defined(__NEED_MEDIA_LEGACY_API) -- 2.7.4