blob: bf48c51080a5969edda07e4016164147af570063 [file] [log] [blame] [edit]
/*
* 32bit -> 64bit ioctl wrapper for control API
* Copyright (c) by Takashi Iwai <tiwai@suse.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <sound/driver.h>
#include <linux/sched.h>
#include <linux/smp_lock.h>
#include <linux/init.h>
#include <linux/time.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <sound/core.h>
#include <sound/control.h>
#include <sound/minors.h>
#include <asm/uaccess.h>
#include "ioctl32.h"
/*
* register/unregister mappers
* exported for other modules
*/
MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
MODULE_DESCRIPTION("ioctl32 wrapper for ALSA");
MODULE_LICENSE("GPL");
int register_ioctl32_conversion(unsigned int cmd, int (*handler)(unsigned int, unsigned int, unsigned long, struct file *));
int unregister_ioctl32_conversion(unsigned int cmd);
int snd_ioctl32_register(struct ioctl32_mapper *mappers)
{
int err;
struct ioctl32_mapper *m;
for (m = mappers; m->cmd; m++) {
err = register_ioctl32_conversion(m->cmd, m->handler);
if (err >= 0)
m->registered++;
}
return 0;
}
void snd_ioctl32_unregister(struct ioctl32_mapper *mappers)
{
struct ioctl32_mapper *m;
for (m = mappers; m->cmd; m++) {
if (m->registered) {
unregister_ioctl32_conversion(m->cmd);
m->registered = 0;
}
}
}
/*
* compatible wrapper
*/
int snd_ioctl32_compat(unsigned int fd, unsigned int cmd, unsigned long arg, struct file *filp)
{
if (! filp->f_op || ! filp->f_op->ioctl)
return -ENOTTY;
return filp->f_op->ioctl(filp->f_dentry->d_inode, filp, cmd, arg);
}
/*
* Controls
*/
struct sndrv_ctl_elem_list32 {
u32 offset;
u32 space;
u32 used;
u32 count;
u32 pids;
unsigned char reserved[50];
} /* don't set packed attribute here */;
static inline int _snd_ioctl32_ctl_elem_list(unsigned int fd, unsigned int cmd, unsigned long arg, struct file *file, unsigned int native_ctl)
{
struct sndrv_ctl_elem_list32 __user *data32;
struct sndrv_ctl_elem_list __user *data;
compat_caddr_t ptr;
int err;
data32 = compat_ptr(arg);
data = compat_alloc_user_space(sizeof(*data));
/* offset, space, used, count */
if (copy_in_user(data, data32, 4 * sizeof(u32)))
return -EFAULT;
/* pids */
if (__get_user(ptr, &data32->pids) ||
__put_user(compat_ptr(ptr), &data->pids))
return -EFAULT;
err = file->f_op->ioctl(file->f_dentry->d_inode, file, native_ctl, (unsigned long)data);
if (err < 0)
return err;
/* copy the result */
if (copy_in_user(data32, data, 4 * sizeof(u32)))
return -EFAULT;
return 0;
}
DEFINE_ALSA_IOCTL_ENTRY(ctl_elem_list, ctl_elem_list, SNDRV_CTL_IOCTL_ELEM_LIST);
/*
* control element info
* it uses union, so the things are not easy..
*/
struct sndrv_ctl_elem_info32 {
struct sndrv_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];
} enumerated;
unsigned char reserved[128];
} value;
unsigned char reserved[64];
} __attribute__((packed));
static inline int _snd_ioctl32_ctl_elem_info(unsigned int fd, unsigned int cmd, unsigned long arg, struct file *file, unsigned int native_ctl)
{
struct sndrv_ctl_elem_info __user *data, *src;
struct sndrv_ctl_elem_info32 __user *data32, *dst;
unsigned int type;
int err;
data32 = compat_ptr(arg);
data = compat_alloc_user_space(sizeof(*data));
/* copy id */
if (copy_in_user(&data->id, &data32->id, sizeof(data->id)))
return -EFAULT;
/* we need to copy the item index.
* hope this doesn't break anything..
*/
if (copy_in_user(&data->value.enumerated.item,
&data32->value.enumerated.item,
sizeof(data->value.enumerated.item)))
return -EFAULT;
err = file->f_op->ioctl(file->f_dentry->d_inode, file, native_ctl, (unsigned long)data);
if (err < 0)
return err;
/* restore info to 32bit */
/* for COPY_CVT macro */
src = data;
dst = data32;
/* id, type, access, count */
if (copy_in_user(&data32->id, &data->id, sizeof(data->id)) ||
copy_in_user(&data32->type, &data->type, 3 * sizeof(u32)))
return -EFAULT;
COPY_CVT(owner);
__get_user(type, &data->type);
switch (type) {
case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
case SNDRV_CTL_ELEM_TYPE_INTEGER:
COPY_CVT(value.integer.min);
COPY_CVT(value.integer.max);
COPY_CVT(value.integer.step);
break;
case SNDRV_CTL_ELEM_TYPE_INTEGER64:
if (copy_in_user(&data32->value.integer64,
&data->value.integer64,
sizeof(data->value.integer64)))
return -EFAULT;
break;
case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
if (copy_in_user(&data32->value.enumerated,
&data->value.enumerated,
sizeof(data->value.enumerated)))
return -EFAULT;
break;
default:
break;
}
return 0;
}
DEFINE_ALSA_IOCTL_ENTRY(ctl_elem_info, ctl_elem_info, SNDRV_CTL_IOCTL_ELEM_INFO);
struct sndrv_ctl_elem_value32 {
struct sndrv_ctl_elem_id id;
unsigned int indirect; /* bit-field causes misalignment */
union {
s32 integer[128]; /* integer and boolean need conversion */
#ifndef CONFIG_X86_64
s64 integer64[64]; /* for alignment */
#endif
unsigned char data[512]; /* others should be compatible */
} value;
unsigned char reserved[128]; /* not used */
};
/* hmm, it's so hard to retrieve the value type from the control id.. */
static int get_ctl_type(snd_card_t *card, snd_ctl_elem_id_t *id)
{
snd_kcontrol_t *kctl;
snd_ctl_elem_info_t info;
int err;
down_read(&card->controls_rwsem);
kctl = snd_ctl_find_id(card, id);
if (! kctl) {
up_read(&card->controls_rwsem);
return -ENXIO;
}
info.id = *id;
err = kctl->info(kctl, &info);
up_read(&card->controls_rwsem);
if (err >= 0)
err = info.type;
return err;
}
extern int snd_major;
static inline int _snd_ioctl32_ctl_elem_value(unsigned int fd, unsigned int cmd, unsigned long arg, struct file *file, unsigned int native_ctl)
{
struct sndrv_ctl_elem_value *data;
struct sndrv_ctl_elem_value32 __user *data32;
snd_ctl_file_t *ctl;
int err, i, indirect;
int type;
/* sanity check */
if (imajor(file->f_dentry->d_inode) != snd_major ||
SNDRV_MINOR_DEVICE(iminor(file->f_dentry->d_inode)) != SNDRV_MINOR_CONTROL)
return -ENOTTY;
if ((ctl = file->private_data) == NULL)
return -ENOTTY;
data32 = compat_ptr(arg);
data = kcalloc(1, sizeof(*data), GFP_KERNEL);
if (data == NULL)
return -ENOMEM;
if (copy_from_user(&data->id, &data32->id, sizeof(data->id))) {
err = -EFAULT;
goto __end;
}
if (__get_user(indirect, &data32->indirect)) {
err = -EFAULT;
goto __end;
}
/* FIXME: indirect access is not supported */
if (indirect) {
err = -EINVAL;
goto __end;
}
type = get_ctl_type(ctl->card, &data->id);
if (type < 0) {
err = type;
goto __end;
}
switch (type) {
case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
case SNDRV_CTL_ELEM_TYPE_INTEGER:
for (i = 0; i < 128; i++) {
int val;
if (__get_user(val, &data32->value.integer[i])) {
err = -EFAULT;
goto __end;
}
data->value.integer.value[i] = val;
}
break;
case SNDRV_CTL_ELEM_TYPE_INTEGER64:
case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
case SNDRV_CTL_ELEM_TYPE_BYTES:
case SNDRV_CTL_ELEM_TYPE_IEC958:
if (__copy_from_user(data->value.bytes.data,
data32->value.data,
sizeof(data32->value.data))) {
err = -EFAULT;
goto __end;
}
break;
default:
printk(KERN_ERR "snd_ioctl32_ctl_elem_value: unknown type %d\n", type);
err = -EINVAL;
goto __end;
}
if (native_ctl == SNDRV_CTL_IOCTL_ELEM_READ)
err = snd_ctl_elem_read(ctl->card, data);
else
err = snd_ctl_elem_write(ctl->card, ctl, data);
if (err < 0)
goto __end;
/* restore info to 32bit */
switch (type) {
case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
case SNDRV_CTL_ELEM_TYPE_INTEGER:
for (i = 0; i < 128; i++) {
int val;
val = data->value.integer.value[i];
if (__put_user(val, &data32->value.integer[i])) {
err = -EFAULT;
goto __end;
}
}
break;
default:
if (__copy_to_user(data32->value.data,
data->value.bytes.data,
sizeof(data32->value.data))) {
err = -EFAULT;
goto __end;
}
break;
break;
}
err = 0;
__end:
kfree(data);
return err;
}
DEFINE_ALSA_IOCTL_ENTRY(ctl_elem_read, ctl_elem_value, SNDRV_CTL_IOCTL_ELEM_READ);
DEFINE_ALSA_IOCTL_ENTRY(ctl_elem_write, ctl_elem_value, SNDRV_CTL_IOCTL_ELEM_WRITE);
/*
*/
#define AP(x) snd_ioctl32_##x
enum {
SNDRV_CTL_IOCTL_ELEM_LIST32 = _IOWR('U', 0x10, struct sndrv_ctl_elem_list32),
SNDRV_CTL_IOCTL_ELEM_INFO32 = _IOWR('U', 0x11, struct sndrv_ctl_elem_info32),
SNDRV_CTL_IOCTL_ELEM_READ32 = _IOWR('U', 0x12, struct sndrv_ctl_elem_value32),
SNDRV_CTL_IOCTL_ELEM_WRITE32 = _IOWR('U', 0x13, struct sndrv_ctl_elem_value32),
};
static struct ioctl32_mapper control_mappers[] = {
/* controls (without rawmidi, hwdep, timer releated ones) */
MAP_COMPAT(SNDRV_CTL_IOCTL_PVERSION),
MAP_COMPAT(SNDRV_CTL_IOCTL_CARD_INFO),
{ SNDRV_CTL_IOCTL_ELEM_LIST32, AP(ctl_elem_list) },
{ SNDRV_CTL_IOCTL_ELEM_INFO32, AP(ctl_elem_info) },
{ SNDRV_CTL_IOCTL_ELEM_READ32, AP(ctl_elem_read) },
{ SNDRV_CTL_IOCTL_ELEM_WRITE32, AP(ctl_elem_write) },
MAP_COMPAT(SNDRV_CTL_IOCTL_ELEM_LOCK),
MAP_COMPAT(SNDRV_CTL_IOCTL_ELEM_UNLOCK),
MAP_COMPAT(SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS),
MAP_COMPAT(SNDRV_CTL_IOCTL_HWDEP_INFO),
MAP_COMPAT(SNDRV_CTL_IOCTL_HWDEP_NEXT_DEVICE),
MAP_COMPAT(SNDRV_CTL_IOCTL_PCM_NEXT_DEVICE),
MAP_COMPAT(SNDRV_CTL_IOCTL_PCM_INFO),
MAP_COMPAT(SNDRV_CTL_IOCTL_PCM_PREFER_SUBDEVICE),
MAP_COMPAT(SNDRV_CTL_IOCTL_POWER),
MAP_COMPAT(SNDRV_CTL_IOCTL_POWER_STATE),
{ 0 }
};
/*
*/
extern struct ioctl32_mapper pcm_mappers[];
extern struct ioctl32_mapper rawmidi_mappers[];
extern struct ioctl32_mapper timer_mappers[];
extern struct ioctl32_mapper hwdep_mappers[];
#if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE))
extern struct ioctl32_mapper seq_mappers[];
#endif
static void snd_ioctl32_done(void)
{
#if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE))
snd_ioctl32_unregister(seq_mappers);
#endif
snd_ioctl32_unregister(hwdep_mappers);
snd_ioctl32_unregister(timer_mappers);
snd_ioctl32_unregister(rawmidi_mappers);
snd_ioctl32_unregister(pcm_mappers);
snd_ioctl32_unregister(control_mappers);
}
static int __init snd_ioctl32_init(void)
{
snd_ioctl32_register(control_mappers);
snd_ioctl32_register(pcm_mappers);
snd_ioctl32_register(rawmidi_mappers);
snd_ioctl32_register(timer_mappers);
snd_ioctl32_register(hwdep_mappers);
#if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE))
snd_ioctl32_register(seq_mappers);
#endif
return 0;
}
module_init(snd_ioctl32_init)
module_exit(snd_ioctl32_done)