diff options
Diffstat (limited to 'sound/core/seq')
47 files changed, 13639 insertions, 0 deletions
diff --git a/sound/core/seq/Kconfig b/sound/core/seq/Kconfig new file mode 100644 index 000000000..f84718a44 --- /dev/null +++ b/sound/core/seq/Kconfig @@ -0,0 +1,63 @@ +# SPDX-License-Identifier: GPL-2.0-only +config SND_SEQUENCER + tristate "Sequencer support" + select SND_TIMER + select SND_SEQ_DEVICE + help + Say Y or M to enable MIDI sequencer and router support. This + feature allows routing and enqueueing of MIDI events. Events + can be processed at a given time. + + Many programs require this feature, so you should enable it + unless you know what you're doing. + +if SND_SEQUENCER + +config SND_SEQ_DUMMY + tristate "Sequencer dummy client" + help + Say Y here to enable the dummy sequencer client. This client + is a simple MIDI-through client: all normal input events are + redirected to the output port immediately. + + You don't need this unless you want to connect many MIDI + devices or applications together. + + To compile this driver as a module, choose M here: the module + will be called snd-seq-dummy. + +config SND_SEQUENCER_OSS + tristate "OSS Sequencer API" + depends on SND_OSSEMUL + select SND_SEQ_MIDI_EVENT + help + Say Y here to enable OSS sequencer emulation (both + /dev/sequencer and /dev/music interfaces). + + Many programs still use the OSS API, so say Y. + + To compile this driver as a module, choose M here: the module + will be called snd-seq-oss. + +config SND_SEQ_HRTIMER_DEFAULT + bool "Use HR-timer as default sequencer timer" + depends on SND_HRTIMER + default y + help + Say Y here to use the HR-timer backend as the default sequencer + timer. + +config SND_SEQ_MIDI_EVENT + tristate + +config SND_SEQ_MIDI + def_tristate SND_RAWMIDI + select SND_SEQ_MIDI_EVENT + +config SND_SEQ_MIDI_EMUL + tristate + +config SND_SEQ_VIRMIDI + tristate + +endif # SND_SEQUENCER diff --git a/sound/core/seq/Makefile b/sound/core/seq/Makefile new file mode 100644 index 000000000..3a2177a7e --- /dev/null +++ b/sound/core/seq/Makefile @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for ALSA +# Copyright (c) 1999 by Jaroslav Kysela <perex@perex.cz> +# + +snd-seq-objs := seq.o seq_lock.o seq_clientmgr.o seq_memory.o seq_queue.o \ + seq_fifo.o seq_prioq.o seq_timer.o \ + seq_system.o seq_ports.o +snd-seq-$(CONFIG_SND_PROC_FS) += seq_info.o +snd-seq-midi-objs := seq_midi.o +snd-seq-midi-emul-objs := seq_midi_emul.o +snd-seq-midi-event-objs := seq_midi_event.o +snd-seq-dummy-objs := seq_dummy.o +snd-seq-virmidi-objs := seq_virmidi.o + +obj-$(CONFIG_SND_SEQUENCER) += snd-seq.o +obj-$(CONFIG_SND_SEQUENCER_OSS) += oss/ + +obj-$(CONFIG_SND_SEQ_DUMMY) += snd-seq-dummy.o +obj-$(CONFIG_SND_SEQ_MIDI) += snd-seq-midi.o +obj-$(CONFIG_SND_SEQ_MIDI_EMUL) += snd-seq-midi-emul.o +obj-$(CONFIG_SND_SEQ_MIDI_EVENT) += snd-seq-midi-event.o +obj-$(CONFIG_SND_SEQ_VIRMIDI) += snd-seq-virmidi.o 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(®ister_mutex); + rc = snd_seq_oss_open(file, level); + mutex_unlock(®ister_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(®ister_mutex); + snd_seq_oss_release(dp); + mutex_unlock(®ister_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(®ister_mutex)) + return -ERESTARTSYS; + rc = snd_seq_oss_ioctl(dp, cmd, arg); + if (cmd != SNDCTL_SEQ_SYNC) + mutex_unlock(®ister_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(®ister_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(®ister_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(®ister_mutex); + return rc; + } + mutex_unlock(®ister_mutex); + return 0; +} + +static void +unregister_device(void) +{ + mutex_lock(®ister_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(®ister_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(®ister_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(®ister_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(®ister_lock, flags); + mdev = midi_devs[dev]; + if (mdev) + snd_use_lock_use(&mdev->use_lock); + spin_unlock_irqrestore(®ister_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(®ister_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(®ister_lock, flags); + return mdev; + } + } + spin_unlock_irqrestore(®ister_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(®ister_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(®ister_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(®ister_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(®ister_lock, flags); + midi_devs[mdev->seq_device] = NULL; + spin_unlock_irqrestore(®ister_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(®ister_lock, flags); + for (index = max_midi_devs - 1; index >= 0; index--) { + if (midi_devs[index]) + break; + } + max_midi_devs = index + 1; + spin_unlock_irqrestore(®ister_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(®ister_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(®ister_lock, flags); +} + + +/* + * set up midi tables + */ +void +snd_seq_oss_midi_setup(struct seq_oss_devinfo *dp) +{ + spin_lock_irq(®ister_lock); + dp->max_mididev = max_midi_devs; + spin_unlock_irq(®ister_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(®ister_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(®ister_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(®ister_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(®ister_lock, flags); + for (index = 0; index < max_synth_devs; index++) { + if (synth_devs[index] == rec) + break; + } + if (index >= max_synth_devs) { + spin_unlock_irqrestore(®ister_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(®ister_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(®ister_lock, flags); + rec = synth_devs[dev]; + if (rec) + snd_use_lock_use(&rec->use_lock); + spin_unlock_irqrestore(®ister_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 diff --git a/sound/core/seq/seq.c b/sound/core/seq/seq.c new file mode 100644 index 000000000..00f7342ee --- /dev/null +++ b/sound/core/seq/seq.c @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA sequencer main module + * Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl> + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <sound/core.h> +#include <sound/initval.h> + +#include <sound/seq_kernel.h> +#include "seq_clientmgr.h" +#include "seq_memory.h" +#include "seq_queue.h" +#include "seq_lock.h" +#include "seq_timer.h" +#include "seq_system.h" +#include "seq_info.h" +#include <sound/minors.h> +#include <sound/seq_device.h> + +#if defined(CONFIG_SND_SEQ_DUMMY_MODULE) +int seq_client_load[15] = {[0] = SNDRV_SEQ_CLIENT_DUMMY, [1 ... 14] = -1}; +#else +int seq_client_load[15] = {[0 ... 14] = -1}; +#endif +int seq_default_timer_class = SNDRV_TIMER_CLASS_GLOBAL; +int seq_default_timer_sclass = SNDRV_TIMER_SCLASS_NONE; +int seq_default_timer_card = -1; +int seq_default_timer_device = +#ifdef CONFIG_SND_SEQ_HRTIMER_DEFAULT + SNDRV_TIMER_GLOBAL_HRTIMER +#else + SNDRV_TIMER_GLOBAL_SYSTEM +#endif + ; +int seq_default_timer_subdevice = 0; +int seq_default_timer_resolution = 0; /* Hz */ + +MODULE_AUTHOR("Frank van de Pol <fvdpol@coil.demon.nl>, Jaroslav Kysela <perex@perex.cz>"); +MODULE_DESCRIPTION("Advanced Linux Sound Architecture sequencer."); +MODULE_LICENSE("GPL"); + +module_param_array(seq_client_load, int, NULL, 0444); +MODULE_PARM_DESC(seq_client_load, "The numbers of global (system) clients to load through kmod."); +module_param(seq_default_timer_class, int, 0644); +MODULE_PARM_DESC(seq_default_timer_class, "The default timer class."); +module_param(seq_default_timer_sclass, int, 0644); +MODULE_PARM_DESC(seq_default_timer_sclass, "The default timer slave class."); +module_param(seq_default_timer_card, int, 0644); +MODULE_PARM_DESC(seq_default_timer_card, "The default timer card number."); +module_param(seq_default_timer_device, int, 0644); +MODULE_PARM_DESC(seq_default_timer_device, "The default timer device number."); +module_param(seq_default_timer_subdevice, int, 0644); +MODULE_PARM_DESC(seq_default_timer_subdevice, "The default timer subdevice number."); +module_param(seq_default_timer_resolution, int, 0644); +MODULE_PARM_DESC(seq_default_timer_resolution, "The default timer resolution in Hz."); + +MODULE_ALIAS_CHARDEV(CONFIG_SND_MAJOR, SNDRV_MINOR_SEQUENCER); +MODULE_ALIAS("devname:snd/seq"); + +/* + * INIT PART + */ + +static int __init alsa_seq_init(void) +{ + int err; + + err = client_init_data(); + if (err < 0) + goto error; + + /* register sequencer device */ + err = snd_sequencer_device_init(); + if (err < 0) + goto error; + + /* register proc interface */ + err = snd_seq_info_init(); + if (err < 0) + goto error_device; + + /* register our internal client */ + err = snd_seq_system_client_init(); + if (err < 0) + goto error_info; + + snd_seq_autoload_init(); + return 0; + + error_info: + snd_seq_info_done(); + error_device: + snd_sequencer_device_done(); + error: + return err; +} + +static void __exit alsa_seq_exit(void) +{ + /* unregister our internal client */ + snd_seq_system_client_done(); + + /* unregister proc interface */ + snd_seq_info_done(); + + /* delete timing queues */ + snd_seq_queues_delete(); + + /* unregister sequencer device */ + snd_sequencer_device_done(); + + snd_seq_autoload_exit(); +} + +module_init(alsa_seq_init) +module_exit(alsa_seq_exit) diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c new file mode 100644 index 000000000..2d707afa1 --- /dev/null +++ b/sound/core/seq/seq_clientmgr.c @@ -0,0 +1,2552 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA sequencer Client Manager + * Copyright (c) 1998-2001 by Frank van de Pol <fvdpol@coil.demon.nl> + * Jaroslav Kysela <perex@perex.cz> + * Takashi Iwai <tiwai@suse.de> + */ + +#include <linux/init.h> +#include <linux/export.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/minors.h> +#include <linux/kmod.h> + +#include <sound/seq_kernel.h> +#include "seq_clientmgr.h" +#include "seq_memory.h" +#include "seq_queue.h" +#include "seq_timer.h" +#include "seq_info.h" +#include "seq_system.h" +#include <sound/seq_device.h> +#ifdef CONFIG_COMPAT +#include <linux/compat.h> +#endif + +/* Client Manager + + * this module handles the connections of userland and kernel clients + * + */ + +/* + * There are four ranges of client numbers (last two shared): + * 0..15: global clients + * 16..127: statically allocated client numbers for cards 0..27 + * 128..191: dynamically allocated client numbers for cards 28..31 + * 128..191: dynamically allocated client numbers for applications + */ + +/* number of kernel non-card clients */ +#define SNDRV_SEQ_GLOBAL_CLIENTS 16 +/* clients per cards, for static clients */ +#define SNDRV_SEQ_CLIENTS_PER_CARD 4 +/* dynamically allocated client numbers (both kernel drivers and user space) */ +#define SNDRV_SEQ_DYNAMIC_CLIENTS_BEGIN 128 + +#define SNDRV_SEQ_LFLG_INPUT 0x0001 +#define SNDRV_SEQ_LFLG_OUTPUT 0x0002 +#define SNDRV_SEQ_LFLG_OPEN (SNDRV_SEQ_LFLG_INPUT|SNDRV_SEQ_LFLG_OUTPUT) + +static DEFINE_SPINLOCK(clients_lock); +static DEFINE_MUTEX(register_mutex); + +/* + * client table + */ +static char clienttablock[SNDRV_SEQ_MAX_CLIENTS]; +static struct snd_seq_client *clienttab[SNDRV_SEQ_MAX_CLIENTS]; +static struct snd_seq_usage client_usage; + +/* + * prototypes + */ +static int bounce_error_event(struct snd_seq_client *client, + struct snd_seq_event *event, + int err, int atomic, int hop); +static int snd_seq_deliver_single_event(struct snd_seq_client *client, + struct snd_seq_event *event, + int filter, int atomic, int hop); + +/* + */ +static inline unsigned short snd_seq_file_flags(struct file *file) +{ + switch (file->f_mode & (FMODE_READ | FMODE_WRITE)) { + case FMODE_WRITE: + return SNDRV_SEQ_LFLG_OUTPUT; + case FMODE_READ: + return SNDRV_SEQ_LFLG_INPUT; + default: + return SNDRV_SEQ_LFLG_OPEN; + } +} + +static inline int snd_seq_write_pool_allocated(struct snd_seq_client *client) +{ + return snd_seq_total_cells(client->pool) > 0; +} + +/* return pointer to client structure for specified id */ +static struct snd_seq_client *clientptr(int clientid) +{ + if (clientid < 0 || clientid >= SNDRV_SEQ_MAX_CLIENTS) { + pr_debug("ALSA: seq: oops. Trying to get pointer to client %d\n", + clientid); + return NULL; + } + return clienttab[clientid]; +} + +struct snd_seq_client *snd_seq_client_use_ptr(int clientid) +{ + unsigned long flags; + struct snd_seq_client *client; + + if (clientid < 0 || clientid >= SNDRV_SEQ_MAX_CLIENTS) { + pr_debug("ALSA: seq: oops. Trying to get pointer to client %d\n", + clientid); + return NULL; + } + spin_lock_irqsave(&clients_lock, flags); + client = clientptr(clientid); + if (client) + goto __lock; + if (clienttablock[clientid]) { + spin_unlock_irqrestore(&clients_lock, flags); + return NULL; + } + spin_unlock_irqrestore(&clients_lock, flags); +#ifdef CONFIG_MODULES + if (!in_interrupt()) { + static DECLARE_BITMAP(client_requested, SNDRV_SEQ_GLOBAL_CLIENTS); + static DECLARE_BITMAP(card_requested, SNDRV_CARDS); + + if (clientid < SNDRV_SEQ_GLOBAL_CLIENTS) { + int idx; + + if (!test_and_set_bit(clientid, client_requested)) { + for (idx = 0; idx < 15; idx++) { + if (seq_client_load[idx] < 0) + break; + if (seq_client_load[idx] == clientid) { + request_module("snd-seq-client-%i", + clientid); + break; + } + } + } + } else if (clientid < SNDRV_SEQ_DYNAMIC_CLIENTS_BEGIN) { + int card = (clientid - SNDRV_SEQ_GLOBAL_CLIENTS) / + SNDRV_SEQ_CLIENTS_PER_CARD; + if (card < snd_ecards_limit) { + if (!test_and_set_bit(card, card_requested)) + snd_request_card(card); + snd_seq_device_load_drivers(); + } + } + spin_lock_irqsave(&clients_lock, flags); + client = clientptr(clientid); + if (client) + goto __lock; + spin_unlock_irqrestore(&clients_lock, flags); + } +#endif + return NULL; + + __lock: + snd_use_lock_use(&client->use_lock); + spin_unlock_irqrestore(&clients_lock, flags); + return client; +} + +/* Take refcount and perform ioctl_mutex lock on the given client; + * used only for OSS sequencer + * Unlock via snd_seq_client_ioctl_unlock() below + */ +bool snd_seq_client_ioctl_lock(int clientid) +{ + struct snd_seq_client *client; + + client = snd_seq_client_use_ptr(clientid); + if (!client) + return false; + mutex_lock(&client->ioctl_mutex); + /* The client isn't unrefed here; see snd_seq_client_ioctl_unlock() */ + return true; +} +EXPORT_SYMBOL_GPL(snd_seq_client_ioctl_lock); + +/* Unlock and unref the given client; for OSS sequencer use only */ +void snd_seq_client_ioctl_unlock(int clientid) +{ + struct snd_seq_client *client; + + client = snd_seq_client_use_ptr(clientid); + if (WARN_ON(!client)) + return; + mutex_unlock(&client->ioctl_mutex); + /* The doubly unrefs below are intentional; the first one releases the + * leftover from snd_seq_client_ioctl_lock() above, and the second one + * is for releasing snd_seq_client_use_ptr() in this function + */ + snd_seq_client_unlock(client); + snd_seq_client_unlock(client); +} +EXPORT_SYMBOL_GPL(snd_seq_client_ioctl_unlock); + +static void usage_alloc(struct snd_seq_usage *res, int num) +{ + res->cur += num; + if (res->cur > res->peak) + res->peak = res->cur; +} + +static void usage_free(struct snd_seq_usage *res, int num) +{ + res->cur -= num; +} + +/* initialise data structures */ +int __init client_init_data(void) +{ + /* zap out the client table */ + memset(&clienttablock, 0, sizeof(clienttablock)); + memset(&clienttab, 0, sizeof(clienttab)); + return 0; +} + + +static struct snd_seq_client *seq_create_client1(int client_index, int poolsize) +{ + int c; + struct snd_seq_client *client; + + /* init client data */ + client = kzalloc(sizeof(*client), GFP_KERNEL); + if (client == NULL) + return NULL; + client->pool = snd_seq_pool_new(poolsize); + if (client->pool == NULL) { + kfree(client); + return NULL; + } + client->type = NO_CLIENT; + snd_use_lock_init(&client->use_lock); + rwlock_init(&client->ports_lock); + mutex_init(&client->ports_mutex); + INIT_LIST_HEAD(&client->ports_list_head); + mutex_init(&client->ioctl_mutex); + + /* find free slot in the client table */ + spin_lock_irq(&clients_lock); + if (client_index < 0) { + for (c = SNDRV_SEQ_DYNAMIC_CLIENTS_BEGIN; + c < SNDRV_SEQ_MAX_CLIENTS; + c++) { + if (clienttab[c] || clienttablock[c]) + continue; + clienttab[client->number = c] = client; + spin_unlock_irq(&clients_lock); + return client; + } + } else { + if (clienttab[client_index] == NULL && !clienttablock[client_index]) { + clienttab[client->number = client_index] = client; + spin_unlock_irq(&clients_lock); + return client; + } + } + spin_unlock_irq(&clients_lock); + snd_seq_pool_delete(&client->pool); + kfree(client); + return NULL; /* no free slot found or busy, return failure code */ +} + + +static int seq_free_client1(struct snd_seq_client *client) +{ + if (!client) + return 0; + spin_lock_irq(&clients_lock); + clienttablock[client->number] = 1; + clienttab[client->number] = NULL; + spin_unlock_irq(&clients_lock); + snd_seq_delete_all_ports(client); + snd_seq_queue_client_leave(client->number); + snd_use_lock_sync(&client->use_lock); + if (client->pool) + snd_seq_pool_delete(&client->pool); + spin_lock_irq(&clients_lock); + clienttablock[client->number] = 0; + spin_unlock_irq(&clients_lock); + return 0; +} + + +static void seq_free_client(struct snd_seq_client * client) +{ + mutex_lock(®ister_mutex); + switch (client->type) { + case NO_CLIENT: + pr_warn("ALSA: seq: Trying to free unused client %d\n", + client->number); + break; + case USER_CLIENT: + case KERNEL_CLIENT: + seq_free_client1(client); + usage_free(&client_usage, 1); + break; + + default: + pr_err("ALSA: seq: Trying to free client %d with undefined type = %d\n", + client->number, client->type); + } + mutex_unlock(®ister_mutex); + + snd_seq_system_client_ev_client_exit(client->number); +} + + + +/* -------------------------------------------------------- */ + +/* create a user client */ +static int snd_seq_open(struct inode *inode, struct file *file) +{ + int c, mode; /* client id */ + struct snd_seq_client *client; + struct snd_seq_user_client *user; + int err; + + err = stream_open(inode, file); + if (err < 0) + return err; + + mutex_lock(®ister_mutex); + client = seq_create_client1(-1, SNDRV_SEQ_DEFAULT_EVENTS); + if (!client) { + mutex_unlock(®ister_mutex); + return -ENOMEM; /* failure code */ + } + + mode = snd_seq_file_flags(file); + if (mode & SNDRV_SEQ_LFLG_INPUT) + client->accept_input = 1; + if (mode & SNDRV_SEQ_LFLG_OUTPUT) + client->accept_output = 1; + + user = &client->data.user; + user->fifo = NULL; + user->fifo_pool_size = 0; + + if (mode & SNDRV_SEQ_LFLG_INPUT) { + user->fifo_pool_size = SNDRV_SEQ_DEFAULT_CLIENT_EVENTS; + user->fifo = snd_seq_fifo_new(user->fifo_pool_size); + if (user->fifo == NULL) { + seq_free_client1(client); + kfree(client); + mutex_unlock(®ister_mutex); + return -ENOMEM; + } + } + + usage_alloc(&client_usage, 1); + client->type = USER_CLIENT; + mutex_unlock(®ister_mutex); + + c = client->number; + file->private_data = client; + + /* fill client data */ + user->file = file; + sprintf(client->name, "Client-%d", c); + client->data.user.owner = get_pid(task_pid(current)); + + /* make others aware this new client */ + snd_seq_system_client_ev_client_start(c); + + return 0; +} + +/* delete a user client */ +static int snd_seq_release(struct inode *inode, struct file *file) +{ + struct snd_seq_client *client = file->private_data; + + if (client) { + seq_free_client(client); + if (client->data.user.fifo) + snd_seq_fifo_delete(&client->data.user.fifo); + put_pid(client->data.user.owner); + kfree(client); + } + + return 0; +} + + +/* handle client read() */ +/* possible error values: + * -ENXIO invalid client or file open mode + * -ENOSPC FIFO overflow (the flag is cleared after this error report) + * -EINVAL no enough user-space buffer to write the whole event + * -EFAULT seg. fault during copy to user space + */ +static ssize_t snd_seq_read(struct file *file, char __user *buf, size_t count, + loff_t *offset) +{ + struct snd_seq_client *client = file->private_data; + struct snd_seq_fifo *fifo; + int err; + long result = 0; + struct snd_seq_event_cell *cell; + + if (!(snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_INPUT)) + return -ENXIO; + + if (!access_ok(buf, count)) + return -EFAULT; + + /* check client structures are in place */ + if (snd_BUG_ON(!client)) + return -ENXIO; + + if (!client->accept_input) + return -ENXIO; + fifo = client->data.user.fifo; + if (!fifo) + return -ENXIO; + + if (atomic_read(&fifo->overflow) > 0) { + /* buffer overflow is detected */ + snd_seq_fifo_clear(fifo); + /* return error code */ + return -ENOSPC; + } + + cell = NULL; + err = 0; + snd_seq_fifo_lock(fifo); + + /* while data available in queue */ + while (count >= sizeof(struct snd_seq_event)) { + int nonblock; + + nonblock = (file->f_flags & O_NONBLOCK) || result > 0; + err = snd_seq_fifo_cell_out(fifo, &cell, nonblock); + if (err < 0) + break; + if (snd_seq_ev_is_variable(&cell->event)) { + struct snd_seq_event tmpev; + tmpev = cell->event; + tmpev.data.ext.len &= ~SNDRV_SEQ_EXT_MASK; + if (copy_to_user(buf, &tmpev, sizeof(struct snd_seq_event))) { + err = -EFAULT; + break; + } + count -= sizeof(struct snd_seq_event); + buf += sizeof(struct snd_seq_event); + err = snd_seq_expand_var_event(&cell->event, count, + (char __force *)buf, 0, + sizeof(struct snd_seq_event)); + if (err < 0) + break; + result += err; + count -= err; + buf += err; + } else { + if (copy_to_user(buf, &cell->event, sizeof(struct snd_seq_event))) { + err = -EFAULT; + break; + } + count -= sizeof(struct snd_seq_event); + buf += sizeof(struct snd_seq_event); + } + snd_seq_cell_free(cell); + cell = NULL; /* to be sure */ + result += sizeof(struct snd_seq_event); + } + + if (err < 0) { + if (cell) + snd_seq_fifo_cell_putback(fifo, cell); + if (err == -EAGAIN && result > 0) + err = 0; + } + snd_seq_fifo_unlock(fifo); + + return (err < 0) ? err : result; +} + + +/* + * check access permission to the port + */ +static int check_port_perm(struct snd_seq_client_port *port, unsigned int flags) +{ + if ((port->capability & flags) != flags) + return 0; + return flags; +} + +/* + * check if the destination client is available, and return the pointer + * if filter is non-zero, client filter bitmap is tested. + */ +static struct snd_seq_client *get_event_dest_client(struct snd_seq_event *event, + int filter) +{ + struct snd_seq_client *dest; + + dest = snd_seq_client_use_ptr(event->dest.client); + if (dest == NULL) + return NULL; + if (! dest->accept_input) + goto __not_avail; + if ((dest->filter & SNDRV_SEQ_FILTER_USE_EVENT) && + ! test_bit(event->type, dest->event_filter)) + goto __not_avail; + if (filter && !(dest->filter & filter)) + goto __not_avail; + + return dest; /* ok - accessible */ +__not_avail: + snd_seq_client_unlock(dest); + return NULL; +} + + +/* + * Return the error event. + * + * If the receiver client is a user client, the original event is + * encapsulated in SNDRV_SEQ_EVENT_BOUNCE as variable length event. If + * the original event is also variable length, the external data is + * copied after the event record. + * If the receiver client is a kernel client, the original event is + * quoted in SNDRV_SEQ_EVENT_KERNEL_ERROR, since this requires no extra + * kmalloc. + */ +static int bounce_error_event(struct snd_seq_client *client, + struct snd_seq_event *event, + int err, int atomic, int hop) +{ + struct snd_seq_event bounce_ev; + int result; + + if (client == NULL || + ! (client->filter & SNDRV_SEQ_FILTER_BOUNCE) || + ! client->accept_input) + return 0; /* ignored */ + + /* set up quoted error */ + memset(&bounce_ev, 0, sizeof(bounce_ev)); + bounce_ev.type = SNDRV_SEQ_EVENT_KERNEL_ERROR; + bounce_ev.flags = SNDRV_SEQ_EVENT_LENGTH_FIXED; + bounce_ev.queue = SNDRV_SEQ_QUEUE_DIRECT; + bounce_ev.source.client = SNDRV_SEQ_CLIENT_SYSTEM; + bounce_ev.source.port = SNDRV_SEQ_PORT_SYSTEM_ANNOUNCE; + bounce_ev.dest.client = client->number; + bounce_ev.dest.port = event->source.port; + bounce_ev.data.quote.origin = event->dest; + bounce_ev.data.quote.event = event; + bounce_ev.data.quote.value = -err; /* use positive value */ + result = snd_seq_deliver_single_event(NULL, &bounce_ev, 0, atomic, hop + 1); + if (result < 0) { + client->event_lost++; + return result; + } + + return result; +} + + +/* + * rewrite the time-stamp of the event record with the curren time + * of the given queue. + * return non-zero if updated. + */ +static int update_timestamp_of_queue(struct snd_seq_event *event, + int queue, int real_time) +{ + struct snd_seq_queue *q; + + q = queueptr(queue); + if (! q) + return 0; + event->queue = queue; + event->flags &= ~SNDRV_SEQ_TIME_STAMP_MASK; + if (real_time) { + event->time.time = snd_seq_timer_get_cur_time(q->timer, true); + event->flags |= SNDRV_SEQ_TIME_STAMP_REAL; + } else { + event->time.tick = snd_seq_timer_get_cur_tick(q->timer); + event->flags |= SNDRV_SEQ_TIME_STAMP_TICK; + } + queuefree(q); + return 1; +} + + +/* + * deliver an event to the specified destination. + * if filter is non-zero, client filter bitmap is tested. + * + * RETURN VALUE: 0 : if succeeded + * <0 : error + */ +static int snd_seq_deliver_single_event(struct snd_seq_client *client, + struct snd_seq_event *event, + int filter, int atomic, int hop) +{ + struct snd_seq_client *dest = NULL; + struct snd_seq_client_port *dest_port = NULL; + int result = -ENOENT; + int direct; + + direct = snd_seq_ev_is_direct(event); + + dest = get_event_dest_client(event, filter); + if (dest == NULL) + goto __skip; + dest_port = snd_seq_port_use_ptr(dest, event->dest.port); + if (dest_port == NULL) + goto __skip; + + /* check permission */ + if (! check_port_perm(dest_port, SNDRV_SEQ_PORT_CAP_WRITE)) { + result = -EPERM; + goto __skip; + } + + if (dest_port->timestamping) + update_timestamp_of_queue(event, dest_port->time_queue, + dest_port->time_real); + + switch (dest->type) { + case USER_CLIENT: + if (dest->data.user.fifo) + result = snd_seq_fifo_event_in(dest->data.user.fifo, event); + break; + + case KERNEL_CLIENT: + if (dest_port->event_input == NULL) + break; + result = dest_port->event_input(event, direct, + dest_port->private_data, + atomic, hop); + break; + default: + break; + } + + __skip: + if (dest_port) + snd_seq_port_unlock(dest_port); + if (dest) + snd_seq_client_unlock(dest); + + if (result < 0 && !direct) { + result = bounce_error_event(client, event, result, atomic, hop); + } + return result; +} + + +/* + * send the event to all subscribers: + */ +static int deliver_to_subscribers(struct snd_seq_client *client, + struct snd_seq_event *event, + int atomic, int hop) +{ + struct snd_seq_subscribers *subs; + int err, result = 0, num_ev = 0; + struct snd_seq_event event_saved; + struct snd_seq_client_port *src_port; + struct snd_seq_port_subs_info *grp; + + src_port = snd_seq_port_use_ptr(client, event->source.port); + if (src_port == NULL) + return -EINVAL; /* invalid source port */ + /* save original event record */ + event_saved = *event; + grp = &src_port->c_src; + + /* lock list */ + if (atomic) + read_lock(&grp->list_lock); + else + down_read_nested(&grp->list_mutex, hop); + list_for_each_entry(subs, &grp->list_head, src_list) { + /* both ports ready? */ + if (atomic_read(&subs->ref_count) != 2) + continue; + event->dest = subs->info.dest; + if (subs->info.flags & SNDRV_SEQ_PORT_SUBS_TIMESTAMP) + /* convert time according to flag with subscription */ + update_timestamp_of_queue(event, subs->info.queue, + subs->info.flags & SNDRV_SEQ_PORT_SUBS_TIME_REAL); + err = snd_seq_deliver_single_event(client, event, + 0, atomic, hop); + if (err < 0) { + /* save first error that occurs and continue */ + if (!result) + result = err; + continue; + } + num_ev++; + /* restore original event record */ + *event = event_saved; + } + if (atomic) + read_unlock(&grp->list_lock); + else + up_read(&grp->list_mutex); + *event = event_saved; /* restore */ + snd_seq_port_unlock(src_port); + return (result < 0) ? result : num_ev; +} + + +#ifdef SUPPORT_BROADCAST +/* + * broadcast to all ports: + */ +static int port_broadcast_event(struct snd_seq_client *client, + struct snd_seq_event *event, + int atomic, int hop) +{ + int num_ev = 0, err, result = 0; + struct snd_seq_client *dest_client; + struct snd_seq_client_port *port; + + dest_client = get_event_dest_client(event, SNDRV_SEQ_FILTER_BROADCAST); + if (dest_client == NULL) + return 0; /* no matching destination */ + + read_lock(&dest_client->ports_lock); + list_for_each_entry(port, &dest_client->ports_list_head, list) { + event->dest.port = port->addr.port; + /* pass NULL as source client to avoid error bounce */ + err = snd_seq_deliver_single_event(NULL, event, + SNDRV_SEQ_FILTER_BROADCAST, + atomic, hop); + if (err < 0) { + /* save first error that occurs and continue */ + if (!result) + result = err; + continue; + } + num_ev++; + } + read_unlock(&dest_client->ports_lock); + snd_seq_client_unlock(dest_client); + event->dest.port = SNDRV_SEQ_ADDRESS_BROADCAST; /* restore */ + return (result < 0) ? result : num_ev; +} + +/* + * send the event to all clients: + * if destination port is also ADDRESS_BROADCAST, deliver to all ports. + */ +static int broadcast_event(struct snd_seq_client *client, + struct snd_seq_event *event, int atomic, int hop) +{ + int err, result = 0, num_ev = 0; + int dest; + struct snd_seq_addr addr; + + addr = event->dest; /* save */ + + for (dest = 0; dest < SNDRV_SEQ_MAX_CLIENTS; dest++) { + /* don't send to itself */ + if (dest == client->number) + continue; + event->dest.client = dest; + event->dest.port = addr.port; + if (addr.port == SNDRV_SEQ_ADDRESS_BROADCAST) + err = port_broadcast_event(client, event, atomic, hop); + else + /* pass NULL as source client to avoid error bounce */ + err = snd_seq_deliver_single_event(NULL, event, + SNDRV_SEQ_FILTER_BROADCAST, + atomic, hop); + if (err < 0) { + /* save first error that occurs and continue */ + if (!result) + result = err; + continue; + } + num_ev += err; + } + event->dest = addr; /* restore */ + return (result < 0) ? result : num_ev; +} + + +/* multicast - not supported yet */ +static int multicast_event(struct snd_seq_client *client, struct snd_seq_event *event, + int atomic, int hop) +{ + pr_debug("ALSA: seq: multicast not supported yet.\n"); + return 0; /* ignored */ +} +#endif /* SUPPORT_BROADCAST */ + + +/* deliver an event to the destination port(s). + * if the event is to subscribers or broadcast, the event is dispatched + * to multiple targets. + * + * RETURN VALUE: n > 0 : the number of delivered events. + * n == 0 : the event was not passed to any client. + * n < 0 : error - event was not processed. + */ +static int snd_seq_deliver_event(struct snd_seq_client *client, struct snd_seq_event *event, + int atomic, int hop) +{ + int result; + + hop++; + if (hop >= SNDRV_SEQ_MAX_HOPS) { + pr_debug("ALSA: seq: too long delivery path (%d:%d->%d:%d)\n", + event->source.client, event->source.port, + event->dest.client, event->dest.port); + return -EMLINK; + } + + if (snd_seq_ev_is_variable(event) && + snd_BUG_ON(atomic && (event->data.ext.len & SNDRV_SEQ_EXT_USRPTR))) + return -EINVAL; + + if (event->queue == SNDRV_SEQ_ADDRESS_SUBSCRIBERS || + event->dest.client == SNDRV_SEQ_ADDRESS_SUBSCRIBERS) + result = deliver_to_subscribers(client, event, atomic, hop); +#ifdef SUPPORT_BROADCAST + else if (event->queue == SNDRV_SEQ_ADDRESS_BROADCAST || + event->dest.client == SNDRV_SEQ_ADDRESS_BROADCAST) + result = broadcast_event(client, event, atomic, hop); + else if (event->dest.client >= SNDRV_SEQ_MAX_CLIENTS) + result = multicast_event(client, event, atomic, hop); + else if (event->dest.port == SNDRV_SEQ_ADDRESS_BROADCAST) + result = port_broadcast_event(client, event, atomic, hop); +#endif + else + result = snd_seq_deliver_single_event(client, event, 0, atomic, hop); + + return result; +} + +/* + * dispatch an event cell: + * This function is called only from queue check routines in timer + * interrupts or after enqueued. + * The event cell shall be released or re-queued in this function. + * + * RETURN VALUE: n > 0 : the number of delivered events. + * n == 0 : the event was not passed to any client. + * n < 0 : error - event was not processed. + */ +int snd_seq_dispatch_event(struct snd_seq_event_cell *cell, int atomic, int hop) +{ + struct snd_seq_client *client; + int result; + + if (snd_BUG_ON(!cell)) + return -EINVAL; + + client = snd_seq_client_use_ptr(cell->event.source.client); + if (client == NULL) { + snd_seq_cell_free(cell); /* release this cell */ + return -EINVAL; + } + + if (cell->event.type == SNDRV_SEQ_EVENT_NOTE) { + /* NOTE event: + * the event cell is re-used as a NOTE-OFF event and + * enqueued again. + */ + struct snd_seq_event tmpev, *ev; + + /* reserve this event to enqueue note-off later */ + tmpev = cell->event; + tmpev.type = SNDRV_SEQ_EVENT_NOTEON; + result = snd_seq_deliver_event(client, &tmpev, atomic, hop); + + /* + * This was originally a note event. We now re-use the + * cell for the note-off event. + */ + + ev = &cell->event; + ev->type = SNDRV_SEQ_EVENT_NOTEOFF; + ev->flags |= SNDRV_SEQ_PRIORITY_HIGH; + + /* add the duration time */ + switch (ev->flags & SNDRV_SEQ_TIME_STAMP_MASK) { + case SNDRV_SEQ_TIME_STAMP_TICK: + ev->time.tick += ev->data.note.duration; + break; + case SNDRV_SEQ_TIME_STAMP_REAL: + /* unit for duration is ms */ + ev->time.time.tv_nsec += 1000000 * (ev->data.note.duration % 1000); + ev->time.time.tv_sec += ev->data.note.duration / 1000 + + ev->time.time.tv_nsec / 1000000000; + ev->time.time.tv_nsec %= 1000000000; + break; + } + ev->data.note.velocity = ev->data.note.off_velocity; + + /* Now queue this cell as the note off event */ + if (snd_seq_enqueue_event(cell, atomic, hop) < 0) + snd_seq_cell_free(cell); /* release this cell */ + + } else { + /* Normal events: + * event cell is freed after processing the event + */ + + result = snd_seq_deliver_event(client, &cell->event, atomic, hop); + snd_seq_cell_free(cell); + } + + snd_seq_client_unlock(client); + return result; +} + + +/* Allocate a cell from client pool and enqueue it to queue: + * if pool is empty and blocking is TRUE, sleep until a new cell is + * available. + */ +static int snd_seq_client_enqueue_event(struct snd_seq_client *client, + struct snd_seq_event *event, + struct file *file, int blocking, + int atomic, int hop, + struct mutex *mutexp) +{ + struct snd_seq_event_cell *cell; + int err; + + /* special queue values - force direct passing */ + if (event->queue == SNDRV_SEQ_ADDRESS_SUBSCRIBERS) { + event->dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS; + event->queue = SNDRV_SEQ_QUEUE_DIRECT; + } else +#ifdef SUPPORT_BROADCAST + if (event->queue == SNDRV_SEQ_ADDRESS_BROADCAST) { + event->dest.client = SNDRV_SEQ_ADDRESS_BROADCAST; + event->queue = SNDRV_SEQ_QUEUE_DIRECT; + } +#endif + if (event->dest.client == SNDRV_SEQ_ADDRESS_SUBSCRIBERS) { + /* check presence of source port */ + struct snd_seq_client_port *src_port = snd_seq_port_use_ptr(client, event->source.port); + if (src_port == NULL) + return -EINVAL; + snd_seq_port_unlock(src_port); + } + + /* direct event processing without enqueued */ + if (snd_seq_ev_is_direct(event)) { + if (event->type == SNDRV_SEQ_EVENT_NOTE) + return -EINVAL; /* this event must be enqueued! */ + return snd_seq_deliver_event(client, event, atomic, hop); + } + + /* Not direct, normal queuing */ + if (snd_seq_queue_is_used(event->queue, client->number) <= 0) + return -EINVAL; /* invalid queue */ + if (! snd_seq_write_pool_allocated(client)) + return -ENXIO; /* queue is not allocated */ + + /* allocate an event cell */ + err = snd_seq_event_dup(client->pool, event, &cell, !blocking || atomic, + file, mutexp); + if (err < 0) + return err; + + /* we got a cell. enqueue it. */ + err = snd_seq_enqueue_event(cell, atomic, hop); + if (err < 0) { + snd_seq_cell_free(cell); + return err; + } + + return 0; +} + + +/* + * check validity of event type and data length. + * return non-zero if invalid. + */ +static int check_event_type_and_length(struct snd_seq_event *ev) +{ + switch (snd_seq_ev_length_type(ev)) { + case SNDRV_SEQ_EVENT_LENGTH_FIXED: + if (snd_seq_ev_is_variable_type(ev)) + return -EINVAL; + break; + case SNDRV_SEQ_EVENT_LENGTH_VARIABLE: + if (! snd_seq_ev_is_variable_type(ev) || + (ev->data.ext.len & ~SNDRV_SEQ_EXT_MASK) >= SNDRV_SEQ_MAX_EVENT_LEN) + return -EINVAL; + break; + case SNDRV_SEQ_EVENT_LENGTH_VARUSR: + if (! snd_seq_ev_is_direct(ev)) + return -EINVAL; + break; + } + return 0; +} + + +/* handle write() */ +/* possible error values: + * -ENXIO invalid client or file open mode + * -ENOMEM malloc failed + * -EFAULT seg. fault during copy from user space + * -EINVAL invalid event + * -EAGAIN no space in output pool + * -EINTR interrupts while sleep + * -EMLINK too many hops + * others depends on return value from driver callback + */ +static ssize_t snd_seq_write(struct file *file, const char __user *buf, + size_t count, loff_t *offset) +{ + struct snd_seq_client *client = file->private_data; + int written = 0, len; + int err, handled; + struct snd_seq_event event; + + if (!(snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_OUTPUT)) + return -ENXIO; + + /* check client structures are in place */ + if (snd_BUG_ON(!client)) + return -ENXIO; + + if (!client->accept_output || client->pool == NULL) + return -ENXIO; + + repeat: + handled = 0; + /* allocate the pool now if the pool is not allocated yet */ + mutex_lock(&client->ioctl_mutex); + if (client->pool->size > 0 && !snd_seq_write_pool_allocated(client)) { + err = snd_seq_pool_init(client->pool); + if (err < 0) + goto out; + } + + /* only process whole events */ + err = -EINVAL; + while (count >= sizeof(struct snd_seq_event)) { + /* Read in the event header from the user */ + len = sizeof(event); + if (copy_from_user(&event, buf, len)) { + err = -EFAULT; + break; + } + event.source.client = client->number; /* fill in client number */ + /* Check for extension data length */ + if (check_event_type_and_length(&event)) { + err = -EINVAL; + break; + } + + /* check for special events */ + if (event.type == SNDRV_SEQ_EVENT_NONE) + goto __skip_event; + else if (snd_seq_ev_is_reserved(&event)) { + err = -EINVAL; + break; + } + + if (snd_seq_ev_is_variable(&event)) { + int extlen = event.data.ext.len & ~SNDRV_SEQ_EXT_MASK; + if ((size_t)(extlen + len) > count) { + /* back out, will get an error this time or next */ + err = -EINVAL; + break; + } + /* set user space pointer */ + event.data.ext.len = extlen | SNDRV_SEQ_EXT_USRPTR; + event.data.ext.ptr = (char __force *)buf + + sizeof(struct snd_seq_event); + len += extlen; /* increment data length */ + } else { +#ifdef CONFIG_COMPAT + if (client->convert32 && snd_seq_ev_is_varusr(&event)) { + void *ptr = (void __force *)compat_ptr(event.data.raw32.d[1]); + event.data.ext.ptr = ptr; + } +#endif + } + + /* ok, enqueue it */ + err = snd_seq_client_enqueue_event(client, &event, file, + !(file->f_flags & O_NONBLOCK), + 0, 0, &client->ioctl_mutex); + if (err < 0) + break; + handled++; + + __skip_event: + /* Update pointers and counts */ + count -= len; + buf += len; + written += len; + + /* let's have a coffee break if too many events are queued */ + if (++handled >= 200) { + mutex_unlock(&client->ioctl_mutex); + goto repeat; + } + } + + out: + mutex_unlock(&client->ioctl_mutex); + return written ? written : err; +} + + +/* + * handle polling + */ +static __poll_t snd_seq_poll(struct file *file, poll_table * wait) +{ + struct snd_seq_client *client = file->private_data; + __poll_t mask = 0; + + /* check client structures are in place */ + if (snd_BUG_ON(!client)) + return EPOLLERR; + + if ((snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_INPUT) && + client->data.user.fifo) { + + /* check if data is available in the outqueue */ + if (snd_seq_fifo_poll_wait(client->data.user.fifo, file, wait)) + mask |= EPOLLIN | EPOLLRDNORM; + } + + if (snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_OUTPUT) { + + /* check if data is available in the pool */ + if (!snd_seq_write_pool_allocated(client) || + snd_seq_pool_poll_wait(client->pool, file, wait)) + mask |= EPOLLOUT | EPOLLWRNORM; + } + + return mask; +} + + +/*-----------------------------------------------------*/ + +static int snd_seq_ioctl_pversion(struct snd_seq_client *client, void *arg) +{ + int *pversion = arg; + + *pversion = SNDRV_SEQ_VERSION; + return 0; +} + +static int snd_seq_ioctl_client_id(struct snd_seq_client *client, void *arg) +{ + int *client_id = arg; + + *client_id = client->number; + return 0; +} + +/* SYSTEM_INFO ioctl() */ +static int snd_seq_ioctl_system_info(struct snd_seq_client *client, void *arg) +{ + struct snd_seq_system_info *info = arg; + + memset(info, 0, sizeof(*info)); + /* fill the info fields */ + info->queues = SNDRV_SEQ_MAX_QUEUES; + info->clients = SNDRV_SEQ_MAX_CLIENTS; + info->ports = SNDRV_SEQ_MAX_PORTS; + info->channels = 256; /* fixed limit */ + info->cur_clients = client_usage.cur; + info->cur_queues = snd_seq_queue_get_cur_queues(); + + return 0; +} + + +/* RUNNING_MODE ioctl() */ +static int snd_seq_ioctl_running_mode(struct snd_seq_client *client, void *arg) +{ + struct snd_seq_running_info *info = arg; + struct snd_seq_client *cptr; + int err = 0; + + /* requested client number */ + cptr = snd_seq_client_use_ptr(info->client); + if (cptr == NULL) + return -ENOENT; /* don't change !!! */ + +#ifdef SNDRV_BIG_ENDIAN + if (!info->big_endian) { + err = -EINVAL; + goto __err; + } +#else + if (info->big_endian) { + err = -EINVAL; + goto __err; + } + +#endif + if (info->cpu_mode > sizeof(long)) { + err = -EINVAL; + goto __err; + } + cptr->convert32 = (info->cpu_mode < sizeof(long)); + __err: + snd_seq_client_unlock(cptr); + return err; +} + +/* CLIENT_INFO ioctl() */ +static void get_client_info(struct snd_seq_client *cptr, + struct snd_seq_client_info *info) +{ + info->client = cptr->number; + + /* fill the info fields */ + info->type = cptr->type; + strcpy(info->name, cptr->name); + info->filter = cptr->filter; + info->event_lost = cptr->event_lost; + memcpy(info->event_filter, cptr->event_filter, 32); + info->num_ports = cptr->num_ports; + + if (cptr->type == USER_CLIENT) + info->pid = pid_vnr(cptr->data.user.owner); + else + info->pid = -1; + + if (cptr->type == KERNEL_CLIENT) + info->card = cptr->data.kernel.card ? cptr->data.kernel.card->number : -1; + else + info->card = -1; + + memset(info->reserved, 0, sizeof(info->reserved)); +} + +static int snd_seq_ioctl_get_client_info(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_client_info *client_info = arg; + struct snd_seq_client *cptr; + + /* requested client number */ + cptr = snd_seq_client_use_ptr(client_info->client); + if (cptr == NULL) + return -ENOENT; /* don't change !!! */ + + get_client_info(cptr, client_info); + snd_seq_client_unlock(cptr); + + return 0; +} + + +/* CLIENT_INFO ioctl() */ +static int snd_seq_ioctl_set_client_info(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_client_info *client_info = arg; + + /* it is not allowed to set the info fields for an another client */ + if (client->number != client_info->client) + return -EPERM; + /* also client type must be set now */ + if (client->type != client_info->type) + return -EINVAL; + + /* fill the info fields */ + if (client_info->name[0]) + strscpy(client->name, client_info->name, sizeof(client->name)); + + client->filter = client_info->filter; + client->event_lost = client_info->event_lost; + memcpy(client->event_filter, client_info->event_filter, 32); + + return 0; +} + + +/* + * CREATE PORT ioctl() + */ +static int snd_seq_ioctl_create_port(struct snd_seq_client *client, void *arg) +{ + struct snd_seq_port_info *info = arg; + struct snd_seq_client_port *port; + struct snd_seq_port_callback *callback; + int port_idx; + + /* it is not allowed to create the port for an another client */ + if (info->addr.client != client->number) + return -EPERM; + + port = snd_seq_create_port(client, (info->flags & SNDRV_SEQ_PORT_FLG_GIVEN_PORT) ? info->addr.port : -1); + if (port == NULL) + return -ENOMEM; + + if (client->type == USER_CLIENT && info->kernel) { + port_idx = port->addr.port; + snd_seq_port_unlock(port); + snd_seq_delete_port(client, port_idx); + return -EINVAL; + } + if (client->type == KERNEL_CLIENT) { + callback = info->kernel; + if (callback) { + if (callback->owner) + port->owner = callback->owner; + port->private_data = callback->private_data; + port->private_free = callback->private_free; + port->event_input = callback->event_input; + port->c_src.open = callback->subscribe; + port->c_src.close = callback->unsubscribe; + port->c_dest.open = callback->use; + port->c_dest.close = callback->unuse; + } + } + + info->addr = port->addr; + + snd_seq_set_port_info(port, info); + snd_seq_system_client_ev_port_start(port->addr.client, port->addr.port); + snd_seq_port_unlock(port); + + return 0; +} + +/* + * DELETE PORT ioctl() + */ +static int snd_seq_ioctl_delete_port(struct snd_seq_client *client, void *arg) +{ + struct snd_seq_port_info *info = arg; + int err; + + /* it is not allowed to remove the port for an another client */ + if (info->addr.client != client->number) + return -EPERM; + + err = snd_seq_delete_port(client, info->addr.port); + if (err >= 0) + snd_seq_system_client_ev_port_exit(client->number, info->addr.port); + return err; +} + + +/* + * GET_PORT_INFO ioctl() (on any client) + */ +static int snd_seq_ioctl_get_port_info(struct snd_seq_client *client, void *arg) +{ + struct snd_seq_port_info *info = arg; + struct snd_seq_client *cptr; + struct snd_seq_client_port *port; + + cptr = snd_seq_client_use_ptr(info->addr.client); + if (cptr == NULL) + return -ENXIO; + + port = snd_seq_port_use_ptr(cptr, info->addr.port); + if (port == NULL) { + snd_seq_client_unlock(cptr); + return -ENOENT; /* don't change */ + } + + /* get port info */ + snd_seq_get_port_info(port, info); + snd_seq_port_unlock(port); + snd_seq_client_unlock(cptr); + + return 0; +} + + +/* + * SET_PORT_INFO ioctl() (only ports on this/own client) + */ +static int snd_seq_ioctl_set_port_info(struct snd_seq_client *client, void *arg) +{ + struct snd_seq_port_info *info = arg; + struct snd_seq_client_port *port; + + if (info->addr.client != client->number) /* only set our own ports ! */ + return -EPERM; + port = snd_seq_port_use_ptr(client, info->addr.port); + if (port) { + snd_seq_set_port_info(port, info); + snd_seq_port_unlock(port); + } + return 0; +} + + +/* + * port subscription (connection) + */ +#define PERM_RD (SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_SUBS_READ) +#define PERM_WR (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_SUBS_WRITE) + +static int check_subscription_permission(struct snd_seq_client *client, + struct snd_seq_client_port *sport, + struct snd_seq_client_port *dport, + struct snd_seq_port_subscribe *subs) +{ + if (client->number != subs->sender.client && + client->number != subs->dest.client) { + /* connection by third client - check export permission */ + if (check_port_perm(sport, SNDRV_SEQ_PORT_CAP_NO_EXPORT)) + return -EPERM; + if (check_port_perm(dport, SNDRV_SEQ_PORT_CAP_NO_EXPORT)) + return -EPERM; + } + + /* check read permission */ + /* if sender or receiver is the subscribing client itself, + * no permission check is necessary + */ + if (client->number != subs->sender.client) { + if (! check_port_perm(sport, PERM_RD)) + return -EPERM; + } + /* check write permission */ + if (client->number != subs->dest.client) { + if (! check_port_perm(dport, PERM_WR)) + return -EPERM; + } + return 0; +} + +/* + * send an subscription notify event to user client: + * client must be user client. + */ +int snd_seq_client_notify_subscription(int client, int port, + struct snd_seq_port_subscribe *info, + int evtype) +{ + struct snd_seq_event event; + + memset(&event, 0, sizeof(event)); + event.type = evtype; + event.data.connect.dest = info->dest; + event.data.connect.sender = info->sender; + + return snd_seq_system_notify(client, port, &event); /* non-atomic */ +} + + +/* + * add to port's subscription list IOCTL interface + */ +static int snd_seq_ioctl_subscribe_port(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_port_subscribe *subs = arg; + int result = -EINVAL; + struct snd_seq_client *receiver = NULL, *sender = NULL; + struct snd_seq_client_port *sport = NULL, *dport = NULL; + + receiver = snd_seq_client_use_ptr(subs->dest.client); + if (!receiver) + goto __end; + sender = snd_seq_client_use_ptr(subs->sender.client); + if (!sender) + goto __end; + sport = snd_seq_port_use_ptr(sender, subs->sender.port); + if (!sport) + goto __end; + dport = snd_seq_port_use_ptr(receiver, subs->dest.port); + if (!dport) + goto __end; + + result = check_subscription_permission(client, sport, dport, subs); + if (result < 0) + goto __end; + + /* connect them */ + result = snd_seq_port_connect(client, sender, sport, receiver, dport, subs); + if (! result) /* broadcast announce */ + snd_seq_client_notify_subscription(SNDRV_SEQ_ADDRESS_SUBSCRIBERS, 0, + subs, SNDRV_SEQ_EVENT_PORT_SUBSCRIBED); + __end: + if (sport) + snd_seq_port_unlock(sport); + if (dport) + snd_seq_port_unlock(dport); + if (sender) + snd_seq_client_unlock(sender); + if (receiver) + snd_seq_client_unlock(receiver); + return result; +} + + +/* + * remove from port's subscription list + */ +static int snd_seq_ioctl_unsubscribe_port(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_port_subscribe *subs = arg; + int result = -ENXIO; + struct snd_seq_client *receiver = NULL, *sender = NULL; + struct snd_seq_client_port *sport = NULL, *dport = NULL; + + receiver = snd_seq_client_use_ptr(subs->dest.client); + if (!receiver) + goto __end; + sender = snd_seq_client_use_ptr(subs->sender.client); + if (!sender) + goto __end; + sport = snd_seq_port_use_ptr(sender, subs->sender.port); + if (!sport) + goto __end; + dport = snd_seq_port_use_ptr(receiver, subs->dest.port); + if (!dport) + goto __end; + + result = check_subscription_permission(client, sport, dport, subs); + if (result < 0) + goto __end; + + result = snd_seq_port_disconnect(client, sender, sport, receiver, dport, subs); + if (! result) /* broadcast announce */ + snd_seq_client_notify_subscription(SNDRV_SEQ_ADDRESS_SUBSCRIBERS, 0, + subs, SNDRV_SEQ_EVENT_PORT_UNSUBSCRIBED); + __end: + if (sport) + snd_seq_port_unlock(sport); + if (dport) + snd_seq_port_unlock(dport); + if (sender) + snd_seq_client_unlock(sender); + if (receiver) + snd_seq_client_unlock(receiver); + return result; +} + + +/* CREATE_QUEUE ioctl() */ +static int snd_seq_ioctl_create_queue(struct snd_seq_client *client, void *arg) +{ + struct snd_seq_queue_info *info = arg; + struct snd_seq_queue *q; + + q = snd_seq_queue_alloc(client->number, info->locked, info->flags); + if (IS_ERR(q)) + return PTR_ERR(q); + + info->queue = q->queue; + info->locked = q->locked; + info->owner = q->owner; + + /* set queue name */ + if (!info->name[0]) + snprintf(info->name, sizeof(info->name), "Queue-%d", q->queue); + strscpy(q->name, info->name, sizeof(q->name)); + snd_use_lock_free(&q->use_lock); + + return 0; +} + +/* DELETE_QUEUE ioctl() */ +static int snd_seq_ioctl_delete_queue(struct snd_seq_client *client, void *arg) +{ + struct snd_seq_queue_info *info = arg; + + return snd_seq_queue_delete(client->number, info->queue); +} + +/* GET_QUEUE_INFO ioctl() */ +static int snd_seq_ioctl_get_queue_info(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_queue_info *info = arg; + struct snd_seq_queue *q; + + q = queueptr(info->queue); + if (q == NULL) + return -EINVAL; + + memset(info, 0, sizeof(*info)); + info->queue = q->queue; + info->owner = q->owner; + info->locked = q->locked; + strscpy(info->name, q->name, sizeof(info->name)); + queuefree(q); + + return 0; +} + +/* SET_QUEUE_INFO ioctl() */ +static int snd_seq_ioctl_set_queue_info(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_queue_info *info = arg; + struct snd_seq_queue *q; + + if (info->owner != client->number) + return -EINVAL; + + /* change owner/locked permission */ + if (snd_seq_queue_check_access(info->queue, client->number)) { + if (snd_seq_queue_set_owner(info->queue, client->number, info->locked) < 0) + return -EPERM; + if (info->locked) + snd_seq_queue_use(info->queue, client->number, 1); + } else { + return -EPERM; + } + + q = queueptr(info->queue); + if (! q) + return -EINVAL; + if (q->owner != client->number) { + queuefree(q); + return -EPERM; + } + strscpy(q->name, info->name, sizeof(q->name)); + queuefree(q); + + return 0; +} + +/* GET_NAMED_QUEUE ioctl() */ +static int snd_seq_ioctl_get_named_queue(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_queue_info *info = arg; + struct snd_seq_queue *q; + + q = snd_seq_queue_find_name(info->name); + if (q == NULL) + return -EINVAL; + info->queue = q->queue; + info->owner = q->owner; + info->locked = q->locked; + queuefree(q); + + return 0; +} + +/* GET_QUEUE_STATUS ioctl() */ +static int snd_seq_ioctl_get_queue_status(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_queue_status *status = arg; + struct snd_seq_queue *queue; + struct snd_seq_timer *tmr; + + queue = queueptr(status->queue); + if (queue == NULL) + return -EINVAL; + memset(status, 0, sizeof(*status)); + status->queue = queue->queue; + + tmr = queue->timer; + status->events = queue->tickq->cells + queue->timeq->cells; + + status->time = snd_seq_timer_get_cur_time(tmr, true); + status->tick = snd_seq_timer_get_cur_tick(tmr); + + status->running = tmr->running; + + status->flags = queue->flags; + queuefree(queue); + + return 0; +} + + +/* GET_QUEUE_TEMPO ioctl() */ +static int snd_seq_ioctl_get_queue_tempo(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_queue_tempo *tempo = arg; + struct snd_seq_queue *queue; + struct snd_seq_timer *tmr; + + queue = queueptr(tempo->queue); + if (queue == NULL) + return -EINVAL; + memset(tempo, 0, sizeof(*tempo)); + tempo->queue = queue->queue; + + tmr = queue->timer; + + tempo->tempo = tmr->tempo; + tempo->ppq = tmr->ppq; + tempo->skew_value = tmr->skew; + tempo->skew_base = tmr->skew_base; + queuefree(queue); + + return 0; +} + + +/* SET_QUEUE_TEMPO ioctl() */ +int snd_seq_set_queue_tempo(int client, struct snd_seq_queue_tempo *tempo) +{ + if (!snd_seq_queue_check_access(tempo->queue, client)) + return -EPERM; + return snd_seq_queue_timer_set_tempo(tempo->queue, client, tempo); +} +EXPORT_SYMBOL(snd_seq_set_queue_tempo); + +static int snd_seq_ioctl_set_queue_tempo(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_queue_tempo *tempo = arg; + int result; + + result = snd_seq_set_queue_tempo(client->number, tempo); + return result < 0 ? result : 0; +} + + +/* GET_QUEUE_TIMER ioctl() */ +static int snd_seq_ioctl_get_queue_timer(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_queue_timer *timer = arg; + struct snd_seq_queue *queue; + struct snd_seq_timer *tmr; + + queue = queueptr(timer->queue); + if (queue == NULL) + return -EINVAL; + + mutex_lock(&queue->timer_mutex); + tmr = queue->timer; + memset(timer, 0, sizeof(*timer)); + timer->queue = queue->queue; + + timer->type = tmr->type; + if (tmr->type == SNDRV_SEQ_TIMER_ALSA) { + timer->u.alsa.id = tmr->alsa_id; + timer->u.alsa.resolution = tmr->preferred_resolution; + } + mutex_unlock(&queue->timer_mutex); + queuefree(queue); + + return 0; +} + + +/* SET_QUEUE_TIMER ioctl() */ +static int snd_seq_ioctl_set_queue_timer(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_queue_timer *timer = arg; + int result = 0; + + if (timer->type != SNDRV_SEQ_TIMER_ALSA) + return -EINVAL; + + if (snd_seq_queue_check_access(timer->queue, client->number)) { + struct snd_seq_queue *q; + struct snd_seq_timer *tmr; + + q = queueptr(timer->queue); + if (q == NULL) + return -ENXIO; + mutex_lock(&q->timer_mutex); + tmr = q->timer; + snd_seq_queue_timer_close(timer->queue); + tmr->type = timer->type; + if (tmr->type == SNDRV_SEQ_TIMER_ALSA) { + tmr->alsa_id = timer->u.alsa.id; + tmr->preferred_resolution = timer->u.alsa.resolution; + } + result = snd_seq_queue_timer_open(timer->queue); + mutex_unlock(&q->timer_mutex); + queuefree(q); + } else { + return -EPERM; + } + + return result; +} + + +/* GET_QUEUE_CLIENT ioctl() */ +static int snd_seq_ioctl_get_queue_client(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_queue_client *info = arg; + int used; + + used = snd_seq_queue_is_used(info->queue, client->number); + if (used < 0) + return -EINVAL; + info->used = used; + info->client = client->number; + + return 0; +} + + +/* SET_QUEUE_CLIENT ioctl() */ +static int snd_seq_ioctl_set_queue_client(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_queue_client *info = arg; + int err; + + if (info->used >= 0) { + err = snd_seq_queue_use(info->queue, client->number, info->used); + if (err < 0) + return err; + } + + return snd_seq_ioctl_get_queue_client(client, arg); +} + + +/* GET_CLIENT_POOL ioctl() */ +static int snd_seq_ioctl_get_client_pool(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_client_pool *info = arg; + struct snd_seq_client *cptr; + + cptr = snd_seq_client_use_ptr(info->client); + if (cptr == NULL) + return -ENOENT; + memset(info, 0, sizeof(*info)); + info->client = cptr->number; + info->output_pool = cptr->pool->size; + info->output_room = cptr->pool->room; + info->output_free = info->output_pool; + info->output_free = snd_seq_unused_cells(cptr->pool); + if (cptr->type == USER_CLIENT) { + info->input_pool = cptr->data.user.fifo_pool_size; + info->input_free = info->input_pool; + info->input_free = snd_seq_fifo_unused_cells(cptr->data.user.fifo); + } else { + info->input_pool = 0; + info->input_free = 0; + } + snd_seq_client_unlock(cptr); + + return 0; +} + +/* SET_CLIENT_POOL ioctl() */ +static int snd_seq_ioctl_set_client_pool(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_client_pool *info = arg; + int rc; + + if (client->number != info->client) + return -EINVAL; /* can't change other clients */ + + if (info->output_pool >= 1 && info->output_pool <= SNDRV_SEQ_MAX_EVENTS && + (! snd_seq_write_pool_allocated(client) || + info->output_pool != client->pool->size)) { + if (snd_seq_write_pool_allocated(client)) { + /* is the pool in use? */ + if (atomic_read(&client->pool->counter)) + return -EBUSY; + /* remove all existing cells */ + snd_seq_pool_mark_closing(client->pool); + snd_seq_pool_done(client->pool); + } + client->pool->size = info->output_pool; + rc = snd_seq_pool_init(client->pool); + if (rc < 0) + return rc; + } + if (client->type == USER_CLIENT && client->data.user.fifo != NULL && + info->input_pool >= 1 && + info->input_pool <= SNDRV_SEQ_MAX_CLIENT_EVENTS && + info->input_pool != client->data.user.fifo_pool_size) { + /* change pool size */ + rc = snd_seq_fifo_resize(client->data.user.fifo, info->input_pool); + if (rc < 0) + return rc; + client->data.user.fifo_pool_size = info->input_pool; + } + if (info->output_room >= 1 && + info->output_room <= client->pool->size) { + client->pool->room = info->output_room; + } + + return snd_seq_ioctl_get_client_pool(client, arg); +} + + +/* REMOVE_EVENTS ioctl() */ +static int snd_seq_ioctl_remove_events(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_remove_events *info = arg; + + /* + * Input mostly not implemented XXX. + */ + if (info->remove_mode & SNDRV_SEQ_REMOVE_INPUT) { + /* + * No restrictions so for a user client we can clear + * the whole fifo + */ + if (client->type == USER_CLIENT && client->data.user.fifo) + snd_seq_fifo_clear(client->data.user.fifo); + } + + if (info->remove_mode & SNDRV_SEQ_REMOVE_OUTPUT) + snd_seq_queue_remove_cells(client->number, info); + + return 0; +} + + +/* + * get subscription info + */ +static int snd_seq_ioctl_get_subscription(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_port_subscribe *subs = arg; + int result; + struct snd_seq_client *sender = NULL; + struct snd_seq_client_port *sport = NULL; + + result = -EINVAL; + sender = snd_seq_client_use_ptr(subs->sender.client); + if (!sender) + goto __end; + sport = snd_seq_port_use_ptr(sender, subs->sender.port); + if (!sport) + goto __end; + result = snd_seq_port_get_subscription(&sport->c_src, &subs->dest, + subs); + __end: + if (sport) + snd_seq_port_unlock(sport); + if (sender) + snd_seq_client_unlock(sender); + + return result; +} + + +/* + * get subscription info - check only its presence + */ +static int snd_seq_ioctl_query_subs(struct snd_seq_client *client, void *arg) +{ + struct snd_seq_query_subs *subs = arg; + int result = -ENXIO; + struct snd_seq_client *cptr = NULL; + struct snd_seq_client_port *port = NULL; + struct snd_seq_port_subs_info *group; + struct list_head *p; + int i; + + cptr = snd_seq_client_use_ptr(subs->root.client); + if (!cptr) + goto __end; + port = snd_seq_port_use_ptr(cptr, subs->root.port); + if (!port) + goto __end; + + switch (subs->type) { + case SNDRV_SEQ_QUERY_SUBS_READ: + group = &port->c_src; + break; + case SNDRV_SEQ_QUERY_SUBS_WRITE: + group = &port->c_dest; + break; + default: + goto __end; + } + + down_read(&group->list_mutex); + /* search for the subscriber */ + subs->num_subs = group->count; + i = 0; + result = -ENOENT; + list_for_each(p, &group->list_head) { + if (i++ == subs->index) { + /* found! */ + struct snd_seq_subscribers *s; + if (subs->type == SNDRV_SEQ_QUERY_SUBS_READ) { + s = list_entry(p, struct snd_seq_subscribers, src_list); + subs->addr = s->info.dest; + } else { + s = list_entry(p, struct snd_seq_subscribers, dest_list); + subs->addr = s->info.sender; + } + subs->flags = s->info.flags; + subs->queue = s->info.queue; + result = 0; + break; + } + } + up_read(&group->list_mutex); + + __end: + if (port) + snd_seq_port_unlock(port); + if (cptr) + snd_seq_client_unlock(cptr); + + return result; +} + + +/* + * query next client + */ +static int snd_seq_ioctl_query_next_client(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_client_info *info = arg; + struct snd_seq_client *cptr = NULL; + + /* search for next client */ + if (info->client < INT_MAX) + info->client++; + if (info->client < 0) + info->client = 0; + for (; info->client < SNDRV_SEQ_MAX_CLIENTS; info->client++) { + cptr = snd_seq_client_use_ptr(info->client); + if (cptr) + break; /* found */ + } + if (cptr == NULL) + return -ENOENT; + + get_client_info(cptr, info); + snd_seq_client_unlock(cptr); + + return 0; +} + +/* + * query next port + */ +static int snd_seq_ioctl_query_next_port(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_port_info *info = arg; + struct snd_seq_client *cptr; + struct snd_seq_client_port *port = NULL; + + cptr = snd_seq_client_use_ptr(info->addr.client); + if (cptr == NULL) + return -ENXIO; + + /* search for next port */ + info->addr.port++; + port = snd_seq_port_query_nearest(cptr, info); + if (port == NULL) { + snd_seq_client_unlock(cptr); + return -ENOENT; + } + + /* get port info */ + info->addr = port->addr; + snd_seq_get_port_info(port, info); + snd_seq_port_unlock(port); + snd_seq_client_unlock(cptr); + + return 0; +} + +/* -------------------------------------------------------- */ + +static const struct ioctl_handler { + unsigned int cmd; + int (*func)(struct snd_seq_client *client, void *arg); +} ioctl_handlers[] = { + { SNDRV_SEQ_IOCTL_PVERSION, snd_seq_ioctl_pversion }, + { SNDRV_SEQ_IOCTL_CLIENT_ID, snd_seq_ioctl_client_id }, + { SNDRV_SEQ_IOCTL_SYSTEM_INFO, snd_seq_ioctl_system_info }, + { SNDRV_SEQ_IOCTL_RUNNING_MODE, snd_seq_ioctl_running_mode }, + { SNDRV_SEQ_IOCTL_GET_CLIENT_INFO, snd_seq_ioctl_get_client_info }, + { SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, snd_seq_ioctl_set_client_info }, + { SNDRV_SEQ_IOCTL_CREATE_PORT, snd_seq_ioctl_create_port }, + { SNDRV_SEQ_IOCTL_DELETE_PORT, snd_seq_ioctl_delete_port }, + { SNDRV_SEQ_IOCTL_GET_PORT_INFO, snd_seq_ioctl_get_port_info }, + { SNDRV_SEQ_IOCTL_SET_PORT_INFO, snd_seq_ioctl_set_port_info }, + { SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, snd_seq_ioctl_subscribe_port }, + { SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT, snd_seq_ioctl_unsubscribe_port }, + { SNDRV_SEQ_IOCTL_CREATE_QUEUE, snd_seq_ioctl_create_queue }, + { SNDRV_SEQ_IOCTL_DELETE_QUEUE, snd_seq_ioctl_delete_queue }, + { SNDRV_SEQ_IOCTL_GET_QUEUE_INFO, snd_seq_ioctl_get_queue_info }, + { SNDRV_SEQ_IOCTL_SET_QUEUE_INFO, snd_seq_ioctl_set_queue_info }, + { SNDRV_SEQ_IOCTL_GET_NAMED_QUEUE, snd_seq_ioctl_get_named_queue }, + { SNDRV_SEQ_IOCTL_GET_QUEUE_STATUS, snd_seq_ioctl_get_queue_status }, + { SNDRV_SEQ_IOCTL_GET_QUEUE_TEMPO, snd_seq_ioctl_get_queue_tempo }, + { SNDRV_SEQ_IOCTL_SET_QUEUE_TEMPO, snd_seq_ioctl_set_queue_tempo }, + { SNDRV_SEQ_IOCTL_GET_QUEUE_TIMER, snd_seq_ioctl_get_queue_timer }, + { SNDRV_SEQ_IOCTL_SET_QUEUE_TIMER, snd_seq_ioctl_set_queue_timer }, + { SNDRV_SEQ_IOCTL_GET_QUEUE_CLIENT, snd_seq_ioctl_get_queue_client }, + { SNDRV_SEQ_IOCTL_SET_QUEUE_CLIENT, snd_seq_ioctl_set_queue_client }, + { SNDRV_SEQ_IOCTL_GET_CLIENT_POOL, snd_seq_ioctl_get_client_pool }, + { SNDRV_SEQ_IOCTL_SET_CLIENT_POOL, snd_seq_ioctl_set_client_pool }, + { SNDRV_SEQ_IOCTL_GET_SUBSCRIPTION, snd_seq_ioctl_get_subscription }, + { SNDRV_SEQ_IOCTL_QUERY_NEXT_CLIENT, snd_seq_ioctl_query_next_client }, + { SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT, snd_seq_ioctl_query_next_port }, + { SNDRV_SEQ_IOCTL_REMOVE_EVENTS, snd_seq_ioctl_remove_events }, + { SNDRV_SEQ_IOCTL_QUERY_SUBS, snd_seq_ioctl_query_subs }, + { 0, NULL }, +}; + +static long snd_seq_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct snd_seq_client *client = file->private_data; + /* To use kernel stack for ioctl data. */ + union { + int pversion; + int client_id; + struct snd_seq_system_info system_info; + struct snd_seq_running_info running_info; + struct snd_seq_client_info client_info; + struct snd_seq_port_info port_info; + struct snd_seq_port_subscribe port_subscribe; + struct snd_seq_queue_info queue_info; + struct snd_seq_queue_status queue_status; + struct snd_seq_queue_tempo tempo; + struct snd_seq_queue_timer queue_timer; + struct snd_seq_queue_client queue_client; + struct snd_seq_client_pool client_pool; + struct snd_seq_remove_events remove_events; + struct snd_seq_query_subs query_subs; + } buf; + const struct ioctl_handler *handler; + unsigned long size; + int err; + + if (snd_BUG_ON(!client)) + return -ENXIO; + + for (handler = ioctl_handlers; handler->cmd > 0; ++handler) { + if (handler->cmd == cmd) + break; + } + if (handler->cmd == 0) + return -ENOTTY; + + memset(&buf, 0, sizeof(buf)); + + /* + * All of ioctl commands for ALSA sequencer get an argument of size + * within 13 bits. We can safely pick up the size from the command. + */ + size = _IOC_SIZE(handler->cmd); + if (handler->cmd & IOC_IN) { + if (copy_from_user(&buf, (const void __user *)arg, size)) + return -EFAULT; + } + + mutex_lock(&client->ioctl_mutex); + err = handler->func(client, &buf); + mutex_unlock(&client->ioctl_mutex); + if (err >= 0) { + /* Some commands includes a bug in 'dir' field. */ + if (handler->cmd == SNDRV_SEQ_IOCTL_SET_QUEUE_CLIENT || + handler->cmd == SNDRV_SEQ_IOCTL_SET_CLIENT_POOL || + (handler->cmd & IOC_OUT)) + if (copy_to_user((void __user *)arg, &buf, size)) + return -EFAULT; + } + + return err; +} + +#ifdef CONFIG_COMPAT +#include "seq_compat.c" +#else +#define snd_seq_ioctl_compat NULL +#endif + +/* -------------------------------------------------------- */ + + +/* exported to kernel modules */ +int snd_seq_create_kernel_client(struct snd_card *card, int client_index, + const char *name_fmt, ...) +{ + struct snd_seq_client *client; + va_list args; + + if (snd_BUG_ON(in_interrupt())) + return -EBUSY; + + if (card && client_index >= SNDRV_SEQ_CLIENTS_PER_CARD) + return -EINVAL; + if (card == NULL && client_index >= SNDRV_SEQ_GLOBAL_CLIENTS) + return -EINVAL; + + mutex_lock(®ister_mutex); + + if (card) { + client_index += SNDRV_SEQ_GLOBAL_CLIENTS + + card->number * SNDRV_SEQ_CLIENTS_PER_CARD; + if (client_index >= SNDRV_SEQ_DYNAMIC_CLIENTS_BEGIN) + client_index = -1; + } + + /* empty write queue as default */ + client = seq_create_client1(client_index, 0); + if (client == NULL) { + mutex_unlock(®ister_mutex); + return -EBUSY; /* failure code */ + } + usage_alloc(&client_usage, 1); + + client->accept_input = 1; + client->accept_output = 1; + client->data.kernel.card = card; + + va_start(args, name_fmt); + vsnprintf(client->name, sizeof(client->name), name_fmt, args); + va_end(args); + + client->type = KERNEL_CLIENT; + mutex_unlock(®ister_mutex); + + /* make others aware this new client */ + snd_seq_system_client_ev_client_start(client->number); + + /* return client number to caller */ + return client->number; +} +EXPORT_SYMBOL(snd_seq_create_kernel_client); + +/* exported to kernel modules */ +int snd_seq_delete_kernel_client(int client) +{ + struct snd_seq_client *ptr; + + if (snd_BUG_ON(in_interrupt())) + return -EBUSY; + + ptr = clientptr(client); + if (ptr == NULL) + return -EINVAL; + + seq_free_client(ptr); + kfree(ptr); + return 0; +} +EXPORT_SYMBOL(snd_seq_delete_kernel_client); + +/* + * exported, called by kernel clients to enqueue events (w/o blocking) + * + * RETURN VALUE: zero if succeed, negative if error + */ +int snd_seq_kernel_client_enqueue(int client, struct snd_seq_event *ev, + struct file *file, bool blocking) +{ + struct snd_seq_client *cptr; + int result; + + if (snd_BUG_ON(!ev)) + return -EINVAL; + + if (ev->type == SNDRV_SEQ_EVENT_NONE) + return 0; /* ignore this */ + if (ev->type == SNDRV_SEQ_EVENT_KERNEL_ERROR) + return -EINVAL; /* quoted events can't be enqueued */ + + /* fill in client number */ + ev->source.client = client; + + if (check_event_type_and_length(ev)) + return -EINVAL; + + cptr = snd_seq_client_use_ptr(client); + if (cptr == NULL) + return -EINVAL; + + if (!cptr->accept_output) { + result = -EPERM; + } else { /* send it */ + mutex_lock(&cptr->ioctl_mutex); + result = snd_seq_client_enqueue_event(cptr, ev, file, blocking, + false, 0, + &cptr->ioctl_mutex); + mutex_unlock(&cptr->ioctl_mutex); + } + + snd_seq_client_unlock(cptr); + return result; +} +EXPORT_SYMBOL(snd_seq_kernel_client_enqueue); + +/* + * exported, called by kernel clients to dispatch events directly to other + * clients, bypassing the queues. Event time-stamp will be updated. + * + * RETURN VALUE: negative = delivery failed, + * zero, or positive: the number of delivered events + */ +int snd_seq_kernel_client_dispatch(int client, struct snd_seq_event * ev, + int atomic, int hop) +{ + struct snd_seq_client *cptr; + int result; + + if (snd_BUG_ON(!ev)) + return -EINVAL; + + /* fill in client number */ + ev->queue = SNDRV_SEQ_QUEUE_DIRECT; + ev->source.client = client; + + if (check_event_type_and_length(ev)) + return -EINVAL; + + cptr = snd_seq_client_use_ptr(client); + if (cptr == NULL) + return -EINVAL; + + if (!cptr->accept_output) + result = -EPERM; + else + result = snd_seq_deliver_event(cptr, ev, atomic, hop); + + snd_seq_client_unlock(cptr); + return result; +} +EXPORT_SYMBOL(snd_seq_kernel_client_dispatch); + +/** + * snd_seq_kernel_client_ctl - operate a command for a client with data in + * kernel space. + * @clientid: A numerical ID for a client. + * @cmd: An ioctl(2) command for ALSA sequencer operation. + * @arg: A pointer to data in kernel space. + * + * Against its name, both kernel/application client can be handled by this + * kernel API. A pointer of 'arg' argument should be in kernel space. + * + * Return: 0 at success. Negative error code at failure. + */ +int snd_seq_kernel_client_ctl(int clientid, unsigned int cmd, void *arg) +{ + const struct ioctl_handler *handler; + struct snd_seq_client *client; + + client = clientptr(clientid); + if (client == NULL) + return -ENXIO; + + for (handler = ioctl_handlers; handler->cmd > 0; ++handler) { + if (handler->cmd == cmd) + return handler->func(client, arg); + } + + pr_debug("ALSA: seq unknown ioctl() 0x%x (type='%c', number=0x%02x)\n", + cmd, _IOC_TYPE(cmd), _IOC_NR(cmd)); + return -ENOTTY; +} +EXPORT_SYMBOL(snd_seq_kernel_client_ctl); + +/* exported (for OSS emulator) */ +int snd_seq_kernel_client_write_poll(int clientid, struct file *file, poll_table *wait) +{ + struct snd_seq_client *client; + + client = clientptr(clientid); + if (client == NULL) + return -ENXIO; + + if (! snd_seq_write_pool_allocated(client)) + return 1; + if (snd_seq_pool_poll_wait(client->pool, file, wait)) + return 1; + return 0; +} +EXPORT_SYMBOL(snd_seq_kernel_client_write_poll); + +/*---------------------------------------------------------------------------*/ + +#ifdef CONFIG_SND_PROC_FS +/* + * /proc interface + */ +static void snd_seq_info_dump_subscribers(struct snd_info_buffer *buffer, + struct snd_seq_port_subs_info *group, + int is_src, char *msg) +{ + struct list_head *p; + struct snd_seq_subscribers *s; + int count = 0; + + down_read(&group->list_mutex); + if (list_empty(&group->list_head)) { + up_read(&group->list_mutex); + return; + } + snd_iprintf(buffer, msg); + list_for_each(p, &group->list_head) { + if (is_src) + s = list_entry(p, struct snd_seq_subscribers, src_list); + else + s = list_entry(p, struct snd_seq_subscribers, dest_list); + if (count++) + snd_iprintf(buffer, ", "); + snd_iprintf(buffer, "%d:%d", + is_src ? s->info.dest.client : s->info.sender.client, + is_src ? s->info.dest.port : s->info.sender.port); + if (s->info.flags & SNDRV_SEQ_PORT_SUBS_TIMESTAMP) + snd_iprintf(buffer, "[%c:%d]", ((s->info.flags & SNDRV_SEQ_PORT_SUBS_TIME_REAL) ? 'r' : 't'), s->info.queue); + if (group->exclusive) + snd_iprintf(buffer, "[ex]"); + } + up_read(&group->list_mutex); + snd_iprintf(buffer, "\n"); +} + +#define FLAG_PERM_RD(perm) ((perm) & SNDRV_SEQ_PORT_CAP_READ ? ((perm) & SNDRV_SEQ_PORT_CAP_SUBS_READ ? 'R' : 'r') : '-') +#define FLAG_PERM_WR(perm) ((perm) & SNDRV_SEQ_PORT_CAP_WRITE ? ((perm) & SNDRV_SEQ_PORT_CAP_SUBS_WRITE ? 'W' : 'w') : '-') +#define FLAG_PERM_EX(perm) ((perm) & SNDRV_SEQ_PORT_CAP_NO_EXPORT ? '-' : 'e') + +#define FLAG_PERM_DUPLEX(perm) ((perm) & SNDRV_SEQ_PORT_CAP_DUPLEX ? 'X' : '-') + +static void snd_seq_info_dump_ports(struct snd_info_buffer *buffer, + struct snd_seq_client *client) +{ + struct snd_seq_client_port *p; + + mutex_lock(&client->ports_mutex); + list_for_each_entry(p, &client->ports_list_head, list) { + snd_iprintf(buffer, " Port %3d : \"%s\" (%c%c%c%c)\n", + p->addr.port, p->name, + FLAG_PERM_RD(p->capability), + FLAG_PERM_WR(p->capability), + FLAG_PERM_EX(p->capability), + FLAG_PERM_DUPLEX(p->capability)); + snd_seq_info_dump_subscribers(buffer, &p->c_src, 1, " Connecting To: "); + snd_seq_info_dump_subscribers(buffer, &p->c_dest, 0, " Connected From: "); + } + mutex_unlock(&client->ports_mutex); +} + + +/* exported to seq_info.c */ +void snd_seq_info_clients_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + int c; + struct snd_seq_client *client; + + snd_iprintf(buffer, "Client info\n"); + snd_iprintf(buffer, " cur clients : %d\n", client_usage.cur); + snd_iprintf(buffer, " peak clients : %d\n", client_usage.peak); + snd_iprintf(buffer, " max clients : %d\n", SNDRV_SEQ_MAX_CLIENTS); + snd_iprintf(buffer, "\n"); + + /* list the client table */ + for (c = 0; c < SNDRV_SEQ_MAX_CLIENTS; c++) { + client = snd_seq_client_use_ptr(c); + if (client == NULL) + continue; + if (client->type == NO_CLIENT) { + snd_seq_client_unlock(client); + continue; + } + + snd_iprintf(buffer, "Client %3d : \"%s\" [%s]\n", + c, client->name, + client->type == USER_CLIENT ? "User" : "Kernel"); + snd_seq_info_dump_ports(buffer, client); + if (snd_seq_write_pool_allocated(client)) { + snd_iprintf(buffer, " Output pool :\n"); + snd_seq_info_pool(buffer, client->pool, " "); + } + if (client->type == USER_CLIENT && client->data.user.fifo && + client->data.user.fifo->pool) { + snd_iprintf(buffer, " Input pool :\n"); + snd_seq_info_pool(buffer, client->data.user.fifo->pool, " "); + } + snd_seq_client_unlock(client); + } +} +#endif /* CONFIG_SND_PROC_FS */ + +/*---------------------------------------------------------------------------*/ + + +/* + * REGISTRATION PART + */ + +static const struct file_operations snd_seq_f_ops = +{ + .owner = THIS_MODULE, + .read = snd_seq_read, + .write = snd_seq_write, + .open = snd_seq_open, + .release = snd_seq_release, + .llseek = no_llseek, + .poll = snd_seq_poll, + .unlocked_ioctl = snd_seq_ioctl, + .compat_ioctl = snd_seq_ioctl_compat, +}; + +static struct device seq_dev; + +/* + * register sequencer device + */ +int __init snd_sequencer_device_init(void) +{ + int err; + + snd_device_initialize(&seq_dev, NULL); + dev_set_name(&seq_dev, "seq"); + + mutex_lock(®ister_mutex); + err = snd_register_device(SNDRV_DEVICE_TYPE_SEQUENCER, NULL, 0, + &snd_seq_f_ops, NULL, &seq_dev); + mutex_unlock(®ister_mutex); + if (err < 0) { + put_device(&seq_dev); + return err; + } + + return 0; +} + + + +/* + * unregister sequencer device + */ +void snd_sequencer_device_done(void) +{ + snd_unregister_device(&seq_dev); + put_device(&seq_dev); +} diff --git a/sound/core/seq/seq_clientmgr.h b/sound/core/seq/seq_clientmgr.h new file mode 100644 index 000000000..8cdd0ee53 --- /dev/null +++ b/sound/core/seq/seq_clientmgr.h @@ -0,0 +1,91 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * ALSA sequencer Client Manager + * Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl> + */ +#ifndef __SND_SEQ_CLIENTMGR_H +#define __SND_SEQ_CLIENTMGR_H + +#include <sound/seq_kernel.h> +#include <linux/bitops.h> +#include "seq_fifo.h" +#include "seq_ports.h" +#include "seq_lock.h" + + +/* client manager */ + +struct snd_seq_user_client { + struct file *file; /* file struct of client */ + /* ... */ + struct pid *owner; + + /* fifo */ + struct snd_seq_fifo *fifo; /* queue for incoming events */ + int fifo_pool_size; +}; + +struct snd_seq_kernel_client { + /* ... */ + struct snd_card *card; +}; + + +struct snd_seq_client { + snd_seq_client_type_t type; + unsigned int accept_input: 1, + accept_output: 1; + char name[64]; /* client name */ + int number; /* client number */ + unsigned int filter; /* filter flags */ + DECLARE_BITMAP(event_filter, 256); + snd_use_lock_t use_lock; + int event_lost; + /* ports */ + int num_ports; /* number of ports */ + struct list_head ports_list_head; + rwlock_t ports_lock; + struct mutex ports_mutex; + struct mutex ioctl_mutex; + int convert32; /* convert 32->64bit */ + + /* output pool */ + struct snd_seq_pool *pool; /* memory pool for this client */ + + union { + struct snd_seq_user_client user; + struct snd_seq_kernel_client kernel; + } data; +}; + +/* usage statistics */ +struct snd_seq_usage { + int cur; + int peak; +}; + + +int client_init_data(void); +int snd_sequencer_device_init(void); +void snd_sequencer_device_done(void); + +/* get locked pointer to client */ +struct snd_seq_client *snd_seq_client_use_ptr(int clientid); + +/* unlock pointer to client */ +#define snd_seq_client_unlock(client) snd_use_lock_free(&(client)->use_lock) + +/* dispatch event to client(s) */ +int snd_seq_dispatch_event(struct snd_seq_event_cell *cell, int atomic, int hop); + +int snd_seq_kernel_client_write_poll(int clientid, struct file *file, poll_table *wait); +int snd_seq_client_notify_subscription(int client, int port, + struct snd_seq_port_subscribe *info, int evtype); + +/* only for OSS sequencer */ +bool snd_seq_client_ioctl_lock(int clientid); +void snd_seq_client_ioctl_unlock(int clientid); + +extern int seq_client_load[15]; + +#endif diff --git a/sound/core/seq/seq_compat.c b/sound/core/seq/seq_compat.c new file mode 100644 index 000000000..54723566c --- /dev/null +++ b/sound/core/seq/seq_compat.c @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * 32bit -> 64bit ioctl wrapper for sequencer API + * Copyright (c) by Takashi Iwai <tiwai@suse.de> + */ + +/* This file included from seq.c */ + +#include <linux/compat.h> +#include <linux/slab.h> + +struct snd_seq_port_info32 { + struct snd_seq_addr addr; /* client/port numbers */ + char name[64]; /* port name */ + + u32 capability; /* port capability bits */ + u32 type; /* port type bits */ + s32 midi_channels; /* channels per MIDI port */ + s32 midi_voices; /* voices per MIDI port */ + s32 synth_voices; /* voices per SYNTH port */ + + s32 read_use; /* R/O: subscribers for output (from this port) */ + s32 write_use; /* R/O: subscribers for input (to this port) */ + + u32 kernel; /* reserved for kernel use (must be NULL) */ + u32 flags; /* misc. conditioning */ + unsigned char time_queue; /* queue # for timestamping */ + char reserved[59]; /* for future use */ +}; + +static int snd_seq_call_port_info_ioctl(struct snd_seq_client *client, unsigned int cmd, + struct snd_seq_port_info32 __user *data32) +{ + int err = -EFAULT; + struct snd_seq_port_info *data; + + data = kmalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + if (copy_from_user(data, data32, sizeof(*data32)) || + get_user(data->flags, &data32->flags) || + get_user(data->time_queue, &data32->time_queue)) + goto error; + data->kernel = NULL; + + err = snd_seq_kernel_client_ctl(client->number, cmd, data); + if (err < 0) + goto error; + + if (copy_to_user(data32, data, sizeof(*data32)) || + put_user(data->flags, &data32->flags) || + put_user(data->time_queue, &data32->time_queue)) + err = -EFAULT; + + error: + kfree(data); + return err; +} + + + +/* + */ + +enum { + SNDRV_SEQ_IOCTL_CREATE_PORT32 = _IOWR('S', 0x20, struct snd_seq_port_info32), + SNDRV_SEQ_IOCTL_DELETE_PORT32 = _IOW ('S', 0x21, struct snd_seq_port_info32), + SNDRV_SEQ_IOCTL_GET_PORT_INFO32 = _IOWR('S', 0x22, struct snd_seq_port_info32), + SNDRV_SEQ_IOCTL_SET_PORT_INFO32 = _IOW ('S', 0x23, struct snd_seq_port_info32), + SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT32 = _IOWR('S', 0x52, struct snd_seq_port_info32), +}; + +static long snd_seq_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct snd_seq_client *client = file->private_data; + void __user *argp = compat_ptr(arg); + + if (snd_BUG_ON(!client)) + return -ENXIO; + + switch (cmd) { + case SNDRV_SEQ_IOCTL_PVERSION: + case SNDRV_SEQ_IOCTL_CLIENT_ID: + case SNDRV_SEQ_IOCTL_SYSTEM_INFO: + case SNDRV_SEQ_IOCTL_GET_CLIENT_INFO: + case SNDRV_SEQ_IOCTL_SET_CLIENT_INFO: + case SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT: + case SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT: + case SNDRV_SEQ_IOCTL_CREATE_QUEUE: + case SNDRV_SEQ_IOCTL_DELETE_QUEUE: + case SNDRV_SEQ_IOCTL_GET_QUEUE_INFO: + case SNDRV_SEQ_IOCTL_SET_QUEUE_INFO: + case SNDRV_SEQ_IOCTL_GET_NAMED_QUEUE: + case SNDRV_SEQ_IOCTL_GET_QUEUE_STATUS: + case SNDRV_SEQ_IOCTL_GET_QUEUE_TEMPO: + case SNDRV_SEQ_IOCTL_SET_QUEUE_TEMPO: + case SNDRV_SEQ_IOCTL_GET_QUEUE_TIMER: + case SNDRV_SEQ_IOCTL_SET_QUEUE_TIMER: + case SNDRV_SEQ_IOCTL_GET_QUEUE_CLIENT: + case SNDRV_SEQ_IOCTL_SET_QUEUE_CLIENT: + case SNDRV_SEQ_IOCTL_GET_CLIENT_POOL: + case SNDRV_SEQ_IOCTL_SET_CLIENT_POOL: + case SNDRV_SEQ_IOCTL_REMOVE_EVENTS: + case SNDRV_SEQ_IOCTL_QUERY_SUBS: + case SNDRV_SEQ_IOCTL_GET_SUBSCRIPTION: + case SNDRV_SEQ_IOCTL_QUERY_NEXT_CLIENT: + case SNDRV_SEQ_IOCTL_RUNNING_MODE: + return snd_seq_ioctl(file, cmd, arg); + case SNDRV_SEQ_IOCTL_CREATE_PORT32: + return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_CREATE_PORT, argp); + case SNDRV_SEQ_IOCTL_DELETE_PORT32: + return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_DELETE_PORT, argp); + case SNDRV_SEQ_IOCTL_GET_PORT_INFO32: + return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_GET_PORT_INFO, argp); + case SNDRV_SEQ_IOCTL_SET_PORT_INFO32: + return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_SET_PORT_INFO, argp); + case SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT32: + return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT, argp); + } + return -ENOIOCTLCMD; +} diff --git a/sound/core/seq/seq_dummy.c b/sound/core/seq/seq_dummy.c new file mode 100644 index 000000000..8c18d8c41 --- /dev/null +++ b/sound/core/seq/seq_dummy.c @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA sequencer MIDI-through client + * Copyright (c) 1999-2000 by Takashi Iwai <tiwai@suse.de> + */ + +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <sound/core.h> +#include "seq_clientmgr.h" +#include <sound/initval.h> +#include <sound/asoundef.h> + +/* + + Sequencer MIDI-through client + + This gives a simple midi-through client. All the normal input events + are redirected to output port immediately. + The routing can be done via aconnect program in alsa-utils. + + Each client has a static client number 14 (= SNDRV_SEQ_CLIENT_DUMMY). + If you want to auto-load this module, you may add the following alias + in your /etc/conf.modules file. + + alias snd-seq-client-14 snd-seq-dummy + + The module is loaded on demand for client 14, or /proc/asound/seq/ + is accessed. If you don't need this module to be loaded, alias + snd-seq-client-14 as "off". This will help modprobe. + + The number of ports to be created can be specified via the module + parameter "ports". For example, to create four ports, add the + following option in a configuration file under /etc/modprobe.d/: + + option snd-seq-dummy ports=4 + + The model option "duplex=1" enables duplex operation to the port. + In duplex mode, a pair of ports are created instead of single port, + and events are tunneled between pair-ports. For example, input to + port A is sent to output port of another port B and vice versa. + In duplex mode, each port has DUPLEX capability. + + */ + + +MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>"); +MODULE_DESCRIPTION("ALSA sequencer MIDI-through client"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("snd-seq-client-" __stringify(SNDRV_SEQ_CLIENT_DUMMY)); + +static int ports = 1; +static bool duplex; + +module_param(ports, int, 0444); +MODULE_PARM_DESC(ports, "number of ports to be created"); +module_param(duplex, bool, 0444); +MODULE_PARM_DESC(duplex, "create DUPLEX ports"); + +struct snd_seq_dummy_port { + int client; + int port; + int duplex; + int connect; +}; + +static int my_client = -1; + +/* + * event input callback - just redirect events to subscribers + */ +static int +dummy_input(struct snd_seq_event *ev, int direct, void *private_data, + int atomic, int hop) +{ + struct snd_seq_dummy_port *p; + struct snd_seq_event tmpev; + + p = private_data; + if (ev->source.client == SNDRV_SEQ_CLIENT_SYSTEM || + ev->type == SNDRV_SEQ_EVENT_KERNEL_ERROR) + return 0; /* ignore system messages */ + tmpev = *ev; + if (p->duplex) + tmpev.source.port = p->connect; + else + tmpev.source.port = p->port; + tmpev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS; + return snd_seq_kernel_client_dispatch(p->client, &tmpev, atomic, hop); +} + +/* + * free_private callback + */ +static void +dummy_free(void *private_data) +{ + kfree(private_data); +} + +/* + * create a port + */ +static struct snd_seq_dummy_port __init * +create_port(int idx, int type) +{ + struct snd_seq_port_info pinfo; + struct snd_seq_port_callback pcb; + struct snd_seq_dummy_port *rec; + + rec = kzalloc(sizeof(*rec), GFP_KERNEL); + if (!rec) + return NULL; + + rec->client = my_client; + rec->duplex = duplex; + rec->connect = 0; + memset(&pinfo, 0, sizeof(pinfo)); + pinfo.addr.client = my_client; + if (duplex) + sprintf(pinfo.name, "Midi Through Port-%d:%c", idx, + (type ? 'B' : 'A')); + else + sprintf(pinfo.name, "Midi Through Port-%d", idx); + pinfo.capability = SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ; + pinfo.capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE; + if (duplex) + pinfo.capability |= SNDRV_SEQ_PORT_CAP_DUPLEX; + pinfo.type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC + | SNDRV_SEQ_PORT_TYPE_SOFTWARE + | SNDRV_SEQ_PORT_TYPE_PORT; + memset(&pcb, 0, sizeof(pcb)); + pcb.owner = THIS_MODULE; + pcb.event_input = dummy_input; + pcb.private_free = dummy_free; + pcb.private_data = rec; + pinfo.kernel = &pcb; + if (snd_seq_kernel_client_ctl(my_client, SNDRV_SEQ_IOCTL_CREATE_PORT, &pinfo) < 0) { + kfree(rec); + return NULL; + } + rec->port = pinfo.addr.port; + return rec; +} + +/* + * register client and create ports + */ +static int __init +register_client(void) +{ + struct snd_seq_dummy_port *rec1, *rec2; + int i; + + if (ports < 1) { + pr_err("ALSA: seq_dummy: invalid number of ports %d\n", ports); + return -EINVAL; + } + + /* create client */ + my_client = snd_seq_create_kernel_client(NULL, SNDRV_SEQ_CLIENT_DUMMY, + "Midi Through"); + if (my_client < 0) + return my_client; + + /* create ports */ + for (i = 0; i < ports; i++) { + rec1 = create_port(i, 0); + if (rec1 == NULL) { + snd_seq_delete_kernel_client(my_client); + return -ENOMEM; + } + if (duplex) { + rec2 = create_port(i, 1); + if (rec2 == NULL) { + snd_seq_delete_kernel_client(my_client); + return -ENOMEM; + } + rec1->connect = rec2->port; + rec2->connect = rec1->port; + } + } + + return 0; +} + +/* + * delete client if exists + */ +static void __exit +delete_client(void) +{ + if (my_client >= 0) + snd_seq_delete_kernel_client(my_client); +} + +/* + * Init part + */ + +static int __init alsa_seq_dummy_init(void) +{ + return register_client(); +} + +static void __exit alsa_seq_dummy_exit(void) +{ + delete_client(); +} + +module_init(alsa_seq_dummy_init) +module_exit(alsa_seq_dummy_exit) diff --git a/sound/core/seq/seq_fifo.c b/sound/core/seq/seq_fifo.c new file mode 100644 index 000000000..f8e02e987 --- /dev/null +++ b/sound/core/seq/seq_fifo.c @@ -0,0 +1,283 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA sequencer FIFO + * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl> + */ + +#include <sound/core.h> +#include <linux/slab.h> +#include <linux/sched/signal.h> + +#include "seq_fifo.h" +#include "seq_lock.h" + + +/* FIFO */ + +/* create new fifo */ +struct snd_seq_fifo *snd_seq_fifo_new(int poolsize) +{ + struct snd_seq_fifo *f; + + f = kzalloc(sizeof(*f), GFP_KERNEL); + if (!f) + return NULL; + + f->pool = snd_seq_pool_new(poolsize); + if (f->pool == NULL) { + kfree(f); + return NULL; + } + if (snd_seq_pool_init(f->pool) < 0) { + snd_seq_pool_delete(&f->pool); + kfree(f); + return NULL; + } + + spin_lock_init(&f->lock); + snd_use_lock_init(&f->use_lock); + init_waitqueue_head(&f->input_sleep); + atomic_set(&f->overflow, 0); + + f->head = NULL; + f->tail = NULL; + f->cells = 0; + + return f; +} + +void snd_seq_fifo_delete(struct snd_seq_fifo **fifo) +{ + struct snd_seq_fifo *f; + + if (snd_BUG_ON(!fifo)) + return; + f = *fifo; + if (snd_BUG_ON(!f)) + return; + *fifo = NULL; + + if (f->pool) + snd_seq_pool_mark_closing(f->pool); + + snd_seq_fifo_clear(f); + + /* wake up clients if any */ + if (waitqueue_active(&f->input_sleep)) + wake_up(&f->input_sleep); + + /* release resources...*/ + /*....................*/ + + if (f->pool) { + snd_seq_pool_done(f->pool); + snd_seq_pool_delete(&f->pool); + } + + kfree(f); +} + +static struct snd_seq_event_cell *fifo_cell_out(struct snd_seq_fifo *f); + +/* clear queue */ +void snd_seq_fifo_clear(struct snd_seq_fifo *f) +{ + struct snd_seq_event_cell *cell; + + /* clear overflow flag */ + atomic_set(&f->overflow, 0); + + snd_use_lock_sync(&f->use_lock); + spin_lock_irq(&f->lock); + /* drain the fifo */ + while ((cell = fifo_cell_out(f)) != NULL) { + snd_seq_cell_free(cell); + } + spin_unlock_irq(&f->lock); +} + + +/* enqueue event to fifo */ +int snd_seq_fifo_event_in(struct snd_seq_fifo *f, + struct snd_seq_event *event) +{ + struct snd_seq_event_cell *cell; + unsigned long flags; + int err; + + if (snd_BUG_ON(!f)) + return -EINVAL; + + snd_use_lock_use(&f->use_lock); + err = snd_seq_event_dup(f->pool, event, &cell, 1, NULL, NULL); /* always non-blocking */ + if (err < 0) { + if ((err == -ENOMEM) || (err == -EAGAIN)) + atomic_inc(&f->overflow); + snd_use_lock_free(&f->use_lock); + return err; + } + + /* append new cells to fifo */ + spin_lock_irqsave(&f->lock, flags); + if (f->tail != NULL) + f->tail->next = cell; + f->tail = cell; + if (f->head == NULL) + f->head = cell; + cell->next = NULL; + f->cells++; + spin_unlock_irqrestore(&f->lock, flags); + + /* wakeup client */ + if (waitqueue_active(&f->input_sleep)) + wake_up(&f->input_sleep); + + snd_use_lock_free(&f->use_lock); + + return 0; /* success */ + +} + +/* dequeue cell from fifo */ +static struct snd_seq_event_cell *fifo_cell_out(struct snd_seq_fifo *f) +{ + struct snd_seq_event_cell *cell; + + cell = f->head; + if (cell) { + f->head = cell->next; + + /* reset tail if this was the last element */ + if (f->tail == cell) + f->tail = NULL; + + cell->next = NULL; + f->cells--; + } + + return cell; +} + +/* dequeue cell from fifo and copy on user space */ +int snd_seq_fifo_cell_out(struct snd_seq_fifo *f, + struct snd_seq_event_cell **cellp, int nonblock) +{ + struct snd_seq_event_cell *cell; + unsigned long flags; + wait_queue_entry_t wait; + + if (snd_BUG_ON(!f)) + return -EINVAL; + + *cellp = NULL; + init_waitqueue_entry(&wait, current); + spin_lock_irqsave(&f->lock, flags); + while ((cell = fifo_cell_out(f)) == NULL) { + if (nonblock) { + /* non-blocking - return immediately */ + spin_unlock_irqrestore(&f->lock, flags); + return -EAGAIN; + } + set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&f->input_sleep, &wait); + spin_unlock_irqrestore(&f->lock, flags); + schedule(); + spin_lock_irqsave(&f->lock, flags); + remove_wait_queue(&f->input_sleep, &wait); + if (signal_pending(current)) { + spin_unlock_irqrestore(&f->lock, flags); + return -ERESTARTSYS; + } + } + spin_unlock_irqrestore(&f->lock, flags); + *cellp = cell; + + return 0; +} + + +void snd_seq_fifo_cell_putback(struct snd_seq_fifo *f, + struct snd_seq_event_cell *cell) +{ + unsigned long flags; + + if (cell) { + spin_lock_irqsave(&f->lock, flags); + cell->next = f->head; + f->head = cell; + if (!f->tail) + f->tail = cell; + f->cells++; + spin_unlock_irqrestore(&f->lock, flags); + } +} + + +/* polling; return non-zero if queue is available */ +int snd_seq_fifo_poll_wait(struct snd_seq_fifo *f, struct file *file, + poll_table *wait) +{ + poll_wait(file, &f->input_sleep, wait); + return (f->cells > 0); +} + +/* change the size of pool; all old events are removed */ +int snd_seq_fifo_resize(struct snd_seq_fifo *f, int poolsize) +{ + struct snd_seq_pool *newpool, *oldpool; + struct snd_seq_event_cell *cell, *next, *oldhead; + + if (snd_BUG_ON(!f || !f->pool)) + return -EINVAL; + + /* allocate new pool */ + newpool = snd_seq_pool_new(poolsize); + if (newpool == NULL) + return -ENOMEM; + if (snd_seq_pool_init(newpool) < 0) { + snd_seq_pool_delete(&newpool); + return -ENOMEM; + } + + spin_lock_irq(&f->lock); + /* remember old pool */ + oldpool = f->pool; + oldhead = f->head; + /* exchange pools */ + f->pool = newpool; + f->head = NULL; + f->tail = NULL; + f->cells = 0; + /* NOTE: overflow flag is not cleared */ + spin_unlock_irq(&f->lock); + + /* close the old pool and wait until all users are gone */ + snd_seq_pool_mark_closing(oldpool); + snd_use_lock_sync(&f->use_lock); + + /* release cells in old pool */ + for (cell = oldhead; cell; cell = next) { + next = cell->next; + snd_seq_cell_free(cell); + } + snd_seq_pool_delete(&oldpool); + + return 0; +} + +/* get the number of unused cells safely */ +int snd_seq_fifo_unused_cells(struct snd_seq_fifo *f) +{ + unsigned long flags; + int cells; + + if (!f) + return 0; + + snd_use_lock_use(&f->use_lock); + spin_lock_irqsave(&f->lock, flags); + cells = snd_seq_unused_cells(f->pool); + spin_unlock_irqrestore(&f->lock, flags); + snd_use_lock_free(&f->use_lock); + return cells; +} diff --git a/sound/core/seq/seq_fifo.h b/sound/core/seq/seq_fifo.h new file mode 100644 index 000000000..b56a7b897 --- /dev/null +++ b/sound/core/seq/seq_fifo.h @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * ALSA sequencer FIFO + * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl> + */ +#ifndef __SND_SEQ_FIFO_H +#define __SND_SEQ_FIFO_H + +#include "seq_memory.h" +#include "seq_lock.h" + + +/* === FIFO === */ + +struct snd_seq_fifo { + struct snd_seq_pool *pool; /* FIFO pool */ + struct snd_seq_event_cell *head; /* pointer to head of fifo */ + struct snd_seq_event_cell *tail; /* pointer to tail of fifo */ + int cells; + spinlock_t lock; + snd_use_lock_t use_lock; + wait_queue_head_t input_sleep; + atomic_t overflow; + +}; + +/* create new fifo (constructor) */ +struct snd_seq_fifo *snd_seq_fifo_new(int poolsize); + +/* delete fifo (destructor) */ +void snd_seq_fifo_delete(struct snd_seq_fifo **f); + + +/* enqueue event to fifo */ +int snd_seq_fifo_event_in(struct snd_seq_fifo *f, struct snd_seq_event *event); + +/* lock fifo from release */ +#define snd_seq_fifo_lock(fifo) snd_use_lock_use(&(fifo)->use_lock) +#define snd_seq_fifo_unlock(fifo) snd_use_lock_free(&(fifo)->use_lock) + +/* get a cell from fifo - fifo should be locked */ +int snd_seq_fifo_cell_out(struct snd_seq_fifo *f, struct snd_seq_event_cell **cellp, int nonblock); + +/* free dequeued cell - fifo should be locked */ +void snd_seq_fifo_cell_putback(struct snd_seq_fifo *f, struct snd_seq_event_cell *cell); + +/* clean up queue */ +void snd_seq_fifo_clear(struct snd_seq_fifo *f); + +/* polling */ +int snd_seq_fifo_poll_wait(struct snd_seq_fifo *f, struct file *file, poll_table *wait); + +/* resize pool in fifo */ +int snd_seq_fifo_resize(struct snd_seq_fifo *f, int poolsize); + +/* get the number of unused cells safely */ +int snd_seq_fifo_unused_cells(struct snd_seq_fifo *f); + +#endif diff --git a/sound/core/seq/seq_info.c b/sound/core/seq/seq_info.c new file mode 100644 index 000000000..3e9fce7be --- /dev/null +++ b/sound/core/seq/seq_info.c @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA sequencer /proc interface + * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl> + */ + +#include <linux/init.h> +#include <linux/export.h> +#include <sound/core.h> + +#include "seq_info.h" +#include "seq_clientmgr.h" +#include "seq_timer.h" + +static struct snd_info_entry *queues_entry; +static struct snd_info_entry *clients_entry; +static struct snd_info_entry *timer_entry; + + +static struct snd_info_entry * __init +create_info_entry(char *name, void (*read)(struct snd_info_entry *, + struct snd_info_buffer *)) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_module_entry(THIS_MODULE, name, snd_seq_root); + if (entry == NULL) + return NULL; + entry->content = SNDRV_INFO_CONTENT_TEXT; + entry->c.text.read = read; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + return NULL; + } + return entry; +} + +void snd_seq_info_done(void) +{ + snd_info_free_entry(queues_entry); + snd_info_free_entry(clients_entry); + snd_info_free_entry(timer_entry); +} + +/* create all our /proc entries */ +int __init snd_seq_info_init(void) +{ + queues_entry = create_info_entry("queues", + snd_seq_info_queues_read); + clients_entry = create_info_entry("clients", + snd_seq_info_clients_read); + timer_entry = create_info_entry("timer", snd_seq_info_timer_read); + if (!queues_entry || !clients_entry || !timer_entry) + goto error; + return 0; + + error: + snd_seq_info_done(); + return -ENOMEM; +} diff --git a/sound/core/seq/seq_info.h b/sound/core/seq/seq_info.h new file mode 100644 index 000000000..576cf0522 --- /dev/null +++ b/sound/core/seq/seq_info.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * ALSA sequencer /proc info + * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl> + */ +#ifndef __SND_SEQ_INFO_H +#define __SND_SEQ_INFO_H + +#include <sound/info.h> +#include <sound/seq_kernel.h> + +void snd_seq_info_clients_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer); +void snd_seq_info_timer_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer); +void snd_seq_info_queues_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer); + + +#ifdef CONFIG_SND_PROC_FS +int snd_seq_info_init(void); +void snd_seq_info_done(void); +#else +static inline int snd_seq_info_init(void) { return 0; } +static inline void snd_seq_info_done(void) {} +#endif + +#endif diff --git a/sound/core/seq/seq_lock.c b/sound/core/seq/seq_lock.c new file mode 100644 index 000000000..48b4ffb4b --- /dev/null +++ b/sound/core/seq/seq_lock.c @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Do sleep inside a spin-lock + * Copyright (c) 1999 by Takashi Iwai <tiwai@suse.de> + */ + +#include <linux/export.h> +#include <sound/core.h> +#include "seq_lock.h" + +/* wait until all locks are released */ +void snd_use_lock_sync_helper(snd_use_lock_t *lockp, const char *file, int line) +{ + int warn_count = 5 * HZ; + + if (atomic_read(lockp) < 0) { + pr_warn("ALSA: seq_lock: lock trouble [counter = %d] in %s:%d\n", atomic_read(lockp), file, line); + return; + } + while (atomic_read(lockp) > 0) { + if (warn_count-- == 0) + pr_warn("ALSA: seq_lock: waiting [%d left] in %s:%d\n", atomic_read(lockp), file, line); + schedule_timeout_uninterruptible(1); + } +} +EXPORT_SYMBOL(snd_use_lock_sync_helper); diff --git a/sound/core/seq/seq_lock.h b/sound/core/seq/seq_lock.h new file mode 100644 index 000000000..a973860eb --- /dev/null +++ b/sound/core/seq/seq_lock.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __SND_SEQ_LOCK_H +#define __SND_SEQ_LOCK_H + +#include <linux/sched.h> + +typedef atomic_t snd_use_lock_t; + +/* initialize lock */ +#define snd_use_lock_init(lockp) atomic_set(lockp, 0) + +/* increment lock */ +#define snd_use_lock_use(lockp) atomic_inc(lockp) + +/* release lock */ +#define snd_use_lock_free(lockp) atomic_dec(lockp) + +/* wait until all locks are released */ +void snd_use_lock_sync_helper(snd_use_lock_t *lock, const char *file, int line); +#define snd_use_lock_sync(lockp) snd_use_lock_sync_helper(lockp, __BASE_FILE__, __LINE__) + +#endif /* __SND_SEQ_LOCK_H */ diff --git a/sound/core/seq/seq_memory.c b/sound/core/seq/seq_memory.c new file mode 100644 index 000000000..47ef6bc30 --- /dev/null +++ b/sound/core/seq/seq_memory.c @@ -0,0 +1,507 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA sequencer Memory Manager + * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl> + * Jaroslav Kysela <perex@perex.cz> + * 2000 by Takashi Iwai <tiwai@suse.de> + */ + +#include <linux/init.h> +#include <linux/export.h> +#include <linux/slab.h> +#include <linux/sched/signal.h> +#include <linux/mm.h> +#include <sound/core.h> + +#include <sound/seq_kernel.h> +#include "seq_memory.h" +#include "seq_queue.h" +#include "seq_info.h" +#include "seq_lock.h" + +static inline int snd_seq_pool_available(struct snd_seq_pool *pool) +{ + return pool->total_elements - atomic_read(&pool->counter); +} + +static inline int snd_seq_output_ok(struct snd_seq_pool *pool) +{ + return snd_seq_pool_available(pool) >= pool->room; +} + +/* + * Variable length event: + * The event like sysex uses variable length type. + * The external data may be stored in three different formats. + * 1) kernel space + * This is the normal case. + * ext.data.len = length + * ext.data.ptr = buffer pointer + * 2) user space + * When an event is generated via read(), the external data is + * kept in user space until expanded. + * ext.data.len = length | SNDRV_SEQ_EXT_USRPTR + * ext.data.ptr = userspace pointer + * 3) chained cells + * When the variable length event is enqueued (in prioq or fifo), + * the external data is decomposed to several cells. + * ext.data.len = length | SNDRV_SEQ_EXT_CHAINED + * ext.data.ptr = the additiona cell head + * -> cell.next -> cell.next -> .. + */ + +/* + * exported: + * call dump function to expand external data. + */ + +static int get_var_len(const struct snd_seq_event *event) +{ + if ((event->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARIABLE) + return -EINVAL; + + return event->data.ext.len & ~SNDRV_SEQ_EXT_MASK; +} + +int snd_seq_dump_var_event(const struct snd_seq_event *event, + snd_seq_dump_func_t func, void *private_data) +{ + int len, err; + struct snd_seq_event_cell *cell; + + len = get_var_len(event); + if (len <= 0) + return len; + + if (event->data.ext.len & SNDRV_SEQ_EXT_USRPTR) { + char buf[32]; + char __user *curptr = (char __force __user *)event->data.ext.ptr; + while (len > 0) { + int size = sizeof(buf); + if (len < size) + size = len; + if (copy_from_user(buf, curptr, size)) + return -EFAULT; + err = func(private_data, buf, size); + if (err < 0) + return err; + curptr += size; + len -= size; + } + return 0; + } + if (!(event->data.ext.len & SNDRV_SEQ_EXT_CHAINED)) + return func(private_data, event->data.ext.ptr, len); + + cell = (struct snd_seq_event_cell *)event->data.ext.ptr; + for (; len > 0 && cell; cell = cell->next) { + int size = sizeof(struct snd_seq_event); + if (len < size) + size = len; + err = func(private_data, &cell->event, size); + if (err < 0) + return err; + len -= size; + } + return 0; +} +EXPORT_SYMBOL(snd_seq_dump_var_event); + + +/* + * exported: + * expand the variable length event to linear buffer space. + */ + +static int seq_copy_in_kernel(void *ptr, void *src, int size) +{ + char **bufptr = ptr; + + memcpy(*bufptr, src, size); + *bufptr += size; + return 0; +} + +static int seq_copy_in_user(void *ptr, void *src, int size) +{ + char __user **bufptr = ptr; + + if (copy_to_user(*bufptr, src, size)) + return -EFAULT; + *bufptr += size; + return 0; +} + +int snd_seq_expand_var_event(const struct snd_seq_event *event, int count, char *buf, + int in_kernel, int size_aligned) +{ + int len, newlen; + int err; + + len = get_var_len(event); + if (len < 0) + return len; + newlen = len; + if (size_aligned > 0) + newlen = roundup(len, size_aligned); + if (count < newlen) + return -EAGAIN; + + if (event->data.ext.len & SNDRV_SEQ_EXT_USRPTR) { + if (! in_kernel) + return -EINVAL; + if (copy_from_user(buf, (void __force __user *)event->data.ext.ptr, len)) + return -EFAULT; + return newlen; + } + err = snd_seq_dump_var_event(event, + in_kernel ? seq_copy_in_kernel : seq_copy_in_user, + &buf); + return err < 0 ? err : newlen; +} +EXPORT_SYMBOL(snd_seq_expand_var_event); + +/* + * release this cell, free extended data if available + */ + +static inline void free_cell(struct snd_seq_pool *pool, + struct snd_seq_event_cell *cell) +{ + cell->next = pool->free; + pool->free = cell; + atomic_dec(&pool->counter); +} + +void snd_seq_cell_free(struct snd_seq_event_cell * cell) +{ + unsigned long flags; + struct snd_seq_pool *pool; + + if (snd_BUG_ON(!cell)) + return; + pool = cell->pool; + if (snd_BUG_ON(!pool)) + return; + + spin_lock_irqsave(&pool->lock, flags); + free_cell(pool, cell); + if (snd_seq_ev_is_variable(&cell->event)) { + if (cell->event.data.ext.len & SNDRV_SEQ_EXT_CHAINED) { + struct snd_seq_event_cell *curp, *nextptr; + curp = cell->event.data.ext.ptr; + for (; curp; curp = nextptr) { + nextptr = curp->next; + curp->next = pool->free; + free_cell(pool, curp); + } + } + } + if (waitqueue_active(&pool->output_sleep)) { + /* has enough space now? */ + if (snd_seq_output_ok(pool)) + wake_up(&pool->output_sleep); + } + spin_unlock_irqrestore(&pool->lock, flags); +} + + +/* + * allocate an event cell. + */ +static int snd_seq_cell_alloc(struct snd_seq_pool *pool, + struct snd_seq_event_cell **cellp, + int nonblock, struct file *file, + struct mutex *mutexp) +{ + struct snd_seq_event_cell *cell; + unsigned long flags; + int err = -EAGAIN; + wait_queue_entry_t wait; + + if (pool == NULL) + return -EINVAL; + + *cellp = NULL; + + init_waitqueue_entry(&wait, current); + spin_lock_irqsave(&pool->lock, flags); + if (pool->ptr == NULL) { /* not initialized */ + pr_debug("ALSA: seq: pool is not initialized\n"); + err = -EINVAL; + goto __error; + } + while (pool->free == NULL && ! nonblock && ! pool->closing) { + + set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&pool->output_sleep, &wait); + spin_unlock_irqrestore(&pool->lock, flags); + if (mutexp) + mutex_unlock(mutexp); + schedule(); + if (mutexp) + mutex_lock(mutexp); + spin_lock_irqsave(&pool->lock, flags); + remove_wait_queue(&pool->output_sleep, &wait); + /* interrupted? */ + if (signal_pending(current)) { + err = -ERESTARTSYS; + goto __error; + } + } + if (pool->closing) { /* closing.. */ + err = -ENOMEM; + goto __error; + } + + cell = pool->free; + if (cell) { + int used; + pool->free = cell->next; + atomic_inc(&pool->counter); + used = atomic_read(&pool->counter); + if (pool->max_used < used) + pool->max_used = used; + pool->event_alloc_success++; + /* clear cell pointers */ + cell->next = NULL; + err = 0; + } else + pool->event_alloc_failures++; + *cellp = cell; + +__error: + spin_unlock_irqrestore(&pool->lock, flags); + return err; +} + + +/* + * duplicate the event to a cell. + * if the event has external data, the data is decomposed to additional + * cells. + */ +int snd_seq_event_dup(struct snd_seq_pool *pool, struct snd_seq_event *event, + struct snd_seq_event_cell **cellp, int nonblock, + struct file *file, struct mutex *mutexp) +{ + int ncells, err; + unsigned int extlen; + struct snd_seq_event_cell *cell; + + *cellp = NULL; + + ncells = 0; + extlen = 0; + if (snd_seq_ev_is_variable(event)) { + extlen = event->data.ext.len & ~SNDRV_SEQ_EXT_MASK; + ncells = DIV_ROUND_UP(extlen, sizeof(struct snd_seq_event)); + } + if (ncells >= pool->total_elements) + return -ENOMEM; + + err = snd_seq_cell_alloc(pool, &cell, nonblock, file, mutexp); + if (err < 0) + return err; + + /* copy the event */ + cell->event = *event; + + /* decompose */ + if (snd_seq_ev_is_variable(event)) { + int len = extlen; + int is_chained = event->data.ext.len & SNDRV_SEQ_EXT_CHAINED; + int is_usrptr = event->data.ext.len & SNDRV_SEQ_EXT_USRPTR; + struct snd_seq_event_cell *src, *tmp, *tail; + char *buf; + + cell->event.data.ext.len = extlen | SNDRV_SEQ_EXT_CHAINED; + cell->event.data.ext.ptr = NULL; + + src = (struct snd_seq_event_cell *)event->data.ext.ptr; + buf = (char *)event->data.ext.ptr; + tail = NULL; + + while (ncells-- > 0) { + int size = sizeof(struct snd_seq_event); + if (len < size) + size = len; + err = snd_seq_cell_alloc(pool, &tmp, nonblock, file, + mutexp); + if (err < 0) + goto __error; + if (cell->event.data.ext.ptr == NULL) + cell->event.data.ext.ptr = tmp; + if (tail) + tail->next = tmp; + tail = tmp; + /* copy chunk */ + if (is_chained && src) { + tmp->event = src->event; + src = src->next; + } else if (is_usrptr) { + if (copy_from_user(&tmp->event, (char __force __user *)buf, size)) { + err = -EFAULT; + goto __error; + } + } else { + memcpy(&tmp->event, buf, size); + } + buf += size; + len -= size; + } + } + + *cellp = cell; + return 0; + +__error: + snd_seq_cell_free(cell); + return err; +} + + +/* poll wait */ +int snd_seq_pool_poll_wait(struct snd_seq_pool *pool, struct file *file, + poll_table *wait) +{ + poll_wait(file, &pool->output_sleep, wait); + return snd_seq_output_ok(pool); +} + + +/* allocate room specified number of events */ +int snd_seq_pool_init(struct snd_seq_pool *pool) +{ + int cell; + struct snd_seq_event_cell *cellptr; + + if (snd_BUG_ON(!pool)) + return -EINVAL; + + cellptr = kvmalloc_array(sizeof(struct snd_seq_event_cell), pool->size, + GFP_KERNEL); + if (!cellptr) + return -ENOMEM; + + /* add new cells to the free cell list */ + spin_lock_irq(&pool->lock); + if (pool->ptr) { + spin_unlock_irq(&pool->lock); + kvfree(cellptr); + return 0; + } + + pool->ptr = cellptr; + pool->free = NULL; + + for (cell = 0; cell < pool->size; cell++) { + cellptr = pool->ptr + cell; + cellptr->pool = pool; + cellptr->next = pool->free; + pool->free = cellptr; + } + pool->room = (pool->size + 1) / 2; + + /* init statistics */ + pool->max_used = 0; + pool->total_elements = pool->size; + spin_unlock_irq(&pool->lock); + return 0; +} + +/* refuse the further insertion to the pool */ +void snd_seq_pool_mark_closing(struct snd_seq_pool *pool) +{ + unsigned long flags; + + if (snd_BUG_ON(!pool)) + return; + spin_lock_irqsave(&pool->lock, flags); + pool->closing = 1; + spin_unlock_irqrestore(&pool->lock, flags); +} + +/* remove events */ +int snd_seq_pool_done(struct snd_seq_pool *pool) +{ + struct snd_seq_event_cell *ptr; + + if (snd_BUG_ON(!pool)) + return -EINVAL; + + /* wait for closing all threads */ + if (waitqueue_active(&pool->output_sleep)) + wake_up(&pool->output_sleep); + + while (atomic_read(&pool->counter) > 0) + schedule_timeout_uninterruptible(1); + + /* release all resources */ + spin_lock_irq(&pool->lock); + ptr = pool->ptr; + pool->ptr = NULL; + pool->free = NULL; + pool->total_elements = 0; + spin_unlock_irq(&pool->lock); + + kvfree(ptr); + + spin_lock_irq(&pool->lock); + pool->closing = 0; + spin_unlock_irq(&pool->lock); + + return 0; +} + + +/* init new memory pool */ +struct snd_seq_pool *snd_seq_pool_new(int poolsize) +{ + struct snd_seq_pool *pool; + + /* create pool block */ + pool = kzalloc(sizeof(*pool), GFP_KERNEL); + if (!pool) + return NULL; + spin_lock_init(&pool->lock); + pool->ptr = NULL; + pool->free = NULL; + pool->total_elements = 0; + atomic_set(&pool->counter, 0); + pool->closing = 0; + init_waitqueue_head(&pool->output_sleep); + + pool->size = poolsize; + + /* init statistics */ + pool->max_used = 0; + return pool; +} + +/* remove memory pool */ +int snd_seq_pool_delete(struct snd_seq_pool **ppool) +{ + struct snd_seq_pool *pool = *ppool; + + *ppool = NULL; + if (pool == NULL) + return 0; + snd_seq_pool_mark_closing(pool); + snd_seq_pool_done(pool); + kfree(pool); + return 0; +} + +/* exported to seq_clientmgr.c */ +void snd_seq_info_pool(struct snd_info_buffer *buffer, + struct snd_seq_pool *pool, char *space) +{ + if (pool == NULL) + return; + snd_iprintf(buffer, "%sPool size : %d\n", space, pool->total_elements); + snd_iprintf(buffer, "%sCells in use : %d\n", space, atomic_read(&pool->counter)); + snd_iprintf(buffer, "%sPeak cells in use : %d\n", space, pool->max_used); + snd_iprintf(buffer, "%sAlloc success : %d\n", space, pool->event_alloc_success); + snd_iprintf(buffer, "%sAlloc failures : %d\n", space, pool->event_alloc_failures); +} diff --git a/sound/core/seq/seq_memory.h b/sound/core/seq/seq_memory.h new file mode 100644 index 000000000..7d7ff80f9 --- /dev/null +++ b/sound/core/seq/seq_memory.h @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * ALSA sequencer Memory Manager + * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl> + */ +#ifndef __SND_SEQ_MEMORYMGR_H +#define __SND_SEQ_MEMORYMGR_H + +#include <sound/seq_kernel.h> +#include <linux/poll.h> + +struct snd_info_buffer; + +/* container for sequencer event (internal use) */ +struct snd_seq_event_cell { + struct snd_seq_event event; + struct snd_seq_pool *pool; /* used pool */ + struct snd_seq_event_cell *next; /* next cell */ +}; + +/* design note: the pool is a contiguous block of memory, if we dynamicly + want to add additional cells to the pool be better store this in another + pool as we need to know the base address of the pool when releasing + memory. */ + +struct snd_seq_pool { + struct snd_seq_event_cell *ptr; /* pointer to first event chunk */ + struct snd_seq_event_cell *free; /* pointer to the head of the free list */ + + int total_elements; /* pool size actually allocated */ + atomic_t counter; /* cells free */ + + int size; /* pool size to be allocated */ + int room; /* watermark for sleep/wakeup */ + + int closing; + + /* statistics */ + int max_used; + int event_alloc_nopool; + int event_alloc_failures; + int event_alloc_success; + + /* Write locking */ + wait_queue_head_t output_sleep; + + /* Pool lock */ + spinlock_t lock; +}; + +void snd_seq_cell_free(struct snd_seq_event_cell *cell); + +int snd_seq_event_dup(struct snd_seq_pool *pool, struct snd_seq_event *event, + struct snd_seq_event_cell **cellp, int nonblock, + struct file *file, struct mutex *mutexp); + +/* return number of unused (free) cells */ +static inline int snd_seq_unused_cells(struct snd_seq_pool *pool) +{ + return pool ? pool->total_elements - atomic_read(&pool->counter) : 0; +} + +/* return total number of allocated cells */ +static inline int snd_seq_total_cells(struct snd_seq_pool *pool) +{ + return pool ? pool->total_elements : 0; +} + +/* init pool - allocate events */ +int snd_seq_pool_init(struct snd_seq_pool *pool); + +/* done pool - free events */ +void snd_seq_pool_mark_closing(struct snd_seq_pool *pool); +int snd_seq_pool_done(struct snd_seq_pool *pool); + +/* create pool */ +struct snd_seq_pool *snd_seq_pool_new(int poolsize); + +/* remove pool */ +int snd_seq_pool_delete(struct snd_seq_pool **pool); + +/* polling */ +int snd_seq_pool_poll_wait(struct snd_seq_pool *pool, struct file *file, poll_table *wait); + +void snd_seq_info_pool(struct snd_info_buffer *buffer, + struct snd_seq_pool *pool, char *space); + +#endif diff --git a/sound/core/seq/seq_midi.c b/sound/core/seq/seq_midi.c new file mode 100644 index 000000000..4589aac09 --- /dev/null +++ b/sound/core/seq/seq_midi.c @@ -0,0 +1,459 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Generic MIDI synth driver for ALSA sequencer + * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl> + * Jaroslav Kysela <perex@perex.cz> + */ + +/* +Possible options for midisynth module: + - automatic opening of midi ports on first received event or subscription + (close will be performed when client leaves) +*/ + + +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <sound/core.h> +#include <sound/rawmidi.h> +#include <sound/seq_kernel.h> +#include <sound/seq_device.h> +#include <sound/seq_midi_event.h> +#include <sound/initval.h> + +MODULE_AUTHOR("Frank van de Pol <fvdpol@coil.demon.nl>, Jaroslav Kysela <perex@perex.cz>"); +MODULE_DESCRIPTION("Advanced Linux Sound Architecture sequencer MIDI synth."); +MODULE_LICENSE("GPL"); +static int output_buffer_size = PAGE_SIZE; +module_param(output_buffer_size, int, 0644); +MODULE_PARM_DESC(output_buffer_size, "Output buffer size in bytes."); +static int input_buffer_size = PAGE_SIZE; +module_param(input_buffer_size, int, 0644); +MODULE_PARM_DESC(input_buffer_size, "Input buffer size in bytes."); + +/* data for this midi synth driver */ +struct seq_midisynth { + struct snd_card *card; + int device; + int subdevice; + struct snd_rawmidi_file input_rfile; + struct snd_rawmidi_file output_rfile; + int seq_client; + int seq_port; + struct snd_midi_event *parser; +}; + +struct seq_midisynth_client { + int seq_client; + int num_ports; + int ports_per_device[SNDRV_RAWMIDI_DEVICES]; + struct seq_midisynth *ports[SNDRV_RAWMIDI_DEVICES]; +}; + +static struct seq_midisynth_client *synths[SNDRV_CARDS]; +static DEFINE_MUTEX(register_mutex); + +/* handle rawmidi input event (MIDI v1.0 stream) */ +static void snd_midi_input_event(struct snd_rawmidi_substream *substream) +{ + struct snd_rawmidi_runtime *runtime; + struct seq_midisynth *msynth; + struct snd_seq_event ev; + char buf[16], *pbuf; + long res; + + if (substream == NULL) + return; + runtime = substream->runtime; + msynth = runtime->private_data; + if (msynth == NULL) + return; + memset(&ev, 0, sizeof(ev)); + while (runtime->avail > 0) { + res = snd_rawmidi_kernel_read(substream, buf, sizeof(buf)); + if (res <= 0) + continue; + if (msynth->parser == NULL) + continue; + pbuf = buf; + while (res-- > 0) { + if (!snd_midi_event_encode_byte(msynth->parser, + *pbuf++, &ev)) + continue; + ev.source.port = msynth->seq_port; + ev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS; + snd_seq_kernel_client_dispatch(msynth->seq_client, &ev, 1, 0); + /* clear event and reset header */ + memset(&ev, 0, sizeof(ev)); + } + } +} + +static int dump_midi(struct snd_rawmidi_substream *substream, const char *buf, int count) +{ + struct snd_rawmidi_runtime *runtime; + int tmp; + + if (snd_BUG_ON(!substream || !buf)) + return -EINVAL; + runtime = substream->runtime; + tmp = runtime->avail; + if (tmp < count) { + if (printk_ratelimit()) + pr_err("ALSA: seq_midi: MIDI output buffer overrun\n"); + return -ENOMEM; + } + if (snd_rawmidi_kernel_write(substream, buf, count) < count) + return -EINVAL; + return 0; +} + +static int event_process_midi(struct snd_seq_event *ev, int direct, + void *private_data, int atomic, int hop) +{ + struct seq_midisynth *msynth = private_data; + unsigned char msg[10]; /* buffer for constructing midi messages */ + struct snd_rawmidi_substream *substream; + int len; + + if (snd_BUG_ON(!msynth)) + return -EINVAL; + substream = msynth->output_rfile.output; + if (substream == NULL) + return -ENODEV; + if (ev->type == SNDRV_SEQ_EVENT_SYSEX) { /* special case, to save space */ + if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARIABLE) { + /* invalid event */ + pr_debug("ALSA: seq_midi: invalid sysex event flags = 0x%x\n", ev->flags); + return 0; + } + snd_seq_dump_var_event(ev, (snd_seq_dump_func_t)dump_midi, substream); + snd_midi_event_reset_decode(msynth->parser); + } else { + if (msynth->parser == NULL) + return -EIO; + len = snd_midi_event_decode(msynth->parser, msg, sizeof(msg), ev); + if (len < 0) + return 0; + if (dump_midi(substream, msg, len) < 0) + snd_midi_event_reset_decode(msynth->parser); + } + return 0; +} + + +static int snd_seq_midisynth_new(struct seq_midisynth *msynth, + struct snd_card *card, + int device, + int subdevice) +{ + if (snd_midi_event_new(MAX_MIDI_EVENT_BUF, &msynth->parser) < 0) + return -ENOMEM; + msynth->card = card; + msynth->device = device; + msynth->subdevice = subdevice; + return 0; +} + +/* open associated midi device for input */ +static int midisynth_subscribe(void *private_data, struct snd_seq_port_subscribe *info) +{ + int err; + struct seq_midisynth *msynth = private_data; + struct snd_rawmidi_runtime *runtime; + struct snd_rawmidi_params params; + + /* open midi port */ + err = snd_rawmidi_kernel_open(msynth->card, msynth->device, + msynth->subdevice, + SNDRV_RAWMIDI_LFLG_INPUT, + &msynth->input_rfile); + if (err < 0) { + pr_debug("ALSA: seq_midi: midi input open failed!!!\n"); + return err; + } + runtime = msynth->input_rfile.input->runtime; + memset(¶ms, 0, sizeof(params)); + params.avail_min = 1; + params.buffer_size = input_buffer_size; + err = snd_rawmidi_input_params(msynth->input_rfile.input, ¶ms); + if (err < 0) { + snd_rawmidi_kernel_release(&msynth->input_rfile); + return err; + } + snd_midi_event_reset_encode(msynth->parser); + runtime->event = snd_midi_input_event; + runtime->private_data = msynth; + snd_rawmidi_kernel_read(msynth->input_rfile.input, NULL, 0); + return 0; +} + +/* close associated midi device for input */ +static int midisynth_unsubscribe(void *private_data, struct snd_seq_port_subscribe *info) +{ + int err; + struct seq_midisynth *msynth = private_data; + + if (snd_BUG_ON(!msynth->input_rfile.input)) + return -EINVAL; + err = snd_rawmidi_kernel_release(&msynth->input_rfile); + return err; +} + +/* open associated midi device for output */ +static int midisynth_use(void *private_data, struct snd_seq_port_subscribe *info) +{ + int err; + struct seq_midisynth *msynth = private_data; + struct snd_rawmidi_params params; + + /* open midi port */ + err = snd_rawmidi_kernel_open(msynth->card, msynth->device, + msynth->subdevice, + SNDRV_RAWMIDI_LFLG_OUTPUT, + &msynth->output_rfile); + if (err < 0) { + pr_debug("ALSA: seq_midi: midi output open failed!!!\n"); + return err; + } + memset(¶ms, 0, sizeof(params)); + params.avail_min = 1; + params.buffer_size = output_buffer_size; + params.no_active_sensing = 1; + err = snd_rawmidi_output_params(msynth->output_rfile.output, ¶ms); + if (err < 0) { + snd_rawmidi_kernel_release(&msynth->output_rfile); + return err; + } + snd_midi_event_reset_decode(msynth->parser); + return 0; +} + +/* close associated midi device for output */ +static int midisynth_unuse(void *private_data, struct snd_seq_port_subscribe *info) +{ + struct seq_midisynth *msynth = private_data; + + if (snd_BUG_ON(!msynth->output_rfile.output)) + return -EINVAL; + snd_rawmidi_drain_output(msynth->output_rfile.output); + return snd_rawmidi_kernel_release(&msynth->output_rfile); +} + +/* delete given midi synth port */ +static void snd_seq_midisynth_delete(struct seq_midisynth *msynth) +{ + if (msynth == NULL) + return; + + if (msynth->seq_client > 0) { + /* delete port */ + snd_seq_event_port_detach(msynth->seq_client, msynth->seq_port); + } + + snd_midi_event_free(msynth->parser); +} + +/* register new midi synth port */ +static int +snd_seq_midisynth_probe(struct device *_dev) +{ + struct snd_seq_device *dev = to_seq_dev(_dev); + struct seq_midisynth_client *client; + struct seq_midisynth *msynth, *ms; + struct snd_seq_port_info *port; + struct snd_rawmidi_info *info; + struct snd_rawmidi *rmidi = dev->private_data; + int newclient = 0; + unsigned int p, ports; + struct snd_seq_port_callback pcallbacks; + struct snd_card *card = dev->card; + int device = dev->device; + unsigned int input_count = 0, output_count = 0; + + if (snd_BUG_ON(!card || device < 0 || device >= SNDRV_RAWMIDI_DEVICES)) + return -EINVAL; + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (! info) + return -ENOMEM; + info->device = device; + info->stream = SNDRV_RAWMIDI_STREAM_OUTPUT; + info->subdevice = 0; + if (snd_rawmidi_info_select(card, info) >= 0) + output_count = info->subdevices_count; + info->stream = SNDRV_RAWMIDI_STREAM_INPUT; + if (snd_rawmidi_info_select(card, info) >= 0) { + input_count = info->subdevices_count; + } + ports = output_count; + if (ports < input_count) + ports = input_count; + if (ports == 0) { + kfree(info); + return -ENODEV; + } + if (ports > (256 / SNDRV_RAWMIDI_DEVICES)) + ports = 256 / SNDRV_RAWMIDI_DEVICES; + + mutex_lock(®ister_mutex); + client = synths[card->number]; + if (client == NULL) { + newclient = 1; + client = kzalloc(sizeof(*client), GFP_KERNEL); + if (client == NULL) { + mutex_unlock(®ister_mutex); + kfree(info); + return -ENOMEM; + } + client->seq_client = + snd_seq_create_kernel_client( + card, 0, "%s", card->shortname[0] ? + (const char *)card->shortname : "External MIDI"); + if (client->seq_client < 0) { + kfree(client); + mutex_unlock(®ister_mutex); + kfree(info); + return -ENOMEM; + } + } + + msynth = kcalloc(ports, sizeof(struct seq_midisynth), GFP_KERNEL); + port = kmalloc(sizeof(*port), GFP_KERNEL); + if (msynth == NULL || port == NULL) + goto __nomem; + + for (p = 0; p < ports; p++) { + ms = &msynth[p]; + + if (snd_seq_midisynth_new(ms, card, device, p) < 0) + goto __nomem; + + /* declare port */ + memset(port, 0, sizeof(*port)); + port->addr.client = client->seq_client; + port->addr.port = device * (256 / SNDRV_RAWMIDI_DEVICES) + p; + port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT; + memset(info, 0, sizeof(*info)); + info->device = device; + if (p < output_count) + info->stream = SNDRV_RAWMIDI_STREAM_OUTPUT; + else + info->stream = SNDRV_RAWMIDI_STREAM_INPUT; + info->subdevice = p; + if (snd_rawmidi_info_select(card, info) >= 0) + strcpy(port->name, info->subname); + if (! port->name[0]) { + if (info->name[0]) { + if (ports > 1) + snprintf(port->name, sizeof(port->name), "%s-%u", info->name, p); + else + snprintf(port->name, sizeof(port->name), "%s", info->name); + } else { + /* last resort */ + if (ports > 1) + sprintf(port->name, "MIDI %d-%d-%u", card->number, device, p); + else + sprintf(port->name, "MIDI %d-%d", card->number, device); + } + } + if ((info->flags & SNDRV_RAWMIDI_INFO_OUTPUT) && p < output_count) + port->capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SYNC_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE; + if ((info->flags & SNDRV_RAWMIDI_INFO_INPUT) && p < input_count) + port->capability |= SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SYNC_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ; + if ((port->capability & (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_READ)) == (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_READ) && + info->flags & SNDRV_RAWMIDI_INFO_DUPLEX) + port->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX; + port->type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC + | SNDRV_SEQ_PORT_TYPE_HARDWARE + | SNDRV_SEQ_PORT_TYPE_PORT; + port->midi_channels = 16; + memset(&pcallbacks, 0, sizeof(pcallbacks)); + pcallbacks.owner = THIS_MODULE; + pcallbacks.private_data = ms; + pcallbacks.subscribe = midisynth_subscribe; + pcallbacks.unsubscribe = midisynth_unsubscribe; + pcallbacks.use = midisynth_use; + pcallbacks.unuse = midisynth_unuse; + pcallbacks.event_input = event_process_midi; + port->kernel = &pcallbacks; + if (rmidi->ops && rmidi->ops->get_port_info) + rmidi->ops->get_port_info(rmidi, p, port); + if (snd_seq_kernel_client_ctl(client->seq_client, SNDRV_SEQ_IOCTL_CREATE_PORT, port)<0) + goto __nomem; + ms->seq_client = client->seq_client; + ms->seq_port = port->addr.port; + } + client->ports_per_device[device] = ports; + client->ports[device] = msynth; + client->num_ports++; + if (newclient) + synths[card->number] = client; + mutex_unlock(®ister_mutex); + kfree(info); + kfree(port); + return 0; /* success */ + + __nomem: + if (msynth != NULL) { + for (p = 0; p < ports; p++) + snd_seq_midisynth_delete(&msynth[p]); + kfree(msynth); + } + if (newclient) { + snd_seq_delete_kernel_client(client->seq_client); + kfree(client); + } + kfree(info); + kfree(port); + mutex_unlock(®ister_mutex); + return -ENOMEM; +} + +/* release midi synth port */ +static int +snd_seq_midisynth_remove(struct device *_dev) +{ + struct snd_seq_device *dev = to_seq_dev(_dev); + struct seq_midisynth_client *client; + struct seq_midisynth *msynth; + struct snd_card *card = dev->card; + int device = dev->device, p, ports; + + mutex_lock(®ister_mutex); + client = synths[card->number]; + if (client == NULL || client->ports[device] == NULL) { + mutex_unlock(®ister_mutex); + return -ENODEV; + } + ports = client->ports_per_device[device]; + client->ports_per_device[device] = 0; + msynth = client->ports[device]; + client->ports[device] = NULL; + for (p = 0; p < ports; p++) + snd_seq_midisynth_delete(&msynth[p]); + kfree(msynth); + client->num_ports--; + if (client->num_ports <= 0) { + snd_seq_delete_kernel_client(client->seq_client); + synths[card->number] = NULL; + kfree(client); + } + mutex_unlock(®ister_mutex); + return 0; +} + +static struct snd_seq_driver seq_midisynth_driver = { + .driver = { + .name = KBUILD_MODNAME, + .probe = snd_seq_midisynth_probe, + .remove = snd_seq_midisynth_remove, + }, + .id = SNDRV_SEQ_DEV_ID_MIDISYNTH, + .argsize = 0, +}; + +module_snd_seq_driver(seq_midisynth_driver); diff --git a/sound/core/seq/seq_midi_emul.c b/sound/core/seq/seq_midi_emul.c new file mode 100644 index 000000000..81d2ef5e5 --- /dev/null +++ b/sound/core/seq/seq_midi_emul.c @@ -0,0 +1,723 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * GM/GS/XG midi module. + * + * Copyright (C) 1999 Steve Ratcliffe + * + * Based on awe_wave.c by Takashi Iwai + */ +/* + * This module is used to keep track of the current midi state. + * It can be used for drivers that are required to emulate midi when + * the hardware doesn't. + * + * It was written for a AWE64 driver, but there should be no AWE specific + * code in here. If there is it should be reported as a bug. + */ + +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/module.h> +#include <sound/core.h> +#include <sound/seq_kernel.h> +#include <sound/seq_midi_emul.h> +#include <sound/initval.h> +#include <sound/asoundef.h> + +MODULE_AUTHOR("Takashi Iwai / Steve Ratcliffe"); +MODULE_DESCRIPTION("Advanced Linux Sound Architecture sequencer MIDI emulation."); +MODULE_LICENSE("GPL"); + +/* Prototypes for static functions */ +static void note_off(const struct snd_midi_op *ops, void *drv, + struct snd_midi_channel *chan, + int note, int vel); +static void do_control(const struct snd_midi_op *ops, void *private, + struct snd_midi_channel_set *chset, + struct snd_midi_channel *chan, + int control, int value); +static void rpn(const struct snd_midi_op *ops, void *drv, + struct snd_midi_channel *chan, + struct snd_midi_channel_set *chset); +static void nrpn(const struct snd_midi_op *ops, void *drv, + struct snd_midi_channel *chan, + struct snd_midi_channel_set *chset); +static void sysex(const struct snd_midi_op *ops, void *private, + unsigned char *sysex, + int len, struct snd_midi_channel_set *chset); +static void all_sounds_off(const struct snd_midi_op *ops, void *private, + struct snd_midi_channel *chan); +static void all_notes_off(const struct snd_midi_op *ops, void *private, + struct snd_midi_channel *chan); +static void snd_midi_reset_controllers(struct snd_midi_channel *chan); +static void reset_all_channels(struct snd_midi_channel_set *chset); + + +/* + * Process an event in a driver independent way. This means dealing + * with RPN, NRPN, SysEx etc that are defined for common midi applications + * such as GM, GS and XG. + * There modes that this module will run in are: + * Generic MIDI - no interpretation at all, it will just save current values + * of controllers etc. + * GM - You can use all gm_ prefixed elements of chan. Controls, RPN, NRPN, + * SysEx will be interpreded as defined in General Midi. + * GS - You can use all gs_ prefixed elements of chan. Codes for GS will be + * interpreted. + * XG - You can use all xg_ prefixed elements of chan. Codes for XG will + * be interpreted. + */ +void +snd_midi_process_event(const struct snd_midi_op *ops, + struct snd_seq_event *ev, + struct snd_midi_channel_set *chanset) +{ + struct snd_midi_channel *chan; + void *drv; + int dest_channel = 0; + + if (ev == NULL || chanset == NULL) { + pr_debug("ALSA: seq_midi_emul: ev or chanbase NULL (snd_midi_process_event)\n"); + return; + } + if (chanset->channels == NULL) + return; + + if (snd_seq_ev_is_channel_type(ev)) { + dest_channel = ev->data.note.channel; + if (dest_channel >= chanset->max_channels) { + pr_debug("ALSA: seq_midi_emul: dest channel is %d, max is %d\n", + dest_channel, chanset->max_channels); + return; + } + } + + chan = chanset->channels + dest_channel; + drv = chanset->private_data; + + /* EVENT_NOTE should be processed before queued */ + if (ev->type == SNDRV_SEQ_EVENT_NOTE) + return; + + /* Make sure that we don't have a note on that should really be + * a note off */ + if (ev->type == SNDRV_SEQ_EVENT_NOTEON && ev->data.note.velocity == 0) + ev->type = SNDRV_SEQ_EVENT_NOTEOFF; + + /* Make sure the note is within array range */ + if (ev->type == SNDRV_SEQ_EVENT_NOTEON || + ev->type == SNDRV_SEQ_EVENT_NOTEOFF || + ev->type == SNDRV_SEQ_EVENT_KEYPRESS) { + if (ev->data.note.note >= 128) + return; + } + + switch (ev->type) { + case SNDRV_SEQ_EVENT_NOTEON: + if (chan->note[ev->data.note.note] & SNDRV_MIDI_NOTE_ON) { + if (ops->note_off) + ops->note_off(drv, ev->data.note.note, 0, chan); + } + chan->note[ev->data.note.note] = SNDRV_MIDI_NOTE_ON; + if (ops->note_on) + ops->note_on(drv, ev->data.note.note, ev->data.note.velocity, chan); + break; + case SNDRV_SEQ_EVENT_NOTEOFF: + if (! (chan->note[ev->data.note.note] & SNDRV_MIDI_NOTE_ON)) + break; + if (ops->note_off) + note_off(ops, drv, chan, ev->data.note.note, ev->data.note.velocity); + break; + case SNDRV_SEQ_EVENT_KEYPRESS: + if (ops->key_press) + ops->key_press(drv, ev->data.note.note, ev->data.note.velocity, chan); + break; + case SNDRV_SEQ_EVENT_CONTROLLER: + do_control(ops, drv, chanset, chan, + ev->data.control.param, ev->data.control.value); + break; + case SNDRV_SEQ_EVENT_PGMCHANGE: + chan->midi_program = ev->data.control.value; + break; + case SNDRV_SEQ_EVENT_PITCHBEND: + chan->midi_pitchbend = ev->data.control.value; + if (ops->control) + ops->control(drv, MIDI_CTL_PITCHBEND, chan); + break; + case SNDRV_SEQ_EVENT_CHANPRESS: + chan->midi_pressure = ev->data.control.value; + if (ops->control) + ops->control(drv, MIDI_CTL_CHAN_PRESSURE, chan); + break; + case SNDRV_SEQ_EVENT_CONTROL14: + /* Best guess is that this is any of the 14 bit controller values */ + if (ev->data.control.param < 32) { + /* set low part first */ + chan->control[ev->data.control.param + 32] = + ev->data.control.value & 0x7f; + do_control(ops, drv, chanset, chan, + ev->data.control.param, + ((ev->data.control.value>>7) & 0x7f)); + } else + do_control(ops, drv, chanset, chan, + ev->data.control.param, + ev->data.control.value); + break; + case SNDRV_SEQ_EVENT_NONREGPARAM: + /* Break it back into its controller values */ + chan->param_type = SNDRV_MIDI_PARAM_TYPE_NONREGISTERED; + chan->control[MIDI_CTL_MSB_DATA_ENTRY] + = (ev->data.control.value >> 7) & 0x7f; + chan->control[MIDI_CTL_LSB_DATA_ENTRY] + = ev->data.control.value & 0x7f; + chan->control[MIDI_CTL_NONREG_PARM_NUM_MSB] + = (ev->data.control.param >> 7) & 0x7f; + chan->control[MIDI_CTL_NONREG_PARM_NUM_LSB] + = ev->data.control.param & 0x7f; + nrpn(ops, drv, chan, chanset); + break; + case SNDRV_SEQ_EVENT_REGPARAM: + /* Break it back into its controller values */ + chan->param_type = SNDRV_MIDI_PARAM_TYPE_REGISTERED; + chan->control[MIDI_CTL_MSB_DATA_ENTRY] + = (ev->data.control.value >> 7) & 0x7f; + chan->control[MIDI_CTL_LSB_DATA_ENTRY] + = ev->data.control.value & 0x7f; + chan->control[MIDI_CTL_REGIST_PARM_NUM_MSB] + = (ev->data.control.param >> 7) & 0x7f; + chan->control[MIDI_CTL_REGIST_PARM_NUM_LSB] + = ev->data.control.param & 0x7f; + rpn(ops, drv, chan, chanset); + break; + case SNDRV_SEQ_EVENT_SYSEX: + if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) == SNDRV_SEQ_EVENT_LENGTH_VARIABLE) { + unsigned char sysexbuf[64]; + int len; + len = snd_seq_expand_var_event(ev, sizeof(sysexbuf), sysexbuf, 1, 0); + if (len > 0) + sysex(ops, drv, sysexbuf, len, chanset); + } + break; + case SNDRV_SEQ_EVENT_SONGPOS: + case SNDRV_SEQ_EVENT_SONGSEL: + case SNDRV_SEQ_EVENT_CLOCK: + case SNDRV_SEQ_EVENT_START: + case SNDRV_SEQ_EVENT_CONTINUE: + case SNDRV_SEQ_EVENT_STOP: + case SNDRV_SEQ_EVENT_QFRAME: + case SNDRV_SEQ_EVENT_TEMPO: + case SNDRV_SEQ_EVENT_TIMESIGN: + case SNDRV_SEQ_EVENT_KEYSIGN: + goto not_yet; + case SNDRV_SEQ_EVENT_SENSING: + break; + case SNDRV_SEQ_EVENT_CLIENT_START: + case SNDRV_SEQ_EVENT_CLIENT_EXIT: + case SNDRV_SEQ_EVENT_CLIENT_CHANGE: + case SNDRV_SEQ_EVENT_PORT_START: + case SNDRV_SEQ_EVENT_PORT_EXIT: + case SNDRV_SEQ_EVENT_PORT_CHANGE: + case SNDRV_SEQ_EVENT_ECHO: + not_yet: + default: + /*pr_debug("ALSA: seq_midi_emul: Unimplemented event %d\n", ev->type);*/ + break; + } +} +EXPORT_SYMBOL(snd_midi_process_event); + + +/* + * release note + */ +static void +note_off(const struct snd_midi_op *ops, void *drv, + struct snd_midi_channel *chan, + int note, int vel) +{ + if (chan->gm_hold) { + /* Hold this note until pedal is turned off */ + chan->note[note] |= SNDRV_MIDI_NOTE_RELEASED; + } else if (chan->note[note] & SNDRV_MIDI_NOTE_SOSTENUTO) { + /* Mark this note as release; it will be turned off when sostenuto + * is turned off */ + chan->note[note] |= SNDRV_MIDI_NOTE_RELEASED; + } else { + chan->note[note] = 0; + if (ops->note_off) + ops->note_off(drv, note, vel, chan); + } +} + +/* + * Do all driver independent operations for this controller and pass + * events that need to take place immediately to the driver. + */ +static void +do_control(const struct snd_midi_op *ops, void *drv, + struct snd_midi_channel_set *chset, + struct snd_midi_channel *chan, int control, int value) +{ + int i; + + if (control >= ARRAY_SIZE(chan->control)) + return; + + /* Switches */ + if ((control >=64 && control <=69) || (control >= 80 && control <= 83)) { + /* These are all switches; either off or on so set to 0 or 127 */ + value = (value >= 64)? 127: 0; + } + chan->control[control] = value; + + switch (control) { + case MIDI_CTL_SUSTAIN: + if (value == 0) { + /* Sustain has been released, turn off held notes */ + for (i = 0; i < 128; i++) { + if (chan->note[i] & SNDRV_MIDI_NOTE_RELEASED) { + chan->note[i] = SNDRV_MIDI_NOTE_OFF; + if (ops->note_off) + ops->note_off(drv, i, 0, chan); + } + } + } + break; + case MIDI_CTL_PORTAMENTO: + break; + case MIDI_CTL_SOSTENUTO: + if (value) { + /* Mark each note that is currently held down */ + for (i = 0; i < 128; i++) { + if (chan->note[i] & SNDRV_MIDI_NOTE_ON) + chan->note[i] |= SNDRV_MIDI_NOTE_SOSTENUTO; + } + } else { + /* release all notes that were held */ + for (i = 0; i < 128; i++) { + if (chan->note[i] & SNDRV_MIDI_NOTE_SOSTENUTO) { + chan->note[i] &= ~SNDRV_MIDI_NOTE_SOSTENUTO; + if (chan->note[i] & SNDRV_MIDI_NOTE_RELEASED) { + chan->note[i] = SNDRV_MIDI_NOTE_OFF; + if (ops->note_off) + ops->note_off(drv, i, 0, chan); + } + } + } + } + break; + case MIDI_CTL_MSB_DATA_ENTRY: + chan->control[MIDI_CTL_LSB_DATA_ENTRY] = 0; + fallthrough; + case MIDI_CTL_LSB_DATA_ENTRY: + if (chan->param_type == SNDRV_MIDI_PARAM_TYPE_REGISTERED) + rpn(ops, drv, chan, chset); + else + nrpn(ops, drv, chan, chset); + break; + case MIDI_CTL_REGIST_PARM_NUM_LSB: + case MIDI_CTL_REGIST_PARM_NUM_MSB: + chan->param_type = SNDRV_MIDI_PARAM_TYPE_REGISTERED; + break; + case MIDI_CTL_NONREG_PARM_NUM_LSB: + case MIDI_CTL_NONREG_PARM_NUM_MSB: + chan->param_type = SNDRV_MIDI_PARAM_TYPE_NONREGISTERED; + break; + + case MIDI_CTL_ALL_SOUNDS_OFF: + all_sounds_off(ops, drv, chan); + break; + + case MIDI_CTL_ALL_NOTES_OFF: + all_notes_off(ops, drv, chan); + break; + + case MIDI_CTL_MSB_BANK: + if (chset->midi_mode == SNDRV_MIDI_MODE_XG) { + if (value == 127) + chan->drum_channel = 1; + else + chan->drum_channel = 0; + } + break; + case MIDI_CTL_LSB_BANK: + break; + + case MIDI_CTL_RESET_CONTROLLERS: + snd_midi_reset_controllers(chan); + break; + + case MIDI_CTL_SOFT_PEDAL: + case MIDI_CTL_LEGATO_FOOTSWITCH: + case MIDI_CTL_HOLD2: + case MIDI_CTL_SC1_SOUND_VARIATION: + case MIDI_CTL_SC2_TIMBRE: + case MIDI_CTL_SC3_RELEASE_TIME: + case MIDI_CTL_SC4_ATTACK_TIME: + case MIDI_CTL_SC5_BRIGHTNESS: + case MIDI_CTL_E1_REVERB_DEPTH: + case MIDI_CTL_E2_TREMOLO_DEPTH: + case MIDI_CTL_E3_CHORUS_DEPTH: + case MIDI_CTL_E4_DETUNE_DEPTH: + case MIDI_CTL_E5_PHASER_DEPTH: + goto notyet; + notyet: + default: + if (ops->control) + ops->control(drv, control, chan); + break; + } +} + + +/* + * initialize the MIDI status + */ +void +snd_midi_channel_set_clear(struct snd_midi_channel_set *chset) +{ + int i; + + chset->midi_mode = SNDRV_MIDI_MODE_GM; + chset->gs_master_volume = 127; + + for (i = 0; i < chset->max_channels; i++) { + struct snd_midi_channel *chan = chset->channels + i; + memset(chan->note, 0, sizeof(chan->note)); + + chan->midi_aftertouch = 0; + chan->midi_pressure = 0; + chan->midi_program = 0; + chan->midi_pitchbend = 0; + snd_midi_reset_controllers(chan); + chan->gm_rpn_pitch_bend_range = 256; /* 2 semitones */ + chan->gm_rpn_fine_tuning = 0; + chan->gm_rpn_coarse_tuning = 0; + + if (i == 9) + chan->drum_channel = 1; + else + chan->drum_channel = 0; + } +} +EXPORT_SYMBOL(snd_midi_channel_set_clear); + +/* + * Process a rpn message. + */ +static void +rpn(const struct snd_midi_op *ops, void *drv, struct snd_midi_channel *chan, + struct snd_midi_channel_set *chset) +{ + int type; + int val; + + if (chset->midi_mode != SNDRV_MIDI_MODE_NONE) { + type = (chan->control[MIDI_CTL_REGIST_PARM_NUM_MSB] << 8) | + chan->control[MIDI_CTL_REGIST_PARM_NUM_LSB]; + val = (chan->control[MIDI_CTL_MSB_DATA_ENTRY] << 7) | + chan->control[MIDI_CTL_LSB_DATA_ENTRY]; + + switch (type) { + case 0x0000: /* Pitch bend sensitivity */ + /* MSB only / 1 semitone per 128 */ + chan->gm_rpn_pitch_bend_range = val; + break; + + case 0x0001: /* fine tuning: */ + /* MSB/LSB, 8192=center, 100/8192 cent step */ + chan->gm_rpn_fine_tuning = val - 8192; + break; + + case 0x0002: /* coarse tuning */ + /* MSB only / 8192=center, 1 semitone per 128 */ + chan->gm_rpn_coarse_tuning = val - 8192; + break; + + case 0x7F7F: /* "lock-in" RPN */ + /* ignored */ + break; + } + } + /* should call nrpn or rpn callback here.. */ +} + +/* + * Process an nrpn message. + */ +static void +nrpn(const struct snd_midi_op *ops, void *drv, struct snd_midi_channel *chan, + struct snd_midi_channel_set *chset) +{ + /* parse XG NRPNs here if possible */ + if (ops->nrpn) + ops->nrpn(drv, chan, chset); +} + + +/* + * convert channel parameter in GS sysex + */ +static int +get_channel(unsigned char cmd) +{ + int p = cmd & 0x0f; + if (p == 0) + p = 9; + else if (p < 10) + p--; + return p; +} + + +/* + * Process a sysex message. + */ +static void +sysex(const struct snd_midi_op *ops, void *private, unsigned char *buf, int len, + struct snd_midi_channel_set *chset) +{ + /* GM on */ + static const unsigned char gm_on_macro[] = { + 0x7e,0x7f,0x09,0x01, + }; + /* XG on */ + static const unsigned char xg_on_macro[] = { + 0x43,0x10,0x4c,0x00,0x00,0x7e,0x00, + }; + /* GS prefix + * drum channel: XX=0x1?(channel), YY=0x15, ZZ=on/off + * reverb mode: XX=0x01, YY=0x30, ZZ=0-7 + * chorus mode: XX=0x01, YY=0x38, ZZ=0-7 + * master vol: XX=0x00, YY=0x04, ZZ=0-127 + */ + static const unsigned char gs_pfx_macro[] = { + 0x41,0x10,0x42,0x12,0x40,/*XX,YY,ZZ*/ + }; + + int parsed = SNDRV_MIDI_SYSEX_NOT_PARSED; + + if (len <= 0 || buf[0] != 0xf0) + return; + /* skip first byte */ + buf++; + len--; + + /* GM on */ + if (len >= (int)sizeof(gm_on_macro) && + memcmp(buf, gm_on_macro, sizeof(gm_on_macro)) == 0) { + if (chset->midi_mode != SNDRV_MIDI_MODE_GS && + chset->midi_mode != SNDRV_MIDI_MODE_XG) { + chset->midi_mode = SNDRV_MIDI_MODE_GM; + reset_all_channels(chset); + parsed = SNDRV_MIDI_SYSEX_GM_ON; + } + } + + /* GS macros */ + else if (len >= 8 && + memcmp(buf, gs_pfx_macro, sizeof(gs_pfx_macro)) == 0) { + if (chset->midi_mode != SNDRV_MIDI_MODE_GS && + chset->midi_mode != SNDRV_MIDI_MODE_XG) + chset->midi_mode = SNDRV_MIDI_MODE_GS; + + if (buf[5] == 0x00 && buf[6] == 0x7f && buf[7] == 0x00) { + /* GS reset */ + parsed = SNDRV_MIDI_SYSEX_GS_RESET; + reset_all_channels(chset); + } + + else if ((buf[5] & 0xf0) == 0x10 && buf[6] == 0x15) { + /* drum pattern */ + int p = get_channel(buf[5]); + if (p < chset->max_channels) { + parsed = SNDRV_MIDI_SYSEX_GS_DRUM_CHANNEL; + if (buf[7]) + chset->channels[p].drum_channel = 1; + else + chset->channels[p].drum_channel = 0; + } + + } else if ((buf[5] & 0xf0) == 0x10 && buf[6] == 0x21) { + /* program */ + int p = get_channel(buf[5]); + if (p < chset->max_channels && + ! chset->channels[p].drum_channel) { + parsed = SNDRV_MIDI_SYSEX_GS_DRUM_CHANNEL; + chset->channels[p].midi_program = buf[7]; + } + + } else if (buf[5] == 0x01 && buf[6] == 0x30) { + /* reverb mode */ + parsed = SNDRV_MIDI_SYSEX_GS_REVERB_MODE; + chset->gs_reverb_mode = buf[7]; + + } else if (buf[5] == 0x01 && buf[6] == 0x38) { + /* chorus mode */ + parsed = SNDRV_MIDI_SYSEX_GS_CHORUS_MODE; + chset->gs_chorus_mode = buf[7]; + + } else if (buf[5] == 0x00 && buf[6] == 0x04) { + /* master volume */ + parsed = SNDRV_MIDI_SYSEX_GS_MASTER_VOLUME; + chset->gs_master_volume = buf[7]; + + } + } + + /* XG on */ + else if (len >= (int)sizeof(xg_on_macro) && + memcmp(buf, xg_on_macro, sizeof(xg_on_macro)) == 0) { + int i; + chset->midi_mode = SNDRV_MIDI_MODE_XG; + parsed = SNDRV_MIDI_SYSEX_XG_ON; + /* reset CC#0 for drums */ + for (i = 0; i < chset->max_channels; i++) { + if (chset->channels[i].drum_channel) + chset->channels[i].control[MIDI_CTL_MSB_BANK] = 127; + else + chset->channels[i].control[MIDI_CTL_MSB_BANK] = 0; + } + } + + if (ops->sysex) + ops->sysex(private, buf - 1, len + 1, parsed, chset); +} + +/* + * all sound off + */ +static void +all_sounds_off(const struct snd_midi_op *ops, void *drv, + struct snd_midi_channel *chan) +{ + int n; + + if (! ops->note_terminate) + return; + for (n = 0; n < 128; n++) { + if (chan->note[n]) { + ops->note_terminate(drv, n, chan); + chan->note[n] = 0; + } + } +} + +/* + * all notes off + */ +static void +all_notes_off(const struct snd_midi_op *ops, void *drv, + struct snd_midi_channel *chan) +{ + int n; + + if (! ops->note_off) + return; + for (n = 0; n < 128; n++) { + if (chan->note[n] == SNDRV_MIDI_NOTE_ON) + note_off(ops, drv, chan, n, 0); + } +} + +/* + * Initialise a single midi channel control block. + */ +static void snd_midi_channel_init(struct snd_midi_channel *p, int n) +{ + if (p == NULL) + return; + + memset(p, 0, sizeof(struct snd_midi_channel)); + p->private = NULL; + p->number = n; + + snd_midi_reset_controllers(p); + p->gm_rpn_pitch_bend_range = 256; /* 2 semitones */ + p->gm_rpn_fine_tuning = 0; + p->gm_rpn_coarse_tuning = 0; + + if (n == 9) + p->drum_channel = 1; /* Default ch 10 as drums */ +} + +/* + * Allocate and initialise a set of midi channel control blocks. + */ +static struct snd_midi_channel *snd_midi_channel_init_set(int n) +{ + struct snd_midi_channel *chan; + int i; + + chan = kmalloc_array(n, sizeof(struct snd_midi_channel), GFP_KERNEL); + if (chan) { + for (i = 0; i < n; i++) + snd_midi_channel_init(chan+i, i); + } + + return chan; +} + +/* + * reset all midi channels + */ +static void +reset_all_channels(struct snd_midi_channel_set *chset) +{ + int ch; + for (ch = 0; ch < chset->max_channels; ch++) { + struct snd_midi_channel *chan = chset->channels + ch; + snd_midi_reset_controllers(chan); + chan->gm_rpn_pitch_bend_range = 256; /* 2 semitones */ + chan->gm_rpn_fine_tuning = 0; + chan->gm_rpn_coarse_tuning = 0; + + if (ch == 9) + chan->drum_channel = 1; + else + chan->drum_channel = 0; + } +} + + +/* + * Allocate and initialise a midi channel set. + */ +struct snd_midi_channel_set *snd_midi_channel_alloc_set(int n) +{ + struct snd_midi_channel_set *chset; + + chset = kmalloc(sizeof(*chset), GFP_KERNEL); + if (chset) { + chset->channels = snd_midi_channel_init_set(n); + chset->private_data = NULL; + chset->max_channels = n; + } + return chset; +} +EXPORT_SYMBOL(snd_midi_channel_alloc_set); + +/* + * Reset the midi controllers on a particular channel to default values. + */ +static void snd_midi_reset_controllers(struct snd_midi_channel *chan) +{ + memset(chan->control, 0, sizeof(chan->control)); + chan->gm_volume = 127; + chan->gm_expression = 127; + chan->gm_pan = 64; +} + + +/* + * Free a midi channel set. + */ +void snd_midi_channel_free_set(struct snd_midi_channel_set *chset) +{ + if (chset == NULL) + return; + kfree(chset->channels); + kfree(chset); +} +EXPORT_SYMBOL(snd_midi_channel_free_set); diff --git a/sound/core/seq/seq_midi_event.c b/sound/core/seq/seq_midi_event.c new file mode 100644 index 000000000..7511462fe --- /dev/null +++ b/sound/core/seq/seq_midi_event.c @@ -0,0 +1,459 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MIDI byte <-> sequencer event coder + * + * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>, + * Jaroslav Kysela <perex@perex.cz> + */ + +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/module.h> +#include <sound/core.h> +#include <sound/seq_kernel.h> +#include <sound/seq_midi_event.h> +#include <sound/asoundef.h> + +MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>, Jaroslav Kysela <perex@perex.cz>"); +MODULE_DESCRIPTION("MIDI byte <-> sequencer event coder"); +MODULE_LICENSE("GPL"); + +/* event type, index into status_event[] */ +/* from 0 to 6 are normal commands (note off, on, etc.) for 0x9?-0xe? */ +#define ST_INVALID 7 +#define ST_SPECIAL 8 +#define ST_SYSEX ST_SPECIAL +/* from 8 to 15 are events for 0xf0-0xf7 */ + + +/* + * prototypes + */ +static void note_event(struct snd_midi_event *dev, struct snd_seq_event *ev); +static void one_param_ctrl_event(struct snd_midi_event *dev, struct snd_seq_event *ev); +static void pitchbend_ctrl_event(struct snd_midi_event *dev, struct snd_seq_event *ev); +static void two_param_ctrl_event(struct snd_midi_event *dev, struct snd_seq_event *ev); +static void one_param_event(struct snd_midi_event *dev, struct snd_seq_event *ev); +static void songpos_event(struct snd_midi_event *dev, struct snd_seq_event *ev); +static void note_decode(struct snd_seq_event *ev, unsigned char *buf); +static void one_param_decode(struct snd_seq_event *ev, unsigned char *buf); +static void pitchbend_decode(struct snd_seq_event *ev, unsigned char *buf); +static void two_param_decode(struct snd_seq_event *ev, unsigned char *buf); +static void songpos_decode(struct snd_seq_event *ev, unsigned char *buf); + +/* + * event list + */ +static struct status_event_list { + int event; + int qlen; + void (*encode)(struct snd_midi_event *dev, struct snd_seq_event *ev); + void (*decode)(struct snd_seq_event *ev, unsigned char *buf); +} status_event[] = { + /* 0x80 - 0xef */ + {SNDRV_SEQ_EVENT_NOTEOFF, 2, note_event, note_decode}, + {SNDRV_SEQ_EVENT_NOTEON, 2, note_event, note_decode}, + {SNDRV_SEQ_EVENT_KEYPRESS, 2, note_event, note_decode}, + {SNDRV_SEQ_EVENT_CONTROLLER, 2, two_param_ctrl_event, two_param_decode}, + {SNDRV_SEQ_EVENT_PGMCHANGE, 1, one_param_ctrl_event, one_param_decode}, + {SNDRV_SEQ_EVENT_CHANPRESS, 1, one_param_ctrl_event, one_param_decode}, + {SNDRV_SEQ_EVENT_PITCHBEND, 2, pitchbend_ctrl_event, pitchbend_decode}, + /* invalid */ + {SNDRV_SEQ_EVENT_NONE, -1, NULL, NULL}, + /* 0xf0 - 0xff */ + {SNDRV_SEQ_EVENT_SYSEX, 1, NULL, NULL}, /* sysex: 0xf0 */ + {SNDRV_SEQ_EVENT_QFRAME, 1, one_param_event, one_param_decode}, /* 0xf1 */ + {SNDRV_SEQ_EVENT_SONGPOS, 2, songpos_event, songpos_decode}, /* 0xf2 */ + {SNDRV_SEQ_EVENT_SONGSEL, 1, one_param_event, one_param_decode}, /* 0xf3 */ + {SNDRV_SEQ_EVENT_NONE, -1, NULL, NULL}, /* 0xf4 */ + {SNDRV_SEQ_EVENT_NONE, -1, NULL, NULL}, /* 0xf5 */ + {SNDRV_SEQ_EVENT_TUNE_REQUEST, 0, NULL, NULL}, /* 0xf6 */ + {SNDRV_SEQ_EVENT_NONE, -1, NULL, NULL}, /* 0xf7 */ + {SNDRV_SEQ_EVENT_CLOCK, 0, NULL, NULL}, /* 0xf8 */ + {SNDRV_SEQ_EVENT_NONE, -1, NULL, NULL}, /* 0xf9 */ + {SNDRV_SEQ_EVENT_START, 0, NULL, NULL}, /* 0xfa */ + {SNDRV_SEQ_EVENT_CONTINUE, 0, NULL, NULL}, /* 0xfb */ + {SNDRV_SEQ_EVENT_STOP, 0, NULL, NULL}, /* 0xfc */ + {SNDRV_SEQ_EVENT_NONE, -1, NULL, NULL}, /* 0xfd */ + {SNDRV_SEQ_EVENT_SENSING, 0, NULL, NULL}, /* 0xfe */ + {SNDRV_SEQ_EVENT_RESET, 0, NULL, NULL}, /* 0xff */ +}; + +static int extra_decode_ctrl14(struct snd_midi_event *dev, unsigned char *buf, int len, + struct snd_seq_event *ev); +static int extra_decode_xrpn(struct snd_midi_event *dev, unsigned char *buf, int count, + struct snd_seq_event *ev); + +static struct extra_event_list { + int event; + int (*decode)(struct snd_midi_event *dev, unsigned char *buf, int len, + struct snd_seq_event *ev); +} extra_event[] = { + {SNDRV_SEQ_EVENT_CONTROL14, extra_decode_ctrl14}, + {SNDRV_SEQ_EVENT_NONREGPARAM, extra_decode_xrpn}, + {SNDRV_SEQ_EVENT_REGPARAM, extra_decode_xrpn}, +}; + +/* + * new/delete record + */ + +int snd_midi_event_new(int bufsize, struct snd_midi_event **rdev) +{ + struct snd_midi_event *dev; + + *rdev = NULL; + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (dev == NULL) + return -ENOMEM; + if (bufsize > 0) { + dev->buf = kmalloc(bufsize, GFP_KERNEL); + if (dev->buf == NULL) { + kfree(dev); + return -ENOMEM; + } + } + dev->bufsize = bufsize; + dev->lastcmd = 0xff; + dev->type = ST_INVALID; + spin_lock_init(&dev->lock); + *rdev = dev; + return 0; +} +EXPORT_SYMBOL(snd_midi_event_new); + +void snd_midi_event_free(struct snd_midi_event *dev) +{ + if (dev != NULL) { + kfree(dev->buf); + kfree(dev); + } +} +EXPORT_SYMBOL(snd_midi_event_free); + +/* + * initialize record + */ +static inline void reset_encode(struct snd_midi_event *dev) +{ + dev->read = 0; + dev->qlen = 0; + dev->type = ST_INVALID; +} + +void snd_midi_event_reset_encode(struct snd_midi_event *dev) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + reset_encode(dev); + spin_unlock_irqrestore(&dev->lock, flags); +} +EXPORT_SYMBOL(snd_midi_event_reset_encode); + +void snd_midi_event_reset_decode(struct snd_midi_event *dev) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + dev->lastcmd = 0xff; + spin_unlock_irqrestore(&dev->lock, flags); +} +EXPORT_SYMBOL(snd_midi_event_reset_decode); + +void snd_midi_event_no_status(struct snd_midi_event *dev, int on) +{ + dev->nostat = on ? 1 : 0; +} +EXPORT_SYMBOL(snd_midi_event_no_status); + +/* + * read one byte and encode to sequencer event: + * return true if MIDI bytes are encoded to an event + * false data is not finished + */ +bool snd_midi_event_encode_byte(struct snd_midi_event *dev, unsigned char c, + struct snd_seq_event *ev) +{ + bool rc = false; + unsigned long flags; + + if (c >= MIDI_CMD_COMMON_CLOCK) { + /* real-time event */ + ev->type = status_event[ST_SPECIAL + c - 0xf0].event; + ev->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK; + ev->flags |= SNDRV_SEQ_EVENT_LENGTH_FIXED; + return ev->type != SNDRV_SEQ_EVENT_NONE; + } + + spin_lock_irqsave(&dev->lock, flags); + if ((c & 0x80) && + (c != MIDI_CMD_COMMON_SYSEX_END || dev->type != ST_SYSEX)) { + /* new command */ + dev->buf[0] = c; + if ((c & 0xf0) == 0xf0) /* system messages */ + dev->type = (c & 0x0f) + ST_SPECIAL; + else + dev->type = (c >> 4) & 0x07; + dev->read = 1; + dev->qlen = status_event[dev->type].qlen; + } else { + if (dev->qlen > 0) { + /* rest of command */ + dev->buf[dev->read++] = c; + if (dev->type != ST_SYSEX) + dev->qlen--; + } else { + /* running status */ + dev->buf[1] = c; + dev->qlen = status_event[dev->type].qlen - 1; + dev->read = 2; + } + } + if (dev->qlen == 0) { + ev->type = status_event[dev->type].event; + ev->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK; + ev->flags |= SNDRV_SEQ_EVENT_LENGTH_FIXED; + if (status_event[dev->type].encode) /* set data values */ + status_event[dev->type].encode(dev, ev); + if (dev->type >= ST_SPECIAL) + dev->type = ST_INVALID; + rc = true; + } else if (dev->type == ST_SYSEX) { + if (c == MIDI_CMD_COMMON_SYSEX_END || + dev->read >= dev->bufsize) { + ev->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK; + ev->flags |= SNDRV_SEQ_EVENT_LENGTH_VARIABLE; + ev->type = SNDRV_SEQ_EVENT_SYSEX; + ev->data.ext.len = dev->read; + ev->data.ext.ptr = dev->buf; + if (c != MIDI_CMD_COMMON_SYSEX_END) + dev->read = 0; /* continue to parse */ + else + reset_encode(dev); /* all parsed */ + rc = true; + } + } + + spin_unlock_irqrestore(&dev->lock, flags); + return rc; +} +EXPORT_SYMBOL(snd_midi_event_encode_byte); + +/* encode note event */ +static void note_event(struct snd_midi_event *dev, struct snd_seq_event *ev) +{ + ev->data.note.channel = dev->buf[0] & 0x0f; + ev->data.note.note = dev->buf[1]; + ev->data.note.velocity = dev->buf[2]; +} + +/* encode one parameter controls */ +static void one_param_ctrl_event(struct snd_midi_event *dev, struct snd_seq_event *ev) +{ + ev->data.control.channel = dev->buf[0] & 0x0f; + ev->data.control.value = dev->buf[1]; +} + +/* encode pitch wheel change */ +static void pitchbend_ctrl_event(struct snd_midi_event *dev, struct snd_seq_event *ev) +{ + ev->data.control.channel = dev->buf[0] & 0x0f; + ev->data.control.value = (int)dev->buf[2] * 128 + (int)dev->buf[1] - 8192; +} + +/* encode midi control change */ +static void two_param_ctrl_event(struct snd_midi_event *dev, struct snd_seq_event *ev) +{ + ev->data.control.channel = dev->buf[0] & 0x0f; + ev->data.control.param = dev->buf[1]; + ev->data.control.value = dev->buf[2]; +} + +/* encode one parameter value*/ +static void one_param_event(struct snd_midi_event *dev, struct snd_seq_event *ev) +{ + ev->data.control.value = dev->buf[1]; +} + +/* encode song position */ +static void songpos_event(struct snd_midi_event *dev, struct snd_seq_event *ev) +{ + ev->data.control.value = (int)dev->buf[2] * 128 + (int)dev->buf[1]; +} + +/* + * decode from a sequencer event to midi bytes + * return the size of decoded midi events + */ +long snd_midi_event_decode(struct snd_midi_event *dev, unsigned char *buf, long count, + struct snd_seq_event *ev) +{ + unsigned int cmd, type; + + if (ev->type == SNDRV_SEQ_EVENT_NONE) + return -ENOENT; + + for (type = 0; type < ARRAY_SIZE(status_event); type++) { + if (ev->type == status_event[type].event) + goto __found; + } + for (type = 0; type < ARRAY_SIZE(extra_event); type++) { + if (ev->type == extra_event[type].event) + return extra_event[type].decode(dev, buf, count, ev); + } + return -ENOENT; + + __found: + if (type >= ST_SPECIAL) + cmd = 0xf0 + (type - ST_SPECIAL); + else + /* data.note.channel and data.control.channel is identical */ + cmd = 0x80 | (type << 4) | (ev->data.note.channel & 0x0f); + + + if (cmd == MIDI_CMD_COMMON_SYSEX) { + snd_midi_event_reset_decode(dev); + return snd_seq_expand_var_event(ev, count, buf, 1, 0); + } else { + int qlen; + unsigned char xbuf[4]; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + if ((cmd & 0xf0) == 0xf0 || dev->lastcmd != cmd || dev->nostat) { + dev->lastcmd = cmd; + spin_unlock_irqrestore(&dev->lock, flags); + xbuf[0] = cmd; + if (status_event[type].decode) + status_event[type].decode(ev, xbuf + 1); + qlen = status_event[type].qlen + 1; + } else { + spin_unlock_irqrestore(&dev->lock, flags); + if (status_event[type].decode) + status_event[type].decode(ev, xbuf + 0); + qlen = status_event[type].qlen; + } + if (count < qlen) + return -ENOMEM; + memcpy(buf, xbuf, qlen); + return qlen; + } +} +EXPORT_SYMBOL(snd_midi_event_decode); + + +/* decode note event */ +static void note_decode(struct snd_seq_event *ev, unsigned char *buf) +{ + buf[0] = ev->data.note.note & 0x7f; + buf[1] = ev->data.note.velocity & 0x7f; +} + +/* decode one parameter controls */ +static void one_param_decode(struct snd_seq_event *ev, unsigned char *buf) +{ + buf[0] = ev->data.control.value & 0x7f; +} + +/* decode pitch wheel change */ +static void pitchbend_decode(struct snd_seq_event *ev, unsigned char *buf) +{ + int value = ev->data.control.value + 8192; + buf[0] = value & 0x7f; + buf[1] = (value >> 7) & 0x7f; +} + +/* decode midi control change */ +static void two_param_decode(struct snd_seq_event *ev, unsigned char *buf) +{ + buf[0] = ev->data.control.param & 0x7f; + buf[1] = ev->data.control.value & 0x7f; +} + +/* decode song position */ +static void songpos_decode(struct snd_seq_event *ev, unsigned char *buf) +{ + buf[0] = ev->data.control.value & 0x7f; + buf[1] = (ev->data.control.value >> 7) & 0x7f; +} + +/* decode 14bit control */ +static int extra_decode_ctrl14(struct snd_midi_event *dev, unsigned char *buf, + int count, struct snd_seq_event *ev) +{ + unsigned char cmd; + int idx = 0; + + cmd = MIDI_CMD_CONTROL|(ev->data.control.channel & 0x0f); + if (ev->data.control.param < 0x20) { + if (count < 4) + return -ENOMEM; + if (dev->nostat && count < 6) + return -ENOMEM; + if (cmd != dev->lastcmd || dev->nostat) { + if (count < 5) + return -ENOMEM; + buf[idx++] = dev->lastcmd = cmd; + } + buf[idx++] = ev->data.control.param; + buf[idx++] = (ev->data.control.value >> 7) & 0x7f; + if (dev->nostat) + buf[idx++] = cmd; + buf[idx++] = ev->data.control.param + 0x20; + buf[idx++] = ev->data.control.value & 0x7f; + } else { + if (count < 2) + return -ENOMEM; + if (cmd != dev->lastcmd || dev->nostat) { + if (count < 3) + return -ENOMEM; + buf[idx++] = dev->lastcmd = cmd; + } + buf[idx++] = ev->data.control.param & 0x7f; + buf[idx++] = ev->data.control.value & 0x7f; + } + return idx; +} + +/* decode reg/nonreg param */ +static int extra_decode_xrpn(struct snd_midi_event *dev, unsigned char *buf, + int count, struct snd_seq_event *ev) +{ + unsigned char cmd; + const char *cbytes; + static const char cbytes_nrpn[4] = { MIDI_CTL_NONREG_PARM_NUM_MSB, + MIDI_CTL_NONREG_PARM_NUM_LSB, + MIDI_CTL_MSB_DATA_ENTRY, + MIDI_CTL_LSB_DATA_ENTRY }; + static const char cbytes_rpn[4] = { MIDI_CTL_REGIST_PARM_NUM_MSB, + MIDI_CTL_REGIST_PARM_NUM_LSB, + MIDI_CTL_MSB_DATA_ENTRY, + MIDI_CTL_LSB_DATA_ENTRY }; + unsigned char bytes[4]; + int idx = 0, i; + + if (count < 8) + return -ENOMEM; + if (dev->nostat && count < 12) + return -ENOMEM; + cmd = MIDI_CMD_CONTROL|(ev->data.control.channel & 0x0f); + bytes[0] = (ev->data.control.param & 0x3f80) >> 7; + bytes[1] = ev->data.control.param & 0x007f; + bytes[2] = (ev->data.control.value & 0x3f80) >> 7; + bytes[3] = ev->data.control.value & 0x007f; + if (cmd != dev->lastcmd && !dev->nostat) { + if (count < 9) + return -ENOMEM; + buf[idx++] = dev->lastcmd = cmd; + } + cbytes = ev->type == SNDRV_SEQ_EVENT_NONREGPARAM ? cbytes_nrpn : cbytes_rpn; + for (i = 0; i < 4; i++) { + if (dev->nostat) + buf[idx++] = dev->lastcmd = cmd; + buf[idx++] = cbytes[i]; + buf[idx++] = bytes[i]; + } + return idx; +} diff --git a/sound/core/seq/seq_ports.c b/sound/core/seq/seq_ports.c new file mode 100644 index 000000000..25fcf5a2c --- /dev/null +++ b/sound/core/seq/seq_ports.c @@ -0,0 +1,711 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA sequencer Ports + * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl> + * Jaroslav Kysela <perex@perex.cz> + */ + +#include <sound/core.h> +#include <linux/slab.h> +#include <linux/module.h> +#include "seq_system.h" +#include "seq_ports.h" +#include "seq_clientmgr.h" + +/* + + registration of client ports + + */ + + +/* + +NOTE: the current implementation of the port structure as a linked list is +not optimal for clients that have many ports. For sending messages to all +subscribers of a port we first need to find the address of the port +structure, which means we have to traverse the list. A direct access table +(array) would be better, but big preallocated arrays waste memory. + +Possible actions: + +1) leave it this way, a client does normaly does not have more than a few +ports + +2) replace the linked list of ports by a array of pointers which is +dynamicly kmalloced. When a port is added or deleted we can simply allocate +a new array, copy the corresponding pointers, and delete the old one. We +then only need a pointer to this array, and an integer that tells us how +much elements are in array. + +*/ + +/* return pointer to port structure - port is locked if found */ +struct snd_seq_client_port *snd_seq_port_use_ptr(struct snd_seq_client *client, + int num) +{ + struct snd_seq_client_port *port; + + if (client == NULL) + return NULL; + read_lock(&client->ports_lock); + list_for_each_entry(port, &client->ports_list_head, list) { + if (port->addr.port == num) { + if (port->closing) + break; /* deleting now */ + snd_use_lock_use(&port->use_lock); + read_unlock(&client->ports_lock); + return port; + } + } + read_unlock(&client->ports_lock); + return NULL; /* not found */ +} + + +/* search for the next port - port is locked if found */ +struct snd_seq_client_port *snd_seq_port_query_nearest(struct snd_seq_client *client, + struct snd_seq_port_info *pinfo) +{ + int num; + struct snd_seq_client_port *port, *found; + + num = pinfo->addr.port; + found = NULL; + read_lock(&client->ports_lock); + list_for_each_entry(port, &client->ports_list_head, list) { + if (port->addr.port < num) + continue; + if (port->addr.port == num) { + found = port; + break; + } + if (found == NULL || port->addr.port < found->addr.port) + found = port; + } + if (found) { + if (found->closing) + found = NULL; + else + snd_use_lock_use(&found->use_lock); + } + read_unlock(&client->ports_lock); + return found; +} + + +/* initialize snd_seq_port_subs_info */ +static void port_subs_info_init(struct snd_seq_port_subs_info *grp) +{ + INIT_LIST_HEAD(&grp->list_head); + grp->count = 0; + grp->exclusive = 0; + rwlock_init(&grp->list_lock); + init_rwsem(&grp->list_mutex); + grp->open = NULL; + grp->close = NULL; +} + + +/* create a port, port number is returned (-1 on failure); + * the caller needs to unref the port via snd_seq_port_unlock() appropriately + */ +struct snd_seq_client_port *snd_seq_create_port(struct snd_seq_client *client, + int port) +{ + struct snd_seq_client_port *new_port, *p; + int num = -1; + + /* sanity check */ + if (snd_BUG_ON(!client)) + return NULL; + + if (client->num_ports >= SNDRV_SEQ_MAX_PORTS) { + pr_warn("ALSA: seq: too many ports for client %d\n", client->number); + return NULL; + } + + /* create a new port */ + new_port = kzalloc(sizeof(*new_port), GFP_KERNEL); + if (!new_port) + return NULL; /* failure, out of memory */ + /* init port data */ + new_port->addr.client = client->number; + new_port->addr.port = -1; + new_port->owner = THIS_MODULE; + sprintf(new_port->name, "port-%d", num); + snd_use_lock_init(&new_port->use_lock); + port_subs_info_init(&new_port->c_src); + port_subs_info_init(&new_port->c_dest); + snd_use_lock_use(&new_port->use_lock); + + num = max(port, 0); + mutex_lock(&client->ports_mutex); + write_lock_irq(&client->ports_lock); + list_for_each_entry(p, &client->ports_list_head, list) { + if (p->addr.port > num) + break; + if (port < 0) /* auto-probe mode */ + num = p->addr.port + 1; + } + /* insert the new port */ + list_add_tail(&new_port->list, &p->list); + client->num_ports++; + new_port->addr.port = num; /* store the port number in the port */ + sprintf(new_port->name, "port-%d", num); + write_unlock_irq(&client->ports_lock); + mutex_unlock(&client->ports_mutex); + + return new_port; +} + +/* */ +static int subscribe_port(struct snd_seq_client *client, + struct snd_seq_client_port *port, + struct snd_seq_port_subs_info *grp, + struct snd_seq_port_subscribe *info, int send_ack); +static int unsubscribe_port(struct snd_seq_client *client, + struct snd_seq_client_port *port, + struct snd_seq_port_subs_info *grp, + struct snd_seq_port_subscribe *info, int send_ack); + + +static struct snd_seq_client_port *get_client_port(struct snd_seq_addr *addr, + struct snd_seq_client **cp) +{ + struct snd_seq_client_port *p; + *cp = snd_seq_client_use_ptr(addr->client); + if (*cp) { + p = snd_seq_port_use_ptr(*cp, addr->port); + if (! p) { + snd_seq_client_unlock(*cp); + *cp = NULL; + } + return p; + } + return NULL; +} + +static void delete_and_unsubscribe_port(struct snd_seq_client *client, + struct snd_seq_client_port *port, + struct snd_seq_subscribers *subs, + bool is_src, bool ack); + +static inline struct snd_seq_subscribers * +get_subscriber(struct list_head *p, bool is_src) +{ + if (is_src) + return list_entry(p, struct snd_seq_subscribers, src_list); + else + return list_entry(p, struct snd_seq_subscribers, dest_list); +} + +/* + * remove all subscribers on the list + * this is called from port_delete, for each src and dest list. + */ +static void clear_subscriber_list(struct snd_seq_client *client, + struct snd_seq_client_port *port, + struct snd_seq_port_subs_info *grp, + int is_src) +{ + struct list_head *p, *n; + + list_for_each_safe(p, n, &grp->list_head) { + struct snd_seq_subscribers *subs; + struct snd_seq_client *c; + struct snd_seq_client_port *aport; + + subs = get_subscriber(p, is_src); + if (is_src) + aport = get_client_port(&subs->info.dest, &c); + else + aport = get_client_port(&subs->info.sender, &c); + delete_and_unsubscribe_port(client, port, subs, is_src, false); + + if (!aport) { + /* looks like the connected port is being deleted. + * we decrease the counter, and when both ports are deleted + * remove the subscriber info + */ + if (atomic_dec_and_test(&subs->ref_count)) + kfree(subs); + continue; + } + + /* ok we got the connected port */ + delete_and_unsubscribe_port(c, aport, subs, !is_src, true); + kfree(subs); + snd_seq_port_unlock(aport); + snd_seq_client_unlock(c); + } +} + +/* delete port data */ +static int port_delete(struct snd_seq_client *client, + struct snd_seq_client_port *port) +{ + /* set closing flag and wait for all port access are gone */ + port->closing = 1; + snd_use_lock_sync(&port->use_lock); + + /* clear subscribers info */ + clear_subscriber_list(client, port, &port->c_src, true); + clear_subscriber_list(client, port, &port->c_dest, false); + + if (port->private_free) + port->private_free(port->private_data); + + snd_BUG_ON(port->c_src.count != 0); + snd_BUG_ON(port->c_dest.count != 0); + + kfree(port); + return 0; +} + + +/* delete a port with the given port id */ +int snd_seq_delete_port(struct snd_seq_client *client, int port) +{ + struct snd_seq_client_port *found = NULL, *p; + + mutex_lock(&client->ports_mutex); + write_lock_irq(&client->ports_lock); + list_for_each_entry(p, &client->ports_list_head, list) { + if (p->addr.port == port) { + /* ok found. delete from the list at first */ + list_del(&p->list); + client->num_ports--; + found = p; + break; + } + } + write_unlock_irq(&client->ports_lock); + mutex_unlock(&client->ports_mutex); + if (found) + return port_delete(client, found); + else + return -ENOENT; +} + +/* delete the all ports belonging to the given client */ +int snd_seq_delete_all_ports(struct snd_seq_client *client) +{ + struct list_head deleted_list; + struct snd_seq_client_port *port, *tmp; + + /* move the port list to deleted_list, and + * clear the port list in the client data. + */ + mutex_lock(&client->ports_mutex); + write_lock_irq(&client->ports_lock); + if (! list_empty(&client->ports_list_head)) { + list_add(&deleted_list, &client->ports_list_head); + list_del_init(&client->ports_list_head); + } else { + INIT_LIST_HEAD(&deleted_list); + } + client->num_ports = 0; + write_unlock_irq(&client->ports_lock); + + /* remove each port in deleted_list */ + list_for_each_entry_safe(port, tmp, &deleted_list, list) { + list_del(&port->list); + snd_seq_system_client_ev_port_exit(port->addr.client, port->addr.port); + port_delete(client, port); + } + mutex_unlock(&client->ports_mutex); + return 0; +} + +/* set port info fields */ +int snd_seq_set_port_info(struct snd_seq_client_port * port, + struct snd_seq_port_info * info) +{ + if (snd_BUG_ON(!port || !info)) + return -EINVAL; + + /* set port name */ + if (info->name[0]) + strscpy(port->name, info->name, sizeof(port->name)); + + /* set capabilities */ + port->capability = info->capability; + + /* get port type */ + port->type = info->type; + + /* information about supported channels/voices */ + port->midi_channels = info->midi_channels; + port->midi_voices = info->midi_voices; + port->synth_voices = info->synth_voices; + + /* timestamping */ + port->timestamping = (info->flags & SNDRV_SEQ_PORT_FLG_TIMESTAMP) ? 1 : 0; + port->time_real = (info->flags & SNDRV_SEQ_PORT_FLG_TIME_REAL) ? 1 : 0; + port->time_queue = info->time_queue; + + return 0; +} + +/* get port info fields */ +int snd_seq_get_port_info(struct snd_seq_client_port * port, + struct snd_seq_port_info * info) +{ + if (snd_BUG_ON(!port || !info)) + return -EINVAL; + + /* get port name */ + strscpy(info->name, port->name, sizeof(info->name)); + + /* get capabilities */ + info->capability = port->capability; + + /* get port type */ + info->type = port->type; + + /* information about supported channels/voices */ + info->midi_channels = port->midi_channels; + info->midi_voices = port->midi_voices; + info->synth_voices = port->synth_voices; + + /* get subscriber counts */ + info->read_use = port->c_src.count; + info->write_use = port->c_dest.count; + + /* timestamping */ + info->flags = 0; + if (port->timestamping) { + info->flags |= SNDRV_SEQ_PORT_FLG_TIMESTAMP; + if (port->time_real) + info->flags |= SNDRV_SEQ_PORT_FLG_TIME_REAL; + info->time_queue = port->time_queue; + } + + return 0; +} + + + +/* + * call callback functions (if any): + * the callbacks are invoked only when the first (for connection) or + * the last subscription (for disconnection) is done. Second or later + * subscription results in increment of counter, but no callback is + * invoked. + * This feature is useful if these callbacks are associated with + * initialization or termination of devices (see seq_midi.c). + */ + +static int subscribe_port(struct snd_seq_client *client, + struct snd_seq_client_port *port, + struct snd_seq_port_subs_info *grp, + struct snd_seq_port_subscribe *info, + int send_ack) +{ + int err = 0; + + if (!try_module_get(port->owner)) + return -EFAULT; + grp->count++; + if (grp->open && grp->count == 1) { + err = grp->open(port->private_data, info); + if (err < 0) { + module_put(port->owner); + grp->count--; + } + } + if (err >= 0 && send_ack && client->type == USER_CLIENT) + snd_seq_client_notify_subscription(port->addr.client, port->addr.port, + info, SNDRV_SEQ_EVENT_PORT_SUBSCRIBED); + + return err; +} + +static int unsubscribe_port(struct snd_seq_client *client, + struct snd_seq_client_port *port, + struct snd_seq_port_subs_info *grp, + struct snd_seq_port_subscribe *info, + int send_ack) +{ + int err = 0; + + if (! grp->count) + return -EINVAL; + grp->count--; + if (grp->close && grp->count == 0) + err = grp->close(port->private_data, info); + if (send_ack && client->type == USER_CLIENT) + snd_seq_client_notify_subscription(port->addr.client, port->addr.port, + info, SNDRV_SEQ_EVENT_PORT_UNSUBSCRIBED); + module_put(port->owner); + return err; +} + + + +/* check if both addresses are identical */ +static inline int addr_match(struct snd_seq_addr *r, struct snd_seq_addr *s) +{ + return (r->client == s->client) && (r->port == s->port); +} + +/* check the two subscribe info match */ +/* if flags is zero, checks only sender and destination addresses */ +static int match_subs_info(struct snd_seq_port_subscribe *r, + struct snd_seq_port_subscribe *s) +{ + if (addr_match(&r->sender, &s->sender) && + addr_match(&r->dest, &s->dest)) { + if (r->flags && r->flags == s->flags) + return r->queue == s->queue; + else if (! r->flags) + return 1; + } + return 0; +} + +static int check_and_subscribe_port(struct snd_seq_client *client, + struct snd_seq_client_port *port, + struct snd_seq_subscribers *subs, + bool is_src, bool exclusive, bool ack) +{ + struct snd_seq_port_subs_info *grp; + struct list_head *p; + struct snd_seq_subscribers *s; + int err; + + grp = is_src ? &port->c_src : &port->c_dest; + err = -EBUSY; + down_write(&grp->list_mutex); + if (exclusive) { + if (!list_empty(&grp->list_head)) + goto __error; + } else { + if (grp->exclusive) + goto __error; + /* check whether already exists */ + list_for_each(p, &grp->list_head) { + s = get_subscriber(p, is_src); + if (match_subs_info(&subs->info, &s->info)) + goto __error; + } + } + + err = subscribe_port(client, port, grp, &subs->info, ack); + if (err < 0) { + grp->exclusive = 0; + goto __error; + } + + /* add to list */ + write_lock_irq(&grp->list_lock); + if (is_src) + list_add_tail(&subs->src_list, &grp->list_head); + else + list_add_tail(&subs->dest_list, &grp->list_head); + grp->exclusive = exclusive; + atomic_inc(&subs->ref_count); + write_unlock_irq(&grp->list_lock); + err = 0; + + __error: + up_write(&grp->list_mutex); + return err; +} + +/* called with grp->list_mutex held */ +static void __delete_and_unsubscribe_port(struct snd_seq_client *client, + struct snd_seq_client_port *port, + struct snd_seq_subscribers *subs, + bool is_src, bool ack) +{ + struct snd_seq_port_subs_info *grp; + struct list_head *list; + bool empty; + + grp = is_src ? &port->c_src : &port->c_dest; + list = is_src ? &subs->src_list : &subs->dest_list; + write_lock_irq(&grp->list_lock); + empty = list_empty(list); + if (!empty) + list_del_init(list); + grp->exclusive = 0; + write_unlock_irq(&grp->list_lock); + + if (!empty) + unsubscribe_port(client, port, grp, &subs->info, ack); +} + +static void delete_and_unsubscribe_port(struct snd_seq_client *client, + struct snd_seq_client_port *port, + struct snd_seq_subscribers *subs, + bool is_src, bool ack) +{ + struct snd_seq_port_subs_info *grp; + + grp = is_src ? &port->c_src : &port->c_dest; + down_write(&grp->list_mutex); + __delete_and_unsubscribe_port(client, port, subs, is_src, ack); + up_write(&grp->list_mutex); +} + +/* connect two ports */ +int snd_seq_port_connect(struct snd_seq_client *connector, + struct snd_seq_client *src_client, + struct snd_seq_client_port *src_port, + struct snd_seq_client *dest_client, + struct snd_seq_client_port *dest_port, + struct snd_seq_port_subscribe *info) +{ + struct snd_seq_subscribers *subs; + bool exclusive; + int err; + + subs = kzalloc(sizeof(*subs), GFP_KERNEL); + if (!subs) + return -ENOMEM; + + subs->info = *info; + atomic_set(&subs->ref_count, 0); + INIT_LIST_HEAD(&subs->src_list); + INIT_LIST_HEAD(&subs->dest_list); + + exclusive = !!(info->flags & SNDRV_SEQ_PORT_SUBS_EXCLUSIVE); + + err = check_and_subscribe_port(src_client, src_port, subs, true, + exclusive, + connector->number != src_client->number); + if (err < 0) + goto error; + err = check_and_subscribe_port(dest_client, dest_port, subs, false, + exclusive, + connector->number != dest_client->number); + if (err < 0) + goto error_dest; + + return 0; + + error_dest: + delete_and_unsubscribe_port(src_client, src_port, subs, true, + connector->number != src_client->number); + error: + kfree(subs); + return err; +} + +/* remove the connection */ +int snd_seq_port_disconnect(struct snd_seq_client *connector, + struct snd_seq_client *src_client, + struct snd_seq_client_port *src_port, + struct snd_seq_client *dest_client, + struct snd_seq_client_port *dest_port, + struct snd_seq_port_subscribe *info) +{ + struct snd_seq_port_subs_info *dest = &dest_port->c_dest; + struct snd_seq_subscribers *subs; + int err = -ENOENT; + + /* always start from deleting the dest port for avoiding concurrent + * deletions + */ + down_write(&dest->list_mutex); + /* look for the connection */ + list_for_each_entry(subs, &dest->list_head, dest_list) { + if (match_subs_info(info, &subs->info)) { + __delete_and_unsubscribe_port(dest_client, dest_port, + subs, false, + connector->number != dest_client->number); + err = 0; + break; + } + } + up_write(&dest->list_mutex); + if (err < 0) + return err; + + delete_and_unsubscribe_port(src_client, src_port, subs, true, + connector->number != src_client->number); + kfree(subs); + return 0; +} + + +/* get matched subscriber */ +int snd_seq_port_get_subscription(struct snd_seq_port_subs_info *src_grp, + struct snd_seq_addr *dest_addr, + struct snd_seq_port_subscribe *subs) +{ + struct snd_seq_subscribers *s; + int err = -ENOENT; + + down_read(&src_grp->list_mutex); + list_for_each_entry(s, &src_grp->list_head, src_list) { + if (addr_match(dest_addr, &s->info.dest)) { + *subs = s->info; + err = 0; + break; + } + } + up_read(&src_grp->list_mutex); + return err; +} + +/* + * Attach a device driver that wants to receive events from the + * sequencer. Returns the new port number on success. + * A driver that wants to receive the events converted to midi, will + * use snd_seq_midisynth_register_port(). + */ +/* exported */ +int snd_seq_event_port_attach(int client, + struct snd_seq_port_callback *pcbp, + int cap, int type, int midi_channels, + int midi_voices, char *portname) +{ + struct snd_seq_port_info portinfo; + int ret; + + /* Set up the port */ + memset(&portinfo, 0, sizeof(portinfo)); + portinfo.addr.client = client; + strscpy(portinfo.name, portname ? portname : "Unnamed port", + sizeof(portinfo.name)); + + portinfo.capability = cap; + portinfo.type = type; + portinfo.kernel = pcbp; + portinfo.midi_channels = midi_channels; + portinfo.midi_voices = midi_voices; + + /* Create it */ + ret = snd_seq_kernel_client_ctl(client, + SNDRV_SEQ_IOCTL_CREATE_PORT, + &portinfo); + + if (ret >= 0) + ret = portinfo.addr.port; + + return ret; +} +EXPORT_SYMBOL(snd_seq_event_port_attach); + +/* + * Detach the driver from a port. + */ +/* exported */ +int snd_seq_event_port_detach(int client, int port) +{ + struct snd_seq_port_info portinfo; + int err; + + memset(&portinfo, 0, sizeof(portinfo)); + portinfo.addr.client = client; + portinfo.addr.port = port; + err = snd_seq_kernel_client_ctl(client, + SNDRV_SEQ_IOCTL_DELETE_PORT, + &portinfo); + + return err; +} +EXPORT_SYMBOL(snd_seq_event_port_detach); diff --git a/sound/core/seq/seq_ports.h b/sound/core/seq/seq_ports.h new file mode 100644 index 000000000..b1f2c4943 --- /dev/null +++ b/sound/core/seq/seq_ports.h @@ -0,0 +1,127 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * ALSA sequencer Ports + * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl> + */ +#ifndef __SND_SEQ_PORTS_H +#define __SND_SEQ_PORTS_H + +#include <sound/seq_kernel.h> +#include "seq_lock.h" + +/* list of 'exported' ports */ + +/* Client ports that are not exported are still accessible, but are + anonymous ports. + + If a port supports SUBSCRIPTION, that port can send events to all + subscribersto a special address, with address + (queue==SNDRV_SEQ_ADDRESS_SUBSCRIBERS). The message is then send to all + recipients that are registered in the subscription list. A typical + application for these SUBSCRIPTION events is handling of incoming MIDI + data. The port doesn't 'know' what other clients are interested in this + message. If for instance a MIDI recording application would like to receive + the events from that port, it will first have to subscribe with that port. + +*/ + +struct snd_seq_subscribers { + struct snd_seq_port_subscribe info; /* additional info */ + struct list_head src_list; /* link of sources */ + struct list_head dest_list; /* link of destinations */ + atomic_t ref_count; +}; + +struct snd_seq_port_subs_info { + struct list_head list_head; /* list of subscribed ports */ + unsigned int count; /* count of subscribers */ + unsigned int exclusive: 1; /* exclusive mode */ + struct rw_semaphore list_mutex; + rwlock_t list_lock; + int (*open)(void *private_data, struct snd_seq_port_subscribe *info); + int (*close)(void *private_data, struct snd_seq_port_subscribe *info); +}; + +struct snd_seq_client_port { + + struct snd_seq_addr addr; /* client/port number */ + struct module *owner; /* owner of this port */ + char name[64]; /* port name */ + struct list_head list; /* port list */ + snd_use_lock_t use_lock; + + /* subscribers */ + struct snd_seq_port_subs_info c_src; /* read (sender) list */ + struct snd_seq_port_subs_info c_dest; /* write (dest) list */ + + int (*event_input)(struct snd_seq_event *ev, int direct, void *private_data, + int atomic, int hop); + void (*private_free)(void *private_data); + void *private_data; + unsigned int closing : 1; + unsigned int timestamping: 1; + unsigned int time_real: 1; + int time_queue; + + /* capability, inport, output, sync */ + unsigned int capability; /* port capability bits */ + unsigned int type; /* port type bits */ + + /* supported channels */ + int midi_channels; + int midi_voices; + int synth_voices; + +}; + +struct snd_seq_client; + +/* return pointer to port structure and lock port */ +struct snd_seq_client_port *snd_seq_port_use_ptr(struct snd_seq_client *client, int num); + +/* search for next port - port is locked if found */ +struct snd_seq_client_port *snd_seq_port_query_nearest(struct snd_seq_client *client, + struct snd_seq_port_info *pinfo); + +/* unlock the port */ +#define snd_seq_port_unlock(port) snd_use_lock_free(&(port)->use_lock) + +/* create a port, port number is returned (-1 on failure) */ +struct snd_seq_client_port *snd_seq_create_port(struct snd_seq_client *client, int port_index); + +/* delete a port */ +int snd_seq_delete_port(struct snd_seq_client *client, int port); + +/* delete all ports */ +int snd_seq_delete_all_ports(struct snd_seq_client *client); + +/* set port info fields */ +int snd_seq_set_port_info(struct snd_seq_client_port *port, + struct snd_seq_port_info *info); + +/* get port info fields */ +int snd_seq_get_port_info(struct snd_seq_client_port *port, + struct snd_seq_port_info *info); + +/* add subscriber to subscription list */ +int snd_seq_port_connect(struct snd_seq_client *caller, + struct snd_seq_client *s, struct snd_seq_client_port *sp, + struct snd_seq_client *d, struct snd_seq_client_port *dp, + struct snd_seq_port_subscribe *info); + +/* remove subscriber from subscription list */ +int snd_seq_port_disconnect(struct snd_seq_client *caller, + struct snd_seq_client *s, struct snd_seq_client_port *sp, + struct snd_seq_client *d, struct snd_seq_client_port *dp, + struct snd_seq_port_subscribe *info); + +/* subscribe port */ +int snd_seq_port_subscribe(struct snd_seq_client_port *port, + struct snd_seq_port_subscribe *info); + +/* get matched subscriber */ +int snd_seq_port_get_subscription(struct snd_seq_port_subs_info *src_grp, + struct snd_seq_addr *dest_addr, + struct snd_seq_port_subscribe *subs); + +#endif diff --git a/sound/core/seq/seq_prioq.c b/sound/core/seq/seq_prioq.c new file mode 100644 index 000000000..1d857981e --- /dev/null +++ b/sound/core/seq/seq_prioq.c @@ -0,0 +1,436 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA sequencer Priority Queue + * Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl> + */ + +#include <linux/time.h> +#include <linux/slab.h> +#include <sound/core.h> +#include "seq_timer.h" +#include "seq_prioq.h" + + +/* Implementation is a simple linked list for now... + + This priority queue orders the events on timestamp. For events with an + equeal timestamp the queue behaves as a FIFO. + + * + * +-------+ + * Head --> | first | + * +-------+ + * |next + * +-----v-+ + * | | + * +-------+ + * | + * +-----v-+ + * | | + * +-------+ + * | + * +-----v-+ + * Tail --> | last | + * +-------+ + * + + */ + + + +/* create new prioq (constructor) */ +struct snd_seq_prioq *snd_seq_prioq_new(void) +{ + struct snd_seq_prioq *f; + + f = kzalloc(sizeof(*f), GFP_KERNEL); + if (!f) + return NULL; + + spin_lock_init(&f->lock); + f->head = NULL; + f->tail = NULL; + f->cells = 0; + + return f; +} + +/* delete prioq (destructor) */ +void snd_seq_prioq_delete(struct snd_seq_prioq **fifo) +{ + struct snd_seq_prioq *f = *fifo; + *fifo = NULL; + + if (f == NULL) { + pr_debug("ALSA: seq: snd_seq_prioq_delete() called with NULL prioq\n"); + return; + } + + /* release resources...*/ + /*....................*/ + + if (f->cells > 0) { + /* drain prioQ */ + while (f->cells > 0) + snd_seq_cell_free(snd_seq_prioq_cell_out(f, NULL)); + } + + kfree(f); +} + + + + +/* compare timestamp between events */ +/* return 1 if a >= b; 0 */ +static inline int compare_timestamp(struct snd_seq_event *a, + struct snd_seq_event *b) +{ + if ((a->flags & SNDRV_SEQ_TIME_STAMP_MASK) == SNDRV_SEQ_TIME_STAMP_TICK) { + /* compare ticks */ + return (snd_seq_compare_tick_time(&a->time.tick, &b->time.tick)); + } else { + /* compare real time */ + return (snd_seq_compare_real_time(&a->time.time, &b->time.time)); + } +} + +/* compare timestamp between events */ +/* return negative if a < b; + * zero if a = b; + * positive if a > b; + */ +static inline int compare_timestamp_rel(struct snd_seq_event *a, + struct snd_seq_event *b) +{ + if ((a->flags & SNDRV_SEQ_TIME_STAMP_MASK) == SNDRV_SEQ_TIME_STAMP_TICK) { + /* compare ticks */ + if (a->time.tick > b->time.tick) + return 1; + else if (a->time.tick == b->time.tick) + return 0; + else + return -1; + } else { + /* compare real time */ + if (a->time.time.tv_sec > b->time.time.tv_sec) + return 1; + else if (a->time.time.tv_sec == b->time.time.tv_sec) { + if (a->time.time.tv_nsec > b->time.time.tv_nsec) + return 1; + else if (a->time.time.tv_nsec == b->time.time.tv_nsec) + return 0; + else + return -1; + } else + return -1; + } +} + +/* enqueue cell to prioq */ +int snd_seq_prioq_cell_in(struct snd_seq_prioq * f, + struct snd_seq_event_cell * cell) +{ + struct snd_seq_event_cell *cur, *prev; + unsigned long flags; + int count; + int prior; + + if (snd_BUG_ON(!f || !cell)) + return -EINVAL; + + /* check flags */ + prior = (cell->event.flags & SNDRV_SEQ_PRIORITY_MASK); + + spin_lock_irqsave(&f->lock, flags); + + /* check if this element needs to inserted at the end (ie. ordered + data is inserted) This will be very likeley if a sequencer + application or midi file player is feeding us (sequential) data */ + if (f->tail && !prior) { + if (compare_timestamp(&cell->event, &f->tail->event)) { + /* add new cell to tail of the fifo */ + f->tail->next = cell; + f->tail = cell; + cell->next = NULL; + f->cells++; + spin_unlock_irqrestore(&f->lock, flags); + return 0; + } + } + /* traverse list of elements to find the place where the new cell is + to be inserted... Note that this is a order n process ! */ + + prev = NULL; /* previous cell */ + cur = f->head; /* cursor */ + + count = 10000; /* FIXME: enough big, isn't it? */ + while (cur != NULL) { + /* compare timestamps */ + int rel = compare_timestamp_rel(&cell->event, &cur->event); + if (rel < 0) + /* new cell has earlier schedule time, */ + break; + else if (rel == 0 && prior) + /* equal schedule time and prior to others */ + break; + /* new cell has equal or larger schedule time, */ + /* move cursor to next cell */ + prev = cur; + cur = cur->next; + if (! --count) { + spin_unlock_irqrestore(&f->lock, flags); + pr_err("ALSA: seq: cannot find a pointer.. infinite loop?\n"); + return -EINVAL; + } + } + + /* insert it before cursor */ + if (prev != NULL) + prev->next = cell; + cell->next = cur; + + if (f->head == cur) /* this is the first cell, set head to it */ + f->head = cell; + if (cur == NULL) /* reached end of the list */ + f->tail = cell; + f->cells++; + spin_unlock_irqrestore(&f->lock, flags); + return 0; +} + +/* return 1 if the current time >= event timestamp */ +static int event_is_ready(struct snd_seq_event *ev, void *current_time) +{ + if ((ev->flags & SNDRV_SEQ_TIME_STAMP_MASK) == SNDRV_SEQ_TIME_STAMP_TICK) + return snd_seq_compare_tick_time(current_time, &ev->time.tick); + else + return snd_seq_compare_real_time(current_time, &ev->time.time); +} + +/* dequeue cell from prioq */ +struct snd_seq_event_cell *snd_seq_prioq_cell_out(struct snd_seq_prioq *f, + void *current_time) +{ + struct snd_seq_event_cell *cell; + unsigned long flags; + + if (f == NULL) { + pr_debug("ALSA: seq: snd_seq_prioq_cell_in() called with NULL prioq\n"); + return NULL; + } + spin_lock_irqsave(&f->lock, flags); + + cell = f->head; + if (cell && current_time && !event_is_ready(&cell->event, current_time)) + cell = NULL; + if (cell) { + f->head = cell->next; + + /* reset tail if this was the last element */ + if (f->tail == cell) + f->tail = NULL; + + cell->next = NULL; + f->cells--; + } + + spin_unlock_irqrestore(&f->lock, flags); + return cell; +} + +/* return number of events available in prioq */ +int snd_seq_prioq_avail(struct snd_seq_prioq * f) +{ + if (f == NULL) { + pr_debug("ALSA: seq: snd_seq_prioq_cell_in() called with NULL prioq\n"); + return 0; + } + return f->cells; +} + +static inline int prioq_match(struct snd_seq_event_cell *cell, + int client, int timestamp) +{ + if (cell->event.source.client == client || + cell->event.dest.client == client) + return 1; + if (!timestamp) + return 0; + switch (cell->event.flags & SNDRV_SEQ_TIME_STAMP_MASK) { + case SNDRV_SEQ_TIME_STAMP_TICK: + if (cell->event.time.tick) + return 1; + break; + case SNDRV_SEQ_TIME_STAMP_REAL: + if (cell->event.time.time.tv_sec || + cell->event.time.time.tv_nsec) + return 1; + break; + } + return 0; +} + +/* remove cells for left client */ +void snd_seq_prioq_leave(struct snd_seq_prioq * f, int client, int timestamp) +{ + register struct snd_seq_event_cell *cell, *next; + unsigned long flags; + struct snd_seq_event_cell *prev = NULL; + struct snd_seq_event_cell *freefirst = NULL, *freeprev = NULL, *freenext; + + /* collect all removed cells */ + spin_lock_irqsave(&f->lock, flags); + cell = f->head; + while (cell) { + next = cell->next; + if (prioq_match(cell, client, timestamp)) { + /* remove cell from prioq */ + if (cell == f->head) { + f->head = cell->next; + } else { + prev->next = cell->next; + } + if (cell == f->tail) + f->tail = cell->next; + f->cells--; + /* add cell to free list */ + cell->next = NULL; + if (freefirst == NULL) { + freefirst = cell; + } else { + freeprev->next = cell; + } + freeprev = cell; + } else { +#if 0 + pr_debug("ALSA: seq: type = %i, source = %i, dest = %i, " + "client = %i\n", + cell->event.type, + cell->event.source.client, + cell->event.dest.client, + client); +#endif + prev = cell; + } + cell = next; + } + spin_unlock_irqrestore(&f->lock, flags); + + /* remove selected cells */ + while (freefirst) { + freenext = freefirst->next; + snd_seq_cell_free(freefirst); + freefirst = freenext; + } +} + +static int prioq_remove_match(struct snd_seq_remove_events *info, + struct snd_seq_event *ev) +{ + int res; + + if (info->remove_mode & SNDRV_SEQ_REMOVE_DEST) { + if (ev->dest.client != info->dest.client || + ev->dest.port != info->dest.port) + return 0; + } + if (info->remove_mode & SNDRV_SEQ_REMOVE_DEST_CHANNEL) { + if (! snd_seq_ev_is_channel_type(ev)) + return 0; + /* data.note.channel and data.control.channel are identical */ + if (ev->data.note.channel != info->channel) + return 0; + } + if (info->remove_mode & SNDRV_SEQ_REMOVE_TIME_AFTER) { + if (info->remove_mode & SNDRV_SEQ_REMOVE_TIME_TICK) + res = snd_seq_compare_tick_time(&ev->time.tick, &info->time.tick); + else + res = snd_seq_compare_real_time(&ev->time.time, &info->time.time); + if (!res) + return 0; + } + if (info->remove_mode & SNDRV_SEQ_REMOVE_TIME_BEFORE) { + if (info->remove_mode & SNDRV_SEQ_REMOVE_TIME_TICK) + res = snd_seq_compare_tick_time(&ev->time.tick, &info->time.tick); + else + res = snd_seq_compare_real_time(&ev->time.time, &info->time.time); + if (res) + return 0; + } + if (info->remove_mode & SNDRV_SEQ_REMOVE_EVENT_TYPE) { + if (ev->type != info->type) + return 0; + } + if (info->remove_mode & SNDRV_SEQ_REMOVE_IGNORE_OFF) { + /* Do not remove off events */ + switch (ev->type) { + case SNDRV_SEQ_EVENT_NOTEOFF: + /* case SNDRV_SEQ_EVENT_SAMPLE_STOP: */ + return 0; + default: + break; + } + } + if (info->remove_mode & SNDRV_SEQ_REMOVE_TAG_MATCH) { + if (info->tag != ev->tag) + return 0; + } + + return 1; +} + +/* remove cells matching remove criteria */ +void snd_seq_prioq_remove_events(struct snd_seq_prioq * f, int client, + struct snd_seq_remove_events *info) +{ + struct snd_seq_event_cell *cell, *next; + unsigned long flags; + struct snd_seq_event_cell *prev = NULL; + struct snd_seq_event_cell *freefirst = NULL, *freeprev = NULL, *freenext; + + /* collect all removed cells */ + spin_lock_irqsave(&f->lock, flags); + cell = f->head; + + while (cell) { + next = cell->next; + if (cell->event.source.client == client && + prioq_remove_match(info, &cell->event)) { + + /* remove cell from prioq */ + if (cell == f->head) { + f->head = cell->next; + } else { + prev->next = cell->next; + } + + if (cell == f->tail) + f->tail = cell->next; + f->cells--; + + /* add cell to free list */ + cell->next = NULL; + if (freefirst == NULL) { + freefirst = cell; + } else { + freeprev->next = cell; + } + + freeprev = cell; + } else { + prev = cell; + } + cell = next; + } + spin_unlock_irqrestore(&f->lock, flags); + + /* remove selected cells */ + while (freefirst) { + freenext = freefirst->next; + snd_seq_cell_free(freefirst); + freefirst = freenext; + } +} + + diff --git a/sound/core/seq/seq_prioq.h b/sound/core/seq/seq_prioq.h new file mode 100644 index 000000000..5811a87de --- /dev/null +++ b/sound/core/seq/seq_prioq.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * ALSA sequencer Priority Queue + * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl> + */ +#ifndef __SND_SEQ_PRIOQ_H +#define __SND_SEQ_PRIOQ_H + +#include "seq_memory.h" + + +/* === PRIOQ === */ + +struct snd_seq_prioq { + struct snd_seq_event_cell *head; /* pointer to head of prioq */ + struct snd_seq_event_cell *tail; /* pointer to tail of prioq */ + int cells; + spinlock_t lock; +}; + + +/* create new prioq (constructor) */ +struct snd_seq_prioq *snd_seq_prioq_new(void); + +/* delete prioq (destructor) */ +void snd_seq_prioq_delete(struct snd_seq_prioq **fifo); + +/* enqueue cell to prioq */ +int snd_seq_prioq_cell_in(struct snd_seq_prioq *f, struct snd_seq_event_cell *cell); + +/* dequeue cell from prioq */ +struct snd_seq_event_cell *snd_seq_prioq_cell_out(struct snd_seq_prioq *f, + void *current_time); + +/* return number of events available in prioq */ +int snd_seq_prioq_avail(struct snd_seq_prioq *f); + +/* client left queue */ +void snd_seq_prioq_leave(struct snd_seq_prioq *f, int client, int timestamp); + +/* Remove events */ +void snd_seq_prioq_remove_events(struct snd_seq_prioq *f, int client, + struct snd_seq_remove_events *info); + +#endif diff --git a/sound/core/seq/seq_queue.c b/sound/core/seq/seq_queue.c new file mode 100644 index 000000000..bc933104c --- /dev/null +++ b/sound/core/seq/seq_queue.c @@ -0,0 +1,774 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA sequencer Timing queue handling + * Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl> + * + * MAJOR CHANGES + * Nov. 13, 1999 Takashi Iwai <iwai@ww.uni-erlangen.de> + * - Queues are allocated dynamically via ioctl. + * - When owner client is deleted, all owned queues are deleted, too. + * - Owner of unlocked queue is kept unmodified even if it is + * manipulated by other clients. + * - Owner field in SET_QUEUE_OWNER ioctl must be identical with the + * caller client. i.e. Changing owner to a third client is not + * allowed. + * + * Aug. 30, 2000 Takashi Iwai + * - Queues are managed in static array again, but with better way. + * The API itself is identical. + * - The queue is locked when struct snd_seq_queue pointer is returned via + * queueptr(). This pointer *MUST* be released afterward by + * queuefree(ptr). + * - Addition of experimental sync support. + */ + +#include <linux/init.h> +#include <linux/slab.h> +#include <sound/core.h> + +#include "seq_memory.h" +#include "seq_queue.h" +#include "seq_clientmgr.h" +#include "seq_fifo.h" +#include "seq_timer.h" +#include "seq_info.h" + +/* list of allocated queues */ +static struct snd_seq_queue *queue_list[SNDRV_SEQ_MAX_QUEUES]; +static DEFINE_SPINLOCK(queue_list_lock); +/* number of queues allocated */ +static int num_queues; + +int snd_seq_queue_get_cur_queues(void) +{ + return num_queues; +} + +/*----------------------------------------------------------------*/ + +/* assign queue id and insert to list */ +static int queue_list_add(struct snd_seq_queue *q) +{ + int i; + unsigned long flags; + + spin_lock_irqsave(&queue_list_lock, flags); + for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) { + if (! queue_list[i]) { + queue_list[i] = q; + q->queue = i; + num_queues++; + spin_unlock_irqrestore(&queue_list_lock, flags); + return i; + } + } + spin_unlock_irqrestore(&queue_list_lock, flags); + return -1; +} + +static struct snd_seq_queue *queue_list_remove(int id, int client) +{ + struct snd_seq_queue *q; + unsigned long flags; + + spin_lock_irqsave(&queue_list_lock, flags); + q = queue_list[id]; + if (q) { + spin_lock(&q->owner_lock); + if (q->owner == client) { + /* found */ + q->klocked = 1; + spin_unlock(&q->owner_lock); + queue_list[id] = NULL; + num_queues--; + spin_unlock_irqrestore(&queue_list_lock, flags); + return q; + } + spin_unlock(&q->owner_lock); + } + spin_unlock_irqrestore(&queue_list_lock, flags); + return NULL; +} + +/*----------------------------------------------------------------*/ + +/* create new queue (constructor) */ +static struct snd_seq_queue *queue_new(int owner, int locked) +{ + struct snd_seq_queue *q; + + q = kzalloc(sizeof(*q), GFP_KERNEL); + if (!q) + return NULL; + + spin_lock_init(&q->owner_lock); + spin_lock_init(&q->check_lock); + mutex_init(&q->timer_mutex); + snd_use_lock_init(&q->use_lock); + q->queue = -1; + + q->tickq = snd_seq_prioq_new(); + q->timeq = snd_seq_prioq_new(); + q->timer = snd_seq_timer_new(); + if (q->tickq == NULL || q->timeq == NULL || q->timer == NULL) { + snd_seq_prioq_delete(&q->tickq); + snd_seq_prioq_delete(&q->timeq); + snd_seq_timer_delete(&q->timer); + kfree(q); + return NULL; + } + + q->owner = owner; + q->locked = locked; + q->klocked = 0; + + return q; +} + +/* delete queue (destructor) */ +static void queue_delete(struct snd_seq_queue *q) +{ + /* stop and release the timer */ + mutex_lock(&q->timer_mutex); + snd_seq_timer_stop(q->timer); + snd_seq_timer_close(q); + mutex_unlock(&q->timer_mutex); + /* wait until access free */ + snd_use_lock_sync(&q->use_lock); + /* release resources... */ + snd_seq_prioq_delete(&q->tickq); + snd_seq_prioq_delete(&q->timeq); + snd_seq_timer_delete(&q->timer); + + kfree(q); +} + + +/*----------------------------------------------------------------*/ + +/* delete all existing queues */ +void snd_seq_queues_delete(void) +{ + int i; + + /* clear list */ + for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) { + if (queue_list[i]) + queue_delete(queue_list[i]); + } +} + +static void queue_use(struct snd_seq_queue *queue, int client, int use); + +/* allocate a new queue - + * return pointer to new queue or ERR_PTR(-errno) for error + * The new queue's use_lock is set to 1. It is the caller's responsibility to + * call snd_use_lock_free(&q->use_lock). + */ +struct snd_seq_queue *snd_seq_queue_alloc(int client, int locked, unsigned int info_flags) +{ + struct snd_seq_queue *q; + + q = queue_new(client, locked); + if (q == NULL) + return ERR_PTR(-ENOMEM); + q->info_flags = info_flags; + queue_use(q, client, 1); + snd_use_lock_use(&q->use_lock); + if (queue_list_add(q) < 0) { + snd_use_lock_free(&q->use_lock); + queue_delete(q); + return ERR_PTR(-ENOMEM); + } + return q; +} + +/* delete a queue - queue must be owned by the client */ +int snd_seq_queue_delete(int client, int queueid) +{ + struct snd_seq_queue *q; + + if (queueid < 0 || queueid >= SNDRV_SEQ_MAX_QUEUES) + return -EINVAL; + q = queue_list_remove(queueid, client); + if (q == NULL) + return -EINVAL; + queue_delete(q); + + return 0; +} + + +/* return pointer to queue structure for specified id */ +struct snd_seq_queue *queueptr(int queueid) +{ + struct snd_seq_queue *q; + unsigned long flags; + + if (queueid < 0 || queueid >= SNDRV_SEQ_MAX_QUEUES) + return NULL; + spin_lock_irqsave(&queue_list_lock, flags); + q = queue_list[queueid]; + if (q) + snd_use_lock_use(&q->use_lock); + spin_unlock_irqrestore(&queue_list_lock, flags); + return q; +} + +/* return the (first) queue matching with the specified name */ +struct snd_seq_queue *snd_seq_queue_find_name(char *name) +{ + int i; + struct snd_seq_queue *q; + + for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) { + q = queueptr(i); + if (q) { + if (strncmp(q->name, name, sizeof(q->name)) == 0) + return q; + queuefree(q); + } + } + return NULL; +} + + +/* -------------------------------------------------------- */ + +#define MAX_CELL_PROCESSES_IN_QUEUE 1000 + +void snd_seq_check_queue(struct snd_seq_queue *q, int atomic, int hop) +{ + unsigned long flags; + struct snd_seq_event_cell *cell; + snd_seq_tick_time_t cur_tick; + snd_seq_real_time_t cur_time; + int processed = 0; + + if (q == NULL) + return; + + /* make this function non-reentrant */ + spin_lock_irqsave(&q->check_lock, flags); + if (q->check_blocked) { + q->check_again = 1; + spin_unlock_irqrestore(&q->check_lock, flags); + return; /* other thread is already checking queues */ + } + q->check_blocked = 1; + spin_unlock_irqrestore(&q->check_lock, flags); + + __again: + /* Process tick queue... */ + cur_tick = snd_seq_timer_get_cur_tick(q->timer); + for (;;) { + cell = snd_seq_prioq_cell_out(q->tickq, &cur_tick); + if (!cell) + break; + snd_seq_dispatch_event(cell, atomic, hop); + if (++processed >= MAX_CELL_PROCESSES_IN_QUEUE) + goto out; /* the rest processed at the next batch */ + } + + /* Process time queue... */ + cur_time = snd_seq_timer_get_cur_time(q->timer, false); + for (;;) { + cell = snd_seq_prioq_cell_out(q->timeq, &cur_time); + if (!cell) + break; + snd_seq_dispatch_event(cell, atomic, hop); + if (++processed >= MAX_CELL_PROCESSES_IN_QUEUE) + goto out; /* the rest processed at the next batch */ + } + + out: + /* free lock */ + spin_lock_irqsave(&q->check_lock, flags); + if (q->check_again) { + q->check_again = 0; + if (processed < MAX_CELL_PROCESSES_IN_QUEUE) { + spin_unlock_irqrestore(&q->check_lock, flags); + goto __again; + } + } + q->check_blocked = 0; + spin_unlock_irqrestore(&q->check_lock, flags); +} + + +/* enqueue a event to singe queue */ +int snd_seq_enqueue_event(struct snd_seq_event_cell *cell, int atomic, int hop) +{ + int dest, err; + struct snd_seq_queue *q; + + if (snd_BUG_ON(!cell)) + return -EINVAL; + dest = cell->event.queue; /* destination queue */ + q = queueptr(dest); + if (q == NULL) + return -EINVAL; + /* handle relative time stamps, convert them into absolute */ + if ((cell->event.flags & SNDRV_SEQ_TIME_MODE_MASK) == SNDRV_SEQ_TIME_MODE_REL) { + switch (cell->event.flags & SNDRV_SEQ_TIME_STAMP_MASK) { + case SNDRV_SEQ_TIME_STAMP_TICK: + cell->event.time.tick += q->timer->tick.cur_tick; + break; + + case SNDRV_SEQ_TIME_STAMP_REAL: + snd_seq_inc_real_time(&cell->event.time.time, + &q->timer->cur_time); + break; + } + cell->event.flags &= ~SNDRV_SEQ_TIME_MODE_MASK; + cell->event.flags |= SNDRV_SEQ_TIME_MODE_ABS; + } + /* enqueue event in the real-time or midi queue */ + switch (cell->event.flags & SNDRV_SEQ_TIME_STAMP_MASK) { + case SNDRV_SEQ_TIME_STAMP_TICK: + err = snd_seq_prioq_cell_in(q->tickq, cell); + break; + + case SNDRV_SEQ_TIME_STAMP_REAL: + default: + err = snd_seq_prioq_cell_in(q->timeq, cell); + break; + } + + if (err < 0) { + queuefree(q); /* unlock */ + return err; + } + + /* trigger dispatching */ + snd_seq_check_queue(q, atomic, hop); + + queuefree(q); /* unlock */ + + return 0; +} + + +/*----------------------------------------------------------------*/ + +static inline int check_access(struct snd_seq_queue *q, int client) +{ + return (q->owner == client) || (!q->locked && !q->klocked); +} + +/* check if the client has permission to modify queue parameters. + * if it does, lock the queue + */ +static int queue_access_lock(struct snd_seq_queue *q, int client) +{ + unsigned long flags; + int access_ok; + + spin_lock_irqsave(&q->owner_lock, flags); + access_ok = check_access(q, client); + if (access_ok) + q->klocked = 1; + spin_unlock_irqrestore(&q->owner_lock, flags); + return access_ok; +} + +/* unlock the queue */ +static inline void queue_access_unlock(struct snd_seq_queue *q) +{ + unsigned long flags; + + spin_lock_irqsave(&q->owner_lock, flags); + q->klocked = 0; + spin_unlock_irqrestore(&q->owner_lock, flags); +} + +/* exported - only checking permission */ +int snd_seq_queue_check_access(int queueid, int client) +{ + struct snd_seq_queue *q = queueptr(queueid); + int access_ok; + unsigned long flags; + + if (! q) + return 0; + spin_lock_irqsave(&q->owner_lock, flags); + access_ok = check_access(q, client); + spin_unlock_irqrestore(&q->owner_lock, flags); + queuefree(q); + return access_ok; +} + +/*----------------------------------------------------------------*/ + +/* + * change queue's owner and permission + */ +int snd_seq_queue_set_owner(int queueid, int client, int locked) +{ + struct snd_seq_queue *q = queueptr(queueid); + unsigned long flags; + + if (q == NULL) + return -EINVAL; + + if (! queue_access_lock(q, client)) { + queuefree(q); + return -EPERM; + } + + spin_lock_irqsave(&q->owner_lock, flags); + q->locked = locked ? 1 : 0; + q->owner = client; + spin_unlock_irqrestore(&q->owner_lock, flags); + queue_access_unlock(q); + queuefree(q); + + return 0; +} + + +/*----------------------------------------------------------------*/ + +/* open timer - + * q->use mutex should be down before calling this function to avoid + * confliction with snd_seq_queue_use() + */ +int snd_seq_queue_timer_open(int queueid) +{ + int result = 0; + struct snd_seq_queue *queue; + struct snd_seq_timer *tmr; + + queue = queueptr(queueid); + if (queue == NULL) + return -EINVAL; + tmr = queue->timer; + result = snd_seq_timer_open(queue); + if (result < 0) { + snd_seq_timer_defaults(tmr); + result = snd_seq_timer_open(queue); + } + queuefree(queue); + return result; +} + +/* close timer - + * q->use mutex should be down before calling this function + */ +int snd_seq_queue_timer_close(int queueid) +{ + struct snd_seq_queue *queue; + int result = 0; + + queue = queueptr(queueid); + if (queue == NULL) + return -EINVAL; + snd_seq_timer_close(queue); + queuefree(queue); + return result; +} + +/* change queue tempo and ppq */ +int snd_seq_queue_timer_set_tempo(int queueid, int client, + struct snd_seq_queue_tempo *info) +{ + struct snd_seq_queue *q = queueptr(queueid); + int result; + + if (q == NULL) + return -EINVAL; + if (! queue_access_lock(q, client)) { + queuefree(q); + return -EPERM; + } + + result = snd_seq_timer_set_tempo_ppq(q->timer, info->tempo, info->ppq); + if (result >= 0 && info->skew_base > 0) + result = snd_seq_timer_set_skew(q->timer, info->skew_value, + info->skew_base); + queue_access_unlock(q); + queuefree(q); + return result; +} + +/* use or unuse this queue */ +static void queue_use(struct snd_seq_queue *queue, int client, int use) +{ + if (use) { + if (!test_and_set_bit(client, queue->clients_bitmap)) + queue->clients++; + } else { + if (test_and_clear_bit(client, queue->clients_bitmap)) + queue->clients--; + } + if (queue->clients) { + if (use && queue->clients == 1) + snd_seq_timer_defaults(queue->timer); + snd_seq_timer_open(queue); + } else { + snd_seq_timer_close(queue); + } +} + +/* use or unuse this queue - + * if it is the first client, starts the timer. + * if it is not longer used by any clients, stop the timer. + */ +int snd_seq_queue_use(int queueid, int client, int use) +{ + struct snd_seq_queue *queue; + + queue = queueptr(queueid); + if (queue == NULL) + return -EINVAL; + mutex_lock(&queue->timer_mutex); + queue_use(queue, client, use); + mutex_unlock(&queue->timer_mutex); + queuefree(queue); + return 0; +} + +/* + * check if queue is used by the client + * return negative value if the queue is invalid. + * return 0 if not used, 1 if used. + */ +int snd_seq_queue_is_used(int queueid, int client) +{ + struct snd_seq_queue *q; + int result; + + q = queueptr(queueid); + if (q == NULL) + return -EINVAL; /* invalid queue */ + result = test_bit(client, q->clients_bitmap) ? 1 : 0; + queuefree(q); + return result; +} + + +/*----------------------------------------------------------------*/ + +/* final stage notification - + * remove cells for no longer exist client (for non-owned queue) + * or delete this queue (for owned queue) + */ +void snd_seq_queue_client_leave(int client) +{ + int i; + struct snd_seq_queue *q; + + /* delete own queues from queue list */ + for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) { + q = queue_list_remove(i, client); + if (q) + queue_delete(q); + } + + /* remove cells from existing queues - + * they are not owned by this client + */ + for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) { + q = queueptr(i); + if (!q) + continue; + if (test_bit(client, q->clients_bitmap)) { + snd_seq_prioq_leave(q->tickq, client, 0); + snd_seq_prioq_leave(q->timeq, client, 0); + snd_seq_queue_use(q->queue, client, 0); + } + queuefree(q); + } +} + + + +/*----------------------------------------------------------------*/ + +/* remove cells from all queues */ +void snd_seq_queue_client_leave_cells(int client) +{ + int i; + struct snd_seq_queue *q; + + for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) { + q = queueptr(i); + if (!q) + continue; + snd_seq_prioq_leave(q->tickq, client, 0); + snd_seq_prioq_leave(q->timeq, client, 0); + queuefree(q); + } +} + +/* remove cells based on flush criteria */ +void snd_seq_queue_remove_cells(int client, struct snd_seq_remove_events *info) +{ + int i; + struct snd_seq_queue *q; + + for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) { + q = queueptr(i); + if (!q) + continue; + if (test_bit(client, q->clients_bitmap) && + (! (info->remove_mode & SNDRV_SEQ_REMOVE_DEST) || + q->queue == info->queue)) { + snd_seq_prioq_remove_events(q->tickq, client, info); + snd_seq_prioq_remove_events(q->timeq, client, info); + } + queuefree(q); + } +} + +/*----------------------------------------------------------------*/ + +/* + * send events to all subscribed ports + */ +static void queue_broadcast_event(struct snd_seq_queue *q, struct snd_seq_event *ev, + int atomic, int hop) +{ + struct snd_seq_event sev; + + sev = *ev; + + sev.flags = SNDRV_SEQ_TIME_STAMP_TICK|SNDRV_SEQ_TIME_MODE_ABS; + sev.time.tick = q->timer->tick.cur_tick; + sev.queue = q->queue; + sev.data.queue.queue = q->queue; + + /* broadcast events from Timer port */ + sev.source.client = SNDRV_SEQ_CLIENT_SYSTEM; + sev.source.port = SNDRV_SEQ_PORT_SYSTEM_TIMER; + sev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS; + snd_seq_kernel_client_dispatch(SNDRV_SEQ_CLIENT_SYSTEM, &sev, atomic, hop); +} + +/* + * process a received queue-control event. + * this function is exported for seq_sync.c. + */ +static void snd_seq_queue_process_event(struct snd_seq_queue *q, + struct snd_seq_event *ev, + int atomic, int hop) +{ + switch (ev->type) { + case SNDRV_SEQ_EVENT_START: + snd_seq_prioq_leave(q->tickq, ev->source.client, 1); + snd_seq_prioq_leave(q->timeq, ev->source.client, 1); + if (! snd_seq_timer_start(q->timer)) + queue_broadcast_event(q, ev, atomic, hop); + break; + + case SNDRV_SEQ_EVENT_CONTINUE: + if (! snd_seq_timer_continue(q->timer)) + queue_broadcast_event(q, ev, atomic, hop); + break; + + case SNDRV_SEQ_EVENT_STOP: + snd_seq_timer_stop(q->timer); + queue_broadcast_event(q, ev, atomic, hop); + break; + + case SNDRV_SEQ_EVENT_TEMPO: + snd_seq_timer_set_tempo(q->timer, ev->data.queue.param.value); + queue_broadcast_event(q, ev, atomic, hop); + break; + + case SNDRV_SEQ_EVENT_SETPOS_TICK: + if (snd_seq_timer_set_position_tick(q->timer, ev->data.queue.param.time.tick) == 0) { + queue_broadcast_event(q, ev, atomic, hop); + } + break; + + case SNDRV_SEQ_EVENT_SETPOS_TIME: + if (snd_seq_timer_set_position_time(q->timer, ev->data.queue.param.time.time) == 0) { + queue_broadcast_event(q, ev, atomic, hop); + } + break; + case SNDRV_SEQ_EVENT_QUEUE_SKEW: + if (snd_seq_timer_set_skew(q->timer, + ev->data.queue.param.skew.value, + ev->data.queue.param.skew.base) == 0) { + queue_broadcast_event(q, ev, atomic, hop); + } + break; + } +} + + +/* + * Queue control via timer control port: + * this function is exported as a callback of timer port. + */ +int snd_seq_control_queue(struct snd_seq_event *ev, int atomic, int hop) +{ + struct snd_seq_queue *q; + + if (snd_BUG_ON(!ev)) + return -EINVAL; + q = queueptr(ev->data.queue.queue); + + if (q == NULL) + return -EINVAL; + + if (! queue_access_lock(q, ev->source.client)) { + queuefree(q); + return -EPERM; + } + + snd_seq_queue_process_event(q, ev, atomic, hop); + + queue_access_unlock(q); + queuefree(q); + return 0; +} + + +/*----------------------------------------------------------------*/ + +#ifdef CONFIG_SND_PROC_FS +/* exported to seq_info.c */ +void snd_seq_info_queues_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + int i, bpm; + struct snd_seq_queue *q; + struct snd_seq_timer *tmr; + bool locked; + int owner; + + for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) { + q = queueptr(i); + if (!q) + continue; + + tmr = q->timer; + if (tmr->tempo) + bpm = 60000000 / tmr->tempo; + else + bpm = 0; + + spin_lock_irq(&q->owner_lock); + locked = q->locked; + owner = q->owner; + spin_unlock_irq(&q->owner_lock); + + snd_iprintf(buffer, "queue %d: [%s]\n", q->queue, q->name); + snd_iprintf(buffer, "owned by client : %d\n", owner); + snd_iprintf(buffer, "lock status : %s\n", locked ? "Locked" : "Free"); + snd_iprintf(buffer, "queued time events : %d\n", snd_seq_prioq_avail(q->timeq)); + snd_iprintf(buffer, "queued tick events : %d\n", snd_seq_prioq_avail(q->tickq)); + snd_iprintf(buffer, "timer state : %s\n", tmr->running ? "Running" : "Stopped"); + snd_iprintf(buffer, "timer PPQ : %d\n", tmr->ppq); + snd_iprintf(buffer, "current tempo : %d\n", tmr->tempo); + snd_iprintf(buffer, "current BPM : %d\n", bpm); + snd_iprintf(buffer, "current time : %d.%09d s\n", tmr->cur_time.tv_sec, tmr->cur_time.tv_nsec); + snd_iprintf(buffer, "current tick : %d\n", tmr->tick.cur_tick); + snd_iprintf(buffer, "\n"); + queuefree(q); + } +} +#endif /* CONFIG_SND_PROC_FS */ + diff --git a/sound/core/seq/seq_queue.h b/sound/core/seq/seq_queue.h new file mode 100644 index 000000000..c69105dc1 --- /dev/null +++ b/sound/core/seq/seq_queue.h @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * ALSA sequencer Queue handling + * Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl> + */ +#ifndef __SND_SEQ_QUEUE_H +#define __SND_SEQ_QUEUE_H + +#include "seq_memory.h" +#include "seq_prioq.h" +#include "seq_timer.h" +#include "seq_lock.h" +#include <linux/interrupt.h> +#include <linux/list.h> +#include <linux/bitops.h> + +#define SEQ_QUEUE_NO_OWNER (-1) + +struct snd_seq_queue { + int queue; /* queue number */ + + char name[64]; /* name of this queue */ + + struct snd_seq_prioq *tickq; /* midi tick event queue */ + struct snd_seq_prioq *timeq; /* real-time event queue */ + + struct snd_seq_timer *timer; /* time keeper for this queue */ + int owner; /* client that 'owns' the timer */ + bool locked; /* timer is only accesibble by owner if set */ + bool klocked; /* kernel lock (after START) */ + bool check_again; /* concurrent access happened during check */ + bool check_blocked; /* queue being checked */ + + unsigned int flags; /* status flags */ + unsigned int info_flags; /* info for sync */ + + spinlock_t owner_lock; + spinlock_t check_lock; + + /* clients which uses this queue (bitmap) */ + DECLARE_BITMAP(clients_bitmap, SNDRV_SEQ_MAX_CLIENTS); + unsigned int clients; /* users of this queue */ + struct mutex timer_mutex; + + snd_use_lock_t use_lock; +}; + + +/* get the number of current queues */ +int snd_seq_queue_get_cur_queues(void); + +/* delete queues */ +void snd_seq_queues_delete(void); + + +/* create new queue (constructor) */ +struct snd_seq_queue *snd_seq_queue_alloc(int client, int locked, unsigned int flags); + +/* delete queue (destructor) */ +int snd_seq_queue_delete(int client, int queueid); + +/* final stage */ +void snd_seq_queue_client_leave(int client); + +/* enqueue a event received from one the clients */ +int snd_seq_enqueue_event(struct snd_seq_event_cell *cell, int atomic, int hop); + +/* Remove events */ +void snd_seq_queue_client_leave_cells(int client); +void snd_seq_queue_remove_cells(int client, struct snd_seq_remove_events *info); + +/* return pointer to queue structure for specified id */ +struct snd_seq_queue *queueptr(int queueid); +/* unlock */ +#define queuefree(q) snd_use_lock_free(&(q)->use_lock) + +/* return the (first) queue matching with the specified name */ +struct snd_seq_queue *snd_seq_queue_find_name(char *name); + +/* check single queue and dispatch events */ +void snd_seq_check_queue(struct snd_seq_queue *q, int atomic, int hop); + +/* access to queue's parameters */ +int snd_seq_queue_check_access(int queueid, int client); +int snd_seq_queue_timer_set_tempo(int queueid, int client, struct snd_seq_queue_tempo *info); +int snd_seq_queue_set_owner(int queueid, int client, int locked); +int snd_seq_queue_set_locked(int queueid, int client, int locked); +int snd_seq_queue_timer_open(int queueid); +int snd_seq_queue_timer_close(int queueid); +int snd_seq_queue_use(int queueid, int client, int use); +int snd_seq_queue_is_used(int queueid, int client); + +int snd_seq_control_queue(struct snd_seq_event *ev, int atomic, int hop); + +#endif diff --git a/sound/core/seq/seq_system.c b/sound/core/seq/seq_system.c new file mode 100644 index 000000000..32c2d9b57 --- /dev/null +++ b/sound/core/seq/seq_system.c @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA sequencer System services Client + * Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl> + */ + +#include <linux/init.h> +#include <linux/export.h> +#include <linux/slab.h> +#include <sound/core.h> +#include "seq_system.h" +#include "seq_timer.h" +#include "seq_queue.h" + +/* internal client that provide system services, access to timer etc. */ + +/* + * Port "Timer" + * - send tempo /start/stop etc. events to this port to manipulate the + * queue's timer. The queue address is specified in + * data.queue.queue. + * - this port supports subscription. The received timer events are + * broadcasted to all subscribed clients. The modified tempo + * value is stored on data.queue.value. + * The modifier client/port is not send. + * + * Port "Announce" + * - does not receive message + * - supports supscription. For each client or port attaching to or + * detaching from the system an announcement is send to the subscribed + * clients. + * + * Idea: the subscription mechanism might also work handy for distributing + * synchronisation and timing information. In this case we would ideally have + * a list of subscribers for each type of sync (time, tick), for each timing + * queue. + * + * NOTE: the queue to be started, stopped, etc. must be specified + * in data.queue.addr.queue field. queue is used only for + * scheduling, and no longer referred as affected queue. + * They are used only for timer broadcast (see above). + * -- iwai + */ + + +/* client id of our system client */ +static int sysclient = -1; + +/* port id numbers for this client */ +static int announce_port = -1; + + + +/* fill standard header data, source port & channel are filled in */ +static int setheader(struct snd_seq_event * ev, int client, int port) +{ + if (announce_port < 0) + return -ENODEV; + + memset(ev, 0, sizeof(struct snd_seq_event)); + + ev->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK; + ev->flags |= SNDRV_SEQ_EVENT_LENGTH_FIXED; + + ev->source.client = sysclient; + ev->source.port = announce_port; + ev->dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS; + + /* fill data */ + /*ev->data.addr.queue = SNDRV_SEQ_ADDRESS_UNKNOWN;*/ + ev->data.addr.client = client; + ev->data.addr.port = port; + + return 0; +} + + +/* entry points for broadcasting system events */ +void snd_seq_system_broadcast(int client, int port, int type) +{ + struct snd_seq_event ev; + + if (setheader(&ev, client, port) < 0) + return; + ev.type = type; + snd_seq_kernel_client_dispatch(sysclient, &ev, 0, 0); +} + +/* entry points for broadcasting system events */ +int snd_seq_system_notify(int client, int port, struct snd_seq_event *ev) +{ + ev->flags = SNDRV_SEQ_EVENT_LENGTH_FIXED; + ev->source.client = sysclient; + ev->source.port = announce_port; + ev->dest.client = client; + ev->dest.port = port; + return snd_seq_kernel_client_dispatch(sysclient, ev, 0, 0); +} + +/* call-back handler for timer events */ +static int event_input_timer(struct snd_seq_event * ev, int direct, void *private_data, int atomic, int hop) +{ + return snd_seq_control_queue(ev, atomic, hop); +} + +/* register our internal client */ +int __init snd_seq_system_client_init(void) +{ + struct snd_seq_port_callback pcallbacks; + struct snd_seq_port_info *port; + int err; + + port = kzalloc(sizeof(*port), GFP_KERNEL); + if (!port) + return -ENOMEM; + + memset(&pcallbacks, 0, sizeof(pcallbacks)); + pcallbacks.owner = THIS_MODULE; + pcallbacks.event_input = event_input_timer; + + /* register client */ + sysclient = snd_seq_create_kernel_client(NULL, 0, "System"); + if (sysclient < 0) { + kfree(port); + return sysclient; + } + + /* register timer */ + strcpy(port->name, "Timer"); + port->capability = SNDRV_SEQ_PORT_CAP_WRITE; /* accept queue control */ + port->capability |= SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_SUBS_READ; /* for broadcast */ + port->kernel = &pcallbacks; + port->type = 0; + port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT; + port->addr.client = sysclient; + port->addr.port = SNDRV_SEQ_PORT_SYSTEM_TIMER; + err = snd_seq_kernel_client_ctl(sysclient, SNDRV_SEQ_IOCTL_CREATE_PORT, + port); + if (err < 0) + goto error_port; + + /* register announcement port */ + strcpy(port->name, "Announce"); + port->capability = SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_SUBS_READ; /* for broadcast only */ + port->kernel = NULL; + port->type = 0; + port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT; + port->addr.client = sysclient; + port->addr.port = SNDRV_SEQ_PORT_SYSTEM_ANNOUNCE; + err = snd_seq_kernel_client_ctl(sysclient, SNDRV_SEQ_IOCTL_CREATE_PORT, + port); + if (err < 0) + goto error_port; + announce_port = port->addr.port; + + kfree(port); + return 0; + + error_port: + snd_seq_system_client_done(); + kfree(port); + return err; +} + + +/* unregister our internal client */ +void snd_seq_system_client_done(void) +{ + int oldsysclient = sysclient; + + if (oldsysclient >= 0) { + sysclient = -1; + announce_port = -1; + snd_seq_delete_kernel_client(oldsysclient); + } +} diff --git a/sound/core/seq/seq_system.h b/sound/core/seq/seq_system.h new file mode 100644 index 000000000..4fe88ad40 --- /dev/null +++ b/sound/core/seq/seq_system.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * ALSA sequencer System Client + * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl> + */ +#ifndef __SND_SEQ_SYSTEM_H +#define __SND_SEQ_SYSTEM_H + +#include <sound/seq_kernel.h> + + +/* entry points for broadcasting system events */ +void snd_seq_system_broadcast(int client, int port, int type); + +#define snd_seq_system_client_ev_client_start(client) snd_seq_system_broadcast(client, 0, SNDRV_SEQ_EVENT_CLIENT_START) +#define snd_seq_system_client_ev_client_exit(client) snd_seq_system_broadcast(client, 0, SNDRV_SEQ_EVENT_CLIENT_EXIT) +#define snd_seq_system_client_ev_client_change(client) snd_seq_system_broadcast(client, 0, SNDRV_SEQ_EVENT_CLIENT_CHANGE) +#define snd_seq_system_client_ev_port_start(client, port) snd_seq_system_broadcast(client, port, SNDRV_SEQ_EVENT_PORT_START) +#define snd_seq_system_client_ev_port_exit(client, port) snd_seq_system_broadcast(client, port, SNDRV_SEQ_EVENT_PORT_EXIT) +#define snd_seq_system_client_ev_port_change(client, port) snd_seq_system_broadcast(client, port, SNDRV_SEQ_EVENT_PORT_CHANGE) + +int snd_seq_system_notify(int client, int port, struct snd_seq_event *ev); + +/* register our internal client */ +int snd_seq_system_client_init(void); + +/* unregister our internal client */ +void snd_seq_system_client_done(void); + + +#endif diff --git a/sound/core/seq/seq_timer.c b/sound/core/seq/seq_timer.c new file mode 100644 index 000000000..9863be6fd --- /dev/null +++ b/sound/core/seq/seq_timer.c @@ -0,0 +1,506 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA sequencer Timer + * Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl> + * Jaroslav Kysela <perex@perex.cz> + */ + +#include <sound/core.h> +#include <linux/slab.h> +#include "seq_timer.h" +#include "seq_queue.h" +#include "seq_info.h" + +/* allowed sequencer timer frequencies, in Hz */ +#define MIN_FREQUENCY 10 +#define MAX_FREQUENCY 6250 +#define DEFAULT_FREQUENCY 1000 + +#define SKEW_BASE 0x10000 /* 16bit shift */ + +static void snd_seq_timer_set_tick_resolution(struct snd_seq_timer *tmr) +{ + if (tmr->tempo < 1000000) + tmr->tick.resolution = (tmr->tempo * 1000) / tmr->ppq; + else { + /* might overflow.. */ + unsigned int s; + s = tmr->tempo % tmr->ppq; + s = (s * 1000) / tmr->ppq; + tmr->tick.resolution = (tmr->tempo / tmr->ppq) * 1000; + tmr->tick.resolution += s; + } + if (tmr->tick.resolution <= 0) + tmr->tick.resolution = 1; + snd_seq_timer_update_tick(&tmr->tick, 0); +} + +/* create new timer (constructor) */ +struct snd_seq_timer *snd_seq_timer_new(void) +{ + struct snd_seq_timer *tmr; + + tmr = kzalloc(sizeof(*tmr), GFP_KERNEL); + if (!tmr) + return NULL; + spin_lock_init(&tmr->lock); + + /* reset setup to defaults */ + snd_seq_timer_defaults(tmr); + + /* reset time */ + snd_seq_timer_reset(tmr); + + return tmr; +} + +/* delete timer (destructor) */ +void snd_seq_timer_delete(struct snd_seq_timer **tmr) +{ + struct snd_seq_timer *t = *tmr; + *tmr = NULL; + + if (t == NULL) { + pr_debug("ALSA: seq: snd_seq_timer_delete() called with NULL timer\n"); + return; + } + t->running = 0; + + /* reset time */ + snd_seq_timer_stop(t); + snd_seq_timer_reset(t); + + kfree(t); +} + +void snd_seq_timer_defaults(struct snd_seq_timer * tmr) +{ + unsigned long flags; + + spin_lock_irqsave(&tmr->lock, flags); + /* setup defaults */ + tmr->ppq = 96; /* 96 PPQ */ + tmr->tempo = 500000; /* 120 BPM */ + snd_seq_timer_set_tick_resolution(tmr); + tmr->running = 0; + + tmr->type = SNDRV_SEQ_TIMER_ALSA; + tmr->alsa_id.dev_class = seq_default_timer_class; + tmr->alsa_id.dev_sclass = seq_default_timer_sclass; + tmr->alsa_id.card = seq_default_timer_card; + tmr->alsa_id.device = seq_default_timer_device; + tmr->alsa_id.subdevice = seq_default_timer_subdevice; + tmr->preferred_resolution = seq_default_timer_resolution; + + tmr->skew = tmr->skew_base = SKEW_BASE; + spin_unlock_irqrestore(&tmr->lock, flags); +} + +static void seq_timer_reset(struct snd_seq_timer *tmr) +{ + /* reset time & songposition */ + tmr->cur_time.tv_sec = 0; + tmr->cur_time.tv_nsec = 0; + + tmr->tick.cur_tick = 0; + tmr->tick.fraction = 0; +} + +void snd_seq_timer_reset(struct snd_seq_timer *tmr) +{ + unsigned long flags; + + spin_lock_irqsave(&tmr->lock, flags); + seq_timer_reset(tmr); + spin_unlock_irqrestore(&tmr->lock, flags); +} + + +/* called by timer interrupt routine. the period time since previous invocation is passed */ +static void snd_seq_timer_interrupt(struct snd_timer_instance *timeri, + unsigned long resolution, + unsigned long ticks) +{ + unsigned long flags; + struct snd_seq_queue *q = timeri->callback_data; + struct snd_seq_timer *tmr; + + if (q == NULL) + return; + tmr = q->timer; + if (tmr == NULL) + return; + spin_lock_irqsave(&tmr->lock, flags); + if (!tmr->running) { + spin_unlock_irqrestore(&tmr->lock, flags); + return; + } + + resolution *= ticks; + if (tmr->skew != tmr->skew_base) { + /* FIXME: assuming skew_base = 0x10000 */ + resolution = (resolution >> 16) * tmr->skew + + (((resolution & 0xffff) * tmr->skew) >> 16); + } + + /* update timer */ + snd_seq_inc_time_nsec(&tmr->cur_time, resolution); + + /* calculate current tick */ + snd_seq_timer_update_tick(&tmr->tick, resolution); + + /* register actual time of this timer update */ + ktime_get_ts64(&tmr->last_update); + + spin_unlock_irqrestore(&tmr->lock, flags); + + /* check queues and dispatch events */ + snd_seq_check_queue(q, 1, 0); +} + +/* set current tempo */ +int snd_seq_timer_set_tempo(struct snd_seq_timer * tmr, int tempo) +{ + unsigned long flags; + + if (snd_BUG_ON(!tmr)) + return -EINVAL; + if (tempo <= 0) + return -EINVAL; + spin_lock_irqsave(&tmr->lock, flags); + if ((unsigned int)tempo != tmr->tempo) { + tmr->tempo = tempo; + snd_seq_timer_set_tick_resolution(tmr); + } + spin_unlock_irqrestore(&tmr->lock, flags); + return 0; +} + +/* set current tempo and ppq in a shot */ +int snd_seq_timer_set_tempo_ppq(struct snd_seq_timer *tmr, int tempo, int ppq) +{ + int changed; + unsigned long flags; + + if (snd_BUG_ON(!tmr)) + return -EINVAL; + if (tempo <= 0 || ppq <= 0) + return -EINVAL; + spin_lock_irqsave(&tmr->lock, flags); + if (tmr->running && (ppq != tmr->ppq)) { + /* refuse to change ppq on running timers */ + /* because it will upset the song position (ticks) */ + spin_unlock_irqrestore(&tmr->lock, flags); + pr_debug("ALSA: seq: cannot change ppq of a running timer\n"); + return -EBUSY; + } + changed = (tempo != tmr->tempo) || (ppq != tmr->ppq); + tmr->tempo = tempo; + tmr->ppq = ppq; + if (changed) + snd_seq_timer_set_tick_resolution(tmr); + spin_unlock_irqrestore(&tmr->lock, flags); + return 0; +} + +/* set current tick position */ +int snd_seq_timer_set_position_tick(struct snd_seq_timer *tmr, + snd_seq_tick_time_t position) +{ + unsigned long flags; + + if (snd_BUG_ON(!tmr)) + return -EINVAL; + + spin_lock_irqsave(&tmr->lock, flags); + tmr->tick.cur_tick = position; + tmr->tick.fraction = 0; + spin_unlock_irqrestore(&tmr->lock, flags); + return 0; +} + +/* set current real-time position */ +int snd_seq_timer_set_position_time(struct snd_seq_timer *tmr, + snd_seq_real_time_t position) +{ + unsigned long flags; + + if (snd_BUG_ON(!tmr)) + return -EINVAL; + + snd_seq_sanity_real_time(&position); + spin_lock_irqsave(&tmr->lock, flags); + tmr->cur_time = position; + spin_unlock_irqrestore(&tmr->lock, flags); + return 0; +} + +/* set timer skew */ +int snd_seq_timer_set_skew(struct snd_seq_timer *tmr, unsigned int skew, + unsigned int base) +{ + unsigned long flags; + + if (snd_BUG_ON(!tmr)) + return -EINVAL; + + /* FIXME */ + if (base != SKEW_BASE) { + pr_debug("ALSA: seq: invalid skew base 0x%x\n", base); + return -EINVAL; + } + spin_lock_irqsave(&tmr->lock, flags); + tmr->skew = skew; + spin_unlock_irqrestore(&tmr->lock, flags); + return 0; +} + +int snd_seq_timer_open(struct snd_seq_queue *q) +{ + struct snd_timer_instance *t; + struct snd_seq_timer *tmr; + char str[32]; + int err; + + tmr = q->timer; + if (snd_BUG_ON(!tmr)) + return -EINVAL; + if (tmr->timeri) + return -EBUSY; + sprintf(str, "sequencer queue %i", q->queue); + if (tmr->type != SNDRV_SEQ_TIMER_ALSA) /* standard ALSA timer */ + return -EINVAL; + if (tmr->alsa_id.dev_class != SNDRV_TIMER_CLASS_SLAVE) + tmr->alsa_id.dev_sclass = SNDRV_TIMER_SCLASS_SEQUENCER; + t = snd_timer_instance_new(str); + if (!t) + return -ENOMEM; + t->callback = snd_seq_timer_interrupt; + t->callback_data = q; + t->flags |= SNDRV_TIMER_IFLG_AUTO; + err = snd_timer_open(t, &tmr->alsa_id, q->queue); + if (err < 0 && tmr->alsa_id.dev_class != SNDRV_TIMER_CLASS_SLAVE) { + if (tmr->alsa_id.dev_class != SNDRV_TIMER_CLASS_GLOBAL || + tmr->alsa_id.device != SNDRV_TIMER_GLOBAL_SYSTEM) { + struct snd_timer_id tid; + memset(&tid, 0, sizeof(tid)); + tid.dev_class = SNDRV_TIMER_CLASS_GLOBAL; + tid.dev_sclass = SNDRV_TIMER_SCLASS_SEQUENCER; + tid.card = -1; + tid.device = SNDRV_TIMER_GLOBAL_SYSTEM; + err = snd_timer_open(t, &tid, q->queue); + } + } + if (err < 0) { + pr_err("ALSA: seq fatal error: cannot create timer (%i)\n", err); + snd_timer_instance_free(t); + return err; + } + spin_lock_irq(&tmr->lock); + if (tmr->timeri) + err = -EBUSY; + else + tmr->timeri = t; + spin_unlock_irq(&tmr->lock); + if (err < 0) { + snd_timer_close(t); + snd_timer_instance_free(t); + return err; + } + return 0; +} + +int snd_seq_timer_close(struct snd_seq_queue *q) +{ + struct snd_seq_timer *tmr; + struct snd_timer_instance *t; + + tmr = q->timer; + if (snd_BUG_ON(!tmr)) + return -EINVAL; + spin_lock_irq(&tmr->lock); + t = tmr->timeri; + tmr->timeri = NULL; + spin_unlock_irq(&tmr->lock); + if (t) { + snd_timer_close(t); + snd_timer_instance_free(t); + } + return 0; +} + +static int seq_timer_stop(struct snd_seq_timer *tmr) +{ + if (! tmr->timeri) + return -EINVAL; + if (!tmr->running) + return 0; + tmr->running = 0; + snd_timer_pause(tmr->timeri); + return 0; +} + +int snd_seq_timer_stop(struct snd_seq_timer *tmr) +{ + unsigned long flags; + int err; + + spin_lock_irqsave(&tmr->lock, flags); + err = seq_timer_stop(tmr); + spin_unlock_irqrestore(&tmr->lock, flags); + return err; +} + +static int initialize_timer(struct snd_seq_timer *tmr) +{ + struct snd_timer *t; + unsigned long freq; + + t = tmr->timeri->timer; + if (!t) + return -EINVAL; + + freq = tmr->preferred_resolution; + if (!freq) + freq = DEFAULT_FREQUENCY; + else if (freq < MIN_FREQUENCY) + freq = MIN_FREQUENCY; + else if (freq > MAX_FREQUENCY) + freq = MAX_FREQUENCY; + + tmr->ticks = 1; + if (!(t->hw.flags & SNDRV_TIMER_HW_SLAVE)) { + unsigned long r = snd_timer_resolution(tmr->timeri); + if (r) { + tmr->ticks = (unsigned int)(1000000000uL / (r * freq)); + if (! tmr->ticks) + tmr->ticks = 1; + } + } + tmr->initialized = 1; + return 0; +} + +static int seq_timer_start(struct snd_seq_timer *tmr) +{ + if (! tmr->timeri) + return -EINVAL; + if (tmr->running) + seq_timer_stop(tmr); + seq_timer_reset(tmr); + if (initialize_timer(tmr) < 0) + return -EINVAL; + snd_timer_start(tmr->timeri, tmr->ticks); + tmr->running = 1; + ktime_get_ts64(&tmr->last_update); + return 0; +} + +int snd_seq_timer_start(struct snd_seq_timer *tmr) +{ + unsigned long flags; + int err; + + spin_lock_irqsave(&tmr->lock, flags); + err = seq_timer_start(tmr); + spin_unlock_irqrestore(&tmr->lock, flags); + return err; +} + +static int seq_timer_continue(struct snd_seq_timer *tmr) +{ + if (! tmr->timeri) + return -EINVAL; + if (tmr->running) + return -EBUSY; + if (! tmr->initialized) { + seq_timer_reset(tmr); + if (initialize_timer(tmr) < 0) + return -EINVAL; + } + snd_timer_start(tmr->timeri, tmr->ticks); + tmr->running = 1; + ktime_get_ts64(&tmr->last_update); + return 0; +} + +int snd_seq_timer_continue(struct snd_seq_timer *tmr) +{ + unsigned long flags; + int err; + + spin_lock_irqsave(&tmr->lock, flags); + err = seq_timer_continue(tmr); + spin_unlock_irqrestore(&tmr->lock, flags); + return err; +} + +/* return current 'real' time. use timeofday() to get better granularity. */ +snd_seq_real_time_t snd_seq_timer_get_cur_time(struct snd_seq_timer *tmr, + bool adjust_ktime) +{ + snd_seq_real_time_t cur_time; + unsigned long flags; + + spin_lock_irqsave(&tmr->lock, flags); + cur_time = tmr->cur_time; + if (adjust_ktime && tmr->running) { + struct timespec64 tm; + + ktime_get_ts64(&tm); + tm = timespec64_sub(tm, tmr->last_update); + cur_time.tv_nsec += tm.tv_nsec; + cur_time.tv_sec += tm.tv_sec; + snd_seq_sanity_real_time(&cur_time); + } + spin_unlock_irqrestore(&tmr->lock, flags); + return cur_time; +} + +/* TODO: use interpolation on tick queue (will only be useful for very + high PPQ values) */ +snd_seq_tick_time_t snd_seq_timer_get_cur_tick(struct snd_seq_timer *tmr) +{ + snd_seq_tick_time_t cur_tick; + unsigned long flags; + + spin_lock_irqsave(&tmr->lock, flags); + cur_tick = tmr->tick.cur_tick; + spin_unlock_irqrestore(&tmr->lock, flags); + return cur_tick; +} + + +#ifdef CONFIG_SND_PROC_FS +/* exported to seq_info.c */ +void snd_seq_info_timer_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + int idx; + struct snd_seq_queue *q; + struct snd_seq_timer *tmr; + struct snd_timer_instance *ti; + unsigned long resolution; + + for (idx = 0; idx < SNDRV_SEQ_MAX_QUEUES; idx++) { + q = queueptr(idx); + if (q == NULL) + continue; + mutex_lock(&q->timer_mutex); + tmr = q->timer; + if (!tmr) + goto unlock; + ti = tmr->timeri; + if (!ti) + goto unlock; + snd_iprintf(buffer, "Timer for queue %i : %s\n", q->queue, ti->timer->name); + resolution = snd_timer_resolution(ti) * tmr->ticks; + snd_iprintf(buffer, " Period time : %lu.%09lu\n", resolution / 1000000000, resolution % 1000000000); + snd_iprintf(buffer, " Skew : %u / %u\n", tmr->skew, tmr->skew_base); +unlock: + mutex_unlock(&q->timer_mutex); + queuefree(q); + } +} +#endif /* CONFIG_SND_PROC_FS */ + diff --git a/sound/core/seq/seq_timer.h b/sound/core/seq/seq_timer.h new file mode 100644 index 000000000..4bec57df8 --- /dev/null +++ b/sound/core/seq/seq_timer.h @@ -0,0 +1,134 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * ALSA sequencer Timer + * Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl> + */ +#ifndef __SND_SEQ_TIMER_H +#define __SND_SEQ_TIMER_H + +#include <sound/timer.h> +#include <sound/seq_kernel.h> + +struct snd_seq_timer_tick { + snd_seq_tick_time_t cur_tick; /* current tick */ + unsigned long resolution; /* time per tick in nsec */ + unsigned long fraction; /* current time per tick in nsec */ +}; + +struct snd_seq_timer { + /* ... tempo / offset / running state */ + + unsigned int running:1, /* running state of queue */ + initialized:1; /* timer is initialized */ + + unsigned int tempo; /* current tempo, us/tick */ + int ppq; /* time resolution, ticks/quarter */ + + snd_seq_real_time_t cur_time; /* current time */ + struct snd_seq_timer_tick tick; /* current tick */ + int tick_updated; + + int type; /* timer type */ + struct snd_timer_id alsa_id; /* ALSA's timer ID */ + struct snd_timer_instance *timeri; /* timer instance */ + unsigned int ticks; + unsigned long preferred_resolution; /* timer resolution, ticks/sec */ + + unsigned int skew; + unsigned int skew_base; + + struct timespec64 last_update; /* time of last clock update, used for interpolation */ + + spinlock_t lock; +}; + + +/* create new timer (constructor) */ +struct snd_seq_timer *snd_seq_timer_new(void); + +/* delete timer (destructor) */ +void snd_seq_timer_delete(struct snd_seq_timer **tmr); + +/* */ +static inline void snd_seq_timer_update_tick(struct snd_seq_timer_tick *tick, + unsigned long resolution) +{ + if (tick->resolution > 0) { + tick->fraction += resolution; + tick->cur_tick += (unsigned int)(tick->fraction / tick->resolution); + tick->fraction %= tick->resolution; + } +} + + +/* compare timestamp between events */ +/* return 1 if a >= b; otherwise return 0 */ +static inline int snd_seq_compare_tick_time(snd_seq_tick_time_t *a, snd_seq_tick_time_t *b) +{ + /* compare ticks */ + return (*a >= *b); +} + +static inline int snd_seq_compare_real_time(snd_seq_real_time_t *a, snd_seq_real_time_t *b) +{ + /* compare real time */ + if (a->tv_sec > b->tv_sec) + return 1; + if ((a->tv_sec == b->tv_sec) && (a->tv_nsec >= b->tv_nsec)) + return 1; + return 0; +} + + +static inline void snd_seq_sanity_real_time(snd_seq_real_time_t *tm) +{ + while (tm->tv_nsec >= 1000000000) { + /* roll-over */ + tm->tv_nsec -= 1000000000; + tm->tv_sec++; + } +} + + +/* increment timestamp */ +static inline void snd_seq_inc_real_time(snd_seq_real_time_t *tm, snd_seq_real_time_t *inc) +{ + tm->tv_sec += inc->tv_sec; + tm->tv_nsec += inc->tv_nsec; + snd_seq_sanity_real_time(tm); +} + +static inline void snd_seq_inc_time_nsec(snd_seq_real_time_t *tm, unsigned long nsec) +{ + tm->tv_nsec += nsec; + snd_seq_sanity_real_time(tm); +} + +/* called by timer isr */ +struct snd_seq_queue; +int snd_seq_timer_open(struct snd_seq_queue *q); +int snd_seq_timer_close(struct snd_seq_queue *q); +int snd_seq_timer_midi_open(struct snd_seq_queue *q); +int snd_seq_timer_midi_close(struct snd_seq_queue *q); +void snd_seq_timer_defaults(struct snd_seq_timer *tmr); +void snd_seq_timer_reset(struct snd_seq_timer *tmr); +int snd_seq_timer_stop(struct snd_seq_timer *tmr); +int snd_seq_timer_start(struct snd_seq_timer *tmr); +int snd_seq_timer_continue(struct snd_seq_timer *tmr); +int snd_seq_timer_set_tempo(struct snd_seq_timer *tmr, int tempo); +int snd_seq_timer_set_tempo_ppq(struct snd_seq_timer *tmr, int tempo, int ppq); +int snd_seq_timer_set_position_tick(struct snd_seq_timer *tmr, snd_seq_tick_time_t position); +int snd_seq_timer_set_position_time(struct snd_seq_timer *tmr, snd_seq_real_time_t position); +int snd_seq_timer_set_skew(struct snd_seq_timer *tmr, unsigned int skew, unsigned int base); +snd_seq_real_time_t snd_seq_timer_get_cur_time(struct snd_seq_timer *tmr, + bool adjust_ktime); +snd_seq_tick_time_t snd_seq_timer_get_cur_tick(struct snd_seq_timer *tmr); + +extern int seq_default_timer_class; +extern int seq_default_timer_sclass; +extern int seq_default_timer_card; +extern int seq_default_timer_device; +extern int seq_default_timer_subdevice; +extern int seq_default_timer_resolution; + +#endif diff --git a/sound/core/seq/seq_virmidi.c b/sound/core/seq/seq_virmidi.c new file mode 100644 index 000000000..f5cae4950 --- /dev/null +++ b/sound/core/seq/seq_virmidi.c @@ -0,0 +1,527 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Virtual Raw MIDI client on Sequencer + * + * Copyright (c) 2000 by Takashi Iwai <tiwai@suse.de>, + * Jaroslav Kysela <perex@perex.cz> + */ + +/* + * Virtual Raw MIDI client + * + * The virtual rawmidi client is a sequencer client which associate + * a rawmidi device file. The created rawmidi device file can be + * accessed as a normal raw midi, but its MIDI source and destination + * are arbitrary. For example, a user-client software synth connected + * to this port can be used as a normal midi device as well. + * + * The virtual rawmidi device accepts also multiple opens. Each file + * has its own input buffer, so that no conflict would occur. The drain + * of input/output buffer acts only to the local buffer. + * + */ + +#include <linux/init.h> +#include <linux/wait.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/rawmidi.h> +#include <sound/info.h> +#include <sound/control.h> +#include <sound/minors.h> +#include <sound/seq_kernel.h> +#include <sound/seq_midi_event.h> +#include <sound/seq_virmidi.h> + +MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>"); +MODULE_DESCRIPTION("Virtual Raw MIDI client on Sequencer"); +MODULE_LICENSE("GPL"); + +/* + * initialize an event record + */ +static void snd_virmidi_init_event(struct snd_virmidi *vmidi, + struct snd_seq_event *ev) +{ + memset(ev, 0, sizeof(*ev)); + ev->source.port = vmidi->port; + switch (vmidi->seq_mode) { + case SNDRV_VIRMIDI_SEQ_DISPATCH: + ev->dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS; + break; + case SNDRV_VIRMIDI_SEQ_ATTACH: + /* FIXME: source and destination are same - not good.. */ + ev->dest.client = vmidi->client; + ev->dest.port = vmidi->port; + break; + } + ev->type = SNDRV_SEQ_EVENT_NONE; +} + +/* + * decode input event and put to read buffer of each opened file + */ +static int snd_virmidi_dev_receive_event(struct snd_virmidi_dev *rdev, + struct snd_seq_event *ev, + bool atomic) +{ + struct snd_virmidi *vmidi; + unsigned char msg[4]; + int len; + + if (atomic) + read_lock(&rdev->filelist_lock); + else + down_read(&rdev->filelist_sem); + list_for_each_entry(vmidi, &rdev->filelist, list) { + if (!READ_ONCE(vmidi->trigger)) + continue; + if (ev->type == SNDRV_SEQ_EVENT_SYSEX) { + if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARIABLE) + continue; + snd_seq_dump_var_event(ev, (snd_seq_dump_func_t)snd_rawmidi_receive, vmidi->substream); + snd_midi_event_reset_decode(vmidi->parser); + } else { + len = snd_midi_event_decode(vmidi->parser, msg, sizeof(msg), ev); + if (len > 0) + snd_rawmidi_receive(vmidi->substream, msg, len); + } + } + if (atomic) + read_unlock(&rdev->filelist_lock); + else + up_read(&rdev->filelist_sem); + + return 0; +} + +/* + * event handler of virmidi port + */ +static int snd_virmidi_event_input(struct snd_seq_event *ev, int direct, + void *private_data, int atomic, int hop) +{ + struct snd_virmidi_dev *rdev; + + rdev = private_data; + if (!(rdev->flags & SNDRV_VIRMIDI_USE)) + return 0; /* ignored */ + return snd_virmidi_dev_receive_event(rdev, ev, atomic); +} + +/* + * trigger rawmidi stream for input + */ +static void snd_virmidi_input_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct snd_virmidi *vmidi = substream->runtime->private_data; + + WRITE_ONCE(vmidi->trigger, !!up); +} + +/* process rawmidi bytes and send events; + * we need no lock here for vmidi->event since it's handled only in this work + */ +static void snd_vmidi_output_work(struct work_struct *work) +{ + struct snd_virmidi *vmidi; + struct snd_rawmidi_substream *substream; + unsigned char input; + int ret; + + vmidi = container_of(work, struct snd_virmidi, output_work); + substream = vmidi->substream; + + /* discard the outputs in dispatch mode unless subscribed */ + if (vmidi->seq_mode == SNDRV_VIRMIDI_SEQ_DISPATCH && + !(vmidi->rdev->flags & SNDRV_VIRMIDI_SUBSCRIBE)) { + snd_rawmidi_proceed(substream); + return; + } + + while (READ_ONCE(vmidi->trigger)) { + if (snd_rawmidi_transmit(substream, &input, 1) != 1) + break; + if (!snd_midi_event_encode_byte(vmidi->parser, input, + &vmidi->event)) + continue; + if (vmidi->event.type != SNDRV_SEQ_EVENT_NONE) { + ret = snd_seq_kernel_client_dispatch(vmidi->client, + &vmidi->event, + false, 0); + vmidi->event.type = SNDRV_SEQ_EVENT_NONE; + if (ret < 0) + break; + } + /* rawmidi input might be huge, allow to have a break */ + cond_resched(); + } +} + +/* + * trigger rawmidi stream for output + */ +static void snd_virmidi_output_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct snd_virmidi *vmidi = substream->runtime->private_data; + + WRITE_ONCE(vmidi->trigger, !!up); + if (up) + queue_work(system_highpri_wq, &vmidi->output_work); +} + +/* + * open rawmidi handle for input + */ +static int snd_virmidi_input_open(struct snd_rawmidi_substream *substream) +{ + struct snd_virmidi_dev *rdev = substream->rmidi->private_data; + struct snd_rawmidi_runtime *runtime = substream->runtime; + struct snd_virmidi *vmidi; + + vmidi = kzalloc(sizeof(*vmidi), GFP_KERNEL); + if (vmidi == NULL) + return -ENOMEM; + vmidi->substream = substream; + if (snd_midi_event_new(0, &vmidi->parser) < 0) { + kfree(vmidi); + return -ENOMEM; + } + vmidi->seq_mode = rdev->seq_mode; + vmidi->client = rdev->client; + vmidi->port = rdev->port; + runtime->private_data = vmidi; + down_write(&rdev->filelist_sem); + write_lock_irq(&rdev->filelist_lock); + list_add_tail(&vmidi->list, &rdev->filelist); + write_unlock_irq(&rdev->filelist_lock); + up_write(&rdev->filelist_sem); + vmidi->rdev = rdev; + return 0; +} + +/* + * open rawmidi handle for output + */ +static int snd_virmidi_output_open(struct snd_rawmidi_substream *substream) +{ + struct snd_virmidi_dev *rdev = substream->rmidi->private_data; + struct snd_rawmidi_runtime *runtime = substream->runtime; + struct snd_virmidi *vmidi; + + vmidi = kzalloc(sizeof(*vmidi), GFP_KERNEL); + if (vmidi == NULL) + return -ENOMEM; + vmidi->substream = substream; + if (snd_midi_event_new(MAX_MIDI_EVENT_BUF, &vmidi->parser) < 0) { + kfree(vmidi); + return -ENOMEM; + } + vmidi->seq_mode = rdev->seq_mode; + vmidi->client = rdev->client; + vmidi->port = rdev->port; + snd_virmidi_init_event(vmidi, &vmidi->event); + vmidi->rdev = rdev; + INIT_WORK(&vmidi->output_work, snd_vmidi_output_work); + runtime->private_data = vmidi; + return 0; +} + +/* + * close rawmidi handle for input + */ +static int snd_virmidi_input_close(struct snd_rawmidi_substream *substream) +{ + struct snd_virmidi_dev *rdev = substream->rmidi->private_data; + struct snd_virmidi *vmidi = substream->runtime->private_data; + + down_write(&rdev->filelist_sem); + write_lock_irq(&rdev->filelist_lock); + list_del(&vmidi->list); + write_unlock_irq(&rdev->filelist_lock); + up_write(&rdev->filelist_sem); + snd_midi_event_free(vmidi->parser); + substream->runtime->private_data = NULL; + kfree(vmidi); + return 0; +} + +/* + * close rawmidi handle for output + */ +static int snd_virmidi_output_close(struct snd_rawmidi_substream *substream) +{ + struct snd_virmidi *vmidi = substream->runtime->private_data; + + WRITE_ONCE(vmidi->trigger, false); /* to be sure */ + cancel_work_sync(&vmidi->output_work); + snd_midi_event_free(vmidi->parser); + substream->runtime->private_data = NULL; + kfree(vmidi); + return 0; +} + +/* + * drain output work queue + */ +static void snd_virmidi_output_drain(struct snd_rawmidi_substream *substream) +{ + struct snd_virmidi *vmidi = substream->runtime->private_data; + + flush_work(&vmidi->output_work); +} + +/* + * subscribe callback - allow output to rawmidi device + */ +static int snd_virmidi_subscribe(void *private_data, + struct snd_seq_port_subscribe *info) +{ + struct snd_virmidi_dev *rdev; + + rdev = private_data; + if (!try_module_get(rdev->card->module)) + return -EFAULT; + rdev->flags |= SNDRV_VIRMIDI_SUBSCRIBE; + return 0; +} + +/* + * unsubscribe callback - disallow output to rawmidi device + */ +static int snd_virmidi_unsubscribe(void *private_data, + struct snd_seq_port_subscribe *info) +{ + struct snd_virmidi_dev *rdev; + + rdev = private_data; + rdev->flags &= ~SNDRV_VIRMIDI_SUBSCRIBE; + module_put(rdev->card->module); + return 0; +} + + +/* + * use callback - allow input to rawmidi device + */ +static int snd_virmidi_use(void *private_data, + struct snd_seq_port_subscribe *info) +{ + struct snd_virmidi_dev *rdev; + + rdev = private_data; + if (!try_module_get(rdev->card->module)) + return -EFAULT; + rdev->flags |= SNDRV_VIRMIDI_USE; + return 0; +} + +/* + * unuse callback - disallow input to rawmidi device + */ +static int snd_virmidi_unuse(void *private_data, + struct snd_seq_port_subscribe *info) +{ + struct snd_virmidi_dev *rdev; + + rdev = private_data; + rdev->flags &= ~SNDRV_VIRMIDI_USE; + module_put(rdev->card->module); + return 0; +} + + +/* + * Register functions + */ + +static const struct snd_rawmidi_ops snd_virmidi_input_ops = { + .open = snd_virmidi_input_open, + .close = snd_virmidi_input_close, + .trigger = snd_virmidi_input_trigger, +}; + +static const struct snd_rawmidi_ops snd_virmidi_output_ops = { + .open = snd_virmidi_output_open, + .close = snd_virmidi_output_close, + .trigger = snd_virmidi_output_trigger, + .drain = snd_virmidi_output_drain, +}; + +/* + * create a sequencer client and a port + */ +static int snd_virmidi_dev_attach_seq(struct snd_virmidi_dev *rdev) +{ + int client; + struct snd_seq_port_callback pcallbacks; + struct snd_seq_port_info *pinfo; + int err; + + if (rdev->client >= 0) + return 0; + + pinfo = kzalloc(sizeof(*pinfo), GFP_KERNEL); + if (!pinfo) { + err = -ENOMEM; + goto __error; + } + + client = snd_seq_create_kernel_client(rdev->card, rdev->device, + "%s %d-%d", rdev->rmidi->name, + rdev->card->number, + rdev->device); + if (client < 0) { + err = client; + goto __error; + } + rdev->client = client; + + /* create a port */ + pinfo->addr.client = client; + sprintf(pinfo->name, "VirMIDI %d-%d", rdev->card->number, rdev->device); + /* set all capabilities */ + pinfo->capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SYNC_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE; + pinfo->capability |= SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SYNC_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ; + pinfo->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX; + pinfo->type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC + | SNDRV_SEQ_PORT_TYPE_SOFTWARE + | SNDRV_SEQ_PORT_TYPE_PORT; + pinfo->midi_channels = 16; + memset(&pcallbacks, 0, sizeof(pcallbacks)); + pcallbacks.owner = THIS_MODULE; + pcallbacks.private_data = rdev; + pcallbacks.subscribe = snd_virmidi_subscribe; + pcallbacks.unsubscribe = snd_virmidi_unsubscribe; + pcallbacks.use = snd_virmidi_use; + pcallbacks.unuse = snd_virmidi_unuse; + pcallbacks.event_input = snd_virmidi_event_input; + pinfo->kernel = &pcallbacks; + err = snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_CREATE_PORT, pinfo); + if (err < 0) { + snd_seq_delete_kernel_client(client); + rdev->client = -1; + goto __error; + } + + rdev->port = pinfo->addr.port; + err = 0; /* success */ + + __error: + kfree(pinfo); + return err; +} + + +/* + * release the sequencer client + */ +static void snd_virmidi_dev_detach_seq(struct snd_virmidi_dev *rdev) +{ + if (rdev->client >= 0) { + snd_seq_delete_kernel_client(rdev->client); + rdev->client = -1; + } +} + +/* + * register the device + */ +static int snd_virmidi_dev_register(struct snd_rawmidi *rmidi) +{ + struct snd_virmidi_dev *rdev = rmidi->private_data; + int err; + + switch (rdev->seq_mode) { + case SNDRV_VIRMIDI_SEQ_DISPATCH: + err = snd_virmidi_dev_attach_seq(rdev); + if (err < 0) + return err; + break; + case SNDRV_VIRMIDI_SEQ_ATTACH: + if (rdev->client == 0) + return -EINVAL; + /* should check presence of port more strictly.. */ + break; + default: + pr_err("ALSA: seq_virmidi: seq_mode is not set: %d\n", rdev->seq_mode); + return -EINVAL; + } + return 0; +} + + +/* + * unregister the device + */ +static int snd_virmidi_dev_unregister(struct snd_rawmidi *rmidi) +{ + struct snd_virmidi_dev *rdev = rmidi->private_data; + + if (rdev->seq_mode == SNDRV_VIRMIDI_SEQ_DISPATCH) + snd_virmidi_dev_detach_seq(rdev); + return 0; +} + +/* + * + */ +static const struct snd_rawmidi_global_ops snd_virmidi_global_ops = { + .dev_register = snd_virmidi_dev_register, + .dev_unregister = snd_virmidi_dev_unregister, +}; + +/* + * free device + */ +static void snd_virmidi_free(struct snd_rawmidi *rmidi) +{ + struct snd_virmidi_dev *rdev = rmidi->private_data; + kfree(rdev); +} + +/* + * create a new device + * + */ +/* exported */ +int snd_virmidi_new(struct snd_card *card, int device, struct snd_rawmidi **rrmidi) +{ + struct snd_rawmidi *rmidi; + struct snd_virmidi_dev *rdev; + int err; + + *rrmidi = NULL; + err = snd_rawmidi_new(card, "VirMidi", device, + 16, /* may be configurable */ + 16, /* may be configurable */ + &rmidi); + if (err < 0) + return err; + strcpy(rmidi->name, rmidi->id); + rdev = kzalloc(sizeof(*rdev), GFP_KERNEL); + if (rdev == NULL) { + snd_device_free(card, rmidi); + return -ENOMEM; + } + rdev->card = card; + rdev->rmidi = rmidi; + rdev->device = device; + rdev->client = -1; + init_rwsem(&rdev->filelist_sem); + rwlock_init(&rdev->filelist_lock); + INIT_LIST_HEAD(&rdev->filelist); + rdev->seq_mode = SNDRV_VIRMIDI_SEQ_DISPATCH; + rmidi->private_data = rdev; + rmidi->private_free = snd_virmidi_free; + rmidi->ops = &snd_virmidi_global_ops; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_virmidi_input_ops); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_virmidi_output_ops); + rmidi->info_flags = SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + *rrmidi = rmidi; + return 0; +} +EXPORT_SYMBOL(snd_virmidi_new); |