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> --- Changes since v0: - Make the request ID 32 bits wide internally --- drivers/media/media-device.c | 175 +++++++++++++++++++++++++++++++++++++++++++ include/media/media-device.h | 41 ++++++++++ include/uapi/linux/media.h | 12 +++ 3 files changed, 228 insertions(+) diff --git a/drivers/media/media-device.c b/drivers/media/media-device.c index d483a34b6565..6270fe9e3db0 100644 --- a/drivers/media/media-device.c +++ b/drivers/media/media-device.c @@ -33,6 +33,7 @@ struct media_device_fh { struct media_devnode_fh fh; + struct list_head requests; }; static inline struct media_device_fh *media_device_fh(struct file *filp) @@ -41,6 +42,161 @@ static inline struct media_device_fh *media_device_fh(struct file *filp) } /* ----------------------------------------------------------------------------- + * Requests + */ + +/** + * media_device_request_find - Find a request based from its ID + * @mdev: The media device + * @reqid: The request ID + * + * Find and return the request associated with the given ID, or NULL if no such + * request exists. + * + * When the function returns a non-NULL 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, u16 reqid) +{ + struct media_device_request *req; + unsigned long flags; + bool found = false; + + spin_lock_irqsave(&mdev->req_lock, flags); + list_for_each_entry(req, &mdev->requests, list) { + if (req->id == reqid) { + kref_get(&req->kref); + found = true; + break; + } + } + spin_unlock_irqrestore(&mdev->req_lock, flags); + + return found ? req : NULL; +} +EXPORT_SYMBOL_GPL(media_device_request_find); + +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; + + 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_alloc(struct media_device *mdev, + struct media_device_fh *fh, + struct media_request_cmd *cmd) +{ + struct media_device_request *req; + unsigned long flags; + + req = mdev->ops->req_alloc(mdev); + if (!req) + return -ENOMEM; + + req->mdev = mdev; + kref_init(&req->kref); + + spin_lock_irqsave(&mdev->req_lock, flags); + req->id = ++mdev->req_id; + list_add_tail(&req->list, &mdev->requests); + list_add_tail(&req->fh_list, &fh->requests); + spin_unlock_irqrestore(&mdev->req_lock, flags); + + cmd->request = req->id; + return 0; +} + +static void media_device_request_delete(struct media_device *mdev, + struct media_device_request *req) +{ + unsigned long flags; + + spin_lock_irqsave(&mdev->req_lock, flags); + list_del(&req->list); + spin_unlock_irqrestore(&mdev->req_lock, flags); + + media_device_request_put(req); +} + +static long media_device_request_cmd(struct media_device *mdev, + struct media_device_fh *fh, + struct media_request_cmd __user *_cmd) +{ + struct media_device_request *req = NULL; + struct media_request_cmd cmd; + int ret; + + if (!mdev->ops || !mdev->ops->req_alloc || !mdev->ops->req_free) + return -ENOTTY; + + if (copy_from_user(&cmd, _cmd, sizeof(cmd))) + return -EFAULT; + + 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, fh, &cmd); + break; + + case MEDIA_REQ_CMD_DELETE: + media_device_request_delete(mdev, req); + ret = 0; + break; + + case MEDIA_REQ_CMD_APPLY: + if (!mdev->ops->req_apply) + return -ENOSYS; + + ret = mdev->ops->req_apply(mdev, req); + break; + + case MEDIA_REQ_CMD_QUEUE: + if (!mdev->ops->req_queue) + return -ENOSYS; + + ret = mdev->ops->req_queue(mdev, req); + break; + + default: + ret = -EINVAL; + break; + } + + if (req) + media_device_request_put(req); + + if (ret < 0) + return ret; + + if (copy_to_user(_cmd, &cmd, sizeof(cmd))) + return -EFAULT; + + return 0; +} + +/* ----------------------------------------------------------------------------- * Userspace API */ @@ -52,6 +208,7 @@ static int media_device_open(struct file *filp) if (!fh) return -ENOMEM; + INIT_LIST_HEAD(&fh->requests); filp->private_data = &fh->fh; return 0; @@ -60,6 +217,15 @@ static int media_device_open(struct file *filp) static int media_device_close(struct file *filp) { struct media_device_fh *fh = media_device_fh(filp); + struct media_device *mdev = to_media_device(fh->fh.devnode); + struct media_device_request *req, *next; + + spin_lock_irq(&mdev->req_lock); + list_for_each_entry_safe(req, next, &fh->requests, fh_list) { + list_del(&req->fh_list); + media_device_request_put(req); + } + spin_unlock_irq(&mdev->req_lock); kfree(fh); @@ -257,6 +423,7 @@ static long media_device_ioctl(struct file *filp, unsigned int cmd, { struct media_devnode *devnode = media_devnode_data(filp); struct media_device *dev = to_media_device(devnode); + struct media_device_fh *fh = media_device_fh(filp); long ret; switch (cmd) { @@ -284,6 +451,11 @@ static long media_device_ioctl(struct file *filp, unsigned int cmd, mutex_unlock(&dev->graph_mutex); break; + case MEDIA_IOC_REQUEST_CMD: + ret = media_device_request_cmd(dev, fh, + (struct media_request_cmd __user *)arg); + break; + default: ret = -ENOIOCTLCMD; } @@ -404,6 +576,9 @@ int __must_check __media_device_register(struct media_device *mdev, spin_lock_init(&mdev->lock); mutex_init(&mdev->graph_mutex); + spin_lock_init(&mdev->req_lock); + INIT_LIST_HEAD(&mdev->requests); + /* Register the device node. */ mdev->devnode.fops = &media_device_fops; mdev->devnode.parent = mdev->dev; diff --git a/include/media/media-device.h b/include/media/media-device.h index 7e6de4dbf497..bc003bedf087 100644 --- a/include/media/media-device.h +++ b/include/media/media-device.h @@ -23,6 +23,7 @@ #ifndef _MEDIA_DEVICE_H #define _MEDIA_DEVICE_H +#include <linux/kref.h> #include <linux/list.h> #include <linux/mutex.h> #include <linux/spinlock.h> @@ -31,14 +32,42 @@ #include <media/media-entity.h> struct device; +struct media_device; + +/** + * struct media_device_request - Media device request + * @id: Request ID + * @mdev: Media device this request belongs to + * @kref: Reference count + * @list: List entry in the media device requests list + * @fh_list: List entry in the media file handle requests list + */ +struct media_device_request { + u32 id; + struct media_device *mdev; + struct kref kref; + struct list_head list; + struct list_head fh_list; +}; /** * struct media_device_ops - Media device operations * @link_notify: Link state change notification callback + * @req_alloc: Allocate a request + * @req_free: Free a request + * @req_apply: Apply 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_apply)(struct media_device *mdev, + struct media_device_request *req); + int (*req_queue)(struct media_device *mdev, + struct media_device_request *req); }; /** @@ -55,6 +84,9 @@ struct media_device_ops { * @lock: Entities list lock * @graph_mutex: Entities graph operation lock * @ops: Operation handler callbacks + * @req_lock: Protects the req_id and requests fields + * @req_id: Last request ID that was allocated + * @requests: List of allocated requests * * This structure represents an abstract high-level media device. It allows easy * access to entities and provides basic media device-level support. The @@ -86,6 +118,10 @@ struct media_device { struct mutex graph_mutex; const struct media_device_ops *ops; + + spinlock_t req_lock; + u32 req_id; + struct list_head requests; }; /* Supported link_notify @notification values. */ @@ -108,4 +144,9 @@ void media_device_unregister_entity(struct media_entity *entity); #define media_device_for_each_entity(entity, mdev) \ list_for_each_entry(entity, &(mdev)->entities, list) +struct media_device_request * +media_device_request_find(struct media_device *mdev, u16 reqid); +void media_device_request_get(struct media_device_request *req); +void media_device_request_put(struct media_device_request *req); + #endif diff --git a/include/uapi/linux/media.h b/include/uapi/linux/media.h index 4e816be3de39..fd8887a03988 100644 --- a/include/uapi/linux/media.h +++ b/include/uapi/linux/media.h @@ -167,9 +167,21 @@ struct media_links_enum { __u32 reserved[4]; }; +#define MEDIA_REQ_CMD_ALLOC 0 +#define MEDIA_REQ_CMD_DELETE 1 +#define MEDIA_REQ_CMD_APPLY 2 +#define MEDIA_REQ_CMD_QUEUE 3 + +struct media_request_cmd { + __u32 cmd; + __u32 request; + __u32 reserved[9]; +}; + #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_REQUEST_CMD _IOWR('|', 0x04, struct media_request_cmd) #endif /* __LINUX_MEDIA_H */ -- 2.4.10