summaryrefslogtreecommitdiffstats
path: root/sound/core/seq/oss
diff options
context:
space:
mode:
Diffstat (limited to 'sound/core/seq/oss')
-rw-r--r--sound/core/seq/oss/Makefile11
-rw-r--r--sound/core/seq/oss/seq_oss.c311
-rw-r--r--sound/core/seq/oss/seq_oss_device.h168
-rw-r--r--sound/core/seq/oss/seq_oss_event.c447
-rw-r--r--sound/core/seq/oss/seq_oss_event.h99
-rw-r--r--sound/core/seq/oss/seq_oss_init.c505
-rw-r--r--sound/core/seq/oss/seq_oss_ioctl.c178
-rw-r--r--sound/core/seq/oss/seq_oss_midi.c718
-rw-r--r--sound/core/seq/oss/seq_oss_midi.h35
-rw-r--r--sound/core/seq/oss/seq_oss_readq.c250
-rw-r--r--sound/core/seq/oss/seq_oss_readq.h45
-rw-r--r--sound/core/seq/oss/seq_oss_rw.c201
-rw-r--r--sound/core/seq/oss/seq_oss_synth.c666
-rw-r--r--sound/core/seq/oss/seq_oss_synth.h39
-rw-r--r--sound/core/seq/oss/seq_oss_timer.c264
-rw-r--r--sound/core/seq/oss/seq_oss_timer.h47
-rw-r--r--sound/core/seq/oss/seq_oss_writeq.c160
-rw-r--r--sound/core/seq/oss/seq_oss_writeq.h37
18 files changed, 4181 insertions, 0 deletions
diff --git a/sound/core/seq/oss/Makefile b/sound/core/seq/oss/Makefile
new file mode 100644
index 000000000..f1a608785
--- /dev/null
+++ b/sound/core/seq/oss/Makefile
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for ALSA
+# Copyright (c) 1999 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-seq-oss-objs := seq_oss.o seq_oss_init.o seq_oss_timer.o seq_oss_ioctl.o \
+ seq_oss_event.o seq_oss_rw.o seq_oss_synth.o \
+ seq_oss_midi.o seq_oss_readq.o seq_oss_writeq.o
+
+obj-$(CONFIG_SND_SEQUENCER_OSS) += snd-seq-oss.o
diff --git a/sound/core/seq/oss/seq_oss.c b/sound/core/seq/oss/seq_oss.c
new file mode 100644
index 000000000..77c1214ac
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss.c
@@ -0,0 +1,311 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * OSS compatible sequencer driver
+ *
+ * registration of device and proc
+ *
+ * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/compat.h>
+#include <sound/core.h>
+#include <sound/minors.h>
+#include <sound/initval.h>
+#include "seq_oss_device.h"
+#include "seq_oss_synth.h"
+
+/*
+ * module option
+ */
+MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
+MODULE_DESCRIPTION("OSS-compatible sequencer module");
+MODULE_LICENSE("GPL");
+/* Takashi says this is really only for sound-service-0-, but this is OK. */
+MODULE_ALIAS_SNDRV_MINOR(SNDRV_MINOR_OSS_SEQUENCER);
+MODULE_ALIAS_SNDRV_MINOR(SNDRV_MINOR_OSS_MUSIC);
+
+
+/*
+ * prototypes
+ */
+static int register_device(void);
+static void unregister_device(void);
+#ifdef CONFIG_SND_PROC_FS
+static int register_proc(void);
+static void unregister_proc(void);
+#else
+static inline int register_proc(void) { return 0; }
+static inline void unregister_proc(void) {}
+#endif
+
+static int odev_open(struct inode *inode, struct file *file);
+static int odev_release(struct inode *inode, struct file *file);
+static ssize_t odev_read(struct file *file, char __user *buf, size_t count, loff_t *offset);
+static ssize_t odev_write(struct file *file, const char __user *buf, size_t count, loff_t *offset);
+static long odev_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
+static __poll_t odev_poll(struct file *file, poll_table * wait);
+
+
+/*
+ * module interface
+ */
+
+static struct snd_seq_driver seq_oss_synth_driver = {
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .probe = snd_seq_oss_synth_probe,
+ .remove = snd_seq_oss_synth_remove,
+ },
+ .id = SNDRV_SEQ_DEV_ID_OSS,
+ .argsize = sizeof(struct snd_seq_oss_reg),
+};
+
+static int __init alsa_seq_oss_init(void)
+{
+ int rc;
+
+ rc = register_device();
+ if (rc < 0)
+ goto error;
+ rc = register_proc();
+ if (rc < 0) {
+ unregister_device();
+ goto error;
+ }
+ rc = snd_seq_oss_create_client();
+ if (rc < 0) {
+ unregister_proc();
+ unregister_device();
+ goto error;
+ }
+
+ rc = snd_seq_driver_register(&seq_oss_synth_driver);
+ if (rc < 0) {
+ snd_seq_oss_delete_client();
+ unregister_proc();
+ unregister_device();
+ goto error;
+ }
+
+ /* success */
+ snd_seq_oss_synth_init();
+
+ error:
+ return rc;
+}
+
+static void __exit alsa_seq_oss_exit(void)
+{
+ snd_seq_driver_unregister(&seq_oss_synth_driver);
+ snd_seq_oss_delete_client();
+ unregister_proc();
+ unregister_device();
+}
+
+module_init(alsa_seq_oss_init)
+module_exit(alsa_seq_oss_exit)
+
+/*
+ * ALSA minor device interface
+ */
+
+static DEFINE_MUTEX(register_mutex);
+
+static int
+odev_open(struct inode *inode, struct file *file)
+{
+ int level, rc;
+
+ if (iminor(inode) == SNDRV_MINOR_OSS_MUSIC)
+ level = SNDRV_SEQ_OSS_MODE_MUSIC;
+ else
+ level = SNDRV_SEQ_OSS_MODE_SYNTH;
+
+ mutex_lock(&register_mutex);
+ rc = snd_seq_oss_open(file, level);
+ mutex_unlock(&register_mutex);
+
+ return rc;
+}
+
+static int
+odev_release(struct inode *inode, struct file *file)
+{
+ struct seq_oss_devinfo *dp;
+
+ dp = file->private_data;
+ if (!dp)
+ return 0;
+
+ mutex_lock(&register_mutex);
+ snd_seq_oss_release(dp);
+ mutex_unlock(&register_mutex);
+
+ return 0;
+}
+
+static ssize_t
+odev_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
+{
+ struct seq_oss_devinfo *dp;
+ dp = file->private_data;
+ if (snd_BUG_ON(!dp))
+ return -ENXIO;
+ return snd_seq_oss_read(dp, buf, count);
+}
+
+
+static ssize_t
+odev_write(struct file *file, const char __user *buf, size_t count, loff_t *offset)
+{
+ struct seq_oss_devinfo *dp;
+ dp = file->private_data;
+ if (snd_BUG_ON(!dp))
+ return -ENXIO;
+ return snd_seq_oss_write(dp, buf, count, file);
+}
+
+static long
+odev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct seq_oss_devinfo *dp;
+ long rc;
+
+ dp = file->private_data;
+ if (snd_BUG_ON(!dp))
+ return -ENXIO;
+
+ if (cmd != SNDCTL_SEQ_SYNC &&
+ mutex_lock_interruptible(&register_mutex))
+ return -ERESTARTSYS;
+ rc = snd_seq_oss_ioctl(dp, cmd, arg);
+ if (cmd != SNDCTL_SEQ_SYNC)
+ mutex_unlock(&register_mutex);
+ return rc;
+}
+
+#ifdef CONFIG_COMPAT
+static long odev_ioctl_compat(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ return odev_ioctl(file, cmd, (unsigned long)compat_ptr(arg));
+}
+#else
+#define odev_ioctl_compat NULL
+#endif
+
+static __poll_t
+odev_poll(struct file *file, poll_table * wait)
+{
+ struct seq_oss_devinfo *dp;
+ dp = file->private_data;
+ if (snd_BUG_ON(!dp))
+ return EPOLLERR;
+ return snd_seq_oss_poll(dp, file, wait);
+}
+
+/*
+ * registration of sequencer minor device
+ */
+
+static const struct file_operations seq_oss_f_ops =
+{
+ .owner = THIS_MODULE,
+ .read = odev_read,
+ .write = odev_write,
+ .open = odev_open,
+ .release = odev_release,
+ .poll = odev_poll,
+ .unlocked_ioctl = odev_ioctl,
+ .compat_ioctl = odev_ioctl_compat,
+ .llseek = noop_llseek,
+};
+
+static int __init
+register_device(void)
+{
+ int rc;
+
+ mutex_lock(&register_mutex);
+ rc = snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_SEQUENCER,
+ NULL, 0,
+ &seq_oss_f_ops, NULL);
+ if (rc < 0) {
+ pr_err("ALSA: seq_oss: can't register device seq\n");
+ mutex_unlock(&register_mutex);
+ return rc;
+ }
+ rc = snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_MUSIC,
+ NULL, 0,
+ &seq_oss_f_ops, NULL);
+ if (rc < 0) {
+ pr_err("ALSA: seq_oss: can't register device music\n");
+ snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_SEQUENCER, NULL, 0);
+ mutex_unlock(&register_mutex);
+ return rc;
+ }
+ mutex_unlock(&register_mutex);
+ return 0;
+}
+
+static void
+unregister_device(void)
+{
+ mutex_lock(&register_mutex);
+ if (snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_MUSIC, NULL, 0) < 0)
+ pr_err("ALSA: seq_oss: error unregister device music\n");
+ if (snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_SEQUENCER, NULL, 0) < 0)
+ pr_err("ALSA: seq_oss: error unregister device seq\n");
+ mutex_unlock(&register_mutex);
+}
+
+/*
+ * /proc interface
+ */
+
+#ifdef CONFIG_SND_PROC_FS
+
+static struct snd_info_entry *info_entry;
+
+static void
+info_read(struct snd_info_entry *entry, struct snd_info_buffer *buf)
+{
+ mutex_lock(&register_mutex);
+ snd_iprintf(buf, "OSS sequencer emulation version %s\n", SNDRV_SEQ_OSS_VERSION_STR);
+ snd_seq_oss_system_info_read(buf);
+ snd_seq_oss_synth_info_read(buf);
+ snd_seq_oss_midi_info_read(buf);
+ mutex_unlock(&register_mutex);
+}
+
+
+static int __init
+register_proc(void)
+{
+ struct snd_info_entry *entry;
+
+ entry = snd_info_create_module_entry(THIS_MODULE, SNDRV_SEQ_OSS_PROCNAME, snd_seq_root);
+ if (entry == NULL)
+ return -ENOMEM;
+
+ entry->content = SNDRV_INFO_CONTENT_TEXT;
+ entry->private_data = NULL;
+ entry->c.text.read = info_read;
+ if (snd_info_register(entry) < 0) {
+ snd_info_free_entry(entry);
+ return -ENOMEM;
+ }
+ info_entry = entry;
+ return 0;
+}
+
+static void
+unregister_proc(void)
+{
+ snd_info_free_entry(info_entry);
+ info_entry = NULL;
+}
+#endif /* CONFIG_SND_PROC_FS */
diff --git a/sound/core/seq/oss/seq_oss_device.h b/sound/core/seq/oss/seq_oss_device.h
new file mode 100644
index 000000000..6c2c4fb9b
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_device.h
@@ -0,0 +1,168 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * OSS compatible sequencer driver
+ *
+ * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
+ */
+
+#ifndef __SEQ_OSS_DEVICE_H
+#define __SEQ_OSS_DEVICE_H
+
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <linux/slab.h>
+#include <linux/sched/signal.h>
+#include <sound/core.h>
+#include <sound/seq_oss.h>
+#include <sound/rawmidi.h>
+#include <sound/seq_kernel.h>
+#include <sound/info.h>
+#include "../seq_clientmgr.h"
+
+/* max. applications */
+#define SNDRV_SEQ_OSS_MAX_CLIENTS 16
+#define SNDRV_SEQ_OSS_MAX_SYNTH_DEVS 16
+#define SNDRV_SEQ_OSS_MAX_MIDI_DEVS 32
+
+/* version */
+#define SNDRV_SEQ_OSS_MAJOR_VERSION 0
+#define SNDRV_SEQ_OSS_MINOR_VERSION 1
+#define SNDRV_SEQ_OSS_TINY_VERSION 8
+#define SNDRV_SEQ_OSS_VERSION_STR "0.1.8"
+
+/* device and proc interface name */
+#define SNDRV_SEQ_OSS_PROCNAME "oss"
+
+
+/*
+ * type definitions
+ */
+
+typedef unsigned int reltime_t;
+typedef unsigned int abstime_t;
+
+
+/*
+ * synthesizer channel information
+ */
+struct seq_oss_chinfo {
+ int note, vel;
+};
+
+/*
+ * synthesizer information
+ */
+struct seq_oss_synthinfo {
+ struct snd_seq_oss_arg arg;
+ struct seq_oss_chinfo *ch;
+ struct seq_oss_synth_sysex *sysex;
+ int nr_voices;
+ int opened;
+ int is_midi;
+ int midi_mapped;
+};
+
+
+/*
+ * sequencer client information
+ */
+
+struct seq_oss_devinfo {
+
+ int index; /* application index */
+ int cseq; /* sequencer client number */
+ int port; /* sequencer port number */
+ int queue; /* sequencer queue number */
+
+ struct snd_seq_addr addr; /* address of this device */
+
+ int seq_mode; /* sequencer mode */
+ int file_mode; /* file access */
+
+ /* midi device table */
+ int max_mididev;
+
+ /* synth device table */
+ int max_synthdev;
+ struct seq_oss_synthinfo synths[SNDRV_SEQ_OSS_MAX_SYNTH_DEVS];
+ int synth_opened;
+
+ /* output queue */
+ struct seq_oss_writeq *writeq;
+
+ /* midi input queue */
+ struct seq_oss_readq *readq;
+
+ /* timer */
+ struct seq_oss_timer *timer;
+};
+
+
+/*
+ * function prototypes
+ */
+
+/* create/delete OSS sequencer client */
+int snd_seq_oss_create_client(void);
+int snd_seq_oss_delete_client(void);
+
+/* device file interface */
+int snd_seq_oss_open(struct file *file, int level);
+void snd_seq_oss_release(struct seq_oss_devinfo *dp);
+int snd_seq_oss_ioctl(struct seq_oss_devinfo *dp, unsigned int cmd, unsigned long arg);
+int snd_seq_oss_read(struct seq_oss_devinfo *dev, char __user *buf, int count);
+int snd_seq_oss_write(struct seq_oss_devinfo *dp, const char __user *buf, int count, struct file *opt);
+__poll_t snd_seq_oss_poll(struct seq_oss_devinfo *dp, struct file *file, poll_table * wait);
+
+void snd_seq_oss_reset(struct seq_oss_devinfo *dp);
+
+/* */
+void snd_seq_oss_process_queue(struct seq_oss_devinfo *dp, abstime_t time);
+
+
+/* proc interface */
+void snd_seq_oss_system_info_read(struct snd_info_buffer *buf);
+void snd_seq_oss_midi_info_read(struct snd_info_buffer *buf);
+void snd_seq_oss_synth_info_read(struct snd_info_buffer *buf);
+void snd_seq_oss_readq_info_read(struct seq_oss_readq *q, struct snd_info_buffer *buf);
+
+/* file mode macros */
+#define is_read_mode(mode) ((mode) & SNDRV_SEQ_OSS_FILE_READ)
+#define is_write_mode(mode) ((mode) & SNDRV_SEQ_OSS_FILE_WRITE)
+#define is_nonblock_mode(mode) ((mode) & SNDRV_SEQ_OSS_FILE_NONBLOCK)
+
+/* dispatch event */
+static inline int
+snd_seq_oss_dispatch(struct seq_oss_devinfo *dp, struct snd_seq_event *ev, int atomic, int hop)
+{
+ return snd_seq_kernel_client_dispatch(dp->cseq, ev, atomic, hop);
+}
+
+/* ioctl for writeq */
+static inline int
+snd_seq_oss_control(struct seq_oss_devinfo *dp, unsigned int type, void *arg)
+{
+ int err;
+
+ snd_seq_client_ioctl_lock(dp->cseq);
+ err = snd_seq_kernel_client_ctl(dp->cseq, type, arg);
+ snd_seq_client_ioctl_unlock(dp->cseq);
+ return err;
+}
+
+/* fill the addresses in header */
+static inline void
+snd_seq_oss_fill_addr(struct seq_oss_devinfo *dp, struct snd_seq_event *ev,
+ int dest_client, int dest_port)
+{
+ ev->queue = dp->queue;
+ ev->source = dp->addr;
+ ev->dest.client = dest_client;
+ ev->dest.port = dest_port;
+}
+
+
+/* misc. functions for proc interface */
+char *enabled_str(int bool);
+
+#endif /* __SEQ_OSS_DEVICE_H */
diff --git a/sound/core/seq/oss/seq_oss_event.c b/sound/core/seq/oss/seq_oss_event.c
new file mode 100644
index 000000000..7b7c925dd
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_event.c
@@ -0,0 +1,447 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * OSS compatible sequencer driver
+ *
+ * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include "seq_oss_device.h"
+#include "seq_oss_synth.h"
+#include "seq_oss_midi.h"
+#include "seq_oss_event.h"
+#include "seq_oss_timer.h"
+#include <sound/seq_oss_legacy.h>
+#include "seq_oss_readq.h"
+#include "seq_oss_writeq.h"
+#include <linux/nospec.h>
+
+
+/*
+ * prototypes
+ */
+static int extended_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev);
+static int chn_voice_event(struct seq_oss_devinfo *dp, union evrec *event_rec, struct snd_seq_event *ev);
+static int chn_common_event(struct seq_oss_devinfo *dp, union evrec *event_rec, struct snd_seq_event *ev);
+static int timing_event(struct seq_oss_devinfo *dp, union evrec *event_rec, struct snd_seq_event *ev);
+static int local_event(struct seq_oss_devinfo *dp, union evrec *event_rec, struct snd_seq_event *ev);
+static int old_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev);
+static int note_on_event(struct seq_oss_devinfo *dp, int dev, int ch, int note, int vel, struct snd_seq_event *ev);
+static int note_off_event(struct seq_oss_devinfo *dp, int dev, int ch, int note, int vel, struct snd_seq_event *ev);
+static int set_note_event(struct seq_oss_devinfo *dp, int dev, int type, int ch, int note, int vel, struct snd_seq_event *ev);
+static int set_control_event(struct seq_oss_devinfo *dp, int dev, int type, int ch, int param, int val, struct snd_seq_event *ev);
+static int set_echo_event(struct seq_oss_devinfo *dp, union evrec *rec, struct snd_seq_event *ev);
+
+
+/*
+ * convert an OSS event to ALSA event
+ * return 0 : enqueued
+ * non-zero : invalid - ignored
+ */
+
+int
+snd_seq_oss_process_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev)
+{
+ switch (q->s.code) {
+ case SEQ_EXTENDED:
+ return extended_event(dp, q, ev);
+
+ case EV_CHN_VOICE:
+ return chn_voice_event(dp, q, ev);
+
+ case EV_CHN_COMMON:
+ return chn_common_event(dp, q, ev);
+
+ case EV_TIMING:
+ return timing_event(dp, q, ev);
+
+ case EV_SEQ_LOCAL:
+ return local_event(dp, q, ev);
+
+ case EV_SYSEX:
+ return snd_seq_oss_synth_sysex(dp, q->x.dev, q->x.buf, ev);
+
+ case SEQ_MIDIPUTC:
+ if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC)
+ return -EINVAL;
+ /* put a midi byte */
+ if (! is_write_mode(dp->file_mode))
+ break;
+ if (snd_seq_oss_midi_open(dp, q->s.dev, SNDRV_SEQ_OSS_FILE_WRITE))
+ break;
+ if (snd_seq_oss_midi_filemode(dp, q->s.dev) & SNDRV_SEQ_OSS_FILE_WRITE)
+ return snd_seq_oss_midi_putc(dp, q->s.dev, q->s.parm1, ev);
+ break;
+
+ case SEQ_ECHO:
+ if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC)
+ return -EINVAL;
+ return set_echo_event(dp, q, ev);
+
+ case SEQ_PRIVATE:
+ if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC)
+ return -EINVAL;
+ return snd_seq_oss_synth_raw_event(dp, q->c[1], q->c, ev);
+
+ default:
+ if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC)
+ return -EINVAL;
+ return old_event(dp, q, ev);
+ }
+ return -EINVAL;
+}
+
+/* old type events: mode1 only */
+static int
+old_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev)
+{
+ switch (q->s.code) {
+ case SEQ_NOTEOFF:
+ return note_off_event(dp, 0, q->n.chn, q->n.note, q->n.vel, ev);
+
+ case SEQ_NOTEON:
+ return note_on_event(dp, 0, q->n.chn, q->n.note, q->n.vel, ev);
+
+ case SEQ_WAIT:
+ /* skip */
+ break;
+
+ case SEQ_PGMCHANGE:
+ return set_control_event(dp, 0, SNDRV_SEQ_EVENT_PGMCHANGE,
+ q->n.chn, 0, q->n.note, ev);
+
+ case SEQ_SYNCTIMER:
+ return snd_seq_oss_timer_reset(dp->timer);
+ }
+
+ return -EINVAL;
+}
+
+/* 8bytes extended event: mode1 only */
+static int
+extended_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev)
+{
+ int val;
+
+ switch (q->e.cmd) {
+ case SEQ_NOTEOFF:
+ return note_off_event(dp, q->e.dev, q->e.chn, q->e.p1, q->e.p2, ev);
+
+ case SEQ_NOTEON:
+ return note_on_event(dp, q->e.dev, q->e.chn, q->e.p1, q->e.p2, ev);
+
+ case SEQ_PGMCHANGE:
+ return set_control_event(dp, q->e.dev, SNDRV_SEQ_EVENT_PGMCHANGE,
+ q->e.chn, 0, q->e.p1, ev);
+
+ case SEQ_AFTERTOUCH:
+ return set_control_event(dp, q->e.dev, SNDRV_SEQ_EVENT_CHANPRESS,
+ q->e.chn, 0, q->e.p1, ev);
+
+ case SEQ_BALANCE:
+ /* convert -128:127 to 0:127 */
+ val = (char)q->e.p1;
+ val = (val + 128) / 2;
+ return set_control_event(dp, q->e.dev, SNDRV_SEQ_EVENT_CONTROLLER,
+ q->e.chn, CTL_PAN, val, ev);
+
+ case SEQ_CONTROLLER:
+ val = ((short)q->e.p3 << 8) | (short)q->e.p2;
+ switch (q->e.p1) {
+ case CTRL_PITCH_BENDER: /* SEQ1 V2 control */
+ /* -0x2000:0x1fff */
+ return set_control_event(dp, q->e.dev,
+ SNDRV_SEQ_EVENT_PITCHBEND,
+ q->e.chn, 0, val, ev);
+ case CTRL_PITCH_BENDER_RANGE:
+ /* conversion: 100/semitone -> 128/semitone */
+ return set_control_event(dp, q->e.dev,
+ SNDRV_SEQ_EVENT_REGPARAM,
+ q->e.chn, 0, val*128/100, ev);
+ default:
+ return set_control_event(dp, q->e.dev,
+ SNDRV_SEQ_EVENT_CONTROL14,
+ q->e.chn, q->e.p1, val, ev);
+ }
+
+ case SEQ_VOLMODE:
+ return snd_seq_oss_synth_raw_event(dp, q->e.dev, q->c, ev);
+
+ }
+ return -EINVAL;
+}
+
+/* channel voice events: mode1 and 2 */
+static int
+chn_voice_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev)
+{
+ if (q->v.chn >= 32)
+ return -EINVAL;
+ switch (q->v.cmd) {
+ case MIDI_NOTEON:
+ return note_on_event(dp, q->v.dev, q->v.chn, q->v.note, q->v.parm, ev);
+
+ case MIDI_NOTEOFF:
+ return note_off_event(dp, q->v.dev, q->v.chn, q->v.note, q->v.parm, ev);
+
+ case MIDI_KEY_PRESSURE:
+ return set_note_event(dp, q->v.dev, SNDRV_SEQ_EVENT_KEYPRESS,
+ q->v.chn, q->v.note, q->v.parm, ev);
+
+ }
+ return -EINVAL;
+}
+
+/* channel common events: mode1 and 2 */
+static int
+chn_common_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev)
+{
+ if (q->l.chn >= 32)
+ return -EINVAL;
+ switch (q->l.cmd) {
+ case MIDI_PGM_CHANGE:
+ return set_control_event(dp, q->l.dev, SNDRV_SEQ_EVENT_PGMCHANGE,
+ q->l.chn, 0, q->l.p1, ev);
+
+ case MIDI_CTL_CHANGE:
+ return set_control_event(dp, q->l.dev, SNDRV_SEQ_EVENT_CONTROLLER,
+ q->l.chn, q->l.p1, q->l.val, ev);
+
+ case MIDI_PITCH_BEND:
+ /* conversion: 0:0x3fff -> -0x2000:0x1fff */
+ return set_control_event(dp, q->l.dev, SNDRV_SEQ_EVENT_PITCHBEND,
+ q->l.chn, 0, q->l.val - 8192, ev);
+
+ case MIDI_CHN_PRESSURE:
+ return set_control_event(dp, q->l.dev, SNDRV_SEQ_EVENT_CHANPRESS,
+ q->l.chn, 0, q->l.val, ev);
+ }
+ return -EINVAL;
+}
+
+/* timer events: mode1 and mode2 */
+static int
+timing_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev)
+{
+ switch (q->t.cmd) {
+ case TMR_ECHO:
+ if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC)
+ return set_echo_event(dp, q, ev);
+ else {
+ union evrec tmp;
+ memset(&tmp, 0, sizeof(tmp));
+ /* XXX: only for little-endian! */
+ tmp.echo = (q->t.time << 8) | SEQ_ECHO;
+ return set_echo_event(dp, &tmp, ev);
+ }
+
+ case TMR_STOP:
+ if (dp->seq_mode)
+ return snd_seq_oss_timer_stop(dp->timer);
+ return 0;
+
+ case TMR_CONTINUE:
+ if (dp->seq_mode)
+ return snd_seq_oss_timer_continue(dp->timer);
+ return 0;
+
+ case TMR_TEMPO:
+ if (dp->seq_mode)
+ return snd_seq_oss_timer_tempo(dp->timer, q->t.time);
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+/* local events: mode1 and 2 */
+static int
+local_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev)
+{
+ return -EINVAL;
+}
+
+/*
+ * process note-on event for OSS synth
+ * three different modes are available:
+ * - SNDRV_SEQ_OSS_PROCESS_EVENTS (for one-voice per channel mode)
+ * Accept note 255 as volume change.
+ * - SNDRV_SEQ_OSS_PASS_EVENTS
+ * Pass all events to lowlevel driver anyway
+ * - SNDRV_SEQ_OSS_PROCESS_KEYPRESS (mostly for Emu8000)
+ * Use key-pressure if note >= 128
+ */
+static int
+note_on_event(struct seq_oss_devinfo *dp, int dev, int ch, int note, int vel, struct snd_seq_event *ev)
+{
+ struct seq_oss_synthinfo *info;
+
+ info = snd_seq_oss_synth_info(dp, dev);
+ if (!info)
+ return -ENXIO;
+
+ switch (info->arg.event_passing) {
+ case SNDRV_SEQ_OSS_PROCESS_EVENTS:
+ if (! info->ch || ch < 0 || ch >= info->nr_voices) {
+ /* pass directly */
+ return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev);
+ }
+
+ ch = array_index_nospec(ch, info->nr_voices);
+ if (note == 255 && info->ch[ch].note >= 0) {
+ /* volume control */
+ int type;
+ //if (! vel)
+ /* set volume to zero -- note off */
+ // type = SNDRV_SEQ_EVENT_NOTEOFF;
+ //else
+ if (info->ch[ch].vel)
+ /* sample already started -- volume change */
+ type = SNDRV_SEQ_EVENT_KEYPRESS;
+ else
+ /* sample not started -- start now */
+ type = SNDRV_SEQ_EVENT_NOTEON;
+ info->ch[ch].vel = vel;
+ return set_note_event(dp, dev, type, ch, info->ch[ch].note, vel, ev);
+ } else if (note >= 128)
+ return -EINVAL; /* invalid */
+
+ if (note != info->ch[ch].note && info->ch[ch].note >= 0)
+ /* note changed - note off at beginning */
+ set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEOFF, ch, info->ch[ch].note, 0, ev);
+ /* set current status */
+ info->ch[ch].note = note;
+ info->ch[ch].vel = vel;
+ if (vel) /* non-zero velocity - start the note now */
+ return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev);
+ return -EINVAL;
+
+ case SNDRV_SEQ_OSS_PASS_EVENTS:
+ /* pass the event anyway */
+ return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev);
+
+ case SNDRV_SEQ_OSS_PROCESS_KEYPRESS:
+ if (note >= 128) /* key pressure: shifted by 128 */
+ return set_note_event(dp, dev, SNDRV_SEQ_EVENT_KEYPRESS, ch, note - 128, vel, ev);
+ else /* normal note-on event */
+ return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev);
+ }
+ return -EINVAL;
+}
+
+/*
+ * process note-off event for OSS synth
+ */
+static int
+note_off_event(struct seq_oss_devinfo *dp, int dev, int ch, int note, int vel, struct snd_seq_event *ev)
+{
+ struct seq_oss_synthinfo *info;
+
+ info = snd_seq_oss_synth_info(dp, dev);
+ if (!info)
+ return -ENXIO;
+
+ switch (info->arg.event_passing) {
+ case SNDRV_SEQ_OSS_PROCESS_EVENTS:
+ if (! info->ch || ch < 0 || ch >= info->nr_voices) {
+ /* pass directly */
+ return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev);
+ }
+
+ ch = array_index_nospec(ch, info->nr_voices);
+ if (info->ch[ch].note >= 0) {
+ note = info->ch[ch].note;
+ info->ch[ch].vel = 0;
+ info->ch[ch].note = -1;
+ return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEOFF, ch, note, vel, ev);
+ }
+ return -EINVAL; /* invalid */
+
+ case SNDRV_SEQ_OSS_PASS_EVENTS:
+ case SNDRV_SEQ_OSS_PROCESS_KEYPRESS:
+ /* pass the event anyway */
+ return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEOFF, ch, note, vel, ev);
+
+ }
+ return -EINVAL;
+}
+
+/*
+ * create a note event
+ */
+static int
+set_note_event(struct seq_oss_devinfo *dp, int dev, int type, int ch, int note, int vel, struct snd_seq_event *ev)
+{
+ if (!snd_seq_oss_synth_info(dp, dev))
+ return -ENXIO;
+
+ ev->type = type;
+ snd_seq_oss_synth_addr(dp, dev, ev);
+ ev->data.note.channel = ch;
+ ev->data.note.note = note;
+ ev->data.note.velocity = vel;
+
+ return 0;
+}
+
+/*
+ * create a control event
+ */
+static int
+set_control_event(struct seq_oss_devinfo *dp, int dev, int type, int ch, int param, int val, struct snd_seq_event *ev)
+{
+ if (!snd_seq_oss_synth_info(dp, dev))
+ return -ENXIO;
+
+ ev->type = type;
+ snd_seq_oss_synth_addr(dp, dev, ev);
+ ev->data.control.channel = ch;
+ ev->data.control.param = param;
+ ev->data.control.value = val;
+
+ return 0;
+}
+
+/*
+ * create an echo event
+ */
+static int
+set_echo_event(struct seq_oss_devinfo *dp, union evrec *rec, struct snd_seq_event *ev)
+{
+ ev->type = SNDRV_SEQ_EVENT_ECHO;
+ /* echo back to itself */
+ snd_seq_oss_fill_addr(dp, ev, dp->addr.client, dp->addr.port);
+ memcpy(&ev->data, rec, LONG_EVENT_SIZE);
+ return 0;
+}
+
+/*
+ * event input callback from ALSA sequencer:
+ * the echo event is processed here.
+ */
+int
+snd_seq_oss_event_input(struct snd_seq_event *ev, int direct, void *private_data,
+ int atomic, int hop)
+{
+ struct seq_oss_devinfo *dp = (struct seq_oss_devinfo *)private_data;
+ union evrec *rec;
+
+ if (ev->type != SNDRV_SEQ_EVENT_ECHO)
+ return snd_seq_oss_midi_input(ev, direct, private_data);
+
+ if (ev->source.client != dp->cseq)
+ return 0; /* ignored */
+
+ rec = (union evrec*)&ev->data;
+ if (rec->s.code == SEQ_SYNCTIMER) {
+ /* sync echo back */
+ snd_seq_oss_writeq_wakeup(dp->writeq, rec->t.time);
+
+ } else {
+ /* echo back event */
+ if (dp->readq == NULL)
+ return 0;
+ snd_seq_oss_readq_put_event(dp->readq, rec);
+ }
+ return 0;
+}
+
diff --git a/sound/core/seq/oss/seq_oss_event.h b/sound/core/seq/oss/seq_oss_event.h
new file mode 100644
index 000000000..b4f723949
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_event.h
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * OSS compatible sequencer driver
+ *
+ * seq_oss_event.h - OSS event queue record
+ *
+ * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
+ */
+
+#ifndef __SEQ_OSS_EVENT_H
+#define __SEQ_OSS_EVENT_H
+
+#include "seq_oss_device.h"
+
+#define SHORT_EVENT_SIZE 4
+#define LONG_EVENT_SIZE 8
+
+/* short event (4bytes) */
+struct evrec_short {
+ unsigned char code;
+ unsigned char parm1;
+ unsigned char dev;
+ unsigned char parm2;
+};
+
+/* short note events (4bytes) */
+struct evrec_note {
+ unsigned char code;
+ unsigned char chn;
+ unsigned char note;
+ unsigned char vel;
+};
+
+/* long timer events (8bytes) */
+struct evrec_timer {
+ unsigned char code;
+ unsigned char cmd;
+ unsigned char dummy1, dummy2;
+ unsigned int time;
+};
+
+/* long extended events (8bytes) */
+struct evrec_extended {
+ unsigned char code;
+ unsigned char cmd;
+ unsigned char dev;
+ unsigned char chn;
+ unsigned char p1, p2, p3, p4;
+};
+
+/* long channel events (8bytes) */
+struct evrec_long {
+ unsigned char code;
+ unsigned char dev;
+ unsigned char cmd;
+ unsigned char chn;
+ unsigned char p1, p2;
+ unsigned short val;
+};
+
+/* channel voice events (8bytes) */
+struct evrec_voice {
+ unsigned char code;
+ unsigned char dev;
+ unsigned char cmd;
+ unsigned char chn;
+ unsigned char note, parm;
+ unsigned short dummy;
+};
+
+/* sysex events (8bytes) */
+struct evrec_sysex {
+ unsigned char code;
+ unsigned char dev;
+ unsigned char buf[6];
+};
+
+/* event record */
+union evrec {
+ struct evrec_short s;
+ struct evrec_note n;
+ struct evrec_long l;
+ struct evrec_voice v;
+ struct evrec_timer t;
+ struct evrec_extended e;
+ struct evrec_sysex x;
+ unsigned int echo;
+ unsigned char c[LONG_EVENT_SIZE];
+};
+
+#define ev_is_long(ev) ((ev)->s.code >= 128)
+#define ev_length(ev) ((ev)->s.code >= 128 ? LONG_EVENT_SIZE : SHORT_EVENT_SIZE)
+
+int snd_seq_oss_process_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev);
+int snd_seq_oss_process_timer_event(struct seq_oss_timer *rec, union evrec *q);
+int snd_seq_oss_event_input(struct snd_seq_event *ev, int direct, void *private_data, int atomic, int hop);
+
+
+#endif /* __SEQ_OSS_EVENT_H */
diff --git a/sound/core/seq/oss/seq_oss_init.c b/sound/core/seq/oss/seq_oss_init.c
new file mode 100644
index 000000000..42d4e7535
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_init.c
@@ -0,0 +1,505 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * OSS compatible sequencer driver
+ *
+ * open/close and reset interface
+ *
+ * Copyright (C) 1998-1999 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include "seq_oss_device.h"
+#include "seq_oss_synth.h"
+#include "seq_oss_midi.h"
+#include "seq_oss_writeq.h"
+#include "seq_oss_readq.h"
+#include "seq_oss_timer.h"
+#include "seq_oss_event.h"
+#include <linux/init.h>
+#include <linux/export.h>
+#include <linux/moduleparam.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+/*
+ * common variables
+ */
+static int maxqlen = SNDRV_SEQ_OSS_MAX_QLEN;
+module_param(maxqlen, int, 0444);
+MODULE_PARM_DESC(maxqlen, "maximum queue length");
+
+static int system_client = -1; /* ALSA sequencer client number */
+static int system_port = -1;
+
+static int num_clients;
+static struct seq_oss_devinfo *client_table[SNDRV_SEQ_OSS_MAX_CLIENTS];
+
+
+/*
+ * prototypes
+ */
+static int receive_announce(struct snd_seq_event *ev, int direct, void *private, int atomic, int hop);
+static int translate_mode(struct file *file);
+static int create_port(struct seq_oss_devinfo *dp);
+static int delete_port(struct seq_oss_devinfo *dp);
+static int alloc_seq_queue(struct seq_oss_devinfo *dp);
+static int delete_seq_queue(int queue);
+static void free_devinfo(void *private);
+
+#define call_ctl(type,rec) snd_seq_kernel_client_ctl(system_client, type, rec)
+
+
+/* call snd_seq_oss_midi_lookup_ports() asynchronously */
+static void async_call_lookup_ports(struct work_struct *work)
+{
+ snd_seq_oss_midi_lookup_ports(system_client);
+}
+
+static DECLARE_WORK(async_lookup_work, async_call_lookup_ports);
+
+/*
+ * create sequencer client for OSS sequencer
+ */
+int __init
+snd_seq_oss_create_client(void)
+{
+ int rc;
+ struct snd_seq_port_info *port;
+ struct snd_seq_port_callback port_callback;
+
+ port = kzalloc(sizeof(*port), GFP_KERNEL);
+ if (!port) {
+ rc = -ENOMEM;
+ goto __error;
+ }
+
+ /* create ALSA client */
+ rc = snd_seq_create_kernel_client(NULL, SNDRV_SEQ_CLIENT_OSS,
+ "OSS sequencer");
+ if (rc < 0)
+ goto __error;
+
+ system_client = rc;
+
+ /* create announcement receiver port */
+ strcpy(port->name, "Receiver");
+ port->addr.client = system_client;
+ port->capability = SNDRV_SEQ_PORT_CAP_WRITE; /* receive only */
+ port->type = 0;
+
+ memset(&port_callback, 0, sizeof(port_callback));
+ /* don't set port_callback.owner here. otherwise the module counter
+ * is incremented and we can no longer release the module..
+ */
+ port_callback.event_input = receive_announce;
+ port->kernel = &port_callback;
+
+ if (call_ctl(SNDRV_SEQ_IOCTL_CREATE_PORT, port) >= 0) {
+ struct snd_seq_port_subscribe subs;
+
+ system_port = port->addr.port;
+ memset(&subs, 0, sizeof(subs));
+ subs.sender.client = SNDRV_SEQ_CLIENT_SYSTEM;
+ subs.sender.port = SNDRV_SEQ_PORT_SYSTEM_ANNOUNCE;
+ subs.dest.client = system_client;
+ subs.dest.port = system_port;
+ call_ctl(SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, &subs);
+ }
+ rc = 0;
+
+ /* look up midi devices */
+ schedule_work(&async_lookup_work);
+
+ __error:
+ kfree(port);
+ return rc;
+}
+
+
+/*
+ * receive annoucement from system port, and check the midi device
+ */
+static int
+receive_announce(struct snd_seq_event *ev, int direct, void *private, int atomic, int hop)
+{
+ struct snd_seq_port_info pinfo;
+
+ if (atomic)
+ return 0; /* it must not happen */
+
+ switch (ev->type) {
+ case SNDRV_SEQ_EVENT_PORT_START:
+ case SNDRV_SEQ_EVENT_PORT_CHANGE:
+ if (ev->data.addr.client == system_client)
+ break; /* ignore myself */
+ memset(&pinfo, 0, sizeof(pinfo));
+ pinfo.addr = ev->data.addr;
+ if (call_ctl(SNDRV_SEQ_IOCTL_GET_PORT_INFO, &pinfo) >= 0)
+ snd_seq_oss_midi_check_new_port(&pinfo);
+ break;
+
+ case SNDRV_SEQ_EVENT_PORT_EXIT:
+ if (ev->data.addr.client == system_client)
+ break; /* ignore myself */
+ snd_seq_oss_midi_check_exit_port(ev->data.addr.client,
+ ev->data.addr.port);
+ break;
+ }
+ return 0;
+}
+
+
+/*
+ * delete OSS sequencer client
+ */
+int
+snd_seq_oss_delete_client(void)
+{
+ cancel_work_sync(&async_lookup_work);
+ if (system_client >= 0)
+ snd_seq_delete_kernel_client(system_client);
+
+ snd_seq_oss_midi_clear_all();
+
+ return 0;
+}
+
+
+/*
+ * open sequencer device
+ */
+int
+snd_seq_oss_open(struct file *file, int level)
+{
+ int i, rc;
+ struct seq_oss_devinfo *dp;
+
+ dp = kzalloc(sizeof(*dp), GFP_KERNEL);
+ if (!dp)
+ return -ENOMEM;
+
+ dp->cseq = system_client;
+ dp->port = -1;
+ dp->queue = -1;
+
+ for (i = 0; i < SNDRV_SEQ_OSS_MAX_CLIENTS; i++) {
+ if (client_table[i] == NULL)
+ break;
+ }
+
+ dp->index = i;
+ if (i >= SNDRV_SEQ_OSS_MAX_CLIENTS) {
+ pr_debug("ALSA: seq_oss: too many applications\n");
+ rc = -ENOMEM;
+ goto _error;
+ }
+
+ /* look up synth and midi devices */
+ snd_seq_oss_synth_setup(dp);
+ snd_seq_oss_midi_setup(dp);
+
+ if (dp->synth_opened == 0 && dp->max_mididev == 0) {
+ /* pr_err("ALSA: seq_oss: no device found\n"); */
+ rc = -ENODEV;
+ goto _error;
+ }
+
+ /* create port */
+ rc = create_port(dp);
+ if (rc < 0) {
+ pr_err("ALSA: seq_oss: can't create port\n");
+ goto _error;
+ }
+
+ /* allocate queue */
+ rc = alloc_seq_queue(dp);
+ if (rc < 0)
+ goto _error;
+
+ /* set address */
+ dp->addr.client = dp->cseq;
+ dp->addr.port = dp->port;
+ /*dp->addr.queue = dp->queue;*/
+ /*dp->addr.channel = 0;*/
+
+ dp->seq_mode = level;
+
+ /* set up file mode */
+ dp->file_mode = translate_mode(file);
+
+ /* initialize read queue */
+ if (is_read_mode(dp->file_mode)) {
+ dp->readq = snd_seq_oss_readq_new(dp, maxqlen);
+ if (!dp->readq) {
+ rc = -ENOMEM;
+ goto _error;
+ }
+ }
+
+ /* initialize write queue */
+ if (is_write_mode(dp->file_mode)) {
+ dp->writeq = snd_seq_oss_writeq_new(dp, maxqlen);
+ if (!dp->writeq) {
+ rc = -ENOMEM;
+ goto _error;
+ }
+ }
+
+ /* initialize timer */
+ dp->timer = snd_seq_oss_timer_new(dp);
+ if (!dp->timer) {
+ pr_err("ALSA: seq_oss: can't alloc timer\n");
+ rc = -ENOMEM;
+ goto _error;
+ }
+
+ /* set private data pointer */
+ file->private_data = dp;
+
+ /* set up for mode2 */
+ if (level == SNDRV_SEQ_OSS_MODE_MUSIC)
+ snd_seq_oss_synth_setup_midi(dp);
+ else if (is_read_mode(dp->file_mode))
+ snd_seq_oss_midi_open_all(dp, SNDRV_SEQ_OSS_FILE_READ);
+
+ client_table[dp->index] = dp;
+ num_clients++;
+
+ return 0;
+
+ _error:
+ snd_seq_oss_synth_cleanup(dp);
+ snd_seq_oss_midi_cleanup(dp);
+ delete_seq_queue(dp->queue);
+ delete_port(dp);
+
+ return rc;
+}
+
+/*
+ * translate file flags to private mode
+ */
+static int
+translate_mode(struct file *file)
+{
+ int file_mode = 0;
+ if ((file->f_flags & O_ACCMODE) != O_RDONLY)
+ file_mode |= SNDRV_SEQ_OSS_FILE_WRITE;
+ if ((file->f_flags & O_ACCMODE) != O_WRONLY)
+ file_mode |= SNDRV_SEQ_OSS_FILE_READ;
+ if (file->f_flags & O_NONBLOCK)
+ file_mode |= SNDRV_SEQ_OSS_FILE_NONBLOCK;
+ return file_mode;
+}
+
+
+/*
+ * create sequencer port
+ */
+static int
+create_port(struct seq_oss_devinfo *dp)
+{
+ int rc;
+ struct snd_seq_port_info port;
+ struct snd_seq_port_callback callback;
+
+ memset(&port, 0, sizeof(port));
+ port.addr.client = dp->cseq;
+ sprintf(port.name, "Sequencer-%d", dp->index);
+ port.capability = SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_WRITE; /* no subscription */
+ port.type = SNDRV_SEQ_PORT_TYPE_SPECIFIC;
+ port.midi_channels = 128;
+ port.synth_voices = 128;
+
+ memset(&callback, 0, sizeof(callback));
+ callback.owner = THIS_MODULE;
+ callback.private_data = dp;
+ callback.event_input = snd_seq_oss_event_input;
+ callback.private_free = free_devinfo;
+ port.kernel = &callback;
+
+ rc = call_ctl(SNDRV_SEQ_IOCTL_CREATE_PORT, &port);
+ if (rc < 0)
+ return rc;
+
+ dp->port = port.addr.port;
+
+ return 0;
+}
+
+/*
+ * delete ALSA port
+ */
+static int
+delete_port(struct seq_oss_devinfo *dp)
+{
+ if (dp->port < 0) {
+ kfree(dp);
+ return 0;
+ }
+
+ return snd_seq_event_port_detach(dp->cseq, dp->port);
+}
+
+/*
+ * allocate a queue
+ */
+static int
+alloc_seq_queue(struct seq_oss_devinfo *dp)
+{
+ struct snd_seq_queue_info qinfo;
+ int rc;
+
+ memset(&qinfo, 0, sizeof(qinfo));
+ qinfo.owner = system_client;
+ qinfo.locked = 1;
+ strcpy(qinfo.name, "OSS Sequencer Emulation");
+ rc = call_ctl(SNDRV_SEQ_IOCTL_CREATE_QUEUE, &qinfo);
+ if (rc < 0)
+ return rc;
+ dp->queue = qinfo.queue;
+ return 0;
+}
+
+/*
+ * release queue
+ */
+static int
+delete_seq_queue(int queue)
+{
+ struct snd_seq_queue_info qinfo;
+ int rc;
+
+ if (queue < 0)
+ return 0;
+ memset(&qinfo, 0, sizeof(qinfo));
+ qinfo.queue = queue;
+ rc = call_ctl(SNDRV_SEQ_IOCTL_DELETE_QUEUE, &qinfo);
+ if (rc < 0)
+ pr_err("ALSA: seq_oss: unable to delete queue %d (%d)\n", queue, rc);
+ return rc;
+}
+
+
+/*
+ * free device informations - private_free callback of port
+ */
+static void
+free_devinfo(void *private)
+{
+ struct seq_oss_devinfo *dp = (struct seq_oss_devinfo *)private;
+
+ snd_seq_oss_timer_delete(dp->timer);
+
+ snd_seq_oss_writeq_delete(dp->writeq);
+
+ snd_seq_oss_readq_delete(dp->readq);
+
+ kfree(dp);
+}
+
+
+/*
+ * close sequencer device
+ */
+void
+snd_seq_oss_release(struct seq_oss_devinfo *dp)
+{
+ int queue;
+
+ client_table[dp->index] = NULL;
+ num_clients--;
+
+ snd_seq_oss_reset(dp);
+
+ snd_seq_oss_synth_cleanup(dp);
+ snd_seq_oss_midi_cleanup(dp);
+
+ /* clear slot */
+ queue = dp->queue;
+ if (dp->port >= 0)
+ delete_port(dp);
+ delete_seq_queue(queue);
+}
+
+
+/*
+ * reset sequencer devices
+ */
+void
+snd_seq_oss_reset(struct seq_oss_devinfo *dp)
+{
+ int i;
+
+ /* reset all synth devices */
+ for (i = 0; i < dp->max_synthdev; i++)
+ snd_seq_oss_synth_reset(dp, i);
+
+ /* reset all midi devices */
+ if (dp->seq_mode != SNDRV_SEQ_OSS_MODE_MUSIC) {
+ for (i = 0; i < dp->max_mididev; i++)
+ snd_seq_oss_midi_reset(dp, i);
+ }
+
+ /* remove queues */
+ if (dp->readq)
+ snd_seq_oss_readq_clear(dp->readq);
+ if (dp->writeq)
+ snd_seq_oss_writeq_clear(dp->writeq);
+
+ /* reset timer */
+ snd_seq_oss_timer_stop(dp->timer);
+}
+
+#ifdef CONFIG_SND_PROC_FS
+/*
+ * misc. functions for proc interface
+ */
+char *
+enabled_str(int bool)
+{
+ return bool ? "enabled" : "disabled";
+}
+
+static const char *
+filemode_str(int val)
+{
+ static const char * const str[] = {
+ "none", "read", "write", "read/write",
+ };
+ return str[val & SNDRV_SEQ_OSS_FILE_ACMODE];
+}
+
+
+/*
+ * proc interface
+ */
+void
+snd_seq_oss_system_info_read(struct snd_info_buffer *buf)
+{
+ int i;
+ struct seq_oss_devinfo *dp;
+
+ snd_iprintf(buf, "ALSA client number %d\n", system_client);
+ snd_iprintf(buf, "ALSA receiver port %d\n", system_port);
+
+ snd_iprintf(buf, "\nNumber of applications: %d\n", num_clients);
+ for (i = 0; i < num_clients; i++) {
+ snd_iprintf(buf, "\nApplication %d: ", i);
+ dp = client_table[i];
+ if (!dp) {
+ snd_iprintf(buf, "*empty*\n");
+ continue;
+ }
+ snd_iprintf(buf, "port %d : queue %d\n", dp->port, dp->queue);
+ snd_iprintf(buf, " sequencer mode = %s : file open mode = %s\n",
+ (dp->seq_mode ? "music" : "synth"),
+ filemode_str(dp->file_mode));
+ if (dp->seq_mode)
+ snd_iprintf(buf, " timer tempo = %d, timebase = %d\n",
+ dp->timer->oss_tempo, dp->timer->oss_timebase);
+ snd_iprintf(buf, " max queue length %d\n", maxqlen);
+ if (is_read_mode(dp->file_mode) && dp->readq)
+ snd_seq_oss_readq_info_read(dp->readq, buf);
+ }
+}
+#endif /* CONFIG_SND_PROC_FS */
diff --git a/sound/core/seq/oss/seq_oss_ioctl.c b/sound/core/seq/oss/seq_oss_ioctl.c
new file mode 100644
index 000000000..ccf682689
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_ioctl.c
@@ -0,0 +1,178 @@
+// 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;
+}
+
diff --git a/sound/core/seq/oss/seq_oss_midi.c b/sound/core/seq/oss/seq_oss_midi.c
new file mode 100644
index 000000000..f2940b295
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_midi.c
@@ -0,0 +1,718 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * OSS compatible sequencer driver
+ *
+ * MIDI device handlers
+ *
+ * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <sound/asoundef.h>
+#include "seq_oss_midi.h"
+#include "seq_oss_readq.h"
+#include "seq_oss_timer.h"
+#include "seq_oss_event.h"
+#include <sound/seq_midi_event.h>
+#include "../seq_lock.h"
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/nospec.h>
+
+
+/*
+ * constants
+ */
+#define SNDRV_SEQ_OSS_MAX_MIDI_NAME 30
+
+/*
+ * definition of midi device record
+ */
+struct seq_oss_midi {
+ int seq_device; /* device number */
+ int client; /* sequencer client number */
+ int port; /* sequencer port number */
+ unsigned int flags; /* port capability */
+ int opened; /* flag for opening */
+ unsigned char name[SNDRV_SEQ_OSS_MAX_MIDI_NAME];
+ struct snd_midi_event *coder; /* MIDI event coder */
+ struct seq_oss_devinfo *devinfo; /* assigned OSSseq device */
+ snd_use_lock_t use_lock;
+ struct mutex open_mutex;
+};
+
+
+/*
+ * midi device table
+ */
+static int max_midi_devs;
+static struct seq_oss_midi *midi_devs[SNDRV_SEQ_OSS_MAX_MIDI_DEVS];
+
+static DEFINE_SPINLOCK(register_lock);
+
+/*
+ * prototypes
+ */
+static struct seq_oss_midi *get_mdev(int dev);
+static struct seq_oss_midi *get_mididev(struct seq_oss_devinfo *dp, int dev);
+static int send_synth_event(struct seq_oss_devinfo *dp, struct snd_seq_event *ev, int dev);
+static int send_midi_event(struct seq_oss_devinfo *dp, struct snd_seq_event *ev, struct seq_oss_midi *mdev);
+
+/*
+ * look up the existing ports
+ * this looks a very exhausting job.
+ */
+int
+snd_seq_oss_midi_lookup_ports(int client)
+{
+ struct snd_seq_client_info *clinfo;
+ struct snd_seq_port_info *pinfo;
+
+ clinfo = kzalloc(sizeof(*clinfo), GFP_KERNEL);
+ pinfo = kzalloc(sizeof(*pinfo), GFP_KERNEL);
+ if (! clinfo || ! pinfo) {
+ kfree(clinfo);
+ kfree(pinfo);
+ return -ENOMEM;
+ }
+ clinfo->client = -1;
+ while (snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_QUERY_NEXT_CLIENT, clinfo) == 0) {
+ if (clinfo->client == client)
+ continue; /* ignore myself */
+ pinfo->addr.client = clinfo->client;
+ pinfo->addr.port = -1;
+ while (snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT, pinfo) == 0)
+ snd_seq_oss_midi_check_new_port(pinfo);
+ }
+ kfree(clinfo);
+ kfree(pinfo);
+ return 0;
+}
+
+
+/*
+ */
+static struct seq_oss_midi *
+get_mdev(int dev)
+{
+ struct seq_oss_midi *mdev;
+ unsigned long flags;
+
+ spin_lock_irqsave(&register_lock, flags);
+ mdev = midi_devs[dev];
+ if (mdev)
+ snd_use_lock_use(&mdev->use_lock);
+ spin_unlock_irqrestore(&register_lock, flags);
+ return mdev;
+}
+
+/*
+ * look for the identical slot
+ */
+static struct seq_oss_midi *
+find_slot(int client, int port)
+{
+ int i;
+ struct seq_oss_midi *mdev;
+ unsigned long flags;
+
+ spin_lock_irqsave(&register_lock, flags);
+ for (i = 0; i < max_midi_devs; i++) {
+ mdev = midi_devs[i];
+ if (mdev && mdev->client == client && mdev->port == port) {
+ /* found! */
+ snd_use_lock_use(&mdev->use_lock);
+ spin_unlock_irqrestore(&register_lock, flags);
+ return mdev;
+ }
+ }
+ spin_unlock_irqrestore(&register_lock, flags);
+ return NULL;
+}
+
+
+#define PERM_WRITE (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_SUBS_WRITE)
+#define PERM_READ (SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_SUBS_READ)
+/*
+ * register a new port if it doesn't exist yet
+ */
+int
+snd_seq_oss_midi_check_new_port(struct snd_seq_port_info *pinfo)
+{
+ int i;
+ struct seq_oss_midi *mdev;
+ unsigned long flags;
+
+ /* the port must include generic midi */
+ if (! (pinfo->type & SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC))
+ return 0;
+ /* either read or write subscribable */
+ if ((pinfo->capability & PERM_WRITE) != PERM_WRITE &&
+ (pinfo->capability & PERM_READ) != PERM_READ)
+ return 0;
+
+ /*
+ * look for the identical slot
+ */
+ mdev = find_slot(pinfo->addr.client, pinfo->addr.port);
+ if (mdev) {
+ /* already exists */
+ snd_use_lock_free(&mdev->use_lock);
+ return 0;
+ }
+
+ /*
+ * allocate midi info record
+ */
+ mdev = kzalloc(sizeof(*mdev), GFP_KERNEL);
+ if (!mdev)
+ return -ENOMEM;
+
+ /* copy the port information */
+ mdev->client = pinfo->addr.client;
+ mdev->port = pinfo->addr.port;
+ mdev->flags = pinfo->capability;
+ mdev->opened = 0;
+ snd_use_lock_init(&mdev->use_lock);
+ mutex_init(&mdev->open_mutex);
+
+ /* copy and truncate the name of synth device */
+ strscpy(mdev->name, pinfo->name, sizeof(mdev->name));
+
+ /* create MIDI coder */
+ if (snd_midi_event_new(MAX_MIDI_EVENT_BUF, &mdev->coder) < 0) {
+ pr_err("ALSA: seq_oss: can't malloc midi coder\n");
+ kfree(mdev);
+ return -ENOMEM;
+ }
+ /* OSS sequencer adds running status to all sequences */
+ snd_midi_event_no_status(mdev->coder, 1);
+
+ /*
+ * look for en empty slot
+ */
+ spin_lock_irqsave(&register_lock, flags);
+ for (i = 0; i < max_midi_devs; i++) {
+ if (midi_devs[i] == NULL)
+ break;
+ }
+ if (i >= max_midi_devs) {
+ if (max_midi_devs >= SNDRV_SEQ_OSS_MAX_MIDI_DEVS) {
+ spin_unlock_irqrestore(&register_lock, flags);
+ snd_midi_event_free(mdev->coder);
+ kfree(mdev);
+ return -ENOMEM;
+ }
+ max_midi_devs++;
+ }
+ mdev->seq_device = i;
+ midi_devs[mdev->seq_device] = mdev;
+ spin_unlock_irqrestore(&register_lock, flags);
+
+ return 0;
+}
+
+/*
+ * release the midi device if it was registered
+ */
+int
+snd_seq_oss_midi_check_exit_port(int client, int port)
+{
+ struct seq_oss_midi *mdev;
+ unsigned long flags;
+ int index;
+
+ mdev = find_slot(client, port);
+ if (mdev) {
+ spin_lock_irqsave(&register_lock, flags);
+ midi_devs[mdev->seq_device] = NULL;
+ spin_unlock_irqrestore(&register_lock, flags);
+ snd_use_lock_free(&mdev->use_lock);
+ snd_use_lock_sync(&mdev->use_lock);
+ snd_midi_event_free(mdev->coder);
+ kfree(mdev);
+ }
+ spin_lock_irqsave(&register_lock, flags);
+ for (index = max_midi_devs - 1; index >= 0; index--) {
+ if (midi_devs[index])
+ break;
+ }
+ max_midi_devs = index + 1;
+ spin_unlock_irqrestore(&register_lock, flags);
+ return 0;
+}
+
+
+/*
+ * release the midi device if it was registered
+ */
+void
+snd_seq_oss_midi_clear_all(void)
+{
+ int i;
+ struct seq_oss_midi *mdev;
+ unsigned long flags;
+
+ spin_lock_irqsave(&register_lock, flags);
+ for (i = 0; i < max_midi_devs; i++) {
+ mdev = midi_devs[i];
+ if (mdev) {
+ snd_midi_event_free(mdev->coder);
+ kfree(mdev);
+ midi_devs[i] = NULL;
+ }
+ }
+ max_midi_devs = 0;
+ spin_unlock_irqrestore(&register_lock, flags);
+}
+
+
+/*
+ * set up midi tables
+ */
+void
+snd_seq_oss_midi_setup(struct seq_oss_devinfo *dp)
+{
+ spin_lock_irq(&register_lock);
+ dp->max_mididev = max_midi_devs;
+ spin_unlock_irq(&register_lock);
+}
+
+/*
+ * clean up midi tables
+ */
+void
+snd_seq_oss_midi_cleanup(struct seq_oss_devinfo *dp)
+{
+ int i;
+ for (i = 0; i < dp->max_mididev; i++)
+ snd_seq_oss_midi_close(dp, i);
+ dp->max_mididev = 0;
+}
+
+
+/*
+ * open all midi devices. ignore errors.
+ */
+void
+snd_seq_oss_midi_open_all(struct seq_oss_devinfo *dp, int file_mode)
+{
+ int i;
+ for (i = 0; i < dp->max_mididev; i++)
+ snd_seq_oss_midi_open(dp, i, file_mode);
+}
+
+
+/*
+ * get the midi device information
+ */
+static struct seq_oss_midi *
+get_mididev(struct seq_oss_devinfo *dp, int dev)
+{
+ if (dev < 0 || dev >= dp->max_mididev)
+ return NULL;
+ dev = array_index_nospec(dev, dp->max_mididev);
+ return get_mdev(dev);
+}
+
+
+/*
+ * open the midi device if not opened yet
+ */
+int
+snd_seq_oss_midi_open(struct seq_oss_devinfo *dp, int dev, int fmode)
+{
+ int perm;
+ struct seq_oss_midi *mdev;
+ struct snd_seq_port_subscribe subs;
+ int err;
+
+ mdev = get_mididev(dp, dev);
+ if (!mdev)
+ return -ENODEV;
+
+ mutex_lock(&mdev->open_mutex);
+ /* already used? */
+ if (mdev->opened && mdev->devinfo != dp) {
+ err = -EBUSY;
+ goto unlock;
+ }
+
+ perm = 0;
+ if (is_write_mode(fmode))
+ perm |= PERM_WRITE;
+ if (is_read_mode(fmode))
+ perm |= PERM_READ;
+ perm &= mdev->flags;
+ if (perm == 0) {
+ err = -ENXIO;
+ goto unlock;
+ }
+
+ /* already opened? */
+ if ((mdev->opened & perm) == perm) {
+ err = 0;
+ goto unlock;
+ }
+
+ perm &= ~mdev->opened;
+
+ memset(&subs, 0, sizeof(subs));
+
+ if (perm & PERM_WRITE) {
+ subs.sender = dp->addr;
+ subs.dest.client = mdev->client;
+ subs.dest.port = mdev->port;
+ if (snd_seq_kernel_client_ctl(dp->cseq, SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, &subs) >= 0)
+ mdev->opened |= PERM_WRITE;
+ }
+ if (perm & PERM_READ) {
+ subs.sender.client = mdev->client;
+ subs.sender.port = mdev->port;
+ subs.dest = dp->addr;
+ subs.flags = SNDRV_SEQ_PORT_SUBS_TIMESTAMP;
+ subs.queue = dp->queue; /* queue for timestamps */
+ if (snd_seq_kernel_client_ctl(dp->cseq, SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, &subs) >= 0)
+ mdev->opened |= PERM_READ;
+ }
+
+ if (! mdev->opened) {
+ err = -ENXIO;
+ goto unlock;
+ }
+
+ mdev->devinfo = dp;
+ err = 0;
+
+ unlock:
+ mutex_unlock(&mdev->open_mutex);
+ snd_use_lock_free(&mdev->use_lock);
+ return err;
+}
+
+/*
+ * close the midi device if already opened
+ */
+int
+snd_seq_oss_midi_close(struct seq_oss_devinfo *dp, int dev)
+{
+ struct seq_oss_midi *mdev;
+ struct snd_seq_port_subscribe subs;
+
+ mdev = get_mididev(dp, dev);
+ if (!mdev)
+ return -ENODEV;
+ mutex_lock(&mdev->open_mutex);
+ if (!mdev->opened || mdev->devinfo != dp)
+ goto unlock;
+
+ memset(&subs, 0, sizeof(subs));
+ if (mdev->opened & PERM_WRITE) {
+ subs.sender = dp->addr;
+ subs.dest.client = mdev->client;
+ subs.dest.port = mdev->port;
+ snd_seq_kernel_client_ctl(dp->cseq, SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT, &subs);
+ }
+ if (mdev->opened & PERM_READ) {
+ subs.sender.client = mdev->client;
+ subs.sender.port = mdev->port;
+ subs.dest = dp->addr;
+ snd_seq_kernel_client_ctl(dp->cseq, SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT, &subs);
+ }
+
+ mdev->opened = 0;
+ mdev->devinfo = NULL;
+
+ unlock:
+ mutex_unlock(&mdev->open_mutex);
+ snd_use_lock_free(&mdev->use_lock);
+ return 0;
+}
+
+/*
+ * change seq capability flags to file mode flags
+ */
+int
+snd_seq_oss_midi_filemode(struct seq_oss_devinfo *dp, int dev)
+{
+ struct seq_oss_midi *mdev;
+ int mode;
+
+ mdev = get_mididev(dp, dev);
+ if (!mdev)
+ return 0;
+
+ mode = 0;
+ if (mdev->opened & PERM_WRITE)
+ mode |= SNDRV_SEQ_OSS_FILE_WRITE;
+ if (mdev->opened & PERM_READ)
+ mode |= SNDRV_SEQ_OSS_FILE_READ;
+
+ snd_use_lock_free(&mdev->use_lock);
+ return mode;
+}
+
+/*
+ * reset the midi device and close it:
+ * so far, only close the device.
+ */
+void
+snd_seq_oss_midi_reset(struct seq_oss_devinfo *dp, int dev)
+{
+ struct seq_oss_midi *mdev;
+
+ mdev = get_mididev(dp, dev);
+ if (!mdev)
+ return;
+ if (! mdev->opened) {
+ snd_use_lock_free(&mdev->use_lock);
+ return;
+ }
+
+ if (mdev->opened & PERM_WRITE) {
+ struct snd_seq_event ev;
+ int c;
+
+ memset(&ev, 0, sizeof(ev));
+ ev.dest.client = mdev->client;
+ ev.dest.port = mdev->port;
+ ev.queue = dp->queue;
+ ev.source.port = dp->port;
+ if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_SYNTH) {
+ ev.type = SNDRV_SEQ_EVENT_SENSING;
+ snd_seq_oss_dispatch(dp, &ev, 0, 0);
+ }
+ for (c = 0; c < 16; c++) {
+ ev.type = SNDRV_SEQ_EVENT_CONTROLLER;
+ ev.data.control.channel = c;
+ ev.data.control.param = MIDI_CTL_ALL_NOTES_OFF;
+ snd_seq_oss_dispatch(dp, &ev, 0, 0);
+ if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) {
+ ev.data.control.param =
+ MIDI_CTL_RESET_CONTROLLERS;
+ snd_seq_oss_dispatch(dp, &ev, 0, 0);
+ ev.type = SNDRV_SEQ_EVENT_PITCHBEND;
+ ev.data.control.value = 0;
+ snd_seq_oss_dispatch(dp, &ev, 0, 0);
+ }
+ }
+ }
+ // snd_seq_oss_midi_close(dp, dev);
+ snd_use_lock_free(&mdev->use_lock);
+}
+
+
+/*
+ * get client/port of the specified MIDI device
+ */
+void
+snd_seq_oss_midi_get_addr(struct seq_oss_devinfo *dp, int dev, struct snd_seq_addr *addr)
+{
+ struct seq_oss_midi *mdev;
+
+ mdev = get_mididev(dp, dev);
+ if (!mdev)
+ return;
+ addr->client = mdev->client;
+ addr->port = mdev->port;
+ snd_use_lock_free(&mdev->use_lock);
+}
+
+
+/*
+ * input callback - this can be atomic
+ */
+int
+snd_seq_oss_midi_input(struct snd_seq_event *ev, int direct, void *private_data)
+{
+ struct seq_oss_devinfo *dp = (struct seq_oss_devinfo *)private_data;
+ struct seq_oss_midi *mdev;
+ int rc;
+
+ if (dp->readq == NULL)
+ return 0;
+ mdev = find_slot(ev->source.client, ev->source.port);
+ if (!mdev)
+ return 0;
+ if (! (mdev->opened & PERM_READ)) {
+ snd_use_lock_free(&mdev->use_lock);
+ return 0;
+ }
+
+ if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC)
+ rc = send_synth_event(dp, ev, mdev->seq_device);
+ else
+ rc = send_midi_event(dp, ev, mdev);
+
+ snd_use_lock_free(&mdev->use_lock);
+ return rc;
+}
+
+/*
+ * convert ALSA sequencer event to OSS synth event
+ */
+static int
+send_synth_event(struct seq_oss_devinfo *dp, struct snd_seq_event *ev, int dev)
+{
+ union evrec ossev;
+
+ memset(&ossev, 0, sizeof(ossev));
+
+ switch (ev->type) {
+ case SNDRV_SEQ_EVENT_NOTEON:
+ ossev.v.cmd = MIDI_NOTEON; break;
+ case SNDRV_SEQ_EVENT_NOTEOFF:
+ ossev.v.cmd = MIDI_NOTEOFF; break;
+ case SNDRV_SEQ_EVENT_KEYPRESS:
+ ossev.v.cmd = MIDI_KEY_PRESSURE; break;
+ case SNDRV_SEQ_EVENT_CONTROLLER:
+ ossev.l.cmd = MIDI_CTL_CHANGE; break;
+ case SNDRV_SEQ_EVENT_PGMCHANGE:
+ ossev.l.cmd = MIDI_PGM_CHANGE; break;
+ case SNDRV_SEQ_EVENT_CHANPRESS:
+ ossev.l.cmd = MIDI_CHN_PRESSURE; break;
+ case SNDRV_SEQ_EVENT_PITCHBEND:
+ ossev.l.cmd = MIDI_PITCH_BEND; break;
+ default:
+ return 0; /* not supported */
+ }
+
+ ossev.v.dev = dev;
+
+ switch (ev->type) {
+ case SNDRV_SEQ_EVENT_NOTEON:
+ case SNDRV_SEQ_EVENT_NOTEOFF:
+ case SNDRV_SEQ_EVENT_KEYPRESS:
+ ossev.v.code = EV_CHN_VOICE;
+ ossev.v.note = ev->data.note.note;
+ ossev.v.parm = ev->data.note.velocity;
+ ossev.v.chn = ev->data.note.channel;
+ break;
+ case SNDRV_SEQ_EVENT_CONTROLLER:
+ case SNDRV_SEQ_EVENT_PGMCHANGE:
+ case SNDRV_SEQ_EVENT_CHANPRESS:
+ ossev.l.code = EV_CHN_COMMON;
+ ossev.l.p1 = ev->data.control.param;
+ ossev.l.val = ev->data.control.value;
+ ossev.l.chn = ev->data.control.channel;
+ break;
+ case SNDRV_SEQ_EVENT_PITCHBEND:
+ ossev.l.code = EV_CHN_COMMON;
+ ossev.l.val = ev->data.control.value + 8192;
+ ossev.l.chn = ev->data.control.channel;
+ break;
+ }
+
+ snd_seq_oss_readq_put_timestamp(dp->readq, ev->time.tick, dp->seq_mode);
+ snd_seq_oss_readq_put_event(dp->readq, &ossev);
+
+ return 0;
+}
+
+/*
+ * decode event and send MIDI bytes to read queue
+ */
+static int
+send_midi_event(struct seq_oss_devinfo *dp, struct snd_seq_event *ev, struct seq_oss_midi *mdev)
+{
+ char msg[32];
+ int len;
+
+ snd_seq_oss_readq_put_timestamp(dp->readq, ev->time.tick, dp->seq_mode);
+ if (!dp->timer->running)
+ len = snd_seq_oss_timer_start(dp->timer);
+ if (ev->type == SNDRV_SEQ_EVENT_SYSEX) {
+ snd_seq_oss_readq_sysex(dp->readq, mdev->seq_device, ev);
+ snd_midi_event_reset_decode(mdev->coder);
+ } else {
+ len = snd_midi_event_decode(mdev->coder, msg, sizeof(msg), ev);
+ if (len > 0)
+ snd_seq_oss_readq_puts(dp->readq, mdev->seq_device, msg, len);
+ }
+
+ return 0;
+}
+
+
+/*
+ * dump midi data
+ * return 0 : enqueued
+ * non-zero : invalid - ignored
+ */
+int
+snd_seq_oss_midi_putc(struct seq_oss_devinfo *dp, int dev, unsigned char c, struct snd_seq_event *ev)
+{
+ struct seq_oss_midi *mdev;
+
+ mdev = get_mididev(dp, dev);
+ if (!mdev)
+ return -ENODEV;
+ if (snd_midi_event_encode_byte(mdev->coder, c, ev)) {
+ snd_seq_oss_fill_addr(dp, ev, mdev->client, mdev->port);
+ snd_use_lock_free(&mdev->use_lock);
+ return 0;
+ }
+ snd_use_lock_free(&mdev->use_lock);
+ return -EINVAL;
+}
+
+/*
+ * create OSS compatible midi_info record
+ */
+int
+snd_seq_oss_midi_make_info(struct seq_oss_devinfo *dp, int dev, struct midi_info *inf)
+{
+ struct seq_oss_midi *mdev;
+
+ mdev = get_mididev(dp, dev);
+ if (!mdev)
+ return -ENXIO;
+ inf->device = dev;
+ inf->dev_type = 0; /* FIXME: ?? */
+ inf->capabilities = 0; /* FIXME: ?? */
+ strscpy(inf->name, mdev->name, sizeof(inf->name));
+ snd_use_lock_free(&mdev->use_lock);
+ return 0;
+}
+
+
+#ifdef CONFIG_SND_PROC_FS
+/*
+ * proc interface
+ */
+static char *
+capmode_str(int val)
+{
+ val &= PERM_READ|PERM_WRITE;
+ if (val == (PERM_READ|PERM_WRITE))
+ return "read/write";
+ else if (val == PERM_READ)
+ return "read";
+ else if (val == PERM_WRITE)
+ return "write";
+ else
+ return "none";
+}
+
+void
+snd_seq_oss_midi_info_read(struct snd_info_buffer *buf)
+{
+ int i;
+ struct seq_oss_midi *mdev;
+
+ snd_iprintf(buf, "\nNumber of MIDI devices: %d\n", max_midi_devs);
+ for (i = 0; i < max_midi_devs; i++) {
+ snd_iprintf(buf, "\nmidi %d: ", i);
+ mdev = get_mdev(i);
+ if (mdev == NULL) {
+ snd_iprintf(buf, "*empty*\n");
+ continue;
+ }
+ snd_iprintf(buf, "[%s] ALSA port %d:%d\n", mdev->name,
+ mdev->client, mdev->port);
+ snd_iprintf(buf, " capability %s / opened %s\n",
+ capmode_str(mdev->flags),
+ capmode_str(mdev->opened));
+ snd_use_lock_free(&mdev->use_lock);
+ }
+}
+#endif /* CONFIG_SND_PROC_FS */
diff --git a/sound/core/seq/oss/seq_oss_midi.h b/sound/core/seq/oss/seq_oss_midi.h
new file mode 100644
index 000000000..bcc168377
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_midi.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * OSS compatible sequencer driver
+ *
+ * midi device information
+ *
+ * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
+ */
+
+#ifndef __SEQ_OSS_MIDI_H
+#define __SEQ_OSS_MIDI_H
+
+#include "seq_oss_device.h"
+#include <sound/seq_oss_legacy.h>
+
+int snd_seq_oss_midi_lookup_ports(int client);
+int snd_seq_oss_midi_check_new_port(struct snd_seq_port_info *pinfo);
+int snd_seq_oss_midi_check_exit_port(int client, int port);
+void snd_seq_oss_midi_clear_all(void);
+
+void snd_seq_oss_midi_setup(struct seq_oss_devinfo *dp);
+void snd_seq_oss_midi_cleanup(struct seq_oss_devinfo *dp);
+
+int snd_seq_oss_midi_open(struct seq_oss_devinfo *dp, int dev, int file_mode);
+void snd_seq_oss_midi_open_all(struct seq_oss_devinfo *dp, int file_mode);
+int snd_seq_oss_midi_close(struct seq_oss_devinfo *dp, int dev);
+void snd_seq_oss_midi_reset(struct seq_oss_devinfo *dp, int dev);
+int snd_seq_oss_midi_putc(struct seq_oss_devinfo *dp, int dev, unsigned char c,
+ struct snd_seq_event *ev);
+int snd_seq_oss_midi_input(struct snd_seq_event *ev, int direct, void *private);
+int snd_seq_oss_midi_filemode(struct seq_oss_devinfo *dp, int dev);
+int snd_seq_oss_midi_make_info(struct seq_oss_devinfo *dp, int dev, struct midi_info *inf);
+void snd_seq_oss_midi_get_addr(struct seq_oss_devinfo *dp, int dev, struct snd_seq_addr *addr);
+
+#endif
diff --git a/sound/core/seq/oss/seq_oss_readq.c b/sound/core/seq/oss/seq_oss_readq.c
new file mode 100644
index 000000000..f0db5d3dc
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_readq.c
@@ -0,0 +1,250 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * OSS compatible sequencer driver
+ *
+ * seq_oss_readq.c - MIDI input queue
+ *
+ * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include "seq_oss_readq.h"
+#include "seq_oss_event.h"
+#include <sound/seq_oss_legacy.h>
+#include "../seq_lock.h"
+#include <linux/wait.h>
+#include <linux/slab.h>
+
+/*
+ * constants
+ */
+//#define SNDRV_SEQ_OSS_MAX_TIMEOUT (unsigned long)(-1)
+#define SNDRV_SEQ_OSS_MAX_TIMEOUT (HZ * 3600)
+
+
+/*
+ * prototypes
+ */
+
+
+/*
+ * create a read queue
+ */
+struct seq_oss_readq *
+snd_seq_oss_readq_new(struct seq_oss_devinfo *dp, int maxlen)
+{
+ struct seq_oss_readq *q;
+
+ q = kzalloc(sizeof(*q), GFP_KERNEL);
+ if (!q)
+ return NULL;
+
+ q->q = kcalloc(maxlen, sizeof(union evrec), GFP_KERNEL);
+ if (!q->q) {
+ kfree(q);
+ return NULL;
+ }
+
+ q->maxlen = maxlen;
+ q->qlen = 0;
+ q->head = q->tail = 0;
+ init_waitqueue_head(&q->midi_sleep);
+ spin_lock_init(&q->lock);
+ q->pre_event_timeout = SNDRV_SEQ_OSS_MAX_TIMEOUT;
+ q->input_time = (unsigned long)-1;
+
+ return q;
+}
+
+/*
+ * delete the read queue
+ */
+void
+snd_seq_oss_readq_delete(struct seq_oss_readq *q)
+{
+ if (q) {
+ kfree(q->q);
+ kfree(q);
+ }
+}
+
+/*
+ * reset the read queue
+ */
+void
+snd_seq_oss_readq_clear(struct seq_oss_readq *q)
+{
+ if (q->qlen) {
+ q->qlen = 0;
+ q->head = q->tail = 0;
+ }
+ /* if someone sleeping, wake'em up */
+ wake_up(&q->midi_sleep);
+ q->input_time = (unsigned long)-1;
+}
+
+/*
+ * put a midi byte
+ */
+int
+snd_seq_oss_readq_puts(struct seq_oss_readq *q, int dev, unsigned char *data, int len)
+{
+ union evrec rec;
+ int result;
+
+ memset(&rec, 0, sizeof(rec));
+ rec.c[0] = SEQ_MIDIPUTC;
+ rec.c[2] = dev;
+
+ while (len-- > 0) {
+ rec.c[1] = *data++;
+ result = snd_seq_oss_readq_put_event(q, &rec);
+ if (result < 0)
+ return result;
+ }
+ return 0;
+}
+
+/*
+ * put MIDI sysex bytes; the event buffer may be chained, thus it has
+ * to be expanded via snd_seq_dump_var_event().
+ */
+struct readq_sysex_ctx {
+ struct seq_oss_readq *readq;
+ int dev;
+};
+
+static int readq_dump_sysex(void *ptr, void *buf, int count)
+{
+ struct readq_sysex_ctx *ctx = ptr;
+
+ return snd_seq_oss_readq_puts(ctx->readq, ctx->dev, buf, count);
+}
+
+int snd_seq_oss_readq_sysex(struct seq_oss_readq *q, int dev,
+ struct snd_seq_event *ev)
+{
+ struct readq_sysex_ctx ctx = {
+ .readq = q,
+ .dev = dev
+ };
+
+ if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARIABLE)
+ return 0;
+ return snd_seq_dump_var_event(ev, readq_dump_sysex, &ctx);
+}
+
+/*
+ * copy an event to input queue:
+ * return zero if enqueued
+ */
+int
+snd_seq_oss_readq_put_event(struct seq_oss_readq *q, union evrec *ev)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&q->lock, flags);
+ if (q->qlen >= q->maxlen - 1) {
+ spin_unlock_irqrestore(&q->lock, flags);
+ return -ENOMEM;
+ }
+
+ memcpy(&q->q[q->tail], ev, sizeof(*ev));
+ q->tail = (q->tail + 1) % q->maxlen;
+ q->qlen++;
+
+ /* wake up sleeper */
+ wake_up(&q->midi_sleep);
+
+ spin_unlock_irqrestore(&q->lock, flags);
+
+ return 0;
+}
+
+
+/*
+ * pop queue
+ * caller must hold lock
+ */
+int
+snd_seq_oss_readq_pick(struct seq_oss_readq *q, union evrec *rec)
+{
+ if (q->qlen == 0)
+ return -EAGAIN;
+ memcpy(rec, &q->q[q->head], sizeof(*rec));
+ return 0;
+}
+
+/*
+ * sleep until ready
+ */
+void
+snd_seq_oss_readq_wait(struct seq_oss_readq *q)
+{
+ wait_event_interruptible_timeout(q->midi_sleep,
+ (q->qlen > 0 || q->head == q->tail),
+ q->pre_event_timeout);
+}
+
+/*
+ * drain one record
+ * caller must hold lock
+ */
+void
+snd_seq_oss_readq_free(struct seq_oss_readq *q)
+{
+ if (q->qlen > 0) {
+ q->head = (q->head + 1) % q->maxlen;
+ q->qlen--;
+ }
+}
+
+/*
+ * polling/select:
+ * return non-zero if readq is not empty.
+ */
+unsigned int
+snd_seq_oss_readq_poll(struct seq_oss_readq *q, struct file *file, poll_table *wait)
+{
+ poll_wait(file, &q->midi_sleep, wait);
+ return q->qlen;
+}
+
+/*
+ * put a timestamp
+ */
+int
+snd_seq_oss_readq_put_timestamp(struct seq_oss_readq *q, unsigned long curt, int seq_mode)
+{
+ if (curt != q->input_time) {
+ union evrec rec;
+ memset(&rec, 0, sizeof(rec));
+ switch (seq_mode) {
+ case SNDRV_SEQ_OSS_MODE_SYNTH:
+ rec.echo = (curt << 8) | SEQ_WAIT;
+ snd_seq_oss_readq_put_event(q, &rec);
+ break;
+ case SNDRV_SEQ_OSS_MODE_MUSIC:
+ rec.t.code = EV_TIMING;
+ rec.t.cmd = TMR_WAIT_ABS;
+ rec.t.time = curt;
+ snd_seq_oss_readq_put_event(q, &rec);
+ break;
+ }
+ q->input_time = curt;
+ }
+ return 0;
+}
+
+
+#ifdef CONFIG_SND_PROC_FS
+/*
+ * proc interface
+ */
+void
+snd_seq_oss_readq_info_read(struct seq_oss_readq *q, struct snd_info_buffer *buf)
+{
+ snd_iprintf(buf, " read queue [%s] length = %d : tick = %ld\n",
+ (waitqueue_active(&q->midi_sleep) ? "sleeping":"running"),
+ q->qlen, q->input_time);
+}
+#endif /* CONFIG_SND_PROC_FS */
diff --git a/sound/core/seq/oss/seq_oss_readq.h b/sound/core/seq/oss/seq_oss_readq.h
new file mode 100644
index 000000000..38d0c4682
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_readq.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * OSS compatible sequencer driver
+ * read fifo queue
+ *
+ * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
+ */
+
+#ifndef __SEQ_OSS_READQ_H
+#define __SEQ_OSS_READQ_H
+
+#include "seq_oss_device.h"
+
+
+/*
+ * definition of read queue
+ */
+struct seq_oss_readq {
+ union evrec *q;
+ int qlen;
+ int maxlen;
+ int head, tail;
+ unsigned long pre_event_timeout;
+ unsigned long input_time;
+ wait_queue_head_t midi_sleep;
+ spinlock_t lock;
+};
+
+struct seq_oss_readq *snd_seq_oss_readq_new(struct seq_oss_devinfo *dp, int maxlen);
+void snd_seq_oss_readq_delete(struct seq_oss_readq *q);
+void snd_seq_oss_readq_clear(struct seq_oss_readq *readq);
+unsigned int snd_seq_oss_readq_poll(struct seq_oss_readq *readq, struct file *file, poll_table *wait);
+int snd_seq_oss_readq_puts(struct seq_oss_readq *readq, int dev, unsigned char *data, int len);
+int snd_seq_oss_readq_sysex(struct seq_oss_readq *q, int dev,
+ struct snd_seq_event *ev);
+int snd_seq_oss_readq_put_event(struct seq_oss_readq *readq, union evrec *ev);
+int snd_seq_oss_readq_put_timestamp(struct seq_oss_readq *readq, unsigned long curt, int seq_mode);
+int snd_seq_oss_readq_pick(struct seq_oss_readq *q, union evrec *rec);
+void snd_seq_oss_readq_wait(struct seq_oss_readq *q);
+void snd_seq_oss_readq_free(struct seq_oss_readq *q);
+
+#define snd_seq_oss_readq_lock(q, flags) spin_lock_irqsave(&(q)->lock, flags)
+#define snd_seq_oss_readq_unlock(q, flags) spin_unlock_irqrestore(&(q)->lock, flags)
+
+#endif
diff --git a/sound/core/seq/oss/seq_oss_rw.c b/sound/core/seq/oss/seq_oss_rw.c
new file mode 100644
index 000000000..8a142fd54
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_rw.c
@@ -0,0 +1,201 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * OSS compatible sequencer driver
+ *
+ * read/write/select interface to device file
+ *
+ * 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_synth.h"
+#include <sound/seq_oss_legacy.h>
+#include "seq_oss_event.h"
+#include "seq_oss_timer.h"
+#include "../seq_clientmgr.h"
+
+
+/*
+ * protoypes
+ */
+static int insert_queue(struct seq_oss_devinfo *dp, union evrec *rec, struct file *opt);
+
+
+/*
+ * read interface
+ */
+
+int
+snd_seq_oss_read(struct seq_oss_devinfo *dp, char __user *buf, int count)
+{
+ struct seq_oss_readq *readq = dp->readq;
+ int result = 0, err = 0;
+ int ev_len;
+ union evrec rec;
+ unsigned long flags;
+
+ if (readq == NULL || ! is_read_mode(dp->file_mode))
+ return -ENXIO;
+
+ while (count >= SHORT_EVENT_SIZE) {
+ snd_seq_oss_readq_lock(readq, flags);
+ err = snd_seq_oss_readq_pick(readq, &rec);
+ if (err == -EAGAIN &&
+ !is_nonblock_mode(dp->file_mode) && result == 0) {
+ snd_seq_oss_readq_unlock(readq, flags);
+ snd_seq_oss_readq_wait(readq);
+ snd_seq_oss_readq_lock(readq, flags);
+ if (signal_pending(current))
+ err = -ERESTARTSYS;
+ else
+ err = snd_seq_oss_readq_pick(readq, &rec);
+ }
+ if (err < 0) {
+ snd_seq_oss_readq_unlock(readq, flags);
+ break;
+ }
+ ev_len = ev_length(&rec);
+ if (ev_len < count) {
+ snd_seq_oss_readq_unlock(readq, flags);
+ break;
+ }
+ snd_seq_oss_readq_free(readq);
+ snd_seq_oss_readq_unlock(readq, flags);
+ if (copy_to_user(buf, &rec, ev_len)) {
+ err = -EFAULT;
+ break;
+ }
+ result += ev_len;
+ buf += ev_len;
+ count -= ev_len;
+ }
+ return result > 0 ? result : err;
+}
+
+
+/*
+ * write interface
+ */
+
+int
+snd_seq_oss_write(struct seq_oss_devinfo *dp, const char __user *buf, int count, struct file *opt)
+{
+ int result = 0, err = 0;
+ int ev_size, fmt;
+ union evrec rec;
+
+ if (! is_write_mode(dp->file_mode) || dp->writeq == NULL)
+ return -ENXIO;
+
+ while (count >= SHORT_EVENT_SIZE) {
+ if (copy_from_user(&rec, buf, SHORT_EVENT_SIZE)) {
+ err = -EFAULT;
+ break;
+ }
+ if (rec.s.code == SEQ_FULLSIZE) {
+ /* load patch */
+ if (result > 0) {
+ err = -EINVAL;
+ break;
+ }
+ fmt = (*(unsigned short *)rec.c) & 0xffff;
+ /* FIXME the return value isn't correct */
+ return snd_seq_oss_synth_load_patch(dp, rec.s.dev,
+ fmt, buf, 0, count);
+ }
+ if (ev_is_long(&rec)) {
+ /* extended code */
+ if (rec.s.code == SEQ_EXTENDED &&
+ dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) {
+ err = -EINVAL;
+ break;
+ }
+ ev_size = LONG_EVENT_SIZE;
+ if (count < ev_size)
+ break;
+ /* copy the reset 4 bytes */
+ if (copy_from_user(rec.c + SHORT_EVENT_SIZE,
+ buf + SHORT_EVENT_SIZE,
+ LONG_EVENT_SIZE - SHORT_EVENT_SIZE)) {
+ err = -EFAULT;
+ break;
+ }
+ } else {
+ /* old-type code */
+ if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) {
+ err = -EINVAL;
+ break;
+ }
+ ev_size = SHORT_EVENT_SIZE;
+ }
+
+ /* insert queue */
+ err = insert_queue(dp, &rec, opt);
+ if (err < 0)
+ break;
+
+ result += ev_size;
+ buf += ev_size;
+ count -= ev_size;
+ }
+ return result > 0 ? result : err;
+}
+
+
+/*
+ * insert event record to write queue
+ * return: 0 = OK, non-zero = NG
+ */
+static int
+insert_queue(struct seq_oss_devinfo *dp, union evrec *rec, struct file *opt)
+{
+ int rc = 0;
+ struct snd_seq_event event;
+
+ /* if this is a timing event, process the current time */
+ if (snd_seq_oss_process_timer_event(dp->timer, rec))
+ return 0; /* no need to insert queue */
+
+ /* parse this event */
+ memset(&event, 0, sizeof(event));
+ /* set dummy -- to be sure */
+ event.type = SNDRV_SEQ_EVENT_NOTEOFF;
+ snd_seq_oss_fill_addr(dp, &event, dp->addr.client, dp->addr.port);
+
+ if (snd_seq_oss_process_event(dp, rec, &event))
+ return 0; /* invalid event - no need to insert queue */
+
+ event.time.tick = snd_seq_oss_timer_cur_tick(dp->timer);
+ if (dp->timer->realtime || !dp->timer->running)
+ snd_seq_oss_dispatch(dp, &event, 0, 0);
+ else
+ rc = snd_seq_kernel_client_enqueue(dp->cseq, &event, opt,
+ !is_nonblock_mode(dp->file_mode));
+ return rc;
+}
+
+
+/*
+ * select / poll
+ */
+
+__poll_t
+snd_seq_oss_poll(struct seq_oss_devinfo *dp, struct file *file, poll_table * wait)
+{
+ __poll_t mask = 0;
+
+ /* input */
+ if (dp->readq && is_read_mode(dp->file_mode)) {
+ if (snd_seq_oss_readq_poll(dp->readq, file, wait))
+ mask |= EPOLLIN | EPOLLRDNORM;
+ }
+
+ /* output */
+ if (dp->writeq && is_write_mode(dp->file_mode)) {
+ if (snd_seq_kernel_client_write_poll(dp->cseq, file, wait))
+ mask |= EPOLLOUT | EPOLLWRNORM;
+ }
+ return mask;
+}
diff --git a/sound/core/seq/oss/seq_oss_synth.c b/sound/core/seq/oss/seq_oss_synth.c
new file mode 100644
index 000000000..e3394919d
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_synth.c
@@ -0,0 +1,666 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * OSS compatible sequencer driver
+ *
+ * synth device handlers
+ *
+ * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include "seq_oss_synth.h"
+#include "seq_oss_midi.h"
+#include "../seq_lock.h"
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/nospec.h>
+
+/*
+ * constants
+ */
+#define SNDRV_SEQ_OSS_MAX_SYNTH_NAME 30
+#define MAX_SYSEX_BUFLEN 128
+
+
+/*
+ * definition of synth info records
+ */
+
+/* sysex buffer */
+struct seq_oss_synth_sysex {
+ int len;
+ int skip;
+ unsigned char buf[MAX_SYSEX_BUFLEN];
+};
+
+/* synth info */
+struct seq_oss_synth {
+ int seq_device;
+
+ /* for synth_info */
+ int synth_type;
+ int synth_subtype;
+ int nr_voices;
+
+ char name[SNDRV_SEQ_OSS_MAX_SYNTH_NAME];
+ struct snd_seq_oss_callback oper;
+
+ int opened;
+
+ void *private_data;
+ snd_use_lock_t use_lock;
+};
+
+
+/*
+ * device table
+ */
+static int max_synth_devs;
+static struct seq_oss_synth *synth_devs[SNDRV_SEQ_OSS_MAX_SYNTH_DEVS];
+static struct seq_oss_synth midi_synth_dev = {
+ .seq_device = -1,
+ .synth_type = SYNTH_TYPE_MIDI,
+ .synth_subtype = 0,
+ .nr_voices = 16,
+ .name = "MIDI",
+};
+
+static DEFINE_SPINLOCK(register_lock);
+
+/*
+ * prototypes
+ */
+static struct seq_oss_synth *get_synthdev(struct seq_oss_devinfo *dp, int dev);
+static void reset_channels(struct seq_oss_synthinfo *info);
+
+/*
+ * global initialization
+ */
+void __init
+snd_seq_oss_synth_init(void)
+{
+ snd_use_lock_init(&midi_synth_dev.use_lock);
+}
+
+/*
+ * registration of the synth device
+ */
+int
+snd_seq_oss_synth_probe(struct device *_dev)
+{
+ struct snd_seq_device *dev = to_seq_dev(_dev);
+ int i;
+ struct seq_oss_synth *rec;
+ struct snd_seq_oss_reg *reg = SNDRV_SEQ_DEVICE_ARGPTR(dev);
+ unsigned long flags;
+
+ rec = kzalloc(sizeof(*rec), GFP_KERNEL);
+ if (!rec)
+ return -ENOMEM;
+ rec->seq_device = -1;
+ rec->synth_type = reg->type;
+ rec->synth_subtype = reg->subtype;
+ rec->nr_voices = reg->nvoices;
+ rec->oper = reg->oper;
+ rec->private_data = reg->private_data;
+ rec->opened = 0;
+ snd_use_lock_init(&rec->use_lock);
+
+ /* copy and truncate the name of synth device */
+ strscpy(rec->name, dev->name, sizeof(rec->name));
+
+ /* registration */
+ spin_lock_irqsave(&register_lock, flags);
+ for (i = 0; i < max_synth_devs; i++) {
+ if (synth_devs[i] == NULL)
+ break;
+ }
+ if (i >= max_synth_devs) {
+ if (max_synth_devs >= SNDRV_SEQ_OSS_MAX_SYNTH_DEVS) {
+ spin_unlock_irqrestore(&register_lock, flags);
+ pr_err("ALSA: seq_oss: no more synth slot\n");
+ kfree(rec);
+ return -ENOMEM;
+ }
+ max_synth_devs++;
+ }
+ rec->seq_device = i;
+ synth_devs[i] = rec;
+ spin_unlock_irqrestore(&register_lock, flags);
+ dev->driver_data = rec;
+#ifdef SNDRV_OSS_INFO_DEV_SYNTH
+ if (i < SNDRV_CARDS)
+ snd_oss_info_register(SNDRV_OSS_INFO_DEV_SYNTH, i, rec->name);
+#endif
+ return 0;
+}
+
+
+int
+snd_seq_oss_synth_remove(struct device *_dev)
+{
+ struct snd_seq_device *dev = to_seq_dev(_dev);
+ int index;
+ struct seq_oss_synth *rec = dev->driver_data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&register_lock, flags);
+ for (index = 0; index < max_synth_devs; index++) {
+ if (synth_devs[index] == rec)
+ break;
+ }
+ if (index >= max_synth_devs) {
+ spin_unlock_irqrestore(&register_lock, flags);
+ pr_err("ALSA: seq_oss: can't unregister synth\n");
+ return -EINVAL;
+ }
+ synth_devs[index] = NULL;
+ if (index == max_synth_devs - 1) {
+ for (index--; index >= 0; index--) {
+ if (synth_devs[index])
+ break;
+ }
+ max_synth_devs = index + 1;
+ }
+ spin_unlock_irqrestore(&register_lock, flags);
+#ifdef SNDRV_OSS_INFO_DEV_SYNTH
+ if (rec->seq_device < SNDRV_CARDS)
+ snd_oss_info_unregister(SNDRV_OSS_INFO_DEV_SYNTH, rec->seq_device);
+#endif
+
+ snd_use_lock_sync(&rec->use_lock);
+ kfree(rec);
+
+ return 0;
+}
+
+
+/*
+ */
+static struct seq_oss_synth *
+get_sdev(int dev)
+{
+ struct seq_oss_synth *rec;
+ unsigned long flags;
+
+ spin_lock_irqsave(&register_lock, flags);
+ rec = synth_devs[dev];
+ if (rec)
+ snd_use_lock_use(&rec->use_lock);
+ spin_unlock_irqrestore(&register_lock, flags);
+ return rec;
+}
+
+
+/*
+ * set up synth tables
+ */
+
+void
+snd_seq_oss_synth_setup(struct seq_oss_devinfo *dp)
+{
+ int i;
+ struct seq_oss_synth *rec;
+ struct seq_oss_synthinfo *info;
+
+ dp->max_synthdev = max_synth_devs;
+ dp->synth_opened = 0;
+ memset(dp->synths, 0, sizeof(dp->synths));
+ for (i = 0; i < dp->max_synthdev; i++) {
+ rec = get_sdev(i);
+ if (rec == NULL)
+ continue;
+ if (rec->oper.open == NULL || rec->oper.close == NULL) {
+ snd_use_lock_free(&rec->use_lock);
+ continue;
+ }
+ info = &dp->synths[i];
+ info->arg.app_index = dp->port;
+ info->arg.file_mode = dp->file_mode;
+ info->arg.seq_mode = dp->seq_mode;
+ if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_SYNTH)
+ info->arg.event_passing = SNDRV_SEQ_OSS_PROCESS_EVENTS;
+ else
+ info->arg.event_passing = SNDRV_SEQ_OSS_PASS_EVENTS;
+ info->opened = 0;
+ if (!try_module_get(rec->oper.owner)) {
+ snd_use_lock_free(&rec->use_lock);
+ continue;
+ }
+ if (rec->oper.open(&info->arg, rec->private_data) < 0) {
+ module_put(rec->oper.owner);
+ snd_use_lock_free(&rec->use_lock);
+ continue;
+ }
+ info->nr_voices = rec->nr_voices;
+ if (info->nr_voices > 0) {
+ info->ch = kcalloc(info->nr_voices, sizeof(struct seq_oss_chinfo), GFP_KERNEL);
+ if (!info->ch) {
+ rec->oper.close(&info->arg);
+ module_put(rec->oper.owner);
+ snd_use_lock_free(&rec->use_lock);
+ continue;
+ }
+ reset_channels(info);
+ }
+ info->opened++;
+ rec->opened++;
+ dp->synth_opened++;
+ snd_use_lock_free(&rec->use_lock);
+ }
+}
+
+
+/*
+ * set up synth tables for MIDI emulation - /dev/music mode only
+ */
+
+void
+snd_seq_oss_synth_setup_midi(struct seq_oss_devinfo *dp)
+{
+ int i;
+
+ if (dp->max_synthdev >= SNDRV_SEQ_OSS_MAX_SYNTH_DEVS)
+ return;
+
+ for (i = 0; i < dp->max_mididev; i++) {
+ struct seq_oss_synthinfo *info;
+ info = &dp->synths[dp->max_synthdev];
+ if (snd_seq_oss_midi_open(dp, i, dp->file_mode) < 0)
+ continue;
+ info->arg.app_index = dp->port;
+ info->arg.file_mode = dp->file_mode;
+ info->arg.seq_mode = dp->seq_mode;
+ info->arg.private_data = info;
+ info->is_midi = 1;
+ info->midi_mapped = i;
+ info->arg.event_passing = SNDRV_SEQ_OSS_PASS_EVENTS;
+ snd_seq_oss_midi_get_addr(dp, i, &info->arg.addr);
+ info->opened = 1;
+ midi_synth_dev.opened++;
+ dp->max_synthdev++;
+ if (dp->max_synthdev >= SNDRV_SEQ_OSS_MAX_SYNTH_DEVS)
+ break;
+ }
+}
+
+
+/*
+ * clean up synth tables
+ */
+
+void
+snd_seq_oss_synth_cleanup(struct seq_oss_devinfo *dp)
+{
+ int i;
+ struct seq_oss_synth *rec;
+ struct seq_oss_synthinfo *info;
+
+ if (snd_BUG_ON(dp->max_synthdev > SNDRV_SEQ_OSS_MAX_SYNTH_DEVS))
+ return;
+ for (i = 0; i < dp->max_synthdev; i++) {
+ info = &dp->synths[i];
+ if (! info->opened)
+ continue;
+ if (info->is_midi) {
+ if (midi_synth_dev.opened > 0) {
+ snd_seq_oss_midi_close(dp, info->midi_mapped);
+ midi_synth_dev.opened--;
+ }
+ } else {
+ rec = get_sdev(i);
+ if (rec == NULL)
+ continue;
+ if (rec->opened > 0) {
+ rec->oper.close(&info->arg);
+ module_put(rec->oper.owner);
+ rec->opened = 0;
+ }
+ snd_use_lock_free(&rec->use_lock);
+ }
+ kfree(info->sysex);
+ info->sysex = NULL;
+ kfree(info->ch);
+ info->ch = NULL;
+ }
+ dp->synth_opened = 0;
+ dp->max_synthdev = 0;
+}
+
+static struct seq_oss_synthinfo *
+get_synthinfo_nospec(struct seq_oss_devinfo *dp, int dev)
+{
+ if (dev < 0 || dev >= dp->max_synthdev)
+ return NULL;
+ dev = array_index_nospec(dev, SNDRV_SEQ_OSS_MAX_SYNTH_DEVS);
+ return &dp->synths[dev];
+}
+
+/*
+ * return synth device information pointer
+ */
+static struct seq_oss_synth *
+get_synthdev(struct seq_oss_devinfo *dp, int dev)
+{
+ struct seq_oss_synth *rec;
+ struct seq_oss_synthinfo *info = get_synthinfo_nospec(dp, dev);
+
+ if (!info)
+ return NULL;
+ if (!info->opened)
+ return NULL;
+ if (info->is_midi) {
+ rec = &midi_synth_dev;
+ snd_use_lock_use(&rec->use_lock);
+ } else {
+ rec = get_sdev(dev);
+ if (!rec)
+ return NULL;
+ }
+ if (! rec->opened) {
+ snd_use_lock_free(&rec->use_lock);
+ return NULL;
+ }
+ return rec;
+}
+
+
+/*
+ * reset note and velocity on each channel.
+ */
+static void
+reset_channels(struct seq_oss_synthinfo *info)
+{
+ int i;
+ if (info->ch == NULL || ! info->nr_voices)
+ return;
+ for (i = 0; i < info->nr_voices; i++) {
+ info->ch[i].note = -1;
+ info->ch[i].vel = 0;
+ }
+}
+
+
+/*
+ * reset synth device:
+ * call reset callback. if no callback is defined, send a heartbeat
+ * event to the corresponding port.
+ */
+void
+snd_seq_oss_synth_reset(struct seq_oss_devinfo *dp, int dev)
+{
+ struct seq_oss_synth *rec;
+ struct seq_oss_synthinfo *info;
+
+ info = get_synthinfo_nospec(dp, dev);
+ if (!info || !info->opened)
+ return;
+ if (info->sysex)
+ info->sysex->len = 0; /* reset sysex */
+ reset_channels(info);
+ if (info->is_midi) {
+ if (midi_synth_dev.opened <= 0)
+ return;
+ snd_seq_oss_midi_reset(dp, info->midi_mapped);
+ /* reopen the device */
+ snd_seq_oss_midi_close(dp, dev);
+ if (snd_seq_oss_midi_open(dp, info->midi_mapped,
+ dp->file_mode) < 0) {
+ midi_synth_dev.opened--;
+ info->opened = 0;
+ kfree(info->sysex);
+ info->sysex = NULL;
+ kfree(info->ch);
+ info->ch = NULL;
+ }
+ return;
+ }
+
+ rec = get_sdev(dev);
+ if (rec == NULL)
+ return;
+ if (rec->oper.reset) {
+ rec->oper.reset(&info->arg);
+ } else {
+ struct snd_seq_event ev;
+ memset(&ev, 0, sizeof(ev));
+ snd_seq_oss_fill_addr(dp, &ev, info->arg.addr.client,
+ info->arg.addr.port);
+ ev.type = SNDRV_SEQ_EVENT_RESET;
+ snd_seq_oss_dispatch(dp, &ev, 0, 0);
+ }
+ snd_use_lock_free(&rec->use_lock);
+}
+
+
+/*
+ * load a patch record:
+ * call load_patch callback function
+ */
+int
+snd_seq_oss_synth_load_patch(struct seq_oss_devinfo *dp, int dev, int fmt,
+ const char __user *buf, int p, int c)
+{
+ struct seq_oss_synth *rec;
+ struct seq_oss_synthinfo *info;
+ int rc;
+
+ info = get_synthinfo_nospec(dp, dev);
+ if (!info)
+ return -ENXIO;
+
+ if (info->is_midi)
+ return 0;
+ rec = get_synthdev(dp, dev);
+ if (!rec)
+ return -ENXIO;
+
+ if (rec->oper.load_patch == NULL)
+ rc = -ENXIO;
+ else
+ rc = rec->oper.load_patch(&info->arg, fmt, buf, p, c);
+ snd_use_lock_free(&rec->use_lock);
+ return rc;
+}
+
+/*
+ * check if the device is valid synth device and return the synth info
+ */
+struct seq_oss_synthinfo *
+snd_seq_oss_synth_info(struct seq_oss_devinfo *dp, int dev)
+{
+ struct seq_oss_synth *rec;
+
+ rec = get_synthdev(dp, dev);
+ if (rec) {
+ snd_use_lock_free(&rec->use_lock);
+ return get_synthinfo_nospec(dp, dev);
+ }
+ return NULL;
+}
+
+
+/*
+ * receive OSS 6 byte sysex packet:
+ * the full sysex message will be sent if it reaches to the end of data
+ * (0xff).
+ */
+int
+snd_seq_oss_synth_sysex(struct seq_oss_devinfo *dp, int dev, unsigned char *buf, struct snd_seq_event *ev)
+{
+ int i, send;
+ unsigned char *dest;
+ struct seq_oss_synth_sysex *sysex;
+ struct seq_oss_synthinfo *info;
+
+ info = snd_seq_oss_synth_info(dp, dev);
+ if (!info)
+ return -ENXIO;
+
+ sysex = info->sysex;
+ if (sysex == NULL) {
+ sysex = kzalloc(sizeof(*sysex), GFP_KERNEL);
+ if (sysex == NULL)
+ return -ENOMEM;
+ info->sysex = sysex;
+ }
+
+ send = 0;
+ dest = sysex->buf + sysex->len;
+ /* copy 6 byte packet to the buffer */
+ for (i = 0; i < 6; i++) {
+ if (buf[i] == 0xff) {
+ send = 1;
+ break;
+ }
+ dest[i] = buf[i];
+ sysex->len++;
+ if (sysex->len >= MAX_SYSEX_BUFLEN) {
+ sysex->len = 0;
+ sysex->skip = 1;
+ break;
+ }
+ }
+
+ if (sysex->len && send) {
+ if (sysex->skip) {
+ sysex->skip = 0;
+ sysex->len = 0;
+ return -EINVAL; /* skip */
+ }
+ /* copy the data to event record and send it */
+ ev->flags = SNDRV_SEQ_EVENT_LENGTH_VARIABLE;
+ if (snd_seq_oss_synth_addr(dp, dev, ev))
+ return -EINVAL;
+ ev->data.ext.len = sysex->len;
+ ev->data.ext.ptr = sysex->buf;
+ sysex->len = 0;
+ return 0;
+ }
+
+ return -EINVAL; /* skip */
+}
+
+/*
+ * fill the event source/destination addresses
+ */
+int
+snd_seq_oss_synth_addr(struct seq_oss_devinfo *dp, int dev, struct snd_seq_event *ev)
+{
+ struct seq_oss_synthinfo *info = snd_seq_oss_synth_info(dp, dev);
+
+ if (!info)
+ return -EINVAL;
+ snd_seq_oss_fill_addr(dp, ev, info->arg.addr.client,
+ info->arg.addr.port);
+ return 0;
+}
+
+
+/*
+ * OSS compatible ioctl
+ */
+int
+snd_seq_oss_synth_ioctl(struct seq_oss_devinfo *dp, int dev, unsigned int cmd, unsigned long addr)
+{
+ struct seq_oss_synth *rec;
+ struct seq_oss_synthinfo *info;
+ int rc;
+
+ info = get_synthinfo_nospec(dp, dev);
+ if (!info || info->is_midi)
+ return -ENXIO;
+ rec = get_synthdev(dp, dev);
+ if (!rec)
+ return -ENXIO;
+ if (rec->oper.ioctl == NULL)
+ rc = -ENXIO;
+ else
+ rc = rec->oper.ioctl(&info->arg, cmd, addr);
+ snd_use_lock_free(&rec->use_lock);
+ return rc;
+}
+
+
+/*
+ * send OSS raw events - SEQ_PRIVATE and SEQ_VOLUME
+ */
+int
+snd_seq_oss_synth_raw_event(struct seq_oss_devinfo *dp, int dev, unsigned char *data, struct snd_seq_event *ev)
+{
+ struct seq_oss_synthinfo *info;
+
+ info = snd_seq_oss_synth_info(dp, dev);
+ if (!info || info->is_midi)
+ return -ENXIO;
+ ev->type = SNDRV_SEQ_EVENT_OSS;
+ memcpy(ev->data.raw8.d, data, 8);
+ return snd_seq_oss_synth_addr(dp, dev, ev);
+}
+
+
+/*
+ * create OSS compatible synth_info record
+ */
+int
+snd_seq_oss_synth_make_info(struct seq_oss_devinfo *dp, int dev, struct synth_info *inf)
+{
+ struct seq_oss_synth *rec;
+ struct seq_oss_synthinfo *info = get_synthinfo_nospec(dp, dev);
+
+ if (!info)
+ return -ENXIO;
+
+ if (info->is_midi) {
+ struct midi_info minf;
+ if (snd_seq_oss_midi_make_info(dp, info->midi_mapped, &minf))
+ return -ENXIO;
+ inf->synth_type = SYNTH_TYPE_MIDI;
+ inf->synth_subtype = 0;
+ inf->nr_voices = 16;
+ inf->device = dev;
+ strscpy(inf->name, minf.name, sizeof(inf->name));
+ } else {
+ rec = get_synthdev(dp, dev);
+ if (!rec)
+ return -ENXIO;
+ inf->synth_type = rec->synth_type;
+ inf->synth_subtype = rec->synth_subtype;
+ inf->nr_voices = rec->nr_voices;
+ inf->device = dev;
+ strscpy(inf->name, rec->name, sizeof(inf->name));
+ snd_use_lock_free(&rec->use_lock);
+ }
+ return 0;
+}
+
+
+#ifdef CONFIG_SND_PROC_FS
+/*
+ * proc interface
+ */
+void
+snd_seq_oss_synth_info_read(struct snd_info_buffer *buf)
+{
+ int i;
+ struct seq_oss_synth *rec;
+
+ snd_iprintf(buf, "\nNumber of synth devices: %d\n", max_synth_devs);
+ for (i = 0; i < max_synth_devs; i++) {
+ snd_iprintf(buf, "\nsynth %d: ", i);
+ rec = get_sdev(i);
+ if (rec == NULL) {
+ snd_iprintf(buf, "*empty*\n");
+ continue;
+ }
+ snd_iprintf(buf, "[%s]\n", rec->name);
+ snd_iprintf(buf, " type 0x%x : subtype 0x%x : voices %d\n",
+ rec->synth_type, rec->synth_subtype,
+ rec->nr_voices);
+ snd_iprintf(buf, " capabilities : ioctl %s / load_patch %s\n",
+ enabled_str((long)rec->oper.ioctl),
+ enabled_str((long)rec->oper.load_patch));
+ snd_use_lock_free(&rec->use_lock);
+ }
+}
+#endif /* CONFIG_SND_PROC_FS */
diff --git a/sound/core/seq/oss/seq_oss_synth.h b/sound/core/seq/oss/seq_oss_synth.h
new file mode 100644
index 000000000..ffc40d8a7
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_synth.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * OSS compatible sequencer driver
+ *
+ * synth device information
+ *
+ * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
+ */
+
+#ifndef __SEQ_OSS_SYNTH_H
+#define __SEQ_OSS_SYNTH_H
+
+#include "seq_oss_device.h"
+#include <sound/seq_oss_legacy.h>
+#include <sound/seq_device.h>
+
+void snd_seq_oss_synth_init(void);
+int snd_seq_oss_synth_probe(struct device *dev);
+int snd_seq_oss_synth_remove(struct device *dev);
+void snd_seq_oss_synth_setup(struct seq_oss_devinfo *dp);
+void snd_seq_oss_synth_setup_midi(struct seq_oss_devinfo *dp);
+void snd_seq_oss_synth_cleanup(struct seq_oss_devinfo *dp);
+
+void snd_seq_oss_synth_reset(struct seq_oss_devinfo *dp, int dev);
+int snd_seq_oss_synth_load_patch(struct seq_oss_devinfo *dp, int dev, int fmt,
+ const char __user *buf, int p, int c);
+struct seq_oss_synthinfo *snd_seq_oss_synth_info(struct seq_oss_devinfo *dp,
+ int dev);
+int snd_seq_oss_synth_sysex(struct seq_oss_devinfo *dp, int dev, unsigned char *buf,
+ struct snd_seq_event *ev);
+int snd_seq_oss_synth_addr(struct seq_oss_devinfo *dp, int dev, struct snd_seq_event *ev);
+int snd_seq_oss_synth_ioctl(struct seq_oss_devinfo *dp, int dev, unsigned int cmd,
+ unsigned long addr);
+int snd_seq_oss_synth_raw_event(struct seq_oss_devinfo *dp, int dev,
+ unsigned char *data, struct snd_seq_event *ev);
+
+int snd_seq_oss_synth_make_info(struct seq_oss_devinfo *dp, int dev, struct synth_info *inf);
+
+#endif
diff --git a/sound/core/seq/oss/seq_oss_timer.c b/sound/core/seq/oss/seq_oss_timer.c
new file mode 100644
index 000000000..f9f57232a
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_timer.c
@@ -0,0 +1,264 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * OSS compatible sequencer driver
+ *
+ * Timer control routines
+ *
+ * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include "seq_oss_timer.h"
+#include "seq_oss_event.h"
+#include <sound/seq_oss_legacy.h>
+#include <linux/slab.h>
+
+/*
+ */
+#define MIN_OSS_TEMPO 8
+#define MAX_OSS_TEMPO 360
+#define MIN_OSS_TIMEBASE 1
+#define MAX_OSS_TIMEBASE 1000
+
+/*
+ */
+static void calc_alsa_tempo(struct seq_oss_timer *timer);
+static int send_timer_event(struct seq_oss_devinfo *dp, int type, int value);
+
+
+/*
+ * create and register a new timer.
+ * if queue is not started yet, start it.
+ */
+struct seq_oss_timer *
+snd_seq_oss_timer_new(struct seq_oss_devinfo *dp)
+{
+ struct seq_oss_timer *rec;
+
+ rec = kzalloc(sizeof(*rec), GFP_KERNEL);
+ if (rec == NULL)
+ return NULL;
+
+ rec->dp = dp;
+ rec->cur_tick = 0;
+ rec->realtime = 0;
+ rec->running = 0;
+ rec->oss_tempo = 60;
+ rec->oss_timebase = 100;
+ calc_alsa_tempo(rec);
+
+ return rec;
+}
+
+
+/*
+ * delete timer.
+ * if no more timer exists, stop the queue.
+ */
+void
+snd_seq_oss_timer_delete(struct seq_oss_timer *rec)
+{
+ if (rec) {
+ snd_seq_oss_timer_stop(rec);
+ kfree(rec);
+ }
+}
+
+
+/*
+ * process one timing event
+ * return 1 : event proceseed -- skip this event
+ * 0 : not a timer event -- enqueue this event
+ */
+int
+snd_seq_oss_process_timer_event(struct seq_oss_timer *rec, union evrec *ev)
+{
+ abstime_t parm = ev->t.time;
+
+ if (ev->t.code == EV_TIMING) {
+ switch (ev->t.cmd) {
+ case TMR_WAIT_REL:
+ parm += rec->cur_tick;
+ rec->realtime = 0;
+ fallthrough;
+ case TMR_WAIT_ABS:
+ if (parm == 0) {
+ rec->realtime = 1;
+ } else if (parm >= rec->cur_tick) {
+ rec->realtime = 0;
+ rec->cur_tick = parm;
+ }
+ return 1; /* skip this event */
+
+ case TMR_START:
+ snd_seq_oss_timer_start(rec);
+ return 1;
+
+ }
+ } else if (ev->s.code == SEQ_WAIT) {
+ /* time = from 1 to 3 bytes */
+ parm = (ev->echo >> 8) & 0xffffff;
+ if (parm > rec->cur_tick) {
+ /* set next event time */
+ rec->cur_tick = parm;
+ rec->realtime = 0;
+ }
+ return 1;
+ }
+
+ return 0;
+}
+
+
+/*
+ * convert tempo units
+ */
+static void
+calc_alsa_tempo(struct seq_oss_timer *timer)
+{
+ timer->tempo = (60 * 1000000) / timer->oss_tempo;
+ timer->ppq = timer->oss_timebase;
+}
+
+
+/*
+ * dispatch a timer event
+ */
+static int
+send_timer_event(struct seq_oss_devinfo *dp, int type, int value)
+{
+ struct snd_seq_event ev;
+
+ memset(&ev, 0, sizeof(ev));
+ ev.type = type;
+ ev.source.client = dp->cseq;
+ ev.source.port = 0;
+ ev.dest.client = SNDRV_SEQ_CLIENT_SYSTEM;
+ ev.dest.port = SNDRV_SEQ_PORT_SYSTEM_TIMER;
+ ev.queue = dp->queue;
+ ev.data.queue.queue = dp->queue;
+ ev.data.queue.param.value = value;
+ return snd_seq_kernel_client_dispatch(dp->cseq, &ev, 1, 0);
+}
+
+/*
+ * set queue tempo and start queue
+ */
+int
+snd_seq_oss_timer_start(struct seq_oss_timer *timer)
+{
+ struct seq_oss_devinfo *dp = timer->dp;
+ struct snd_seq_queue_tempo tmprec;
+
+ if (timer->running)
+ snd_seq_oss_timer_stop(timer);
+
+ memset(&tmprec, 0, sizeof(tmprec));
+ tmprec.queue = dp->queue;
+ tmprec.ppq = timer->ppq;
+ tmprec.tempo = timer->tempo;
+ snd_seq_set_queue_tempo(dp->cseq, &tmprec);
+
+ send_timer_event(dp, SNDRV_SEQ_EVENT_START, 0);
+ timer->running = 1;
+ timer->cur_tick = 0;
+ return 0;
+}
+
+
+/*
+ * stop queue
+ */
+int
+snd_seq_oss_timer_stop(struct seq_oss_timer *timer)
+{
+ if (! timer->running)
+ return 0;
+ send_timer_event(timer->dp, SNDRV_SEQ_EVENT_STOP, 0);
+ timer->running = 0;
+ return 0;
+}
+
+
+/*
+ * continue queue
+ */
+int
+snd_seq_oss_timer_continue(struct seq_oss_timer *timer)
+{
+ if (timer->running)
+ return 0;
+ send_timer_event(timer->dp, SNDRV_SEQ_EVENT_CONTINUE, 0);
+ timer->running = 1;
+ return 0;
+}
+
+
+/*
+ * change queue tempo
+ */
+int
+snd_seq_oss_timer_tempo(struct seq_oss_timer *timer, int value)
+{
+ if (value < MIN_OSS_TEMPO)
+ value = MIN_OSS_TEMPO;
+ else if (value > MAX_OSS_TEMPO)
+ value = MAX_OSS_TEMPO;
+ timer->oss_tempo = value;
+ calc_alsa_tempo(timer);
+ if (timer->running)
+ send_timer_event(timer->dp, SNDRV_SEQ_EVENT_TEMPO, timer->tempo);
+ return 0;
+}
+
+
+/*
+ * ioctls
+ */
+int
+snd_seq_oss_timer_ioctl(struct seq_oss_timer *timer, unsigned int cmd, int __user *arg)
+{
+ int value;
+
+ if (cmd == SNDCTL_SEQ_CTRLRATE) {
+ /* if *arg == 0, just return the current rate */
+ if (get_user(value, arg))
+ return -EFAULT;
+ if (value)
+ return -EINVAL;
+ value = ((timer->oss_tempo * timer->oss_timebase) + 30) / 60;
+ return put_user(value, arg) ? -EFAULT : 0;
+ }
+
+ if (timer->dp->seq_mode == SNDRV_SEQ_OSS_MODE_SYNTH)
+ return 0;
+
+ switch (cmd) {
+ case SNDCTL_TMR_START:
+ return snd_seq_oss_timer_start(timer);
+ case SNDCTL_TMR_STOP:
+ return snd_seq_oss_timer_stop(timer);
+ case SNDCTL_TMR_CONTINUE:
+ return snd_seq_oss_timer_continue(timer);
+ case SNDCTL_TMR_TEMPO:
+ if (get_user(value, arg))
+ return -EFAULT;
+ return snd_seq_oss_timer_tempo(timer, value);
+ case SNDCTL_TMR_TIMEBASE:
+ if (get_user(value, arg))
+ return -EFAULT;
+ if (value < MIN_OSS_TIMEBASE)
+ value = MIN_OSS_TIMEBASE;
+ else if (value > MAX_OSS_TIMEBASE)
+ value = MAX_OSS_TIMEBASE;
+ timer->oss_timebase = value;
+ calc_alsa_tempo(timer);
+ return 0;
+
+ case SNDCTL_TMR_METRONOME:
+ case SNDCTL_TMR_SELECT:
+ case SNDCTL_TMR_SOURCE:
+ /* not supported */
+ return 0;
+ }
+ return 0;
+}
diff --git a/sound/core/seq/oss/seq_oss_timer.h b/sound/core/seq/oss/seq_oss_timer.h
new file mode 100644
index 000000000..dee190b4e
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_timer.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * OSS compatible sequencer driver
+ * timer handling routines
+ *
+ * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
+ */
+
+#ifndef __SEQ_OSS_TIMER_H
+#define __SEQ_OSS_TIMER_H
+
+#include "seq_oss_device.h"
+
+/*
+ * timer information definition
+ */
+struct seq_oss_timer {
+ struct seq_oss_devinfo *dp;
+ reltime_t cur_tick;
+ int realtime;
+ int running;
+ int tempo, ppq; /* ALSA queue */
+ int oss_tempo, oss_timebase;
+};
+
+
+struct seq_oss_timer *snd_seq_oss_timer_new(struct seq_oss_devinfo *dp);
+void snd_seq_oss_timer_delete(struct seq_oss_timer *dp);
+
+int snd_seq_oss_timer_start(struct seq_oss_timer *timer);
+int snd_seq_oss_timer_stop(struct seq_oss_timer *timer);
+int snd_seq_oss_timer_continue(struct seq_oss_timer *timer);
+int snd_seq_oss_timer_tempo(struct seq_oss_timer *timer, int value);
+#define snd_seq_oss_timer_reset snd_seq_oss_timer_start
+
+int snd_seq_oss_timer_ioctl(struct seq_oss_timer *timer, unsigned int cmd, int __user *arg);
+
+/*
+ * get current processed time
+ */
+static inline abstime_t
+snd_seq_oss_timer_cur_tick(struct seq_oss_timer *timer)
+{
+ return timer->cur_tick;
+}
+
+#endif
diff --git a/sound/core/seq/oss/seq_oss_writeq.c b/sound/core/seq/oss/seq_oss_writeq.c
new file mode 100644
index 000000000..3e3209ce5
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_writeq.c
@@ -0,0 +1,160 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * OSS compatible sequencer driver
+ *
+ * seq_oss_writeq.c - write queue and sync
+ *
+ * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include "seq_oss_writeq.h"
+#include "seq_oss_event.h"
+#include "seq_oss_timer.h"
+#include <sound/seq_oss_legacy.h>
+#include "../seq_lock.h"
+#include "../seq_clientmgr.h"
+#include <linux/wait.h>
+#include <linux/slab.h>
+#include <linux/sched/signal.h>
+
+
+/*
+ * create a write queue record
+ */
+struct seq_oss_writeq *
+snd_seq_oss_writeq_new(struct seq_oss_devinfo *dp, int maxlen)
+{
+ struct seq_oss_writeq *q;
+ struct snd_seq_client_pool pool;
+
+ q = kzalloc(sizeof(*q), GFP_KERNEL);
+ if (!q)
+ return NULL;
+ q->dp = dp;
+ q->maxlen = maxlen;
+ spin_lock_init(&q->sync_lock);
+ q->sync_event_put = 0;
+ q->sync_time = 0;
+ init_waitqueue_head(&q->sync_sleep);
+
+ memset(&pool, 0, sizeof(pool));
+ pool.client = dp->cseq;
+ pool.output_pool = maxlen;
+ pool.output_room = maxlen / 2;
+
+ snd_seq_oss_control(dp, SNDRV_SEQ_IOCTL_SET_CLIENT_POOL, &pool);
+
+ return q;
+}
+
+/*
+ * delete the write queue
+ */
+void
+snd_seq_oss_writeq_delete(struct seq_oss_writeq *q)
+{
+ if (q) {
+ snd_seq_oss_writeq_clear(q); /* to be sure */
+ kfree(q);
+ }
+}
+
+
+/*
+ * reset the write queue
+ */
+void
+snd_seq_oss_writeq_clear(struct seq_oss_writeq *q)
+{
+ struct snd_seq_remove_events reset;
+
+ memset(&reset, 0, sizeof(reset));
+ reset.remove_mode = SNDRV_SEQ_REMOVE_OUTPUT; /* remove all */
+ snd_seq_oss_control(q->dp, SNDRV_SEQ_IOCTL_REMOVE_EVENTS, &reset);
+
+ /* wake up sleepers if any */
+ snd_seq_oss_writeq_wakeup(q, 0);
+}
+
+/*
+ * wait until the write buffer has enough room
+ */
+int
+snd_seq_oss_writeq_sync(struct seq_oss_writeq *q)
+{
+ struct seq_oss_devinfo *dp = q->dp;
+ abstime_t time;
+
+ time = snd_seq_oss_timer_cur_tick(dp->timer);
+ if (q->sync_time >= time)
+ return 0; /* already finished */
+
+ if (! q->sync_event_put) {
+ struct snd_seq_event ev;
+ union evrec *rec;
+
+ /* put echoback event */
+ memset(&ev, 0, sizeof(ev));
+ ev.flags = 0;
+ ev.type = SNDRV_SEQ_EVENT_ECHO;
+ ev.time.tick = time;
+ /* echo back to itself */
+ snd_seq_oss_fill_addr(dp, &ev, dp->addr.client, dp->addr.port);
+ rec = (union evrec *)&ev.data;
+ rec->t.code = SEQ_SYNCTIMER;
+ rec->t.time = time;
+ q->sync_event_put = 1;
+ snd_seq_kernel_client_enqueue(dp->cseq, &ev, NULL, true);
+ }
+
+ wait_event_interruptible_timeout(q->sync_sleep, ! q->sync_event_put, HZ);
+ if (signal_pending(current))
+ /* interrupted - return 0 to finish sync */
+ q->sync_event_put = 0;
+ if (! q->sync_event_put || q->sync_time >= time)
+ return 0;
+ return 1;
+}
+
+/*
+ * wake up sync - echo event was catched
+ */
+void
+snd_seq_oss_writeq_wakeup(struct seq_oss_writeq *q, abstime_t time)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&q->sync_lock, flags);
+ q->sync_time = time;
+ q->sync_event_put = 0;
+ wake_up(&q->sync_sleep);
+ spin_unlock_irqrestore(&q->sync_lock, flags);
+}
+
+
+/*
+ * return the unused pool size
+ */
+int
+snd_seq_oss_writeq_get_free_size(struct seq_oss_writeq *q)
+{
+ struct snd_seq_client_pool pool;
+ pool.client = q->dp->cseq;
+ snd_seq_oss_control(q->dp, SNDRV_SEQ_IOCTL_GET_CLIENT_POOL, &pool);
+ return pool.output_free;
+}
+
+
+/*
+ * set output threshold size from ioctl
+ */
+void
+snd_seq_oss_writeq_set_output(struct seq_oss_writeq *q, int val)
+{
+ struct snd_seq_client_pool pool;
+ pool.client = q->dp->cseq;
+ snd_seq_oss_control(q->dp, SNDRV_SEQ_IOCTL_GET_CLIENT_POOL, &pool);
+ pool.output_room = val;
+ snd_seq_oss_control(q->dp, SNDRV_SEQ_IOCTL_SET_CLIENT_POOL, &pool);
+}
+
diff --git a/sound/core/seq/oss/seq_oss_writeq.h b/sound/core/seq/oss/seq_oss_writeq.h
new file mode 100644
index 000000000..490d27a7b
--- /dev/null
+++ b/sound/core/seq/oss/seq_oss_writeq.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * OSS compatible sequencer driver
+ * write priority queue
+ *
+ * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
+ */
+
+#ifndef __SEQ_OSS_WRITEQ_H
+#define __SEQ_OSS_WRITEQ_H
+
+#include "seq_oss_device.h"
+
+
+struct seq_oss_writeq {
+ struct seq_oss_devinfo *dp;
+ int maxlen;
+ abstime_t sync_time;
+ int sync_event_put;
+ wait_queue_head_t sync_sleep;
+ spinlock_t sync_lock;
+};
+
+
+/*
+ * seq_oss_writeq.c
+ */
+struct seq_oss_writeq *snd_seq_oss_writeq_new(struct seq_oss_devinfo *dp, int maxlen);
+void snd_seq_oss_writeq_delete(struct seq_oss_writeq *q);
+void snd_seq_oss_writeq_clear(struct seq_oss_writeq *q);
+int snd_seq_oss_writeq_sync(struct seq_oss_writeq *q);
+void snd_seq_oss_writeq_wakeup(struct seq_oss_writeq *q, abstime_t time);
+int snd_seq_oss_writeq_get_free_size(struct seq_oss_writeq *q);
+void snd_seq_oss_writeq_set_output(struct seq_oss_writeq *q, int size);
+
+
+#endif