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