162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * HWDEP Interface for HD-audio codec
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (c) 2007 Takashi Iwai <tiwai@suse.de>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/init.h>
962306a36Sopenharmony_ci#include <linux/slab.h>
1062306a36Sopenharmony_ci#include <linux/compat.h>
1162306a36Sopenharmony_ci#include <linux/nospec.h>
1262306a36Sopenharmony_ci#include <sound/core.h>
1362306a36Sopenharmony_ci#include <sound/hda_codec.h>
1462306a36Sopenharmony_ci#include "hda_local.h"
1562306a36Sopenharmony_ci#include <sound/hda_hwdep.h>
1662306a36Sopenharmony_ci#include <sound/minors.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci/*
1962306a36Sopenharmony_ci * write/read an out-of-bound verb
2062306a36Sopenharmony_ci */
2162306a36Sopenharmony_cistatic int verb_write_ioctl(struct hda_codec *codec,
2262306a36Sopenharmony_ci			    struct hda_verb_ioctl __user *arg)
2362306a36Sopenharmony_ci{
2462306a36Sopenharmony_ci	u32 verb, res;
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci	if (get_user(verb, &arg->verb))
2762306a36Sopenharmony_ci		return -EFAULT;
2862306a36Sopenharmony_ci	res = snd_hda_codec_read(codec, verb >> 24, 0,
2962306a36Sopenharmony_ci				 (verb >> 8) & 0xffff, verb & 0xff);
3062306a36Sopenharmony_ci	if (put_user(res, &arg->res))
3162306a36Sopenharmony_ci		return -EFAULT;
3262306a36Sopenharmony_ci	return 0;
3362306a36Sopenharmony_ci}
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_cistatic int get_wcap_ioctl(struct hda_codec *codec,
3662306a36Sopenharmony_ci			  struct hda_verb_ioctl __user *arg)
3762306a36Sopenharmony_ci{
3862306a36Sopenharmony_ci	u32 verb, res;
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	if (get_user(verb, &arg->verb))
4162306a36Sopenharmony_ci		return -EFAULT;
4262306a36Sopenharmony_ci	/* open-code get_wcaps(verb>>24) with nospec */
4362306a36Sopenharmony_ci	verb >>= 24;
4462306a36Sopenharmony_ci	if (verb < codec->core.start_nid ||
4562306a36Sopenharmony_ci	    verb >= codec->core.start_nid + codec->core.num_nodes) {
4662306a36Sopenharmony_ci		res = 0;
4762306a36Sopenharmony_ci	} else {
4862306a36Sopenharmony_ci		verb -= codec->core.start_nid;
4962306a36Sopenharmony_ci		verb = array_index_nospec(verb, codec->core.num_nodes);
5062306a36Sopenharmony_ci		res = codec->wcaps[verb];
5162306a36Sopenharmony_ci	}
5262306a36Sopenharmony_ci	if (put_user(res, &arg->res))
5362306a36Sopenharmony_ci		return -EFAULT;
5462306a36Sopenharmony_ci	return 0;
5562306a36Sopenharmony_ci}
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci/*
5962306a36Sopenharmony_ci */
6062306a36Sopenharmony_cistatic int hda_hwdep_ioctl(struct snd_hwdep *hw, struct file *file,
6162306a36Sopenharmony_ci			   unsigned int cmd, unsigned long arg)
6262306a36Sopenharmony_ci{
6362306a36Sopenharmony_ci	struct hda_codec *codec = hw->private_data;
6462306a36Sopenharmony_ci	void __user *argp = (void __user *)arg;
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	switch (cmd) {
6762306a36Sopenharmony_ci	case HDA_IOCTL_PVERSION:
6862306a36Sopenharmony_ci		return put_user(HDA_HWDEP_VERSION, (int __user *)argp);
6962306a36Sopenharmony_ci	case HDA_IOCTL_VERB_WRITE:
7062306a36Sopenharmony_ci		return verb_write_ioctl(codec, argp);
7162306a36Sopenharmony_ci	case HDA_IOCTL_GET_WCAP:
7262306a36Sopenharmony_ci		return get_wcap_ioctl(codec, argp);
7362306a36Sopenharmony_ci	}
7462306a36Sopenharmony_ci	return -ENOIOCTLCMD;
7562306a36Sopenharmony_ci}
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci#ifdef CONFIG_COMPAT
7862306a36Sopenharmony_cistatic int hda_hwdep_ioctl_compat(struct snd_hwdep *hw, struct file *file,
7962306a36Sopenharmony_ci				  unsigned int cmd, unsigned long arg)
8062306a36Sopenharmony_ci{
8162306a36Sopenharmony_ci	return hda_hwdep_ioctl(hw, file, cmd, (unsigned long)compat_ptr(arg));
8262306a36Sopenharmony_ci}
8362306a36Sopenharmony_ci#endif
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_cistatic int hda_hwdep_open(struct snd_hwdep *hw, struct file *file)
8662306a36Sopenharmony_ci{
8762306a36Sopenharmony_ci#ifndef CONFIG_SND_DEBUG_VERBOSE
8862306a36Sopenharmony_ci	if (!capable(CAP_SYS_RAWIO))
8962306a36Sopenharmony_ci		return -EACCES;
9062306a36Sopenharmony_ci#endif
9162306a36Sopenharmony_ci	return 0;
9262306a36Sopenharmony_ci}
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ciint snd_hda_create_hwdep(struct hda_codec *codec)
9562306a36Sopenharmony_ci{
9662306a36Sopenharmony_ci	char hwname[16];
9762306a36Sopenharmony_ci	struct snd_hwdep *hwdep;
9862306a36Sopenharmony_ci	int err;
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	sprintf(hwname, "HDA Codec %d", codec->addr);
10162306a36Sopenharmony_ci	err = snd_hwdep_new(codec->card, hwname, codec->addr, &hwdep);
10262306a36Sopenharmony_ci	if (err < 0)
10362306a36Sopenharmony_ci		return err;
10462306a36Sopenharmony_ci	codec->hwdep = hwdep;
10562306a36Sopenharmony_ci	sprintf(hwdep->name, "HDA Codec %d", codec->addr);
10662306a36Sopenharmony_ci	hwdep->iface = SNDRV_HWDEP_IFACE_HDA;
10762306a36Sopenharmony_ci	hwdep->private_data = codec;
10862306a36Sopenharmony_ci	hwdep->exclusive = 1;
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	hwdep->ops.open = hda_hwdep_open;
11162306a36Sopenharmony_ci	hwdep->ops.ioctl = hda_hwdep_ioctl;
11262306a36Sopenharmony_ci#ifdef CONFIG_COMPAT
11362306a36Sopenharmony_ci	hwdep->ops.ioctl_compat = hda_hwdep_ioctl_compat;
11462306a36Sopenharmony_ci#endif
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	/* for sysfs */
11762306a36Sopenharmony_ci	hwdep->dev->groups = snd_hda_dev_attr_groups;
11862306a36Sopenharmony_ci	dev_set_drvdata(hwdep->dev, codec);
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	return 0;
12162306a36Sopenharmony_ci}
122