@@ -25,7 +25,6 @@
#include <linux/compat.h>
#include <linux/export.h>
-#include <linux/idr.h>
#include <linux/ioctl.h>
#include <linux/media.h>
#include <linux/slab.h>
@@ -41,6 +40,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)
@@ -49,6 +49,192 @@ 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);
+
+ if (!found) {
+ dev_dbg(mdev->dev,
+ "request: can't find %u\n", reqid);
+ return NULL;
+ }
+
+ return req;
+}
+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;
+
+ dev_dbg(mdev->dev, "release request %u\n", req->id);
+
+ ida_simple_remove(&mdev->req_ids, req->id);
+
+ 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 file *filp,
+ struct media_request_cmd *cmd)
+{
+ struct media_device_fh *fh = media_device_fh(filp);
+ struct media_device_request *req;
+ unsigned long flags;
+ int id = ida_simple_get(&mdev->req_ids, 1, 0, GFP_KERNEL);
+ int ret;
+
+ if (id < 0) {
+ dev_dbg(mdev->dev, "request: unable to obtain new id\n");
+ return id;
+ }
+
+ req = mdev->ops->req_alloc(mdev);
+ if (!req) {
+ ret = -ENOMEM;
+ goto out_ida_simple_remove;
+ }
+
+ req->mdev = mdev;
+ req->id = id;
+ req->filp = filp;
+ kref_init(&req->kref);
+
+ spin_lock_irqsave(&mdev->req_lock, flags);
+ 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;
+
+ dev_dbg(mdev->dev, "request: allocated id %u\n", req->id);
+
+ return 0;
+
+out_ida_simple_remove:
+ ida_simple_remove(&mdev->req_ids, id);
+
+ return ret;
+}
+
+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);
+ if (req->filp) {
+ /*
+ * If the file handle is gone by now the
+ * request has already been deleted from the
+ * two lists.
+ */
+ list_del(&req->list);
+ list_del(&req->fh_list);
+ req->filp = NULL;
+ }
+ spin_unlock_irqrestore(&mdev->req_lock, flags);
+
+ media_device_request_put(req);
+}
+
+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)
+ 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_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;
+
+ return 0;
+}
+
+/* -----------------------------------------------------------------------------
* Userspace API
*/
@@ -65,6 +251,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;
@@ -73,6 +260,21 @@ 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);
+
+ spin_lock_irq(&mdev->req_lock);
+ while (!list_empty(&fh->requests)) {
+ struct media_device_request *req =
+ list_first_entry(&fh->requests, typeof(*req), fh_list);
+
+ list_del(&req->list);
+ list_del(&req->fh_list);
+ req->filp = NULL;
+ spin_unlock_irq(&mdev->req_lock);
+ media_device_request_put(req);
+ spin_lock_irq(&mdev->req_lock);
+ }
+ spin_unlock_irq(&mdev->req_lock);
kfree(fh);
@@ -80,6 +282,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));
@@ -119,6 +322,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;
@@ -172,6 +376,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;
@@ -220,6 +425,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;
@@ -248,6 +454,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;
@@ -417,7 +624,8 @@ static long copy_arg_to_user_nop(void __user *uarg, void *karg,
#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, \
@@ -438,7 +646,7 @@ static long copy_arg_to_user_nop(void __user *uarg, void *karg,
/* the table is indexed by _IOC_NR(cmd) */
struct media_ioctl_info {
unsigned int cmd;
- long (*fn)(struct media_device *dev, void *arg);
+ long (*fn)(struct media_device *dev, struct file *file, void *arg);
unsigned short flags;
const unsigned short *alt_arg_sizes;
long (*arg_from_user)(void *karg, void __user *uarg, unsigned int cmd);
@@ -513,7 +721,7 @@ static long __media_device_ioctl(
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);
@@ -534,6 +742,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),
};
static long media_device_ioctl(struct file *filp, unsigned int cmd,
@@ -581,6 +790,7 @@ static const struct media_ioctl_info compat_ioctl_info[] = {
MEDIA_IOC_ARG(ENUM_LINKS32, media_device_enum_links, MEDIA_IOC_FL_GRAPH_MUTEX, from_user_enum_links32, copy_arg_to_user_nop),
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),
};
static long media_device_compat_ioctl(struct file *filp, unsigned int cmd,
@@ -765,6 +975,7 @@ void media_device_init(struct media_device *mdev)
INIT_LIST_HEAD(&mdev->entity_notify);
mutex_init(&mdev->graph_mutex);
ida_init(&mdev->entity_internal_idx);
+ ida_init(&mdev->req_ids);
dev_dbg(mdev->dev, "Media device initialized\n");
}
@@ -772,6 +983,7 @@ EXPORT_SYMBOL_GPL(media_device_init);
void media_device_cleanup(struct media_device *mdev)
{
+ ida_destroy(&mdev->req_ids);
ida_destroy(&mdev->entity_internal_idx);
mdev->entity_internal_idx_max = 0;
media_entity_graph_walk_cleanup(&mdev->pm_count_walk);
@@ -784,6 +996,9 @@ int __must_check __media_device_register(struct media_device *mdev,
{
int ret;
+ 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;
@@ -23,6 +23,8 @@
#ifndef _MEDIA_DEVICE_H
#define _MEDIA_DEVICE_H
+#include <linux/kref.h>
+#include <linux/idr.h>
#include <linux/list.h>
#include <linux/mutex.h>
@@ -260,8 +262,25 @@
* in the end provide a way to use driver-specific callbacks.
*/
-struct ida;
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 file *filp;
+ struct kref kref;
+ struct list_head list;
+ struct list_head fh_list;
+};
/**
* struct media_entity_notify - Media Entity Notify
@@ -283,10 +302,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_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);
};
/**
@@ -322,6 +352,9 @@ struct media_device_ops {
* @disable_source: Disable Source Handler function pointer
*
* @ops: Operation handler callbacks
+ * @req_ids: Allocated request IDs
+ * @req_lock: Serialise access to requests list
+ * @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
@@ -389,6 +422,10 @@ struct media_device {
void (*disable_source)(struct media_entity *entity);
const struct media_device_ops *ops;
+
+ struct ida req_ids;
+ spinlock_t req_lock;
+ struct list_head requests;
};
/* We don't need to include pci.h or usb.h here */
@@ -715,4 +752,9 @@ 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, u16 reqid);
+void media_device_request_get(struct media_device_request *req);
+void media_device_request_put(struct media_device_request *req);
+
#endif
@@ -389,10 +389,21 @@ struct media_v2_topology {
/* ioctls */
+#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 __attribute__ ((packed)) media_request_cmd {
+ __u32 cmd;
+ __u32 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)
#endif /* __LINUX_MEDIA_H */