Re: [RFC 1/2] media: add helpers for memory-to-memory media controller

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Hans,

Thanks for the review.

On Fri, 2018-06-15 at 11:24 +0200, Hans Verkuil wrote:
> On 12/06/18 12:48, Ezequiel Garcia wrote:
> > 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 <laurent.pinchart@xxxxxxxxxxxxxxxx>
> > Suggested-by: Hans Verkuil <hans.verkuil@xxxxxxxxx>
> > Signed-off-by: Ezequiel Garcia <ezequiel@xxxxxxxxxxxxx>
> > ---
> >  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) {
> 
> As mentioned this should be vfl_dir != VFL_DIR_M2M. No need for a new VFL_TYPE.
> 

Right.

> >  		/* 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;
> 
> And that means that this is also no longer needed.
> 

Right, it should be simplified a lot. I hated to introduce
a new type, just thought it was the cleaner way.

> >  	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++)
> 
> ARRAY_SIZE? Or use a define.
> 

Ugh, yes, you are right.

> > +		media_entity_remove_links(&m2m_dev->entities[i].entity);
> > +	for (i = 0; i < 3; i++)
> 
> Ditto.
> 
> > +		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));
> 
> Either use "capture" and "output" (to conform to the V4L2_BUF_TYPE naming) or
> "source" and "sink".
> 

Right.

> > +		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;
> 
> Why not just strlcpy into the m2m_entity->entity.name field? Then you can
> drop m2m_entity->name. Or am I missing something?
> 

Hm, I think you are right.

> > +	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);
> 
> Weird indentation.
> 
> > +	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);
> 
> Ditto.
> 
> > +	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;
> 
> Looking at this code I think it would be much cleaner if you drop the array
> and make it explicit instead:
> 
> struct v4l2_m2m_entities {
> 	struct media_entity source;
> 	struct media_pad source_pad;
> 	struct media_entity sink;
> 	struct media_pad sink_pad;
> 	struct media_entity proc;
> 	struct media_pad proc_pads[2];
> };
> 
> I think this will simplify the code quite a bit.
> 
> Note: one of these media_entity structs can be removed since struct video_device
> already has an entity you can use. It probably makes the most sense to assign
> that one the role of the sink or source entity.
> 
> Perhaps it might be easiest to change e.g. struct media_entity source to
> 'struct media_entity *source' and have it point to &vdev->entity.
> 
> Up to you.
> 

Right, I'll take a look.

> > 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.
> 
> I see no need for this. An M2M device is of type VIDEO_DEVICE, no need to
> change that.
> 

Well, the problem is that this type is used to cast the media_entity
using container_of macro, by means of is_media_entity_v4l2_video_device
and is_media_entity_v4l2_subdev.

So, by using one these types we'd be breaking that assumption. 

> >   *
> >   * 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)
> 
> Drop this as well. Just stick to MEDIA_ENT_F_IO_V4L which is what we've decided to
> call such entities (for better or worse).
> 

Will do.

> >  
> >  /*
> >   * 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)
> 
> I think we need to be a bit more specific here:
> 
> #define MEDIA_ENT_F_PROC_VIDEO_DECODER
> #define MEDIA_ENT_F_PROC_VIDEO_ENCODER
> #define MEDIA_ENT_F_PROC_VIDEO_DEINTERLACER
> // others?
> 

OK. And what about "composite" devices that can encode and perform
other transforms, e.g. scale, rotation, etc. 

Regards,
Eze



[Index of Archives]     [Linux Input]     [Video for Linux]     [Gstreamer Embedded]     [Mplayer Users]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]

  Powered by Linux