/* * 32bit -> 64bit ioctl wrapper for control API * Copyright (c) by Takashi Iwai * * 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 #include #include #include #include #include #include #include #include #include #include #include "ioctl32.h" /* * register/unregister mappers * exported for other modules */ MODULE_AUTHOR("Takashi Iwai "); 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)