mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
480 lines
12 KiB
480 lines
12 KiB
// SPDX-License-Identifier: GPL-2.0-or-later |
|
/* |
|
* compat ioctls for control API |
|
* |
|
* Copyright (c) by Takashi Iwai <[email protected]> |
|
*/ |
|
|
|
/* this file included from control.c */ |
|
|
|
#include <linux/compat.h> |
|
#include <linux/slab.h> |
|
|
|
struct snd_ctl_elem_list32 { |
|
u32 offset; |
|
u32 space; |
|
u32 used; |
|
u32 count; |
|
u32 pids; |
|
unsigned char reserved[50]; |
|
} /* don't set packed attribute here */; |
|
|
|
static int snd_ctl_elem_list_compat(struct snd_card *card, |
|
struct snd_ctl_elem_list32 __user *data32) |
|
{ |
|
struct snd_ctl_elem_list data = {}; |
|
compat_caddr_t ptr; |
|
int err; |
|
|
|
/* offset, space, used, count */ |
|
if (copy_from_user(&data, data32, 4 * sizeof(u32))) |
|
return -EFAULT; |
|
/* pids */ |
|
if (get_user(ptr, &data32->pids)) |
|
return -EFAULT; |
|
data.pids = compat_ptr(ptr); |
|
err = snd_ctl_elem_list(card, &data); |
|
if (err < 0) |
|
return err; |
|
/* copy the result */ |
|
if (copy_to_user(data32, &data, 4 * sizeof(u32))) |
|
return -EFAULT; |
|
return 0; |
|
} |
|
|
|
/* |
|
* control element info |
|
* it uses union, so the things are not easy.. |
|
*/ |
|
|
|
struct snd_ctl_elem_info32 { |
|
struct snd_ctl_elem_id id; // the size of struct is same |
|
s32 type; |
|
u32 access; |
|
u32 count; |
|
s32 owner; |
|
union { |
|
struct { |
|
s32 min; |
|
s32 max; |
|
s32 step; |
|
} integer; |
|
struct { |
|
u64 min; |
|
u64 max; |
|
u64 step; |
|
} integer64; |
|
struct { |
|
u32 items; |
|
u32 item; |
|
char name[64]; |
|
u64 names_ptr; |
|
u32 names_length; |
|
} enumerated; |
|
unsigned char reserved[128]; |
|
} value; |
|
unsigned char reserved[64]; |
|
} __attribute__((packed)); |
|
|
|
static int snd_ctl_elem_info_compat(struct snd_ctl_file *ctl, |
|
struct snd_ctl_elem_info32 __user *data32) |
|
{ |
|
struct snd_ctl_elem_info *data; |
|
int err; |
|
|
|
data = kzalloc(sizeof(*data), GFP_KERNEL); |
|
if (! data) |
|
return -ENOMEM; |
|
|
|
err = -EFAULT; |
|
/* copy id */ |
|
if (copy_from_user(&data->id, &data32->id, sizeof(data->id))) |
|
goto error; |
|
/* we need to copy the item index. |
|
* hope this doesn't break anything.. |
|
*/ |
|
if (get_user(data->value.enumerated.item, &data32->value.enumerated.item)) |
|
goto error; |
|
|
|
err = snd_ctl_elem_info(ctl, data); |
|
if (err < 0) |
|
goto error; |
|
/* restore info to 32bit */ |
|
err = -EFAULT; |
|
/* id, type, access, count */ |
|
if (copy_to_user(&data32->id, &data->id, sizeof(data->id)) || |
|
copy_to_user(&data32->type, &data->type, 3 * sizeof(u32))) |
|
goto error; |
|
if (put_user(data->owner, &data32->owner)) |
|
goto error; |
|
switch (data->type) { |
|
case SNDRV_CTL_ELEM_TYPE_BOOLEAN: |
|
case SNDRV_CTL_ELEM_TYPE_INTEGER: |
|
if (put_user(data->value.integer.min, &data32->value.integer.min) || |
|
put_user(data->value.integer.max, &data32->value.integer.max) || |
|
put_user(data->value.integer.step, &data32->value.integer.step)) |
|
goto error; |
|
break; |
|
case SNDRV_CTL_ELEM_TYPE_INTEGER64: |
|
if (copy_to_user(&data32->value.integer64, |
|
&data->value.integer64, |
|
sizeof(data->value.integer64))) |
|
goto error; |
|
break; |
|
case SNDRV_CTL_ELEM_TYPE_ENUMERATED: |
|
if (copy_to_user(&data32->value.enumerated, |
|
&data->value.enumerated, |
|
sizeof(data->value.enumerated))) |
|
goto error; |
|
break; |
|
default: |
|
break; |
|
} |
|
err = 0; |
|
error: |
|
kfree(data); |
|
return err; |
|
} |
|
|
|
/* read / write */ |
|
struct snd_ctl_elem_value32 { |
|
struct snd_ctl_elem_id id; |
|
unsigned int indirect; /* bit-field causes misalignment */ |
|
union { |
|
s32 integer[128]; |
|
unsigned char data[512]; |
|
#ifndef CONFIG_X86_64 |
|
s64 integer64[64]; |
|
#endif |
|
} value; |
|
unsigned char reserved[128]; |
|
}; |
|
|
|
#ifdef CONFIG_X86_X32 |
|
/* x32 has a different alignment for 64bit values from ia32 */ |
|
struct snd_ctl_elem_value_x32 { |
|
struct snd_ctl_elem_id id; |
|
unsigned int indirect; /* bit-field causes misalignment */ |
|
union { |
|
s32 integer[128]; |
|
unsigned char data[512]; |
|
s64 integer64[64]; |
|
} value; |
|
unsigned char reserved[128]; |
|
}; |
|
#endif /* CONFIG_X86_X32 */ |
|
|
|
/* get the value type and count of the control */ |
|
static int get_ctl_type(struct snd_card *card, struct snd_ctl_elem_id *id, |
|
int *countp) |
|
{ |
|
struct snd_kcontrol *kctl; |
|
struct snd_ctl_elem_info *info; |
|
int err; |
|
|
|
down_read(&card->controls_rwsem); |
|
kctl = snd_ctl_find_id(card, id); |
|
if (! kctl) { |
|
up_read(&card->controls_rwsem); |
|
return -ENOENT; |
|
} |
|
info = kzalloc(sizeof(*info), GFP_KERNEL); |
|
if (info == NULL) { |
|
up_read(&card->controls_rwsem); |
|
return -ENOMEM; |
|
} |
|
info->id = *id; |
|
err = snd_power_ref_and_wait(card); |
|
if (!err) |
|
err = kctl->info(kctl, info); |
|
snd_power_unref(card); |
|
up_read(&card->controls_rwsem); |
|
if (err >= 0) { |
|
err = info->type; |
|
*countp = info->count; |
|
} |
|
kfree(info); |
|
return err; |
|
} |
|
|
|
static int get_elem_size(int type, int count) |
|
{ |
|
switch (type) { |
|
case SNDRV_CTL_ELEM_TYPE_INTEGER64: |
|
return sizeof(s64) * count; |
|
case SNDRV_CTL_ELEM_TYPE_ENUMERATED: |
|
return sizeof(int) * count; |
|
case SNDRV_CTL_ELEM_TYPE_BYTES: |
|
return 512; |
|
case SNDRV_CTL_ELEM_TYPE_IEC958: |
|
return sizeof(struct snd_aes_iec958); |
|
default: |
|
return -1; |
|
} |
|
} |
|
|
|
static int copy_ctl_value_from_user(struct snd_card *card, |
|
struct snd_ctl_elem_value *data, |
|
void __user *userdata, |
|
void __user *valuep, |
|
int *typep, int *countp) |
|
{ |
|
struct snd_ctl_elem_value32 __user *data32 = userdata; |
|
int i, type, size; |
|
int count; |
|
unsigned int indirect; |
|
|
|
if (copy_from_user(&data->id, &data32->id, sizeof(data->id))) |
|
return -EFAULT; |
|
if (get_user(indirect, &data32->indirect)) |
|
return -EFAULT; |
|
if (indirect) |
|
return -EINVAL; |
|
type = get_ctl_type(card, &data->id, &count); |
|
if (type < 0) |
|
return type; |
|
|
|
if (type == SNDRV_CTL_ELEM_TYPE_BOOLEAN || |
|
type == SNDRV_CTL_ELEM_TYPE_INTEGER) { |
|
for (i = 0; i < count; i++) { |
|
s32 __user *intp = valuep; |
|
int val; |
|
if (get_user(val, &intp[i])) |
|
return -EFAULT; |
|
data->value.integer.value[i] = val; |
|
} |
|
} else { |
|
size = get_elem_size(type, count); |
|
if (size < 0) { |
|
dev_err(card->dev, "snd_ioctl32_ctl_elem_value: unknown type %d\n", type); |
|
return -EINVAL; |
|
} |
|
if (copy_from_user(data->value.bytes.data, valuep, size)) |
|
return -EFAULT; |
|
} |
|
|
|
*typep = type; |
|
*countp = count; |
|
return 0; |
|
} |
|
|
|
/* restore the value to 32bit */ |
|
static int copy_ctl_value_to_user(void __user *userdata, |
|
void __user *valuep, |
|
struct snd_ctl_elem_value *data, |
|
int type, int count) |
|
{ |
|
int i, size; |
|
|
|
if (type == SNDRV_CTL_ELEM_TYPE_BOOLEAN || |
|
type == SNDRV_CTL_ELEM_TYPE_INTEGER) { |
|
for (i = 0; i < count; i++) { |
|
s32 __user *intp = valuep; |
|
int val; |
|
val = data->value.integer.value[i]; |
|
if (put_user(val, &intp[i])) |
|
return -EFAULT; |
|
} |
|
} else { |
|
size = get_elem_size(type, count); |
|
if (copy_to_user(valuep, data->value.bytes.data, size)) |
|
return -EFAULT; |
|
} |
|
return 0; |
|
} |
|
|
|
static int ctl_elem_read_user(struct snd_card *card, |
|
void __user *userdata, void __user *valuep) |
|
{ |
|
struct snd_ctl_elem_value *data; |
|
int err, type, count; |
|
|
|
data = kzalloc(sizeof(*data), GFP_KERNEL); |
|
if (data == NULL) |
|
return -ENOMEM; |
|
|
|
err = copy_ctl_value_from_user(card, data, userdata, valuep, |
|
&type, &count); |
|
if (err < 0) |
|
goto error; |
|
|
|
err = snd_ctl_elem_read(card, data); |
|
if (err < 0) |
|
goto error; |
|
err = copy_ctl_value_to_user(userdata, valuep, data, type, count); |
|
error: |
|
kfree(data); |
|
return err; |
|
} |
|
|
|
static int ctl_elem_write_user(struct snd_ctl_file *file, |
|
void __user *userdata, void __user *valuep) |
|
{ |
|
struct snd_ctl_elem_value *data; |
|
struct snd_card *card = file->card; |
|
int err, type, count; |
|
|
|
data = kzalloc(sizeof(*data), GFP_KERNEL); |
|
if (data == NULL) |
|
return -ENOMEM; |
|
|
|
err = copy_ctl_value_from_user(card, data, userdata, valuep, |
|
&type, &count); |
|
if (err < 0) |
|
goto error; |
|
|
|
err = snd_ctl_elem_write(card, file, data); |
|
if (err < 0) |
|
goto error; |
|
err = copy_ctl_value_to_user(userdata, valuep, data, type, count); |
|
error: |
|
kfree(data); |
|
return err; |
|
} |
|
|
|
static int snd_ctl_elem_read_user_compat(struct snd_card *card, |
|
struct snd_ctl_elem_value32 __user *data32) |
|
{ |
|
return ctl_elem_read_user(card, data32, &data32->value); |
|
} |
|
|
|
static int snd_ctl_elem_write_user_compat(struct snd_ctl_file *file, |
|
struct snd_ctl_elem_value32 __user *data32) |
|
{ |
|
return ctl_elem_write_user(file, data32, &data32->value); |
|
} |
|
|
|
#ifdef CONFIG_X86_X32 |
|
static int snd_ctl_elem_read_user_x32(struct snd_card *card, |
|
struct snd_ctl_elem_value_x32 __user *data32) |
|
{ |
|
return ctl_elem_read_user(card, data32, &data32->value); |
|
} |
|
|
|
static int snd_ctl_elem_write_user_x32(struct snd_ctl_file *file, |
|
struct snd_ctl_elem_value_x32 __user *data32) |
|
{ |
|
return ctl_elem_write_user(file, data32, &data32->value); |
|
} |
|
#endif /* CONFIG_X86_X32 */ |
|
|
|
/* add or replace a user control */ |
|
static int snd_ctl_elem_add_compat(struct snd_ctl_file *file, |
|
struct snd_ctl_elem_info32 __user *data32, |
|
int replace) |
|
{ |
|
struct snd_ctl_elem_info *data; |
|
int err; |
|
|
|
data = kzalloc(sizeof(*data), GFP_KERNEL); |
|
if (! data) |
|
return -ENOMEM; |
|
|
|
err = -EFAULT; |
|
/* id, type, access, count */ \ |
|
if (copy_from_user(&data->id, &data32->id, sizeof(data->id)) || |
|
copy_from_user(&data->type, &data32->type, 3 * sizeof(u32))) |
|
goto error; |
|
if (get_user(data->owner, &data32->owner)) |
|
goto error; |
|
switch (data->type) { |
|
case SNDRV_CTL_ELEM_TYPE_BOOLEAN: |
|
case SNDRV_CTL_ELEM_TYPE_INTEGER: |
|
if (get_user(data->value.integer.min, &data32->value.integer.min) || |
|
get_user(data->value.integer.max, &data32->value.integer.max) || |
|
get_user(data->value.integer.step, &data32->value.integer.step)) |
|
goto error; |
|
break; |
|
case SNDRV_CTL_ELEM_TYPE_INTEGER64: |
|
if (copy_from_user(&data->value.integer64, |
|
&data32->value.integer64, |
|
sizeof(data->value.integer64))) |
|
goto error; |
|
break; |
|
case SNDRV_CTL_ELEM_TYPE_ENUMERATED: |
|
if (copy_from_user(&data->value.enumerated, |
|
&data32->value.enumerated, |
|
sizeof(data->value.enumerated))) |
|
goto error; |
|
data->value.enumerated.names_ptr = |
|
(uintptr_t)compat_ptr(data->value.enumerated.names_ptr); |
|
break; |
|
default: |
|
break; |
|
} |
|
err = snd_ctl_elem_add(file, data, replace); |
|
error: |
|
kfree(data); |
|
return err; |
|
} |
|
|
|
enum { |
|
SNDRV_CTL_IOCTL_ELEM_LIST32 = _IOWR('U', 0x10, struct snd_ctl_elem_list32), |
|
SNDRV_CTL_IOCTL_ELEM_INFO32 = _IOWR('U', 0x11, struct snd_ctl_elem_info32), |
|
SNDRV_CTL_IOCTL_ELEM_READ32 = _IOWR('U', 0x12, struct snd_ctl_elem_value32), |
|
SNDRV_CTL_IOCTL_ELEM_WRITE32 = _IOWR('U', 0x13, struct snd_ctl_elem_value32), |
|
SNDRV_CTL_IOCTL_ELEM_ADD32 = _IOWR('U', 0x17, struct snd_ctl_elem_info32), |
|
SNDRV_CTL_IOCTL_ELEM_REPLACE32 = _IOWR('U', 0x18, struct snd_ctl_elem_info32), |
|
#ifdef CONFIG_X86_X32 |
|
SNDRV_CTL_IOCTL_ELEM_READ_X32 = _IOWR('U', 0x12, struct snd_ctl_elem_value_x32), |
|
SNDRV_CTL_IOCTL_ELEM_WRITE_X32 = _IOWR('U', 0x13, struct snd_ctl_elem_value_x32), |
|
#endif /* CONFIG_X86_X32 */ |
|
}; |
|
|
|
static inline long snd_ctl_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg) |
|
{ |
|
struct snd_ctl_file *ctl; |
|
struct snd_kctl_ioctl *p; |
|
void __user *argp = compat_ptr(arg); |
|
int err; |
|
|
|
ctl = file->private_data; |
|
if (snd_BUG_ON(!ctl || !ctl->card)) |
|
return -ENXIO; |
|
|
|
switch (cmd) { |
|
case SNDRV_CTL_IOCTL_PVERSION: |
|
case SNDRV_CTL_IOCTL_CARD_INFO: |
|
case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS: |
|
case SNDRV_CTL_IOCTL_POWER: |
|
case SNDRV_CTL_IOCTL_POWER_STATE: |
|
case SNDRV_CTL_IOCTL_ELEM_LOCK: |
|
case SNDRV_CTL_IOCTL_ELEM_UNLOCK: |
|
case SNDRV_CTL_IOCTL_ELEM_REMOVE: |
|
case SNDRV_CTL_IOCTL_TLV_READ: |
|
case SNDRV_CTL_IOCTL_TLV_WRITE: |
|
case SNDRV_CTL_IOCTL_TLV_COMMAND: |
|
return snd_ctl_ioctl(file, cmd, (unsigned long)argp); |
|
case SNDRV_CTL_IOCTL_ELEM_LIST32: |
|
return snd_ctl_elem_list_compat(ctl->card, argp); |
|
case SNDRV_CTL_IOCTL_ELEM_INFO32: |
|
return snd_ctl_elem_info_compat(ctl, argp); |
|
case SNDRV_CTL_IOCTL_ELEM_READ32: |
|
return snd_ctl_elem_read_user_compat(ctl->card, argp); |
|
case SNDRV_CTL_IOCTL_ELEM_WRITE32: |
|
return snd_ctl_elem_write_user_compat(ctl, argp); |
|
case SNDRV_CTL_IOCTL_ELEM_ADD32: |
|
return snd_ctl_elem_add_compat(ctl, argp, 0); |
|
case SNDRV_CTL_IOCTL_ELEM_REPLACE32: |
|
return snd_ctl_elem_add_compat(ctl, argp, 1); |
|
#ifdef CONFIG_X86_X32 |
|
case SNDRV_CTL_IOCTL_ELEM_READ_X32: |
|
return snd_ctl_elem_read_user_x32(ctl->card, argp); |
|
case SNDRV_CTL_IOCTL_ELEM_WRITE_X32: |
|
return snd_ctl_elem_write_user_x32(ctl, argp); |
|
#endif /* CONFIG_X86_X32 */ |
|
} |
|
|
|
down_read(&snd_ioctl_rwsem); |
|
list_for_each_entry(p, &snd_control_compat_ioctls, list) { |
|
if (p->fioctl) { |
|
err = p->fioctl(ctl->card, ctl, cmd, arg); |
|
if (err != -ENOIOCTLCMD) { |
|
up_read(&snd_ioctl_rwsem); |
|
return err; |
|
} |
|
} |
|
} |
|
up_read(&snd_ioctl_rwsem); |
|
return -ENOIOCTLCMD; |
|
}
|
|
|