Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+76
View File
@@ -0,0 +1,76 @@
# SPDX-License-Identifier: GPL-2.0-only
config SND_SEQUENCER
tristate "Sequencer support"
select SND_TIMER
select SND_SEQ_DEVICE
help
Say Y or M to enable MIDI sequencer and router support. This
feature allows routing and enqueueing of MIDI events. Events
can be processed at a given time.
Many programs require this feature, so you should enable it
unless you know what you're doing.
if SND_SEQUENCER
config SND_SEQ_DUMMY
tristate "Sequencer dummy client"
help
Say Y here to enable the dummy sequencer client. This client
is a simple MIDI-through client: all normal input events are
redirected to the output port immediately.
You don't need this unless you want to connect many MIDI
devices or applications together.
To compile this driver as a module, choose M here: the module
will be called snd-seq-dummy.
config SND_SEQUENCER_OSS
tristate "OSS Sequencer API"
depends on SND_OSSEMUL
select SND_SEQ_MIDI_EVENT
help
Say Y here to enable OSS sequencer emulation (both
/dev/sequencer and /dev/music interfaces).
Many programs still use the OSS API, so say Y.
To compile this driver as a module, choose M here: the module
will be called snd-seq-oss.
config SND_SEQ_HRTIMER_DEFAULT
bool "Use HR-timer as default sequencer timer"
depends on SND_HRTIMER
default y
help
Say Y here to use the HR-timer backend as the default sequencer
timer.
config SND_SEQ_MIDI_EVENT
tristate
config SND_SEQ_MIDI
def_tristate SND_RAWMIDI
select SND_SEQ_MIDI_EVENT
config SND_SEQ_MIDI_EMUL
tristate
config SND_SEQ_VIRMIDI
tristate
config SND_SEQ_UMP
bool "Support for UMP events"
default SND_UMP
help
Say Y here to enable the support for handling UMP (Universal MIDI
Packet) events via ALSA sequencer infrastructure, which is an
essential feature for enabling MIDI 2.0 support.
It includes the automatic conversion of ALSA sequencer events
among legacy and UMP clients.
config SND_SEQ_UMP_CLIENT
def_tristate SND_UMP && SND_SEQ_UMP
endif # SND_SEQUENCER
+27
View File
@@ -0,0 +1,27 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for ALSA
# Copyright (c) 1999 by Jaroslav Kysela <perex@perex.cz>
#
snd-seq-y := seq.o seq_lock.o seq_clientmgr.o seq_memory.o seq_queue.o \
seq_fifo.o seq_prioq.o seq_timer.o \
seq_system.o seq_ports.o
snd-seq-$(CONFIG_SND_PROC_FS) += seq_info.o
snd-seq-$(CONFIG_SND_SEQ_UMP) += seq_ump_convert.o
snd-seq-midi-y := seq_midi.o
snd-seq-midi-emul-y := seq_midi_emul.o
snd-seq-midi-event-y := seq_midi_event.o
snd-seq-dummy-y := seq_dummy.o
snd-seq-virmidi-y := seq_virmidi.o
snd-seq-ump-client-y := seq_ump_client.o
obj-$(CONFIG_SND_SEQUENCER) += snd-seq.o
obj-$(CONFIG_SND_SEQUENCER_OSS) += oss/
obj-$(CONFIG_SND_SEQ_DUMMY) += snd-seq-dummy.o
obj-$(CONFIG_SND_SEQ_MIDI) += snd-seq-midi.o
obj-$(CONFIG_SND_SEQ_UMP_CLIENT) += snd-seq-ump-client.o
obj-$(CONFIG_SND_SEQ_MIDI_EMUL) += snd-seq-midi-emul.o
obj-$(CONFIG_SND_SEQ_MIDI_EVENT) += snd-seq-midi-event.o
obj-$(CONFIG_SND_SEQ_VIRMIDI) += snd-seq-virmidi.o
+11
View File
@@ -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-y := 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
+301
View File
@@ -0,0 +1,301 @@
// 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 = {
.probe = snd_seq_oss_synth_probe,
.remove = snd_seq_oss_synth_remove,
.driver = {
.name = KBUILD_MODNAME,
},
.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;
if (iminor(inode) == SNDRV_MINOR_OSS_MUSIC)
level = SNDRV_SEQ_OSS_MODE_MUSIC;
else
level = SNDRV_SEQ_OSS_MODE_SYNTH;
guard(mutex)(&register_mutex);
return snd_seq_oss_open(file, level);
}
static int
odev_release(struct inode *inode, struct file *file)
{
struct seq_oss_devinfo *dp;
dp = file->private_data;
if (!dp)
return 0;
guard(mutex)(&register_mutex);
snd_seq_oss_release(dp);
return 0;
}
static ssize_t
odev_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
{
struct seq_oss_devinfo *dp;
dp = file->private_data;
if (snd_BUG_ON(!dp))
return -ENXIO;
return snd_seq_oss_read(dp, buf, count);
}
static ssize_t
odev_write(struct file *file, const char __user *buf, size_t count, loff_t *offset)
{
struct seq_oss_devinfo *dp;
dp = file->private_data;
if (snd_BUG_ON(!dp))
return -ENXIO;
return snd_seq_oss_write(dp, buf, count, file);
}
static long
odev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct seq_oss_devinfo *dp;
long rc;
dp = file->private_data;
if (snd_BUG_ON(!dp))
return -ENXIO;
if (cmd != SNDCTL_SEQ_SYNC &&
mutex_lock_interruptible(&register_mutex))
return -ERESTARTSYS;
rc = snd_seq_oss_ioctl(dp, cmd, arg);
if (cmd != SNDCTL_SEQ_SYNC)
mutex_unlock(&register_mutex);
return rc;
}
#ifdef CONFIG_COMPAT
static long odev_ioctl_compat(struct file *file, unsigned int cmd,
unsigned long arg)
{
return odev_ioctl(file, cmd, (unsigned long)compat_ptr(arg));
}
#else
#define odev_ioctl_compat NULL
#endif
static __poll_t
odev_poll(struct file *file, poll_table * wait)
{
struct seq_oss_devinfo *dp;
dp = file->private_data;
if (snd_BUG_ON(!dp))
return EPOLLERR;
return snd_seq_oss_poll(dp, file, wait);
}
/*
* registration of sequencer minor device
*/
static const struct file_operations seq_oss_f_ops =
{
.owner = THIS_MODULE,
.read = odev_read,
.write = odev_write,
.open = odev_open,
.release = odev_release,
.poll = odev_poll,
.unlocked_ioctl = odev_ioctl,
.compat_ioctl = odev_ioctl_compat,
.llseek = noop_llseek,
};
static int __init
register_device(void)
{
int rc;
guard(mutex)(&register_mutex);
rc = snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_SEQUENCER,
NULL, 0,
&seq_oss_f_ops, NULL);
if (rc < 0) {
pr_err("ALSA: seq_oss: can't register device seq\n");
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);
return rc;
}
return 0;
}
static void
unregister_device(void)
{
guard(mutex)(&register_mutex);
if (snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_MUSIC, NULL, 0) < 0)
pr_err("ALSA: seq_oss: error unregister device music\n");
if (snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_SEQUENCER, NULL, 0) < 0)
pr_err("ALSA: seq_oss: error unregister device seq\n");
}
/*
* /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)
{
guard(mutex)(&register_mutex);
snd_iprintf(buf, "OSS sequencer emulation version %s\n", SNDRV_SEQ_OSS_VERSION_STR);
snd_seq_oss_system_info_read(buf);
snd_seq_oss_synth_info_read(buf);
snd_seq_oss_midi_info_read(buf);
}
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 */
@@ -0,0 +1,154 @@
/* 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;
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);
/* 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)
{
return snd_seq_kernel_client_ioctl(dp->cseq, type, arg);
}
/* 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;
}
#endif /* __SEQ_OSS_DEVICE_H */
@@ -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, snd_use_lock_t **lockp)
{
*lockp = NULL;
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, lockp);
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 (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;
}
@@ -0,0 +1,101 @@
/* 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, snd_use_lock_t **lockp);
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);
DEFINE_FREE(seq_oss_use_lock, snd_use_lock_t *, if (_T) snd_use_lock_free(_T))
#endif /* __SEQ_OSS_EVENT_H */
@@ -0,0 +1,494 @@
// 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 __ro_after_init = -1; /* ALSA sequencer client number */
static int system_port __ro_after_init = -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_callback port_callback;
struct snd_seq_port_info *port __free(kfree) =
kzalloc_obj(*port);
if (!port)
return -ENOMEM;
/* create ALSA client */
rc = snd_seq_create_kernel_client(NULL, SNDRV_SEQ_CLIENT_OSS,
"OSS sequencer");
if (rc < 0)
return rc;
system_client = rc;
/* create announcement receiver port */
strscpy(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);
}
/* look up midi devices */
schedule_work(&async_lookup_work);
return 0;
}
/*
* receive announcement 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_obj(*dp);
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;
strscpy(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
*/
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 */
@@ -0,0 +1,179 @@
// 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;
snd_use_lock_t *lock __free(seq_oss_use_lock) = NULL;
if (!snd_seq_oss_process_event(dp, (union evrec *)ev, &tmpev, &lock))
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;
}
@@ -0,0 +1,672 @@
// 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;
};
DEFINE_FREE(seq_oss_midi, struct seq_oss_midi *, if (!IS_ERR_OR_NULL(_T)) snd_use_lock_free(&(_T)->use_lock))
/*
* 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 __free(kfree) =
kzalloc_obj(*clinfo);
struct snd_seq_port_info *pinfo __free(kfree) =
kzalloc_obj(*pinfo);
if (!clinfo || !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);
}
return 0;
}
/*
*/
static struct seq_oss_midi *
get_mdev(int dev)
{
struct seq_oss_midi *mdev;
guard(spinlock_irqsave)(&register_lock);
mdev = midi_devs[dev];
if (mdev)
snd_use_lock_use(&mdev->use_lock);
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;
guard(spinlock_irqsave)(&register_lock);
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);
return mdev;
}
}
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;
/* 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_obj(*mdev);
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
*/
guard(spinlock_irqsave)(&register_lock);
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) {
snd_midi_event_free(mdev->coder);
kfree(mdev);
return -ENOMEM;
}
max_midi_devs++;
}
mdev->seq_device = i;
midi_devs[mdev->seq_device] = mdev;
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;
int index;
mdev = find_slot(client, port);
if (mdev) {
scoped_guard(spinlock_irqsave, &register_lock) {
midi_devs[mdev->seq_device] = NULL;
}
snd_use_lock_free(&mdev->use_lock);
snd_use_lock_sync(&mdev->use_lock);
snd_midi_event_free(mdev->coder);
kfree(mdev);
}
guard(spinlock_irqsave)(&register_lock);
for (index = max_midi_devs - 1; index >= 0; index--) {
if (midi_devs[index])
break;
}
max_midi_devs = index + 1;
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;
guard(spinlock_irqsave)(&register_lock);
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;
}
/*
* set up midi tables
*/
void
snd_seq_oss_midi_setup(struct seq_oss_devinfo *dp)
{
guard(spinlock_irq)(&register_lock);
dp->max_mididev = max_midi_devs;
}
/*
* 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 snd_seq_port_subscribe subs;
struct seq_oss_midi *mdev __free(seq_oss_midi) =
get_mididev(dp, dev);
if (!mdev)
return -ENODEV;
guard(mutex)(&mdev->open_mutex);
/* already used? */
if (mdev->opened && mdev->devinfo != dp)
return -EBUSY;
perm = 0;
if (is_write_mode(fmode))
perm |= PERM_WRITE;
if (is_read_mode(fmode))
perm |= PERM_READ;
perm &= mdev->flags;
if (perm == 0)
return -ENXIO;
/* already opened? */
if ((mdev->opened & perm) == perm)
return 0;
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)
return -ENXIO;
mdev->devinfo = dp;
return 0;
}
/*
* close the midi device if already opened
*/
int
snd_seq_oss_midi_close(struct seq_oss_devinfo *dp, int dev)
{
struct snd_seq_port_subscribe subs;
struct seq_oss_midi *mdev __free(seq_oss_midi) =
get_mididev(dp, dev);
if (!mdev)
return -ENODEV;
guard(mutex)(&mdev->open_mutex);
if (!mdev->opened || mdev->devinfo != dp)
return 0;
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;
return 0;
}
/*
* change seq capability flags to file mode flags
*/
int
snd_seq_oss_midi_filemode(struct seq_oss_devinfo *dp, int dev)
{
int mode;
struct seq_oss_midi *mdev __free(seq_oss_midi) =
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;
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 __free(seq_oss_midi) =
get_mididev(dp, dev);
if (!mdev)
return;
if (!mdev->opened)
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);
}
/*
* 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 __free(seq_oss_midi) =
get_mididev(dp, dev);
if (!mdev)
return;
addr->client = mdev->client;
addr->port = mdev->port;
}
/*
* 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;
if (dp->readq == NULL)
return 0;
struct seq_oss_midi *mdev __free(seq_oss_midi) =
find_slot(ev->source.client, ev->source.port);
if (!mdev)
return 0;
if (!(mdev->opened & PERM_READ))
return 0;
if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC)
return send_synth_event(dp, ev, mdev->seq_device);
else
return send_midi_event(dp, ev, mdev);
}
/*
* 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, snd_use_lock_t **lockp)
{
struct seq_oss_midi *mdev __free(seq_oss_midi) =
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);
/* the caller must release this later */
*lockp = &mdev->use_lock;
snd_use_lock_use(*lockp);
return 0;
}
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 __free(seq_oss_midi) =
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));
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;
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);
struct seq_oss_midi *mdev __free(seq_oss_midi) =
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));
}
}
#endif /* CONFIG_SND_PROC_FS */
@@ -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, snd_use_lock_t **lockp);
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
@@ -0,0 +1,262 @@
// 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_flex(*q, q, maxlen);
if (!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)
{
kfree(q);
}
/*
* reset the read queue
*/
void
snd_seq_oss_readq_clear(struct seq_oss_readq *q)
{
scoped_guard(spinlock_irqsave, &q->lock) {
if (q->qlen) {
q->qlen = 0;
q->head = 0;
q->tail = 0;
}
q->input_time = (unsigned long)-1;
}
/* if someone sleeping, wake'em up */
wake_up(&q->midi_sleep);
}
/*
* 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
* caller must hold lock
*/
static int snd_seq_oss_readq_put_event_locked(struct seq_oss_readq *q,
union evrec *ev)
{
if (q->qlen >= q->maxlen - 1)
return -ENOMEM;
memcpy(&q->q[q->tail], ev, sizeof(*ev));
q->tail = (q->tail + 1) % q->maxlen;
q->qlen++;
return 0;
}
/*
* 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)
{
int rc;
scoped_guard(spinlock_irqsave, &q->lock) {
rc = snd_seq_oss_readq_put_event_locked(q, ev);
if (!rc)
wake_up(&q->midi_sleep);
}
return rc;
}
/*
* 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)
{
int queued = 0;
scoped_guard(spinlock_irqsave, &q->lock) {
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;
queued = !snd_seq_oss_readq_put_event_locked(q, &rec);
break;
case SNDRV_SEQ_OSS_MODE_MUSIC:
rec.t.code = EV_TIMING;
rec.t.cmd = TMR_WAIT_ABS;
rec.t.time = curt;
queued = !snd_seq_oss_readq_put_event_locked(q, &rec);
break;
}
q->input_time = curt;
}
}
if (queued)
wake_up(&q->midi_sleep);
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 */
@@ -0,0 +1,46 @@
/* 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"
#include "seq_oss_event.h"
/*
* definition of read queue
*/
struct seq_oss_readq {
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;
union evrec q[] __counted_by(maxlen);
};
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
+204
View File
@@ -0,0 +1,204 @@
// 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 (count < ev_len) {
err = -EINVAL;
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;
err = snd_seq_oss_synth_load_patch(dp, rec.s.dev,
fmt, buf, 0, count);
return err < 0 ? err : 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);
snd_use_lock_t *lock __free(seq_oss_use_lock) = NULL;
if (snd_seq_oss_process_event(dp, rec, &event, &lock))
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;
}
@@ -0,0 +1,593 @@
// 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
*/
/* 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;
};
DEFINE_FREE(seq_oss_synth, struct seq_oss_synth *, if (!IS_ERR_OR_NULL(_T)) snd_use_lock_free(&(_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 snd_seq_device *dev)
{
int i;
struct seq_oss_synth *rec;
struct snd_seq_oss_reg *reg = SNDRV_SEQ_DEVICE_ARGPTR(dev);
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 */
scoped_guard(spinlock_irqsave, &register_lock) {
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) {
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;
}
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;
}
void
snd_seq_oss_synth_remove(struct snd_seq_device *dev)
{
int index;
struct seq_oss_synth *rec = dev->driver_data;
scoped_guard(spinlock_irqsave, &register_lock) {
for (index = 0; index < max_synth_devs; index++) {
if (synth_devs[index] == rec)
break;
}
if (index >= max_synth_devs) {
pr_err("ALSA: seq_oss: can't unregister synth\n");
return;
}
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;
}
}
#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);
}
/*
*/
static struct seq_oss_synth *
get_sdev(int dev)
{
struct seq_oss_synth *rec;
guard(spinlock_irqsave)(&register_lock);
rec = synth_devs[dev];
if (rec)
snd_use_lock_use(&rec->use_lock);
return rec;
}
/*
* set up synth tables
*/
void
snd_seq_oss_synth_setup(struct seq_oss_devinfo *dp)
{
int i;
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++) {
struct seq_oss_synth *rec __free(seq_oss_synth) = get_sdev(i);
if (rec == NULL)
continue;
if (rec->oper.open == NULL || rec->oper.close == NULL)
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))
continue;
if (rec->oper.open(&info->arg, rec->private_data) < 0) {
module_put(rec->oper.owner);
continue;
}
info->nr_voices = rec->nr_voices;
if (info->nr_voices > 0) {
info->ch = kzalloc_objs(struct seq_oss_chinfo,
info->nr_voices);
if (!info->ch) {
rec->oper.close(&info->arg);
module_put(rec->oper.owner);
continue;
}
reset_channels(info);
}
info->opened++;
rec->opened++;
dp->synth_opened++;
}
}
/*
* 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_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 {
struct seq_oss_synth *rec __free(seq_oss_synth) =
get_sdev(i);
if (rec == NULL)
continue;
if (rec->opened > 0) {
rec->oper.close(&info->arg);
module_put(rec->oper.owner);
rec->opened = 0;
}
}
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_synthinfo *info;
info = get_synthinfo_nospec(dp, dev);
if (!info || !info->opened)
return;
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->ch);
info->ch = NULL;
}
return;
}
struct seq_oss_synth *rec __free(seq_oss_synth) =
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);
}
}
/*
* 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_synthinfo *info;
info = get_synthinfo_nospec(dp, dev);
if (!info)
return -ENXIO;
if (info->is_midi)
return 0;
struct seq_oss_synth *rec __free(seq_oss_synth) =
get_synthdev(dp, dev);
if (!rec)
return -ENXIO;
if (rec->oper.load_patch == NULL)
return -ENXIO;
else
return rec->oper.load_patch(&info->arg, fmt, buf, p, c);
}
/*
* 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 __free(seq_oss_synth) =
get_synthdev(dp, dev);
if (rec)
return get_synthinfo_nospec(dp, dev);
return NULL;
}
/*
* receive OSS 6 byte sysex packet:
* the event is filled and prepared for sending immediately
* (i.e. sysex messages are fragmented)
*/
int
snd_seq_oss_synth_sysex(struct seq_oss_devinfo *dp, int dev, unsigned char *buf, struct snd_seq_event *ev)
{
unsigned char *p;
int len = 6;
p = memchr(buf, 0xff, 6);
if (p)
len = p - buf + 1;
/* copy the data to event record and send it */
if (snd_seq_oss_synth_addr(dp, dev, ev))
return -EINVAL;
ev->flags = SNDRV_SEQ_EVENT_LENGTH_VARIABLE;
ev->data.ext.len = len;
ev->data.ext.ptr = buf;
return 0;
}
/*
* 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_synthinfo *info;
info = get_synthinfo_nospec(dp, dev);
if (!info || info->is_midi)
return -ENXIO;
struct seq_oss_synth *rec __free(seq_oss_synth) =
get_synthdev(dp, dev);
if (!rec)
return -ENXIO;
if (rec->oper.ioctl == NULL)
return -ENXIO;
else
return rec->oper.ioctl(&info->arg, cmd, addr);
}
/*
* 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_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 {
struct seq_oss_synth *rec __free(seq_oss_synth) =
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));
}
return 0;
}
#ifdef CONFIG_SND_PROC_FS
/*
* proc interface
*/
void
snd_seq_oss_synth_info_read(struct snd_info_buffer *buf)
{
int i;
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);
struct seq_oss_synth *rec __free(seq_oss_synth) =
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",
str_enabled_disabled((long)rec->oper.ioctl),
str_enabled_disabled((long)rec->oper.load_patch));
}
}
#endif /* CONFIG_SND_PROC_FS */
@@ -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 snd_seq_device *dev);
void snd_seq_oss_synth_remove(struct snd_seq_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
@@ -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_obj(*rec);
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;
}
@@ -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
@@ -0,0 +1,157 @@
// 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_obj(*q);
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)
{
guard(spinlock_irqsave)(&q->sync_lock);
q->sync_time = time;
q->sync_event_put = 0;
wake_up(&q->sync_sleep);
}
/*
* 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);
}
@@ -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
+120
View File
@@ -0,0 +1,120 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* ALSA sequencer main module
* Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl>
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/seq_kernel.h>
#include "seq_clientmgr.h"
#include "seq_memory.h"
#include "seq_queue.h"
#include "seq_lock.h"
#include "seq_timer.h"
#include "seq_system.h"
#include "seq_info.h"
#include <sound/minors.h>
#include <sound/seq_device.h>
#if defined(CONFIG_SND_SEQ_DUMMY_MODULE)
int seq_client_load[15] = {[0] = SNDRV_SEQ_CLIENT_DUMMY, [1 ... 14] = -1};
#else
int seq_client_load[15] = {[0 ... 14] = -1};
#endif
int seq_default_timer_class = SNDRV_TIMER_CLASS_GLOBAL;
int seq_default_timer_sclass = SNDRV_TIMER_SCLASS_NONE;
int seq_default_timer_card = -1;
int seq_default_timer_device =
#ifdef CONFIG_SND_SEQ_HRTIMER_DEFAULT
SNDRV_TIMER_GLOBAL_HRTIMER
#else
SNDRV_TIMER_GLOBAL_SYSTEM
#endif
;
int seq_default_timer_subdevice = 0;
int seq_default_timer_resolution = 0; /* Hz */
MODULE_AUTHOR("Frank van de Pol <fvdpol@coil.demon.nl>, Jaroslav Kysela <perex@perex.cz>");
MODULE_DESCRIPTION("Advanced Linux Sound Architecture sequencer.");
MODULE_LICENSE("GPL");
module_param_array(seq_client_load, int, NULL, 0444);
MODULE_PARM_DESC(seq_client_load, "The numbers of global (system) clients to load through kmod.");
module_param(seq_default_timer_class, int, 0644);
MODULE_PARM_DESC(seq_default_timer_class, "The default timer class.");
module_param(seq_default_timer_sclass, int, 0644);
MODULE_PARM_DESC(seq_default_timer_sclass, "The default timer slave class.");
module_param(seq_default_timer_card, int, 0644);
MODULE_PARM_DESC(seq_default_timer_card, "The default timer card number.");
module_param(seq_default_timer_device, int, 0644);
MODULE_PARM_DESC(seq_default_timer_device, "The default timer device number.");
module_param(seq_default_timer_subdevice, int, 0644);
MODULE_PARM_DESC(seq_default_timer_subdevice, "The default timer subdevice number.");
module_param(seq_default_timer_resolution, int, 0644);
MODULE_PARM_DESC(seq_default_timer_resolution, "The default timer resolution in Hz.");
MODULE_ALIAS_CHARDEV(CONFIG_SND_MAJOR, SNDRV_MINOR_SEQUENCER);
MODULE_ALIAS("devname:snd/seq");
/*
* INIT PART
*/
static int __init alsa_seq_init(void)
{
int err;
err = client_init_data();
if (err < 0)
goto error;
/* register sequencer device */
err = snd_sequencer_device_init();
if (err < 0)
goto error;
/* register proc interface */
err = snd_seq_info_init();
if (err < 0)
goto error_device;
/* register our internal client */
err = snd_seq_system_client_init();
if (err < 0)
goto error_info;
snd_seq_autoload_init();
return 0;
error_info:
snd_seq_info_done();
error_device:
snd_sequencer_device_done();
error:
return err;
}
static void __exit alsa_seq_exit(void)
{
/* unregister our internal client */
snd_seq_system_client_done();
/* unregister proc interface */
snd_seq_info_done();
/* delete timing queues */
snd_seq_queues_delete();
/* unregister sequencer device */
snd_sequencer_device_done();
snd_seq_autoload_exit();
}
module_init(alsa_seq_init)
module_exit(alsa_seq_exit)
File diff suppressed because it is too large Load Diff
+130
View File
@@ -0,0 +1,130 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* ALSA sequencer Client Manager
* Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl>
*/
#ifndef __SND_SEQ_CLIENTMGR_H
#define __SND_SEQ_CLIENTMGR_H
#include <sound/seq_kernel.h>
#include <linux/bitops.h>
#include "seq_fifo.h"
#include "seq_ports.h"
#include "seq_lock.h"
/* client manager */
#define SND_SEQ_GROUP_FILTER_MASK GENMASK(SNDRV_UMP_MAX_GROUPS, 0)
#define SND_SEQ_GROUP_FILTER_GROUPS GENMASK(SNDRV_UMP_MAX_GROUPS, 1)
struct snd_seq_user_client {
struct file *file; /* file struct of client */
/* ... */
struct pid *owner;
/* fifo */
struct snd_seq_fifo *fifo; /* queue for incoming events */
int fifo_pool_size;
};
struct snd_seq_kernel_client {
/* ... */
struct snd_card *card;
};
struct snd_seq_client {
snd_seq_client_type_t type;
unsigned int accept_input: 1,
accept_output: 1;
unsigned int midi_version;
unsigned int user_pversion;
char name[64]; /* client name */
int number; /* client number */
unsigned int filter; /* filter flags */
DECLARE_BITMAP(event_filter, 256);
unsigned int group_filter;
snd_use_lock_t use_lock;
int event_lost;
/* ports */
int num_ports; /* number of ports */
struct list_head ports_list_head;
rwlock_t ports_lock;
struct mutex ports_mutex;
struct mutex ioctl_mutex;
int convert32; /* convert 32->64bit */
int ump_endpoint_port;
/* output pool */
struct snd_seq_pool *pool; /* memory pool for this client */
union {
struct snd_seq_user_client user;
struct snd_seq_kernel_client kernel;
} data;
/* for UMP */
void **ump_info;
};
/* usage statistics */
struct snd_seq_usage {
int cur;
int peak;
};
int client_init_data(void);
int snd_sequencer_device_init(void);
void snd_sequencer_device_done(void);
/* get locked pointer to client */
struct snd_seq_client *snd_seq_client_use_ptr(int clientid);
static inline struct snd_seq_client *
snd_seq_client_ref(struct snd_seq_client *client)
{
snd_use_lock_use(&client->use_lock);
return client;
}
/* unlock pointer to client */
static inline void snd_seq_client_unref(struct snd_seq_client *client)
{
snd_use_lock_free(&client->use_lock);
}
DEFINE_FREE(snd_seq_client, struct snd_seq_client *, if (!IS_ERR_OR_NULL(_T)) snd_seq_client_unref(_T))
/* dispatch event to client(s) */
int snd_seq_dispatch_event(struct snd_seq_event_cell *cell, int atomic, int hop);
int snd_seq_kernel_client_write_poll(int clientid, struct file *file, poll_table *wait);
int snd_seq_client_notify_subscription(int client, int port,
struct snd_seq_port_subscribe *info, int evtype);
int __snd_seq_deliver_single_event(struct snd_seq_client *dest,
struct snd_seq_client_port *dest_port,
struct snd_seq_event *event,
int atomic, int hop);
/* only for OSS sequencer */
int snd_seq_kernel_client_ioctl(int clientid, unsigned int cmd, void *arg);
extern int seq_client_load[15];
/* for internal use between kernel sequencer clients */
struct snd_seq_client *snd_seq_kernel_client_get(int client);
void snd_seq_kernel_client_put(struct snd_seq_client *cptr);
static inline bool snd_seq_client_is_ump(struct snd_seq_client *c)
{
return c->midi_version != SNDRV_SEQ_CLIENT_LEGACY_MIDI;
}
static inline bool snd_seq_client_is_midi2(struct snd_seq_client *c)
{
return c->midi_version == SNDRV_SEQ_CLIENT_UMP_MIDI_2_0;
}
#endif
+123
View File
@@ -0,0 +1,123 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* 32bit -> 64bit ioctl wrapper for sequencer API
* Copyright (c) by Takashi Iwai <tiwai@suse.de>
*/
/* This file included from seq.c */
#include <linux/compat.h>
#include <linux/slab.h>
struct snd_seq_port_info32 {
struct snd_seq_addr addr; /* client/port numbers */
char name[64]; /* port name */
u32 capability; /* port capability bits */
u32 type; /* port type bits */
s32 midi_channels; /* channels per MIDI port */
s32 midi_voices; /* voices per MIDI port */
s32 synth_voices; /* voices per SYNTH port */
s32 read_use; /* R/O: subscribers for output (from this port) */
s32 write_use; /* R/O: subscribers for input (to this port) */
u32 kernel; /* reserved for kernel use (must be NULL) */
u32 flags; /* misc. conditioning */
unsigned char time_queue; /* queue # for timestamping */
char reserved[59]; /* for future use */
};
static int snd_seq_call_port_info_ioctl(struct snd_seq_client *client, unsigned int cmd,
struct snd_seq_port_info32 __user *data32)
{
int err;
struct snd_seq_port_info *data __free(kfree) =
kmalloc_obj(*data);
if (!data)
return -ENOMEM;
if (copy_from_user(data, data32, sizeof(*data32)) ||
get_user(data->flags, &data32->flags) ||
get_user(data->time_queue, &data32->time_queue))
return -EFAULT;
data->kernel = NULL;
err = snd_seq_kernel_client_ctl(client->number, cmd, data);
if (err < 0)
return err;
if (copy_to_user(data32, data, sizeof(*data32)) ||
put_user(data->flags, &data32->flags) ||
put_user(data->time_queue, &data32->time_queue))
return -EFAULT;
return err;
}
/*
*/
enum {
SNDRV_SEQ_IOCTL_CREATE_PORT32 = _IOWR('S', 0x20, struct snd_seq_port_info32),
SNDRV_SEQ_IOCTL_DELETE_PORT32 = _IOW ('S', 0x21, struct snd_seq_port_info32),
SNDRV_SEQ_IOCTL_GET_PORT_INFO32 = _IOWR('S', 0x22, struct snd_seq_port_info32),
SNDRV_SEQ_IOCTL_SET_PORT_INFO32 = _IOW ('S', 0x23, struct snd_seq_port_info32),
SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT32 = _IOWR('S', 0x52, struct snd_seq_port_info32),
};
static long snd_seq_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg)
{
struct snd_seq_client *client = file->private_data;
void __user *argp = compat_ptr(arg);
if (snd_BUG_ON(!client))
return -ENXIO;
switch (cmd) {
case SNDRV_SEQ_IOCTL_PVERSION:
case SNDRV_SEQ_IOCTL_USER_PVERSION:
case SNDRV_SEQ_IOCTL_CLIENT_ID:
case SNDRV_SEQ_IOCTL_SYSTEM_INFO:
case SNDRV_SEQ_IOCTL_GET_CLIENT_INFO:
case SNDRV_SEQ_IOCTL_SET_CLIENT_INFO:
case SNDRV_SEQ_IOCTL_GET_CLIENT_UMP_INFO:
case SNDRV_SEQ_IOCTL_SET_CLIENT_UMP_INFO:
case SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT:
case SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT:
case SNDRV_SEQ_IOCTL_CREATE_QUEUE:
case SNDRV_SEQ_IOCTL_DELETE_QUEUE:
case SNDRV_SEQ_IOCTL_GET_QUEUE_INFO:
case SNDRV_SEQ_IOCTL_SET_QUEUE_INFO:
case SNDRV_SEQ_IOCTL_GET_NAMED_QUEUE:
case SNDRV_SEQ_IOCTL_GET_QUEUE_STATUS:
case SNDRV_SEQ_IOCTL_GET_QUEUE_TEMPO:
case SNDRV_SEQ_IOCTL_SET_QUEUE_TEMPO:
case SNDRV_SEQ_IOCTL_GET_QUEUE_TIMER:
case SNDRV_SEQ_IOCTL_SET_QUEUE_TIMER:
case SNDRV_SEQ_IOCTL_GET_QUEUE_CLIENT:
case SNDRV_SEQ_IOCTL_SET_QUEUE_CLIENT:
case SNDRV_SEQ_IOCTL_GET_CLIENT_POOL:
case SNDRV_SEQ_IOCTL_SET_CLIENT_POOL:
case SNDRV_SEQ_IOCTL_REMOVE_EVENTS:
case SNDRV_SEQ_IOCTL_QUERY_SUBS:
case SNDRV_SEQ_IOCTL_GET_SUBSCRIPTION:
case SNDRV_SEQ_IOCTL_QUERY_NEXT_CLIENT:
case SNDRV_SEQ_IOCTL_RUNNING_MODE:
return snd_seq_ioctl(file, cmd, arg);
case SNDRV_SEQ_IOCTL_CREATE_PORT32:
return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_CREATE_PORT, argp);
case SNDRV_SEQ_IOCTL_DELETE_PORT32:
return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_DELETE_PORT, argp);
case SNDRV_SEQ_IOCTL_GET_PORT_INFO32:
return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_GET_PORT_INFO, argp);
case SNDRV_SEQ_IOCTL_SET_PORT_INFO32:
return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_SET_PORT_INFO, argp);
case SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT32:
return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT, argp);
}
return -ENOIOCTLCMD;
}
+245
View File
@@ -0,0 +1,245 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* ALSA sequencer MIDI-through client
* Copyright (c) 1999-2000 by Takashi Iwai <tiwai@suse.de>
*/
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <sound/core.h>
#include "seq_clientmgr.h"
#include "seq_memory.h"
#include <sound/initval.h>
#include <sound/asoundef.h>
/*
Sequencer MIDI-through client
This gives a simple midi-through client. All the normal input events
are redirected to output port immediately.
The routing can be done via aconnect program in alsa-utils.
Each client has a static client number 14 (= SNDRV_SEQ_CLIENT_DUMMY).
If you want to auto-load this module, you may add the following alias
in your /etc/conf.modules file.
alias snd-seq-client-14 snd-seq-dummy
The module is loaded on demand for client 14, or /proc/asound/seq/
is accessed. If you don't need this module to be loaded, alias
snd-seq-client-14 as "off". This will help modprobe.
The number of ports to be created can be specified via the module
parameter "ports". For example, to create four ports, add the
following option in a configuration file under /etc/modprobe.d/:
option snd-seq-dummy ports=4
The model option "duplex=1" enables duplex operation to the port.
In duplex mode, a pair of ports are created instead of single port,
and events are tunneled between pair-ports. For example, input to
port A is sent to output port of another port B and vice versa.
In duplex mode, each port has DUPLEX capability.
*/
MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
MODULE_DESCRIPTION("ALSA sequencer MIDI-through client");
MODULE_LICENSE("GPL");
MODULE_ALIAS("snd-seq-client-" __stringify(SNDRV_SEQ_CLIENT_DUMMY));
static int ports = 1;
static bool duplex;
module_param(ports, int, 0444);
MODULE_PARM_DESC(ports, "number of ports to be created");
module_param(duplex, bool, 0444);
MODULE_PARM_DESC(duplex, "create DUPLEX ports");
#if IS_ENABLED(CONFIG_SND_SEQ_UMP)
static int ump;
module_param(ump, int, 0444);
MODULE_PARM_DESC(ump, "UMP conversion (0: no convert, 1: MIDI 1.0, 2: MIDI 2.0)");
#endif
struct snd_seq_dummy_port {
int client;
int port;
int duplex;
int connect;
};
static int my_client __ro_after_init = -1;
/*
* event input callback - just redirect events to subscribers
*/
static int
dummy_input(struct snd_seq_event *ev, int direct, void *private_data,
int atomic, int hop)
{
struct snd_seq_dummy_port *p;
union __snd_seq_event tmpev;
size_t size;
p = private_data;
if (ev->source.client == SNDRV_SEQ_CLIENT_SYSTEM ||
ev->type == SNDRV_SEQ_EVENT_KERNEL_ERROR)
return 0; /* ignore system messages */
size = snd_seq_event_packet_size(ev);
memcpy(&tmpev, ev, size);
if (p->duplex)
tmpev.legacy.source.port = p->connect;
else
tmpev.legacy.source.port = p->port;
tmpev.legacy.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
return snd_seq_kernel_client_dispatch(p->client, &tmpev.legacy, atomic, hop);
}
/*
* free_private callback
*/
static void
dummy_free(void *private_data)
{
kfree(private_data);
}
/*
* create a port
*/
static struct snd_seq_dummy_port __init *
create_port(int idx, int type)
{
struct snd_seq_port_info pinfo;
struct snd_seq_port_callback pcb;
struct snd_seq_dummy_port *rec;
rec = kzalloc_obj(*rec);
if (!rec)
return NULL;
rec->client = my_client;
rec->duplex = duplex;
rec->connect = 0;
memset(&pinfo, 0, sizeof(pinfo));
pinfo.addr.client = my_client;
if (duplex)
sprintf(pinfo.name, "Midi Through Port-%d:%c", idx,
(type ? 'B' : 'A'));
else
sprintf(pinfo.name, "Midi Through Port-%d", idx);
pinfo.capability = SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ;
pinfo.capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE;
if (duplex)
pinfo.capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;
pinfo.direction = SNDRV_SEQ_PORT_DIR_BIDIRECTION;
pinfo.type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC
| SNDRV_SEQ_PORT_TYPE_SOFTWARE
| SNDRV_SEQ_PORT_TYPE_PORT;
memset(&pcb, 0, sizeof(pcb));
pcb.owner = THIS_MODULE;
pcb.event_input = dummy_input;
pcb.private_free = dummy_free;
pcb.private_data = rec;
pinfo.kernel = &pcb;
if (snd_seq_kernel_client_ctl(my_client, SNDRV_SEQ_IOCTL_CREATE_PORT, &pinfo) < 0) {
kfree(rec);
return NULL;
}
rec->port = pinfo.addr.port;
return rec;
}
/*
* register client and create ports
*/
static int __init
register_client(void)
{
struct snd_seq_dummy_port *rec1, *rec2;
#if IS_ENABLED(CONFIG_SND_SEQ_UMP)
struct snd_seq_client *client;
#endif
int i;
if (ports < 1) {
pr_err("ALSA: seq_dummy: invalid number of ports %d\n", ports);
return -EINVAL;
}
/* create client */
my_client = snd_seq_create_kernel_client(NULL, SNDRV_SEQ_CLIENT_DUMMY,
"Midi Through");
if (my_client < 0)
return my_client;
#if IS_ENABLED(CONFIG_SND_SEQ_UMP)
client = snd_seq_kernel_client_get(my_client);
if (!client)
return -EINVAL;
switch (ump) {
case 1:
client->midi_version = SNDRV_SEQ_CLIENT_UMP_MIDI_1_0;
break;
case 2:
client->midi_version = SNDRV_SEQ_CLIENT_UMP_MIDI_2_0;
break;
default:
/* don't convert events but just pass-through */
client->filter = SNDRV_SEQ_FILTER_NO_CONVERT;
break;
}
snd_seq_kernel_client_put(client);
#endif
/* create ports */
for (i = 0; i < ports; i++) {
rec1 = create_port(i, 0);
if (rec1 == NULL) {
snd_seq_delete_kernel_client(my_client);
return -ENOMEM;
}
if (duplex) {
rec2 = create_port(i, 1);
if (rec2 == NULL) {
snd_seq_delete_kernel_client(my_client);
return -ENOMEM;
}
rec1->connect = rec2->port;
rec2->connect = rec1->port;
}
}
return 0;
}
/*
* delete client if exists
*/
static void __exit
delete_client(void)
{
if (my_client >= 0)
snd_seq_delete_kernel_client(my_client);
}
/*
* Init part
*/
static int __init alsa_seq_dummy_init(void)
{
return register_client();
}
static void __exit alsa_seq_dummy_exit(void)
{
delete_client();
}
module_init(alsa_seq_dummy_init)
module_exit(alsa_seq_dummy_exit)
+292
View File
@@ -0,0 +1,292 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* ALSA sequencer FIFO
* Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
*/
#include <sound/core.h>
#include <linux/slab.h>
#include <linux/sched/signal.h>
#include "seq_fifo.h"
#include "seq_lock.h"
/* FIFO */
/* create new fifo */
struct snd_seq_fifo *snd_seq_fifo_new(int poolsize)
{
struct snd_seq_fifo *f;
f = kzalloc_obj(*f);
if (!f)
return NULL;
f->pool = snd_seq_pool_new(poolsize);
if (f->pool == NULL) {
kfree(f);
return NULL;
}
if (snd_seq_pool_init(f->pool) < 0) {
snd_seq_pool_delete(&f->pool);
kfree(f);
return NULL;
}
spin_lock_init(&f->lock);
snd_use_lock_init(&f->use_lock);
init_waitqueue_head(&f->input_sleep);
atomic_set(&f->overflow, 0);
f->head = NULL;
f->tail = NULL;
f->cells = 0;
return f;
}
void snd_seq_fifo_delete(struct snd_seq_fifo **fifo)
{
struct snd_seq_fifo *f;
if (snd_BUG_ON(!fifo))
return;
f = *fifo;
if (snd_BUG_ON(!f))
return;
*fifo = NULL;
if (f->pool)
snd_seq_pool_mark_closing(f->pool);
snd_seq_fifo_clear(f);
/* wake up clients if any */
if (waitqueue_active(&f->input_sleep))
wake_up(&f->input_sleep);
/* release resources...*/
/*....................*/
if (f->pool) {
snd_seq_pool_done(f->pool);
snd_seq_pool_delete(&f->pool);
}
kfree(f);
}
static struct snd_seq_event_cell *fifo_cell_out(struct snd_seq_fifo *f);
/* clear queue */
void snd_seq_fifo_clear(struct snd_seq_fifo *f)
{
struct snd_seq_event_cell *cell;
/* clear overflow flag */
atomic_set(&f->overflow, 0);
snd_use_lock_sync(&f->use_lock);
guard(spinlock_irq)(&f->lock);
/* drain the fifo */
while ((cell = fifo_cell_out(f)) != NULL) {
snd_seq_cell_free(cell);
}
}
/* enqueue event to fifo */
int snd_seq_fifo_event_in(struct snd_seq_fifo *f,
struct snd_seq_event *event)
{
struct snd_seq_event_cell *cell;
struct snd_seq_pool *pool;
bool linked;
int err;
if (snd_BUG_ON(!f))
return -EINVAL;
guard(snd_seq_fifo)(f);
retry:
pool = READ_ONCE(f->pool);
err = snd_seq_event_dup(pool, event, &cell, 1, NULL, NULL); /* always non-blocking */
if (err < 0) {
if ((err == -ENOMEM) || (err == -EAGAIN))
atomic_inc(&f->overflow);
return err;
}
/* append new cells to fifo */
linked = false;
scoped_guard(spinlock_irqsave, &f->lock) {
if (cell->pool == f->pool) {
if (f->tail)
f->tail->next = cell;
f->tail = cell;
if (!f->head)
f->head = cell;
cell->next = NULL;
f->cells++;
linked = true;
}
}
if (!linked) {
/* Retry against the replacement pool after resize publishes it. */
snd_seq_cell_free(cell);
goto retry;
}
/* wakeup client */
if (waitqueue_active(&f->input_sleep))
wake_up(&f->input_sleep);
return 0; /* success */
}
/* dequeue cell from fifo */
static struct snd_seq_event_cell *fifo_cell_out(struct snd_seq_fifo *f)
{
struct snd_seq_event_cell *cell;
cell = f->head;
if (cell) {
f->head = cell->next;
/* reset tail if this was the last element */
if (f->tail == cell)
f->tail = NULL;
cell->next = NULL;
f->cells--;
}
return cell;
}
/* dequeue cell from fifo and copy on user space */
int snd_seq_fifo_cell_out(struct snd_seq_fifo *f,
struct snd_seq_event_cell **cellp, int nonblock)
{
struct snd_seq_event_cell *cell;
unsigned long flags;
wait_queue_entry_t wait;
if (snd_BUG_ON(!f))
return -EINVAL;
*cellp = NULL;
init_waitqueue_entry(&wait, current);
spin_lock_irqsave(&f->lock, flags);
while ((cell = fifo_cell_out(f)) == NULL) {
if (nonblock) {
/* non-blocking - return immediately */
spin_unlock_irqrestore(&f->lock, flags);
return -EAGAIN;
}
set_current_state(TASK_INTERRUPTIBLE);
add_wait_queue(&f->input_sleep, &wait);
spin_unlock_irqrestore(&f->lock, flags);
schedule();
spin_lock_irqsave(&f->lock, flags);
remove_wait_queue(&f->input_sleep, &wait);
if (signal_pending(current)) {
spin_unlock_irqrestore(&f->lock, flags);
return -ERESTARTSYS;
}
}
spin_unlock_irqrestore(&f->lock, flags);
*cellp = cell;
return 0;
}
void snd_seq_fifo_cell_putback(struct snd_seq_fifo *f,
struct snd_seq_event_cell *cell)
{
bool linked = false;
if (cell) {
scoped_guard(spinlock_irqsave, &f->lock) {
if (cell->pool == f->pool) {
cell->next = f->head;
f->head = cell;
if (!f->tail)
f->tail = cell;
f->cells++;
linked = true;
}
}
if (!linked)
snd_seq_cell_free(cell);
}
}
/* polling; return non-zero if queue is available */
int snd_seq_fifo_poll_wait(struct snd_seq_fifo *f, struct file *file,
poll_table *wait)
{
poll_wait(file, &f->input_sleep, wait);
guard(spinlock_irq)(&f->lock);
return (f->cells > 0);
}
/* change the size of pool; all old events are removed */
int snd_seq_fifo_resize(struct snd_seq_fifo *f, int poolsize)
{
struct snd_seq_pool *newpool, *oldpool;
struct snd_seq_event_cell *cell, *next, *oldhead;
if (snd_BUG_ON(!f || !f->pool))
return -EINVAL;
/* allocate new pool */
newpool = snd_seq_pool_new(poolsize);
if (newpool == NULL)
return -ENOMEM;
if (snd_seq_pool_init(newpool) < 0) {
snd_seq_pool_delete(&newpool);
return -ENOMEM;
}
scoped_guard(spinlock_irq, &f->lock) {
/* remember old pool */
oldpool = f->pool;
oldhead = f->head;
/* exchange pools */
WRITE_ONCE(f->pool, newpool);
f->head = NULL;
f->tail = NULL;
f->cells = 0;
/* NOTE: overflow flag is not cleared */
}
/* close the old pool and wait until all users are gone */
snd_seq_pool_mark_closing(oldpool);
snd_use_lock_sync(&f->use_lock);
/* release cells in old pool */
for (cell = oldhead; cell; cell = next) {
next = cell->next;
snd_seq_cell_free(cell);
}
snd_seq_pool_delete(&oldpool);
return 0;
}
/* get the number of unused cells safely */
int snd_seq_fifo_unused_cells(struct snd_seq_fifo *f)
{
if (!f)
return 0;
guard(snd_seq_fifo)(f);
guard(spinlock_irqsave)(&f->lock);
return snd_seq_unused_cells(f->pool);
}
+60
View File
@@ -0,0 +1,60 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* ALSA sequencer FIFO
* Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
*/
#ifndef __SND_SEQ_FIFO_H
#define __SND_SEQ_FIFO_H
#include "seq_memory.h"
#include "seq_lock.h"
/* === FIFO === */
struct snd_seq_fifo {
struct snd_seq_pool *pool; /* FIFO pool */
struct snd_seq_event_cell *head; /* pointer to head of fifo */
struct snd_seq_event_cell *tail; /* pointer to tail of fifo */
int cells;
spinlock_t lock;
snd_use_lock_t use_lock;
wait_queue_head_t input_sleep;
atomic_t overflow;
};
/* create new fifo (constructor) */
struct snd_seq_fifo *snd_seq_fifo_new(int poolsize);
/* delete fifo (destructor) */
void snd_seq_fifo_delete(struct snd_seq_fifo **f);
/* enqueue event to fifo */
int snd_seq_fifo_event_in(struct snd_seq_fifo *f, struct snd_seq_event *event);
/* lock fifo from release */
#define snd_seq_fifo_lock(fifo) snd_use_lock_use(&(fifo)->use_lock)
#define snd_seq_fifo_unlock(fifo) snd_use_lock_free(&(fifo)->use_lock)
DEFINE_GUARD(snd_seq_fifo, struct snd_seq_fifo *, snd_seq_fifo_lock(_T), snd_seq_fifo_unlock(_T))
/* get a cell from fifo - fifo should be locked */
int snd_seq_fifo_cell_out(struct snd_seq_fifo *f, struct snd_seq_event_cell **cellp, int nonblock);
/* free dequeued cell - fifo should be locked */
void snd_seq_fifo_cell_putback(struct snd_seq_fifo *f, struct snd_seq_event_cell *cell);
/* clean up queue */
void snd_seq_fifo_clear(struct snd_seq_fifo *f);
/* polling */
int snd_seq_fifo_poll_wait(struct snd_seq_fifo *f, struct file *file, poll_table *wait);
/* resize pool in fifo */
int snd_seq_fifo_resize(struct snd_seq_fifo *f, int poolsize);
/* get the number of unused cells safely */
int snd_seq_fifo_unused_cells(struct snd_seq_fifo *f);
#endif
+60
View File
@@ -0,0 +1,60 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* ALSA sequencer /proc interface
* Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
*/
#include <linux/init.h>
#include <linux/export.h>
#include <sound/core.h>
#include "seq_info.h"
#include "seq_clientmgr.h"
#include "seq_timer.h"
static struct snd_info_entry *queues_entry;
static struct snd_info_entry *clients_entry;
static struct snd_info_entry *timer_entry;
static struct snd_info_entry * __init
create_info_entry(char *name, void (*read)(struct snd_info_entry *,
struct snd_info_buffer *))
{
struct snd_info_entry *entry;
entry = snd_info_create_module_entry(THIS_MODULE, name, snd_seq_root);
if (entry == NULL)
return NULL;
entry->content = SNDRV_INFO_CONTENT_TEXT;
entry->c.text.read = read;
if (snd_info_register(entry) < 0) {
snd_info_free_entry(entry);
return NULL;
}
return entry;
}
void snd_seq_info_done(void)
{
snd_info_free_entry(queues_entry);
snd_info_free_entry(clients_entry);
snd_info_free_entry(timer_entry);
}
/* create all our /proc entries */
int __init snd_seq_info_init(void)
{
queues_entry = create_info_entry("queues",
snd_seq_info_queues_read);
clients_entry = create_info_entry("clients",
snd_seq_info_clients_read);
timer_entry = create_info_entry("timer", snd_seq_info_timer_read);
if (!queues_entry || !clients_entry || !timer_entry)
goto error;
return 0;
error:
snd_seq_info_done();
return -ENOMEM;
}
+25
View File
@@ -0,0 +1,25 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* ALSA sequencer /proc info
* Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
*/
#ifndef __SND_SEQ_INFO_H
#define __SND_SEQ_INFO_H
#include <sound/info.h>
#include <sound/seq_kernel.h>
void snd_seq_info_clients_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer);
void snd_seq_info_timer_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer);
void snd_seq_info_queues_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer);
#ifdef CONFIG_SND_PROC_FS
int snd_seq_info_init(void);
void snd_seq_info_done(void);
#else
static inline int snd_seq_info_init(void) { return 0; }
static inline void snd_seq_info_done(void) {}
#endif
#endif
+26
View File
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Do sleep inside a spin-lock
* Copyright (c) 1999 by Takashi Iwai <tiwai@suse.de>
*/
#include <linux/export.h>
#include <sound/core.h>
#include "seq_lock.h"
/* wait until all locks are released */
void snd_use_lock_sync_helper(snd_use_lock_t *lockp, const char *file, int line)
{
int warn_count = 5 * HZ;
if (atomic_read(lockp) < 0) {
pr_warn("ALSA: seq_lock: lock trouble [counter = %d] in %s:%d\n", atomic_read(lockp), file, line);
return;
}
while (atomic_read(lockp) > 0) {
if (warn_count-- == 0)
pr_warn("ALSA: seq_lock: waiting [%d left] in %s:%d\n", atomic_read(lockp), file, line);
schedule_timeout_uninterruptible(1);
}
}
EXPORT_SYMBOL(snd_use_lock_sync_helper);
+22
View File
@@ -0,0 +1,22 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __SND_SEQ_LOCK_H
#define __SND_SEQ_LOCK_H
#include <linux/sched.h>
typedef atomic_t snd_use_lock_t;
/* initialize lock */
#define snd_use_lock_init(lockp) atomic_set(lockp, 0)
/* increment lock */
#define snd_use_lock_use(lockp) atomic_inc(lockp)
/* release lock */
#define snd_use_lock_free(lockp) atomic_dec(lockp)
/* wait until all locks are released */
void snd_use_lock_sync_helper(snd_use_lock_t *lock, const char *file, int line);
#define snd_use_lock_sync(lockp) snd_use_lock_sync_helper(lockp, __BASE_FILE__, __LINE__)
#endif /* __SND_SEQ_LOCK_H */
+562
View File
@@ -0,0 +1,562 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* ALSA sequencer Memory Manager
* Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
* Jaroslav Kysela <perex@perex.cz>
* 2000 by Takashi Iwai <tiwai@suse.de>
*/
#include <linux/init.h>
#include <linux/export.h>
#include <linux/slab.h>
#include <linux/sched/signal.h>
#include <linux/mm.h>
#include <sound/core.h>
#include <sound/seq_kernel.h>
#include "seq_memory.h"
#include "seq_queue.h"
#include "seq_info.h"
#include "seq_lock.h"
static inline int snd_seq_pool_available(struct snd_seq_pool *pool)
{
return pool->total_elements - atomic_read(&pool->counter);
}
static inline int snd_seq_output_ok(struct snd_seq_pool *pool)
{
return snd_seq_pool_available(pool) >= pool->room;
}
/*
* Variable length event:
* The event like sysex uses variable length type.
* The external data may be stored in three different formats.
* 1) kernel space
* This is the normal case.
* ext.data.len = length
* ext.data.ptr = buffer pointer
* 2) user space
* When an event is generated via read(), the external data is
* kept in user space until expanded.
* ext.data.len = length | SNDRV_SEQ_EXT_USRPTR
* ext.data.ptr = userspace pointer
* 3) chained cells
* When the variable length event is enqueued (in prioq or fifo),
* the external data is decomposed to several cells.
* ext.data.len = length | SNDRV_SEQ_EXT_CHAINED
* ext.data.ptr = the additiona cell head
* -> cell.next -> cell.next -> ..
*/
/*
* exported:
* call dump function to expand external data.
*/
static int get_var_len(const struct snd_seq_event *event)
{
if ((event->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARIABLE)
return -EINVAL;
return event->data.ext.len & ~SNDRV_SEQ_EXT_MASK;
}
static int dump_var_event(const struct snd_seq_event *event,
snd_seq_dump_func_t func, void *private_data,
int offset, int maxlen)
{
int len, err;
struct snd_seq_event_cell *cell;
len = get_var_len(event);
if (len <= 0)
return len;
if (len <= offset)
return 0;
if (maxlen && len > offset + maxlen)
len = offset + maxlen;
if (event->data.ext.len & SNDRV_SEQ_EXT_USRPTR) {
char buf[32];
char __user *curptr = (char __force __user *)event->data.ext.ptr;
curptr += offset;
len -= offset;
while (len > 0) {
int size = sizeof(buf);
if (len < size)
size = len;
if (copy_from_user(buf, curptr, size))
return -EFAULT;
err = func(private_data, buf, size);
if (err < 0)
return err;
curptr += size;
len -= size;
}
return 0;
}
if (!(event->data.ext.len & SNDRV_SEQ_EXT_CHAINED))
return func(private_data, event->data.ext.ptr + offset,
len - offset);
cell = (struct snd_seq_event_cell *)event->data.ext.ptr;
for (; len > 0 && cell; cell = cell->next) {
int size = sizeof(struct snd_seq_event);
char *curptr = (char *)&cell->event;
if (offset >= size) {
offset -= size;
len -= size;
continue;
}
if (len < size)
size = len;
err = func(private_data, curptr + offset, size - offset);
if (err < 0)
return err;
offset = 0;
len -= size;
}
return 0;
}
int snd_seq_dump_var_event(const struct snd_seq_event *event,
snd_seq_dump_func_t func, void *private_data)
{
return dump_var_event(event, func, private_data, 0, 0);
}
EXPORT_SYMBOL(snd_seq_dump_var_event);
/*
* exported:
* expand the variable length event to linear buffer space.
*/
static int seq_copy_in_kernel(void *ptr, void *src, int size)
{
char **bufptr = ptr;
memcpy(*bufptr, src, size);
*bufptr += size;
return 0;
}
static int seq_copy_in_user(void *ptr, void *src, int size)
{
char __user **bufptr = ptr;
if (copy_to_user(*bufptr, src, size))
return -EFAULT;
*bufptr += size;
return 0;
}
static int expand_var_event(const struct snd_seq_event *event,
int offset, int size, char *buf, bool in_kernel)
{
if (event->data.ext.len & SNDRV_SEQ_EXT_USRPTR) {
if (! in_kernel)
return -EINVAL;
if (copy_from_user(buf,
(char __force __user *)event->data.ext.ptr + offset,
size))
return -EFAULT;
return 0;
}
return dump_var_event(event,
in_kernel ? seq_copy_in_kernel : seq_copy_in_user,
&buf, offset, size);
}
int snd_seq_expand_var_event(const struct snd_seq_event *event, int count, char *buf,
int in_kernel, int size_aligned)
{
int len, newlen, err;
len = get_var_len(event);
if (len < 0)
return len;
newlen = len;
if (size_aligned > 0)
newlen = roundup(len, size_aligned);
if (count < newlen)
return -EAGAIN;
err = expand_var_event(event, 0, len, buf, in_kernel);
if (err < 0)
return err;
if (len != newlen) {
if (in_kernel)
memset(buf + len, 0, newlen - len);
else if (clear_user((__force void __user *)buf + len,
newlen - len))
return -EFAULT;
}
return newlen;
}
EXPORT_SYMBOL(snd_seq_expand_var_event);
int snd_seq_expand_var_event_at(const struct snd_seq_event *event, int count,
char *buf, int offset)
{
int len, err;
len = get_var_len(event);
if (len < 0)
return len;
if (len <= offset)
return 0;
len -= offset;
if (len > count)
len = count;
err = expand_var_event(event, offset, len, buf, true);
if (err < 0)
return err;
return len;
}
EXPORT_SYMBOL_GPL(snd_seq_expand_var_event_at);
/*
* release this cell, free extended data if available
*/
static inline void free_cell(struct snd_seq_pool *pool,
struct snd_seq_event_cell *cell)
{
cell->next = pool->free;
pool->free = cell;
atomic_dec(&pool->counter);
}
void snd_seq_cell_free(struct snd_seq_event_cell * cell)
{
struct snd_seq_pool *pool;
if (snd_BUG_ON(!cell))
return;
pool = cell->pool;
if (snd_BUG_ON(!pool))
return;
guard(spinlock_irqsave)(&pool->lock);
free_cell(pool, cell);
if (snd_seq_ev_is_variable(&cell->event)) {
if (cell->event.data.ext.len & SNDRV_SEQ_EXT_CHAINED) {
struct snd_seq_event_cell *curp, *nextptr;
curp = cell->event.data.ext.ptr;
for (; curp; curp = nextptr) {
nextptr = curp->next;
curp->next = pool->free;
free_cell(pool, curp);
}
}
}
if (waitqueue_active(&pool->output_sleep)) {
/* has enough space now? */
if (snd_seq_output_ok(pool))
wake_up(&pool->output_sleep);
}
}
/*
* allocate an event cell.
*/
static int snd_seq_cell_alloc(struct snd_seq_pool *pool,
struct snd_seq_event_cell **cellp,
int nonblock, struct file *file,
struct mutex *mutexp)
{
struct snd_seq_event_cell *cell;
unsigned long flags;
int err = -EAGAIN;
wait_queue_entry_t wait;
if (pool == NULL)
return -EINVAL;
*cellp = NULL;
init_waitqueue_entry(&wait, current);
spin_lock_irqsave(&pool->lock, flags);
if (pool->ptr == NULL) { /* not initialized */
pr_debug("ALSA: seq: pool is not initialized\n");
err = -EINVAL;
goto __error;
}
while (pool->free == NULL && ! nonblock && ! pool->closing) {
set_current_state(TASK_INTERRUPTIBLE);
add_wait_queue(&pool->output_sleep, &wait);
spin_unlock_irqrestore(&pool->lock, flags);
if (mutexp)
mutex_unlock(mutexp);
schedule();
if (mutexp)
mutex_lock(mutexp);
spin_lock_irqsave(&pool->lock, flags);
remove_wait_queue(&pool->output_sleep, &wait);
/* interrupted? */
if (signal_pending(current)) {
err = -ERESTARTSYS;
goto __error;
}
}
if (pool->closing) { /* closing.. */
err = -ENOMEM;
goto __error;
}
cell = pool->free;
if (cell) {
int used;
pool->free = cell->next;
atomic_inc(&pool->counter);
used = atomic_read(&pool->counter);
if (pool->max_used < used)
pool->max_used = used;
pool->event_alloc_success++;
/* clear cell pointers */
cell->next = NULL;
err = 0;
} else
pool->event_alloc_failures++;
*cellp = cell;
__error:
spin_unlock_irqrestore(&pool->lock, flags);
return err;
}
/*
* duplicate the event to a cell.
* if the event has external data, the data is decomposed to additional
* cells.
*/
int snd_seq_event_dup(struct snd_seq_pool *pool, struct snd_seq_event *event,
struct snd_seq_event_cell **cellp, int nonblock,
struct file *file, struct mutex *mutexp)
{
int ncells, err;
unsigned int extlen;
struct snd_seq_event_cell *cell;
int size;
*cellp = NULL;
ncells = 0;
extlen = 0;
if (snd_seq_ev_is_variable(event)) {
extlen = event->data.ext.len & ~SNDRV_SEQ_EXT_MASK;
ncells = DIV_ROUND_UP(extlen, sizeof(struct snd_seq_event));
}
if (ncells >= pool->total_elements)
return -ENOMEM;
err = snd_seq_cell_alloc(pool, &cell, nonblock, file, mutexp);
if (err < 0)
return err;
/* copy the event */
size = snd_seq_event_packet_size(event);
memcpy(&cell->ump, event, size);
#if IS_ENABLED(CONFIG_SND_SEQ_UMP)
if (size < sizeof(cell->ump))
cell->ump.raw.extra = 0;
#endif
/* decompose */
if (snd_seq_ev_is_variable(event)) {
int len = extlen;
int is_chained = event->data.ext.len & SNDRV_SEQ_EXT_CHAINED;
int is_usrptr = event->data.ext.len & SNDRV_SEQ_EXT_USRPTR;
struct snd_seq_event_cell *src, *tmp, *tail;
char *buf;
cell->event.data.ext.len = extlen | SNDRV_SEQ_EXT_CHAINED;
cell->event.data.ext.ptr = NULL;
src = (struct snd_seq_event_cell *)event->data.ext.ptr;
buf = (char *)event->data.ext.ptr;
tail = NULL;
while (ncells-- > 0) {
size = sizeof(struct snd_seq_event);
if (len < size)
size = len;
err = snd_seq_cell_alloc(pool, &tmp, nonblock, file,
mutexp);
if (err < 0)
goto __error;
if (cell->event.data.ext.ptr == NULL)
cell->event.data.ext.ptr = tmp;
if (tail)
tail->next = tmp;
tail = tmp;
/* copy chunk */
if (is_chained && src) {
tmp->event = src->event;
src = src->next;
} else if (is_usrptr) {
if (copy_from_user(&tmp->event, (char __force __user *)buf, size)) {
err = -EFAULT;
goto __error;
}
} else {
memcpy(&tmp->event, buf, size);
}
buf += size;
len -= size;
}
}
*cellp = cell;
return 0;
__error:
snd_seq_cell_free(cell);
return err;
}
/* poll wait */
int snd_seq_pool_poll_wait(struct snd_seq_pool *pool, struct file *file,
poll_table *wait)
{
poll_wait(file, &pool->output_sleep, wait);
guard(spinlock_irq)(&pool->lock);
return snd_seq_output_ok(pool);
}
/* allocate room specified number of events */
int snd_seq_pool_init(struct snd_seq_pool *pool)
{
int cell;
struct snd_seq_event_cell *cellptr;
if (snd_BUG_ON(!pool))
return -EINVAL;
cellptr = kvmalloc_objs(struct snd_seq_event_cell, pool->size);
if (!cellptr)
return -ENOMEM;
/* add new cells to the free cell list */
guard(spinlock_irq)(&pool->lock);
if (pool->ptr) {
kvfree(cellptr);
return 0;
}
pool->ptr = cellptr;
pool->free = NULL;
for (cell = 0; cell < pool->size; cell++) {
cellptr = pool->ptr + cell;
cellptr->pool = pool;
cellptr->next = pool->free;
pool->free = cellptr;
}
pool->room = (pool->size + 1) / 2;
/* init statistics */
pool->max_used = 0;
pool->total_elements = pool->size;
return 0;
}
/* refuse the further insertion to the pool */
void snd_seq_pool_mark_closing(struct snd_seq_pool *pool)
{
if (snd_BUG_ON(!pool))
return;
guard(spinlock_irqsave)(&pool->lock);
pool->closing = 1;
}
/* remove events */
int snd_seq_pool_done(struct snd_seq_pool *pool)
{
struct snd_seq_event_cell *ptr;
if (snd_BUG_ON(!pool))
return -EINVAL;
/* wait for closing all threads */
if (waitqueue_active(&pool->output_sleep))
wake_up(&pool->output_sleep);
while (atomic_read(&pool->counter) > 0)
schedule_timeout_uninterruptible(1);
/* release all resources */
scoped_guard(spinlock_irq, &pool->lock) {
ptr = pool->ptr;
pool->ptr = NULL;
pool->free = NULL;
pool->total_elements = 0;
}
kvfree(ptr);
guard(spinlock_irq)(&pool->lock);
pool->closing = 0;
return 0;
}
/* init new memory pool */
struct snd_seq_pool *snd_seq_pool_new(int poolsize)
{
struct snd_seq_pool *pool;
/* create pool block */
pool = kzalloc_obj(*pool);
if (!pool)
return NULL;
spin_lock_init(&pool->lock);
pool->ptr = NULL;
pool->free = NULL;
pool->total_elements = 0;
atomic_set(&pool->counter, 0);
pool->closing = 0;
init_waitqueue_head(&pool->output_sleep);
pool->size = poolsize;
/* init statistics */
pool->max_used = 0;
return pool;
}
/* remove memory pool */
int snd_seq_pool_delete(struct snd_seq_pool **ppool)
{
struct snd_seq_pool *pool = *ppool;
*ppool = NULL;
if (pool == NULL)
return 0;
snd_seq_pool_mark_closing(pool);
snd_seq_pool_done(pool);
kfree(pool);
return 0;
}
/* exported to seq_clientmgr.c */
void snd_seq_info_pool(struct snd_info_buffer *buffer,
struct snd_seq_pool *pool, char *space)
{
if (pool == NULL)
return;
snd_iprintf(buffer, "%sPool size : %d\n", space, pool->total_elements);
snd_iprintf(buffer, "%sCells in use : %d\n", space, atomic_read(&pool->counter));
snd_iprintf(buffer, "%sPeak cells in use : %d\n", space, pool->max_used);
snd_iprintf(buffer, "%sAlloc success : %d\n", space, pool->event_alloc_success);
snd_iprintf(buffer, "%sAlloc failures : %d\n", space, pool->event_alloc_failures);
}
+105
View File
@@ -0,0 +1,105 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* ALSA sequencer Memory Manager
* Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
*/
#ifndef __SND_SEQ_MEMORYMGR_H
#define __SND_SEQ_MEMORYMGR_H
#include <sound/seq_kernel.h>
#include <linux/poll.h>
struct snd_info_buffer;
/* aliasing for legacy and UMP event packet handling */
union __snd_seq_event {
struct snd_seq_event legacy;
#if IS_ENABLED(CONFIG_SND_SEQ_UMP)
struct snd_seq_ump_event ump;
#endif
struct {
struct snd_seq_event event;
#if IS_ENABLED(CONFIG_SND_SEQ_UMP)
u32 extra;
#endif
} __packed raw;
};
/* container for sequencer event (internal use) */
struct snd_seq_event_cell {
union {
struct snd_seq_event event;
union __snd_seq_event ump;
};
struct snd_seq_pool *pool; /* used pool */
struct snd_seq_event_cell *next; /* next cell */
};
/* design note: the pool is a contiguous block of memory, if we dynamicly
want to add additional cells to the pool be better store this in another
pool as we need to know the base address of the pool when releasing
memory. */
struct snd_seq_pool {
struct snd_seq_event_cell *ptr; /* pointer to first event chunk */
struct snd_seq_event_cell *free; /* pointer to the head of the free list */
int total_elements; /* pool size actually allocated */
atomic_t counter; /* cells free */
int size; /* pool size to be allocated */
int room; /* watermark for sleep/wakeup */
int closing;
/* statistics */
int max_used;
int event_alloc_nopool;
int event_alloc_failures;
int event_alloc_success;
/* Write locking */
wait_queue_head_t output_sleep;
/* Pool lock */
spinlock_t lock;
};
void snd_seq_cell_free(struct snd_seq_event_cell *cell);
int snd_seq_event_dup(struct snd_seq_pool *pool, struct snd_seq_event *event,
struct snd_seq_event_cell **cellp, int nonblock,
struct file *file, struct mutex *mutexp);
/* return number of unused (free) cells */
static inline int snd_seq_unused_cells(struct snd_seq_pool *pool)
{
return pool ? pool->total_elements - atomic_read(&pool->counter) : 0;
}
/* return total number of allocated cells */
static inline int snd_seq_total_cells(struct snd_seq_pool *pool)
{
return pool ? pool->total_elements : 0;
}
/* init pool - allocate events */
int snd_seq_pool_init(struct snd_seq_pool *pool);
/* done pool - free events */
void snd_seq_pool_mark_closing(struct snd_seq_pool *pool);
int snd_seq_pool_done(struct snd_seq_pool *pool);
/* create pool */
struct snd_seq_pool *snd_seq_pool_new(int poolsize);
/* remove pool */
int snd_seq_pool_delete(struct snd_seq_pool **pool);
/* polling */
int snd_seq_pool_poll_wait(struct snd_seq_pool *pool, struct file *file, poll_table *wait);
void snd_seq_info_pool(struct snd_info_buffer *buffer,
struct snd_seq_pool *pool, char *space);
#endif
+479
View File
@@ -0,0 +1,479 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Generic MIDI synth driver for ALSA sequencer
* Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
* Jaroslav Kysela <perex@perex.cz>
*/
/*
Possible options for midisynth module:
- automatic opening of midi ports on first received event or subscription
(close will be performed when client leaves)
*/
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <sound/core.h>
#include <sound/rawmidi.h>
#include <sound/seq_kernel.h>
#include <sound/seq_device.h>
#include <sound/seq_midi_event.h>
#include <sound/initval.h>
#include "seq_lock.h"
MODULE_AUTHOR("Frank van de Pol <fvdpol@coil.demon.nl>, Jaroslav Kysela <perex@perex.cz>");
MODULE_DESCRIPTION("Advanced Linux Sound Architecture sequencer MIDI synth.");
MODULE_LICENSE("GPL");
static int output_buffer_size = PAGE_SIZE;
module_param(output_buffer_size, int, 0644);
MODULE_PARM_DESC(output_buffer_size, "Output buffer size in bytes.");
static int input_buffer_size = PAGE_SIZE;
module_param(input_buffer_size, int, 0644);
MODULE_PARM_DESC(input_buffer_size, "Input buffer size in bytes.");
/* data for this midi synth driver */
struct seq_midisynth {
struct snd_card *card;
struct snd_rawmidi *rmidi;
int device;
int subdevice;
struct snd_rawmidi_file input_rfile;
spinlock_t output_lock; /* protects output_rfile publication */
snd_use_lock_t output_use_lock; /* in-flight event_input users */
struct snd_rawmidi_file output_rfile;
int seq_client;
int seq_port;
struct snd_midi_event *parser;
};
struct seq_midisynth_client {
int seq_client;
int num_ports;
int ports_per_device[SNDRV_RAWMIDI_DEVICES];
struct seq_midisynth *ports[SNDRV_RAWMIDI_DEVICES];
};
static struct seq_midisynth_client *synths[SNDRV_CARDS];
static DEFINE_MUTEX(register_mutex);
/* handle rawmidi input event (MIDI v1.0 stream) */
static void snd_midi_input_event(struct snd_rawmidi_substream *substream)
{
struct snd_rawmidi_runtime *runtime;
struct seq_midisynth *msynth;
struct snd_seq_event ev;
char buf[16], *pbuf;
long res;
if (substream == NULL)
return;
runtime = substream->runtime;
msynth = runtime->private_data;
if (msynth == NULL)
return;
memset(&ev, 0, sizeof(ev));
while (runtime->avail > 0) {
res = snd_rawmidi_kernel_read(substream, buf, sizeof(buf));
if (res <= 0)
continue;
if (msynth->parser == NULL)
continue;
pbuf = buf;
while (res-- > 0) {
if (!snd_midi_event_encode_byte(msynth->parser,
*pbuf++, &ev))
continue;
ev.source.port = msynth->seq_port;
ev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
snd_seq_kernel_client_dispatch(msynth->seq_client, &ev, 1, 0);
/* clear event and reset header */
memset(&ev, 0, sizeof(ev));
}
}
}
static int dump_midi(struct snd_rawmidi_substream *substream, const char *buf, int count)
{
struct snd_rawmidi_runtime *runtime;
int tmp;
if (snd_BUG_ON(!substream || !buf))
return -EINVAL;
runtime = substream->runtime;
tmp = runtime->avail;
if (tmp < count) {
if (printk_ratelimit())
pr_err("ALSA: seq_midi: MIDI output buffer overrun\n");
return -ENOMEM;
}
if (snd_rawmidi_kernel_write(substream, buf, count) < count)
return -EINVAL;
return 0;
}
/* callback for snd_seq_dump_var_event(), bridging to dump_midi() */
static int __dump_midi(void *ptr, void *buf, int count)
{
return dump_midi(ptr, buf, count);
}
static int event_process_midi(struct snd_seq_event *ev, int direct,
void *private_data, int atomic, int hop)
{
struct seq_midisynth *msynth = private_data;
unsigned char msg[10]; /* buffer for constructing midi messages */
struct snd_rawmidi_substream *substream;
int err = 0;
int len;
if (snd_BUG_ON(!msynth))
return -EINVAL;
scoped_guard(spinlock_irqsave, &msynth->output_lock) {
substream = msynth->output_rfile.output;
if (!substream)
return -ENODEV;
snd_use_lock_use(&msynth->output_use_lock);
}
if (ev->type == SNDRV_SEQ_EVENT_SYSEX) { /* special case, to save space */
if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARIABLE) {
/* invalid event */
pr_debug("ALSA: seq_midi: invalid sysex event flags = 0x%x\n", ev->flags);
goto out;
}
snd_seq_dump_var_event(ev, __dump_midi, substream);
snd_midi_event_reset_decode(msynth->parser);
} else {
if (!msynth->parser) {
err = -EIO;
goto out;
}
len = snd_midi_event_decode(msynth->parser, msg, sizeof(msg), ev);
if (len < 0)
goto out;
if (dump_midi(substream, msg, len) < 0)
snd_midi_event_reset_decode(msynth->parser);
}
out:
snd_use_lock_free(&msynth->output_use_lock);
return err;
}
static int snd_seq_midisynth_new(struct seq_midisynth *msynth,
struct snd_card *card,
int device,
int subdevice)
{
if (snd_midi_event_new(MAX_MIDI_EVENT_BUF, &msynth->parser) < 0)
return -ENOMEM;
msynth->card = card;
msynth->device = device;
msynth->subdevice = subdevice;
spin_lock_init(&msynth->output_lock);
snd_use_lock_init(&msynth->output_use_lock);
return 0;
}
/* open associated midi device for input */
static int midisynth_subscribe(void *private_data, struct snd_seq_port_subscribe *info)
{
int err;
struct seq_midisynth *msynth = private_data;
struct snd_rawmidi_runtime *runtime;
struct snd_rawmidi_params params;
/* open midi port */
err = snd_rawmidi_kernel_open(msynth->rmidi, msynth->subdevice,
SNDRV_RAWMIDI_LFLG_INPUT,
&msynth->input_rfile);
if (err < 0) {
pr_debug("ALSA: seq_midi: midi input open failed!!!\n");
return err;
}
runtime = msynth->input_rfile.input->runtime;
memset(&params, 0, sizeof(params));
params.avail_min = 1;
params.buffer_size = input_buffer_size;
err = snd_rawmidi_input_params(msynth->input_rfile.input, &params);
if (err < 0) {
snd_rawmidi_kernel_release(&msynth->input_rfile);
return err;
}
snd_midi_event_reset_encode(msynth->parser);
runtime->event = snd_midi_input_event;
runtime->private_data = msynth;
snd_rawmidi_kernel_read(msynth->input_rfile.input, NULL, 0);
return 0;
}
/* close associated midi device for input */
static int midisynth_unsubscribe(void *private_data, struct snd_seq_port_subscribe *info)
{
int err;
struct seq_midisynth *msynth = private_data;
if (snd_BUG_ON(!msynth->input_rfile.input))
return -EINVAL;
err = snd_rawmidi_kernel_release(&msynth->input_rfile);
return err;
}
/* open associated midi device for output */
static int midisynth_use(void *private_data, struct snd_seq_port_subscribe *info)
{
int err;
struct seq_midisynth *msynth = private_data;
struct snd_rawmidi_file rfile = {};
struct snd_rawmidi_params params;
/* open midi port */
err = snd_rawmidi_kernel_open(msynth->rmidi, msynth->subdevice,
SNDRV_RAWMIDI_LFLG_OUTPUT,
&rfile);
if (err < 0) {
pr_debug("ALSA: seq_midi: midi output open failed!!!\n");
return err;
}
memset(&params, 0, sizeof(params));
params.avail_min = 1;
params.buffer_size = output_buffer_size;
params.no_active_sensing = 1;
err = snd_rawmidi_output_params(rfile.output, &params);
if (err < 0) {
snd_rawmidi_kernel_release(&rfile);
return err;
}
snd_midi_event_reset_decode(msynth->parser);
scoped_guard(spinlock_irqsave, &msynth->output_lock)
msynth->output_rfile = rfile;
return 0;
}
/* close associated midi device for output */
static int midisynth_unuse(void *private_data, struct snd_seq_port_subscribe *info)
{
struct seq_midisynth *msynth = private_data;
struct snd_rawmidi_file rfile = {};
scoped_guard(spinlock_irqsave, &msynth->output_lock) {
rfile = msynth->output_rfile;
msynth->output_rfile = (struct snd_rawmidi_file){};
}
if (snd_BUG_ON(!rfile.output))
return -EINVAL;
snd_use_lock_sync(&msynth->output_use_lock);
snd_rawmidi_drain_output(rfile.output);
return snd_rawmidi_kernel_release(&rfile);
}
/* delete given midi synth port */
static void snd_seq_midisynth_delete(struct seq_midisynth *msynth)
{
if (msynth == NULL)
return;
if (msynth->seq_client > 0) {
/* delete port */
snd_seq_event_port_detach(msynth->seq_client, msynth->seq_port);
}
snd_midi_event_free(msynth->parser);
}
/* register new midi synth port */
static int
snd_seq_midisynth_probe(struct snd_seq_device *dev)
{
struct seq_midisynth_client *client;
struct seq_midisynth *msynth, *ms;
struct snd_rawmidi *rmidi = dev->private_data;
int newclient = 0;
unsigned int p, ports;
struct snd_seq_port_callback pcallbacks;
struct snd_card *card = dev->card;
int device = dev->device;
unsigned int input_count = 0, output_count = 0;
if (snd_BUG_ON(!card || device < 0 || device >= SNDRV_RAWMIDI_DEVICES))
return -EINVAL;
struct snd_rawmidi_info *info __free(kfree) =
kmalloc_obj(*info);
if (! info)
return -ENOMEM;
info->device = device;
info->stream = SNDRV_RAWMIDI_STREAM_OUTPUT;
info->subdevice = 0;
if (snd_rawmidi_info_select(card, info) >= 0)
output_count = info->subdevices_count;
info->stream = SNDRV_RAWMIDI_STREAM_INPUT;
if (snd_rawmidi_info_select(card, info) >= 0) {
input_count = info->subdevices_count;
}
ports = output_count;
if (ports < input_count)
ports = input_count;
if (ports == 0)
return -ENODEV;
if (ports > (256 / SNDRV_RAWMIDI_DEVICES))
ports = 256 / SNDRV_RAWMIDI_DEVICES;
guard(mutex)(&register_mutex);
client = synths[card->number];
if (client == NULL) {
newclient = 1;
client = kzalloc_obj(*client);
if (client == NULL)
return -ENOMEM;
client->seq_client =
snd_seq_create_kernel_client(
card, 0, "%s", card->shortname[0] ?
(const char *)card->shortname : "External MIDI");
if (client->seq_client < 0) {
kfree(client);
return -ENOMEM;
}
}
msynth = kzalloc_objs(struct seq_midisynth, ports);
struct snd_seq_port_info *port __free(kfree) =
kmalloc_obj(*port);
if (msynth == NULL || port == NULL)
goto __nomem;
for (p = 0; p < ports; p++) {
ms = &msynth[p];
ms->rmidi = rmidi;
if (snd_seq_midisynth_new(ms, card, device, p) < 0)
goto __nomem;
/* declare port */
memset(port, 0, sizeof(*port));
port->addr.client = client->seq_client;
port->addr.port = device * (256 / SNDRV_RAWMIDI_DEVICES) + p;
port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT;
memset(info, 0, sizeof(*info));
info->device = device;
if (p < output_count)
info->stream = SNDRV_RAWMIDI_STREAM_OUTPUT;
else
info->stream = SNDRV_RAWMIDI_STREAM_INPUT;
info->subdevice = p;
if (snd_rawmidi_info_select(card, info) >= 0)
strscpy(port->name, info->subname);
if (! port->name[0]) {
if (info->name[0]) {
if (ports > 1)
scnprintf(port->name, sizeof(port->name), "%s-%u", info->name, p);
else
scnprintf(port->name, sizeof(port->name), "%s", info->name);
} else {
/* last resort */
if (ports > 1)
sprintf(port->name, "MIDI %d-%d-%u", card->number, device, p);
else
sprintf(port->name, "MIDI %d-%d", card->number, device);
}
}
if ((info->flags & SNDRV_RAWMIDI_INFO_OUTPUT) && p < output_count)
port->capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SYNC_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE;
if ((info->flags & SNDRV_RAWMIDI_INFO_INPUT) && p < input_count)
port->capability |= SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SYNC_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ;
if ((port->capability & (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_READ)) == (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_READ) &&
info->flags & SNDRV_RAWMIDI_INFO_DUPLEX)
port->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;
if (port->capability & SNDRV_SEQ_PORT_CAP_READ)
port->direction |= SNDRV_SEQ_PORT_DIR_INPUT;
if (port->capability & SNDRV_SEQ_PORT_CAP_WRITE)
port->direction |= SNDRV_SEQ_PORT_DIR_OUTPUT;
port->type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC
| SNDRV_SEQ_PORT_TYPE_HARDWARE
| SNDRV_SEQ_PORT_TYPE_PORT;
port->midi_channels = 16;
memset(&pcallbacks, 0, sizeof(pcallbacks));
pcallbacks.owner = THIS_MODULE;
pcallbacks.private_data = ms;
pcallbacks.subscribe = midisynth_subscribe;
pcallbacks.unsubscribe = midisynth_unsubscribe;
pcallbacks.use = midisynth_use;
pcallbacks.unuse = midisynth_unuse;
pcallbacks.event_input = event_process_midi;
port->kernel = &pcallbacks;
if (rmidi->ops && rmidi->ops->get_port_info)
rmidi->ops->get_port_info(rmidi, p, port);
if (snd_seq_kernel_client_ctl(client->seq_client, SNDRV_SEQ_IOCTL_CREATE_PORT, port)<0)
goto __nomem;
ms->seq_client = client->seq_client;
ms->seq_port = port->addr.port;
}
client->ports_per_device[device] = ports;
client->ports[device] = msynth;
client->num_ports++;
if (newclient)
synths[card->number] = client;
return 0; /* success */
__nomem:
if (msynth != NULL) {
for (p = 0; p < ports; p++)
snd_seq_midisynth_delete(&msynth[p]);
kfree(msynth);
}
if (newclient) {
snd_seq_delete_kernel_client(client->seq_client);
kfree(client);
}
return -ENOMEM;
}
/* release midi synth port */
static void
snd_seq_midisynth_remove(struct snd_seq_device *dev)
{
struct seq_midisynth_client *client;
struct seq_midisynth *msynth;
struct snd_card *card = dev->card;
int device = dev->device, p, ports;
guard(mutex)(&register_mutex);
client = synths[card->number];
if (client == NULL || client->ports[device] == NULL)
return;
ports = client->ports_per_device[device];
client->ports_per_device[device] = 0;
msynth = client->ports[device];
client->ports[device] = NULL;
for (p = 0; p < ports; p++)
snd_seq_midisynth_delete(&msynth[p]);
kfree(msynth);
client->num_ports--;
if (client->num_ports <= 0) {
snd_seq_delete_kernel_client(client->seq_client);
synths[card->number] = NULL;
kfree(client);
}
}
static struct snd_seq_driver seq_midisynth_driver = {
.probe = snd_seq_midisynth_probe,
.remove = snd_seq_midisynth_remove,
.driver = {
.name = KBUILD_MODNAME,
},
.id = SNDRV_SEQ_DEV_ID_MIDISYNTH,
.argsize = 0,
};
module_snd_seq_driver(seq_midisynth_driver);
+707
View File
@@ -0,0 +1,707 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* GM/GS/XG midi module.
*
* Copyright (C) 1999 Steve Ratcliffe
*
* Based on awe_wave.c by Takashi Iwai
*/
/*
* This module is used to keep track of the current midi state.
* It can be used for drivers that are required to emulate midi when
* the hardware doesn't.
*
* It was written for a AWE64 driver, but there should be no AWE specific
* code in here. If there is it should be reported as a bug.
*/
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/module.h>
#include <sound/core.h>
#include <sound/seq_kernel.h>
#include <sound/seq_midi_emul.h>
#include <sound/initval.h>
#include <sound/asoundef.h>
MODULE_AUTHOR("Takashi Iwai / Steve Ratcliffe");
MODULE_DESCRIPTION("Advanced Linux Sound Architecture sequencer MIDI emulation.");
MODULE_LICENSE("GPL");
/* Prototypes for static functions */
static void note_off(const struct snd_midi_op *ops, void *drv,
struct snd_midi_channel *chan,
int note, int vel);
static void do_control(const struct snd_midi_op *ops, void *private,
struct snd_midi_channel_set *chset,
struct snd_midi_channel *chan,
int control, int value);
static void rpn(const struct snd_midi_op *ops, void *drv,
struct snd_midi_channel *chan,
struct snd_midi_channel_set *chset);
static void nrpn(const struct snd_midi_op *ops, void *drv,
struct snd_midi_channel *chan,
struct snd_midi_channel_set *chset);
static void sysex(const struct snd_midi_op *ops, void *private,
unsigned char *sysex,
int len, struct snd_midi_channel_set *chset);
static void all_sounds_off(const struct snd_midi_op *ops, void *private,
struct snd_midi_channel *chan);
static void all_notes_off(const struct snd_midi_op *ops, void *private,
struct snd_midi_channel *chan);
static void snd_midi_reset_controllers(struct snd_midi_channel *chan);
static void reset_all_channels(struct snd_midi_channel_set *chset);
/*
* Process an event in a driver independent way. This means dealing
* with RPN, NRPN, SysEx etc that are defined for common midi applications
* such as GM, GS and XG.
* There modes that this module will run in are:
* Generic MIDI - no interpretation at all, it will just save current values
* of controllers etc.
* GM - You can use all gm_ prefixed elements of chan. Controls, RPN, NRPN,
* SysEx will be interpreded as defined in General Midi.
* GS - You can use all gs_ prefixed elements of chan. Codes for GS will be
* interpreted.
* XG - You can use all xg_ prefixed elements of chan. Codes for XG will
* be interpreted.
*/
void
snd_midi_process_event(const struct snd_midi_op *ops,
struct snd_seq_event *ev,
struct snd_midi_channel_set *chanset)
{
struct snd_midi_channel *chan;
void *drv;
int dest_channel = 0;
if (ev == NULL || chanset == NULL) {
pr_debug("ALSA: seq_midi_emul: ev or chanbase NULL (snd_midi_process_event)\n");
return;
}
if (snd_seq_ev_is_channel_type(ev)) {
dest_channel = ev->data.note.channel;
if (dest_channel >= chanset->max_channels) {
pr_debug("ALSA: seq_midi_emul: dest channel is %d, max is %d\n",
dest_channel, chanset->max_channels);
return;
}
}
chan = chanset->channels + dest_channel;
drv = chanset->private_data;
/* EVENT_NOTE should be processed before queued */
if (ev->type == SNDRV_SEQ_EVENT_NOTE)
return;
/* Make sure that we don't have a note on that should really be
* a note off */
if (ev->type == SNDRV_SEQ_EVENT_NOTEON && ev->data.note.velocity == 0)
ev->type = SNDRV_SEQ_EVENT_NOTEOFF;
/* Make sure the note is within array range */
if (ev->type == SNDRV_SEQ_EVENT_NOTEON ||
ev->type == SNDRV_SEQ_EVENT_NOTEOFF ||
ev->type == SNDRV_SEQ_EVENT_KEYPRESS) {
if (ev->data.note.note >= 128)
return;
}
switch (ev->type) {
case SNDRV_SEQ_EVENT_NOTEON:
if (chan->note[ev->data.note.note] & SNDRV_MIDI_NOTE_ON) {
if (ops->note_off)
ops->note_off(drv, ev->data.note.note, 0, chan);
}
chan->note[ev->data.note.note] = SNDRV_MIDI_NOTE_ON;
if (ops->note_on)
ops->note_on(drv, ev->data.note.note, ev->data.note.velocity, chan);
break;
case SNDRV_SEQ_EVENT_NOTEOFF:
if (! (chan->note[ev->data.note.note] & SNDRV_MIDI_NOTE_ON))
break;
if (ops->note_off)
note_off(ops, drv, chan, ev->data.note.note, ev->data.note.velocity);
break;
case SNDRV_SEQ_EVENT_KEYPRESS:
if (ops->key_press)
ops->key_press(drv, ev->data.note.note, ev->data.note.velocity, chan);
break;
case SNDRV_SEQ_EVENT_CONTROLLER:
do_control(ops, drv, chanset, chan,
ev->data.control.param, ev->data.control.value);
break;
case SNDRV_SEQ_EVENT_PGMCHANGE:
chan->midi_program = ev->data.control.value;
break;
case SNDRV_SEQ_EVENT_PITCHBEND:
chan->midi_pitchbend = ev->data.control.value;
if (ops->control)
ops->control(drv, MIDI_CTL_PITCHBEND, chan);
break;
case SNDRV_SEQ_EVENT_CHANPRESS:
chan->midi_pressure = ev->data.control.value;
if (ops->control)
ops->control(drv, MIDI_CTL_CHAN_PRESSURE, chan);
break;
case SNDRV_SEQ_EVENT_CONTROL14:
/* Best guess is that this is any of the 14 bit controller values */
if (ev->data.control.param < 32) {
/* set low part first */
chan->control[ev->data.control.param + 32] =
ev->data.control.value & 0x7f;
do_control(ops, drv, chanset, chan,
ev->data.control.param,
((ev->data.control.value>>7) & 0x7f));
} else
do_control(ops, drv, chanset, chan,
ev->data.control.param,
ev->data.control.value);
break;
case SNDRV_SEQ_EVENT_NONREGPARAM:
/* Break it back into its controller values */
chan->param_type = SNDRV_MIDI_PARAM_TYPE_NONREGISTERED;
chan->control[MIDI_CTL_MSB_DATA_ENTRY]
= (ev->data.control.value >> 7) & 0x7f;
chan->control[MIDI_CTL_LSB_DATA_ENTRY]
= ev->data.control.value & 0x7f;
chan->control[MIDI_CTL_NONREG_PARM_NUM_MSB]
= (ev->data.control.param >> 7) & 0x7f;
chan->control[MIDI_CTL_NONREG_PARM_NUM_LSB]
= ev->data.control.param & 0x7f;
nrpn(ops, drv, chan, chanset);
break;
case SNDRV_SEQ_EVENT_REGPARAM:
/* Break it back into its controller values */
chan->param_type = SNDRV_MIDI_PARAM_TYPE_REGISTERED;
chan->control[MIDI_CTL_MSB_DATA_ENTRY]
= (ev->data.control.value >> 7) & 0x7f;
chan->control[MIDI_CTL_LSB_DATA_ENTRY]
= ev->data.control.value & 0x7f;
chan->control[MIDI_CTL_REGIST_PARM_NUM_MSB]
= (ev->data.control.param >> 7) & 0x7f;
chan->control[MIDI_CTL_REGIST_PARM_NUM_LSB]
= ev->data.control.param & 0x7f;
rpn(ops, drv, chan, chanset);
break;
case SNDRV_SEQ_EVENT_SYSEX:
if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) == SNDRV_SEQ_EVENT_LENGTH_VARIABLE) {
unsigned char sysexbuf[64];
int len;
len = snd_seq_expand_var_event(ev, sizeof(sysexbuf), sysexbuf, 1, 0);
if (len > 0)
sysex(ops, drv, sysexbuf, len, chanset);
}
break;
case SNDRV_SEQ_EVENT_SONGPOS:
case SNDRV_SEQ_EVENT_SONGSEL:
case SNDRV_SEQ_EVENT_CLOCK:
case SNDRV_SEQ_EVENT_START:
case SNDRV_SEQ_EVENT_CONTINUE:
case SNDRV_SEQ_EVENT_STOP:
case SNDRV_SEQ_EVENT_QFRAME:
case SNDRV_SEQ_EVENT_TEMPO:
case SNDRV_SEQ_EVENT_TIMESIGN:
case SNDRV_SEQ_EVENT_KEYSIGN:
goto not_yet;
case SNDRV_SEQ_EVENT_SENSING:
break;
case SNDRV_SEQ_EVENT_CLIENT_START:
case SNDRV_SEQ_EVENT_CLIENT_EXIT:
case SNDRV_SEQ_EVENT_CLIENT_CHANGE:
case SNDRV_SEQ_EVENT_PORT_START:
case SNDRV_SEQ_EVENT_PORT_EXIT:
case SNDRV_SEQ_EVENT_PORT_CHANGE:
case SNDRV_SEQ_EVENT_ECHO:
not_yet:
default:
/*pr_debug("ALSA: seq_midi_emul: Unimplemented event %d\n", ev->type);*/
break;
}
}
EXPORT_SYMBOL(snd_midi_process_event);
/*
* release note
*/
static void
note_off(const struct snd_midi_op *ops, void *drv,
struct snd_midi_channel *chan,
int note, int vel)
{
if (chan->gm_hold) {
/* Hold this note until pedal is turned off */
chan->note[note] |= SNDRV_MIDI_NOTE_RELEASED;
} else if (chan->note[note] & SNDRV_MIDI_NOTE_SOSTENUTO) {
/* Mark this note as release; it will be turned off when sostenuto
* is turned off */
chan->note[note] |= SNDRV_MIDI_NOTE_RELEASED;
} else {
chan->note[note] = 0;
if (ops->note_off)
ops->note_off(drv, note, vel, chan);
}
}
/*
* Do all driver independent operations for this controller and pass
* events that need to take place immediately to the driver.
*/
static void
do_control(const struct snd_midi_op *ops, void *drv,
struct snd_midi_channel_set *chset,
struct snd_midi_channel *chan, int control, int value)
{
int i;
if (control >= ARRAY_SIZE(chan->control))
return;
/* Switches */
if ((control >=64 && control <=69) || (control >= 80 && control <= 83)) {
/* These are all switches; either off or on so set to 0 or 127 */
value = (value >= 64)? 127: 0;
}
chan->control[control] = value;
switch (control) {
case MIDI_CTL_SUSTAIN:
if (value == 0) {
/* Sustain has been released, turn off held notes */
for (i = 0; i < 128; i++) {
if (chan->note[i] & SNDRV_MIDI_NOTE_RELEASED) {
chan->note[i] = SNDRV_MIDI_NOTE_OFF;
if (ops->note_off)
ops->note_off(drv, i, 0, chan);
}
}
}
break;
case MIDI_CTL_PORTAMENTO:
break;
case MIDI_CTL_SOSTENUTO:
if (value) {
/* Mark each note that is currently held down */
for (i = 0; i < 128; i++) {
if (chan->note[i] & SNDRV_MIDI_NOTE_ON)
chan->note[i] |= SNDRV_MIDI_NOTE_SOSTENUTO;
}
} else {
/* release all notes that were held */
for (i = 0; i < 128; i++) {
if (chan->note[i] & SNDRV_MIDI_NOTE_SOSTENUTO) {
chan->note[i] &= ~SNDRV_MIDI_NOTE_SOSTENUTO;
if (chan->note[i] & SNDRV_MIDI_NOTE_RELEASED) {
chan->note[i] = SNDRV_MIDI_NOTE_OFF;
if (ops->note_off)
ops->note_off(drv, i, 0, chan);
}
}
}
}
break;
case MIDI_CTL_MSB_DATA_ENTRY:
chan->control[MIDI_CTL_LSB_DATA_ENTRY] = 0;
fallthrough;
case MIDI_CTL_LSB_DATA_ENTRY:
if (chan->param_type == SNDRV_MIDI_PARAM_TYPE_REGISTERED)
rpn(ops, drv, chan, chset);
else
nrpn(ops, drv, chan, chset);
break;
case MIDI_CTL_REGIST_PARM_NUM_LSB:
case MIDI_CTL_REGIST_PARM_NUM_MSB:
chan->param_type = SNDRV_MIDI_PARAM_TYPE_REGISTERED;
break;
case MIDI_CTL_NONREG_PARM_NUM_LSB:
case MIDI_CTL_NONREG_PARM_NUM_MSB:
chan->param_type = SNDRV_MIDI_PARAM_TYPE_NONREGISTERED;
break;
case MIDI_CTL_ALL_SOUNDS_OFF:
all_sounds_off(ops, drv, chan);
break;
case MIDI_CTL_ALL_NOTES_OFF:
all_notes_off(ops, drv, chan);
break;
case MIDI_CTL_MSB_BANK:
if (chset->midi_mode == SNDRV_MIDI_MODE_XG) {
if (value == 127)
chan->drum_channel = 1;
else
chan->drum_channel = 0;
}
break;
case MIDI_CTL_LSB_BANK:
break;
case MIDI_CTL_RESET_CONTROLLERS:
snd_midi_reset_controllers(chan);
break;
case MIDI_CTL_SOFT_PEDAL:
case MIDI_CTL_LEGATO_FOOTSWITCH:
case MIDI_CTL_HOLD2:
case MIDI_CTL_SC1_SOUND_VARIATION:
case MIDI_CTL_SC2_TIMBRE:
case MIDI_CTL_SC3_RELEASE_TIME:
case MIDI_CTL_SC4_ATTACK_TIME:
case MIDI_CTL_SC5_BRIGHTNESS:
case MIDI_CTL_E1_REVERB_DEPTH:
case MIDI_CTL_E2_TREMOLO_DEPTH:
case MIDI_CTL_E3_CHORUS_DEPTH:
case MIDI_CTL_E4_DETUNE_DEPTH:
case MIDI_CTL_E5_PHASER_DEPTH:
goto notyet;
notyet:
default:
if (ops->control)
ops->control(drv, control, chan);
break;
}
}
/*
* initialize the MIDI status
*/
void
snd_midi_channel_set_clear(struct snd_midi_channel_set *chset)
{
int i;
chset->midi_mode = SNDRV_MIDI_MODE_GM;
chset->gs_master_volume = 127;
for (i = 0; i < chset->max_channels; i++) {
struct snd_midi_channel *chan = chset->channels + i;
memset(chan->note, 0, sizeof(chan->note));
chan->midi_aftertouch = 0;
chan->midi_pressure = 0;
chan->midi_program = 0;
chan->midi_pitchbend = 0;
snd_midi_reset_controllers(chan);
chan->gm_rpn_pitch_bend_range = 256; /* 2 semitones */
chan->gm_rpn_fine_tuning = 0;
chan->gm_rpn_coarse_tuning = 0;
if (i == 9)
chan->drum_channel = 1;
else
chan->drum_channel = 0;
}
}
EXPORT_SYMBOL(snd_midi_channel_set_clear);
/*
* Process a rpn message.
*/
static void
rpn(const struct snd_midi_op *ops, void *drv, struct snd_midi_channel *chan,
struct snd_midi_channel_set *chset)
{
int type;
int val;
if (chset->midi_mode != SNDRV_MIDI_MODE_NONE) {
type = (chan->control[MIDI_CTL_REGIST_PARM_NUM_MSB] << 8) |
chan->control[MIDI_CTL_REGIST_PARM_NUM_LSB];
val = (chan->control[MIDI_CTL_MSB_DATA_ENTRY] << 7) |
chan->control[MIDI_CTL_LSB_DATA_ENTRY];
switch (type) {
case 0x0000: /* Pitch bend sensitivity */
/* MSB only / 1 semitone per 128 */
chan->gm_rpn_pitch_bend_range = val;
break;
case 0x0001: /* fine tuning: */
/* MSB/LSB, 8192=center, 100/8192 cent step */
chan->gm_rpn_fine_tuning = val - 8192;
break;
case 0x0002: /* coarse tuning */
/* MSB only / 8192=center, 1 semitone per 128 */
chan->gm_rpn_coarse_tuning = val - 8192;
break;
case 0x7F7F: /* "lock-in" RPN */
/* ignored */
break;
}
}
/* should call nrpn or rpn callback here.. */
}
/*
* Process an nrpn message.
*/
static void
nrpn(const struct snd_midi_op *ops, void *drv, struct snd_midi_channel *chan,
struct snd_midi_channel_set *chset)
{
/* parse XG NRPNs here if possible */
if (ops->nrpn)
ops->nrpn(drv, chan, chset);
}
/*
* convert channel parameter in GS sysex
*/
static int
get_channel(unsigned char cmd)
{
int p = cmd & 0x0f;
if (p == 0)
p = 9;
else if (p < 10)
p--;
return p;
}
/*
* Process a sysex message.
*/
static void
sysex(const struct snd_midi_op *ops, void *private, unsigned char *buf, int len,
struct snd_midi_channel_set *chset)
{
/* GM on */
static const unsigned char gm_on_macro[] = {
0x7e,0x7f,0x09,0x01,
};
/* XG on */
static const unsigned char xg_on_macro[] = {
0x43,0x10,0x4c,0x00,0x00,0x7e,0x00,
};
/* GS prefix
* drum channel: XX=0x1?(channel), YY=0x15, ZZ=on/off
* reverb mode: XX=0x01, YY=0x30, ZZ=0-7
* chorus mode: XX=0x01, YY=0x38, ZZ=0-7
* master vol: XX=0x00, YY=0x04, ZZ=0-127
*/
static const unsigned char gs_pfx_macro[] = {
0x41,0x10,0x42,0x12,0x40,/*XX,YY,ZZ*/
};
int parsed = SNDRV_MIDI_SYSEX_NOT_PARSED;
if (len <= 0 || buf[0] != 0xf0)
return;
/* skip first byte */
buf++;
len--;
/* GM on */
if (len >= (int)sizeof(gm_on_macro) &&
memcmp(buf, gm_on_macro, sizeof(gm_on_macro)) == 0) {
if (chset->midi_mode != SNDRV_MIDI_MODE_GS &&
chset->midi_mode != SNDRV_MIDI_MODE_XG) {
chset->midi_mode = SNDRV_MIDI_MODE_GM;
reset_all_channels(chset);
parsed = SNDRV_MIDI_SYSEX_GM_ON;
}
}
/* GS macros */
else if (len >= 8 &&
memcmp(buf, gs_pfx_macro, sizeof(gs_pfx_macro)) == 0) {
if (chset->midi_mode != SNDRV_MIDI_MODE_GS &&
chset->midi_mode != SNDRV_MIDI_MODE_XG)
chset->midi_mode = SNDRV_MIDI_MODE_GS;
if (buf[5] == 0x00 && buf[6] == 0x7f && buf[7] == 0x00) {
/* GS reset */
parsed = SNDRV_MIDI_SYSEX_GS_RESET;
reset_all_channels(chset);
}
else if ((buf[5] & 0xf0) == 0x10 && buf[6] == 0x15) {
/* drum pattern */
int p = get_channel(buf[5]);
if (p < chset->max_channels) {
parsed = SNDRV_MIDI_SYSEX_GS_DRUM_CHANNEL;
if (buf[7])
chset->channels[p].drum_channel = 1;
else
chset->channels[p].drum_channel = 0;
}
} else if ((buf[5] & 0xf0) == 0x10 && buf[6] == 0x21) {
/* program */
int p = get_channel(buf[5]);
if (p < chset->max_channels &&
! chset->channels[p].drum_channel) {
parsed = SNDRV_MIDI_SYSEX_GS_DRUM_CHANNEL;
chset->channels[p].midi_program = buf[7];
}
} else if (buf[5] == 0x01 && buf[6] == 0x30) {
/* reverb mode */
parsed = SNDRV_MIDI_SYSEX_GS_REVERB_MODE;
chset->gs_reverb_mode = buf[7];
} else if (buf[5] == 0x01 && buf[6] == 0x38) {
/* chorus mode */
parsed = SNDRV_MIDI_SYSEX_GS_CHORUS_MODE;
chset->gs_chorus_mode = buf[7];
} else if (buf[5] == 0x00 && buf[6] == 0x04) {
/* master volume */
parsed = SNDRV_MIDI_SYSEX_GS_MASTER_VOLUME;
chset->gs_master_volume = buf[7];
}
}
/* XG on */
else if (len >= (int)sizeof(xg_on_macro) &&
memcmp(buf, xg_on_macro, sizeof(xg_on_macro)) == 0) {
int i;
chset->midi_mode = SNDRV_MIDI_MODE_XG;
parsed = SNDRV_MIDI_SYSEX_XG_ON;
/* reset CC#0 for drums */
for (i = 0; i < chset->max_channels; i++) {
if (chset->channels[i].drum_channel)
chset->channels[i].control[MIDI_CTL_MSB_BANK] = 127;
else
chset->channels[i].control[MIDI_CTL_MSB_BANK] = 0;
}
}
if (ops->sysex)
ops->sysex(private, buf - 1, len + 1, parsed, chset);
}
/*
* all sound off
*/
static void
all_sounds_off(const struct snd_midi_op *ops, void *drv,
struct snd_midi_channel *chan)
{
int n;
if (! ops->note_terminate)
return;
for (n = 0; n < 128; n++) {
if (chan->note[n]) {
ops->note_terminate(drv, n, chan);
chan->note[n] = 0;
}
}
}
/*
* all notes off
*/
static void
all_notes_off(const struct snd_midi_op *ops, void *drv,
struct snd_midi_channel *chan)
{
int n;
if (! ops->note_off)
return;
for (n = 0; n < 128; n++) {
if (chan->note[n] == SNDRV_MIDI_NOTE_ON)
note_off(ops, drv, chan, n, 0);
}
}
/*
* Initialise a single midi channel control block.
*/
static void snd_midi_channel_init(struct snd_midi_channel *p, int n)
{
if (p == NULL)
return;
memset(p, 0, sizeof(struct snd_midi_channel));
p->private = NULL;
p->number = n;
snd_midi_reset_controllers(p);
p->gm_rpn_pitch_bend_range = 256; /* 2 semitones */
p->gm_rpn_fine_tuning = 0;
p->gm_rpn_coarse_tuning = 0;
if (n == 9)
p->drum_channel = 1; /* Default ch 10 as drums */
}
/*
* reset all midi channels
*/
static void
reset_all_channels(struct snd_midi_channel_set *chset)
{
int ch;
for (ch = 0; ch < chset->max_channels; ch++) {
struct snd_midi_channel *chan = chset->channels + ch;
snd_midi_reset_controllers(chan);
chan->gm_rpn_pitch_bend_range = 256; /* 2 semitones */
chan->gm_rpn_fine_tuning = 0;
chan->gm_rpn_coarse_tuning = 0;
if (ch == 9)
chan->drum_channel = 1;
else
chan->drum_channel = 0;
}
}
/*
* Allocate and initialise a midi channel set.
*/
struct snd_midi_channel_set *snd_midi_channel_alloc_set(int n)
{
struct snd_midi_channel_set *chset;
int i;
chset = kmalloc_flex(*chset, channels, n);
if (!chset)
return NULL;
chset->max_channels = n;
chset->private_data = NULL;
for (i = 0; i < n; i++)
snd_midi_channel_init(&chset->channels[i], i);
return chset;
}
EXPORT_SYMBOL(snd_midi_channel_alloc_set);
/*
* Reset the midi controllers on a particular channel to default values.
*/
static void snd_midi_reset_controllers(struct snd_midi_channel *chan)
{
memset(chan->control, 0, sizeof(chan->control));
chan->gm_volume = 127;
chan->gm_expression = 127;
chan->gm_pan = 64;
}
/*
* Free a midi channel set.
*/
void snd_midi_channel_free_set(struct snd_midi_channel_set *chset)
{
if (chset == NULL)
return;
kfree(chset);
}
EXPORT_SYMBOL(snd_midi_channel_free_set);
+451
View File
@@ -0,0 +1,451 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* MIDI byte <-> sequencer event coder
*
* Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>,
* Jaroslav Kysela <perex@perex.cz>
*/
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/module.h>
#include <sound/core.h>
#include <sound/seq_kernel.h>
#include <sound/seq_midi_event.h>
#include <sound/asoundef.h>
MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>, Jaroslav Kysela <perex@perex.cz>");
MODULE_DESCRIPTION("MIDI byte <-> sequencer event coder");
MODULE_LICENSE("GPL");
/* event type, index into status_event[] */
/* from 0 to 6 are normal commands (note off, on, etc.) for 0x9?-0xe? */
#define ST_INVALID 7
#define ST_SPECIAL 8
#define ST_SYSEX ST_SPECIAL
/* from 8 to 15 are events for 0xf0-0xf7 */
/*
* prototypes
*/
static void note_event(struct snd_midi_event *dev, struct snd_seq_event *ev);
static void one_param_ctrl_event(struct snd_midi_event *dev, struct snd_seq_event *ev);
static void pitchbend_ctrl_event(struct snd_midi_event *dev, struct snd_seq_event *ev);
static void two_param_ctrl_event(struct snd_midi_event *dev, struct snd_seq_event *ev);
static void one_param_event(struct snd_midi_event *dev, struct snd_seq_event *ev);
static void songpos_event(struct snd_midi_event *dev, struct snd_seq_event *ev);
static void note_decode(struct snd_seq_event *ev, unsigned char *buf);
static void one_param_decode(struct snd_seq_event *ev, unsigned char *buf);
static void pitchbend_decode(struct snd_seq_event *ev, unsigned char *buf);
static void two_param_decode(struct snd_seq_event *ev, unsigned char *buf);
static void songpos_decode(struct snd_seq_event *ev, unsigned char *buf);
/*
* event list
*/
static struct status_event_list {
int event;
int qlen;
void (*encode)(struct snd_midi_event *dev, struct snd_seq_event *ev);
void (*decode)(struct snd_seq_event *ev, unsigned char *buf);
} status_event[] = {
/* 0x80 - 0xef */
{SNDRV_SEQ_EVENT_NOTEOFF, 2, note_event, note_decode},
{SNDRV_SEQ_EVENT_NOTEON, 2, note_event, note_decode},
{SNDRV_SEQ_EVENT_KEYPRESS, 2, note_event, note_decode},
{SNDRV_SEQ_EVENT_CONTROLLER, 2, two_param_ctrl_event, two_param_decode},
{SNDRV_SEQ_EVENT_PGMCHANGE, 1, one_param_ctrl_event, one_param_decode},
{SNDRV_SEQ_EVENT_CHANPRESS, 1, one_param_ctrl_event, one_param_decode},
{SNDRV_SEQ_EVENT_PITCHBEND, 2, pitchbend_ctrl_event, pitchbend_decode},
/* invalid */
{SNDRV_SEQ_EVENT_NONE, -1, NULL, NULL},
/* 0xf0 - 0xff */
{SNDRV_SEQ_EVENT_SYSEX, 1, NULL, NULL}, /* sysex: 0xf0 */
{SNDRV_SEQ_EVENT_QFRAME, 1, one_param_event, one_param_decode}, /* 0xf1 */
{SNDRV_SEQ_EVENT_SONGPOS, 2, songpos_event, songpos_decode}, /* 0xf2 */
{SNDRV_SEQ_EVENT_SONGSEL, 1, one_param_event, one_param_decode}, /* 0xf3 */
{SNDRV_SEQ_EVENT_NONE, -1, NULL, NULL}, /* 0xf4 */
{SNDRV_SEQ_EVENT_NONE, -1, NULL, NULL}, /* 0xf5 */
{SNDRV_SEQ_EVENT_TUNE_REQUEST, 0, NULL, NULL}, /* 0xf6 */
{SNDRV_SEQ_EVENT_NONE, -1, NULL, NULL}, /* 0xf7 */
{SNDRV_SEQ_EVENT_CLOCK, 0, NULL, NULL}, /* 0xf8 */
{SNDRV_SEQ_EVENT_NONE, -1, NULL, NULL}, /* 0xf9 */
{SNDRV_SEQ_EVENT_START, 0, NULL, NULL}, /* 0xfa */
{SNDRV_SEQ_EVENT_CONTINUE, 0, NULL, NULL}, /* 0xfb */
{SNDRV_SEQ_EVENT_STOP, 0, NULL, NULL}, /* 0xfc */
{SNDRV_SEQ_EVENT_NONE, -1, NULL, NULL}, /* 0xfd */
{SNDRV_SEQ_EVENT_SENSING, 0, NULL, NULL}, /* 0xfe */
{SNDRV_SEQ_EVENT_RESET, 0, NULL, NULL}, /* 0xff */
};
static int extra_decode_ctrl14(struct snd_midi_event *dev, unsigned char *buf, int len,
struct snd_seq_event *ev);
static int extra_decode_xrpn(struct snd_midi_event *dev, unsigned char *buf, int count,
struct snd_seq_event *ev);
static struct extra_event_list {
int event;
int (*decode)(struct snd_midi_event *dev, unsigned char *buf, int len,
struct snd_seq_event *ev);
} extra_event[] = {
{SNDRV_SEQ_EVENT_CONTROL14, extra_decode_ctrl14},
{SNDRV_SEQ_EVENT_NONREGPARAM, extra_decode_xrpn},
{SNDRV_SEQ_EVENT_REGPARAM, extra_decode_xrpn},
};
/*
* new/delete record
*/
int snd_midi_event_new(int bufsize, struct snd_midi_event **rdev)
{
struct snd_midi_event *dev;
*rdev = NULL;
dev = kzalloc_obj(*dev);
if (dev == NULL)
return -ENOMEM;
if (bufsize > 0) {
dev->buf = kmalloc(bufsize, GFP_KERNEL);
if (dev->buf == NULL) {
kfree(dev);
return -ENOMEM;
}
}
dev->bufsize = bufsize;
dev->lastcmd = 0xff;
dev->type = ST_INVALID;
spin_lock_init(&dev->lock);
*rdev = dev;
return 0;
}
EXPORT_SYMBOL(snd_midi_event_new);
void snd_midi_event_free(struct snd_midi_event *dev)
{
if (dev != NULL) {
kfree(dev->buf);
kfree(dev);
}
}
EXPORT_SYMBOL(snd_midi_event_free);
/*
* initialize record
*/
static inline void reset_encode(struct snd_midi_event *dev)
{
dev->read = 0;
dev->qlen = 0;
dev->type = ST_INVALID;
}
void snd_midi_event_reset_encode(struct snd_midi_event *dev)
{
guard(spinlock_irqsave)(&dev->lock);
reset_encode(dev);
}
EXPORT_SYMBOL(snd_midi_event_reset_encode);
void snd_midi_event_reset_decode(struct snd_midi_event *dev)
{
guard(spinlock_irqsave)(&dev->lock);
dev->lastcmd = 0xff;
}
EXPORT_SYMBOL(snd_midi_event_reset_decode);
void snd_midi_event_no_status(struct snd_midi_event *dev, int on)
{
dev->nostat = on ? 1 : 0;
}
EXPORT_SYMBOL(snd_midi_event_no_status);
/*
* read one byte and encode to sequencer event:
* return true if MIDI bytes are encoded to an event
* false data is not finished
*/
bool snd_midi_event_encode_byte(struct snd_midi_event *dev, unsigned char c,
struct snd_seq_event *ev)
{
bool rc = false;
if (c >= MIDI_CMD_COMMON_CLOCK) {
/* real-time event */
ev->type = status_event[ST_SPECIAL + c - 0xf0].event;
ev->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK;
ev->flags |= SNDRV_SEQ_EVENT_LENGTH_FIXED;
return ev->type != SNDRV_SEQ_EVENT_NONE;
}
guard(spinlock_irqsave)(&dev->lock);
if ((c & 0x80) &&
(c != MIDI_CMD_COMMON_SYSEX_END || dev->type != ST_SYSEX)) {
/* new command */
dev->buf[0] = c;
if ((c & 0xf0) == 0xf0) /* system messages */
dev->type = (c & 0x0f) + ST_SPECIAL;
else
dev->type = (c >> 4) & 0x07;
dev->read = 1;
dev->qlen = status_event[dev->type].qlen;
} else {
if (dev->qlen > 0) {
/* rest of command */
dev->buf[dev->read++] = c;
if (dev->type != ST_SYSEX)
dev->qlen--;
} else {
/* running status */
dev->buf[1] = c;
dev->qlen = status_event[dev->type].qlen - 1;
dev->read = 2;
}
}
if (dev->qlen == 0) {
ev->type = status_event[dev->type].event;
ev->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK;
ev->flags |= SNDRV_SEQ_EVENT_LENGTH_FIXED;
if (status_event[dev->type].encode) /* set data values */
status_event[dev->type].encode(dev, ev);
if (dev->type >= ST_SPECIAL)
dev->type = ST_INVALID;
rc = true;
} else if (dev->type == ST_SYSEX) {
if (c == MIDI_CMD_COMMON_SYSEX_END ||
dev->read >= dev->bufsize) {
ev->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK;
ev->flags |= SNDRV_SEQ_EVENT_LENGTH_VARIABLE;
ev->type = SNDRV_SEQ_EVENT_SYSEX;
ev->data.ext.len = dev->read;
ev->data.ext.ptr = dev->buf;
if (c != MIDI_CMD_COMMON_SYSEX_END)
dev->read = 0; /* continue to parse */
else
reset_encode(dev); /* all parsed */
rc = true;
}
}
return rc;
}
EXPORT_SYMBOL(snd_midi_event_encode_byte);
/* encode note event */
static void note_event(struct snd_midi_event *dev, struct snd_seq_event *ev)
{
ev->data.note.channel = dev->buf[0] & 0x0f;
ev->data.note.note = dev->buf[1];
ev->data.note.velocity = dev->buf[2];
}
/* encode one parameter controls */
static void one_param_ctrl_event(struct snd_midi_event *dev, struct snd_seq_event *ev)
{
ev->data.control.channel = dev->buf[0] & 0x0f;
ev->data.control.value = dev->buf[1];
}
/* encode pitch wheel change */
static void pitchbend_ctrl_event(struct snd_midi_event *dev, struct snd_seq_event *ev)
{
ev->data.control.channel = dev->buf[0] & 0x0f;
ev->data.control.value = (int)dev->buf[2] * 128 + (int)dev->buf[1] - 8192;
}
/* encode midi control change */
static void two_param_ctrl_event(struct snd_midi_event *dev, struct snd_seq_event *ev)
{
ev->data.control.channel = dev->buf[0] & 0x0f;
ev->data.control.param = dev->buf[1];
ev->data.control.value = dev->buf[2];
}
/* encode one parameter value*/
static void one_param_event(struct snd_midi_event *dev, struct snd_seq_event *ev)
{
ev->data.control.value = dev->buf[1];
}
/* encode song position */
static void songpos_event(struct snd_midi_event *dev, struct snd_seq_event *ev)
{
ev->data.control.value = (int)dev->buf[2] * 128 + (int)dev->buf[1];
}
/*
* decode from a sequencer event to midi bytes
* return the size of decoded midi events
*/
long snd_midi_event_decode(struct snd_midi_event *dev, unsigned char *buf, long count,
struct snd_seq_event *ev)
{
unsigned int cmd, type;
if (ev->type == SNDRV_SEQ_EVENT_NONE)
return -ENOENT;
for (type = 0; type < ARRAY_SIZE(status_event); type++) {
if (ev->type == status_event[type].event)
goto __found;
}
for (type = 0; type < ARRAY_SIZE(extra_event); type++) {
if (ev->type == extra_event[type].event)
return extra_event[type].decode(dev, buf, count, ev);
}
return -ENOENT;
__found:
if (type >= ST_SPECIAL)
cmd = 0xf0 + (type - ST_SPECIAL);
else
/* data.note.channel and data.control.channel is identical */
cmd = 0x80 | (type << 4) | (ev->data.note.channel & 0x0f);
if (cmd == MIDI_CMD_COMMON_SYSEX) {
snd_midi_event_reset_decode(dev);
return snd_seq_expand_var_event(ev, count, buf, 1, 0);
} else {
int qlen;
unsigned char xbuf[4];
unsigned long flags;
spin_lock_irqsave(&dev->lock, flags);
if ((cmd & 0xf0) == 0xf0 || dev->lastcmd != cmd || dev->nostat) {
dev->lastcmd = cmd;
spin_unlock_irqrestore(&dev->lock, flags);
xbuf[0] = cmd;
if (status_event[type].decode)
status_event[type].decode(ev, xbuf + 1);
qlen = status_event[type].qlen + 1;
} else {
spin_unlock_irqrestore(&dev->lock, flags);
if (status_event[type].decode)
status_event[type].decode(ev, xbuf + 0);
qlen = status_event[type].qlen;
}
if (count < qlen)
return -ENOMEM;
memcpy(buf, xbuf, qlen);
return qlen;
}
}
EXPORT_SYMBOL(snd_midi_event_decode);
/* decode note event */
static void note_decode(struct snd_seq_event *ev, unsigned char *buf)
{
buf[0] = ev->data.note.note & 0x7f;
buf[1] = ev->data.note.velocity & 0x7f;
}
/* decode one parameter controls */
static void one_param_decode(struct snd_seq_event *ev, unsigned char *buf)
{
buf[0] = ev->data.control.value & 0x7f;
}
/* decode pitch wheel change */
static void pitchbend_decode(struct snd_seq_event *ev, unsigned char *buf)
{
int value = ev->data.control.value + 8192;
buf[0] = value & 0x7f;
buf[1] = (value >> 7) & 0x7f;
}
/* decode midi control change */
static void two_param_decode(struct snd_seq_event *ev, unsigned char *buf)
{
buf[0] = ev->data.control.param & 0x7f;
buf[1] = ev->data.control.value & 0x7f;
}
/* decode song position */
static void songpos_decode(struct snd_seq_event *ev, unsigned char *buf)
{
buf[0] = ev->data.control.value & 0x7f;
buf[1] = (ev->data.control.value >> 7) & 0x7f;
}
/* decode 14bit control */
static int extra_decode_ctrl14(struct snd_midi_event *dev, unsigned char *buf,
int count, struct snd_seq_event *ev)
{
unsigned char cmd;
int idx = 0;
cmd = MIDI_CMD_CONTROL|(ev->data.control.channel & 0x0f);
if (ev->data.control.param < 0x20) {
if (count < 4)
return -ENOMEM;
if (dev->nostat && count < 6)
return -ENOMEM;
if (cmd != dev->lastcmd || dev->nostat) {
if (count < 5)
return -ENOMEM;
buf[idx++] = dev->lastcmd = cmd;
}
buf[idx++] = ev->data.control.param;
buf[idx++] = (ev->data.control.value >> 7) & 0x7f;
if (dev->nostat)
buf[idx++] = cmd;
buf[idx++] = ev->data.control.param + 0x20;
buf[idx++] = ev->data.control.value & 0x7f;
} else {
if (count < 2)
return -ENOMEM;
if (cmd != dev->lastcmd || dev->nostat) {
if (count < 3)
return -ENOMEM;
buf[idx++] = dev->lastcmd = cmd;
}
buf[idx++] = ev->data.control.param & 0x7f;
buf[idx++] = ev->data.control.value & 0x7f;
}
return idx;
}
/* decode reg/nonreg param */
static int extra_decode_xrpn(struct snd_midi_event *dev, unsigned char *buf,
int count, struct snd_seq_event *ev)
{
unsigned char cmd;
const char *cbytes;
static const char cbytes_nrpn[4] = { MIDI_CTL_NONREG_PARM_NUM_MSB,
MIDI_CTL_NONREG_PARM_NUM_LSB,
MIDI_CTL_MSB_DATA_ENTRY,
MIDI_CTL_LSB_DATA_ENTRY };
static const char cbytes_rpn[4] = { MIDI_CTL_REGIST_PARM_NUM_MSB,
MIDI_CTL_REGIST_PARM_NUM_LSB,
MIDI_CTL_MSB_DATA_ENTRY,
MIDI_CTL_LSB_DATA_ENTRY };
unsigned char bytes[4];
int idx = 0, i;
if (count < 8)
return -ENOMEM;
if (dev->nostat && count < 12)
return -ENOMEM;
cmd = MIDI_CMD_CONTROL|(ev->data.control.channel & 0x0f);
bytes[0] = (ev->data.control.param & 0x3f80) >> 7;
bytes[1] = ev->data.control.param & 0x007f;
bytes[2] = (ev->data.control.value & 0x3f80) >> 7;
bytes[3] = ev->data.control.value & 0x007f;
if (cmd != dev->lastcmd && !dev->nostat) {
if (count < 9)
return -ENOMEM;
buf[idx++] = dev->lastcmd = cmd;
}
cbytes = ev->type == SNDRV_SEQ_EVENT_NONREGPARAM ? cbytes_nrpn : cbytes_rpn;
for (i = 0; i < 4; i++) {
if (dev->nostat)
buf[idx++] = dev->lastcmd = cmd;
buf[idx++] = cbytes[i];
buf[idx++] = bytes[i];
}
return idx;
}
+732
View File
@@ -0,0 +1,732 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* ALSA sequencer Ports
* Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
* Jaroslav Kysela <perex@perex.cz>
*/
#include <sound/core.h>
#include <linux/slab.h>
#include <linux/module.h>
#include "seq_system.h"
#include "seq_ports.h"
#include "seq_clientmgr.h"
/*
registration of client ports
*/
/*
NOTE: the current implementation of the port structure as a linked list is
not optimal for clients that have many ports. For sending messages to all
subscribers of a port we first need to find the address of the port
structure, which means we have to traverse the list. A direct access table
(array) would be better, but big preallocated arrays waste memory.
Possible actions:
1) leave it this way, a client does normaly does not have more than a few
ports
2) replace the linked list of ports by a array of pointers which is
dynamicly kmalloced. When a port is added or deleted we can simply allocate
a new array, copy the corresponding pointers, and delete the old one. We
then only need a pointer to this array, and an integer that tells us how
much elements are in array.
*/
/* return pointer to port structure - port is locked if found */
struct snd_seq_client_port *snd_seq_port_use_ptr(struct snd_seq_client *client,
int num)
{
struct snd_seq_client_port *port;
if (client == NULL)
return NULL;
guard(read_lock)(&client->ports_lock);
list_for_each_entry(port, &client->ports_list_head, list) {
if (port->addr.port == num) {
if (port->closing)
break; /* deleting now */
snd_use_lock_use(&port->use_lock);
return port;
}
}
return NULL; /* not found */
}
/* search for the next port - port is locked if found */
struct snd_seq_client_port *snd_seq_port_query_nearest(struct snd_seq_client *client,
struct snd_seq_port_info *pinfo)
{
int num;
struct snd_seq_client_port *port, *found;
bool check_inactive = (pinfo->capability & SNDRV_SEQ_PORT_CAP_INACTIVE);
num = pinfo->addr.port;
found = NULL;
guard(read_lock)(&client->ports_lock);
list_for_each_entry(port, &client->ports_list_head, list) {
if ((port->capability & SNDRV_SEQ_PORT_CAP_INACTIVE) &&
!check_inactive)
continue; /* skip inactive ports */
if (port->addr.port < num)
continue;
if (port->addr.port == num) {
found = port;
break;
}
if (found == NULL || port->addr.port < found->addr.port)
found = port;
}
if (found) {
if (found->closing)
found = NULL;
else
snd_use_lock_use(&found->use_lock);
}
return found;
}
/* initialize snd_seq_port_subs_info */
static void port_subs_info_init(struct snd_seq_port_subs_info *grp)
{
INIT_LIST_HEAD(&grp->list_head);
grp->count = 0;
grp->exclusive = 0;
rwlock_init(&grp->list_lock);
init_rwsem(&grp->list_mutex);
grp->open = NULL;
grp->close = NULL;
}
/* create a port, 0 on success or a negative error code is returned
* the caller needs to unref the port via snd_seq_port_unlock() appropriately
*/
int snd_seq_create_port(struct snd_seq_client *client,
struct snd_seq_client_port **port_ret)
{
struct snd_seq_client_port *new_port;
*port_ret = NULL;
/* sanity check */
if (snd_BUG_ON(!client))
return -EINVAL;
if (client->num_ports >= SNDRV_SEQ_MAX_PORTS) {
pr_warn("ALSA: seq: too many ports for client %d\n", client->number);
return -EINVAL;
}
/* create a new port */
new_port = kzalloc_obj(*new_port);
if (!new_port)
return -ENOMEM; /* failure, out of memory */
/* init port data */
new_port->addr.client = client->number;
new_port->addr.port = -1;
new_port->owner = THIS_MODULE;
snd_use_lock_init(&new_port->use_lock);
port_subs_info_init(&new_port->c_src);
port_subs_info_init(&new_port->c_dest);
snd_use_lock_use(&new_port->use_lock);
*port_ret = new_port;
return 0;
}
/* insert the port; return the port address or a negative error code */
int snd_seq_insert_port(struct snd_seq_client *client, int port,
struct snd_seq_client_port *new_port)
{
struct snd_seq_client_port *p;
int num;
num = max(port, 0);
guard(mutex)(&client->ports_mutex);
guard(write_lock_irq)(&client->ports_lock);
struct list_head *insert_before = &client->ports_list_head;
list_for_each_entry(p, &client->ports_list_head, list) {
if (p->addr.port == port)
return -EBUSY;
if (p->addr.port > num) {
insert_before = &p->list;
break;
}
if (port < 0) /* auto-probe mode */
num = p->addr.port + 1;
}
/* insert the new port */
list_add_tail(&new_port->list, insert_before);
client->num_ports++;
new_port->addr.port = num; /* store the port number in the port */
if (!new_port->name[0])
sprintf(new_port->name, "port-%d", num);
return num;
}
/* */
static int subscribe_port(struct snd_seq_client *client,
struct snd_seq_client_port *port,
struct snd_seq_port_subs_info *grp,
struct snd_seq_port_subscribe *info, int send_ack);
static int unsubscribe_port(struct snd_seq_client *client,
struct snd_seq_client_port *port,
struct snd_seq_port_subs_info *grp,
struct snd_seq_port_subscribe *info, int send_ack);
static struct snd_seq_client_port *get_client_port(struct snd_seq_addr *addr,
struct snd_seq_client **cp)
{
*cp = snd_seq_client_use_ptr(addr->client);
if (!*cp)
return NULL;
return snd_seq_port_use_ptr(*cp, addr->port);
}
static void delete_and_unsubscribe_port(struct snd_seq_client *client,
struct snd_seq_client_port *port,
struct snd_seq_subscribers *subs,
bool is_src, bool ack);
static inline struct snd_seq_subscribers *
get_subscriber(struct list_head *p, bool is_src)
{
if (is_src)
return list_entry(p, struct snd_seq_subscribers, src_list);
else
return list_entry(p, struct snd_seq_subscribers, dest_list);
}
/*
* remove all subscribers on the list
* this is called from port_delete, for each src and dest list.
*/
static void clear_subscriber_list(struct snd_seq_client *client,
struct snd_seq_client_port *port,
struct snd_seq_port_subs_info *grp,
int is_src)
{
struct list_head *p, *n;
list_for_each_safe(p, n, &grp->list_head) {
struct snd_seq_subscribers *subs;
subs = get_subscriber(p, is_src);
struct snd_seq_client *c __free(snd_seq_client) = NULL;
struct snd_seq_client_port *aport __free(snd_seq_port) =
is_src ?
get_client_port(&subs->info.dest, &c) :
get_client_port(&subs->info.sender, &c);
delete_and_unsubscribe_port(client, port, subs, is_src, false);
if (!aport) {
/* looks like the connected port is being deleted.
* we decrease the counter, and when both ports are deleted
* remove the subscriber info
*/
if (atomic_dec_and_test(&subs->ref_count))
kfree(subs);
continue;
}
/* ok we got the connected port */
delete_and_unsubscribe_port(c, aport, subs, !is_src, true);
kfree(subs);
}
}
/* delete port data */
static int port_delete(struct snd_seq_client *client,
struct snd_seq_client_port *port)
{
/* set closing flag and wait for all port access are gone */
port->closing = 1;
snd_use_lock_sync(&port->use_lock);
/* clear subscribers info */
clear_subscriber_list(client, port, &port->c_src, true);
clear_subscriber_list(client, port, &port->c_dest, false);
if (port->private_free)
port->private_free(port->private_data);
snd_BUG_ON(port->c_src.count != 0);
snd_BUG_ON(port->c_dest.count != 0);
kfree(port);
return 0;
}
/* delete a port with the given port id */
int snd_seq_delete_port(struct snd_seq_client *client, int port)
{
struct snd_seq_client_port *found = NULL, *p;
scoped_guard(mutex, &client->ports_mutex) {
guard(write_lock_irq)(&client->ports_lock);
list_for_each_entry(p, &client->ports_list_head, list) {
if (p->addr.port == port) {
/* ok found. delete from the list at first */
list_del(&p->list);
client->num_ports--;
found = p;
break;
}
}
}
if (found)
return port_delete(client, found);
else
return -ENOENT;
}
/* delete the all ports belonging to the given client */
int snd_seq_delete_all_ports(struct snd_seq_client *client)
{
struct list_head deleted_list;
struct snd_seq_client_port *port, *tmp;
/* move the port list to deleted_list, and
* clear the port list in the client data.
*/
guard(mutex)(&client->ports_mutex);
scoped_guard(write_lock_irq, &client->ports_lock) {
if (!list_empty(&client->ports_list_head)) {
list_add(&deleted_list, &client->ports_list_head);
list_del_init(&client->ports_list_head);
} else {
INIT_LIST_HEAD(&deleted_list);
}
client->num_ports = 0;
}
/* remove each port in deleted_list */
list_for_each_entry_safe(port, tmp, &deleted_list, list) {
list_del(&port->list);
snd_seq_system_client_ev_port_exit(port->addr.client, port->addr.port);
port_delete(client, port);
}
return 0;
}
/* set port info fields */
int snd_seq_set_port_info(struct snd_seq_client_port * port,
struct snd_seq_port_info * info)
{
if (snd_BUG_ON(!port || !info))
return -EINVAL;
/* set port name */
if (info->name[0])
strscpy(port->name, info->name, sizeof(port->name));
/* set capabilities */
port->capability = info->capability;
/* get port type */
port->type = info->type;
/* information about supported channels/voices */
port->midi_channels = info->midi_channels;
port->midi_voices = info->midi_voices;
port->synth_voices = info->synth_voices;
/* timestamping */
port->timestamping = (info->flags & SNDRV_SEQ_PORT_FLG_TIMESTAMP) ? 1 : 0;
port->time_real = (info->flags & SNDRV_SEQ_PORT_FLG_TIME_REAL) ? 1 : 0;
port->time_queue = info->time_queue;
/* UMP direction and group */
port->direction = info->direction;
port->ump_group = info->ump_group;
if (port->ump_group > SNDRV_UMP_MAX_GROUPS)
port->ump_group = 0;
/* fill default port direction */
if (!port->direction) {
if (info->capability & SNDRV_SEQ_PORT_CAP_READ)
port->direction |= SNDRV_SEQ_PORT_DIR_INPUT;
if (info->capability & SNDRV_SEQ_PORT_CAP_WRITE)
port->direction |= SNDRV_SEQ_PORT_DIR_OUTPUT;
}
port->is_midi1 = !!(info->flags & SNDRV_SEQ_PORT_FLG_IS_MIDI1);
return 0;
}
/* get port info fields */
int snd_seq_get_port_info(struct snd_seq_client_port * port,
struct snd_seq_port_info * info)
{
if (snd_BUG_ON(!port || !info))
return -EINVAL;
/* get port name */
strscpy(info->name, port->name, sizeof(info->name));
/* get capabilities */
info->capability = port->capability;
/* get port type */
info->type = port->type;
/* information about supported channels/voices */
info->midi_channels = port->midi_channels;
info->midi_voices = port->midi_voices;
info->synth_voices = port->synth_voices;
/* get subscriber counts */
info->read_use = port->c_src.count;
info->write_use = port->c_dest.count;
/* timestamping */
info->flags = 0;
if (port->timestamping) {
info->flags |= SNDRV_SEQ_PORT_FLG_TIMESTAMP;
if (port->time_real)
info->flags |= SNDRV_SEQ_PORT_FLG_TIME_REAL;
info->time_queue = port->time_queue;
}
if (port->is_midi1)
info->flags |= SNDRV_SEQ_PORT_FLG_IS_MIDI1;
/* UMP direction and group */
info->direction = port->direction;
info->ump_group = port->ump_group;
return 0;
}
/*
* call callback functions (if any):
* the callbacks are invoked only when the first (for connection) or
* the last subscription (for disconnection) is done. Second or later
* subscription results in increment of counter, but no callback is
* invoked.
* This feature is useful if these callbacks are associated with
* initialization or termination of devices (see seq_midi.c).
*/
static int subscribe_port(struct snd_seq_client *client,
struct snd_seq_client_port *port,
struct snd_seq_port_subs_info *grp,
struct snd_seq_port_subscribe *info,
int send_ack)
{
int err = 0;
if (!try_module_get(port->owner))
return -EFAULT;
grp->count++;
if (grp->open && grp->count == 1) {
err = grp->open(port->private_data, info);
if (err < 0) {
module_put(port->owner);
grp->count--;
}
}
if (err >= 0 && send_ack && client->type == USER_CLIENT)
snd_seq_client_notify_subscription(port->addr.client, port->addr.port,
info, SNDRV_SEQ_EVENT_PORT_SUBSCRIBED);
return err;
}
static int unsubscribe_port(struct snd_seq_client *client,
struct snd_seq_client_port *port,
struct snd_seq_port_subs_info *grp,
struct snd_seq_port_subscribe *info,
int send_ack)
{
int err = 0;
if (! grp->count)
return -EINVAL;
grp->count--;
if (grp->close && grp->count == 0)
err = grp->close(port->private_data, info);
if (send_ack && client->type == USER_CLIENT)
snd_seq_client_notify_subscription(port->addr.client, port->addr.port,
info, SNDRV_SEQ_EVENT_PORT_UNSUBSCRIBED);
module_put(port->owner);
return err;
}
/* check if both addresses are identical */
static inline int addr_match(struct snd_seq_addr *r, struct snd_seq_addr *s)
{
return (r->client == s->client) && (r->port == s->port);
}
/* check the two subscribe info match */
/* if flags is zero, checks only sender and destination addresses */
static int match_subs_info(struct snd_seq_port_subscribe *r,
struct snd_seq_port_subscribe *s)
{
if (addr_match(&r->sender, &s->sender) &&
addr_match(&r->dest, &s->dest)) {
if (r->flags && r->flags == s->flags)
return r->queue == s->queue;
else if (! r->flags)
return 1;
}
return 0;
}
static int check_and_subscribe_port(struct snd_seq_client *client,
struct snd_seq_client_port *port,
struct snd_seq_subscribers *subs,
bool is_src, bool exclusive, bool ack)
{
struct snd_seq_port_subs_info *grp;
struct list_head *p;
struct snd_seq_subscribers *s;
int err;
grp = is_src ? &port->c_src : &port->c_dest;
guard(rwsem_write)(&grp->list_mutex);
if (exclusive) {
if (!list_empty(&grp->list_head))
return -EBUSY;
} else {
if (grp->exclusive)
return -EBUSY;
/* check whether already exists */
list_for_each(p, &grp->list_head) {
s = get_subscriber(p, is_src);
if (match_subs_info(&subs->info, &s->info))
return -EBUSY;
}
}
err = subscribe_port(client, port, grp, &subs->info, ack);
if (err < 0) {
grp->exclusive = 0;
return err;
}
/* add to list */
guard(write_lock_irq)(&grp->list_lock);
if (is_src)
list_add_tail(&subs->src_list, &grp->list_head);
else
list_add_tail(&subs->dest_list, &grp->list_head);
grp->exclusive = exclusive;
atomic_inc(&subs->ref_count);
return 0;
}
/* called with grp->list_mutex held */
static void __delete_and_unsubscribe_port(struct snd_seq_client *client,
struct snd_seq_client_port *port,
struct snd_seq_subscribers *subs,
bool is_src, bool ack)
{
struct snd_seq_port_subs_info *grp;
struct list_head *list;
bool empty;
grp = is_src ? &port->c_src : &port->c_dest;
list = is_src ? &subs->src_list : &subs->dest_list;
scoped_guard(write_lock_irq, &grp->list_lock) {
empty = list_empty(list);
if (!empty)
list_del_init(list);
grp->exclusive = 0;
}
if (!empty)
unsubscribe_port(client, port, grp, &subs->info, ack);
}
static void delete_and_unsubscribe_port(struct snd_seq_client *client,
struct snd_seq_client_port *port,
struct snd_seq_subscribers *subs,
bool is_src, bool ack)
{
struct snd_seq_port_subs_info *grp;
grp = is_src ? &port->c_src : &port->c_dest;
guard(rwsem_write)(&grp->list_mutex);
__delete_and_unsubscribe_port(client, port, subs, is_src, ack);
}
/* connect two ports */
int snd_seq_port_connect(struct snd_seq_client *connector,
struct snd_seq_client *src_client,
struct snd_seq_client_port *src_port,
struct snd_seq_client *dest_client,
struct snd_seq_client_port *dest_port,
struct snd_seq_port_subscribe *info)
{
struct snd_seq_subscribers *subs;
bool exclusive;
int err;
subs = kzalloc_obj(*subs);
if (!subs)
return -ENOMEM;
subs->info = *info;
atomic_set(&subs->ref_count, 0);
INIT_LIST_HEAD(&subs->src_list);
INIT_LIST_HEAD(&subs->dest_list);
exclusive = !!(info->flags & SNDRV_SEQ_PORT_SUBS_EXCLUSIVE);
err = check_and_subscribe_port(src_client, src_port, subs, true,
exclusive,
connector->number != src_client->number);
if (err < 0)
goto error;
err = check_and_subscribe_port(dest_client, dest_port, subs, false,
exclusive,
connector->number != dest_client->number);
if (err < 0)
goto error_dest;
return 0;
error_dest:
delete_and_unsubscribe_port(src_client, src_port, subs, true,
connector->number != src_client->number);
error:
kfree(subs);
return err;
}
/* remove the connection */
int snd_seq_port_disconnect(struct snd_seq_client *connector,
struct snd_seq_client *src_client,
struct snd_seq_client_port *src_port,
struct snd_seq_client *dest_client,
struct snd_seq_client_port *dest_port,
struct snd_seq_port_subscribe *info)
{
struct snd_seq_port_subs_info *dest = &dest_port->c_dest;
struct snd_seq_subscribers *subs;
int err = -ENOENT;
/* always start from deleting the dest port for avoiding concurrent
* deletions
*/
scoped_guard(rwsem_write, &dest->list_mutex) {
/* look for the connection */
list_for_each_entry(subs, &dest->list_head, dest_list) {
if (match_subs_info(info, &subs->info)) {
__delete_and_unsubscribe_port(dest_client, dest_port,
subs, false,
connector->number != dest_client->number);
err = 0;
break;
}
}
}
if (err < 0)
return err;
delete_and_unsubscribe_port(src_client, src_port, subs, true,
connector->number != src_client->number);
kfree(subs);
return 0;
}
/* get matched subscriber */
int snd_seq_port_get_subscription(struct snd_seq_port_subs_info *src_grp,
struct snd_seq_addr *dest_addr,
struct snd_seq_port_subscribe *subs)
{
struct snd_seq_subscribers *s;
int err = -ENOENT;
guard(rwsem_read)(&src_grp->list_mutex);
list_for_each_entry(s, &src_grp->list_head, src_list) {
if (addr_match(dest_addr, &s->info.dest)) {
*subs = s->info;
err = 0;
break;
}
}
return err;
}
/*
* Attach a device driver that wants to receive events from the
* sequencer. Returns the new port number on success.
* A driver that wants to receive the events converted to midi, will
* use snd_seq_midisynth_register_port().
*/
/* exported */
int snd_seq_event_port_attach(int client,
struct snd_seq_port_callback *pcbp,
int cap, int type, int midi_channels,
int midi_voices, char *portname)
{
struct snd_seq_port_info portinfo;
int ret;
/* Set up the port */
memset(&portinfo, 0, sizeof(portinfo));
portinfo.addr.client = client;
strscpy(portinfo.name, portname ? portname : "Unnamed port",
sizeof(portinfo.name));
portinfo.capability = cap;
portinfo.type = type;
portinfo.kernel = pcbp;
portinfo.midi_channels = midi_channels;
portinfo.midi_voices = midi_voices;
/* Create it */
ret = snd_seq_kernel_client_ctl(client,
SNDRV_SEQ_IOCTL_CREATE_PORT,
&portinfo);
if (ret >= 0)
ret = portinfo.addr.port;
return ret;
}
EXPORT_SYMBOL(snd_seq_event_port_attach);
/*
* Detach the driver from a port.
*/
/* exported */
int snd_seq_event_port_detach(int client, int port)
{
struct snd_seq_port_info portinfo;
int err;
memset(&portinfo, 0, sizeof(portinfo));
portinfo.addr.client = client;
portinfo.addr.port = port;
err = snd_seq_kernel_client_ctl(client,
SNDRV_SEQ_IOCTL_DELETE_PORT,
&portinfo);
return err;
}
EXPORT_SYMBOL(snd_seq_event_port_detach);
+144
View File
@@ -0,0 +1,144 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* ALSA sequencer Ports
* Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
*/
#ifndef __SND_SEQ_PORTS_H
#define __SND_SEQ_PORTS_H
#include <sound/seq_kernel.h>
#include <sound/ump_convert.h>
#include "seq_lock.h"
/* list of 'exported' ports */
/* Client ports that are not exported are still accessible, but are
anonymous ports.
If a port supports SUBSCRIPTION, that port can send events to all
subscribersto a special address, with address
(queue==SNDRV_SEQ_ADDRESS_SUBSCRIBERS). The message is then send to all
recipients that are registered in the subscription list. A typical
application for these SUBSCRIPTION events is handling of incoming MIDI
data. The port doesn't 'know' what other clients are interested in this
message. If for instance a MIDI recording application would like to receive
the events from that port, it will first have to subscribe with that port.
*/
struct snd_seq_subscribers {
struct snd_seq_port_subscribe info; /* additional info */
struct list_head src_list; /* link of sources */
struct list_head dest_list; /* link of destinations */
atomic_t ref_count;
};
struct snd_seq_port_subs_info {
struct list_head list_head; /* list of subscribed ports */
unsigned int count; /* count of subscribers */
unsigned int exclusive: 1; /* exclusive mode */
struct rw_semaphore list_mutex;
rwlock_t list_lock;
int (*open)(void *private_data, struct snd_seq_port_subscribe *info);
int (*close)(void *private_data, struct snd_seq_port_subscribe *info);
};
struct snd_seq_client_port {
struct snd_seq_addr addr; /* client/port number */
struct module *owner; /* owner of this port */
char name[64]; /* port name */
struct list_head list; /* port list */
snd_use_lock_t use_lock;
/* subscribers */
struct snd_seq_port_subs_info c_src; /* read (sender) list */
struct snd_seq_port_subs_info c_dest; /* write (dest) list */
int (*event_input)(struct snd_seq_event *ev, int direct, void *private_data,
int atomic, int hop);
void (*private_free)(void *private_data);
void *private_data;
unsigned int closing : 1;
unsigned int timestamping: 1;
unsigned int time_real: 1;
int time_queue;
/* capability, inport, output, sync */
unsigned int capability; /* port capability bits */
unsigned int type; /* port type bits */
/* supported channels */
int midi_channels;
int midi_voices;
int synth_voices;
/* UMP direction and group */
unsigned char direction;
unsigned char ump_group;
bool is_midi1; /* keep MIDI 1.0 protocol */
#if IS_ENABLED(CONFIG_SND_SEQ_UMP)
struct ump_cvt_to_ump_bank midi2_bank[16]; /* per channel */
#endif
};
struct snd_seq_client;
/* return pointer to port structure and lock port */
struct snd_seq_client_port *snd_seq_port_use_ptr(struct snd_seq_client *client, int num);
/* search for next port - port is locked if found */
struct snd_seq_client_port *snd_seq_port_query_nearest(struct snd_seq_client *client,
struct snd_seq_port_info *pinfo);
/* unlock the port */
#define snd_seq_port_unlock(port) snd_use_lock_free(&(port)->use_lock)
DEFINE_FREE(snd_seq_port, struct snd_seq_client_port *, if (!IS_ERR_OR_NULL(_T)) snd_seq_port_unlock(_T))
/* create a port, 0 on success or a negative error code is returned */
int snd_seq_create_port(struct snd_seq_client *client,
struct snd_seq_client_port **port_ret);
/* insert the port; return the port address or a negative error code */
int snd_seq_insert_port(struct snd_seq_client *client, int port,
struct snd_seq_client_port *new_port);
/* delete a port */
int snd_seq_delete_port(struct snd_seq_client *client, int port);
/* delete all ports */
int snd_seq_delete_all_ports(struct snd_seq_client *client);
/* set port info fields */
int snd_seq_set_port_info(struct snd_seq_client_port *port,
struct snd_seq_port_info *info);
/* get port info fields */
int snd_seq_get_port_info(struct snd_seq_client_port *port,
struct snd_seq_port_info *info);
/* add subscriber to subscription list */
int snd_seq_port_connect(struct snd_seq_client *caller,
struct snd_seq_client *s, struct snd_seq_client_port *sp,
struct snd_seq_client *d, struct snd_seq_client_port *dp,
struct snd_seq_port_subscribe *info);
/* remove subscriber from subscription list */
int snd_seq_port_disconnect(struct snd_seq_client *caller,
struct snd_seq_client *s, struct snd_seq_client_port *sp,
struct snd_seq_client *d, struct snd_seq_client_port *dp,
struct snd_seq_port_subscribe *info);
/* subscribe port */
int snd_seq_port_subscribe(struct snd_seq_client_port *port,
struct snd_seq_port_subscribe *info);
/* get matched subscriber */
int snd_seq_port_get_subscription(struct snd_seq_port_subs_info *src_grp,
struct snd_seq_addr *dest_addr,
struct snd_seq_port_subscribe *subs);
#endif
+402
View File
@@ -0,0 +1,402 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* ALSA sequencer Priority Queue
* Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl>
*/
#include <linux/time.h>
#include <linux/slab.h>
#include <sound/core.h>
#include "seq_timer.h"
#include "seq_prioq.h"
/* Implementation is a simple linked list for now...
This priority queue orders the events on timestamp. For events with an
equeal timestamp the queue behaves as a FIFO.
*
* +-------+
* Head --> | first |
* +-------+
* |next
* +-----v-+
* | |
* +-------+
* |
* +-----v-+
* | |
* +-------+
* |
* +-----v-+
* Tail --> | last |
* +-------+
*
*/
/* create new prioq (constructor) */
struct snd_seq_prioq *snd_seq_prioq_new(void)
{
struct snd_seq_prioq *f;
f = kzalloc_obj(*f);
if (!f)
return NULL;
spin_lock_init(&f->lock);
f->head = NULL;
f->tail = NULL;
f->cells = 0;
return f;
}
/* delete prioq (destructor) */
void snd_seq_prioq_delete(struct snd_seq_prioq **fifo)
{
struct snd_seq_prioq *f = *fifo;
*fifo = NULL;
if (f == NULL) {
pr_debug("ALSA: seq: snd_seq_prioq_delete() called with NULL prioq\n");
return;
}
/* release resources...*/
/*....................*/
if (f->cells > 0) {
/* drain prioQ */
while (f->cells > 0)
snd_seq_cell_free(snd_seq_prioq_cell_out(f, NULL));
}
kfree(f);
}
/* compare timestamp between events */
/* return 1 if a >= b; 0 */
static inline int compare_timestamp(struct snd_seq_event *a,
struct snd_seq_event *b)
{
if ((a->flags & SNDRV_SEQ_TIME_STAMP_MASK) == SNDRV_SEQ_TIME_STAMP_TICK) {
/* compare ticks */
return (snd_seq_compare_tick_time(&a->time.tick, &b->time.tick));
} else {
/* compare real time */
return (snd_seq_compare_real_time(&a->time.time, &b->time.time));
}
}
/* compare timestamp between events */
/* return negative if a < b;
* zero if a = b;
* positive if a > b;
*/
static inline int compare_timestamp_rel(struct snd_seq_event *a,
struct snd_seq_event *b)
{
if ((a->flags & SNDRV_SEQ_TIME_STAMP_MASK) == SNDRV_SEQ_TIME_STAMP_TICK) {
/* compare ticks */
if (a->time.tick > b->time.tick)
return 1;
else if (a->time.tick == b->time.tick)
return 0;
else
return -1;
} else {
/* compare real time */
if (a->time.time.tv_sec > b->time.time.tv_sec)
return 1;
else if (a->time.time.tv_sec == b->time.time.tv_sec) {
if (a->time.time.tv_nsec > b->time.time.tv_nsec)
return 1;
else if (a->time.time.tv_nsec == b->time.time.tv_nsec)
return 0;
else
return -1;
} else
return -1;
}
}
/* enqueue cell to prioq */
int snd_seq_prioq_cell_in(struct snd_seq_prioq * f,
struct snd_seq_event_cell * cell)
{
struct snd_seq_event_cell *cur, *prev;
int remaining;
int prior;
if (snd_BUG_ON(!f || !cell))
return -EINVAL;
/* check flags */
prior = (cell->event.flags & SNDRV_SEQ_PRIORITY_MASK);
guard(spinlock_irqsave)(&f->lock);
/* check if this element needs to inserted at the end (ie. ordered
data is inserted) This will be very likeley if a sequencer
application or midi file player is feeding us (sequential) data */
if (f->tail && !prior) {
if (compare_timestamp(&cell->event, &f->tail->event)) {
/* add new cell to tail of the fifo */
f->tail->next = cell;
f->tail = cell;
cell->next = NULL;
f->cells++;
return 0;
}
}
/* traverse list of elements to find the place where the new cell is
to be inserted... Note that this is a order n process ! */
prev = NULL; /* previous cell */
cur = f->head; /* cursor */
remaining = f->cells;
while (cur != NULL) {
/* compare timestamps */
int rel = compare_timestamp_rel(&cell->event, &cur->event);
if (remaining-- <= 0) {
pr_err("ALSA: seq: inconsistent prioq cell count\n");
return -EINVAL;
}
if (rel < 0)
/* new cell has earlier schedule time, */
break;
else if (rel == 0 && prior)
/* equal schedule time and prior to others */
break;
/* new cell has equal or larger schedule time, */
/* move cursor to next cell */
prev = cur;
cur = cur->next;
}
/* insert it before cursor */
if (prev != NULL)
prev->next = cell;
cell->next = cur;
if (f->head == cur) /* this is the first cell, set head to it */
f->head = cell;
if (cur == NULL) /* reached end of the list */
f->tail = cell;
f->cells++;
return 0;
}
/* return 1 if the current time >= event timestamp */
static int event_is_ready(struct snd_seq_event *ev, void *current_time)
{
if ((ev->flags & SNDRV_SEQ_TIME_STAMP_MASK) == SNDRV_SEQ_TIME_STAMP_TICK)
return snd_seq_compare_tick_time(current_time, &ev->time.tick);
else
return snd_seq_compare_real_time(current_time, &ev->time.time);
}
/* dequeue cell from prioq */
struct snd_seq_event_cell *snd_seq_prioq_cell_out(struct snd_seq_prioq *f,
void *current_time)
{
struct snd_seq_event_cell *cell;
if (f == NULL) {
pr_debug("ALSA: seq: snd_seq_prioq_cell_in() called with NULL prioq\n");
return NULL;
}
guard(spinlock_irqsave)(&f->lock);
cell = f->head;
if (cell && current_time && !event_is_ready(&cell->event, current_time))
cell = NULL;
if (cell) {
f->head = cell->next;
/* reset tail if this was the last element */
if (f->tail == cell)
f->tail = NULL;
cell->next = NULL;
f->cells--;
}
return cell;
}
/* return number of events available in prioq */
int snd_seq_prioq_avail(struct snd_seq_prioq * f)
{
if (f == NULL) {
pr_debug("ALSA: seq: snd_seq_prioq_cell_in() called with NULL prioq\n");
return 0;
}
return f->cells;
}
/* remove cells matching with the condition */
static void prioq_remove_cells(struct snd_seq_prioq *f,
bool (*match)(struct snd_seq_event_cell *cell,
void *arg),
void *arg)
{
register struct snd_seq_event_cell *cell, *next;
struct snd_seq_event_cell *prev = NULL;
struct snd_seq_event_cell *freefirst = NULL, *freeprev = NULL, *freenext;
/* collect all removed cells */
scoped_guard(spinlock_irqsave, &f->lock) {
for (cell = f->head; cell; cell = next) {
next = cell->next;
if (!match(cell, arg)) {
prev = cell;
continue;
}
/* remove cell from prioq */
if (cell == f->head)
f->head = cell->next;
else
prev->next = cell->next;
if (cell == f->tail)
f->tail = cell->next;
f->cells--;
/* add cell to free list */
cell->next = NULL;
if (freefirst == NULL)
freefirst = cell;
else
freeprev->next = cell;
freeprev = cell;
}
}
/* remove selected cells */
while (freefirst) {
freenext = freefirst->next;
snd_seq_cell_free(freefirst);
freefirst = freenext;
}
}
struct prioq_match_arg {
int client;
int timestamp;
};
static inline bool prioq_match(struct snd_seq_event_cell *cell, void *arg)
{
struct prioq_match_arg *v = arg;
if (cell->event.source.client == v->client ||
cell->event.dest.client == v->client)
return true;
if (!v->timestamp)
return false;
switch (cell->event.flags & SNDRV_SEQ_TIME_STAMP_MASK) {
case SNDRV_SEQ_TIME_STAMP_TICK:
if (cell->event.time.tick)
return true;
break;
case SNDRV_SEQ_TIME_STAMP_REAL:
if (cell->event.time.time.tv_sec ||
cell->event.time.time.tv_nsec)
return true;
break;
}
return false;
}
/* remove cells for left client */
void snd_seq_prioq_leave(struct snd_seq_prioq *f, int client, int timestamp)
{
struct prioq_match_arg arg = { client, timestamp };
return prioq_remove_cells(f, prioq_match, &arg);
}
struct prioq_remove_match_arg {
int client;
struct snd_seq_remove_events *info;
};
static bool prioq_remove_match(struct snd_seq_event_cell *cell, void *arg)
{
struct prioq_remove_match_arg *v = arg;
struct snd_seq_event *ev = &cell->event;
struct snd_seq_remove_events *info = v->info;
int res;
if (ev->source.client != v->client)
return false;
if (info->remove_mode & SNDRV_SEQ_REMOVE_DEST) {
if (ev->dest.client != info->dest.client ||
ev->dest.port != info->dest.port)
return false;
}
if (info->remove_mode & SNDRV_SEQ_REMOVE_DEST_CHANNEL) {
if (! snd_seq_ev_is_channel_type(ev))
return false;
/* data.note.channel and data.control.channel are identical */
if (ev->data.note.channel != info->channel)
return false;
}
if (info->remove_mode & SNDRV_SEQ_REMOVE_TIME_AFTER) {
if (info->remove_mode & SNDRV_SEQ_REMOVE_TIME_TICK)
res = snd_seq_compare_tick_time(&ev->time.tick, &info->time.tick);
else
res = snd_seq_compare_real_time(&ev->time.time, &info->time.time);
if (!res)
return false;
}
if (info->remove_mode & SNDRV_SEQ_REMOVE_TIME_BEFORE) {
if (info->remove_mode & SNDRV_SEQ_REMOVE_TIME_TICK)
res = snd_seq_compare_tick_time(&ev->time.tick, &info->time.tick);
else
res = snd_seq_compare_real_time(&ev->time.time, &info->time.time);
if (res)
return false;
}
if (info->remove_mode & SNDRV_SEQ_REMOVE_EVENT_TYPE) {
if (ev->type != info->type)
return false;
}
if (info->remove_mode & SNDRV_SEQ_REMOVE_IGNORE_OFF) {
/* Do not remove off events */
switch (ev->type) {
case SNDRV_SEQ_EVENT_NOTEOFF:
/* case SNDRV_SEQ_EVENT_SAMPLE_STOP: */
return false;
default:
break;
}
}
if (info->remove_mode & SNDRV_SEQ_REMOVE_TAG_MATCH) {
if (info->tag != ev->tag)
return false;
}
return true;
}
/* remove cells matching remove criteria */
void snd_seq_prioq_remove_events(struct snd_seq_prioq * f, int client,
struct snd_seq_remove_events *info)
{
struct prioq_remove_match_arg arg = { client, info };
return prioq_remove_cells(f, prioq_remove_match, &arg);
}
+45
View File
@@ -0,0 +1,45 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* ALSA sequencer Priority Queue
* Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
*/
#ifndef __SND_SEQ_PRIOQ_H
#define __SND_SEQ_PRIOQ_H
#include "seq_memory.h"
/* === PRIOQ === */
struct snd_seq_prioq {
struct snd_seq_event_cell *head; /* pointer to head of prioq */
struct snd_seq_event_cell *tail; /* pointer to tail of prioq */
int cells;
spinlock_t lock;
};
/* create new prioq (constructor) */
struct snd_seq_prioq *snd_seq_prioq_new(void);
/* delete prioq (destructor) */
void snd_seq_prioq_delete(struct snd_seq_prioq **fifo);
/* enqueue cell to prioq */
int snd_seq_prioq_cell_in(struct snd_seq_prioq *f, struct snd_seq_event_cell *cell);
/* dequeue cell from prioq */
struct snd_seq_event_cell *snd_seq_prioq_cell_out(struct snd_seq_prioq *f,
void *current_time);
/* return number of events available in prioq */
int snd_seq_prioq_avail(struct snd_seq_prioq *f);
/* client left queue */
void snd_seq_prioq_leave(struct snd_seq_prioq *f, int client, int timestamp);
/* Remove events */
void snd_seq_prioq_remove_events(struct snd_seq_prioq *f, int client,
struct snd_seq_remove_events *info);
#endif
+710
View File
@@ -0,0 +1,710 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* ALSA sequencer Timing queue handling
* Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl>
*
* MAJOR CHANGES
* Nov. 13, 1999 Takashi Iwai <iwai@ww.uni-erlangen.de>
* - Queues are allocated dynamically via ioctl.
* - When owner client is deleted, all owned queues are deleted, too.
* - Owner of unlocked queue is kept unmodified even if it is
* manipulated by other clients.
* - Owner field in SET_QUEUE_OWNER ioctl must be identical with the
* caller client. i.e. Changing owner to a third client is not
* allowed.
*
* Aug. 30, 2000 Takashi Iwai
* - Queues are managed in static array again, but with better way.
* The API itself is identical.
* - The queue is locked when struct snd_seq_queue pointer is returned via
* queueptr(). This pointer *MUST* be released afterward by
* queuefree(ptr).
* - Addition of experimental sync support.
*/
#include <linux/init.h>
#include <linux/slab.h>
#include <sound/core.h>
#include "seq_memory.h"
#include "seq_queue.h"
#include "seq_clientmgr.h"
#include "seq_fifo.h"
#include "seq_timer.h"
#include "seq_info.h"
/* list of allocated queues */
static struct snd_seq_queue *queue_list[SNDRV_SEQ_MAX_QUEUES];
static DEFINE_SPINLOCK(queue_list_lock);
/* number of queues allocated */
static int num_queues;
int snd_seq_queue_get_cur_queues(void)
{
return num_queues;
}
/*----------------------------------------------------------------*/
/* assign queue id and insert to list */
static int queue_list_add(struct snd_seq_queue *q)
{
int i;
guard(spinlock_irqsave)(&queue_list_lock);
for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
if (! queue_list[i]) {
queue_list[i] = q;
q->queue = i;
num_queues++;
return i;
}
}
return -1;
}
static struct snd_seq_queue *queue_list_remove(int id, int client)
{
struct snd_seq_queue *q;
guard(spinlock_irqsave)(&queue_list_lock);
q = queue_list[id];
if (q) {
guard(spinlock)(&q->owner_lock);
if (q->owner == client) {
/* found */
q->klocked = 1;
queue_list[id] = NULL;
num_queues--;
return q;
}
}
return NULL;
}
/*----------------------------------------------------------------*/
/* create new queue (constructor) */
static struct snd_seq_queue *queue_new(int owner, int locked)
{
struct snd_seq_queue *q;
q = kzalloc_obj(*q);
if (!q)
return NULL;
spin_lock_init(&q->owner_lock);
spin_lock_init(&q->check_lock);
mutex_init(&q->timer_mutex);
snd_use_lock_init(&q->use_lock);
q->queue = -1;
q->tickq = snd_seq_prioq_new();
q->timeq = snd_seq_prioq_new();
q->timer = snd_seq_timer_new();
if (q->tickq == NULL || q->timeq == NULL || q->timer == NULL) {
snd_seq_prioq_delete(&q->tickq);
snd_seq_prioq_delete(&q->timeq);
snd_seq_timer_delete(&q->timer);
kfree(q);
return NULL;
}
q->owner = owner;
q->locked = locked;
q->klocked = 0;
return q;
}
/* delete queue (destructor) */
static void queue_delete(struct snd_seq_queue *q)
{
/* stop and release the timer */
mutex_lock(&q->timer_mutex);
snd_seq_timer_stop(q->timer);
snd_seq_timer_close(q);
mutex_unlock(&q->timer_mutex);
/* wait until access free */
snd_use_lock_sync(&q->use_lock);
/* release resources... */
snd_seq_prioq_delete(&q->tickq);
snd_seq_prioq_delete(&q->timeq);
snd_seq_timer_delete(&q->timer);
kfree(q);
}
/*----------------------------------------------------------------*/
/* delete all existing queues */
void snd_seq_queues_delete(void)
{
int i;
/* clear list */
for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
if (queue_list[i])
queue_delete(queue_list[i]);
}
}
static void queue_use(struct snd_seq_queue *queue, int client, int use);
/* allocate a new queue -
* return pointer to new queue or ERR_PTR(-errno) for error
* The new queue's use_lock is set to 1. It is the caller's responsibility to
* call snd_use_lock_free(&q->use_lock).
*/
struct snd_seq_queue *snd_seq_queue_alloc(int client, int locked, unsigned int info_flags)
{
struct snd_seq_queue *q;
q = queue_new(client, locked);
if (q == NULL)
return ERR_PTR(-ENOMEM);
q->info_flags = info_flags;
queue_use(q, client, 1);
snd_use_lock_use(&q->use_lock);
if (queue_list_add(q) < 0) {
snd_use_lock_free(&q->use_lock);
queue_delete(q);
return ERR_PTR(-ENOMEM);
}
return q;
}
/* delete a queue - queue must be owned by the client */
int snd_seq_queue_delete(int client, int queueid)
{
struct snd_seq_queue *q;
if (queueid < 0 || queueid >= SNDRV_SEQ_MAX_QUEUES)
return -EINVAL;
q = queue_list_remove(queueid, client);
if (q == NULL)
return -EINVAL;
queue_delete(q);
return 0;
}
/* return pointer to queue structure for specified id */
struct snd_seq_queue *queueptr(int queueid)
{
struct snd_seq_queue *q;
if (queueid < 0 || queueid >= SNDRV_SEQ_MAX_QUEUES)
return NULL;
guard(spinlock_irqsave)(&queue_list_lock);
q = queue_list[queueid];
if (q)
snd_use_lock_use(&q->use_lock);
return q;
}
/* return the (first) queue matching with the specified name */
struct snd_seq_queue *snd_seq_queue_find_name(char *name)
{
int i;
for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
struct snd_seq_queue *q __free(snd_seq_queue) =
queueptr(i);
if (q) {
if (strncmp(q->name, name, sizeof(q->name)) == 0)
return no_free_ptr(q);
}
}
return NULL;
}
/* -------------------------------------------------------- */
#define MAX_CELL_PROCESSES_IN_QUEUE 1000
void snd_seq_check_queue(struct snd_seq_queue *q, int atomic, int hop)
{
struct snd_seq_event_cell *cell;
snd_seq_tick_time_t cur_tick;
snd_seq_real_time_t cur_time;
int processed = 0;
if (q == NULL)
return;
/* make this function non-reentrant */
scoped_guard(spinlock_irqsave, &q->check_lock) {
if (q->check_blocked) {
q->check_again = 1;
return; /* other thread is already checking queues */
}
q->check_blocked = 1;
}
__again:
/* Process tick queue... */
cur_tick = snd_seq_timer_get_cur_tick(q->timer);
for (;;) {
cell = snd_seq_prioq_cell_out(q->tickq, &cur_tick);
if (!cell)
break;
snd_seq_dispatch_event(cell, atomic, hop);
if (++processed >= MAX_CELL_PROCESSES_IN_QUEUE)
goto out; /* the rest processed at the next batch */
}
/* Process time queue... */
cur_time = snd_seq_timer_get_cur_time(q->timer, false);
for (;;) {
cell = snd_seq_prioq_cell_out(q->timeq, &cur_time);
if (!cell)
break;
snd_seq_dispatch_event(cell, atomic, hop);
if (++processed >= MAX_CELL_PROCESSES_IN_QUEUE)
goto out; /* the rest processed at the next batch */
}
out:
/* free lock */
scoped_guard(spinlock_irqsave, &q->check_lock) {
if (q->check_again) {
q->check_again = 0;
if (processed < MAX_CELL_PROCESSES_IN_QUEUE)
goto __again;
}
q->check_blocked = 0;
}
}
/* enqueue a event to singe queue */
int snd_seq_enqueue_event(struct snd_seq_event_cell *cell, int atomic, int hop)
{
int dest, err;
if (snd_BUG_ON(!cell))
return -EINVAL;
dest = cell->event.queue; /* destination queue */
struct snd_seq_queue *q __free(snd_seq_queue) =
queueptr(dest);
if (q == NULL)
return -EINVAL;
/* handle relative time stamps, convert them into absolute */
if ((cell->event.flags & SNDRV_SEQ_TIME_MODE_MASK) == SNDRV_SEQ_TIME_MODE_REL) {
switch (cell->event.flags & SNDRV_SEQ_TIME_STAMP_MASK) {
case SNDRV_SEQ_TIME_STAMP_TICK:
cell->event.time.tick += q->timer->tick.cur_tick;
break;
case SNDRV_SEQ_TIME_STAMP_REAL:
snd_seq_inc_real_time(&cell->event.time.time,
&q->timer->cur_time);
break;
}
cell->event.flags &= ~SNDRV_SEQ_TIME_MODE_MASK;
cell->event.flags |= SNDRV_SEQ_TIME_MODE_ABS;
}
/* enqueue event in the real-time or midi queue */
switch (cell->event.flags & SNDRV_SEQ_TIME_STAMP_MASK) {
case SNDRV_SEQ_TIME_STAMP_TICK:
err = snd_seq_prioq_cell_in(q->tickq, cell);
break;
case SNDRV_SEQ_TIME_STAMP_REAL:
default:
err = snd_seq_prioq_cell_in(q->timeq, cell);
break;
}
if (err < 0)
return err;
/* trigger dispatching */
snd_seq_check_queue(q, atomic, hop);
return 0;
}
/*----------------------------------------------------------------*/
static inline int check_access(struct snd_seq_queue *q, int client)
{
return (q->owner == client) || (!q->locked && !q->klocked);
}
/* check if the client has permission to modify queue parameters.
* if it does, lock the queue
*/
static int queue_access_lock(struct snd_seq_queue *q, int client)
{
int access_ok;
guard(spinlock_irqsave)(&q->owner_lock);
access_ok = check_access(q, client);
if (access_ok)
q->klocked = 1;
return access_ok;
}
/* unlock the queue */
static inline void queue_access_unlock(struct snd_seq_queue *q)
{
guard(spinlock_irqsave)(&q->owner_lock);
q->klocked = 0;
}
/* exported - only checking permission */
int snd_seq_queue_check_access(int queueid, int client)
{
struct snd_seq_queue *q __free(snd_seq_queue) = queueptr(queueid);
if (! q)
return 0;
guard(spinlock_irqsave)(&q->owner_lock);
return check_access(q, client);
}
/*----------------------------------------------------------------*/
/*
* change queue's owner and permission
*/
int snd_seq_queue_set_owner(int queueid, int client, int locked)
{
struct snd_seq_queue *q __free(snd_seq_queue) = queueptr(queueid);
if (q == NULL)
return -EINVAL;
if (!queue_access_lock(q, client))
return -EPERM;
scoped_guard(spinlock_irqsave, &q->owner_lock) {
q->locked = locked ? 1 : 0;
q->owner = client;
}
queue_access_unlock(q);
return 0;
}
/*----------------------------------------------------------------*/
/* open timer -
* q->use mutex should be down before calling this function to avoid
* confliction with snd_seq_queue_use()
*/
int snd_seq_queue_timer_open(int queueid)
{
int result = 0;
struct snd_seq_timer *tmr;
struct snd_seq_queue *queue __free(snd_seq_queue) =
queueptr(queueid);
if (queue == NULL)
return -EINVAL;
tmr = queue->timer;
result = snd_seq_timer_open(queue);
if (result < 0) {
snd_seq_timer_defaults(tmr);
result = snd_seq_timer_open(queue);
}
return result;
}
/* close timer -
* q->use mutex should be down before calling this function
*/
int snd_seq_queue_timer_close(int queueid)
{
int result = 0;
struct snd_seq_queue *queue __free(snd_seq_queue) =
queueptr(queueid);
if (queue == NULL)
return -EINVAL;
snd_seq_timer_close(queue);
return result;
}
/* change queue tempo and ppq */
int snd_seq_queue_timer_set_tempo(int queueid, int client,
struct snd_seq_queue_tempo *info)
{
struct snd_seq_queue *q __free(snd_seq_queue) = queueptr(queueid);
int result;
if (q == NULL)
return -EINVAL;
if (!queue_access_lock(q, client))
return -EPERM;
result = snd_seq_timer_set_tempo_ppq(q->timer, info->tempo, info->ppq,
info->tempo_base);
if (result >= 0 && info->skew_base > 0)
result = snd_seq_timer_set_skew(q->timer, info->skew_value,
info->skew_base);
queue_access_unlock(q);
return result;
}
/* use or unuse this queue */
static void queue_use(struct snd_seq_queue *queue, int client, int use)
{
if (use) {
if (!test_and_set_bit(client, queue->clients_bitmap))
queue->clients++;
} else {
if (test_and_clear_bit(client, queue->clients_bitmap))
queue->clients--;
}
if (queue->clients) {
if (use && queue->clients == 1)
snd_seq_timer_defaults(queue->timer);
snd_seq_timer_open(queue);
} else {
snd_seq_timer_close(queue);
}
}
/* use or unuse this queue -
* if it is the first client, starts the timer.
* if it is not longer used by any clients, stop the timer.
*/
int snd_seq_queue_use(int queueid, int client, int use)
{
struct snd_seq_queue *queue __free(snd_seq_queue) =
queueptr(queueid);
if (queue == NULL)
return -EINVAL;
guard(mutex)(&queue->timer_mutex);
queue_use(queue, client, use);
return 0;
}
/*
* check if queue is used by the client
* return negative value if the queue is invalid.
* return 0 if not used, 1 if used.
*/
int snd_seq_queue_is_used(int queueid, int client)
{
struct snd_seq_queue *q __free(snd_seq_queue) =
queueptr(queueid);
if (q == NULL)
return -EINVAL; /* invalid queue */
return test_bit(client, q->clients_bitmap) ? 1 : 0;
}
/*----------------------------------------------------------------*/
/* final stage notification -
* remove cells for no longer exist client (for non-owned queue)
* or delete this queue (for owned queue)
*/
void snd_seq_queue_client_leave(int client)
{
int i;
/* delete own queues from queue list */
for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
struct snd_seq_queue *q = queue_list_remove(i, client);
if (q)
queue_delete(q);
}
/* remove cells from existing queues -
* they are not owned by this client
*/
for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
struct snd_seq_queue *q __free(snd_seq_queue) = queueptr(i);
if (!q)
continue;
if (test_bit(client, q->clients_bitmap)) {
snd_seq_prioq_leave(q->tickq, client, 0);
snd_seq_prioq_leave(q->timeq, client, 0);
snd_seq_queue_use(q->queue, client, 0);
}
}
}
/*----------------------------------------------------------------*/
/* remove cells based on flush criteria */
void snd_seq_queue_remove_cells(int client, struct snd_seq_remove_events *info)
{
int i;
for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
struct snd_seq_queue *q __free(snd_seq_queue) = queueptr(i);
if (!q)
continue;
if (test_bit(client, q->clients_bitmap) &&
(! (info->remove_mode & SNDRV_SEQ_REMOVE_DEST) ||
q->queue == info->queue)) {
snd_seq_prioq_remove_events(q->tickq, client, info);
snd_seq_prioq_remove_events(q->timeq, client, info);
}
}
}
/*----------------------------------------------------------------*/
/*
* send events to all subscribed ports
*/
static void queue_broadcast_event(struct snd_seq_queue *q, struct snd_seq_event *ev,
int atomic, int hop)
{
struct snd_seq_event sev;
sev = *ev;
sev.flags = SNDRV_SEQ_TIME_STAMP_TICK|SNDRV_SEQ_TIME_MODE_ABS;
sev.time.tick = q->timer->tick.cur_tick;
sev.queue = q->queue;
sev.data.queue.queue = q->queue;
/* broadcast events from Timer port */
sev.source.client = SNDRV_SEQ_CLIENT_SYSTEM;
sev.source.port = SNDRV_SEQ_PORT_SYSTEM_TIMER;
sev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
snd_seq_kernel_client_dispatch(SNDRV_SEQ_CLIENT_SYSTEM, &sev, atomic, hop);
}
/*
* process a received queue-control event.
* this function is exported for seq_sync.c.
*/
static void snd_seq_queue_process_event(struct snd_seq_queue *q,
struct snd_seq_event *ev,
int atomic, int hop)
{
switch (ev->type) {
case SNDRV_SEQ_EVENT_START:
snd_seq_prioq_leave(q->tickq, ev->source.client, 1);
snd_seq_prioq_leave(q->timeq, ev->source.client, 1);
if (! snd_seq_timer_start(q->timer))
queue_broadcast_event(q, ev, atomic, hop);
break;
case SNDRV_SEQ_EVENT_CONTINUE:
if (! snd_seq_timer_continue(q->timer))
queue_broadcast_event(q, ev, atomic, hop);
break;
case SNDRV_SEQ_EVENT_STOP:
snd_seq_timer_stop(q->timer);
queue_broadcast_event(q, ev, atomic, hop);
break;
case SNDRV_SEQ_EVENT_TEMPO:
snd_seq_timer_set_tempo(q->timer, ev->data.queue.param.value);
queue_broadcast_event(q, ev, atomic, hop);
break;
case SNDRV_SEQ_EVENT_SETPOS_TICK:
if (snd_seq_timer_set_position_tick(q->timer, ev->data.queue.param.time.tick) == 0) {
queue_broadcast_event(q, ev, atomic, hop);
}
break;
case SNDRV_SEQ_EVENT_SETPOS_TIME:
if (snd_seq_timer_set_position_time(q->timer, ev->data.queue.param.time.time) == 0) {
queue_broadcast_event(q, ev, atomic, hop);
}
break;
case SNDRV_SEQ_EVENT_QUEUE_SKEW:
if (snd_seq_timer_set_skew(q->timer,
ev->data.queue.param.skew.value,
ev->data.queue.param.skew.base) == 0) {
queue_broadcast_event(q, ev, atomic, hop);
}
break;
}
}
/*
* Queue control via timer control port:
* this function is exported as a callback of timer port.
*/
int snd_seq_control_queue(struct snd_seq_event *ev, int atomic, int hop)
{
if (snd_BUG_ON(!ev))
return -EINVAL;
struct snd_seq_queue *q __free(snd_seq_queue) =
queueptr(ev->data.queue.queue);
if (q == NULL)
return -EINVAL;
if (!queue_access_lock(q, ev->source.client))
return -EPERM;
snd_seq_queue_process_event(q, ev, atomic, hop);
queue_access_unlock(q);
return 0;
}
/*----------------------------------------------------------------*/
#ifdef CONFIG_SND_PROC_FS
/* exported to seq_info.c */
void snd_seq_info_queues_read(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
int i, bpm;
struct snd_seq_timer *tmr;
bool locked;
int owner;
for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
struct snd_seq_queue *q __free(snd_seq_queue) = queueptr(i);
if (!q)
continue;
tmr = q->timer;
if (tmr->tempo)
bpm = (60000 * tmr->tempo_base) / tmr->tempo;
else
bpm = 0;
scoped_guard(spinlock_irq, &q->owner_lock) {
locked = q->locked;
owner = q->owner;
}
snd_iprintf(buffer, "queue %d: [%s]\n", q->queue, q->name);
snd_iprintf(buffer, "owned by client : %d\n", owner);
snd_iprintf(buffer, "lock status : %s\n", locked ? "Locked" : "Free");
snd_iprintf(buffer, "queued time events : %d\n", snd_seq_prioq_avail(q->timeq));
snd_iprintf(buffer, "queued tick events : %d\n", snd_seq_prioq_avail(q->tickq));
snd_iprintf(buffer, "timer state : %s\n", tmr->running ? "Running" : "Stopped");
snd_iprintf(buffer, "timer PPQ : %d\n", tmr->ppq);
snd_iprintf(buffer, "current tempo : %d\n", tmr->tempo);
snd_iprintf(buffer, "tempo base : %d ns\n", tmr->tempo_base);
snd_iprintf(buffer, "current BPM : %d\n", bpm);
snd_iprintf(buffer, "current time : %d.%09d s\n", tmr->cur_time.tv_sec, tmr->cur_time.tv_nsec);
snd_iprintf(buffer, "current tick : %d\n", tmr->tick.cur_tick);
snd_iprintf(buffer, "\n");
}
}
#endif /* CONFIG_SND_PROC_FS */
+95
View File
@@ -0,0 +1,95 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* ALSA sequencer Queue handling
* Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl>
*/
#ifndef __SND_SEQ_QUEUE_H
#define __SND_SEQ_QUEUE_H
#include "seq_memory.h"
#include "seq_prioq.h"
#include "seq_timer.h"
#include "seq_lock.h"
#include <linux/interrupt.h>
#include <linux/list.h>
#include <linux/bitops.h>
#define SEQ_QUEUE_NO_OWNER (-1)
struct snd_seq_queue {
int queue; /* queue number */
char name[64]; /* name of this queue */
struct snd_seq_prioq *tickq; /* midi tick event queue */
struct snd_seq_prioq *timeq; /* real-time event queue */
struct snd_seq_timer *timer; /* time keeper for this queue */
int owner; /* client that 'owns' the timer */
bool locked; /* timer is only accesibble by owner if set */
bool klocked; /* kernel lock (after START) */
bool check_again; /* concurrent access happened during check */
bool check_blocked; /* queue being checked */
unsigned int flags; /* status flags */
unsigned int info_flags; /* info for sync */
spinlock_t owner_lock;
spinlock_t check_lock;
/* clients which uses this queue (bitmap) */
DECLARE_BITMAP(clients_bitmap, SNDRV_SEQ_MAX_CLIENTS);
unsigned int clients; /* users of this queue */
struct mutex timer_mutex;
snd_use_lock_t use_lock;
};
/* get the number of current queues */
int snd_seq_queue_get_cur_queues(void);
/* delete queues */
void snd_seq_queues_delete(void);
/* create new queue (constructor) */
struct snd_seq_queue *snd_seq_queue_alloc(int client, int locked, unsigned int flags);
/* delete queue (destructor) */
int snd_seq_queue_delete(int client, int queueid);
/* final stage */
void snd_seq_queue_client_leave(int client);
/* enqueue a event received from one the clients */
int snd_seq_enqueue_event(struct snd_seq_event_cell *cell, int atomic, int hop);
/* Remove events */
void snd_seq_queue_remove_cells(int client, struct snd_seq_remove_events *info);
/* return pointer to queue structure for specified id */
struct snd_seq_queue *queueptr(int queueid);
/* unlock */
#define queuefree(q) snd_use_lock_free(&(q)->use_lock)
DEFINE_FREE(snd_seq_queue, struct snd_seq_queue *, if (!IS_ERR_OR_NULL(_T)) queuefree(_T))
/* return the (first) queue matching with the specified name */
struct snd_seq_queue *snd_seq_queue_find_name(char *name);
/* check single queue and dispatch events */
void snd_seq_check_queue(struct snd_seq_queue *q, int atomic, int hop);
/* access to queue's parameters */
int snd_seq_queue_check_access(int queueid, int client);
int snd_seq_queue_timer_set_tempo(int queueid, int client, struct snd_seq_queue_tempo *info);
int snd_seq_queue_set_owner(int queueid, int client, int locked);
int snd_seq_queue_timer_open(int queueid);
int snd_seq_queue_timer_close(int queueid);
int snd_seq_queue_use(int queueid, int client, int use);
int snd_seq_queue_is_used(int queueid, int client);
int snd_seq_control_queue(struct snd_seq_event *ev, int atomic, int hop);
#endif
+199
View File
@@ -0,0 +1,199 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* ALSA sequencer System services Client
* Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl>
*/
#include <linux/init.h>
#include <linux/export.h>
#include <linux/slab.h>
#include <sound/core.h>
#include "seq_system.h"
#include "seq_timer.h"
#include "seq_queue.h"
/* internal client that provide system services, access to timer etc. */
/*
* Port "Timer"
* - send tempo /start/stop etc. events to this port to manipulate the
* queue's timer. The queue address is specified in
* data.queue.queue.
* - this port supports subscription. The received timer events are
* broadcasted to all subscribed clients. The modified tempo
* value is stored on data.queue.value.
* The modifier client/port is not send.
*
* Port "Announce"
* - does not receive message
* - supports supscription. For each client or port attaching to or
* detaching from the system an announcement is send to the subscribed
* clients.
*
* Idea: the subscription mechanism might also work handy for distributing
* synchronisation and timing information. In this case we would ideally have
* a list of subscribers for each type of sync (time, tick), for each timing
* queue.
*
* NOTE: the queue to be started, stopped, etc. must be specified
* in data.queue.addr.queue field. queue is used only for
* scheduling, and no longer referred as affected queue.
* They are used only for timer broadcast (see above).
* -- iwai
*/
/* client id of our system client */
static int sysclient = -1;
/* port id numbers for this client */
static int announce_port = -1;
/* number of subscriptions to announce port */
static int announce_subscribed;
/* fill standard header data, source port & channel are filled in */
static int setheader(struct snd_seq_event * ev, int client, int port)
{
if (announce_port < 0 || !announce_subscribed)
return -ENODEV;
memset(ev, 0, sizeof(struct snd_seq_event));
ev->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK;
ev->flags |= SNDRV_SEQ_EVENT_LENGTH_FIXED;
ev->source.client = sysclient;
ev->source.port = announce_port;
ev->dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
/* fill data */
/*ev->data.addr.queue = SNDRV_SEQ_ADDRESS_UNKNOWN;*/
ev->data.addr.client = client;
ev->data.addr.port = port;
return 0;
}
/* entry points for broadcasting system events */
void snd_seq_system_broadcast(int client, int port, int type, bool atomic)
{
struct snd_seq_event ev;
if (setheader(&ev, client, port) < 0)
return;
ev.type = type;
snd_seq_kernel_client_dispatch(sysclient, &ev, atomic, 0);
}
EXPORT_SYMBOL_GPL(snd_seq_system_broadcast);
/* entry points for broadcasting system events */
int snd_seq_system_notify(int client, int port, struct snd_seq_event *ev,
bool atomic)
{
ev->flags = SNDRV_SEQ_EVENT_LENGTH_FIXED;
ev->source.client = sysclient;
ev->source.port = announce_port;
ev->dest.client = client;
ev->dest.port = port;
return snd_seq_kernel_client_dispatch(sysclient, ev, atomic, 0);
}
/* call-back handler for timer events */
static int event_input_timer(struct snd_seq_event * ev, int direct, void *private_data, int atomic, int hop)
{
return snd_seq_control_queue(ev, atomic, hop);
}
static int sys_announce_subscribe(void *private_data,
struct snd_seq_port_subscribe *info)
{
announce_subscribed++;
return 0;
}
static int sys_announce_unsubscribe(void *private_data,
struct snd_seq_port_subscribe *info)
{
if (snd_BUG_ON(!announce_subscribed))
return 0;
announce_subscribed--;
return 0;
}
/* register our internal client */
int __init snd_seq_system_client_init(void)
{
struct snd_seq_port_callback pcallbacks;
struct snd_seq_port_info *port;
int err;
port = kzalloc_obj(*port);
if (!port)
return -ENOMEM;
memset(&pcallbacks, 0, sizeof(pcallbacks));
pcallbacks.owner = THIS_MODULE;
pcallbacks.event_input = event_input_timer;
/* register client */
sysclient = snd_seq_create_kernel_client(NULL, 0, "System");
if (sysclient < 0) {
kfree(port);
return sysclient;
}
/* register timer */
strscpy(port->name, "Timer");
port->capability = SNDRV_SEQ_PORT_CAP_WRITE; /* accept queue control */
port->capability |= SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_SUBS_READ; /* for broadcast */
port->kernel = &pcallbacks;
port->type = 0;
port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT;
port->addr.client = sysclient;
port->addr.port = SNDRV_SEQ_PORT_SYSTEM_TIMER;
err = snd_seq_kernel_client_ctl(sysclient, SNDRV_SEQ_IOCTL_CREATE_PORT,
port);
if (err < 0)
goto error_port;
/* register announcement port */
strscpy(port->name, "Announce");
port->capability = SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_SUBS_READ; /* for broadcast only */
pcallbacks.event_input = NULL;
pcallbacks.subscribe = sys_announce_subscribe;
pcallbacks.unsubscribe = sys_announce_unsubscribe;
port->kernel = &pcallbacks;
port->type = 0;
port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT;
port->addr.client = sysclient;
port->addr.port = SNDRV_SEQ_PORT_SYSTEM_ANNOUNCE;
err = snd_seq_kernel_client_ctl(sysclient, SNDRV_SEQ_IOCTL_CREATE_PORT,
port);
if (err < 0)
goto error_port;
announce_port = port->addr.port;
kfree(port);
return 0;
error_port:
snd_seq_system_client_done();
kfree(port);
return err;
}
/* unregister our internal client */
void snd_seq_system_client_done(void)
{
int oldsysclient = sysclient;
if (oldsysclient >= 0) {
sysclient = -1;
announce_port = -1;
snd_seq_delete_kernel_client(oldsysclient);
}
}
+46
View File
@@ -0,0 +1,46 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* ALSA sequencer System Client
* Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
*/
#ifndef __SND_SEQ_SYSTEM_H
#define __SND_SEQ_SYSTEM_H
#include <sound/seq_kernel.h>
/* entry points for broadcasting system events */
void snd_seq_system_broadcast(int client, int port, int type, bool atomic);
/* normal system notification event broadcast */
#define notify_event(client, port, type) \
snd_seq_system_broadcast(client, port, type, false)
/* notify UMP EP/FB change event */
static inline void snd_seq_system_ump_notify(int client, int block, int type,
bool atomic)
{
/* reuse the existing snd_seq_system_broadcast():
* struct snd_seq_ev_ump_notify is compatible with struct snd_seq_addr
*/
snd_seq_system_broadcast(client, block, type, atomic);
}
#define snd_seq_system_client_ev_client_start(client) notify_event(client, 0, SNDRV_SEQ_EVENT_CLIENT_START)
#define snd_seq_system_client_ev_client_exit(client) notify_event(client, 0, SNDRV_SEQ_EVENT_CLIENT_EXIT)
#define snd_seq_system_client_ev_client_change(client) notify_event(client, 0, SNDRV_SEQ_EVENT_CLIENT_CHANGE)
#define snd_seq_system_client_ev_port_start(client, port) notify_event(client, port, SNDRV_SEQ_EVENT_PORT_START)
#define snd_seq_system_client_ev_port_exit(client, port) notify_event(client, port, SNDRV_SEQ_EVENT_PORT_EXIT)
#define snd_seq_system_client_ev_port_change(client, port) notify_event(client, port, SNDRV_SEQ_EVENT_PORT_CHANGE)
int snd_seq_system_notify(int client, int port, struct snd_seq_event *ev,
bool atomic);
/* register our internal client */
int snd_seq_system_client_init(void);
/* unregister our internal client */
void snd_seq_system_client_done(void);
#endif
+482
View File
@@ -0,0 +1,482 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* ALSA sequencer Timer
* Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl>
* Jaroslav Kysela <perex@perex.cz>
*/
#include <sound/core.h>
#include <linux/slab.h>
#include "seq_timer.h"
#include "seq_queue.h"
#include "seq_info.h"
/* allowed sequencer timer frequencies, in Hz */
#define MIN_FREQUENCY 10
#define MAX_FREQUENCY 6250
#define DEFAULT_FREQUENCY 1000
#define SKEW_BASE 0x10000 /* 16bit shift */
static void snd_seq_timer_set_tick_resolution(struct snd_seq_timer *tmr)
{
unsigned int threshold =
tmr->tempo_base == 1000 ? 1000000 : 10000;
if (tmr->tempo < threshold)
tmr->tick.resolution = (tmr->tempo * tmr->tempo_base) / tmr->ppq;
else {
/* might overflow.. */
unsigned int s;
s = tmr->tempo % tmr->ppq;
s = (s * tmr->tempo_base) / tmr->ppq;
tmr->tick.resolution = (tmr->tempo / tmr->ppq) * tmr->tempo_base;
tmr->tick.resolution += s;
}
if (tmr->tick.resolution <= 0)
tmr->tick.resolution = 1;
snd_seq_timer_update_tick(&tmr->tick, 0);
}
/* create new timer (constructor) */
struct snd_seq_timer *snd_seq_timer_new(void)
{
struct snd_seq_timer *tmr;
tmr = kzalloc_obj(*tmr);
if (!tmr)
return NULL;
spin_lock_init(&tmr->lock);
/* reset setup to defaults */
snd_seq_timer_defaults(tmr);
/* reset time */
snd_seq_timer_reset(tmr);
return tmr;
}
/* delete timer (destructor) */
void snd_seq_timer_delete(struct snd_seq_timer **tmr)
{
struct snd_seq_timer *t = *tmr;
struct snd_timer_instance *ti;
if (t == NULL) {
pr_debug("ALSA: seq: snd_seq_timer_delete() called with NULL timer\n");
return;
}
scoped_guard(spinlock_irq, &t->lock) {
ti = t->timeri;
t->timeri = NULL;
}
if (ti) {
snd_timer_close(ti);
snd_timer_instance_free(ti);
}
*tmr = NULL;
t->running = 0;
/* reset time */
snd_seq_timer_stop(t);
snd_seq_timer_reset(t);
kfree(t);
}
void snd_seq_timer_defaults(struct snd_seq_timer * tmr)
{
guard(spinlock_irqsave)(&tmr->lock);
/* setup defaults */
tmr->ppq = 96; /* 96 PPQ */
tmr->tempo = 500000; /* 120 BPM */
tmr->tempo_base = 1000; /* 1us */
snd_seq_timer_set_tick_resolution(tmr);
tmr->running = 0;
tmr->type = SNDRV_SEQ_TIMER_ALSA;
tmr->alsa_id.dev_class = seq_default_timer_class;
tmr->alsa_id.dev_sclass = seq_default_timer_sclass;
tmr->alsa_id.card = seq_default_timer_card;
tmr->alsa_id.device = seq_default_timer_device;
tmr->alsa_id.subdevice = seq_default_timer_subdevice;
tmr->preferred_resolution = seq_default_timer_resolution;
tmr->skew = tmr->skew_base = SKEW_BASE;
}
static void seq_timer_reset(struct snd_seq_timer *tmr)
{
/* reset time & songposition */
tmr->cur_time.tv_sec = 0;
tmr->cur_time.tv_nsec = 0;
tmr->tick.cur_tick = 0;
tmr->tick.fraction = 0;
}
void snd_seq_timer_reset(struct snd_seq_timer *tmr)
{
guard(spinlock_irqsave)(&tmr->lock);
seq_timer_reset(tmr);
}
/* called by timer interrupt routine. the period time since previous invocation is passed */
static void snd_seq_timer_interrupt(struct snd_timer_instance *timeri,
unsigned long resolution,
unsigned long ticks)
{
struct snd_seq_queue *q = timeri->callback_data;
struct snd_seq_timer *tmr;
if (q == NULL)
return;
tmr = q->timer;
if (tmr == NULL)
return;
scoped_guard(spinlock_irqsave, &tmr->lock) {
if (!tmr->running)
return;
resolution *= ticks;
if (tmr->skew != tmr->skew_base) {
/* FIXME: assuming skew_base = 0x10000 */
resolution = (resolution >> 16) * tmr->skew +
(((resolution & 0xffff) * tmr->skew) >> 16);
}
/* update timer */
snd_seq_inc_time_nsec(&tmr->cur_time, resolution);
/* calculate current tick */
snd_seq_timer_update_tick(&tmr->tick, resolution);
/* register actual time of this timer update */
ktime_get_ts64(&tmr->last_update);
}
/* check queues and dispatch events */
snd_seq_check_queue(q, 1, 0);
}
/* set current tempo */
int snd_seq_timer_set_tempo(struct snd_seq_timer * tmr, int tempo)
{
if (snd_BUG_ON(!tmr))
return -EINVAL;
if (tempo <= 0)
return -EINVAL;
guard(spinlock_irqsave)(&tmr->lock);
if ((unsigned int)tempo != tmr->tempo) {
tmr->tempo = tempo;
snd_seq_timer_set_tick_resolution(tmr);
}
return 0;
}
/* set current tempo, ppq and base in a shot */
int snd_seq_timer_set_tempo_ppq(struct snd_seq_timer *tmr, int tempo, int ppq,
unsigned int tempo_base)
{
int changed;
if (snd_BUG_ON(!tmr))
return -EINVAL;
if (tempo <= 0 || ppq <= 0)
return -EINVAL;
/* allow only 10ns or 1us tempo base for now */
if (tempo_base && tempo_base != 10 && tempo_base != 1000)
return -EINVAL;
guard(spinlock_irqsave)(&tmr->lock);
if (tmr->running && (ppq != tmr->ppq)) {
/* refuse to change ppq on running timers */
/* because it will upset the song position (ticks) */
pr_debug("ALSA: seq: cannot change ppq of a running timer\n");
return -EBUSY;
}
changed = (tempo != tmr->tempo) || (ppq != tmr->ppq);
tmr->tempo = tempo;
tmr->ppq = ppq;
tmr->tempo_base = tempo_base ? tempo_base : 1000;
if (changed)
snd_seq_timer_set_tick_resolution(tmr);
return 0;
}
/* set current tick position */
int snd_seq_timer_set_position_tick(struct snd_seq_timer *tmr,
snd_seq_tick_time_t position)
{
if (snd_BUG_ON(!tmr))
return -EINVAL;
guard(spinlock_irqsave)(&tmr->lock);
tmr->tick.cur_tick = position;
tmr->tick.fraction = 0;
return 0;
}
/* set current real-time position */
int snd_seq_timer_set_position_time(struct snd_seq_timer *tmr,
snd_seq_real_time_t position)
{
if (snd_BUG_ON(!tmr))
return -EINVAL;
snd_seq_sanity_real_time(&position);
guard(spinlock_irqsave)(&tmr->lock);
tmr->cur_time = position;
return 0;
}
/* set timer skew */
int snd_seq_timer_set_skew(struct snd_seq_timer *tmr, unsigned int skew,
unsigned int base)
{
if (snd_BUG_ON(!tmr))
return -EINVAL;
/* FIXME */
if (base != SKEW_BASE) {
pr_debug("ALSA: seq: invalid skew base 0x%x\n", base);
return -EINVAL;
}
guard(spinlock_irqsave)(&tmr->lock);
tmr->skew = skew;
return 0;
}
int snd_seq_timer_open(struct snd_seq_queue *q)
{
struct snd_timer_instance *t;
struct snd_seq_timer *tmr;
char str[32];
int err;
tmr = q->timer;
if (snd_BUG_ON(!tmr))
return -EINVAL;
if (tmr->timeri)
return -EBUSY;
sprintf(str, "sequencer queue %i", q->queue);
if (tmr->type != SNDRV_SEQ_TIMER_ALSA) /* standard ALSA timer */
return -EINVAL;
if (tmr->alsa_id.dev_class != SNDRV_TIMER_CLASS_SLAVE)
tmr->alsa_id.dev_sclass = SNDRV_TIMER_SCLASS_SEQUENCER;
t = snd_timer_instance_new(str);
if (!t)
return -ENOMEM;
t->callback = snd_seq_timer_interrupt;
t->callback_data = q;
t->flags |= SNDRV_TIMER_IFLG_AUTO;
err = snd_timer_open(t, &tmr->alsa_id, q->queue);
if (err < 0 && tmr->alsa_id.dev_class != SNDRV_TIMER_CLASS_SLAVE) {
if (tmr->alsa_id.dev_class != SNDRV_TIMER_CLASS_GLOBAL ||
tmr->alsa_id.device != SNDRV_TIMER_GLOBAL_SYSTEM) {
struct snd_timer_id tid;
memset(&tid, 0, sizeof(tid));
tid.dev_class = SNDRV_TIMER_CLASS_GLOBAL;
tid.dev_sclass = SNDRV_TIMER_SCLASS_SEQUENCER;
tid.card = -1;
tid.device = SNDRV_TIMER_GLOBAL_SYSTEM;
err = snd_timer_open(t, &tid, q->queue);
}
}
if (err < 0) {
pr_err("ALSA: seq fatal error: cannot create timer (%i)\n", err);
snd_timer_instance_free(t);
return err;
}
scoped_guard(spinlock_irq, &tmr->lock) {
if (tmr->timeri)
err = -EBUSY;
else
tmr->timeri = t;
}
if (err < 0) {
snd_timer_close(t);
snd_timer_instance_free(t);
return err;
}
return 0;
}
int snd_seq_timer_close(struct snd_seq_queue *q)
{
struct snd_seq_timer *tmr;
struct snd_timer_instance *t;
tmr = q->timer;
if (snd_BUG_ON(!tmr))
return -EINVAL;
scoped_guard(spinlock_irq, &tmr->lock) {
t = tmr->timeri;
tmr->timeri = NULL;
}
if (t) {
snd_timer_close(t);
snd_timer_instance_free(t);
}
return 0;
}
static int seq_timer_stop(struct snd_seq_timer *tmr)
{
if (! tmr->timeri)
return -EINVAL;
if (!tmr->running)
return 0;
tmr->running = 0;
snd_timer_pause(tmr->timeri);
return 0;
}
int snd_seq_timer_stop(struct snd_seq_timer *tmr)
{
guard(spinlock_irqsave)(&tmr->lock);
return seq_timer_stop(tmr);
}
static int initialize_timer(struct snd_seq_timer *tmr)
{
unsigned long freq;
struct snd_timer *t __free(snd_timeri_timer) =
snd_timeri_timer_get(tmr->timeri);
if (!t)
return -EINVAL;
freq = tmr->preferred_resolution;
if (!freq)
freq = DEFAULT_FREQUENCY;
else if (freq < MIN_FREQUENCY)
freq = MIN_FREQUENCY;
else if (freq > MAX_FREQUENCY)
freq = MAX_FREQUENCY;
tmr->ticks = 1;
if (!(t->hw.flags & SNDRV_TIMER_HW_SLAVE)) {
unsigned long r = snd_timer_resolution(tmr->timeri);
unsigned long den;
if (r && !check_mul_overflow(r, freq, &den))
tmr->ticks = max(1U, (unsigned int)(1000000000uL / den));
}
tmr->initialized = 1;
return 0;
}
static int seq_timer_start(struct snd_seq_timer *tmr)
{
if (! tmr->timeri)
return -EINVAL;
if (tmr->running)
seq_timer_stop(tmr);
seq_timer_reset(tmr);
if (initialize_timer(tmr) < 0)
return -EINVAL;
snd_timer_start(tmr->timeri, tmr->ticks);
tmr->running = 1;
ktime_get_ts64(&tmr->last_update);
return 0;
}
int snd_seq_timer_start(struct snd_seq_timer *tmr)
{
guard(spinlock_irqsave)(&tmr->lock);
return seq_timer_start(tmr);
}
static int seq_timer_continue(struct snd_seq_timer *tmr)
{
if (! tmr->timeri)
return -EINVAL;
if (tmr->running)
return -EBUSY;
if (! tmr->initialized) {
seq_timer_reset(tmr);
if (initialize_timer(tmr) < 0)
return -EINVAL;
}
snd_timer_start(tmr->timeri, tmr->ticks);
tmr->running = 1;
ktime_get_ts64(&tmr->last_update);
return 0;
}
int snd_seq_timer_continue(struct snd_seq_timer *tmr)
{
guard(spinlock_irqsave)(&tmr->lock);
return seq_timer_continue(tmr);
}
/* return current 'real' time. use timeofday() to get better granularity. */
snd_seq_real_time_t snd_seq_timer_get_cur_time(struct snd_seq_timer *tmr,
bool adjust_ktime)
{
snd_seq_real_time_t cur_time;
guard(spinlock_irqsave)(&tmr->lock);
cur_time = tmr->cur_time;
if (adjust_ktime && tmr->running) {
struct timespec64 tm;
ktime_get_ts64(&tm);
tm = timespec64_sub(tm, tmr->last_update);
cur_time.tv_nsec += tm.tv_nsec;
cur_time.tv_sec += tm.tv_sec;
snd_seq_sanity_real_time(&cur_time);
}
return cur_time;
}
/* TODO: use interpolation on tick queue (will only be useful for very
high PPQ values) */
snd_seq_tick_time_t snd_seq_timer_get_cur_tick(struct snd_seq_timer *tmr)
{
guard(spinlock_irqsave)(&tmr->lock);
return tmr->tick.cur_tick;
}
#ifdef CONFIG_SND_PROC_FS
/* exported to seq_info.c */
void snd_seq_info_timer_read(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
int idx;
struct snd_seq_timer *tmr;
struct snd_timer_instance *ti;
unsigned long resolution;
for (idx = 0; idx < SNDRV_SEQ_MAX_QUEUES; idx++) {
struct snd_seq_queue *q __free(snd_seq_queue) = queueptr(idx);
if (q == NULL)
continue;
scoped_guard(mutex, &q->timer_mutex) {
tmr = q->timer;
if (!tmr)
break;
ti = tmr->timeri;
if (!ti)
break;
struct snd_timer *t __free(snd_timeri_timer) =
snd_timeri_timer_get(ti);
snd_iprintf(buffer, "Timer for queue %i : %s\n",
q->queue,
t ? t->name : "DEAD");
resolution = snd_timer_resolution(ti) * tmr->ticks;
snd_iprintf(buffer, " Period time : %lu.%09lu\n", resolution / 1000000000, resolution % 1000000000);
snd_iprintf(buffer, " Skew : %u / %u\n", tmr->skew, tmr->skew_base);
}
}
}
#endif /* CONFIG_SND_PROC_FS */
+134
View File
@@ -0,0 +1,134 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* ALSA sequencer Timer
* Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl>
*/
#ifndef __SND_SEQ_TIMER_H
#define __SND_SEQ_TIMER_H
#include <sound/timer.h>
#include <sound/seq_kernel.h>
struct snd_seq_timer_tick {
snd_seq_tick_time_t cur_tick; /* current tick */
unsigned long resolution; /* time per tick in nsec */
unsigned long fraction; /* current time per tick in nsec */
};
struct snd_seq_timer {
/* ... tempo / offset / running state */
unsigned int running:1, /* running state of queue */
initialized:1; /* timer is initialized */
unsigned int tempo; /* current tempo, us/tick */
int ppq; /* time resolution, ticks/quarter */
snd_seq_real_time_t cur_time; /* current time */
struct snd_seq_timer_tick tick; /* current tick */
int tick_updated;
int type; /* timer type */
struct snd_timer_id alsa_id; /* ALSA's timer ID */
struct snd_timer_instance *timeri; /* timer instance */
unsigned int ticks;
unsigned long preferred_resolution; /* timer resolution, ticks/sec */
unsigned int skew;
unsigned int skew_base;
unsigned int tempo_base;
struct timespec64 last_update; /* time of last clock update, used for interpolation */
spinlock_t lock;
};
/* create new timer (constructor) */
struct snd_seq_timer *snd_seq_timer_new(void);
/* delete timer (destructor) */
void snd_seq_timer_delete(struct snd_seq_timer **tmr);
/* */
static inline void snd_seq_timer_update_tick(struct snd_seq_timer_tick *tick,
unsigned long resolution)
{
if (tick->resolution > 0) {
tick->fraction += resolution;
tick->cur_tick += (unsigned int)(tick->fraction / tick->resolution);
tick->fraction %= tick->resolution;
}
}
/* compare timestamp between events */
/* return 1 if a >= b; otherwise return 0 */
static inline int snd_seq_compare_tick_time(snd_seq_tick_time_t *a, snd_seq_tick_time_t *b)
{
/* compare ticks */
return (*a >= *b);
}
static inline int snd_seq_compare_real_time(snd_seq_real_time_t *a, snd_seq_real_time_t *b)
{
/* compare real time */
if (a->tv_sec > b->tv_sec)
return 1;
if ((a->tv_sec == b->tv_sec) && (a->tv_nsec >= b->tv_nsec))
return 1;
return 0;
}
static inline void snd_seq_sanity_real_time(snd_seq_real_time_t *tm)
{
while (tm->tv_nsec >= 1000000000) {
/* roll-over */
tm->tv_nsec -= 1000000000;
tm->tv_sec++;
}
}
/* increment timestamp */
static inline void snd_seq_inc_real_time(snd_seq_real_time_t *tm, snd_seq_real_time_t *inc)
{
tm->tv_sec += inc->tv_sec;
tm->tv_nsec += inc->tv_nsec;
snd_seq_sanity_real_time(tm);
}
static inline void snd_seq_inc_time_nsec(snd_seq_real_time_t *tm, unsigned long nsec)
{
tm->tv_nsec += nsec;
snd_seq_sanity_real_time(tm);
}
/* called by timer isr */
struct snd_seq_queue;
int snd_seq_timer_open(struct snd_seq_queue *q);
int snd_seq_timer_close(struct snd_seq_queue *q);
void snd_seq_timer_defaults(struct snd_seq_timer *tmr);
void snd_seq_timer_reset(struct snd_seq_timer *tmr);
int snd_seq_timer_stop(struct snd_seq_timer *tmr);
int snd_seq_timer_start(struct snd_seq_timer *tmr);
int snd_seq_timer_continue(struct snd_seq_timer *tmr);
int snd_seq_timer_set_tempo(struct snd_seq_timer *tmr, int tempo);
int snd_seq_timer_set_tempo_ppq(struct snd_seq_timer *tmr, int tempo, int ppq,
unsigned int tempo_base);
int snd_seq_timer_set_position_tick(struct snd_seq_timer *tmr, snd_seq_tick_time_t position);
int snd_seq_timer_set_position_time(struct snd_seq_timer *tmr, snd_seq_real_time_t position);
int snd_seq_timer_set_skew(struct snd_seq_timer *tmr, unsigned int skew, unsigned int base);
snd_seq_real_time_t snd_seq_timer_get_cur_time(struct snd_seq_timer *tmr,
bool adjust_ktime);
snd_seq_tick_time_t snd_seq_timer_get_cur_tick(struct snd_seq_timer *tmr);
extern int seq_default_timer_class;
extern int seq_default_timer_sclass;
extern int seq_default_timer_card;
extern int seq_default_timer_device;
extern int seq_default_timer_subdevice;
extern int seq_default_timer_resolution;
#endif
+550
View File
@@ -0,0 +1,550 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/* ALSA sequencer binding for UMP device */
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/mutex.h>
#include <linux/string.h>
#include <linux/module.h>
#include <asm/byteorder.h>
#include <sound/core.h>
#include <sound/ump.h>
#include <sound/seq_kernel.h>
#include <sound/seq_device.h>
#include "seq_clientmgr.h"
#include "seq_system.h"
struct seq_ump_client;
struct seq_ump_group;
enum {
STR_IN = SNDRV_RAWMIDI_STREAM_INPUT,
STR_OUT = SNDRV_RAWMIDI_STREAM_OUTPUT
};
/* context for UMP input parsing, per EP */
struct seq_ump_input_buffer {
unsigned char len; /* total length in words */
unsigned char pending; /* pending words */
unsigned char type; /* parsed UMP packet type */
unsigned char group; /* parsed UMP packet group */
u32 buf[4]; /* incoming UMP packet */
};
/* sequencer client, per UMP EP (rawmidi) */
struct seq_ump_client {
struct snd_ump_endpoint *ump; /* assigned endpoint */
int seq_client; /* sequencer client id */
int opened[2]; /* current opens for each direction */
rwlock_t output_lock; /* protects out_rfile output access */
struct snd_rawmidi_file out_rfile; /* rawmidi for output */
struct seq_ump_input_buffer input; /* input parser context */
void *ump_info[SNDRV_UMP_MAX_BLOCKS + 1]; /* shadow of seq client ump_info */
struct work_struct group_notify_work; /* FB change notification */
};
/* number of 32bit words for each UMP message type */
static unsigned char ump_packet_words[0x10] = {
1, 1, 1, 2, 2, 4, 1, 1, 2, 2, 2, 3, 3, 4, 4, 4
};
/* conversion between UMP group and seq port;
* assume the port number is equal with UMP group number (1-based)
*/
static unsigned char ump_group_to_seq_port(unsigned char group)
{
return group + 1;
}
/* process the incoming rawmidi stream */
static void seq_ump_input_receive(struct snd_ump_endpoint *ump,
const u32 *val, int words)
{
struct seq_ump_client *client = ump->seq_client;
struct snd_seq_ump_event ev = {};
if (!client->opened[STR_IN])
return;
if (ump_is_groupless_msg(ump_message_type(*val)))
ev.source.port = 0; /* UMP EP port */
else
ev.source.port = ump_group_to_seq_port(ump_message_group(*val));
ev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
ev.flags = SNDRV_SEQ_EVENT_UMP;
memcpy(ev.ump, val, words << 2);
snd_seq_kernel_client_dispatch(client->seq_client,
(struct snd_seq_event *)&ev,
true, 0);
}
/* process an input sequencer event; only deal with UMP types */
static int seq_ump_process_event(struct snd_seq_event *ev, int direct,
void *private_data, int atomic, int hop)
{
struct seq_ump_client *client = private_data;
struct snd_rawmidi_substream *substream;
struct snd_seq_ump_event *ump_ev;
unsigned char type;
int len;
guard(read_lock_irqsave)(&client->output_lock);
substream = client->out_rfile.output;
if (!substream)
return -ENODEV;
if (!snd_seq_ev_is_ump(ev))
return 0; /* invalid event, skip */
ump_ev = (struct snd_seq_ump_event *)ev;
type = ump_message_type(ump_ev->ump[0]);
len = ump_packet_words[type];
if (len > 4)
return 0; // invalid - skip
snd_rawmidi_kernel_write(substream, ev->data.raw8.d, len << 2);
return 0;
}
/* open the rawmidi */
static int seq_ump_client_open(struct seq_ump_client *client, int dir)
{
struct snd_ump_endpoint *ump = client->ump;
struct snd_rawmidi_file rfile = {};
int err;
guard(mutex)(&ump->open_mutex);
if (dir == STR_OUT && !client->opened[dir]) {
err = snd_rawmidi_kernel_open(&ump->core, 0,
SNDRV_RAWMIDI_LFLG_OUTPUT |
SNDRV_RAWMIDI_LFLG_APPEND,
&rfile);
if (err < 0)
return err;
scoped_guard(write_lock_irqsave, &client->output_lock)
client->out_rfile = rfile;
}
client->opened[dir]++;
return 0;
}
/* close the rawmidi */
static int seq_ump_client_close(struct seq_ump_client *client, int dir)
{
struct snd_ump_endpoint *ump = client->ump;
struct snd_rawmidi_file rfile = {};
guard(mutex)(&ump->open_mutex);
if (!--client->opened[dir]) {
if (dir == STR_OUT) {
scoped_guard(write_lock_irqsave, &client->output_lock) {
rfile = client->out_rfile;
client->out_rfile = (struct snd_rawmidi_file){};
}
if (rfile.rmidi)
snd_rawmidi_kernel_release(&rfile);
}
}
return 0;
}
/* sequencer subscription ops for each client */
static int seq_ump_subscribe(void *pdata, struct snd_seq_port_subscribe *info)
{
struct seq_ump_client *client = pdata;
return seq_ump_client_open(client, STR_IN);
}
static int seq_ump_unsubscribe(void *pdata, struct snd_seq_port_subscribe *info)
{
struct seq_ump_client *client = pdata;
return seq_ump_client_close(client, STR_IN);
}
static int seq_ump_use(void *pdata, struct snd_seq_port_subscribe *info)
{
struct seq_ump_client *client = pdata;
return seq_ump_client_open(client, STR_OUT);
}
static int seq_ump_unuse(void *pdata, struct snd_seq_port_subscribe *info)
{
struct seq_ump_client *client = pdata;
return seq_ump_client_close(client, STR_OUT);
}
/* fill port_info from the given UMP EP and group info */
static void fill_port_info(struct snd_seq_port_info *port,
struct seq_ump_client *client,
struct snd_ump_group *group)
{
unsigned int rawmidi_info = client->ump->core.info_flags;
port->addr.client = client->seq_client;
port->addr.port = ump_group_to_seq_port(group->group);
port->capability = 0;
if (rawmidi_info & SNDRV_RAWMIDI_INFO_OUTPUT)
port->capability |= SNDRV_SEQ_PORT_CAP_WRITE |
SNDRV_SEQ_PORT_CAP_SYNC_WRITE |
SNDRV_SEQ_PORT_CAP_SUBS_WRITE;
if (rawmidi_info & SNDRV_RAWMIDI_INFO_INPUT)
port->capability |= SNDRV_SEQ_PORT_CAP_READ |
SNDRV_SEQ_PORT_CAP_SYNC_READ |
SNDRV_SEQ_PORT_CAP_SUBS_READ;
if (rawmidi_info & SNDRV_RAWMIDI_INFO_DUPLEX)
port->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;
if (group->dir_bits & (1 << STR_IN))
port->direction |= SNDRV_SEQ_PORT_DIR_INPUT;
if (group->dir_bits & (1 << STR_OUT))
port->direction |= SNDRV_SEQ_PORT_DIR_OUTPUT;
port->ump_group = group->group + 1;
if (!group->active)
port->capability |= SNDRV_SEQ_PORT_CAP_INACTIVE;
if (group->is_midi1)
port->flags |= SNDRV_SEQ_PORT_FLG_IS_MIDI1;
port->type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC |
SNDRV_SEQ_PORT_TYPE_MIDI_UMP |
SNDRV_SEQ_PORT_TYPE_HARDWARE |
SNDRV_SEQ_PORT_TYPE_PORT;
port->midi_channels = 16;
if (*group->name)
snprintf(port->name, sizeof(port->name), "Group %d (%.53s)",
group->group + 1, group->name);
else
sprintf(port->name, "Group %d", group->group + 1);
}
/* skip non-existing group for static blocks */
static bool skip_group(struct seq_ump_client *client, struct snd_ump_group *group)
{
return !group->valid &&
(client->ump->info.flags & SNDRV_UMP_EP_INFO_STATIC_BLOCKS);
}
/* create a new sequencer port per UMP group */
static int seq_ump_group_init(struct seq_ump_client *client, int group_index)
{
struct snd_ump_group *group = &client->ump->groups[group_index];
struct snd_seq_port_callback pcallbacks;
if (skip_group(client, group))
return 0;
struct snd_seq_port_info *port __free(kfree) =
kzalloc_obj(*port);
if (!port)
return -ENOMEM;
fill_port_info(port, client, group);
port->flags |= SNDRV_SEQ_PORT_FLG_GIVEN_PORT;
memset(&pcallbacks, 0, sizeof(pcallbacks));
pcallbacks.owner = THIS_MODULE;
pcallbacks.private_data = client;
pcallbacks.subscribe = seq_ump_subscribe;
pcallbacks.unsubscribe = seq_ump_unsubscribe;
pcallbacks.use = seq_ump_use;
pcallbacks.unuse = seq_ump_unuse;
pcallbacks.event_input = seq_ump_process_event;
port->kernel = &pcallbacks;
return snd_seq_kernel_client_ctl(client->seq_client,
SNDRV_SEQ_IOCTL_CREATE_PORT,
port);
}
/* update the sequencer ports; called from notify_fb_change callback */
static void update_port_infos(struct seq_ump_client *client)
{
int i, err;
struct snd_seq_port_info *old __free(kfree) =
kzalloc_obj(*old);
struct snd_seq_port_info *new __free(kfree) =
kzalloc_obj(*new);
if (!old || !new)
return;
for (i = 0; i < SNDRV_UMP_MAX_GROUPS; i++) {
if (skip_group(client, &client->ump->groups[i]))
continue;
old->addr.client = client->seq_client;
old->addr.port = ump_group_to_seq_port(i);
err = snd_seq_kernel_client_ctl(client->seq_client,
SNDRV_SEQ_IOCTL_GET_PORT_INFO,
old);
if (err < 0)
continue;
fill_port_info(new, client, &client->ump->groups[i]);
if (old->capability == new->capability &&
!strcmp(old->name, new->name))
continue;
err = snd_seq_kernel_client_ctl(client->seq_client,
SNDRV_SEQ_IOCTL_SET_PORT_INFO,
new);
if (err < 0)
continue;
}
}
/* create a UMP Endpoint port */
static int create_ump_endpoint_port(struct seq_ump_client *client)
{
struct snd_seq_port_callback pcallbacks;
unsigned int rawmidi_info = client->ump->core.info_flags;
int err;
struct snd_seq_port_info *port __free(kfree) =
kzalloc_obj(*port);
if (!port)
return -ENOMEM;
port->addr.client = client->seq_client;
port->addr.port = 0; /* fixed */
port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT;
port->capability = SNDRV_SEQ_PORT_CAP_UMP_ENDPOINT;
if (rawmidi_info & SNDRV_RAWMIDI_INFO_INPUT) {
port->capability |= SNDRV_SEQ_PORT_CAP_READ |
SNDRV_SEQ_PORT_CAP_SYNC_READ |
SNDRV_SEQ_PORT_CAP_SUBS_READ;
port->direction |= SNDRV_SEQ_PORT_DIR_INPUT;
}
if (rawmidi_info & SNDRV_RAWMIDI_INFO_OUTPUT) {
port->capability |= SNDRV_SEQ_PORT_CAP_WRITE |
SNDRV_SEQ_PORT_CAP_SYNC_WRITE |
SNDRV_SEQ_PORT_CAP_SUBS_WRITE;
port->direction |= SNDRV_SEQ_PORT_DIR_OUTPUT;
}
if (rawmidi_info & SNDRV_RAWMIDI_INFO_DUPLEX)
port->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;
port->ump_group = 0; /* no associated group, no conversion */
port->type = SNDRV_SEQ_PORT_TYPE_MIDI_UMP |
SNDRV_SEQ_PORT_TYPE_HARDWARE |
SNDRV_SEQ_PORT_TYPE_PORT;
port->midi_channels = 16;
strscpy(port->name, "MIDI 2.0");
memset(&pcallbacks, 0, sizeof(pcallbacks));
pcallbacks.owner = THIS_MODULE;
pcallbacks.private_data = client;
if (rawmidi_info & SNDRV_RAWMIDI_INFO_INPUT) {
pcallbacks.subscribe = seq_ump_subscribe;
pcallbacks.unsubscribe = seq_ump_unsubscribe;
}
if (rawmidi_info & SNDRV_RAWMIDI_INFO_OUTPUT) {
pcallbacks.use = seq_ump_use;
pcallbacks.unuse = seq_ump_unuse;
pcallbacks.event_input = seq_ump_process_event;
}
port->kernel = &pcallbacks;
err = snd_seq_kernel_client_ctl(client->seq_client,
SNDRV_SEQ_IOCTL_CREATE_PORT,
port);
return err;
}
/* release the client resources */
static void seq_ump_client_free(struct seq_ump_client *client)
{
cancel_work_sync(&client->group_notify_work);
if (client->seq_client >= 0)
snd_seq_delete_kernel_client(client->seq_client);
client->ump->seq_ops = NULL;
client->ump->seq_client = NULL;
kfree(client);
}
/* update the MIDI version for the given client */
static void setup_client_midi_version(struct seq_ump_client *client)
{
struct snd_seq_client *cptr;
cptr = snd_seq_kernel_client_get(client->seq_client);
if (!cptr)
return;
if (client->ump->info.protocol & SNDRV_UMP_EP_INFO_PROTO_MIDI2)
cptr->midi_version = SNDRV_SEQ_CLIENT_UMP_MIDI_2_0;
else
cptr->midi_version = SNDRV_SEQ_CLIENT_UMP_MIDI_1_0;
snd_seq_kernel_client_put(cptr);
}
/* set up client's group_filter bitmap */
static void setup_client_group_filter(struct seq_ump_client *client)
{
struct snd_seq_client *cptr;
unsigned int filter;
int p;
cptr = snd_seq_kernel_client_get(client->seq_client);
if (!cptr)
return;
filter = SND_SEQ_GROUP_FILTER_GROUPS; /* always allow groupless messages */
for (p = 0; p < SNDRV_UMP_MAX_GROUPS; p++) {
if (client->ump->groups[p].active)
filter &= ~(1U << (p + 1));
}
cptr->group_filter = filter;
snd_seq_kernel_client_put(cptr);
}
/* UMP group change notification */
static void handle_group_notify(struct work_struct *work)
{
struct seq_ump_client *client =
container_of(work, struct seq_ump_client, group_notify_work);
update_port_infos(client);
setup_client_group_filter(client);
}
/* UMP EP change notification */
static int seq_ump_notify_ep_change(struct snd_ump_endpoint *ump)
{
struct seq_ump_client *client = ump->seq_client;
struct snd_seq_client *cptr;
int client_id;
if (!client)
return -ENODEV;
client_id = client->seq_client;
cptr = snd_seq_kernel_client_get(client_id);
if (!cptr)
return -ENODEV;
snd_seq_system_ump_notify(client_id, 0, SNDRV_SEQ_EVENT_UMP_EP_CHANGE,
true);
/* update sequencer client name if needed */
if (*ump->core.name && strcmp(ump->core.name, cptr->name)) {
strscpy(cptr->name, ump->core.name, sizeof(cptr->name));
snd_seq_system_client_ev_client_change(client_id);
}
snd_seq_kernel_client_put(cptr);
return 0;
}
/* UMP FB change notification */
static int seq_ump_notify_fb_change(struct snd_ump_endpoint *ump,
struct snd_ump_block *fb)
{
struct seq_ump_client *client = ump->seq_client;
if (!client)
return -ENODEV;
schedule_work(&client->group_notify_work);
snd_seq_system_ump_notify(client->seq_client, fb->info.block_id,
SNDRV_SEQ_EVENT_UMP_BLOCK_CHANGE,
true);
return 0;
}
/* UMP protocol change notification; just update the midi_version field */
static int seq_ump_switch_protocol(struct snd_ump_endpoint *ump)
{
struct seq_ump_client *client = ump->seq_client;
if (!client)
return -ENODEV;
setup_client_midi_version(client);
snd_seq_system_ump_notify(client->seq_client, 0,
SNDRV_SEQ_EVENT_UMP_EP_CHANGE,
true);
return 0;
}
static const struct snd_seq_ump_ops seq_ump_ops = {
.input_receive = seq_ump_input_receive,
.notify_ep_change = seq_ump_notify_ep_change,
.notify_fb_change = seq_ump_notify_fb_change,
.switch_protocol = seq_ump_switch_protocol,
};
/* create a sequencer client and ports for the given UMP endpoint */
static int snd_seq_ump_probe(struct snd_seq_device *dev)
{
struct snd_ump_endpoint *ump = dev->private_data;
struct snd_card *card = dev->card;
struct seq_ump_client *client;
struct snd_ump_block *fb;
struct snd_seq_client *cptr;
int p, err;
client = kzalloc_obj(*client);
if (!client)
return -ENOMEM;
INIT_WORK(&client->group_notify_work, handle_group_notify);
client->ump = ump;
rwlock_init(&client->output_lock);
client->seq_client =
snd_seq_create_kernel_client(card, ump->core.device,
ump->core.name);
if (client->seq_client < 0) {
err = client->seq_client;
goto error;
}
client->ump_info[0] = &ump->info;
list_for_each_entry(fb, &ump->block_list, list)
client->ump_info[fb->info.block_id + 1] = &fb->info;
setup_client_midi_version(client);
for (p = 0; p < SNDRV_UMP_MAX_GROUPS; p++) {
err = seq_ump_group_init(client, p);
if (err < 0)
goto error;
}
setup_client_group_filter(client);
err = create_ump_endpoint_port(client);
if (err < 0)
goto error;
cptr = snd_seq_kernel_client_get(client->seq_client);
if (!cptr) {
err = -EINVAL;
goto error;
}
cptr->ump_info = client->ump_info;
snd_seq_kernel_client_put(cptr);
ump->seq_client = client;
ump->seq_ops = &seq_ump_ops;
return 0;
error:
seq_ump_client_free(client);
return err;
}
/* remove a sequencer client */
static void snd_seq_ump_remove(struct snd_seq_device *dev)
{
struct snd_ump_endpoint *ump = dev->private_data;
if (ump->seq_client)
seq_ump_client_free(ump->seq_client);
}
static struct snd_seq_driver seq_ump_driver = {
.probe = snd_seq_ump_probe,
.remove = snd_seq_ump_remove,
.driver = {
.name = KBUILD_MODNAME,
},
.id = SNDRV_SEQ_DEV_ID_UMP,
.argsize = 0,
};
module_snd_seq_driver(seq_ump_driver);
MODULE_DESCRIPTION("ALSA sequencer client for UMP rawmidi");
MODULE_LICENSE("GPL");
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* ALSA sequencer event conversion between UMP and legacy clients
*/
#ifndef __SEQ_UMP_CONVERT_H
#define __SEQ_UMP_CONVERT_H
#include "seq_clientmgr.h"
#include "seq_ports.h"
int snd_seq_deliver_from_ump(struct snd_seq_client *source,
struct snd_seq_client *dest,
struct snd_seq_client_port *dest_port,
struct snd_seq_event *event,
int atomic, int hop);
int snd_seq_deliver_to_ump(struct snd_seq_client *source,
struct snd_seq_client *dest,
struct snd_seq_client_port *dest_port,
struct snd_seq_event *event,
int atomic, int hop);
int snd_seq_ump_group_port(const struct snd_seq_event *event);
#endif /* __SEQ_UMP_CONVERT_H */
+525
View File
@@ -0,0 +1,525 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Virtual Raw MIDI client on Sequencer
*
* Copyright (c) 2000 by Takashi Iwai <tiwai@suse.de>,
* Jaroslav Kysela <perex@perex.cz>
*/
/*
* Virtual Raw MIDI client
*
* The virtual rawmidi client is a sequencer client which associate
* a rawmidi device file. The created rawmidi device file can be
* accessed as a normal raw midi, but its MIDI source and destination
* are arbitrary. For example, a user-client software synth connected
* to this port can be used as a normal midi device as well.
*
* The virtual rawmidi device accepts also multiple opens. Each file
* has its own input buffer, so that no conflict would occur. The drain
* of input/output buffer acts only to the local buffer.
*
*/
#include <linux/init.h>
#include <linux/wait.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/rawmidi.h>
#include <sound/info.h>
#include <sound/control.h>
#include <sound/minors.h>
#include <sound/seq_kernel.h>
#include <sound/seq_midi_event.h>
#include <sound/seq_virmidi.h>
MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
MODULE_DESCRIPTION("Virtual Raw MIDI client on Sequencer");
MODULE_LICENSE("GPL");
/*
* initialize an event record
*/
static void snd_virmidi_init_event(struct snd_virmidi *vmidi,
struct snd_seq_event *ev)
{
memset(ev, 0, sizeof(*ev));
ev->source.port = vmidi->port;
switch (vmidi->seq_mode) {
case SNDRV_VIRMIDI_SEQ_DISPATCH:
ev->dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
break;
case SNDRV_VIRMIDI_SEQ_ATTACH:
/* FIXME: source and destination are same - not good.. */
ev->dest.client = vmidi->client;
ev->dest.port = vmidi->port;
break;
}
ev->type = SNDRV_SEQ_EVENT_NONE;
}
/*
* decode input event and put to read buffer of each opened file
*/
/* callback for snd_seq_dump_var_event(), bridging to snd_rawmidi_receive() */
static int dump_to_rawmidi(void *ptr, void *buf, int count)
{
return snd_rawmidi_receive(ptr, buf, count);
}
static int snd_virmidi_dev_receive_event(struct snd_virmidi_dev *rdev,
struct snd_seq_event *ev,
bool atomic)
{
struct snd_virmidi *vmidi;
unsigned char msg[4];
int len;
if (atomic)
read_lock(&rdev->filelist_lock);
else
down_read(&rdev->filelist_sem);
list_for_each_entry(vmidi, &rdev->filelist, list) {
if (!READ_ONCE(vmidi->trigger))
continue;
if (ev->type == SNDRV_SEQ_EVENT_SYSEX) {
if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARIABLE)
continue;
snd_seq_dump_var_event(ev, dump_to_rawmidi, vmidi->substream);
snd_midi_event_reset_decode(vmidi->parser);
} else {
len = snd_midi_event_decode(vmidi->parser, msg, sizeof(msg), ev);
if (len > 0)
snd_rawmidi_receive(vmidi->substream, msg, len);
}
}
if (atomic)
read_unlock(&rdev->filelist_lock);
else
up_read(&rdev->filelist_sem);
return 0;
}
/*
* event handler of virmidi port
*/
static int snd_virmidi_event_input(struct snd_seq_event *ev, int direct,
void *private_data, int atomic, int hop)
{
struct snd_virmidi_dev *rdev;
rdev = private_data;
if (!(rdev->flags & SNDRV_VIRMIDI_USE))
return 0; /* ignored */
return snd_virmidi_dev_receive_event(rdev, ev, atomic);
}
/*
* trigger rawmidi stream for input
*/
static void snd_virmidi_input_trigger(struct snd_rawmidi_substream *substream, int up)
{
struct snd_virmidi *vmidi = substream->runtime->private_data;
WRITE_ONCE(vmidi->trigger, !!up);
}
/* process rawmidi bytes and send events;
* we need no lock here for vmidi->event since it's handled only in this work
*/
static void snd_vmidi_output_work(struct work_struct *work)
{
struct snd_virmidi *vmidi;
struct snd_rawmidi_substream *substream;
unsigned char input;
int ret;
vmidi = container_of(work, struct snd_virmidi, output_work);
substream = vmidi->substream;
/* discard the outputs in dispatch mode unless subscribed */
if (vmidi->seq_mode == SNDRV_VIRMIDI_SEQ_DISPATCH &&
!(vmidi->rdev->flags & SNDRV_VIRMIDI_SUBSCRIBE)) {
snd_rawmidi_proceed(substream);
return;
}
while (READ_ONCE(vmidi->trigger)) {
if (snd_rawmidi_transmit(substream, &input, 1) != 1)
break;
if (!snd_midi_event_encode_byte(vmidi->parser, input,
&vmidi->event))
continue;
if (vmidi->event.type != SNDRV_SEQ_EVENT_NONE) {
ret = snd_seq_kernel_client_dispatch(vmidi->client,
&vmidi->event,
false, 0);
vmidi->event.type = SNDRV_SEQ_EVENT_NONE;
if (ret < 0)
break;
}
/* rawmidi input might be huge, allow to have a break */
cond_resched();
}
}
/*
* trigger rawmidi stream for output
*/
static void snd_virmidi_output_trigger(struct snd_rawmidi_substream *substream, int up)
{
struct snd_virmidi *vmidi = substream->runtime->private_data;
WRITE_ONCE(vmidi->trigger, !!up);
if (up)
queue_work(system_highpri_wq, &vmidi->output_work);
}
/*
* open rawmidi handle for input
*/
static int snd_virmidi_input_open(struct snd_rawmidi_substream *substream)
{
struct snd_virmidi_dev *rdev = substream->rmidi->private_data;
struct snd_rawmidi_runtime *runtime = substream->runtime;
struct snd_virmidi *vmidi;
vmidi = kzalloc(sizeof(*vmidi), GFP_KERNEL);
if (vmidi == NULL)
return -ENOMEM;
vmidi->substream = substream;
if (snd_midi_event_new(0, &vmidi->parser) < 0) {
kfree(vmidi);
return -ENOMEM;
}
vmidi->seq_mode = rdev->seq_mode;
vmidi->client = rdev->client;
vmidi->port = rdev->port;
runtime->private_data = vmidi;
scoped_guard(rwsem_write, &rdev->filelist_sem) {
guard(write_lock_irq)(&rdev->filelist_lock);
list_add_tail(&vmidi->list, &rdev->filelist);
}
vmidi->rdev = rdev;
return 0;
}
/*
* open rawmidi handle for output
*/
static int snd_virmidi_output_open(struct snd_rawmidi_substream *substream)
{
struct snd_virmidi_dev *rdev = substream->rmidi->private_data;
struct snd_rawmidi_runtime *runtime = substream->runtime;
struct snd_virmidi *vmidi;
vmidi = kzalloc_obj(*vmidi);
if (vmidi == NULL)
return -ENOMEM;
vmidi->substream = substream;
if (snd_midi_event_new(MAX_MIDI_EVENT_BUF, &vmidi->parser) < 0) {
kfree(vmidi);
return -ENOMEM;
}
vmidi->seq_mode = rdev->seq_mode;
vmidi->client = rdev->client;
vmidi->port = rdev->port;
snd_virmidi_init_event(vmidi, &vmidi->event);
vmidi->rdev = rdev;
INIT_WORK(&vmidi->output_work, snd_vmidi_output_work);
runtime->private_data = vmidi;
return 0;
}
/*
* close rawmidi handle for input
*/
static int snd_virmidi_input_close(struct snd_rawmidi_substream *substream)
{
struct snd_virmidi_dev *rdev = substream->rmidi->private_data;
struct snd_virmidi *vmidi = substream->runtime->private_data;
scoped_guard(rwsem_write, &rdev->filelist_sem) {
guard(write_lock_irq)(&rdev->filelist_lock);
list_del(&vmidi->list);
}
snd_midi_event_free(vmidi->parser);
substream->runtime->private_data = NULL;
kfree(vmidi);
return 0;
}
/*
* close rawmidi handle for output
*/
static int snd_virmidi_output_close(struct snd_rawmidi_substream *substream)
{
struct snd_virmidi *vmidi = substream->runtime->private_data;
WRITE_ONCE(vmidi->trigger, false); /* to be sure */
cancel_work_sync(&vmidi->output_work);
snd_midi_event_free(vmidi->parser);
substream->runtime->private_data = NULL;
kfree(vmidi);
return 0;
}
/*
* drain output work queue
*/
static void snd_virmidi_output_drain(struct snd_rawmidi_substream *substream)
{
struct snd_virmidi *vmidi = substream->runtime->private_data;
flush_work(&vmidi->output_work);
}
/*
* subscribe callback - allow output to rawmidi device
*/
static int snd_virmidi_subscribe(void *private_data,
struct snd_seq_port_subscribe *info)
{
struct snd_virmidi_dev *rdev;
rdev = private_data;
if (!try_module_get(rdev->card->module))
return -EFAULT;
rdev->flags |= SNDRV_VIRMIDI_SUBSCRIBE;
return 0;
}
/*
* unsubscribe callback - disallow output to rawmidi device
*/
static int snd_virmidi_unsubscribe(void *private_data,
struct snd_seq_port_subscribe *info)
{
struct snd_virmidi_dev *rdev;
rdev = private_data;
rdev->flags &= ~SNDRV_VIRMIDI_SUBSCRIBE;
module_put(rdev->card->module);
return 0;
}
/*
* use callback - allow input to rawmidi device
*/
static int snd_virmidi_use(void *private_data,
struct snd_seq_port_subscribe *info)
{
struct snd_virmidi_dev *rdev;
rdev = private_data;
if (!try_module_get(rdev->card->module))
return -EFAULT;
rdev->flags |= SNDRV_VIRMIDI_USE;
return 0;
}
/*
* unuse callback - disallow input to rawmidi device
*/
static int snd_virmidi_unuse(void *private_data,
struct snd_seq_port_subscribe *info)
{
struct snd_virmidi_dev *rdev;
rdev = private_data;
rdev->flags &= ~SNDRV_VIRMIDI_USE;
module_put(rdev->card->module);
return 0;
}
/*
* Register functions
*/
static const struct snd_rawmidi_ops snd_virmidi_input_ops = {
.open = snd_virmidi_input_open,
.close = snd_virmidi_input_close,
.trigger = snd_virmidi_input_trigger,
};
static const struct snd_rawmidi_ops snd_virmidi_output_ops = {
.open = snd_virmidi_output_open,
.close = snd_virmidi_output_close,
.trigger = snd_virmidi_output_trigger,
.drain = snd_virmidi_output_drain,
};
/*
* create a sequencer client and a port
*/
static int snd_virmidi_dev_attach_seq(struct snd_virmidi_dev *rdev)
{
int client;
struct snd_seq_port_callback pcallbacks;
int err;
if (rdev->client >= 0)
return 0;
struct snd_seq_port_info *pinfo __free(kfree) =
kzalloc_obj(*pinfo);
if (!pinfo)
return -ENOMEM;
client = snd_seq_create_kernel_client(rdev->card, rdev->device,
"%s %d-%d", rdev->rmidi->name,
rdev->card->number,
rdev->device);
if (client < 0)
return client;
rdev->client = client;
/* create a port */
pinfo->addr.client = client;
sprintf(pinfo->name, "VirMIDI %d-%d", rdev->card->number, rdev->device);
/* set all capabilities */
pinfo->capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SYNC_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE;
pinfo->capability |= SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SYNC_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ;
pinfo->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;
pinfo->direction = SNDRV_SEQ_PORT_DIR_BIDIRECTION;
pinfo->type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC
| SNDRV_SEQ_PORT_TYPE_SOFTWARE
| SNDRV_SEQ_PORT_TYPE_PORT;
pinfo->midi_channels = 16;
memset(&pcallbacks, 0, sizeof(pcallbacks));
pcallbacks.owner = THIS_MODULE;
pcallbacks.private_data = rdev;
pcallbacks.subscribe = snd_virmidi_subscribe;
pcallbacks.unsubscribe = snd_virmidi_unsubscribe;
pcallbacks.use = snd_virmidi_use;
pcallbacks.unuse = snd_virmidi_unuse;
pcallbacks.event_input = snd_virmidi_event_input;
pinfo->kernel = &pcallbacks;
err = snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_CREATE_PORT, pinfo);
if (err < 0) {
snd_seq_delete_kernel_client(client);
rdev->client = -1;
return err;
}
rdev->port = pinfo->addr.port;
return 0; /* success */
}
/*
* release the sequencer client
*/
static void snd_virmidi_dev_detach_seq(struct snd_virmidi_dev *rdev)
{
if (rdev->client >= 0) {
snd_seq_delete_kernel_client(rdev->client);
rdev->client = -1;
}
}
/*
* register the device
*/
static int snd_virmidi_dev_register(struct snd_rawmidi *rmidi)
{
struct snd_virmidi_dev *rdev = rmidi->private_data;
int err;
switch (rdev->seq_mode) {
case SNDRV_VIRMIDI_SEQ_DISPATCH:
err = snd_virmidi_dev_attach_seq(rdev);
if (err < 0)
return err;
break;
case SNDRV_VIRMIDI_SEQ_ATTACH:
if (rdev->client == 0)
return -EINVAL;
/* should check presence of port more strictly.. */
break;
default:
pr_err("ALSA: seq_virmidi: seq_mode is not set: %d\n", rdev->seq_mode);
return -EINVAL;
}
return 0;
}
/*
* unregister the device
*/
static int snd_virmidi_dev_unregister(struct snd_rawmidi *rmidi)
{
struct snd_virmidi_dev *rdev = rmidi->private_data;
if (rdev->seq_mode == SNDRV_VIRMIDI_SEQ_DISPATCH)
snd_virmidi_dev_detach_seq(rdev);
return 0;
}
/*
*
*/
static const struct snd_rawmidi_global_ops snd_virmidi_global_ops = {
.dev_register = snd_virmidi_dev_register,
.dev_unregister = snd_virmidi_dev_unregister,
};
/*
* free device
*/
static void snd_virmidi_free(struct snd_rawmidi *rmidi)
{
struct snd_virmidi_dev *rdev = rmidi->private_data;
kfree(rdev);
}
/*
* create a new device
*
*/
/* exported */
int snd_virmidi_new(struct snd_card *card, int device, struct snd_rawmidi **rrmidi)
{
struct snd_rawmidi *rmidi;
struct snd_virmidi_dev *rdev;
int err;
*rrmidi = NULL;
err = snd_rawmidi_new(card, "VirMidi", device,
16, /* may be configurable */
16, /* may be configurable */
&rmidi);
if (err < 0)
return err;
strscpy(rmidi->name, rmidi->id);
rdev = kzalloc_obj(*rdev);
if (rdev == NULL) {
snd_device_free(card, rmidi);
return -ENOMEM;
}
rdev->card = card;
rdev->rmidi = rmidi;
rdev->device = device;
rdev->client = -1;
init_rwsem(&rdev->filelist_sem);
rwlock_init(&rdev->filelist_lock);
INIT_LIST_HEAD(&rdev->filelist);
rdev->seq_mode = SNDRV_VIRMIDI_SEQ_DISPATCH;
rmidi->private_data = rdev;
rmidi->private_free = snd_virmidi_free;
rmidi->ops = &snd_virmidi_global_ops;
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_virmidi_input_ops);
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_virmidi_output_ops);
rmidi->info_flags = SNDRV_RAWMIDI_INFO_INPUT |
SNDRV_RAWMIDI_INFO_OUTPUT |
SNDRV_RAWMIDI_INFO_DUPLEX;
*rrmidi = rmidi;
return 0;
}
EXPORT_SYMBOL(snd_virmidi_new);