linux/sound/core/seq/seq_dummy.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * ALSA sequencer MIDI-through client
   4 * Copyright (c) 1999-2000 by Takashi Iwai <tiwai@suse.de>
   5 */
   6
   7#include <linux/init.h>
   8#include <linux/slab.h>
   9#include <linux/module.h>
  10#include <sound/core.h>
  11#include "seq_clientmgr.h"
  12#include <sound/initval.h>
  13#include <sound/asoundef.h>
  14
  15/*
  16
  17  Sequencer MIDI-through client
  18
  19  This gives a simple midi-through client.  All the normal input events
  20  are redirected to output port immediately.
  21  The routing can be done via aconnect program in alsa-utils.
  22
  23  Each client has a static client number 62 (= SNDRV_SEQ_CLIENT_DUMMY).
  24  If you want to auto-load this module, you may add the following alias
  25  in your /etc/conf.modules file.
  26
  27        alias snd-seq-client-62  snd-seq-dummy
  28
  29  The module is loaded on demand for client 62, or /proc/asound/seq/
  30  is accessed.  If you don't need this module to be loaded, alias
  31  snd-seq-client-62 as "off".  This will help modprobe.
  32
  33  The number of ports to be created can be specified via the module
  34  parameter "ports".  For example, to create four ports, add the
  35  following option in a configuration file under /etc/modprobe.d/:
  36
  37        option snd-seq-dummy ports=4
  38
  39  The model option "duplex=1" enables duplex operation to the port.
  40  In duplex mode, a pair of ports are created instead of single port,
  41  and events are tunneled between pair-ports.  For example, input to
  42  port A is sent to output port of another port B and vice versa.
  43  In duplex mode, each port has DUPLEX capability.
  44
  45 */
  46
  47
  48MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
  49MODULE_DESCRIPTION("ALSA sequencer MIDI-through client");
  50MODULE_LICENSE("GPL");
  51MODULE_ALIAS("snd-seq-client-" __stringify(SNDRV_SEQ_CLIENT_DUMMY));
  52
  53static int ports = 1;
  54static bool duplex;
  55
  56module_param(ports, int, 0444);
  57MODULE_PARM_DESC(ports, "number of ports to be created");
  58module_param(duplex, bool, 0444);
  59MODULE_PARM_DESC(duplex, "create DUPLEX ports");
  60
  61struct snd_seq_dummy_port {
  62        int client;
  63        int port;
  64        int duplex;
  65        int connect;
  66};
  67
  68static int my_client = -1;
  69
  70/*
  71 * event input callback - just redirect events to subscribers
  72 */
  73static int
  74dummy_input(struct snd_seq_event *ev, int direct, void *private_data,
  75            int atomic, int hop)
  76{
  77        struct snd_seq_dummy_port *p;
  78        struct snd_seq_event tmpev;
  79
  80        p = private_data;
  81        if (ev->source.client == SNDRV_SEQ_CLIENT_SYSTEM ||
  82            ev->type == SNDRV_SEQ_EVENT_KERNEL_ERROR)
  83                return 0; /* ignore system messages */
  84        tmpev = *ev;
  85        if (p->duplex)
  86                tmpev.source.port = p->connect;
  87        else
  88                tmpev.source.port = p->port;
  89        tmpev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
  90        return snd_seq_kernel_client_dispatch(p->client, &tmpev, atomic, hop);
  91}
  92
  93/*
  94 * free_private callback
  95 */
  96static void
  97dummy_free(void *private_data)
  98{
  99        kfree(private_data);
 100}
 101
 102/*
 103 * create a port
 104 */
 105static struct snd_seq_dummy_port __init *
 106create_port(int idx, int type)
 107{
 108        struct snd_seq_port_info pinfo;
 109        struct snd_seq_port_callback pcb;
 110        struct snd_seq_dummy_port *rec;
 111
 112        if ((rec = kzalloc(sizeof(*rec), GFP_KERNEL)) == NULL)
 113                return NULL;
 114
 115        rec->client = my_client;
 116        rec->duplex = duplex;
 117        rec->connect = 0;
 118        memset(&pinfo, 0, sizeof(pinfo));
 119        pinfo.addr.client = my_client;
 120        if (duplex)
 121                sprintf(pinfo.name, "Midi Through Port-%d:%c", idx,
 122                        (type ? 'B' : 'A'));
 123        else
 124                sprintf(pinfo.name, "Midi Through Port-%d", idx);
 125        pinfo.capability = SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ;
 126        pinfo.capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE;
 127        if (duplex)
 128                pinfo.capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;
 129        pinfo.type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC
 130                | SNDRV_SEQ_PORT_TYPE_SOFTWARE
 131                | SNDRV_SEQ_PORT_TYPE_PORT;
 132        memset(&pcb, 0, sizeof(pcb));
 133        pcb.owner = THIS_MODULE;
 134        pcb.event_input = dummy_input;
 135        pcb.private_free = dummy_free;
 136        pcb.private_data = rec;
 137        pinfo.kernel = &pcb;
 138        if (snd_seq_kernel_client_ctl(my_client, SNDRV_SEQ_IOCTL_CREATE_PORT, &pinfo) < 0) {
 139                kfree(rec);
 140                return NULL;
 141        }
 142        rec->port = pinfo.addr.port;
 143        return rec;
 144}
 145
 146/*
 147 * register client and create ports
 148 */
 149static int __init
 150register_client(void)
 151{
 152        struct snd_seq_dummy_port *rec1, *rec2;
 153        int i;
 154
 155        if (ports < 1) {
 156                pr_err("ALSA: seq_dummy: invalid number of ports %d\n", ports);
 157                return -EINVAL;
 158        }
 159
 160        /* create client */
 161        my_client = snd_seq_create_kernel_client(NULL, SNDRV_SEQ_CLIENT_DUMMY,
 162                                                 "Midi Through");
 163        if (my_client < 0)
 164                return my_client;
 165
 166        /* create ports */
 167        for (i = 0; i < ports; i++) {
 168                rec1 = create_port(i, 0);
 169                if (rec1 == NULL) {
 170                        snd_seq_delete_kernel_client(my_client);
 171                        return -ENOMEM;
 172                }
 173                if (duplex) {
 174                        rec2 = create_port(i, 1);
 175                        if (rec2 == NULL) {
 176                                snd_seq_delete_kernel_client(my_client);
 177                                return -ENOMEM;
 178                        }
 179                        rec1->connect = rec2->port;
 180                        rec2->connect = rec1->port;
 181                }
 182        }
 183
 184        return 0;
 185}
 186
 187/*
 188 * delete client if exists
 189 */
 190static void __exit
 191delete_client(void)
 192{
 193        if (my_client >= 0)
 194                snd_seq_delete_kernel_client(my_client);
 195}
 196
 197/*
 198 *  Init part
 199 */
 200
 201static int __init alsa_seq_dummy_init(void)
 202{
 203        return register_client();
 204}
 205
 206static void __exit alsa_seq_dummy_exit(void)
 207{
 208        delete_client();
 209}
 210
 211module_init(alsa_seq_dummy_init)
 212module_exit(alsa_seq_dummy_exit)
 213