[alsa-devel] [RFC PATCH 1/11] expose sound device topology information
Hi,
these are some tentative patches to allow sound drivers to provide routing information to userspace. (So far, snd-usb-audio has a partial implementation; I haven't yet had time for snd-hda-intel. I haven't even looked at ASoC.)
The media controller API <linux/media.h> allows prividing routing information, but its implementation does not quite fit the sound drivers: * it allows reconfiguration of links, but not in a way that would be useful for ALSA selector controls; * it allows drivers to access the entity graph and pipelines, which is not needed; * it is rather heavyweight.
Therefore, these patches create their own implementation of the media controller API. In this implementation, all entity and link information is completely static, and more space-efficient.
For simplicity, the ioctls hook into the ALSA control device instead of creating a new /dev/media* device.
The separate implementation does not allow sharing of one media controller device in the case of combined audio/video devices. However, separate drivers would already be a problem for HDMI outputs (GPU and sound); it appears we might need some mechanism to connect the topologies of multiple media devices.
TLVs (for jack entities, and for associating controls with entities) are not yet defined.
Example output for my simple UA-1A device (not all information is decoded correctly):
$ media-ctl -p -d /dev/snd/controlC5 Opening media device /dev/snd/controlC5 Enumerating entities Found 4 entities Enumerating pads and links Media controller API version 0.0.0
Media device information ------------------------ driver snd-usb-audio model EDIROL UA-1A serial bus info usb-0000:00:12.2-4.3 hw revision 0x101 driver version 0.0.0
Device topology - entity 1: USB Audio (1 pad, 1 link) type Node subtype ALSA device node name /dev/tty (should be hw:5,0,0) pad0: Source -> "OT3":0 [ENABLED,IMMUTABLE]
- entity 3: OT3 (1 pad, 1 link) type Unknown subtype Unknown (should be Jack) pad0: Sink <- "USB Audio":0 [ENABLED,IMMUTABLE]
- entity 4: IT4 (1 pad, 1 link) type Unknown subtype Unknown pad0: Source -> "USB Audio":0 [ENABLED,IMMUTABLE]
- entity 7: USB Audio (1 pad, 1 link) type Node subtype ALSA device node name /dev/tty pad0: Sink <- "IT4":0 [ENABLED,IMMUTABLE]
include/linux/media.h | 9 include/sound/core.h | 13 + include/sound/media.h | 116 ++++++++++ include/sound/pcm.h | 6 sound/core/Kconfig | 7 sound/core/Makefile | 1 sound/core/init.c | 4 sound/core/media.c | 425 ++++++++++++++++++++++++++++++++++++++ sound/core/pcm.c | 46 ++++ sound/core/sound.c | 3 sound/pci/hda/hda_intel.c | 2 sound/usb/card.c | 2 sound/usb/mixer.c | 278 ++++++++++++++++++++++++ sound/usb/quirks.c | 4 sound/usb/stream.c | 16 - sound/usb/stream.h | 1 16 files changed, 925 insertions(+), 8 deletions(-)
Regards, Clemens
Add a callback to snd_card to allow card drivers to return driver- specific information.
Signed-off-by: Clemens Ladisch clemens@ladisch.de --- include/sound/core.h | 5 +++ include/sound/media.h | 50 ++++++++++++++++++++++++++++++++ sound/core/Kconfig | 7 +++++ sound/core/Makefile | 1 + sound/core/media.c | 76 +++++++++++++++++++++++++++++++++++++++++++++++++ sound/core/sound.c | 3 ++ 6 files changed, 142 insertions(+), 0 deletions(-) create mode 100644 include/sound/media.h create mode 100644 sound/core/media.c
diff --git a/include/sound/core.h b/include/sound/core.h index bc05668..5eca6f5 100644 --- a/include/sound/core.h +++ b/include/sound/core.h @@ -42,6 +42,7 @@ struct pci_dev; struct module; struct device; struct device_attribute; +struct snd_media_card_ops;
/* device allocation stuff */
@@ -145,6 +146,10 @@ struct snd_card { struct snd_mixer_oss *mixer_oss; int mixer_oss_change_count; #endif + +#ifdef CONFIG_SND_MEDIA + const struct snd_media_card_ops *media_ops; +#endif };
#ifdef CONFIG_PM diff --git a/include/sound/media.h b/include/sound/media.h new file mode 100644 index 0000000..d196219 --- /dev/null +++ b/include/sound/media.h @@ -0,0 +1,50 @@ +/* + * media controller interface for ALSA driver + * Copyright (c) Clemens Ladisch clemens@ladisch.de + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +#ifndef SOUND_MEDIA_H_INCLUDED +#define SOUND_MEDIA_H_INCLUDED + +#include <linux/init.h> + +struct snd_card; +struct media_device_info; + +struct snd_media_card_ops { + int (*get_info)(struct snd_card *card, struct media_device_info *info); +}; + +#ifdef CONFIG_SND_MEDIA + +#define snd_card_set_media_ops(card, ops) ((card)->media_ops = (ops)) + +void __init snd_media_init(void); +void __exit snd_media_exit(void); + +#else /* CONFIG_SND_MEDIA */ + +#define snd_card_set_media_ops(card, ops) + +static inline void snd_media_init() +{ } +static inline void snd_media_exit() +{ } + +#endif /* !CONFIG_SND_MEDIA */ + +#endif diff --git a/sound/core/Kconfig b/sound/core/Kconfig index b413ed0..fadbdf5 100644 --- a/sound/core/Kconfig +++ b/sound/core/Kconfig @@ -147,6 +147,13 @@ config SND_SEQ_RTCTIMER_DEFAULT
If in doubt, say Y.
+config SND_MEDIA + bool "Media Controller API (EXPERIMENTAL)" + depends on EXPERIMENTAL + help + Say Y here to enable the media controller API, which allows to + retrieve the internal topology of sound devices. + config SND_DYNAMIC_MINORS bool "Dynamic device file minor numbers" help diff --git a/sound/core/Makefile b/sound/core/Makefile index 43d4117..b6de542 100644 --- a/sound/core/Makefile +++ b/sound/core/Makefile @@ -9,6 +9,7 @@ snd-$(CONFIG_SND_OSSEMUL) += sound_oss.o info_oss.o snd-$(CONFIG_SND_VMASTER) += vmaster.o snd-$(CONFIG_SND_KCTL_JACK) += ctljack.o snd-$(CONFIG_SND_JACK) += jack.o +snd-$(CONFIG_SND_MEDIA) += media.o
snd-pcm-objs := pcm.o pcm_native.o pcm_lib.o pcm_timer.o pcm_misc.o \ pcm_memory.o diff --git a/sound/core/media.c b/sound/core/media.c new file mode 100644 index 0000000..6ceb12a --- /dev/null +++ b/sound/core/media.c @@ -0,0 +1,76 @@ +/* + * media controller interface for ALSA driver + * Copyright (c) Clemens Ladisch clemens@ladisch.de + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +#include <linux/device.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/media.h> +#include <linux/string.h> +#include <linux/uaccess.h> +#include <sound/core.h> +#include <sound/control.h> +#include <sound/media.h> + +static int snd_media_device_info(struct snd_card *card, + struct media_device_info __user *infop) +{ + struct media_device_info info; + int err; + + memset(&info, 0, sizeof(info)); + + if (card->dev && card->dev->driver) + strlcpy(info.driver, card->dev->driver->name, sizeof(info.driver)); + strlcpy(info.model, card->shortname, sizeof(info.model)); + info.media_version = MEDIA_API_VERSION; + + if (card->media_ops) { + err = card->media_ops->get_info(card, &info); + if (err < 0) + return err; + } + + return copy_to_user(infop, &info, sizeof(info)); +} + +static int snd_media_control_ioctl(struct snd_card *card, + struct snd_ctl_file *ctl_file, + unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + + switch (cmd) { + case MEDIA_IOC_DEVICE_INFO: + return snd_media_device_info(card, argp); + default: + return -ENOIOCTLCMD; + } +} + +void __init snd_media_init(void) +{ + snd_ctl_register_ioctl(snd_media_control_ioctl); + snd_ctl_register_ioctl_compat(snd_media_control_ioctl); +} + +void __exit snd_media_exit(void) +{ + snd_ctl_unregister_ioctl(snd_media_control_ioctl); + snd_ctl_unregister_ioctl_compat(snd_media_control_ioctl); +} diff --git a/sound/core/sound.c b/sound/core/sound.c index 28f3559..7a7721c 100644 --- a/sound/core/sound.c +++ b/sound/core/sound.c @@ -30,6 +30,7 @@ #include <sound/version.h> #include <sound/control.h> #include <sound/initval.h> +#include <sound/media.h> #include <linux/kmod.h> #include <linux/mutex.h>
@@ -467,6 +468,7 @@ static int __init alsa_sound_init(void) return -ENOMEM; } snd_info_minor_register(); + snd_media_init(); #ifndef MODULE printk(KERN_INFO "Advanced Linux Sound Architecture Driver Version " CONFIG_SND_VERSION CONFIG_SND_DATE ".\n"); #endif @@ -475,6 +477,7 @@ static int __init alsa_sound_init(void)
static void __exit alsa_sound_exit(void) { + snd_media_exit(); snd_info_minor_unregister(); snd_info_done(); unregister_chrdev(major, "alsa");
On Tue, Aug 28, 2012 at 12:29:33AM +0200, Clemens Ladisch wrote:
+config SND_MEDIA
- bool "Media Controller API (EXPERIMENTAL)"
- depends on EXPERIMENTAL
- help
Say Y here to enable the media controller API, which allows to
retrieve the internal topology of sound devices.
I think there's a general consensus these days that EXPERIMENTAL is pretty meaningless so we probably shouldn't bother with it. The text is also a bit confusing, it enables use of the media controller API for ALSA devices really.
I have to say I'm also a bit surprised that there's no core framework for media controller and we're open coding ioctl() implementations here, I'd have thought there would at least be a way of enumerating all media controller devices in the system...
Mark Brown wrote:
On Tue, Aug 28, 2012 at 12:29:33AM +0200, Clemens Ladisch wrote:
+config SND_MEDIA
- bool "Media Controller API (EXPERIMENTAL)"
- depends on EXPERIMENTAL
- help
Say Y here to enable the media controller API, which allows to
retrieve the internal topology of sound devices.
I think there's a general consensus these days that EXPERIMENTAL is pretty meaningless so we probably shouldn't bother with it.
Well, I couldn't claim this to be any less experimental than the implementation in drivers/media/. :)
The text is also a bit confusing, it enables use of the media controller API for ALSA devices really.
It's in the ALSA menu. I could put "ALSA" in the title if it helps.
I have to say I'm also a bit surprised that there's no core framework for media controller and we're open coding ioctl() implementations here,
drivers/media/media-*.c exists, but I did not use that implementation because someone expressed concern about it being too heavyweight ...
I'd have thought there would at least be a way of enumerating all media controller devices in the system...
It appears userspace is supposed to check all /dev/media* devices. (Using /dev/snd/controlC* is just a makeshift arrangement; I'll have to reuse some part of the media device registration framework, or tell udev to create symlinks.)
Regards, Clemens
At Fri, 07 Sep 2012 09:14:30 +0200, Clemens Ladisch wrote:
Mark Brown wrote:
On Tue, Aug 28, 2012 at 12:29:33AM +0200, Clemens Ladisch wrote:
+config SND_MEDIA
- bool "Media Controller API (EXPERIMENTAL)"
- depends on EXPERIMENTAL
- help
Say Y here to enable the media controller API, which allows to
retrieve the internal topology of sound devices.
I think there's a general consensus these days that EXPERIMENTAL is pretty meaningless so we probably shouldn't bother with it.
Well, I couldn't claim this to be any less experimental than the implementation in drivers/media/. :)
In the previous kernel summit, we agreed to drop CONFIG_EXPERIMENTAL since it's just useless. So, no need to add yet another new entry with that flag.
The text is also a bit confusing, it enables use of the media controller API for ALSA devices really.
It's in the ALSA menu. I could put "ALSA" in the title if it helps.
Yeah, I also prefer with that.
Takashi
Allow drivers to create entities. To avoid bloat, the snd_media_entity structure stores only basic information and retrieves the full entitiy information on demand with a callback.
Signed-off-by: Clemens Ladisch clemens@ladisch.de --- include/sound/core.h | 5 ++ include/sound/media.h | 23 ++++++++++ sound/core/init.c | 4 ++ sound/core/media.c | 114 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 145 insertions(+), 1 deletions(-)
diff --git a/include/sound/core.h b/include/sound/core.h index 5eca6f5..072b642 100644 --- a/include/sound/core.h +++ b/include/sound/core.h @@ -51,7 +51,8 @@ struct snd_media_card_ops; typedef int __bitwise snd_device_type_t; #define SNDRV_DEV_TOPLEVEL ((__force snd_device_type_t) 0) #define SNDRV_DEV_CONTROL ((__force snd_device_type_t) 1) -#define SNDRV_DEV_LOWLEVEL_PRE ((__force snd_device_type_t) 2) +#define SNDRV_DEV_MEDIA ((__force snd_device_type_t) 2) +#define SNDRV_DEV_LOWLEVEL_PRE ((__force snd_device_type_t) 3) #define SNDRV_DEV_LOWLEVEL_NORMAL ((__force snd_device_type_t) 0x1000) #define SNDRV_DEV_PCM ((__force snd_device_type_t) 0x1001) #define SNDRV_DEV_RAWMIDI ((__force snd_device_type_t) 0x1002) @@ -149,6 +150,8 @@ struct snd_card {
#ifdef CONFIG_SND_MEDIA const struct snd_media_card_ops *media_ops; + struct mutex media_mutex; + struct list_head media_entities; #endif };
diff --git a/include/sound/media.h b/include/sound/media.h index d196219..65dd068 100644 --- a/include/sound/media.h +++ b/include/sound/media.h @@ -23,16 +23,29 @@ #include <linux/init.h>
struct snd_card; +struct snd_media_entity; struct media_device_info; +struct media_entity_desc;
struct snd_media_card_ops { int (*get_info)(struct snd_card *card, struct media_device_info *info); };
+typedef int (*snd_media_entity_get_desc_t)(struct snd_card *card, + void *private_data, + struct media_entity_desc *desc); + #ifdef CONFIG_SND_MEDIA
#define snd_card_set_media_ops(card, ops) ((card)->media_ops = (ops))
+int snd_media_entity_create(struct snd_card *card, + snd_media_entity_get_desc_t get_desc, + unsigned int id, + unsigned int sinks, unsigned int sources, + void *private_data); + +int snd_media_create(struct snd_card *card); void __init snd_media_init(void); void __exit snd_media_exit(void);
@@ -40,6 +53,16 @@ void __exit snd_media_exit(void);
#define snd_card_set_media_ops(card, ops)
+static inline int snd_media_entity_create(struct snd_card *card, + snd_media_entity_get_desc_t get_desc, + unsigned int id, + unsigned int sinks, unsigned int sources, + void *private_data) +{ return 0; } + + +static inline int snd_media_create(struct snd_card *card) +{ return 0; } static inline void snd_media_init() { } static inline void snd_media_exit() diff --git a/sound/core/init.c b/sound/core/init.c index d8ec849..e16ef16 100644 --- a/sound/core/init.c +++ b/sound/core/init.c @@ -32,6 +32,7 @@ #include <sound/core.h> #include <sound/control.h> #include <sound/info.h> +#include <sound/media.h>
/* monitor files for graceful shutdown (hotplug) */ struct snd_monitor_file { @@ -224,6 +225,9 @@ int snd_card_create(int idx, const char *xid, snd_printk(KERN_ERR "unable to register control minors\n"); goto __error; } + err = snd_media_create(card); + if (err < 0) + goto __error_ctl; err = snd_info_card_create(card); if (err < 0) { snd_printk(KERN_ERR "unable to create card info\n"); diff --git a/sound/core/media.c b/sound/core/media.c index 6ceb12a..aa94175 100644 --- a/sound/core/media.c +++ b/sound/core/media.c @@ -20,6 +20,7 @@ #include <linux/device.h> #include <linux/init.h> #include <linux/kernel.h> +#include <linux/list.h> #include <linux/media.h> #include <linux/string.h> #include <linux/uaccess.h> @@ -27,6 +28,87 @@ #include <sound/control.h> #include <sound/media.h>
+struct snd_media_entity { + struct list_head list; + snd_media_entity_get_desc_t get_desc; + void *private_data; + unsigned int id; + unsigned int sinks, sources; +}; + +static struct snd_media_entity *search_entity(struct snd_card *card, + unsigned int id) +{ + struct snd_media_entity *e; + bool next = id & MEDIA_ENT_ID_FLAG_NEXT; + + id &= ~MEDIA_ENT_ID_FLAG_NEXT; + if (next) + id++; + + list_for_each_entry(e, &card->media_entities, list) + if (e->id >= id) + return next || e->id == id ? e : NULL; + + return NULL; +} + +int snd_media_entity_create(struct snd_card *card, + snd_media_entity_get_desc_t get_desc, + unsigned int id, + unsigned int sinks, unsigned int sources, + void *private_data) +{ + struct snd_media_entity *entity; + struct list_head *pos; + + entity = kzalloc(sizeof(*entity), GFP_KERNEL); + if (!entity) + return -ENOMEM; + entity->get_desc = get_desc; + entity->id = id; + entity->sinks = sinks; + entity->sources = sources; + entity->private_data = private_data; + + mutex_lock(&card->media_mutex); + list_for_each_prev(pos, &card->media_entities) + if (list_entry(pos, struct snd_media_entity, list)->id < id) + break; + list_add(&entity->list, pos); + mutex_unlock(&card->media_mutex); + + return 0; +} +EXPORT_SYMBOL(snd_media_entity_create); + +static int snd_media_dev_free(struct snd_device *device) +{ + struct snd_card *card = device->device_data; + struct snd_media_entity *e; + + while (!list_empty(&card->media_entities)) { + e = list_first_entry(&card->media_entities, + struct snd_media_entity, list); + list_del(&e->list); + kfree(e); + } + + return 0; +} + +int snd_media_create(struct snd_card *card) +{ + static struct snd_device_ops ops = { + .dev_free = snd_media_dev_free, + }; + + mutex_init(&card->media_mutex); + INIT_LIST_HEAD(&card->media_entities); + + return snd_device_new(card, SNDRV_DEV_MEDIA, card, &ops); +} + static int snd_media_device_info(struct snd_card *card, struct media_device_info __user *infop) { @@ -49,6 +131,36 @@ static int snd_media_device_info(struct snd_card *card, return copy_to_user(infop, &info, sizeof(info)); }
+static int snd_media_enum_entities(struct snd_card *card, + struct media_entity_desc __user *descp) +{ + struct media_entity_desc desc; + struct snd_media_entity *entity; + int err; + + if (copy_from_user(&desc, descp, sizeof(desc))) + return -EFAULT; + + mutex_lock(&card->media_mutex); + entity = search_entity(card, desc.id); + mutex_unlock(&card->media_mutex); + if (!entity) + return -EINVAL; + + desc.id = entity->id; + desc.revision = 0; + desc.flags = 0; + desc.group_id = 0; + desc.pads = entity->sinks + entity->sources; + desc.links = 0; + + err = entity->get_desc(card, entity->private_data, &desc); + if (err < 0) + return err; + + return copy_to_user(descp, &desc, sizeof(desc)); +} + static int snd_media_control_ioctl(struct snd_card *card, struct snd_ctl_file *ctl_file, unsigned int cmd, unsigned long arg) @@ -58,6 +170,8 @@ static int snd_media_control_ioctl(struct snd_card *card, switch (cmd) { case MEDIA_IOC_DEVICE_INFO: return snd_media_device_info(card, argp); + case MEDIA_IOC_ENUM_ENTITIES: + return snd_media_enum_entities(card, argp); default: return -ENOIOCTLCMD; }
Add a helper function for creating entities representing ALSA PCM devices.
Signed-off-by: Clemens Ladisch clemens@ladisch.de --- include/sound/pcm.h | 6 ++++++ sound/core/pcm.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 0 deletions(-)
diff --git a/include/sound/pcm.h b/include/sound/pcm.h index 39e26a3..f0a80b7 100644 --- a/include/sound/pcm.h +++ b/include/sound/pcm.h @@ -480,6 +480,12 @@ int snd_pcm_new_internal(struct snd_card *card, const char *id, int device, int playback_count, int capture_count, struct snd_pcm **rpcm); int snd_pcm_new_stream(struct snd_pcm *pcm, int stream, int substream_count); +#ifdef CONFIG_SND_MEDIA +int snd_pcm_media_entities_create(struct snd_pcm *pcm, int stream, + unsigned int id); +#else +#define snd_pcm_media_entities_create(pcm, stream, id) +#endif
int snd_pcm_notify(struct snd_pcm_notify *notify, int nfree);
diff --git a/sound/core/pcm.c b/sound/core/pcm.c index 1a3070b..a138c57 100644 --- a/sound/core/pcm.c +++ b/sound/core/pcm.c @@ -25,11 +25,13 @@ #include <linux/time.h> #include <linux/mutex.h> #include <linux/device.h> +#include <linux/media.h> #include <sound/core.h> #include <sound/minors.h> #include <sound/pcm.h> #include <sound/control.h> #include <sound/info.h> +#include <sound/media.h>
MODULE_AUTHOR("Jaroslav Kysela perex@perex.cz, Abramo Bagnara abramo@alsa-project.org"); MODULE_DESCRIPTION("Midlevel PCM code for ALSA."); @@ -1136,6 +1138,50 @@ int snd_pcm_notify(struct snd_pcm_notify *notify, int nfree)
EXPORT_SYMBOL(snd_pcm_notify);
+#ifdef CONFIG_SND_MEDIA +static int snd_pcm_get_entity_desc(struct snd_card *card, + void *private_data, + struct media_entity_desc *desc) +{ + struct snd_pcm_substream *substream = private_data; + + if (memcmp(substream->name, "subdevice #", 11)) + strlcpy(desc->name, substream->name, sizeof(desc->name)); + else + strlcpy(desc->name, substream->pcm->name, sizeof(desc->name)); + desc->type = MEDIA_ENT_T_DEVNODE_ALSA; + desc->alsa.card = card->number; + desc->alsa.device = substream->pcm->device; + desc->alsa.subdevice = substream->number; + + return 0; +} + +int snd_pcm_media_entities_create(struct snd_pcm *pcm, int stream, + unsigned int id) +{ + struct snd_pcm_str *pstr = &pcm->streams[stream]; + struct snd_pcm_substream *substream; + int err; + + for (substream = pstr->substream; + substream; + substream = substream->next) { + err = snd_media_entity_create( + pcm->card, snd_pcm_get_entity_desc, + id + substream->number, + stream == SNDRV_PCM_STREAM_CAPTURE ? 1 : 0, + stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0, + substream); + if (err < 0) + return err; + } + + return 0; +} +EXPORT_SYMBOL(snd_pcm_media_entities_create); +#endif + #ifdef CONFIG_PROC_FS /* * Info interface
Allow to retrieve pad information.
Signed-off-by: Clemens Ladisch clemens@ladisch.de --- sound/core/media.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 44 insertions(+), 0 deletions(-)
diff --git a/sound/core/media.c b/sound/core/media.c index aa94175..96b09a3 100644 --- a/sound/core/media.c +++ b/sound/core/media.c @@ -161,6 +161,48 @@ static int snd_media_enum_entities(struct snd_card *card, return copy_to_user(descp, &desc, sizeof(desc)); }
+static int snd_media_enum_links(struct snd_card *card, + struct media_links_enum __user *linksp) +{ + struct media_links_enum links; + struct snd_media_entity *entity; + + if (copy_from_user(&links, linksp, sizeof(links))) + return -EFAULT; + + mutex_lock(&card->media_mutex); + entity = search_entity(card, links.entity); + mutex_unlock(&card->media_mutex); + if (!entity) + return -EINVAL; + + if (links.pads) { + struct media_pad_desc pad; + unsigned int i; + + memset(&pad, 0, sizeof(pad)); + pad.entity = entity->id; + + pad.flags = MEDIA_PAD_FL_SINK; + for (i = 0; i < entity->sinks; i++) { + if (copy_to_user(links.pads, &pad, sizeof(pad))) + return -EFAULT; + links.pads++; + pad.index++; + } + + pad.flags = MEDIA_PAD_FL_SOURCE; + for (i = 0; i < entity->sources; i++) { + if (copy_to_user(links.pads, &pad, sizeof(pad))) + return -EFAULT; + links.pads++; + pad.index++; + } + } + + return 0; +} + static int snd_media_control_ioctl(struct snd_card *card, struct snd_ctl_file *ctl_file, unsigned int cmd, unsigned long arg) @@ -172,6 +214,8 @@ static int snd_media_control_ioctl(struct snd_card *card, return snd_media_device_info(card, argp); case MEDIA_IOC_ENUM_ENTITIES: return snd_media_enum_entities(card, argp); + case MEDIA_IOC_ENUM_LINKS: + return snd_media_enum_links(card, argp); default: return -ENOIOCTLCMD; }
Allow drivers to create links between entities.
Signed-off-by: Clemens Ladisch clemens@ladisch.de --- include/sound/core.h | 3 ++ include/sound/media.h | 11 ++++++ sound/core/media.c | 86 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 0 deletions(-)
diff --git a/include/sound/core.h b/include/sound/core.h index 072b642..f6577ec 100644 --- a/include/sound/core.h +++ b/include/sound/core.h @@ -43,6 +43,7 @@ struct module; struct device; struct device_attribute; struct snd_media_card_ops; +struct snd_media_link;
/* device allocation stuff */
@@ -152,6 +153,8 @@ struct snd_card { const struct snd_media_card_ops *media_ops; struct mutex media_mutex; struct list_head media_entities; + unsigned int media_links_count; + struct snd_media_link *media_links; #endif };
diff --git a/include/sound/media.h b/include/sound/media.h index 65dd068..ebb7c7d 100644 --- a/include/sound/media.h +++ b/include/sound/media.h @@ -44,6 +44,11 @@ int snd_media_entity_create(struct snd_card *card, unsigned int id, unsigned int sinks, unsigned int sources, void *private_data); +int snd_media_link_create(struct snd_card *card, + unsigned int source_entity, + unsigned int source_pad, + unsigned int sink_entity, + unsigned int sink_pad);
int snd_media_create(struct snd_card *card); void __init snd_media_init(void); @@ -59,6 +64,12 @@ static inline int snd_media_entity_create(struct snd_card *card, unsigned int sinks, unsigned int sources, void *private_data) { return 0; } +static inline int snd_media_link_create(struct snd_card *card, + unsigned int source_entity, + unsigned int source_pad, + unsigned int sink_entity, + unsigned int sink_pad) +{ return 0; }
static inline int snd_media_create(struct snd_card *card) diff --git a/sound/core/media.c b/sound/core/media.c index 96b09a3..aa90848 100644 --- a/sound/core/media.c +++ b/sound/core/media.c @@ -36,6 +36,13 @@ struct snd_media_entity { unsigned int sinks, sources; };
+struct snd_media_link { + unsigned int source_entity; + unsigned int sink_entity; + unsigned short source_pad; + unsigned short sink_pad; +}; + static struct snd_media_entity *search_entity(struct snd_card *card, unsigned int id) { @@ -82,6 +89,47 @@ int snd_media_entity_create(struct snd_card *card, } EXPORT_SYMBOL(snd_media_entity_create);
+int snd_media_link_create(struct snd_card *card, + unsigned int source_entity, + unsigned int source_pad, + unsigned int sink_entity, + unsigned int sink_pad) +{ + struct snd_media_link link; + struct snd_media_link *links; + size_t new_size = 0; + + link.source_entity = source_entity; + link.sink_entity = sink_entity; + link.source_pad = source_pad; + link.sink_pad = sink_pad; + + mutex_lock(&card->media_mutex); + + if (card->media_links) { + size_t allocated = ksize(card->media_links); + if (allocated < (card->media_links_count + 1) * sizeof(link)) + new_size = allocated * 2; + } else { + new_size = 4 * sizeof(link); + } + if (new_size > 0) { + links = krealloc(card->media_links, new_size, GFP_KERNEL); + if (!links) { + mutex_unlock(&card->media_mutex); + return -ENOMEM; + } + card->media_links = links; + } + + card->media_links[card->media_links_count++] = link; + + mutex_unlock(&card->media_mutex); + + return 0; +} +EXPORT_SYMBOL(snd_media_link_create); + static int snd_media_dev_free(struct snd_device *device) { struct snd_card *card = device->device_data; @@ -94,6 +142,8 @@ static int snd_media_dev_free(struct snd_device *device) kfree(e); }
+ kfree(card->media_links); + return 0; }
@@ -136,6 +186,7 @@ static int snd_media_enum_entities(struct snd_card *card, { struct media_entity_desc desc; struct snd_media_entity *entity; + unsigned int i; int err;
if (copy_from_user(&desc, descp, sizeof(desc))) @@ -152,7 +203,13 @@ static int snd_media_enum_entities(struct snd_card *card, desc.flags = 0; desc.group_id = 0; desc.pads = entity->sinks + entity->sources; + desc.links = 0; + mutex_lock(&card->media_mutex); + for (i = 0; i < card->media_links_count; i++) + if (card->media_links[i].source_entity == desc.id) + desc.links++; + mutex_unlock(&card->media_mutex);
err = entity->get_desc(card, entity->private_data, &desc); if (err < 0) @@ -200,6 +257,35 @@ static int snd_media_enum_links(struct snd_card *card, } }
+ if (links.links) { + struct media_link_desc desc; + struct snd_media_link *link; + unsigned int i; + + memset(&desc, 0, sizeof(desc)); + desc.source.entity = links.entity; + desc.source.flags = MEDIA_PAD_FL_SOURCE; + desc.sink.flags = MEDIA_PAD_FL_SINK; + desc.flags = MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE; + + mutex_lock(&card->media_mutex); + for (i = 0; i < card->media_links_count; i++) { + link = &card->media_links[i]; + if (link->source_entity != links.entity) + continue; + + desc.source.index = link->source_pad; + desc.sink.entity = link->sink_entity; + desc.sink.index = link->sink_pad; + if (copy_to_user(links.links, &desc, sizeof(desc))) { + mutex_unlock(&card->media_mutex); + return -EFAULT; + } + links.links++; + } + mutex_unlock(&card->media_mutex); + } + return 0; }
Just for compatibility with the media controller API, implement the setup link ioctl, which does not actually do anything.
Signed-off-by: Clemens Ladisch clemens@ladisch.de --- sound/core/media.c | 41 +++++++++++++++++++++++++++++++++++++++++ 1 files changed, 41 insertions(+), 0 deletions(-)
diff --git a/sound/core/media.c b/sound/core/media.c index aa90848..cbc51d9 100644 --- a/sound/core/media.c +++ b/sound/core/media.c @@ -289,6 +289,45 @@ static int snd_media_enum_links(struct snd_card *card, return 0; }
+static int snd_media_setup_link(struct snd_card *card, + struct media_link_desc __user *descp) +{ + struct media_link_desc desc; + struct snd_media_entity *source, *sink; + struct snd_media_link *link; + unsigned int i; + int err; + + if (copy_from_user(&desc, descp, sizeof(desc))) + return -EFAULT; + + if (desc.flags != (MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE)) + return -EINVAL; + + mutex_lock(&card->media_mutex); + source = search_entity(card, desc.source.entity); + sink = search_entity(card, desc.sink.entity); + mutex_unlock(&card->media_mutex); + if (!source || !sink) + return -EINVAL; + + err = -EINVAL; + mutex_lock(&card->media_mutex); + for (i = 0; i < card->media_links_count; i++) { + link = &card->media_links[i]; + if (link->source_entity == desc.source.entity && + link->sink_entity == desc.sink.entity && + link->source_pad == desc.source.index && + link->sink_pad == desc.sink.index) { + err = 0; + break; + } + } + mutex_unlock(&card->media_mutex); + + return err; +} + static int snd_media_control_ioctl(struct snd_card *card, struct snd_ctl_file *ctl_file, unsigned int cmd, unsigned long arg) @@ -302,6 +341,8 @@ static int snd_media_control_ioctl(struct snd_card *card, return snd_media_enum_entities(card, argp); case MEDIA_IOC_ENUM_LINKS: return snd_media_enum_links(card, argp); + case MEDIA_IOC_SETUP_LINK: + return snd_media_setup_link(card, argp); default: return -ENOIOCTLCMD; }
Add entity types for describing the topology of sound devices.
JACK entities represent connections to the outside world. Properties of the jack will be described by a TLV on the jack entity.
MIXER entities add their inputs together, while SELECTOR entities route on of their inputs to the output. PROCESSING entities represent volume/ mute controls or other effects processing units. These entities may have ALSA controls associated with them, which is indicated by those controls having a TLV containing the entity ID and the control type.
SPLIT and MERGE entities are needed for HDA codecs, whose topology is constructed of stereo channel pairs.
Signed-off-by: Clemens Ladisch clemens@ladisch.de --- include/linux/media.h | 9 +++++++++ 1 files changed, 9 insertions(+), 0 deletions(-)
diff --git a/include/linux/media.h b/include/linux/media.h index 0ef8833..9d2ad75 100644 --- a/include/linux/media.h +++ b/include/linux/media.h @@ -57,6 +57,15 @@ struct media_device_info { #define MEDIA_ENT_T_V4L2_SUBDEV_FLASH (MEDIA_ENT_T_V4L2_SUBDEV + 2) #define MEDIA_ENT_T_V4L2_SUBDEV_LENS (MEDIA_ENT_T_V4L2_SUBDEV + 3)
+#define MEDIA_ENT_T_JACK (3 << MEDIA_ENT_TYPE_SHIFT) + +#define MEDIA_ENT_T_ALSA (4 << MEDIA_ENT_TYPE_SHIFT) +#define MEDIA_ENT_T_ALSA_MIXER (MEDIA_ENT_T_ALSA + 1) +#define MEDIA_ENT_T_ALSA_SELECTOR (MEDIA_ENT_T_ALSA + 2) +#define MEDIA_ENT_T_ALSA_PROCESSING (MEDIA_ENT_T_ALSA + 3) +#define MEDIA_ENT_T_ALSA_SPLIT (MEDIA_ENT_T_ALSA + 4) +#define MEDIA_ENT_T_ALSA_MERGE (MEDIA_ENT_T_ALSA + 5) + #define MEDIA_ENT_FL_DEFAULT (1 << 0)
struct media_entity_desc {
Add a helper for retrieving information about USB devices.
Signed-off-by: Clemens Ladisch clemens@ladisch.de --- include/sound/media.h | 16 ++++++++++++++++ sound/core/media.c | 28 ++++++++++++++++++++++++++++ sound/usb/card.c | 2 ++ 3 files changed, 46 insertions(+), 0 deletions(-)
diff --git a/include/sound/media.h b/include/sound/media.h index ebb7c7d..5339acb 100644 --- a/include/sound/media.h +++ b/include/sound/media.h @@ -39,6 +39,15 @@ typedef int (*snd_media_entity_get_desc_t)(struct snd_card *card,
#define snd_card_set_media_ops(card, ops) ((card)->media_ops = (ops))
+#if defined(CONFIG_USB) || defined(CONFIG_USB_MODULE) +int snd_media_get_usb_card_info(struct snd_card *card, + struct media_device_info *info); + +extern const struct snd_media_card_ops snd_media_default_usb_ops; +#define snd_card_set_media_ops_default_usb(card) \ + snd_card_set_media_ops((card), &snd_media_default_usb_ops) +#endif + int snd_media_entity_create(struct snd_card *card, snd_media_entity_get_desc_t get_desc, unsigned int id, @@ -58,6 +67,13 @@ void __exit snd_media_exit(void);
#define snd_card_set_media_ops(card, ops)
+#if defined(CONFIG_USB) || defined(CONFIG_USB_MODULE) +#define snd_card_set_media_ops_default_usb(card) +static inline int snd_media_get_usb_card_info(struct snd_card *card, + struct media_device_info *info) +{ return 0; } +#endif + static inline int snd_media_entity_create(struct snd_card *card, snd_media_entity_get_desc_t get_desc, unsigned int id, diff --git a/sound/core/media.c b/sound/core/media.c index cbc51d9..f42e00b 100644 --- a/sound/core/media.c +++ b/sound/core/media.c @@ -24,6 +24,7 @@ #include <linux/media.h> #include <linux/string.h> #include <linux/uaccess.h> +#include <linux/usb.h> #include <sound/core.h> #include <sound/control.h> #include <sound/media.h> @@ -328,6 +329,33 @@ static int snd_media_setup_link(struct snd_card *card, return err; }
+#if defined(CONFIG_USB) || defined(CONFIG_USB_MODULE) +int snd_media_get_usb_card_info(struct snd_card *card, + struct media_device_info *info) +{ + struct usb_interface *intf; + struct usb_device *usb; + + if (snd_BUG_ON(!card->dev)) + return -ENXIO; + intf = to_usb_interface(card->dev); + usb = interface_to_usbdev(intf); + + if (usb->serial) + strlcpy(info->serial, usb->serial, sizeof(info->serial)); + usb_make_path(usb, info->bus_info, sizeof(info->bus_info)); + info->hw_revision = le16_to_cpu(usb->descriptor.bcdDevice); + + return 0; +} +EXPORT_SYMBOL(snd_media_get_usb_card_info); + +const struct snd_media_card_ops snd_media_default_usb_ops = { + .get_info = snd_media_get_usb_card_info, +}; +EXPORT_SYMBOL(snd_media_default_usb_ops); +#endif + static int snd_media_control_ioctl(struct snd_card *card, struct snd_ctl_file *ctl_file, unsigned int cmd, unsigned long arg) diff --git a/sound/usb/card.c b/sound/usb/card.c index d5b5c33..311366b 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -52,6 +52,7 @@ #include <sound/control.h> #include <sound/core.h> #include <sound/info.h> +#include <sound/media.h> #include <sound/pcm.h> #include <sound/pcm_params.h> #include <sound/initval.h> @@ -490,6 +491,7 @@ snd_usb_audio_probe(struct usb_device *dev, goto __error; } snd_card_set_dev(chip->card, &intf->dev); + snd_card_set_media_ops_default_usb(chip->card); chip->pm_intf = intf; break; }
Signed-off-by: Clemens Ladisch clemens@ladisch.de --- sound/usb/quirks.c | 4 ++-- sound/usb/stream.c | 16 +++++++++++----- sound/usb/stream.h | 1 + 3 files changed, 14 insertions(+), 7 deletions(-)
diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 2781726..293b4ba 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -152,7 +152,7 @@ static int create_fixed_stream_quirk(struct snd_usb_audio *chip,
stream = (fp->endpoint & USB_DIR_IN) ? SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK; - err = snd_usb_add_audio_stream(chip, stream, fp); + err = snd_usb_add_audio_stream(chip, stream, 0, fp); if (err < 0) { kfree(fp); kfree(rate_table); @@ -258,7 +258,7 @@ static int create_uaxx_quirk(struct snd_usb_audio *chip,
stream = (fp->endpoint & USB_DIR_IN) ? SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK; - err = snd_usb_add_audio_stream(chip, stream, fp); + err = snd_usb_add_audio_stream(chip, stream, 0, fp); if (err < 0) { kfree(fp); return err; diff --git a/sound/usb/stream.c b/sound/usb/stream.c index e166eb5..145324d 100644 --- a/sound/usb/stream.c +++ b/sound/usb/stream.c @@ -78,7 +78,7 @@ static void snd_usb_audio_pcm_free(struct snd_pcm *pcm) */
static void snd_usb_init_substream(struct snd_usb_stream *as, - int stream, + int stream, u8 terminal_id, struct audioformat *fp) { struct snd_usb_substream *subs = &as->substream[stream]; @@ -98,6 +98,9 @@ static void snd_usb_init_substream(struct snd_usb_stream *as, subs->num_formats++; subs->fmt_type = fp->fmt_type; subs->ep_num = fp->endpoint; + + if (terminal_id > 0) + snd_pcm_media_entities_create(as->pcm, stream, terminal_id); }
/* @@ -106,7 +109,7 @@ static void snd_usb_init_substream(struct snd_usb_stream *as, * if not, create a new pcm stream. */ int snd_usb_add_audio_stream(struct snd_usb_audio *chip, - int stream, + int stream, u8 terminal_id, struct audioformat *fp) { struct list_head *p; @@ -138,7 +141,7 @@ int snd_usb_add_audio_stream(struct snd_usb_audio *chip, err = snd_pcm_new_stream(as->pcm, stream, 1); if (err < 0) return err; - snd_usb_init_substream(as, stream, fp); + snd_usb_init_substream(as, stream, terminal_id, fp); return 0; }
@@ -166,7 +169,7 @@ int snd_usb_add_audio_stream(struct snd_usb_audio *chip, else strcpy(pcm->name, "USB Audio");
- snd_usb_init_substream(as, stream, fp); + snd_usb_init_substream(as, stream, terminal_id, fp);
list_add(&as->list, &chip->pcm_list); chip->pcm_devs++; @@ -260,6 +263,7 @@ int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no) struct audioformat *fp = NULL; int num, protocol, clock = 0; struct uac_format_type_i_continuous_descriptor *fmt; + u8 terminal_id = 0;
dev = chip->dev;
@@ -324,6 +328,7 @@ int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no) }
format = le16_to_cpu(as->wFormatTag); /* remember the format value */ + terminal_id = as->bTerminalLink; break; }
@@ -347,6 +352,7 @@ int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no)
num_channels = as->bNrChannels; format = le32_to_cpu(as->bmFormats); + terminal_id = as->bTerminalLink;
/* lookup the terminal associated to this interface * to extract the clock */ @@ -460,7 +466,7 @@ int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no) }
snd_printdd(KERN_INFO "%d:%u:%d: add audio endpoint %#x\n", dev->devnum, iface_no, altno, fp->endpoint); - err = snd_usb_add_audio_stream(chip, stream, fp); + err = snd_usb_add_audio_stream(chip, stream, terminal_id, fp); if (err < 0) { kfree(fp->rate_table); kfree(fp); diff --git a/sound/usb/stream.h b/sound/usb/stream.h index c97f679..31f17c0 100644 --- a/sound/usb/stream.h +++ b/sound/usb/stream.h @@ -6,6 +6,7 @@ int snd_usb_parse_audio_interface(struct snd_usb_audio *chip,
int snd_usb_add_audio_stream(struct snd_usb_audio *chip, int stream, + u8 terminal_id, struct audioformat *fp);
#endif /* __USBAUDIO_STREAM_H */
Use the terminal/unit descriptors to create entities and links.
(This should be integrated with parse_audio_unit(), but that function does not yet support all unit types.)
Signed-off-by: Clemens Ladisch clemens@ladisch.de --- sound/usb/mixer.c | 278 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 278 insertions(+), 0 deletions(-)
diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 4f40ba8..a5fa807 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -45,6 +45,7 @@ #include <linux/bitops.h> #include <linux/init.h> #include <linux/list.h> +#include <linux/media.h> #include <linux/slab.h> #include <linux/string.h> #include <linux/usb.h> @@ -55,6 +56,7 @@ #include <sound/control.h> #include <sound/hwdep.h> #include <sound/info.h> +#include <sound/media.h> #include <sound/tlv.h>
#include "usbaudio.h" @@ -1969,6 +1971,278 @@ static int snd_usb_mixer_dev_free(struct snd_device *device) return 0; }
+static void unit_name(struct media_entity_desc *entity, + const u8 *desc, const char *prefix) +{ + /* TODO: read the terminal name from the device */ + sprintf(entity->name, "%s%u", prefix, desc[3]); +} + +static int input_terminal_get_desc(struct snd_card *card, + void *private_data, + struct media_entity_desc *desc) +{ + unit_name(desc, private_data, "IT"); + desc->type = MEDIA_ENT_T_JACK; + return 0; +} + +static int output_terminal_get_desc(struct snd_card *card, + void *private_data, + struct media_entity_desc *desc) +{ + unit_name(desc, private_data, "OT"); + desc->type = MEDIA_ENT_T_JACK; + return 0; +} + +static int mixer_unit_get_desc(struct snd_card *card, + void *private_data, + struct media_entity_desc *desc) +{ + unit_name(desc, private_data, "MU"); + desc->type = MEDIA_ENT_T_ALSA_MIXER; + return 0; +} + +static int selector_unit_get_desc(struct snd_card *card, + void *private_data, + struct media_entity_desc *desc) +{ + unit_name(desc, private_data, "SU"); + desc->type = MEDIA_ENT_T_ALSA_SELECTOR; + return 0; +} + +static int feature_unit_get_desc(struct snd_card *card, + void *private_data, + struct media_entity_desc *desc) +{ + unit_name(desc, private_data, "FU"); + desc->type = MEDIA_ENT_T_ALSA_PROCESSING; + return 0; +} + +static int processing_unit_get_desc(struct snd_card *card, + void *private_data, + struct media_entity_desc *desc) +{ + unit_name(desc, private_data, "PU"); + desc->type = MEDIA_ENT_T_ALSA_PROCESSING; + return 0; +} + +static int extension_unit_get_desc(struct snd_card *card, + void *private_data, + struct media_entity_desc *desc) +{ + unit_name(desc, private_data, "XU"); + desc->type = MEDIA_ENT_T_ALSA_PROCESSING; + return 0; +} + +static int effect_unit_v2_get_desc(struct snd_card *card, + void *private_data, + struct media_entity_desc *desc) +{ + unit_name(desc, private_data, "EU"); + desc->type = MEDIA_ENT_T_ALSA_PROCESSING; + return 0; +} + +static int src_v2_get_desc(struct snd_card *card, + void *private_data, + struct media_entity_desc *desc) +{ + unit_name(desc, private_data, "SRC"); + desc->type = MEDIA_ENT_T_ALSA_PROCESSING; + return 0; +} + +static int snd_usb_mixer_entity(struct mixer_build *state, const u8 *desc) +{ + snd_media_entity_get_desc_t get_desc = NULL; + unsigned int sinks = 0, sources = 0, source_ids_count = 0, i; + const u8 *source_ids = NULL; + u8 source_id = 0; + int err; + + if (desc[0] < 4) + return 0; + + if (state->mixer->protocol == UAC_VERSION_1) { + switch (desc[2]) { + case UAC_INPUT_TERMINAL: + if (desc[0] >= 6 && + desc[5] != (UAC_TERMINAL_STREAMING >> 8)) { + get_desc = &input_terminal_get_desc; + sources = 1; + } + break; + case UAC_OUTPUT_TERMINAL: + if (desc[0] >= 8) { + source_id = desc[7]; + if (desc[5] != (UAC_TERMINAL_STREAMING >> 8)) + get_desc = &output_terminal_get_desc; + } + break; + case UAC_MIXER_UNIT: + if (desc[0] >= 6) { + source_ids = &desc[5]; + get_desc = &mixer_unit_get_desc; + sources = 1; + } + break; + case UAC_SELECTOR_UNIT: + if (desc[0] >= 6) { + source_ids = &desc[5]; + get_desc = &selector_unit_get_desc; + sources = 1; + } + break; + case UAC_FEATURE_UNIT: + if (desc[0] >= 5) { + source_id = desc[4]; + get_desc = &feature_unit_get_desc; + sources = 1; + } + break; + case UAC1_PROCESSING_UNIT: + if (desc[0] >= 8) { + source_ids = &desc[7]; + get_desc = &processing_unit_get_desc; + sources = 1; + } + break; + case UAC1_EXTENSION_UNIT: + if (desc[0] >= 7 && desc[0] >= 7 + desc[6]) { + source_ids_count = desc[6]; + source_ids = &desc[7]; + get_desc = &extension_unit_get_desc; + sources = 1; + } + break; + } + } else { + switch (desc[2]) { + case UAC_INPUT_TERMINAL: + if (desc[0] >= 6 && + desc[5] != (UAC_TERMINAL_STREAMING >> 8)) { + get_desc = &input_terminal_get_desc; + sources = 1; + } + break; + case UAC_OUTPUT_TERMINAL: + if (desc[0] >= 8) { + source_id = desc[7]; + if (desc[5] != (UAC_TERMINAL_STREAMING >> 8)) + get_desc = &output_terminal_get_desc; + } + break; + case UAC_MIXER_UNIT: + if (desc[0] >= 5 && desc[0] >= 5 + desc[4]) { + source_ids_count = desc[4]; + source_ids = &desc[5]; + get_desc = &mixer_unit_get_desc; + sources = 1; + } + break; + case UAC_SELECTOR_UNIT: + if (desc[0] >= 5 && desc[0] >= 5 + desc[4]) { + source_ids_count = desc[4]; + source_ids = &desc[5]; + get_desc = &selector_unit_get_desc; + sources = 1; + } + break; + case UAC_FEATURE_UNIT: + if (desc[0] >= 5) { + source_id = desc[4]; + get_desc = &feature_unit_get_desc; + sources = 1; + } + break; + case UAC2_EFFECT_UNIT: + if (desc[0] >= 7) { + source_id = desc[6]; + get_desc = &effect_unit_v2_get_desc; + sources = 1; + } + break; + case UAC2_PROCESSING_UNIT_V2: + if (desc[0] >= 7 && desc[0] >= 7 + desc[6]) { + source_ids_count = desc[6]; + source_ids = &desc[7]; + get_desc = &processing_unit_get_desc; + sources = 1; + } + break; + case UAC2_EXTENSION_UNIT_V2: + if (desc[0] >= 7 && desc[0] >= 7 + desc[6]) { + source_ids_count = desc[6]; + source_ids = &desc[7]; + get_desc = &extension_unit_get_desc; + sources = 1; + } + break; + case UAC2_SAMPLE_RATE_CONVERTER: + if (desc[0] >= 5) { + source_id = desc[4]; + get_desc = &src_v2_get_desc; + sources = 1; + } + break; + } + } + + if (source_id != 0) { + err = snd_media_link_create(state->chip->card, + source_id, 0, desc[3], 0); + if (err < 0) + return err; + sinks = 1; + } + else if (source_ids != NULL) { + if (source_ids_count == 0 && + source_ids + source_ids_count <= desc + desc[0]) + source_ids_count = desc + desc[0] - source_ids; + for (i = 0; i < source_ids_count; i++) { + err = snd_media_link_create(state->chip->card, + source_ids[i], 0, + desc[3], i); + if (err < 0) + return err; + } + sinks = source_ids_count; + } + + if (get_desc) { + err = snd_media_entity_create(state->chip->card, get_desc, + desc[3], sinks, sources, + (void *)desc); + if (err < 0) + return err; + } + + return 0; +} + +static int snd_usb_mixer_entities(struct mixer_build *state) +{ + void *p; + int err; + + p = NULL; + while ((p = snd_usb_find_desc(state->mixer->hostif->extra, + state->mixer->hostif->extralen, + p, USB_DT_CS_INTERFACE)) != NULL) { + err = snd_usb_mixer_entity(state, p); + if (err < 0) + return err; + } + return 0; +} + /* * create mixer controls * @@ -1997,6 +2271,10 @@ static int snd_usb_mixer_controls(struct usb_mixer_interface *mixer) } }
+ err = snd_usb_mixer_entities(&state); + if (err < 0) + return err; + p = NULL; while ((p = snd_usb_find_csint_desc(mixer->hostif->extra, mixer->hostif->extralen, p, UAC_OUTPUT_TERMINAL)) != NULL) {
Add a helper for retrieving information about PCI devices.
(Codec topology information is not yet implemented.)
Signed-off-by: Clemens Ladisch clemens@ladisch.de --- include/sound/media.h | 16 ++++++++++++++++ sound/core/media.c | 36 ++++++++++++++++++++++++++++++++++++ sound/pci/hda/hda_intel.c | 2 ++ 3 files changed, 54 insertions(+), 0 deletions(-)
diff --git a/include/sound/media.h b/include/sound/media.h index 5339acb..e030e4c 100644 --- a/include/sound/media.h +++ b/include/sound/media.h @@ -39,6 +39,15 @@ typedef int (*snd_media_entity_get_desc_t)(struct snd_card *card,
#define snd_card_set_media_ops(card, ops) ((card)->media_ops = (ops))
+#ifdef CONFIG_PCI +int snd_media_get_pci_card_info(struct snd_card *card, + struct media_device_info *info); + +extern const struct snd_media_card_ops snd_media_default_pci_ops; +#define snd_card_set_media_ops_default_pci(card) \ + snd_card_set_media_ops((card), &snd_media_default_pci_ops) +#endif + #if defined(CONFIG_USB) || defined(CONFIG_USB_MODULE) int snd_media_get_usb_card_info(struct snd_card *card, struct media_device_info *info); @@ -67,6 +76,13 @@ void __exit snd_media_exit(void);
#define snd_card_set_media_ops(card, ops)
+#ifdef CONFIG_PCI +#define snd_card_set_media_ops_default_pci(card) +static inline int snd_media_get_pci_card_info(struct snd_card *card, + struct media_device_info *info) +{ return 0; } +#endif + #if defined(CONFIG_USB) || defined(CONFIG_USB_MODULE) #define snd_card_set_media_ops_default_usb(card) static inline int snd_media_get_usb_card_info(struct snd_card *card, diff --git a/sound/core/media.c b/sound/core/media.c index f42e00b..9da058d 100644 --- a/sound/core/media.c +++ b/sound/core/media.c @@ -18,10 +18,12 @@ */
#include <linux/device.h> +#include <linux/export.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/list.h> #include <linux/media.h> +#include <linux/pci.h> #include <linux/string.h> #include <linux/uaccess.h> #include <linux/usb.h> @@ -329,6 +331,40 @@ static int snd_media_setup_link(struct snd_card *card, return err; }
+#ifdef CONFIG_PCI +int snd_media_get_pci_card_info(struct snd_card *card, + struct media_device_info *info) +{ + struct pci_dev *pci; + + if (snd_BUG_ON(!card->dev)) + return -ENXIO; + pci = to_pci_dev(card->dev); + + if (pci_is_pcie(pci)) { + int pos = pci_find_ext_capability(pci, PCI_EXT_CAP_ID_DSN); + if (pos) { + u32 eui[2]; + + pci_read_config_dword(pci, pos + 4, &eui[0]); + pci_read_config_dword(pci, pos + 8, &eui[1]); + sprintf(info->serial, "%08x%08x", eui[1], eui[0]); + } + } + snprintf(info->bus_info, sizeof(info->bus_info), "%s:%s", + pci_is_pcie(pci) ? "PCIe" : "PCI", pci_name(pci)); + info->hw_revision = pci->revision; + + return 0; +} +EXPORT_SYMBOL(snd_media_get_pci_card_info); + +const struct snd_media_card_ops snd_media_default_pci_ops = { + .get_info = snd_media_get_pci_card_info, +}; +EXPORT_SYMBOL(snd_media_default_pci_ops); +#endif + #if defined(CONFIG_USB) || defined(CONFIG_USB_MODULE) int snd_media_get_usb_card_info(struct snd_card *card, struct media_device_info *info) diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index 1c9c779..a01a43c 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -54,6 +54,7 @@ #endif #include <sound/core.h> #include <sound/initval.h> +#include <sound/media.h> #include <linux/vgaarb.h> #include <linux/vga_switcheroo.h> #include <linux/firmware.h> @@ -3305,6 +3306,7 @@ static int __devinit azx_probe(struct pci_dev *pci, }
snd_card_set_dev(card, &pci->dev); + snd_card_set_media_ops_default_pci(card);
err = azx_create(card, pci, dev, pci_id->driver_data, &chip); if (err < 0)
Hi Clemens,
At Tue, 28 Aug 2012 00:28:30 +0200, Clemens Ladisch wrote:
Hi,
these are some tentative patches to allow sound drivers to provide routing information to userspace. (So far, snd-usb-audio has a partial implementation; I haven't yet had time for snd-hda-intel. I haven't even looked at ASoC.)
Thanks for patches. This brought up interesting discussions at Plumbers. We (kind of) agree that the media controller API can be pretty suitable, and even Mark didn't grumble so much against it :) It's a good sign.
The media controller API <linux/media.h> allows prividing routing information, but its implementation does not quite fit the sound drivers:
- it allows reconfiguration of links, but not in a way that would be useful for ALSA selector controls;
- it allows drivers to access the entity graph and pipelines, which is not needed;
- it is rather heavyweight.
Therefore, these patches create their own implementation of the media controller API. In this implementation, all entity and link information is completely static, and more space-efficient.
I think this is fine for USB-audio and HD-audio. We don't expect that the changes are done via media controller API but it'll provide just some mapping. But, possible embedded guys might want to make it configurable. (Well, in that case, they can reimplement the full stack based on the original media controller helper code...) Maybe Mark can give more comments on it.
For simplicity, the ioctls hook into the ALSA control device instead of creating a new /dev/media* device.
This is OK, I think. But what about multiple codecs on a single control device? Can they be represented well?
The separate implementation does not allow sharing of one media controller device in the case of combined audio/video devices. However, separate drivers would already be a problem for HDMI outputs (GPU and sound); it appears we might need some mechanism to connect the topologies of multiple media devices.
The connection between different devices could be done in a different way. For example, we may provide some Linux device tree connection. (This reminds me of a TODO -- the implementation of standard Linux device model to ALSA components, BTW.)
TLVs (for jack entities, and for associating controls with entities) are not yet defined.
OK, this is a big missing piece. But it should be relatively easy.
Takashi
Example output for my simple UA-1A device (not all information is decoded correctly):
$ media-ctl -p -d /dev/snd/controlC5 Opening media device /dev/snd/controlC5 Enumerating entities Found 4 entities Enumerating pads and links Media controller API version 0.0.0
Media device information
driver snd-usb-audio model EDIROL UA-1A serial bus info usb-0000:00:12.2-4.3 hw revision 0x101 driver version 0.0.0
Device topology
entity 1: USB Audio (1 pad, 1 link) type Node subtype ALSA device node name /dev/tty (should be hw:5,0,0) pad0: Source -> "OT3":0 [ENABLED,IMMUTABLE]
entity 3: OT3 (1 pad, 1 link) type Unknown subtype Unknown (should be Jack) pad0: Sink <- "USB Audio":0 [ENABLED,IMMUTABLE]
entity 4: IT4 (1 pad, 1 link) type Unknown subtype Unknown pad0: Source -> "USB Audio":0 [ENABLED,IMMUTABLE]
entity 7: USB Audio (1 pad, 1 link) type Node subtype ALSA device node name /dev/tty pad0: Sink <- "IT4":0 [ENABLED,IMMUTABLE]
include/linux/media.h | 9 include/sound/core.h | 13 + include/sound/media.h | 116 ++++++++++ include/sound/pcm.h | 6 sound/core/Kconfig | 7 sound/core/Makefile | 1 sound/core/init.c | 4 sound/core/media.c | 425 ++++++++++++++++++++++++++++++++++++++ sound/core/pcm.c | 46 ++++ sound/core/sound.c | 3 sound/pci/hda/hda_intel.c | 2 sound/usb/card.c | 2 sound/usb/mixer.c | 278 ++++++++++++++++++++++++ sound/usb/quirks.c | 4 sound/usb/stream.c | 16 - sound/usb/stream.h | 1 16 files changed, 925 insertions(+), 8 deletions(-)
Regards, Clemens _______________________________________________ Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
Takashi Iwai wrote:
Clemens Ladisch wrote:
The media controller API <linux/media.h> allows prividing routing information, but its implementation does not quite fit the sound drivers:
- it allows reconfiguration of links, but not in a way that would be useful for ALSA selector controls
I didn't mention why: it is not possible to enable two links to a sink pad, and neither can one change the state of two links atomically; therefore, an enum control with exactly one active input cannot be modelled. (It might be possible to change the media controller API to allow this.)
Mute controls _could_ be modelled by disabling links, but with all other mixer control types not represented in the media controller API, this doesn't appear to be very useful.
But what about multiple codecs on a single control device? Can they be represented well?
The driver has to choose the entity IDs so that they don't conflict; something like busnumber<<20 | codecaddress<<10 | nodeid. The USB audio driver will have this problem if there are multiple audio control interfaces, so I guess it will have to include the interface number.
Regards, Clemens
participants (3)
-
Clemens Ladisch
-
Mark Brown
-
Takashi Iwai