GNU Linux-libre 5.15.82-gnu
[releases.git] / sound / core / seq / seq_dummy.c
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
48 MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
49 MODULE_DESCRIPTION("ALSA sequencer MIDI-through client");
50 MODULE_LICENSE("GPL");
51 MODULE_ALIAS("snd-seq-client-" __stringify(SNDRV_SEQ_CLIENT_DUMMY));
52
53 static int ports = 1;
54 static bool duplex;
55
56 module_param(ports, int, 0444);
57 MODULE_PARM_DESC(ports, "number of ports to be created");
58 module_param(duplex, bool, 0444);
59 MODULE_PARM_DESC(duplex, "create DUPLEX ports");
60
61 struct snd_seq_dummy_port {
62         int client;
63         int port;
64         int duplex;
65         int connect;
66 };
67
68 static int my_client = -1;
69
70 /*
71  * event input callback - just redirect events to subscribers
72  */
73 static int
74 dummy_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  */
96 static void
97 dummy_free(void *private_data)
98 {
99         kfree(private_data);
100 }
101
102 /*
103  * create a port
104  */
105 static struct snd_seq_dummy_port __init *
106 create_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  */
150 static int __init
151 register_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  */
191 static void __exit
192 delete_client(void)
193 {
194         if (my_client >= 0)
195                 snd_seq_delete_kernel_client(my_client);
196 }
197
198 /*
199  *  Init part
200  */
201
202 static int __init alsa_seq_dummy_init(void)
203 {
204         return register_client();
205 }
206
207 static void __exit alsa_seq_dummy_exit(void)
208 {
209         delete_client();
210 }
211
212 module_init(alsa_seq_dummy_init)
213 module_exit(alsa_seq_dummy_exit)