diff options
| author | Jinke Han <jinkehan@didiglobal.com> | 2026-09-08 15:37:42 +0800 |
|---|---|---|
| committer | Ingo Molnar <mingo@kernel.org> | 2026-09-17 09:48:42 +0200 |
| commit | a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 (patch) | |
| tree | 73fef824f9a846cf935869d0dbc5070ef6e2f741 /sound/hda/common/sysfs.c | |
| download | linux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.tar.gz linux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.zip | |
x86/kprobes: Fix crash when probing CS CALL instructionsgrafted
When using eBPF to probe CS CALL instructions within a function,
a crash can be triggered.
The eBPF tool probes offset 257 of the __hrtimer_run_queues()
function:
<__hrtimer_run_queues+249>: nopl 0x0(%rax,%rax,1)
<__hrtimer_run_queues+254>: mov %r14,%rdi
<__hrtimer_run_queues+257>: cs call <__x86_indirect_thunk_r12>
<__hrtimer_run_queues+263>: mov %eax,%r12d
<__hrtimer_run_queues+266>: xchg %ax,%ax
<__hrtimer_run_queues+268>: mov %r13,%rdi
Which triggers this crash:
BUG: unable to handle page fault for address: 00000000000f41c9
#PF: supervisor write access in kernel mode
#PF: error_code(0x0002) - not-present page
PGD 0 P4D 0
Oops: 0002 [#1] SMP NOPTI
CPU: 1 PID: 0 Comm: swapper/1 Kdump: loaded Tainted: P
RIP: 0010:__hrtimer_run_queues+0x106/0x230
Note that __hrtimer_run_queues+0x106 is __hrtimer_run_queues+262, which is
at the 6th byte of the above CS CALL instruction. Since the CS CALL
instruction occupies 6 bytes, the exception occurred in the middle of that
call instruction.
The root cause is that when using eBPF tools to probe in the middle of a
function, a kprobe with INT3 is used as the underlying implementation.
During single-step emulation of the original CALL instruction,
int3_emulate_call() assumes that the probed CALL instruction is 5 bytes
long. However, the actual CS-prefixed CALL instruction occupies 6 bytes,
so it constructs an incorrect exception return address. When the CPU
returns from the kprobe handler, the next instruction to be executed is at
the address of the last byte of that CS CALL instruction. Coincidentally,
starting from that address, the CPU fetches and decodes a completely
different instruction, which ultimately triggers a kernel crash.
Fix the issue by using the actual instruction length obtained from
the instruction decoder when constructing the exception return
address, rather than relying on the hardcoded CALL_INSN_SIZE macro.
[ mingo: Refined the changelog ]
Fixes: 6256e668b7af ("x86/kprobes: Use int3 instead of debug trap for single-step")
Suggested-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Signed-off-by: Jinke Han <jinkehan@didiglobal.com>
Signed-off-by: Ingo Molnar <mingo@kernel.org>
Reviewed-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Acked-by: Yafang Shao <laoar.shao@gmail.com>
Acked-by: Borislav Petkov <bp@alien8.de>
Cc: Peter Zijlstra <peterz@infradead.org>
Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000
Diffstat (limited to 'sound/hda/common/sysfs.c')
| -rw-r--r-- | sound/hda/common/sysfs.c | 765 |
1 files changed, 765 insertions, 0 deletions
diff --git a/sound/hda/common/sysfs.c b/sound/hda/common/sysfs.c new file mode 100644 index 000000000..bedf10b30 --- /dev/null +++ b/sound/hda/common/sysfs.c @@ -0,0 +1,765 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * sysfs interface for HD-audio codec + * + * Copyright (c) 2014 Takashi Iwai <tiwai@suse.de> + * + * split from hda_hwdep.c + */ + +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/compat.h> +#include <linux/mutex.h> +#include <linux/ctype.h> +#include <linux/string.h> +#include <linux/export.h> +#include <sound/core.h> +#include <sound/hda_codec.h> +#include "hda_local.h" +#include <sound/hda_hwdep.h> +#include <sound/minors.h> + +/* hint string pair */ +struct hda_hint { + const char *key; + const char *val; /* contained in the same alloc as key */ +}; + +static ssize_t power_on_acct_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hda_codec *codec = dev_get_drvdata(dev); + snd_hda_update_power_acct(codec); + return sysfs_emit(buf, "%u\n", jiffies_to_msecs(codec->power_on_acct)); +} + +static ssize_t power_off_acct_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hda_codec *codec = dev_get_drvdata(dev); + snd_hda_update_power_acct(codec); + return sysfs_emit(buf, "%u\n", jiffies_to_msecs(codec->power_off_acct)); +} + +static DEVICE_ATTR_RO(power_on_acct); +static DEVICE_ATTR_RO(power_off_acct); + +#define CODEC_INFO_SHOW(type, field) \ +static ssize_t type##_show(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct hda_codec *codec = dev_get_drvdata(dev); \ + return sysfs_emit(buf, "0x%x\n", codec->field); \ +} + +#define CODEC_INFO_STR_SHOW(type, field) \ +static ssize_t type##_show(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct hda_codec *codec = dev_get_drvdata(dev); \ + return sysfs_emit(buf, "%s\n", \ + codec->field ? codec->field : ""); \ +} + +CODEC_INFO_SHOW(vendor_id, core.vendor_id); +CODEC_INFO_SHOW(subsystem_id, core.subsystem_id); +CODEC_INFO_SHOW(revision_id, core.revision_id); +CODEC_INFO_SHOW(afg, core.afg); +CODEC_INFO_SHOW(mfg, core.mfg); +CODEC_INFO_STR_SHOW(vendor_name, core.vendor_name); +CODEC_INFO_STR_SHOW(chip_name, core.chip_name); +CODEC_INFO_STR_SHOW(modelname, modelname); + +static ssize_t pin_configs_show(struct hda_codec *codec, + struct snd_array *list, + char *buf) +{ + const struct hda_pincfg *pin; + int i, len = 0; + + guard(mutex)(&codec->user_mutex); + snd_array_for_each(list, i, pin) { + len += sysfs_emit_at(buf, len, "0x%02x 0x%08x\n", + pin->nid, pin->cfg); + } + return len; +} + +static ssize_t init_pin_configs_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hda_codec *codec = dev_get_drvdata(dev); + return pin_configs_show(codec, &codec->init_pins, buf); +} + +static ssize_t driver_pin_configs_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hda_codec *codec = dev_get_drvdata(dev); + return pin_configs_show(codec, &codec->driver_pins, buf); +} + +#ifdef CONFIG_SND_HDA_RECONFIG + +/* + * sysfs interface + */ + +static int clear_codec(struct hda_codec *codec) +{ + int err; + + err = snd_hda_codec_reset(codec); + if (err < 0) { + codec_err(codec, "The codec is being used, can't free.\n"); + return err; + } + snd_hda_sysfs_clear(codec); + return 0; +} + +static int reconfig_codec(struct hda_codec *codec) +{ + int err; + + CLASS(snd_hda_power, pm)(codec); + codec_info(codec, "hda-codec: reconfiguring\n"); + err = snd_hda_codec_reset(codec); + if (err < 0) { + codec_err(codec, + "The codec is being used, can't reconfigure.\n"); + return err; + } + err = device_reprobe(hda_codec_dev(codec)); + if (err < 0) + return err; + return snd_card_register(codec->card); +} + +/* + * allocate a string at most len chars, and remove the trailing EOL + */ +static char *kstrndup_noeol(const char *src, size_t len) +{ + char *s = kstrndup(src, len, GFP_KERNEL); + char *p; + if (!s) + return NULL; + p = strchr(s, '\n'); + if (p) + *p = 0; + return s; +} + +#define CODEC_INFO_STORE(type, field) \ +static ssize_t type##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + struct hda_codec *codec = dev_get_drvdata(dev); \ + unsigned long val; \ + int err = kstrtoul(buf, 0, &val); \ + if (err < 0) \ + return err; \ + codec->field = val; \ + return count; \ +} + +#define CODEC_INFO_STR_STORE(type, field) \ +static ssize_t type##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + struct hda_codec *codec = dev_get_drvdata(dev); \ + char *s = kstrndup_noeol(buf, 64); \ + if (!s) \ + return -ENOMEM; \ + kfree(codec->field); \ + codec->field = s; \ + return count; \ +} + +CODEC_INFO_STORE(vendor_id, core.vendor_id); +CODEC_INFO_STORE(subsystem_id, core.subsystem_id); +CODEC_INFO_STORE(revision_id, core.revision_id); +CODEC_INFO_STR_STORE(vendor_name, core.vendor_name); +CODEC_INFO_STR_STORE(chip_name, core.chip_name); +CODEC_INFO_STR_STORE(modelname, modelname); + +#define CODEC_ACTION_STORE(type) \ +static ssize_t type##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + struct hda_codec *codec = dev_get_drvdata(dev); \ + int err = 0; \ + if (*buf) \ + err = type##_codec(codec); \ + return err < 0 ? err : count; \ +} + +CODEC_ACTION_STORE(reconfig); +CODEC_ACTION_STORE(clear); + +static ssize_t init_verbs_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hda_codec *codec = dev_get_drvdata(dev); + const struct hda_verb *v; + int i, len = 0; + + guard(mutex)(&codec->user_mutex); + snd_array_for_each(&codec->init_verbs, i, v) { + len += sysfs_emit_at(buf, len, "0x%02x 0x%03x 0x%04x\n", + v->nid, v->verb, v->param); + } + return len; +} + +static int parse_init_verbs(struct hda_codec *codec, const char *buf) +{ + struct hda_verb *v; + int nid, verb, param; + + if (sscanf(buf, "%i %i %i", &nid, &verb, ¶m) != 3) + return -EINVAL; + if (!nid || !verb) + return -EINVAL; + guard(mutex)(&codec->user_mutex); + v = snd_array_new(&codec->init_verbs); + if (!v) + return -ENOMEM; + v->nid = nid; + v->verb = verb; + v->param = param; + return 0; +} + +static ssize_t init_verbs_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hda_codec *codec = dev_get_drvdata(dev); + int err = parse_init_verbs(codec, buf); + if (err < 0) + return err; + return count; +} + +static ssize_t hints_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hda_codec *codec = dev_get_drvdata(dev); + const struct hda_hint *hint; + int i, len = 0; + + guard(mutex)(&codec->user_mutex); + snd_array_for_each(&codec->hints, i, hint) { + len += sysfs_emit_at(buf, len, "%s = %s\n", + hint->key, hint->val); + } + return len; +} + +static struct hda_hint *get_hint(struct hda_codec *codec, const char *key) +{ + struct hda_hint *hint; + int i; + + snd_array_for_each(&codec->hints, i, hint) { + if (!strcmp(hint->key, key)) + return hint; + } + return NULL; +} + +static void remove_trail_spaces(char *str) +{ + char *p; + if (!*str) + return; + p = str + strlen(str) - 1; + for (; isspace(*p); p--) { + *p = 0; + if (p == str) + return; + } +} + +#define MAX_HINTS 1024 + +static int parse_hints(struct hda_codec *codec, const char *buf) +{ + char *val; + struct hda_hint *hint; + + buf = skip_spaces(buf); + if (!*buf || *buf == '#' || *buf == '\n') + return 0; + if (*buf == '=') + return -EINVAL; + + char *key __free(kfree) = + kstrndup_noeol(buf, 1024); + if (!key) + return -ENOMEM; + /* extract key and val */ + val = strchr(key, '='); + if (!val) + return -EINVAL; + *val++ = 0; + val = skip_spaces(val); + remove_trail_spaces(key); + remove_trail_spaces(val); + guard(mutex)(&codec->user_mutex); + hint = get_hint(codec, key); + if (hint) { + /* replace */ + kfree(hint->key); + goto replace; + } + /* allocate a new hint entry */ + if (codec->hints.used >= MAX_HINTS) + return -ENOMEM; + hint = snd_array_new(&codec->hints); + if (!hint) + return -ENOMEM; + replace: + hint->key = no_free_ptr(key); + hint->val = val; + return 0; +} + +static ssize_t hints_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hda_codec *codec = dev_get_drvdata(dev); + int err = parse_hints(codec, buf); + if (err < 0) + return err; + return count; +} + +static ssize_t user_pin_configs_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hda_codec *codec = dev_get_drvdata(dev); + return pin_configs_show(codec, &codec->user_pins, buf); +} + +static int parse_user_pin_configs(struct hda_codec *codec, const char *buf) +{ + int nid, cfg; + + if (sscanf(buf, "%i %i", &nid, &cfg) != 2) + return -EINVAL; + if (!nid) + return -EINVAL; + guard(mutex)(&codec->user_mutex); + return snd_hda_add_pincfg(codec, &codec->user_pins, nid, cfg); +} + +static ssize_t user_pin_configs_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hda_codec *codec = dev_get_drvdata(dev); + int err = parse_user_pin_configs(codec, buf); + if (err < 0) + return err; + return count; +} + +/* sysfs attributes exposed only when CONFIG_SND_HDA_RECONFIG=y */ +static DEVICE_ATTR_RW(init_verbs); +static DEVICE_ATTR_RW(hints); +static DEVICE_ATTR_RW(user_pin_configs); +static DEVICE_ATTR_WO(reconfig); +static DEVICE_ATTR_WO(clear); + +/** + * snd_hda_get_hint - Look for hint string + * @codec: the HDA codec + * @key: the hint key string + * + * Look for a hint key/value pair matching with the given key string + * and returns the value string. If nothing found, returns NULL. + */ +const char *snd_hda_get_hint(struct hda_codec *codec, const char *key) +{ + struct hda_hint *hint = get_hint(codec, key); + return hint ? hint->val : NULL; +} +EXPORT_SYMBOL_GPL(snd_hda_get_hint); + +/** + * snd_hda_get_bool_hint - Get a boolean hint value + * @codec: the HDA codec + * @key: the hint key string + * + * Look for a hint key/value pair matching with the given key string + * and returns a boolean value parsed from the value. If no matching + * key is found, return a negative value. + */ +int snd_hda_get_bool_hint(struct hda_codec *codec, const char *key) +{ + const char *p; + + guard(mutex)(&codec->user_mutex); + p = snd_hda_get_hint(codec, key); + if (!p || !*p) + return -ENOENT; + switch (toupper(*p)) { + case 'T': /* true */ + case 'Y': /* yes */ + case '1': + return 1; + default: + return 0; + } +} +EXPORT_SYMBOL_GPL(snd_hda_get_bool_hint); + +/** + * snd_hda_get_int_hint - Get an integer hint value + * @codec: the HDA codec + * @key: the hint key string + * @valp: pointer to store a value + * + * Look for a hint key/value pair matching with the given key string + * and stores the integer value to @valp. If no matching key is found, + * return a negative error code. Otherwise it returns zero. + */ +int snd_hda_get_int_hint(struct hda_codec *codec, const char *key, int *valp) +{ + const char *p; + unsigned long val; + + guard(mutex)(&codec->user_mutex); + p = snd_hda_get_hint(codec, key); + if (!p) + return -ENOENT; + else if (kstrtoul(p, 0, &val)) + return -EINVAL; + else { + *valp = val; + return 0; + } +} +EXPORT_SYMBOL_GPL(snd_hda_get_int_hint); +#endif /* CONFIG_SND_HDA_RECONFIG */ + +/* + * common sysfs attributes + */ +#ifdef CONFIG_SND_HDA_RECONFIG +#define RECONFIG_DEVICE_ATTR(name) DEVICE_ATTR_RW(name) +#else +#define RECONFIG_DEVICE_ATTR(name) DEVICE_ATTR_RO(name) +#endif +static RECONFIG_DEVICE_ATTR(vendor_id); +static RECONFIG_DEVICE_ATTR(subsystem_id); +static RECONFIG_DEVICE_ATTR(revision_id); +static DEVICE_ATTR_RO(afg); +static DEVICE_ATTR_RO(mfg); +static RECONFIG_DEVICE_ATTR(vendor_name); +static RECONFIG_DEVICE_ATTR(chip_name); +static RECONFIG_DEVICE_ATTR(modelname); +static DEVICE_ATTR_RO(init_pin_configs); +static DEVICE_ATTR_RO(driver_pin_configs); + + +#ifdef CONFIG_SND_HDA_PATCH_LOADER + +/* parser mode */ +enum { + LINE_MODE_NONE, + LINE_MODE_CODEC, + LINE_MODE_MODEL, + LINE_MODE_PINCFG, + LINE_MODE_VERB, + LINE_MODE_HINT, + LINE_MODE_VENDOR_ID, + LINE_MODE_SUBSYSTEM_ID, + LINE_MODE_REVISION_ID, + LINE_MODE_CHIP_NAME, + NUM_LINE_MODES, +}; + +static inline int strmatch(const char *a, const char *b) +{ + return strncasecmp(a, b, strlen(b)) == 0; +} + +/* parse the contents after the line "[codec]" + * accept only the line with three numbers, and assign the current codec + */ +static void parse_codec_mode(char *buf, struct hda_bus *bus, + struct hda_codec **codecp) +{ + int vendorid, subid, caddr; + struct hda_codec *codec; + + *codecp = NULL; + if (sscanf(buf, "%i %i %i", &vendorid, &subid, &caddr) == 3) { + list_for_each_codec(codec, bus) { + if ((vendorid <= 0 || codec->core.vendor_id == vendorid) && + (subid <= 0 || codec->core.subsystem_id == subid) && + codec->core.addr == caddr) { + *codecp = codec; + break; + } + } + } +} + +/* parse the contents after the other command tags, [pincfg], [verb], + * [vendor_id], [subsystem_id], [revision_id], [chip_name], [hint] and [model] + * just pass to the sysfs helper (only when any codec was specified) + */ +static void parse_pincfg_mode(char *buf, struct hda_bus *bus, + struct hda_codec **codecp) +{ + parse_user_pin_configs(*codecp, buf); +} + +static void parse_verb_mode(char *buf, struct hda_bus *bus, + struct hda_codec **codecp) +{ + parse_init_verbs(*codecp, buf); +} + +static void parse_hint_mode(char *buf, struct hda_bus *bus, + struct hda_codec **codecp) +{ + parse_hints(*codecp, buf); +} + +static void parse_model_mode(char *buf, struct hda_bus *bus, + struct hda_codec **codecp) +{ + kfree((*codecp)->modelname); + (*codecp)->modelname = kstrdup(buf, GFP_KERNEL); +} + +static void parse_chip_name_mode(char *buf, struct hda_bus *bus, + struct hda_codec **codecp) +{ + snd_hda_codec_set_name(*codecp, buf); +} + +#define DEFINE_PARSE_ID_MODE(name) \ +static void parse_##name##_mode(char *buf, struct hda_bus *bus, \ + struct hda_codec **codecp) \ +{ \ + unsigned long val; \ + if (!kstrtoul(buf, 0, &val)) \ + (*codecp)->core.name = val; \ +} + +DEFINE_PARSE_ID_MODE(vendor_id); +DEFINE_PARSE_ID_MODE(subsystem_id); +DEFINE_PARSE_ID_MODE(revision_id); + + +struct hda_patch_item { + const char *tag; + const char *alias; + void (*parser)(char *buf, struct hda_bus *bus, struct hda_codec **retc); +}; + +static const struct hda_patch_item patch_items[NUM_LINE_MODES] = { + [LINE_MODE_CODEC] = { + .tag = "[codec]", + .parser = parse_codec_mode, + }, + [LINE_MODE_MODEL] = { + .tag = "[model]", + .parser = parse_model_mode, + }, + [LINE_MODE_VERB] = { + .tag = "[verb]", + .alias = "[init_verbs]", + .parser = parse_verb_mode, + }, + [LINE_MODE_PINCFG] = { + .tag = "[pincfg]", + .alias = "[user_pin_configs]", + .parser = parse_pincfg_mode, + }, + [LINE_MODE_HINT] = { + .tag = "[hint]", + .alias = "[hints]", + .parser = parse_hint_mode + }, + [LINE_MODE_VENDOR_ID] = { + .tag = "[vendor_id]", + .parser = parse_vendor_id_mode, + }, + [LINE_MODE_SUBSYSTEM_ID] = { + .tag = "[subsystem_id]", + .parser = parse_subsystem_id_mode, + }, + [LINE_MODE_REVISION_ID] = { + .tag = "[revision_id]", + .parser = parse_revision_id_mode, + }, + [LINE_MODE_CHIP_NAME] = { + .tag = "[chip_name]", + .parser = parse_chip_name_mode, + }, +}; + +/* check the line starting with '[' -- change the parser mode accordingly */ +static int parse_line_mode(char *buf, struct hda_bus *bus) +{ + int i; + for (i = 0; i < ARRAY_SIZE(patch_items); i++) { + if (!patch_items[i].tag) + continue; + if (strmatch(buf, patch_items[i].tag)) + return i; + if (patch_items[i].alias && strmatch(buf, patch_items[i].alias)) + return i; + } + return LINE_MODE_NONE; +} + +/* copy one line from the buffer in fw, and update the fields in fw + * return zero if it reaches to the end of the buffer, or non-zero + * if successfully copied a line + * + * the spaces at the beginning and the end of the line are stripped + */ +static int get_line_from_fw(char *buf, int size, size_t *fw_size_p, + const void **fw_data_p) +{ + int len; + size_t fw_size = *fw_size_p; + const char *p = *fw_data_p; + + while (isspace(*p) && fw_size) { + p++; + fw_size--; + } + if (!fw_size) + return 0; + + for (len = 0; len < fw_size; len++) { + if (!*p) + break; + if (*p == '\n') { + p++; + len++; + break; + } + if (len < size) + *buf++ = *p++; + } + *buf = 0; + *fw_size_p = fw_size - len; + *fw_data_p = p; + remove_trail_spaces(buf); + return 1; +} + +/** + * snd_hda_load_patch - load a "patch" firmware file and parse it + * @bus: HD-audio bus + * @fw_size: the firmware byte size + * @fw_buf: the firmware data + */ +int snd_hda_load_patch(struct hda_bus *bus, size_t fw_size, const void *fw_buf) +{ + char buf[128]; + struct hda_codec *codec; + int line_mode; + + line_mode = LINE_MODE_NONE; + codec = NULL; + while (get_line_from_fw(buf, sizeof(buf) - 1, &fw_size, &fw_buf)) { + if (!*buf || *buf == '#' || *buf == '\n') + continue; + if (*buf == '[') + line_mode = parse_line_mode(buf, bus); + else if (patch_items[line_mode].parser && + (codec || line_mode <= LINE_MODE_CODEC)) + patch_items[line_mode].parser(buf, bus, &codec); + } + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_load_patch); +#endif /* CONFIG_SND_HDA_PATCH_LOADER */ + +/* + * sysfs entries + */ +static struct attribute *hda_dev_attrs[] = { + &dev_attr_vendor_id.attr, + &dev_attr_subsystem_id.attr, + &dev_attr_revision_id.attr, + &dev_attr_afg.attr, + &dev_attr_mfg.attr, + &dev_attr_vendor_name.attr, + &dev_attr_chip_name.attr, + &dev_attr_modelname.attr, + &dev_attr_init_pin_configs.attr, + &dev_attr_driver_pin_configs.attr, + &dev_attr_power_on_acct.attr, + &dev_attr_power_off_acct.attr, +#ifdef CONFIG_SND_HDA_RECONFIG + &dev_attr_init_verbs.attr, + &dev_attr_hints.attr, + &dev_attr_user_pin_configs.attr, + &dev_attr_reconfig.attr, + &dev_attr_clear.attr, +#endif + NULL +}; + +static const struct attribute_group hda_dev_attr_group = { + .attrs = hda_dev_attrs, +}; + +const struct attribute_group *snd_hda_dev_attr_groups[] = { + &hda_dev_attr_group, + NULL +}; + +void snd_hda_sysfs_init(struct hda_codec *codec) +{ + mutex_init(&codec->user_mutex); +#ifdef CONFIG_SND_HDA_RECONFIG + snd_array_init(&codec->init_verbs, sizeof(struct hda_verb), 32); + snd_array_init(&codec->hints, sizeof(struct hda_hint), 32); + snd_array_init(&codec->user_pins, sizeof(struct hda_pincfg), 16); +#endif +} + +void snd_hda_sysfs_clear(struct hda_codec *codec) +{ +#ifdef CONFIG_SND_HDA_RECONFIG + struct hda_hint *hint; + int i; + + /* clear init verbs */ + snd_array_free(&codec->init_verbs); + /* clear hints */ + snd_array_for_each(&codec->hints, i, hint) { + kfree(hint->key); /* we don't need to free hint->val */ + } + snd_array_free(&codec->hints); + snd_array_free(&codec->user_pins); +#endif +} |
