return 0;
}
+/* check whether the given id is contained in the given kctl */
+static bool elem_id_matches(const struct snd_kcontrol *kctl,
+ const struct snd_ctl_elem_id *id)
+{
+ return kctl->id.iface == id->iface &&
+ kctl->id.device == id->device &&
+ kctl->id.subdevice == id->subdevice &&
+ !strncmp(kctl->id.name, id->name, sizeof(kctl->id.name)) &&
+ kctl->id.index <= id->index &&
+ kctl->id.index + kctl->count > id->index;
+}
+
+#ifdef CONFIG_SND_CTL_FAST_LOOKUP
+/* Compute a hash key for the corresponding ctl id
+ * It's for the name lookup, hence the numid is excluded.
+ * The hash key is bound in LONG_MAX to be used for Xarray key.
+ */
+#define MULTIPLIER 37
+static unsigned long get_ctl_id_hash(const struct snd_ctl_elem_id *id)
+{
+ unsigned long h;
+ const unsigned char *p;
+
+ h = id->iface;
+ h = MULTIPLIER * h + id->device;
+ h = MULTIPLIER * h + id->subdevice;
+ for (p = id->name; *p; p++)
+ h = MULTIPLIER * h + *p;
+ h = MULTIPLIER * h + id->index;
+ h &= LONG_MAX;
+ return h;
+}
+
+/* add hash entries to numid and ctl xarray tables */
+static void add_hash_entries(struct snd_card *card,
+ struct snd_kcontrol *kcontrol)
+{
+ struct snd_ctl_elem_id id = kcontrol->id;
+ int i;
+
+ xa_store_range(&card->ctl_numids, kcontrol->id.numid,
+ kcontrol->id.numid + kcontrol->count - 1,
+ kcontrol, GFP_KERNEL);
+
+ for (i = 0; i < kcontrol->count; i++) {
+ id.index = kcontrol->id.index + i;
+ if (xa_insert(&card->ctl_hash, get_ctl_id_hash(&id),
+ kcontrol, GFP_KERNEL)) {
+ /* skip hash for this entry, noting we had collision */
+ card->ctl_hash_collision = true;
+ dev_dbg(card->dev, "ctl_hash collision %d:%s:%d\n",
+ id.iface, id.name, id.index);
+ }
+ }
+}
+
+/* remove hash entries that have been added */
+static void remove_hash_entries(struct snd_card *card,
+ struct snd_kcontrol *kcontrol)
+{
+ struct snd_ctl_elem_id id = kcontrol->id;
+ struct snd_kcontrol *matched;
+ unsigned long h;
+ int i;
+
+ for (i = 0; i < kcontrol->count; i++) {
+ xa_erase(&card->ctl_numids, id.numid);
+ h = get_ctl_id_hash(&id);
+ matched = xa_load(&card->ctl_hash, h);
+ if (matched && (matched == kcontrol ||
+ elem_id_matches(matched, &id)))
+ xa_erase(&card->ctl_hash, h);
+ id.index++;
+ id.numid++;
+ }
+}
+#else /* CONFIG_SND_CTL_FAST_LOOKUP */
+static inline void add_hash_entries(struct snd_card *card,
+ struct snd_kcontrol *kcontrol)
+{
+}
+static inline void remove_hash_entries(struct snd_card *card,
+ struct snd_kcontrol *kcontrol)
+{
+}
+#endif /* CONFIG_SND_CTL_FAST_LOOKUP */
+
enum snd_ctl_add_mode {
CTL_ADD_EXCLUSIVE, CTL_REPLACE, CTL_ADD_ON_REPLACE,
};
kcontrol->id.numid = card->last_numid + 1;
card->last_numid += kcontrol->count;
+ add_hash_entries(card, kcontrol);
+
for (idx = 0; idx < kcontrol->count; idx++)
snd_ctl_notify_one(card, SNDRV_CTL_EVENT_MASK_ADD, kcontrol, idx);
}
EXPORT_SYMBOL(snd_ctl_replace);
+static int __snd_ctl_remove(struct snd_card *card,
+ struct snd_kcontrol *kcontrol,
+ bool remove_hash)
+{
+ unsigned int idx;
+
+ if (snd_BUG_ON(!card || !kcontrol))
+ return -EINVAL;
+ list_del(&kcontrol->list);
+
+ if (remove_hash)
+ remove_hash_entries(card, kcontrol);
+
+ card->controls_count -= kcontrol->count;
+ for (idx = 0; idx < kcontrol->count; idx++)
+ snd_ctl_notify_one(card, SNDRV_CTL_EVENT_MASK_REMOVE, kcontrol, idx);
+ snd_ctl_free_one(kcontrol);
+ return 0;
+}
+
/**
* snd_ctl_remove - remove the control from the card and release it
* @card: the card instance
*/
int snd_ctl_remove(struct snd_card *card, struct snd_kcontrol *kcontrol)
{
- unsigned int idx;
-
- if (snd_BUG_ON(!card || !kcontrol))
- return -EINVAL;
- list_del(&kcontrol->list);
- card->controls_count -= kcontrol->count;
- for (idx = 0; idx < kcontrol->count; idx++)
- snd_ctl_notify_one(card, SNDRV_CTL_EVENT_MASK_REMOVE, kcontrol, idx);
- snd_ctl_free_one(kcontrol);
- return 0;
+ return __snd_ctl_remove(card, kcontrol, true);
}
EXPORT_SYMBOL(snd_ctl_remove);
up_write(&card->controls_rwsem);
return -ENOENT;
}
+ remove_hash_entries(card, kctl);
kctl->id = *dst_id;
kctl->id.numid = card->last_numid + 1;
card->last_numid += kctl->count;
+ add_hash_entries(card, kctl);
up_write(&card->controls_rwsem);
return 0;
}
EXPORT_SYMBOL(snd_ctl_rename_id);
+#ifndef CONFIG_SND_CTL_FAST_LOOKUP
+static struct snd_kcontrol *
+snd_ctl_find_numid_slow(struct snd_card *card, unsigned int numid)
+{
+ struct snd_kcontrol *kctl;
+
+ list_for_each_entry(kctl, &card->controls, list) {
+ if (kctl->id.numid <= numid && kctl->id.numid + kctl->count > numid)
+ return kctl;
+ }
+ return NULL;
+}
+#endif /* !CONFIG_SND_CTL_FAST_LOOKUP */
+
/**
* snd_ctl_find_numid - find the control instance with the given number-id
* @card: the card instance
*/
struct snd_kcontrol *snd_ctl_find_numid(struct snd_card *card, unsigned int numid)
{
- struct snd_kcontrol *kctl;
-
if (snd_BUG_ON(!card || !numid))
return NULL;
- list_for_each_entry(kctl, &card->controls, list) {
- if (kctl->id.numid <= numid && kctl->id.numid + kctl->count > numid)
- return kctl;
- }
- return NULL;
+#ifdef CONFIG_SND_CTL_FAST_LOOKUP
+ return xa_load(&card->ctl_numids, numid);
+#else
+ return snd_ctl_find_numid_slow(card, numid);
+#endif
}
EXPORT_SYMBOL(snd_ctl_find_numid);
return NULL;
if (id->numid != 0)
return snd_ctl_find_numid(card, id->numid);
- list_for_each_entry(kctl, &card->controls, list) {
- if (kctl->id.iface != id->iface)
- continue;
- if (kctl->id.device != id->device)
- continue;
- if (kctl->id.subdevice != id->subdevice)
- continue;
- if (strncmp(kctl->id.name, id->name, sizeof(kctl->id.name)))
- continue;
- if (kctl->id.index > id->index)
- continue;
- if (kctl->id.index + kctl->count <= id->index)
- continue;
+#ifdef CONFIG_SND_CTL_FAST_LOOKUP
+ kctl = xa_load(&card->ctl_hash, get_ctl_id_hash(id));
+ if (kctl && elem_id_matches(kctl, id))
return kctl;
- }
+ if (!card->ctl_hash_collision)
+ return NULL; /* we can rely on only hash table */
+#endif
+ /* no matching in hash table - try all as the last resort */
+ list_for_each_entry(kctl, &card->controls, list)
+ if (elem_id_matches(kctl, id))
+ return kctl;
+
return NULL;
}
EXPORT_SYMBOL(snd_ctl_find_id);
down_write(&card->controls_rwsem);
while (!list_empty(&card->controls)) {
control = snd_kcontrol(card->controls.next);
- snd_ctl_remove(card, control);
+ __snd_ctl_remove(card, control, false);
}
+
+#ifdef CONFIG_SND_CTL_FAST_LOOKUP
+ xa_destroy(&card->ctl_numids);
+ xa_destroy(&card->ctl_hash);
+#endif
up_write(&card->controls_rwsem);
put_device(&card->ctl_dev);
return 0;