A memory-to-memory pipeline device consists in three
entities: two DMA engine and one video processing entities.
The DMA engine entities are linked to a V4L interface.
This commit add a new v4l2_m2m_{un}register_media_controller
API to register this topology.
For instance, a typical mem2mem device topology would
look like this:
- entity 1: input (1 pad, 1 link)
type Node subtype Unknown flags 0
pad0: Source
-> "proc":1 [ENABLED,IMMUTABLE]
- entity 3: proc (2 pads, 2 links)
type Node subtype Unknown flags 0
pad0: Source
-> "output":0 [ENABLED,IMMUTABLE]
pad1: Sink
<- "input":0 [ENABLED,IMMUTABLE]
- entity 6: output (1 pad, 1 link)
type Node subtype Unknown flags 0
pad0: Sink
<- "proc":0 [ENABLED,IMMUTABLE]
Suggested-by: Laurent Pinchart <[email protected]>
Suggested-by: Hans Verkuil <[email protected]>
Signed-off-by: Ezequiel Garcia <[email protected]>
---
drivers/media/v4l2-core/v4l2-dev.c | 23 ++--
drivers/media/v4l2-core/v4l2-mem2mem.c | 157 +++++++++++++++++++++++++
include/media/media-entity.h | 4 +
include/media/v4l2-dev.h | 2 +
include/media/v4l2-mem2mem.h | 5 +
include/uapi/linux/media.h | 2 +
6 files changed, 186 insertions(+), 7 deletions(-)
diff --git a/drivers/media/v4l2-core/v4l2-dev.c
b/drivers/media/v4l2-core/v4l2-dev.c
index 4ffd7d60a901..ec8f20f0fdc5 100644
--- a/drivers/media/v4l2-core/v4l2-dev.c
+++ b/drivers/media/v4l2-core/v4l2-dev.c
@@ -202,7 +202,7 @@ static void v4l2_device_release(struct device *cd)
mutex_unlock(&videodev_lock);
#if defined(CONFIG_MEDIA_CONTROLLER)
- if (v4l2_dev->mdev) {
+ if (v4l2_dev->mdev && vdev->vfl_type != VFL_TYPE_MEM2MEM) {
/* Remove interfaces and interface links */
media_devnode_remove(vdev->intf_devnode);
if (vdev->entity.function != MEDIA_ENT_F_UNKNOWN)
@@ -530,6 +530,7 @@ static void determine_valid_ioctls(struct video_device
*vdev)
bool is_radio = vdev->vfl_type == VFL_TYPE_RADIO;
bool is_sdr = vdev->vfl_type == VFL_TYPE_SDR;
bool is_tch = vdev->vfl_type == VFL_TYPE_TOUCH;
+ bool is_m2m = vdev->vfl_type == VFL_TYPE_MEM2MEM;
bool is_rx = vdev->vfl_dir != VFL_DIR_TX;
bool is_tx = vdev->vfl_dir != VFL_DIR_RX;
@@ -576,7 +577,7 @@ static void determine_valid_ioctls(struct video_device
*vdev)
if (ops->vidioc_enum_freq_bands || ops->vidioc_g_tuner ||
ops->vidioc_g_modulator)
set_bit(_IOC_NR(VIDIOC_ENUM_FREQ_BANDS), valid_ioctls);
- if (is_vid || is_tch) {
+ if (is_vid || is_m2m || is_tch) {
/* video and metadata specific ioctls */
if ((is_rx && (ops->vidioc_enum_fmt_vid_cap ||
ops->vidioc_enum_fmt_vid_cap_mplane ||
@@ -669,7 +670,7 @@ static void determine_valid_ioctls(struct video_device
*vdev)
set_bit(_IOC_NR(VIDIOC_TRY_FMT), valid_ioctls);
}
- if (is_vid || is_vbi || is_sdr || is_tch) {
+ if (is_vid || is_m2m || is_vbi || is_sdr || is_tch) {
/* ioctls valid for video, metadata, vbi or sdr */
SET_VALID_IOCTL(ops, VIDIOC_REQBUFS, vidioc_reqbufs);
SET_VALID_IOCTL(ops, VIDIOC_QUERYBUF, vidioc_querybuf);
@@ -682,7 +683,7 @@ static void determine_valid_ioctls(struct video_device
*vdev)
SET_VALID_IOCTL(ops, VIDIOC_STREAMOFF, vidioc_streamoff);
}
- if (is_vid || is_vbi || is_tch) {
+ if (is_vid || is_m2m || is_vbi || is_tch) {
/* ioctls valid for video or vbi */
if (ops->vidioc_s_std)
set_bit(_IOC_NR(VIDIOC_ENUMSTD), valid_ioctls);
@@ -733,7 +734,7 @@ static void determine_valid_ioctls(struct video_device
*vdev)
BASE_VIDIOC_PRIVATE);
}
-static int video_register_media_controller(struct video_device *vdev, int type)
+static int video_register_media_controller(struct video_device *vdev)
{
#if defined(CONFIG_MEDIA_CONTROLLER)
u32 intf_type;
@@ -745,7 +746,7 @@ static int video_register_media_controller(struct
video_device *vdev, int type)
vdev->entity.obj_type = MEDIA_ENTITY_TYPE_VIDEO_DEVICE;
vdev->entity.function = MEDIA_ENT_F_UNKNOWN;
- switch (type) {
+ switch (vdev->vfl_type) {
case VFL_TYPE_GRABBER:
intf_type = MEDIA_INTF_T_V4L_VIDEO;
vdev->entity.function = MEDIA_ENT_F_IO_V4L;
@@ -774,6 +775,10 @@ static int video_register_media_controller(struct
video_device *vdev, int type)
intf_type = MEDIA_INTF_T_V4L_SUBDEV;
/* Entity will be created via v4l2_device_register_subdev() */
break;
+ case VFL_TYPE_MEM2MEM:
+ /* Memory-to-memory devices are more complex and use
+ * their own function to register.
+ */
default:
return 0;
}
@@ -869,6 +874,10 @@ int __video_register_device(struct video_device *vdev,
case VFL_TYPE_TOUCH:
name_base = "v4l-touch";
break;
+ case VFL_TYPE_MEM2MEM:
+ /* Maintain this name for backwards compatibility */
+ name_base = "video";
+ break;
default:
pr_err("%s called with unknown type: %d\n",
__func__, type);
@@ -993,7 +1002,7 @@ int __video_register_device(struct video_device *vdev,
v4l2_device_get(vdev->v4l2_dev);
/* Part 5: Register the entity. */
- ret = video_register_media_controller(vdev, type);
+ ret = video_register_media_controller(vdev);
/* Part 6: Activate this minor. The char device can now be used. */
set_bit(V4L2_FL_REGISTERED, &vdev->flags);
diff --git a/drivers/media/v4l2-core/v4l2-mem2mem.c
b/drivers/media/v4l2-core/v4l2-mem2mem.c
index c4f963d96a79..0505b65bfa68 100644
--- a/drivers/media/v4l2-core/v4l2-mem2mem.c
+++ b/drivers/media/v4l2-core/v4l2-mem2mem.c
@@ -17,9 +17,11 @@
#include <linux/sched.h>
#include <linux/slab.h>
+#include <media/media-device.h>
#include <media/videobuf2-v4l2.h>
#include <media/v4l2-mem2mem.h>
#include <media/v4l2-dev.h>
+#include <media/v4l2-device.h>
#include <media/v4l2-fh.h>
#include <media/v4l2-event.h>
@@ -50,6 +52,11 @@ module_param(debug, bool, 0644);
* offsets but for different queues */
#define DST_QUEUE_OFF_BASE (1 << 30)
+struct v4l2_m2m_entity {
+ struct media_entity entity;
+ struct media_pad pads[2];
+ char name[64];
+};
/**
* struct v4l2_m2m_dev - per-device context
@@ -60,6 +67,10 @@ module_param(debug, bool, 0644);
*/
struct v4l2_m2m_dev {
struct v4l2_m2m_ctx *curr_ctx;
+#ifdef CONFIG_MEDIA_CONTROLLER
+ struct v4l2_m2m_entity entities[3];
+ struct media_intf_devnode *intf_devnode;
+#endif
struct list_head job_queue;
spinlock_t job_spinlock;
@@ -595,6 +606,152 @@ int v4l2_m2m_mmap(struct file *file, struct v4l2_m2m_ctx
*m2m_ctx,
}
EXPORT_SYMBOL(v4l2_m2m_mmap);
+void v4l2_m2m_unregister_media_controller(struct v4l2_m2m_dev *m2m_dev)
+{
+ int i;
+
+ media_remove_intf_links(&m2m_dev->intf_devnode->intf);
+ media_devnode_remove(m2m_dev->intf_devnode);
+
+ for (i = 0; i < 3; i++)
+ media_entity_remove_links(&m2m_dev->entities[i].entity);
+ for (i = 0; i < 3; i++)
+ media_device_unregister_entity(&m2m_dev->entities[i].entity);
+}
+EXPORT_SYMBOL_GPL(v4l2_m2m_unregister_media_controller);
+
+#define MEM2MEM_ENT_TYPE_INPUT 1
+#define MEM2MEM_ENT_TYPE_OUTPUT 2
+#define MEM2MEM_ENT_TYPE_PROC 3
+
+static int v4l2_m2m_register_entity(struct media_device *mdev,
+ struct v4l2_m2m_entity *m2m_entity, int type)
+{
+ unsigned int function;
+ int num_pads;
+ int ret;
+
+ switch (type) {
+ case MEM2MEM_ENT_TYPE_INPUT:
+ function = MEDIA_ENT_F_IO_DMAENGINE;
+ m2m_entity->pads[0].flags = MEDIA_PAD_FL_SOURCE;
+ strlcpy(m2m_entity->name, "input", sizeof(m2m_entity->name));
+ num_pads = 1;
+ break;
+ case MEM2MEM_ENT_TYPE_OUTPUT:
+ function = MEDIA_ENT_F_IO_DMAENGINE;
+ m2m_entity->pads[0].flags = MEDIA_PAD_FL_SINK;
+ strlcpy(m2m_entity->name, "output", sizeof(m2m_entity->name));
+ num_pads = 1;
+ break;
+ case MEM2MEM_ENT_TYPE_PROC:
+ function = MEDIA_ENT_F_PROC_VIDEO_TRANSFORM;
+ m2m_entity->pads[0].flags = MEDIA_PAD_FL_SOURCE;
+ m2m_entity->pads[1].flags = MEDIA_PAD_FL_SINK;
+ strlcpy(m2m_entity->name, "proc", sizeof(m2m_entity->name));
+ num_pads = 2;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = media_entity_pads_init(&m2m_entity->entity, num_pads,
m2m_entity->pads);
+ if (ret)
+ return ret;
+
+ m2m_entity->entity.obj_type = MEDIA_ENTITY_TYPE_MEM2MEM;
+ m2m_entity->entity.function = function;
+ m2m_entity->entity.name = m2m_entity->name;
+ ret = media_device_register_entity(mdev, &m2m_entity->entity);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+int v4l2_m2m_register_media_controller(struct v4l2_m2m_dev *m2m_dev, struct
video_device *vdev)
+{
+#if defined(CONFIG_MEDIA_CONTROLLER)
+ struct media_device *mdev = vdev->v4l2_dev->mdev;
+ struct media_link *link;
+ int ret;
+
+ if (!mdev)
+ return 0;
+
+ /* A memory-to-memory device consists in two
+ * DMA engine and one video processing entities.
+ * The DMA engine entities are linked to a V4L interface
+ */
+
+ /* Create the three entities with their pads */
+ ret = v4l2_m2m_register_entity(mdev, &m2m_dev->entities[0],
MEM2MEM_ENT_TYPE_INPUT);
+ if (ret)
+ return ret;
+ ret = v4l2_m2m_register_entity(mdev, &m2m_dev->entities[1],
MEM2MEM_ENT_TYPE_PROC);
+ if (ret)
+ goto err_rel_entity0;
+ ret = v4l2_m2m_register_entity(mdev, &m2m_dev->entities[2],
MEM2MEM_ENT_TYPE_OUTPUT);
+ if (ret)
+ goto err_rel_entity1;
+
+ /* Connect the three entities */
+ ret = media_create_pad_link(&m2m_dev->entities[0].entity, 0,
+ &m2m_dev->entities[1].entity, 1,
+ MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED);
+ if (ret)
+ goto err_rel_entity2;
+
+ ret = media_create_pad_link(&m2m_dev->entities[1].entity, 0,
+ &m2m_dev->entities[2].entity, 0,
+ MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED);
+ if (ret)
+ goto err_rm_links0;
+
+ /* Create video interface */
+ m2m_dev->intf_devnode = media_devnode_create(mdev,
MEDIA_INTF_T_V4L_VIDEO, 0, VIDEO_MAJOR, vdev->minor);
+ if (!m2m_dev->intf_devnode) {
+ ret = -ENOMEM;
+ goto err_rm_links1;
+ }
+
+ /* Connect the two DMA engines to the interface */
+ link = media_create_intf_link(&m2m_dev->entities[0].entity,
&m2m_dev->intf_devnode->intf,
+ MEDIA_LNK_FL_ENABLED);
+ if (!link) {
+ ret = -ENOMEM;
+ goto err_rm_devnode;
+ }
+
+ link = media_create_intf_link(&m2m_dev->entities[1].entity,
&m2m_dev->intf_devnode->intf,
+ MEDIA_LNK_FL_ENABLED);
+ if (!link) {
+ ret = -ENOMEM;
+ goto err_rm_intf_link;
+ }
+ return 0;
+
+err_rm_intf_link:
+ media_remove_intf_links(&m2m_dev->intf_devnode->intf);
+err_rm_devnode:
+ media_devnode_remove(m2m_dev->intf_devnode);
+err_rm_links1:
+ media_entity_remove_links(&m2m_dev->entities[2].entity);
+err_rm_links0:
+ media_entity_remove_links(&m2m_dev->entities[1].entity);
+ media_entity_remove_links(&m2m_dev->entities[0].entity);
+err_rel_entity2:
+ media_device_unregister_entity(&m2m_dev->entities[2].entity);
+err_rel_entity1:
+ media_device_unregister_entity(&m2m_dev->entities[1].entity);
+err_rel_entity0:
+ media_device_unregister_entity(&m2m_dev->entities[0].entity);
+ return ret;
+#endif
+ return 0;
+}
+EXPORT_SYMBOL_GPL(v4l2_m2m_register_media_controller);
+
struct v4l2_m2m_dev *v4l2_m2m_init(const struct v4l2_m2m_ops *m2m_ops)
{
struct v4l2_m2m_dev *m2m_dev;
diff --git a/include/media/media-entity.h b/include/media/media-entity.h
index 3aa3d58d1d58..ff6fbe8333e1 100644
--- a/include/media/media-entity.h
+++ b/include/media/media-entity.h
@@ -206,6 +206,9 @@ struct media_entity_operations {
* The entity is embedded in a struct video_device instance.
* @MEDIA_ENTITY_TYPE_V4L2_SUBDEV:
* The entity is embedded in a struct v4l2_subdev instance.
+ * @MEDIA_ENTITY_TYPE_V4L2_MEM2MEM:
+ * The entity is not embedded in any struct, but part of
+ * a memory-to-memory topology.
*
* Media entity objects are often not instantiated directly, but the media
* entity structure is inherited by (through embedding) other
subsystem-specific
@@ -222,6 +225,7 @@ enum media_entity_type {
MEDIA_ENTITY_TYPE_BASE,
MEDIA_ENTITY_TYPE_VIDEO_DEVICE,
MEDIA_ENTITY_TYPE_V4L2_SUBDEV,
+ MEDIA_ENTITY_TYPE_MEM2MEM,
};
/**
diff --git a/include/media/v4l2-dev.h b/include/media/v4l2-dev.h
index 456ac13eca1d..a9df949bb9c3 100644
--- a/include/media/v4l2-dev.h
+++ b/include/media/v4l2-dev.h
@@ -30,6 +30,7 @@
* @VFL_TYPE_SUBDEV: for V4L2 subdevices
* @VFL_TYPE_SDR: for Software Defined Radio tuners
* @VFL_TYPE_TOUCH: for touch sensors
+ * @VFL_TYPE_MEM2MEM: for mem2mem devices
* @VFL_TYPE_MAX: number of VFL types, must always be last in the enum
*/
enum vfl_devnode_type {
@@ -39,6 +40,7 @@ enum vfl_devnode_type {
VFL_TYPE_SUBDEV,
VFL_TYPE_SDR,
VFL_TYPE_TOUCH,
+ VFL_TYPE_MEM2MEM,
VFL_TYPE_MAX /* Shall be the last one */
};
diff --git a/include/media/v4l2-mem2mem.h b/include/media/v4l2-mem2mem.h
index 3d07ba3a8262..9dfe9bd23f89 100644
--- a/include/media/v4l2-mem2mem.h
+++ b/include/media/v4l2-mem2mem.h
@@ -53,6 +53,7 @@ struct v4l2_m2m_ops {
void (*unlock)(void *priv);
};
+struct video_device;
struct v4l2_m2m_dev;
/**
@@ -328,6 +329,10 @@ int v4l2_m2m_mmap(struct file *file, struct v4l2_m2m_ctx
*m2m_ctx,
*/
struct v4l2_m2m_dev *v4l2_m2m_init(const struct v4l2_m2m_ops *m2m_ops);
+int v4l2_m2m_register_media_controller(struct v4l2_m2m_dev *m2m_dev, struct
video_device *vdev);
+
+void v4l2_m2m_unregister_media_controller(struct v4l2_m2m_dev *m2m_dev);
+
/**
* v4l2_m2m_release() - cleans up and frees a m2m_dev structure
*
diff --git a/include/uapi/linux/media.h b/include/uapi/linux/media.h
index c7e9a5cba24e..becb7db77f6a 100644
--- a/include/uapi/linux/media.h
+++ b/include/uapi/linux/media.h
@@ -81,6 +81,7 @@ struct media_device_info {
#define MEDIA_ENT_F_IO_DTV (MEDIA_ENT_F_BASE + 0x01001)
#define MEDIA_ENT_F_IO_VBI (MEDIA_ENT_F_BASE + 0x01002)
#define MEDIA_ENT_F_IO_SWRADIO (MEDIA_ENT_F_BASE + 0x01003)
+#define MEDIA_ENT_F_IO_DMAENGINE (MEDIA_ENT_F_BASE + 0x01004)
/*
* Sensor functions
@@ -132,6 +133,7 @@ struct media_device_info {
#define MEDIA_ENT_F_PROC_VIDEO_LUT (MEDIA_ENT_F_BASE + 0x4004)
#define MEDIA_ENT_F_PROC_VIDEO_SCALER (MEDIA_ENT_F_BASE + 0x4005)
#define MEDIA_ENT_F_PROC_VIDEO_STATISTICS (MEDIA_ENT_F_BASE + 0x4006)
+#define MEDIA_ENT_F_PROC_VIDEO_TRANSFORM (MEDIA_ENT_F_BASE + 0x4007)
/*
* Switch and bridge entity functions
--
2.17.1