18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * HWDEP Interface for HD-audio codec
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (c) 2007 Takashi Iwai <tiwai@suse.de>
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/init.h>
98c2ecf20Sopenharmony_ci#include <linux/slab.h>
108c2ecf20Sopenharmony_ci#include <linux/compat.h>
118c2ecf20Sopenharmony_ci#include <linux/nospec.h>
128c2ecf20Sopenharmony_ci#include <sound/core.h>
138c2ecf20Sopenharmony_ci#include <sound/hda_codec.h>
148c2ecf20Sopenharmony_ci#include "hda_local.h"
158c2ecf20Sopenharmony_ci#include <sound/hda_hwdep.h>
168c2ecf20Sopenharmony_ci#include <sound/minors.h>
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_ci/*
198c2ecf20Sopenharmony_ci * write/read an out-of-bound verb
208c2ecf20Sopenharmony_ci */
218c2ecf20Sopenharmony_cistatic int verb_write_ioctl(struct hda_codec *codec,
228c2ecf20Sopenharmony_ci			    struct hda_verb_ioctl __user *arg)
238c2ecf20Sopenharmony_ci{
248c2ecf20Sopenharmony_ci	u32 verb, res;
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_ci	if (get_user(verb, &arg->verb))
278c2ecf20Sopenharmony_ci		return -EFAULT;
288c2ecf20Sopenharmony_ci	res = snd_hda_codec_read(codec, verb >> 24, 0,
298c2ecf20Sopenharmony_ci				 (verb >> 8) & 0xffff, verb & 0xff);
308c2ecf20Sopenharmony_ci	if (put_user(res, &arg->res))
318c2ecf20Sopenharmony_ci		return -EFAULT;
328c2ecf20Sopenharmony_ci	return 0;
338c2ecf20Sopenharmony_ci}
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_cistatic int get_wcap_ioctl(struct hda_codec *codec,
368c2ecf20Sopenharmony_ci			  struct hda_verb_ioctl __user *arg)
378c2ecf20Sopenharmony_ci{
388c2ecf20Sopenharmony_ci	u32 verb, res;
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci	if (get_user(verb, &arg->verb))
418c2ecf20Sopenharmony_ci		return -EFAULT;
428c2ecf20Sopenharmony_ci	/* open-code get_wcaps(verb>>24) with nospec */
438c2ecf20Sopenharmony_ci	verb >>= 24;
448c2ecf20Sopenharmony_ci	if (verb < codec->core.start_nid ||
458c2ecf20Sopenharmony_ci	    verb >= codec->core.start_nid + codec->core.num_nodes) {
468c2ecf20Sopenharmony_ci		res = 0;
478c2ecf20Sopenharmony_ci	} else {
488c2ecf20Sopenharmony_ci		verb -= codec->core.start_nid;
498c2ecf20Sopenharmony_ci		verb = array_index_nospec(verb, codec->core.num_nodes);
508c2ecf20Sopenharmony_ci		res = codec->wcaps[verb];
518c2ecf20Sopenharmony_ci	}
528c2ecf20Sopenharmony_ci	if (put_user(res, &arg->res))
538c2ecf20Sopenharmony_ci		return -EFAULT;
548c2ecf20Sopenharmony_ci	return 0;
558c2ecf20Sopenharmony_ci}
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci/*
598c2ecf20Sopenharmony_ci */
608c2ecf20Sopenharmony_cistatic int hda_hwdep_ioctl(struct snd_hwdep *hw, struct file *file,
618c2ecf20Sopenharmony_ci			   unsigned int cmd, unsigned long arg)
628c2ecf20Sopenharmony_ci{
638c2ecf20Sopenharmony_ci	struct hda_codec *codec = hw->private_data;
648c2ecf20Sopenharmony_ci	void __user *argp = (void __user *)arg;
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci	switch (cmd) {
678c2ecf20Sopenharmony_ci	case HDA_IOCTL_PVERSION:
688c2ecf20Sopenharmony_ci		return put_user(HDA_HWDEP_VERSION, (int __user *)argp);
698c2ecf20Sopenharmony_ci	case HDA_IOCTL_VERB_WRITE:
708c2ecf20Sopenharmony_ci		return verb_write_ioctl(codec, argp);
718c2ecf20Sopenharmony_ci	case HDA_IOCTL_GET_WCAP:
728c2ecf20Sopenharmony_ci		return get_wcap_ioctl(codec, argp);
738c2ecf20Sopenharmony_ci	}
748c2ecf20Sopenharmony_ci	return -ENOIOCTLCMD;
758c2ecf20Sopenharmony_ci}
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci#ifdef CONFIG_COMPAT
788c2ecf20Sopenharmony_cistatic int hda_hwdep_ioctl_compat(struct snd_hwdep *hw, struct file *file,
798c2ecf20Sopenharmony_ci				  unsigned int cmd, unsigned long arg)
808c2ecf20Sopenharmony_ci{
818c2ecf20Sopenharmony_ci	return hda_hwdep_ioctl(hw, file, cmd, (unsigned long)compat_ptr(arg));
828c2ecf20Sopenharmony_ci}
838c2ecf20Sopenharmony_ci#endif
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_cistatic int hda_hwdep_open(struct snd_hwdep *hw, struct file *file)
868c2ecf20Sopenharmony_ci{
878c2ecf20Sopenharmony_ci#ifndef CONFIG_SND_DEBUG_VERBOSE
888c2ecf20Sopenharmony_ci	if (!capable(CAP_SYS_RAWIO))
898c2ecf20Sopenharmony_ci		return -EACCES;
908c2ecf20Sopenharmony_ci#endif
918c2ecf20Sopenharmony_ci	return 0;
928c2ecf20Sopenharmony_ci}
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ciint snd_hda_create_hwdep(struct hda_codec *codec)
958c2ecf20Sopenharmony_ci{
968c2ecf20Sopenharmony_ci	char hwname[16];
978c2ecf20Sopenharmony_ci	struct snd_hwdep *hwdep;
988c2ecf20Sopenharmony_ci	int err;
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci	sprintf(hwname, "HDA Codec %d", codec->addr);
1018c2ecf20Sopenharmony_ci	err = snd_hwdep_new(codec->card, hwname, codec->addr, &hwdep);
1028c2ecf20Sopenharmony_ci	if (err < 0)
1038c2ecf20Sopenharmony_ci		return err;
1048c2ecf20Sopenharmony_ci	codec->hwdep = hwdep;
1058c2ecf20Sopenharmony_ci	sprintf(hwdep->name, "HDA Codec %d", codec->addr);
1068c2ecf20Sopenharmony_ci	hwdep->iface = SNDRV_HWDEP_IFACE_HDA;
1078c2ecf20Sopenharmony_ci	hwdep->private_data = codec;
1088c2ecf20Sopenharmony_ci	hwdep->exclusive = 1;
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci	hwdep->ops.open = hda_hwdep_open;
1118c2ecf20Sopenharmony_ci	hwdep->ops.ioctl = hda_hwdep_ioctl;
1128c2ecf20Sopenharmony_ci#ifdef CONFIG_COMPAT
1138c2ecf20Sopenharmony_ci	hwdep->ops.ioctl_compat = hda_hwdep_ioctl_compat;
1148c2ecf20Sopenharmony_ci#endif
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	/* for sysfs */
1178c2ecf20Sopenharmony_ci	hwdep->dev.groups = snd_hda_dev_attr_groups;
1188c2ecf20Sopenharmony_ci	dev_set_drvdata(&hwdep->dev, codec);
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci	return 0;
1218c2ecf20Sopenharmony_ci}
122