diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
commit | 2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch) | |
tree | 848558de17fb3008cdf4d861b01ac7781903ce39 /sound/core/seq/oss | |
parent | Initial commit. (diff) | |
download | linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.tar.xz linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.zip |
Adding upstream version 6.1.76.upstream/6.1.76
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sound/core/seq/oss')
-rw-r--r-- | sound/core/seq/oss/Makefile | 11 | ||||
-rw-r--r-- | sound/core/seq/oss/seq_oss.c | 311 | ||||
-rw-r--r-- | sound/core/seq/oss/seq_oss_device.h | 168 | ||||
-rw-r--r-- | sound/core/seq/oss/seq_oss_event.c | 447 | ||||
-rw-r--r-- | sound/core/seq/oss/seq_oss_event.h | 99 | ||||
-rw-r--r-- | sound/core/seq/oss/seq_oss_init.c | 505 | ||||
-rw-r--r-- | sound/core/seq/oss/seq_oss_ioctl.c | 178 | ||||
-rw-r--r-- | sound/core/seq/oss/seq_oss_midi.c | 718 | ||||
-rw-r--r-- | sound/core/seq/oss/seq_oss_midi.h | 35 | ||||
-rw-r--r-- | sound/core/seq/oss/seq_oss_readq.c | 250 | ||||
-rw-r--r-- | sound/core/seq/oss/seq_oss_readq.h | 45 | ||||
-rw-r--r-- | sound/core/seq/oss/seq_oss_rw.c | 201 | ||||
-rw-r--r-- | sound/core/seq/oss/seq_oss_synth.c | 666 | ||||
-rw-r--r-- | sound/core/seq/oss/seq_oss_synth.h | 39 | ||||
-rw-r--r-- | sound/core/seq/oss/seq_oss_timer.c | 264 | ||||
-rw-r--r-- | sound/core/seq/oss/seq_oss_timer.h | 47 | ||||
-rw-r--r-- | sound/core/seq/oss/seq_oss_writeq.c | 160 | ||||
-rw-r--r-- | sound/core/seq/oss/seq_oss_writeq.h | 37 |
18 files changed, 4181 insertions, 0 deletions
diff --git a/sound/core/seq/oss/Makefile b/sound/core/seq/oss/Makefile new file mode 100644 index 000000000..f1a608785 --- /dev/null +++ b/sound/core/seq/oss/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for ALSA +# Copyright (c) 1999 by Jaroslav Kysela <perex@perex.cz> +# + +snd-seq-oss-objs := seq_oss.o seq_oss_init.o seq_oss_timer.o seq_oss_ioctl.o \ + seq_oss_event.o seq_oss_rw.o seq_oss_synth.o \ + seq_oss_midi.o seq_oss_readq.o seq_oss_writeq.o + +obj-$(CONFIG_SND_SEQUENCER_OSS) += snd-seq-oss.o diff --git a/sound/core/seq/oss/seq_oss.c b/sound/core/seq/oss/seq_oss.c new file mode 100644 index 000000000..77c1214ac --- /dev/null +++ b/sound/core/seq/oss/seq_oss.c @@ -0,0 +1,311 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSS compatible sequencer driver + * + * registration of device and proc + * + * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de> + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/compat.h> +#include <sound/core.h> +#include <sound/minors.h> +#include <sound/initval.h> +#include "seq_oss_device.h" +#include "seq_oss_synth.h" + +/* + * module option + */ +MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>"); +MODULE_DESCRIPTION("OSS-compatible sequencer module"); +MODULE_LICENSE("GPL"); +/* Takashi says this is really only for sound-service-0-, but this is OK. */ +MODULE_ALIAS_SNDRV_MINOR(SNDRV_MINOR_OSS_SEQUENCER); +MODULE_ALIAS_SNDRV_MINOR(SNDRV_MINOR_OSS_MUSIC); + + +/* + * prototypes + */ +static int register_device(void); +static void unregister_device(void); +#ifdef CONFIG_SND_PROC_FS +static int register_proc(void); +static void unregister_proc(void); +#else +static inline int register_proc(void) { return 0; } +static inline void unregister_proc(void) {} +#endif + +static int odev_open(struct inode *inode, struct file *file); +static int odev_release(struct inode *inode, struct file *file); +static ssize_t odev_read(struct file *file, char __user *buf, size_t count, loff_t *offset); +static ssize_t odev_write(struct file *file, const char __user *buf, size_t count, loff_t *offset); +static long odev_ioctl(struct file *file, unsigned int cmd, unsigned long arg); +static __poll_t odev_poll(struct file *file, poll_table * wait); + + +/* + * module interface + */ + +static struct snd_seq_driver seq_oss_synth_driver = { + .driver = { + .name = KBUILD_MODNAME, + .probe = snd_seq_oss_synth_probe, + .remove = snd_seq_oss_synth_remove, + }, + .id = SNDRV_SEQ_DEV_ID_OSS, + .argsize = sizeof(struct snd_seq_oss_reg), +}; + +static int __init alsa_seq_oss_init(void) +{ + int rc; + + rc = register_device(); + if (rc < 0) + goto error; + rc = register_proc(); + if (rc < 0) { + unregister_device(); + goto error; + } + rc = snd_seq_oss_create_client(); + if (rc < 0) { + unregister_proc(); + unregister_device(); + goto error; + } + + rc = snd_seq_driver_register(&seq_oss_synth_driver); + if (rc < 0) { + snd_seq_oss_delete_client(); + unregister_proc(); + unregister_device(); + goto error; + } + + /* success */ + snd_seq_oss_synth_init(); + + error: + return rc; +} + +static void __exit alsa_seq_oss_exit(void) +{ + snd_seq_driver_unregister(&seq_oss_synth_driver); + snd_seq_oss_delete_client(); + unregister_proc(); + unregister_device(); +} + +module_init(alsa_seq_oss_init) +module_exit(alsa_seq_oss_exit) + +/* + * ALSA minor device interface + */ + +static DEFINE_MUTEX(register_mutex); + +static int +odev_open(struct inode *inode, struct file *file) +{ + int level, rc; + + if (iminor(inode) == SNDRV_MINOR_OSS_MUSIC) + level = SNDRV_SEQ_OSS_MODE_MUSIC; + else + level = SNDRV_SEQ_OSS_MODE_SYNTH; + + mutex_lock(®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 |