summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/sound/core.h42
-rw-r--r--sound/core/control.c23
-rw-r--r--sound/core/control_compat.c5
-rw-r--r--sound/core/init.c50
4 files changed, 106 insertions, 14 deletions
diff --git a/include/sound/core.h b/include/sound/core.h
index d69dfc6c4953..3acb754108b1 100644
--- a/include/sound/core.h
+++ b/include/sound/core.h
@@ -128,7 +128,9 @@ struct snd_card {
#ifdef CONFIG_PM
unsigned int power_state; /* power state */
+ atomic_t power_ref;
wait_queue_head_t power_sleep;
+ wait_queue_head_t power_ref_sleep;
#endif
#if IS_ENABLED(CONFIG_SND_MIXER_OSS)
@@ -151,12 +153,52 @@ static inline void snd_power_change_state(struct snd_card *card, unsigned int st
wake_up(&card->power_sleep);
}
+/**
+ * snd_power_ref - Take the reference count for power control
+ * @card: sound card object
+ *
+ * The power_ref reference of the card is used for managing to block
+ * the snd_power_sync_ref() operation. This function increments the reference.
+ * The counterpart snd_power_unref() has to be called appropriately later.
+ */
+static inline void snd_power_ref(struct snd_card *card)
+{
+ atomic_inc(&card->power_ref);
+}
+
+/**
+ * snd_power_unref - Release the reference count for power control
+ * @card: sound card object
+ */
+static inline void snd_power_unref(struct snd_card *card)
+{
+ if (atomic_dec_and_test(&card->power_ref))
+ wake_up(&card->power_ref_sleep);
+}
+
+/**
+ * snd_power_sync_ref - wait until the card power_ref is freed
+ * @card: sound card object
+ *
+ * This function is used to synchronize with the pending power_ref being
+ * released.
+ */
+static inline void snd_power_sync_ref(struct snd_card *card)
+{
+ wait_event(card->power_ref_sleep, !atomic_read(&card->power_ref));
+}
+
/* init.c */
int snd_power_wait(struct snd_card *card, unsigned int power_state);
+int snd_power_ref_and_wait(struct snd_card *card);
#else /* ! CONFIG_PM */
static inline int snd_power_wait(struct snd_card *card, unsigned int state) { return 0; }
+static inline void snd_power_ref(struct snd_card *card) {}
+static inline void snd_power_unref(struct snd_card *card) {}
+static inline int snd_power_ref_and_wait(struct snd_card *card) { return 0; }
+static inline void snd_power_sync_ref(struct snd_card *card) {}
#define snd_power_get_state(card) ({ (void)(card); SNDRV_CTL_POWER_D0; })
#define snd_power_change_state(card, state) do { (void)(card); } while (0)
diff --git a/sound/core/control.c b/sound/core/control.c
index 498e3701514a..638da34605ba 100644
--- a/sound/core/control.c
+++ b/sound/core/control.c
@@ -995,7 +995,10 @@ static int __snd_ctl_elem_info(struct snd_card *card,
#ifdef CONFIG_SND_DEBUG
info->access = 0;
#endif
- result = kctl->info(kctl, info);
+ result = snd_power_ref_and_wait(card);
+ if (!result)
+ result = kctl->info(kctl, info);
+ snd_power_unref(card);
if (result >= 0) {
snd_BUG_ON(info->access);
index_offset = snd_ctl_get_ioff(kctl, &info->id);
@@ -1088,7 +1091,10 @@ static int snd_ctl_elem_read(struct snd_card *card,
if (!snd_ctl_skip_validation(&info))
fill_remaining_elem_value(control, &info, pattern);
- ret = kctl->get(kctl, control);
+ ret = snd_power_ref_and_wait(card);
+ if (!ret)
+ ret = kctl->get(kctl, control);
+ snd_power_unref(card);
if (ret < 0)
return ret;
if (!snd_ctl_skip_validation(&info) &&
@@ -1154,7 +1160,10 @@ static int snd_ctl_elem_write(struct snd_card *card, struct snd_ctl_file *file,
}
snd_ctl_build_ioff(&control->id, kctl, index_offset);
- result = kctl->put(kctl, control);
+ result = snd_power_ref_and_wait(card);
+ if (!result)
+ result = kctl->put(kctl, control);
+ snd_power_unref(card);
if (result < 0) {
up_write(&card->controls_rwsem);
return result;
@@ -1669,7 +1678,7 @@ static int call_tlv_handler(struct snd_ctl_file *file, int op_flag,
{SNDRV_CTL_TLV_OP_CMD, SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND},
};
struct snd_kcontrol_volatile *vd = &kctl->vd[snd_ctl_get_ioff(kctl, id)];
- int i;
+ int i, ret;
/* Check support of the request for this element. */
for (i = 0; i < ARRAY_SIZE(pairs); ++i) {
@@ -1687,7 +1696,11 @@ static int call_tlv_handler(struct snd_ctl_file *file, int op_flag,
vd->owner != NULL && vd->owner != file)
return -EPERM;
- return kctl->tlv.c(kctl, op_flag, size, buf);
+ ret = snd_power_ref_and_wait(file->card);
+ if (!ret)
+ ret = kctl->tlv.c(kctl, op_flag, size, buf);
+ snd_power_unref(file->card);
+ return ret;
}
static int read_tlv_buf(struct snd_kcontrol *kctl, struct snd_ctl_elem_id *id,
diff --git a/sound/core/control_compat.c b/sound/core/control_compat.c
index 1d708aab9c98..19133ee076c5 100644
--- a/sound/core/control_compat.c
+++ b/sound/core/control_compat.c
@@ -187,7 +187,10 @@ static int get_ctl_type(struct snd_card *card, struct snd_ctl_elem_id *id,
return -ENOMEM;
}
info->id = *id;
- err = kctl->info(kctl, info);
+ err = snd_power_ref_and_wait(card);
+ if (!err)
+ err = kctl->info(kctl, info);
+ snd_power_unref(card);
up_read(&card->controls_rwsem);
if (err >= 0) {
err = info->type;
diff --git a/sound/core/init.c b/sound/core/init.c
index ef41f5b3a240..2c62e035bc62 100644
--- a/sound/core/init.c
+++ b/sound/core/init.c
@@ -220,6 +220,8 @@ int snd_card_new(struct device *parent, int idx, const char *xid,
mutex_init(&card->memory_mutex);
#ifdef CONFIG_PM
init_waitqueue_head(&card->power_sleep);
+ init_waitqueue_head(&card->power_ref_sleep);
+ atomic_set(&card->power_ref, 0);
#endif
init_waitqueue_head(&card->remove_sleep);
card->sync_irq = -1;
@@ -442,6 +444,7 @@ int snd_card_disconnect(struct snd_card *card)
#ifdef CONFIG_PM
wake_up(&card->power_sleep);
+ snd_power_sync_ref(card);
#endif
return 0;
}
@@ -1002,21 +1005,28 @@ EXPORT_SYMBOL(snd_card_file_remove);
#ifdef CONFIG_PM
/**
- * snd_power_wait - wait until the power-state is changed.
- * @card: soundcard structure
- * @power_state: expected power state
+ * snd_power_ref_and_wait - wait until the card gets powered up
+ * @card: soundcard structure
*
- * Waits until the power-state is changed.
+ * Take the power_ref reference count of the given card, and
+ * wait until the card gets powered up to SNDRV_CTL_POWER_D0 state.
+ * The refcount is down again while sleeping until power-up, hence this
+ * function can be used for syncing the floating control ops accesses,
+ * typically around calling control ops.
*
- * Return: Zero if successful, or a negative error code.
+ * The caller needs to pull down the refcount via snd_power_unref() later
+ * no matter whether the error is returned from this function or not.
+ *
+ * Return: Zero if successful, or a negative error code.
*/
-int snd_power_wait(struct snd_card *card, unsigned int power_state)
+int snd_power_ref_and_wait(struct snd_card *card)
{
wait_queue_entry_t wait;
int result = 0;
+ snd_power_ref(card);
/* fastpath */
- if (snd_power_get_state(card) == power_state)
+ if (snd_power_get_state(card) == SNDRV_CTL_POWER_D0)
return 0;
init_waitqueue_entry(&wait, current);
add_wait_queue(&card->power_sleep, &wait);
@@ -1025,13 +1035,37 @@ int snd_power_wait(struct snd_card *card, unsigned int power_state)
result = -ENODEV;
break;
}
- if (snd_power_get_state(card) == power_state)
+ if (snd_power_get_state(card) == SNDRV_CTL_POWER_D0)
break;
+ snd_power_unref(card);
set_current_state(TASK_UNINTERRUPTIBLE);
schedule_timeout(30 * HZ);
+ snd_power_ref(card);
}
remove_wait_queue(&card->power_sleep, &wait);
return result;
}
+EXPORT_SYMBOL_GPL(snd_power_ref_and_wait);
+
+/**
+ * snd_power_wait - wait until the card gets powered up (old form)
+ * @card: soundcard structure
+ * @power_state: expected power state
+ *
+ * Wait until the card gets powered up to SNDRV_CTL_POWER_D0 state.
+ * @power_state must be SNDRV_CTL_POWER_D0.
+ *
+ * Return: Zero if successful, or a negative error code.
+ */
+int snd_power_wait(struct snd_card *card, unsigned int power_state)
+{
+ int ret;
+
+ if (WARN_ON(power_state != SNDRV_CTL_POWER_D0))
+ return 0;
+ ret = snd_power_ref_and_wait(card);
+ snd_power_unref(card);
+ return ret;
+}
EXPORT_SYMBOL(snd_power_wait);
#endif /* CONFIG_PM */