1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * HWDEP Interface for HD-audio codec
4 *
5 * Copyright (c) 2007 Takashi Iwai <tiwai@suse.de>
6 */
7
8#include <linux/init.h>
9#include <linux/slab.h>
10#include <linux/compat.h>
11#include <linux/nospec.h>
12#include <sound/core.h>
13#include <sound/hda_codec.h>
14#include "hda_local.h"
15#include <sound/hda_hwdep.h>
16#include <sound/minors.h>
17
18/*
19 * write/read an out-of-bound verb
20 */
21static int verb_write_ioctl(struct hda_codec *codec,
22			    struct hda_verb_ioctl __user *arg)
23{
24	u32 verb, res;
25
26	if (get_user(verb, &arg->verb))
27		return -EFAULT;
28	res = snd_hda_codec_read(codec, verb >> 24, 0,
29				 (verb >> 8) & 0xffff, verb & 0xff);
30	if (put_user(res, &arg->res))
31		return -EFAULT;
32	return 0;
33}
34
35static int get_wcap_ioctl(struct hda_codec *codec,
36			  struct hda_verb_ioctl __user *arg)
37{
38	u32 verb, res;
39
40	if (get_user(verb, &arg->verb))
41		return -EFAULT;
42	/* open-code get_wcaps(verb>>24) with nospec */
43	verb >>= 24;
44	if (verb < codec->core.start_nid ||
45	    verb >= codec->core.start_nid + codec->core.num_nodes) {
46		res = 0;
47	} else {
48		verb -= codec->core.start_nid;
49		verb = array_index_nospec(verb, codec->core.num_nodes);
50		res = codec->wcaps[verb];
51	}
52	if (put_user(res, &arg->res))
53		return -EFAULT;
54	return 0;
55}
56
57
58/*
59 */
60static int hda_hwdep_ioctl(struct snd_hwdep *hw, struct file *file,
61			   unsigned int cmd, unsigned long arg)
62{
63	struct hda_codec *codec = hw->private_data;
64	void __user *argp = (void __user *)arg;
65
66	switch (cmd) {
67	case HDA_IOCTL_PVERSION:
68		return put_user(HDA_HWDEP_VERSION, (int __user *)argp);
69	case HDA_IOCTL_VERB_WRITE:
70		return verb_write_ioctl(codec, argp);
71	case HDA_IOCTL_GET_WCAP:
72		return get_wcap_ioctl(codec, argp);
73	}
74	return -ENOIOCTLCMD;
75}
76
77#ifdef CONFIG_COMPAT
78static int hda_hwdep_ioctl_compat(struct snd_hwdep *hw, struct file *file,
79				  unsigned int cmd, unsigned long arg)
80{
81	return hda_hwdep_ioctl(hw, file, cmd, (unsigned long)compat_ptr(arg));
82}
83#endif
84
85static int hda_hwdep_open(struct snd_hwdep *hw, struct file *file)
86{
87#ifndef CONFIG_SND_DEBUG_VERBOSE
88	if (!capable(CAP_SYS_RAWIO))
89		return -EACCES;
90#endif
91	return 0;
92}
93
94int snd_hda_create_hwdep(struct hda_codec *codec)
95{
96	char hwname[16];
97	struct snd_hwdep *hwdep;
98	int err;
99
100	sprintf(hwname, "HDA Codec %d", codec->addr);
101	err = snd_hwdep_new(codec->card, hwname, codec->addr, &hwdep);
102	if (err < 0)
103		return err;
104	codec->hwdep = hwdep;
105	sprintf(hwdep->name, "HDA Codec %d", codec->addr);
106	hwdep->iface = SNDRV_HWDEP_IFACE_HDA;
107	hwdep->private_data = codec;
108	hwdep->exclusive = 1;
109
110	hwdep->ops.open = hda_hwdep_open;
111	hwdep->ops.ioctl = hda_hwdep_ioctl;
112#ifdef CONFIG_COMPAT
113	hwdep->ops.ioctl_compat = hda_hwdep_ioctl_compat;
114#endif
115
116	/* for sysfs */
117	hwdep->dev->groups = snd_hda_dev_attr_groups;
118	dev_set_drvdata(hwdep->dev, codec);
119
120	return 0;
121}
122