162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  Interface for hwdep device
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci *  Copyright (C) 2004 Takashi Iwai <tiwai@suse.de>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <sound/core.h>
962306a36Sopenharmony_ci#include <sound/hwdep.h>
1062306a36Sopenharmony_ci#include <linux/uaccess.h>
1162306a36Sopenharmony_ci#include <linux/nospec.h>
1262306a36Sopenharmony_ci#include "emux_voice.h"
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#define TMP_CLIENT_ID	0x1001
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci/*
1762306a36Sopenharmony_ci * load patch
1862306a36Sopenharmony_ci */
1962306a36Sopenharmony_cistatic int
2062306a36Sopenharmony_cisnd_emux_hwdep_load_patch(struct snd_emux *emu, void __user *arg)
2162306a36Sopenharmony_ci{
2262306a36Sopenharmony_ci	int err;
2362306a36Sopenharmony_ci	struct soundfont_patch_info patch;
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci	if (copy_from_user(&patch, arg, sizeof(patch)))
2662306a36Sopenharmony_ci		return -EFAULT;
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci	if (patch.key == GUS_PATCH)
2962306a36Sopenharmony_ci		return snd_soundfont_load_guspatch(emu->sflist, arg,
3062306a36Sopenharmony_ci						   patch.len + sizeof(patch),
3162306a36Sopenharmony_ci						   TMP_CLIENT_ID);
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci	if (patch.type >= SNDRV_SFNT_LOAD_INFO &&
3462306a36Sopenharmony_ci	    patch.type <= SNDRV_SFNT_PROBE_DATA) {
3562306a36Sopenharmony_ci		err = snd_soundfont_load(emu->sflist, arg, patch.len + sizeof(patch), TMP_CLIENT_ID);
3662306a36Sopenharmony_ci		if (err < 0)
3762306a36Sopenharmony_ci			return err;
3862306a36Sopenharmony_ci	} else {
3962306a36Sopenharmony_ci		if (emu->ops.load_fx)
4062306a36Sopenharmony_ci			return emu->ops.load_fx(emu, patch.type, patch.optarg, arg, patch.len + sizeof(patch));
4162306a36Sopenharmony_ci		else
4262306a36Sopenharmony_ci			return -EINVAL;
4362306a36Sopenharmony_ci	}
4462306a36Sopenharmony_ci	return 0;
4562306a36Sopenharmony_ci}
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci/*
4862306a36Sopenharmony_ci * set misc mode
4962306a36Sopenharmony_ci */
5062306a36Sopenharmony_cistatic int
5162306a36Sopenharmony_cisnd_emux_hwdep_misc_mode(struct snd_emux *emu, void __user *arg)
5262306a36Sopenharmony_ci{
5362306a36Sopenharmony_ci	struct snd_emux_misc_mode info;
5462306a36Sopenharmony_ci	int i;
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci	if (copy_from_user(&info, arg, sizeof(info)))
5762306a36Sopenharmony_ci		return -EFAULT;
5862306a36Sopenharmony_ci	if (info.mode < 0 || info.mode >= EMUX_MD_END)
5962306a36Sopenharmony_ci		return -EINVAL;
6062306a36Sopenharmony_ci	info.mode = array_index_nospec(info.mode, EMUX_MD_END);
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	if (info.port < 0) {
6362306a36Sopenharmony_ci		for (i = 0; i < emu->num_ports; i++)
6462306a36Sopenharmony_ci			emu->portptrs[i]->ctrls[info.mode] = info.value;
6562306a36Sopenharmony_ci	} else {
6662306a36Sopenharmony_ci		if (info.port < emu->num_ports) {
6762306a36Sopenharmony_ci			info.port = array_index_nospec(info.port, emu->num_ports);
6862306a36Sopenharmony_ci			emu->portptrs[info.port]->ctrls[info.mode] = info.value;
6962306a36Sopenharmony_ci		}
7062306a36Sopenharmony_ci	}
7162306a36Sopenharmony_ci	return 0;
7262306a36Sopenharmony_ci}
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci/*
7662306a36Sopenharmony_ci * ioctl
7762306a36Sopenharmony_ci */
7862306a36Sopenharmony_cistatic int
7962306a36Sopenharmony_cisnd_emux_hwdep_ioctl(struct snd_hwdep * hw, struct file *file,
8062306a36Sopenharmony_ci		     unsigned int cmd, unsigned long arg)
8162306a36Sopenharmony_ci{
8262306a36Sopenharmony_ci	struct snd_emux *emu = hw->private_data;
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	switch (cmd) {
8562306a36Sopenharmony_ci	case SNDRV_EMUX_IOCTL_VERSION:
8662306a36Sopenharmony_ci		return put_user(SNDRV_EMUX_VERSION, (unsigned int __user *)arg);
8762306a36Sopenharmony_ci	case SNDRV_EMUX_IOCTL_LOAD_PATCH:
8862306a36Sopenharmony_ci		return snd_emux_hwdep_load_patch(emu, (void __user *)arg);
8962306a36Sopenharmony_ci	case SNDRV_EMUX_IOCTL_RESET_SAMPLES:
9062306a36Sopenharmony_ci		snd_soundfont_remove_samples(emu->sflist);
9162306a36Sopenharmony_ci		break;
9262306a36Sopenharmony_ci	case SNDRV_EMUX_IOCTL_REMOVE_LAST_SAMPLES:
9362306a36Sopenharmony_ci		snd_soundfont_remove_unlocked(emu->sflist);
9462306a36Sopenharmony_ci		break;
9562306a36Sopenharmony_ci	case SNDRV_EMUX_IOCTL_MEM_AVAIL:
9662306a36Sopenharmony_ci		if (emu->memhdr) {
9762306a36Sopenharmony_ci			int size = snd_util_mem_avail(emu->memhdr);
9862306a36Sopenharmony_ci			return put_user(size, (unsigned int __user *)arg);
9962306a36Sopenharmony_ci		}
10062306a36Sopenharmony_ci		break;
10162306a36Sopenharmony_ci	case SNDRV_EMUX_IOCTL_MISC_MODE:
10262306a36Sopenharmony_ci		return snd_emux_hwdep_misc_mode(emu, (void __user *)arg);
10362306a36Sopenharmony_ci	}
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	return 0;
10662306a36Sopenharmony_ci}
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci/*
11062306a36Sopenharmony_ci * register hwdep device
11162306a36Sopenharmony_ci */
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ciint
11462306a36Sopenharmony_cisnd_emux_init_hwdep(struct snd_emux *emu)
11562306a36Sopenharmony_ci{
11662306a36Sopenharmony_ci	struct snd_hwdep *hw;
11762306a36Sopenharmony_ci	int err;
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	err = snd_hwdep_new(emu->card, SNDRV_EMUX_HWDEP_NAME, emu->hwdep_idx, &hw);
12062306a36Sopenharmony_ci	if (err < 0)
12162306a36Sopenharmony_ci		return err;
12262306a36Sopenharmony_ci	emu->hwdep = hw;
12362306a36Sopenharmony_ci	strcpy(hw->name, SNDRV_EMUX_HWDEP_NAME);
12462306a36Sopenharmony_ci	hw->iface = SNDRV_HWDEP_IFACE_EMUX_WAVETABLE;
12562306a36Sopenharmony_ci	hw->ops.ioctl = snd_emux_hwdep_ioctl;
12662306a36Sopenharmony_ci	/* The ioctl parameter types are compatible between 32- and
12762306a36Sopenharmony_ci	 * 64-bit architectures, so use the same function. */
12862306a36Sopenharmony_ci	hw->ops.ioctl_compat = snd_emux_hwdep_ioctl;
12962306a36Sopenharmony_ci	hw->exclusive = 1;
13062306a36Sopenharmony_ci	hw->private_data = emu;
13162306a36Sopenharmony_ci	err = snd_card_register(emu->card);
13262306a36Sopenharmony_ci	if (err < 0)
13362306a36Sopenharmony_ci		return err;
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	return 0;
13662306a36Sopenharmony_ci}
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci/*
14062306a36Sopenharmony_ci * unregister
14162306a36Sopenharmony_ci */
14262306a36Sopenharmony_civoid
14362306a36Sopenharmony_cisnd_emux_delete_hwdep(struct snd_emux *emu)
14462306a36Sopenharmony_ci{
14562306a36Sopenharmony_ci	if (emu->hwdep) {
14662306a36Sopenharmony_ci		snd_device_free(emu->card, emu->hwdep);
14762306a36Sopenharmony_ci		emu->hwdep = NULL;
14862306a36Sopenharmony_ci	}
14962306a36Sopenharmony_ci}
150