2019-05-27 13:55:05 +07:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* Device management routines
|
2007-10-15 14:50:19 +07:00
|
|
|
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/time.h>
|
2011-09-22 20:34:58 +07:00
|
|
|
#include <linux/export.h>
|
2005-04-17 05:20:36 +07:00
|
|
|
#include <linux/errno.h>
|
|
|
|
#include <sound/core.h>
|
|
|
|
|
|
|
|
/**
|
|
|
|
* snd_device_new - create an ALSA device component
|
|
|
|
* @card: the card instance
|
2005-06-30 00:30:42 +07:00
|
|
|
* @type: the device type, SNDRV_DEV_XXX
|
2005-04-17 05:20:36 +07:00
|
|
|
* @device_data: the data pointer of this device
|
|
|
|
* @ops: the operator table
|
|
|
|
*
|
|
|
|
* Creates a new device component for the given data pointer.
|
|
|
|
* The device will be assigned to the card and managed together
|
|
|
|
* by the card.
|
|
|
|
*
|
|
|
|
* The data pointer plays a role as the identifier, too, so the
|
|
|
|
* pointer address must be unique and unchanged.
|
|
|
|
*
|
2013-03-12 04:05:14 +07:00
|
|
|
* Return: Zero if successful, or a negative error code on failure.
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2014-01-29 21:13:33 +07:00
|
|
|
int snd_device_new(struct snd_card *card, enum snd_device_type type,
|
2020-01-03 15:16:19 +07:00
|
|
|
void *device_data, const struct snd_device_ops *ops)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:51:18 +07:00
|
|
|
struct snd_device *dev;
|
ALSA: Use priority list for managing device list
Basically, the device type specifies the priority of the device to be
registered / freed, too. However, the priority value isn't well
utilized but only it's checked as a group. This results in
inconsistent register and free order (where each of them should be in
reversed direction).
This patch simplifies the device list management code by simply
inserting a list entry at creation time in an incremental order for
the priority value. Since we can just follow the link for register,
disconnect and free calls, we don't have to specify the group; so the
whole enum definitions are also simplified as well.
The visible change to outside is that the priorities of some object
types are revisited. For example, now the SNDRV_DEV_LOWLEVEL object
is registered before others (control, PCM, etc) and, in return,
released after others. Similarly, SNDRV_DEV_CODEC is in a lower
priority than SNDRV_DEV_BUS for ensuring the dependency.
Also, the unused SNDRV_DEV_TOPLEVEL, SNDRV_DEV_LOWLEVEL_PRE and
SNDRV_DEV_LOWLEVEL_NORMAL are removed as a cleanup.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2014-01-29 21:53:35 +07:00
|
|
|
struct list_head *p;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2008-08-08 22:09:09 +07:00
|
|
|
if (snd_BUG_ON(!card || !device_data || !ops))
|
|
|
|
return -ENXIO;
|
2005-09-09 19:20:23 +07:00
|
|
|
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
|
2015-03-10 21:42:14 +07:00
|
|
|
if (!dev)
|
2005-04-17 05:20:36 +07:00
|
|
|
return -ENOMEM;
|
ALSA: Use priority list for managing device list
Basically, the device type specifies the priority of the device to be
registered / freed, too. However, the priority value isn't well
utilized but only it's checked as a group. This results in
inconsistent register and free order (where each of them should be in
reversed direction).
This patch simplifies the device list management code by simply
inserting a list entry at creation time in an incremental order for
the priority value. Since we can just follow the link for register,
disconnect and free calls, we don't have to specify the group; so the
whole enum definitions are also simplified as well.
The visible change to outside is that the priorities of some object
types are revisited. For example, now the SNDRV_DEV_LOWLEVEL object
is registered before others (control, PCM, etc) and, in return,
released after others. Similarly, SNDRV_DEV_CODEC is in a lower
priority than SNDRV_DEV_BUS for ensuring the dependency.
Also, the unused SNDRV_DEV_TOPLEVEL, SNDRV_DEV_LOWLEVEL_PRE and
SNDRV_DEV_LOWLEVEL_NORMAL are removed as a cleanup.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2014-01-29 21:53:35 +07:00
|
|
|
INIT_LIST_HEAD(&dev->list);
|
2005-04-17 05:20:36 +07:00
|
|
|
dev->card = card;
|
|
|
|
dev->type = type;
|
|
|
|
dev->state = SNDRV_DEV_BUILD;
|
|
|
|
dev->device_data = device_data;
|
|
|
|
dev->ops = ops;
|
ALSA: Use priority list for managing device list
Basically, the device type specifies the priority of the device to be
registered / freed, too. However, the priority value isn't well
utilized but only it's checked as a group. This results in
inconsistent register and free order (where each of them should be in
reversed direction).
This patch simplifies the device list management code by simply
inserting a list entry at creation time in an incremental order for
the priority value. Since we can just follow the link for register,
disconnect and free calls, we don't have to specify the group; so the
whole enum definitions are also simplified as well.
The visible change to outside is that the priorities of some object
types are revisited. For example, now the SNDRV_DEV_LOWLEVEL object
is registered before others (control, PCM, etc) and, in return,
released after others. Similarly, SNDRV_DEV_CODEC is in a lower
priority than SNDRV_DEV_BUS for ensuring the dependency.
Also, the unused SNDRV_DEV_TOPLEVEL, SNDRV_DEV_LOWLEVEL_PRE and
SNDRV_DEV_LOWLEVEL_NORMAL are removed as a cleanup.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2014-01-29 21:53:35 +07:00
|
|
|
|
|
|
|
/* insert the entry in an incrementally sorted list */
|
|
|
|
list_for_each_prev(p, &card->devices) {
|
|
|
|
struct snd_device *pdev = list_entry(p, struct snd_device, list);
|
|
|
|
if ((unsigned int)pdev->type <= (unsigned int)type)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
list_add(&dev->list, p);
|
2005-04-17 05:20:36 +07:00
|
|
|
return 0;
|
|
|
|
}
|
2006-04-28 20:13:39 +07:00
|
|
|
EXPORT_SYMBOL(snd_device_new);
|
|
|
|
|
2015-02-28 00:01:22 +07:00
|
|
|
static void __snd_device_disconnect(struct snd_device *dev)
|
2014-02-04 17:36:11 +07:00
|
|
|
{
|
|
|
|
if (dev->state == SNDRV_DEV_REGISTERED) {
|
|
|
|
if (dev->ops->dev_disconnect &&
|
|
|
|
dev->ops->dev_disconnect(dev))
|
|
|
|
dev_err(dev->card->dev, "device disconnect failure\n");
|
|
|
|
dev->state = SNDRV_DEV_DISCONNECTED;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void __snd_device_free(struct snd_device *dev)
|
|
|
|
{
|
|
|
|
/* unlink */
|
|
|
|
list_del(&dev->list);
|
|
|
|
|
|
|
|
__snd_device_disconnect(dev);
|
|
|
|
if (dev->ops->dev_free) {
|
|
|
|
if (dev->ops->dev_free(dev))
|
|
|
|
dev_err(dev->card->dev, "device free failure\n");
|
|
|
|
}
|
|
|
|
kfree(dev);
|
|
|
|
}
|
|
|
|
|
ALSA: Use priority list for managing device list
Basically, the device type specifies the priority of the device to be
registered / freed, too. However, the priority value isn't well
utilized but only it's checked as a group. This results in
inconsistent register and free order (where each of them should be in
reversed direction).
This patch simplifies the device list management code by simply
inserting a list entry at creation time in an incremental order for
the priority value. Since we can just follow the link for register,
disconnect and free calls, we don't have to specify the group; so the
whole enum definitions are also simplified as well.
The visible change to outside is that the priorities of some object
types are revisited. For example, now the SNDRV_DEV_LOWLEVEL object
is registered before others (control, PCM, etc) and, in return,
released after others. Similarly, SNDRV_DEV_CODEC is in a lower
priority than SNDRV_DEV_BUS for ensuring the dependency.
Also, the unused SNDRV_DEV_TOPLEVEL, SNDRV_DEV_LOWLEVEL_PRE and
SNDRV_DEV_LOWLEVEL_NORMAL are removed as a cleanup.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2014-01-29 21:53:35 +07:00
|
|
|
static struct snd_device *look_for_dev(struct snd_card *card, void *device_data)
|
|
|
|
{
|
|
|
|
struct snd_device *dev;
|
|
|
|
|
|
|
|
list_for_each_entry(dev, &card->devices, list)
|
|
|
|
if (dev->device_data == device_data)
|
|
|
|
return dev;
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2015-02-28 00:01:22 +07:00
|
|
|
/**
|
|
|
|
* snd_device_disconnect - disconnect the device
|
|
|
|
* @card: the card instance
|
|
|
|
* @device_data: the data pointer to disconnect
|
|
|
|
*
|
|
|
|
* Turns the device into the disconnection state, invoking
|
|
|
|
* dev_disconnect callback, if the device was already registered.
|
|
|
|
*
|
|
|
|
* Usually called from snd_card_disconnect().
|
|
|
|
*
|
|
|
|
* Return: Zero if successful, or a negative error code on failure or if the
|
|
|
|
* device not found.
|
|
|
|
*/
|
|
|
|
void snd_device_disconnect(struct snd_card *card, void *device_data)
|
|
|
|
{
|
|
|
|
struct snd_device *dev;
|
|
|
|
|
|
|
|
if (snd_BUG_ON(!card || !device_data))
|
|
|
|
return;
|
|
|
|
dev = look_for_dev(card, device_data);
|
|
|
|
if (dev)
|
|
|
|
__snd_device_disconnect(dev);
|
|
|
|
else
|
2017-09-07 03:28:01 +07:00
|
|
|
dev_dbg(card->dev, "device disconnect %p (from %pS), not found\n",
|
2015-02-28 00:01:22 +07:00
|
|
|
device_data, __builtin_return_address(0));
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_device_disconnect);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/**
|
|
|
|
* snd_device_free - release the device from the card
|
|
|
|
* @card: the card instance
|
|
|
|
* @device_data: the data pointer to release
|
|
|
|
*
|
|
|
|
* Removes the device from the list on the card and invokes the
|
2006-06-23 19:38:23 +07:00
|
|
|
* callbacks, dev_disconnect and dev_free, corresponding to the state.
|
2005-04-17 05:20:36 +07:00
|
|
|
* Then release the device.
|
|
|
|
*/
|
2014-02-04 17:36:11 +07:00
|
|
|
void snd_device_free(struct snd_card *card, void *device_data)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:51:18 +07:00
|
|
|
struct snd_device *dev;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2008-08-08 22:09:09 +07:00
|
|
|
if (snd_BUG_ON(!card || !device_data))
|
2014-02-04 17:36:11 +07:00
|
|
|
return;
|
ALSA: Use priority list for managing device list
Basically, the device type specifies the priority of the device to be
registered / freed, too. However, the priority value isn't well
utilized but only it's checked as a group. This results in
inconsistent register and free order (where each of them should be in
reversed direction).
This patch simplifies the device list management code by simply
inserting a list entry at creation time in an incremental order for
the priority value. Since we can just follow the link for register,
disconnect and free calls, we don't have to specify the group; so the
whole enum definitions are also simplified as well.
The visible change to outside is that the priorities of some object
types are revisited. For example, now the SNDRV_DEV_LOWLEVEL object
is registered before others (control, PCM, etc) and, in return,
released after others. Similarly, SNDRV_DEV_CODEC is in a lower
priority than SNDRV_DEV_BUS for ensuring the dependency.
Also, the unused SNDRV_DEV_TOPLEVEL, SNDRV_DEV_LOWLEVEL_PRE and
SNDRV_DEV_LOWLEVEL_NORMAL are removed as a cleanup.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2014-01-29 21:53:35 +07:00
|
|
|
dev = look_for_dev(card, device_data);
|
2014-02-04 17:36:11 +07:00
|
|
|
if (dev)
|
|
|
|
__snd_device_free(dev);
|
|
|
|
else
|
2017-09-07 03:28:01 +07:00
|
|
|
dev_dbg(card->dev, "device free %p (from %pS), not found\n",
|
2014-02-04 17:36:11 +07:00
|
|
|
device_data, __builtin_return_address(0));
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2006-04-28 20:13:39 +07:00
|
|
|
EXPORT_SYMBOL(snd_device_free);
|
|
|
|
|
2014-02-04 17:36:11 +07:00
|
|
|
static int __snd_device_register(struct snd_device *dev)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2014-02-04 17:36:11 +07:00
|
|
|
if (dev->state == SNDRV_DEV_BUILD) {
|
|
|
|
if (dev->ops->dev_register) {
|
|
|
|
int err = dev->ops->dev_register(dev);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2014-02-04 17:36:11 +07:00
|
|
|
dev->state = SNDRV_DEV_REGISTERED;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2014-02-04 17:36:11 +07:00
|
|
|
return 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* snd_device_register - register the device
|
|
|
|
* @card: the card instance
|
|
|
|
* @device_data: the data pointer to register
|
|
|
|
*
|
|
|
|
* Registers the device which was already created via
|
|
|
|
* snd_device_new(). Usually this is called from snd_card_register(),
|
|
|
|
* but it can be called later if any new devices are created after
|
|
|
|
* invocation of snd_card_register().
|
|
|
|
*
|
2013-03-12 04:05:14 +07:00
|
|
|
* Return: Zero if successful, or a negative error code on failure or if the
|
2005-04-17 05:20:36 +07:00
|
|
|
* device not found.
|
|
|
|
*/
|
2005-11-17 19:51:18 +07:00
|
|
|
int snd_device_register(struct snd_card *card, void *device_data)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:51:18 +07:00
|
|
|
struct snd_device *dev;
|
2005-06-30 00:30:42 +07:00
|
|
|
|
2008-08-08 22:09:09 +07:00
|
|
|
if (snd_BUG_ON(!card || !device_data))
|
|
|
|
return -ENXIO;
|
ALSA: Use priority list for managing device list
Basically, the device type specifies the priority of the device to be
registered / freed, too. However, the priority value isn't well
utilized but only it's checked as a group. This results in
inconsistent register and free order (where each of them should be in
reversed direction).
This patch simplifies the device list management code by simply
inserting a list entry at creation time in an incremental order for
the priority value. Since we can just follow the link for register,
disconnect and free calls, we don't have to specify the group; so the
whole enum definitions are also simplified as well.
The visible change to outside is that the priorities of some object
types are revisited. For example, now the SNDRV_DEV_LOWLEVEL object
is registered before others (control, PCM, etc) and, in return,
released after others. Similarly, SNDRV_DEV_CODEC is in a lower
priority than SNDRV_DEV_BUS for ensuring the dependency.
Also, the unused SNDRV_DEV_TOPLEVEL, SNDRV_DEV_LOWLEVEL_PRE and
SNDRV_DEV_LOWLEVEL_NORMAL are removed as a cleanup.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2014-01-29 21:53:35 +07:00
|
|
|
dev = look_for_dev(card, device_data);
|
2014-02-04 17:36:11 +07:00
|
|
|
if (dev)
|
|
|
|
return __snd_device_register(dev);
|
2005-04-17 05:20:36 +07:00
|
|
|
snd_BUG();
|
|
|
|
return -ENXIO;
|
|
|
|
}
|
2006-04-28 20:13:39 +07:00
|
|
|
EXPORT_SYMBOL(snd_device_register);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* register all the devices on the card.
|
|
|
|
* called from init.c
|
|
|
|
*/
|
2005-11-17 19:51:18 +07:00
|
|
|
int snd_device_register_all(struct snd_card *card)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:51:18 +07:00
|
|
|
struct snd_device *dev;
|
2005-04-17 05:20:36 +07:00
|
|
|
int err;
|
|
|
|
|
2008-08-08 22:09:09 +07:00
|
|
|
if (snd_BUG_ON(!card))
|
|
|
|
return -ENXIO;
|
2006-10-05 21:02:22 +07:00
|
|
|
list_for_each_entry(dev, &card->devices, list) {
|
2014-02-04 17:36:11 +07:00
|
|
|
err = __snd_device_register(dev);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* disconnect all the devices on the card.
|
|
|
|
* called from init.c
|
|
|
|
*/
|
2015-02-28 00:01:22 +07:00
|
|
|
void snd_device_disconnect_all(struct snd_card *card)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2005-11-17 19:51:18 +07:00
|
|
|
struct snd_device *dev;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2008-08-08 22:09:09 +07:00
|
|
|
if (snd_BUG_ON(!card))
|
2015-02-28 00:01:22 +07:00
|
|
|
return;
|
|
|
|
list_for_each_entry_reverse(dev, &card->devices, list)
|
|
|
|
__snd_device_disconnect(dev);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* release all the devices on the card.
|
|
|
|
* called from init.c
|
|
|
|
*/
|
2014-02-04 17:36:11 +07:00
|
|
|
void snd_device_free_all(struct snd_card *card)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
ALSA: Use priority list for managing device list
Basically, the device type specifies the priority of the device to be
registered / freed, too. However, the priority value isn't well
utilized but only it's checked as a group. This results in
inconsistent register and free order (where each of them should be in
reversed direction).
This patch simplifies the device list management code by simply
inserting a list entry at creation time in an incremental order for
the priority value. Since we can just follow the link for register,
disconnect and free calls, we don't have to specify the group; so the
whole enum definitions are also simplified as well.
The visible change to outside is that the priorities of some object
types are revisited. For example, now the SNDRV_DEV_LOWLEVEL object
is registered before others (control, PCM, etc) and, in return,
released after others. Similarly, SNDRV_DEV_CODEC is in a lower
priority than SNDRV_DEV_BUS for ensuring the dependency.
Also, the unused SNDRV_DEV_TOPLEVEL, SNDRV_DEV_LOWLEVEL_PRE and
SNDRV_DEV_LOWLEVEL_NORMAL are removed as a cleanup.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2014-01-29 21:53:35 +07:00
|
|
|
struct snd_device *dev, *next;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2008-08-08 22:09:09 +07:00
|
|
|
if (snd_BUG_ON(!card))
|
2014-02-04 17:36:11 +07:00
|
|
|
return;
|
ALSA: core: Assure control device to be registered at last
The commit 289ca025ee1d ("ALSA: Use priority list for managing device
list") changed the way to register/disconnect/free devices via a
single priority list. This helped to make behavior consistent, but it
also changed a slight behavior change: namely, the control device is
registered earlier than others, while it was supposed to be the very
last one.
I've put SNDRV_DEV_CONTROL in the current position as the release of
ctl elements often conflict with the private ctl elements some PCM or
other components may create, which often leads to a double-free.
But, the order of register and disconnect should be indeed fixed as
expected in the early days: the control device gets registered at
last, and disconnected at first.
This patch changes the priority list order to move SNDRV_DEV_CONTROL
as the last guy to assure the register / disconnect order. Meanwhile,
for keeping the messy resource release order, manually treat the
control and lowlevel devices as last freed one.
Additional note:
The lowlevel device is the device where a card driver creates at
probe. And, we still keep the release order control -> lowlevel, as
there might be link from a control element back to a lowlevel object.
Fixes: 289ca025ee1d ("ALSA: Use priority list for managing device list")
Reported-by: Tzung-Bi Shih <tzungbi@google.com>
Tested-by: Tzung-Bi Shih <tzungbi@google.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2018-05-16 01:25:29 +07:00
|
|
|
list_for_each_entry_safe_reverse(dev, next, &card->devices, list) {
|
|
|
|
/* exception: free ctl and lowlevel stuff later */
|
|
|
|
if (dev->type == SNDRV_DEV_CONTROL ||
|
|
|
|
dev->type == SNDRV_DEV_LOWLEVEL)
|
|
|
|
continue;
|
|
|
|
__snd_device_free(dev);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* free all */
|
2014-02-04 17:36:11 +07:00
|
|
|
list_for_each_entry_safe_reverse(dev, next, &card->devices, list)
|
|
|
|
__snd_device_free(dev);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2020-03-24 00:06:42 +07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* snd_device_get_state - Get the current state of the given device
|
|
|
|
* @card: the card instance
|
|
|
|
* @device_data: the data pointer to release
|
|
|
|
*
|
|
|
|
* Returns the current state of the given device object. For the valid
|
|
|
|
* device, either @SNDRV_DEV_BUILD, @SNDRV_DEV_REGISTERED or
|
|
|
|
* @SNDRV_DEV_DISCONNECTED is returned.
|
|
|
|
* Or for a non-existing device, -1 is returned as an error.
|
|
|
|
*/
|
|
|
|
int snd_device_get_state(struct snd_card *card, void *device_data)
|
|
|
|
{
|
|
|
|
struct snd_device *dev;
|
|
|
|
|
|
|
|
dev = look_for_dev(card, device_data);
|
|
|
|
if (dev)
|
|
|
|
return dev->state;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_device_get_state);
|