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 14 (= 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-14  snd-seq-dummy
  28
  29  The module is loaded on demand for client 14, or /proc/asound/seq/
  30  is accessed.  If you don't need this module to be loaded, alias
  31  snd-seq-client-14 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        rec = kzalloc(sizeof(*rec), GFP_KERNEL);
 113        if (!rec)
 114                return NULL;
 115
 116        rec->client = my_client;
 117        rec->duplex = duplex;
 118        rec->connect = 0;
 119        memset(&pinfo, 0, sizeof(pinfo));
 120        pinfo.addr.client = my_client;
 121        if (duplex)
 122                sprintf(pinfo.name, "Midi Through Port-%d:%c", idx,
 123                        (type ? 'B' : 'A'));
 124        else
 125                sprintf(pinfo.name, "Midi Through Port-%d", idx);
 126        pinfo.capability = SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ;
 127        pinfo.capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE;
 128        if (duplex)
 129                pinfo.capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;
 130        pinfo.type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC
 131                | SNDRV_SEQ_PORT_TYPE_SOFTWARE
 132                | SNDRV_SEQ_PORT_TYPE_PORT;
 133        memset(&pcb, 0, sizeof(pcb));
 134        pcb.owner = THIS_MODULE;
 135        pcb.event_input = dummy_input;
 136        pcb.private_free = dummy_free;
 137        pcb.private_data = rec;
 138        pinfo.kernel = &pcb;
 139        if (snd_seq_kernel_client_ctl(my_client, SNDRV_SEQ_IOCTL_CREATE_PORT, &pinfo) < 0) {
 140                kfree(rec);
 141                return NULL;
 142        }
 143        rec->port = pinfo.addr.port;
 144        return rec;
 145}
 146
 147/*
 148 * register client and create ports
 149 */
 150static int __init
 151register_client(void)
 152{
 153        struct snd_seq_dummy_port *rec1, *rec2;
 154        int i;
 155
 156        if (ports < 1) {
 157                pr_err("ALSA: seq_dummy: invalid number of ports %d\n", ports);
 158                return -EINVAL;
 159        }
 160
 161        /* create client */
 162        my_client = snd_seq_create_kernel_client(NULL, SNDRV_SEQ_CLIENT_DUMMY,
 163                                                 "Midi Through");
 164        if (my_client < 0)
 165                return my_client;
 166
 167        /* create ports */
 168        for (i = 0; i < ports; i++) {
 169                rec1 = create_port(i, 0);
 170                if (rec1 == NULL) {
 171                        snd_seq_delete_kernel_client(my_client);
 172                        return -ENOMEM;
 173                }
 174                if (duplex) {
 175                        rec2 = create_port(i, 1);
 176                        if (rec2 == NULL) {
 177                                snd_seq_delete_kernel_client(my_client);
 178                                return -ENOMEM;
 179                        }
 180                        rec1->connect = rec2->port;
 181                        rec2->connect = rec1->port;
 182                }
 183        }
 184
 185        return 0;
 186}
 187
 188/*
 189 * delete client if exists
 190 */
 191static void __exit
 192delete_client(void)
 193{
 194        if (my_client >= 0)
 195                snd_seq_delete_kernel_client(my_client);
 196}
 197
 198/*
 199 *  Init part
 200 */
 201
 202static int __init alsa_seq_dummy_init(void)
 203{
 204        return register_client();
 205}
 206
 207static void __exit alsa_seq_dummy_exit(void)
 208{
 209        delete_client();
 210}
 211
 212module_init(alsa_seq_dummy_init)
 213module_exit(alsa_seq_dummy_exit)
 214