root/sound/core/seq/oss/seq_oss_ioctl.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * OSS compatible sequencer driver
 *
 * OSS compatible i/o control
 *
 * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
 */

#include "seq_oss_device.h"
#include "seq_oss_readq.h"
#include "seq_oss_writeq.h"
#include "seq_oss_timer.h"
#include "seq_oss_synth.h"
#include "seq_oss_midi.h"
#include "seq_oss_event.h"

static int snd_seq_oss_synth_info_user(struct seq_oss_devinfo *dp, void __user *arg)
{
        struct synth_info info;

        if (copy_from_user(&info, arg, sizeof(info)))
                return -EFAULT;
        if (snd_seq_oss_synth_make_info(dp, info.device, &info) < 0)
                return -EINVAL;
        if (copy_to_user(arg, &info, sizeof(info)))
                return -EFAULT;
        return 0;
}

static int snd_seq_oss_midi_info_user(struct seq_oss_devinfo *dp, void __user *arg)
{
        struct midi_info info;

        if (copy_from_user(&info, arg, sizeof(info)))
                return -EFAULT;
        if (snd_seq_oss_midi_make_info(dp, info.device, &info) < 0)
                return -EINVAL;
        if (copy_to_user(arg, &info, sizeof(info)))
                return -EFAULT;
        return 0;
}

static int snd_seq_oss_oob_user(struct seq_oss_devinfo *dp, void __user *arg)
{
        unsigned char ev[8];
        struct snd_seq_event tmpev;

        if (copy_from_user(ev, arg, 8))
                return -EFAULT;
        memset(&tmpev, 0, sizeof(tmpev));
        snd_seq_oss_fill_addr(dp, &tmpev, dp->addr.client, dp->addr.port);
        tmpev.time.tick = 0;
        if (! snd_seq_oss_process_event(dp, (union evrec *)ev, &tmpev)) {
                snd_seq_oss_dispatch(dp, &tmpev, 0, 0);
        }
        return 0;
}

int
snd_seq_oss_ioctl(struct seq_oss_devinfo *dp, unsigned int cmd, unsigned long carg)
{
        int dev, val;
        void __user *arg = (void __user *)carg;
        int __user *p = arg;

        switch (cmd) {
        case SNDCTL_TMR_TIMEBASE:
        case SNDCTL_TMR_TEMPO:
        case SNDCTL_TMR_START:
        case SNDCTL_TMR_STOP:
        case SNDCTL_TMR_CONTINUE:
        case SNDCTL_TMR_METRONOME:
        case SNDCTL_TMR_SOURCE:
        case SNDCTL_TMR_SELECT:
        case SNDCTL_SEQ_CTRLRATE:
                return snd_seq_oss_timer_ioctl(dp->timer, cmd, arg);

        case SNDCTL_SEQ_PANIC:
                snd_seq_oss_reset(dp);
                return -EINVAL;

        case SNDCTL_SEQ_SYNC:
                if (! is_write_mode(dp->file_mode) || dp->writeq == NULL)
                        return 0;
                while (snd_seq_oss_writeq_sync(dp->writeq))
                        ;
                if (signal_pending(current))
                        return -ERESTARTSYS;
                return 0;

        case SNDCTL_SEQ_RESET:
                snd_seq_oss_reset(dp);
                return 0;

        case SNDCTL_SEQ_TESTMIDI:
                if (get_user(dev, p))
                        return -EFAULT;
                return snd_seq_oss_midi_open(dp, dev, dp->file_mode);

        case SNDCTL_SEQ_GETINCOUNT:
                if (dp->readq == NULL || ! is_read_mode(dp->file_mode))
                        return 0;
                return put_user(dp->readq->qlen, p) ? -EFAULT : 0;

        case SNDCTL_SEQ_GETOUTCOUNT:
                if (! is_write_mode(dp->file_mode) || dp->writeq == NULL)
                        return 0;
                return put_user(snd_seq_oss_writeq_get_free_size(dp->writeq), p) ? -EFAULT : 0;

        case SNDCTL_SEQ_GETTIME:
                return put_user(snd_seq_oss_timer_cur_tick(dp->timer), p) ? -EFAULT : 0;

        case SNDCTL_SEQ_RESETSAMPLES:
                if (get_user(dev, p))
                        return -EFAULT;
                return snd_seq_oss_synth_ioctl(dp, dev, cmd, carg);

        case SNDCTL_SEQ_NRSYNTHS:
                return put_user(dp->max_synthdev, p) ? -EFAULT : 0;

        case SNDCTL_SEQ_NRMIDIS:
                return put_user(dp->max_mididev, p) ? -EFAULT : 0;

        case SNDCTL_SYNTH_MEMAVL:
                if (get_user(dev, p))
                        return -EFAULT;
                val = snd_seq_oss_synth_ioctl(dp, dev, cmd, carg);
                return put_user(val, p) ? -EFAULT : 0;

        case SNDCTL_FM_4OP_ENABLE:
                if (get_user(dev, p))
                        return -EFAULT;
                snd_seq_oss_synth_ioctl(dp, dev, cmd, carg);
                return 0;

        case SNDCTL_SYNTH_INFO:
        case SNDCTL_SYNTH_ID:
                return snd_seq_oss_synth_info_user(dp, arg);

        case SNDCTL_SEQ_OUTOFBAND:
                return snd_seq_oss_oob_user(dp, arg);

        case SNDCTL_MIDI_INFO:
                return snd_seq_oss_midi_info_user(dp, arg);

        case SNDCTL_SEQ_THRESHOLD:
                if (! is_write_mode(dp->file_mode))
                        return 0;
                if (get_user(val, p))
                        return -EFAULT;
                if (val < 1)
                        val = 1;
                if (val >= dp->writeq->maxlen)
                        val = dp->writeq->maxlen - 1;
                snd_seq_oss_writeq_set_output(dp->writeq, val);
                return 0;

        case SNDCTL_MIDI_PRETIME:
                if (dp->readq == NULL || !is_read_mode(dp->file_mode))
                        return 0;
                if (get_user(val, p))
                        return -EFAULT;
                if (val <= 0)
                        val = -1;
                else
                        val = (HZ * val) / 10;
                dp->readq->pre_event_timeout = val;
                return put_user(val, p) ? -EFAULT : 0;

        default:
                if (! is_write_mode(dp->file_mode))
                        return -EIO;
                return snd_seq_oss_synth_ioctl(dp, 0, cmd, carg);
        }
        return 0;
}