1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * ALSA sequencer MIDI-through client
4 * Copyright (c) 1999-2000 by Takashi Iwai <[email protected]>
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 <[email protected]>");
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 #if IS_ENABLED(CONFIG_SND_SEQ_UMP)
62 static int ump;
63 module_param(ump, int, 0444);
64 MODULE_PARM_DESC(ump, "UMP conversion (0: no convert, 1: MIDI 1.0, 2: MIDI 2.0)");
65 #endif
66
67 struct snd_seq_dummy_port {
68 int client;
69 int port;
70 int duplex;
71 int connect;
72 };
73
74 static int my_client = -1;
75
76 /*
77 * event input callback - just redirect events to subscribers
78 */
79 static int
dummy_input(struct snd_seq_event * ev,int direct,void * private_data,int atomic,int hop)80 dummy_input(struct snd_seq_event *ev, int direct, void *private_data,
81 int atomic, int hop)
82 {
83 struct snd_seq_dummy_port *p;
84 struct snd_seq_event tmpev;
85
86 p = private_data;
87 if (ev->source.client == SNDRV_SEQ_CLIENT_SYSTEM ||
88 ev->type == SNDRV_SEQ_EVENT_KERNEL_ERROR)
89 return 0; /* ignore system messages */
90 tmpev = *ev;
91 if (p->duplex)
92 tmpev.source.port = p->connect;
93 else
94 tmpev.source.port = p->port;
95 tmpev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
96 return snd_seq_kernel_client_dispatch(p->client, &tmpev, atomic, hop);
97 }
98
99 /*
100 * free_private callback
101 */
102 static void
dummy_free(void * private_data)103 dummy_free(void *private_data)
104 {
105 kfree(private_data);
106 }
107
108 /*
109 * create a port
110 */
111 static struct snd_seq_dummy_port __init *
create_port(int idx,int type)112 create_port(int idx, int type)
113 {
114 struct snd_seq_port_info pinfo;
115 struct snd_seq_port_callback pcb;
116 struct snd_seq_dummy_port *rec;
117
118 rec = kzalloc(sizeof(*rec), GFP_KERNEL);
119 if (!rec)
120 return NULL;
121
122 rec->client = my_client;
123 rec->duplex = duplex;
124 rec->connect = 0;
125 memset(&pinfo, 0, sizeof(pinfo));
126 pinfo.addr.client = my_client;
127 if (duplex)
128 sprintf(pinfo.name, "Midi Through Port-%d:%c", idx,
129 (type ? 'B' : 'A'));
130 else
131 sprintf(pinfo.name, "Midi Through Port-%d", idx);
132 pinfo.capability = SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ;
133 pinfo.capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE;
134 if (duplex)
135 pinfo.capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;
136 pinfo.direction = SNDRV_SEQ_PORT_DIR_BIDIRECTION;
137 pinfo.type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC
138 | SNDRV_SEQ_PORT_TYPE_SOFTWARE
139 | SNDRV_SEQ_PORT_TYPE_PORT;
140 memset(&pcb, 0, sizeof(pcb));
141 pcb.owner = THIS_MODULE;
142 pcb.event_input = dummy_input;
143 pcb.private_free = dummy_free;
144 pcb.private_data = rec;
145 pinfo.kernel = &pcb;
146 if (snd_seq_kernel_client_ctl(my_client, SNDRV_SEQ_IOCTL_CREATE_PORT, &pinfo) < 0) {
147 kfree(rec);
148 return NULL;
149 }
150 rec->port = pinfo.addr.port;
151 return rec;
152 }
153
154 /*
155 * register client and create ports
156 */
157 static int __init
register_client(void)158 register_client(void)
159 {
160 struct snd_seq_dummy_port *rec1, *rec2;
161 #if IS_ENABLED(CONFIG_SND_SEQ_UMP)
162 struct snd_seq_client *client;
163 #endif
164 int i;
165
166 if (ports < 1) {
167 pr_err("ALSA: seq_dummy: invalid number of ports %d\n", ports);
168 return -EINVAL;
169 }
170
171 /* create client */
172 my_client = snd_seq_create_kernel_client(NULL, SNDRV_SEQ_CLIENT_DUMMY,
173 "Midi Through");
174 if (my_client < 0)
175 return my_client;
176
177 #if IS_ENABLED(CONFIG_SND_SEQ_UMP)
178 client = snd_seq_kernel_client_get(my_client);
179 if (!client)
180 return -EINVAL;
181 switch (ump) {
182 case 1:
183 client->midi_version = SNDRV_SEQ_CLIENT_UMP_MIDI_1_0;
184 break;
185 case 2:
186 client->midi_version = SNDRV_SEQ_CLIENT_UMP_MIDI_2_0;
187 break;
188 default:
189 /* don't convert events but just pass-through */
190 client->filter = SNDRV_SEQ_FILTER_NO_CONVERT;
191 break;
192 }
193 snd_seq_kernel_client_put(client);
194 #endif
195
196 /* create ports */
197 for (i = 0; i < ports; i++) {
198 rec1 = create_port(i, 0);
199 if (rec1 == NULL) {
200 snd_seq_delete_kernel_client(my_client);
201 return -ENOMEM;
202 }
203 if (duplex) {
204 rec2 = create_port(i, 1);
205 if (rec2 == NULL) {
206 snd_seq_delete_kernel_client(my_client);
207 return -ENOMEM;
208 }
209 rec1->connect = rec2->port;
210 rec2->connect = rec1->port;
211 }
212 }
213
214 return 0;
215 }
216
217 /*
218 * delete client if exists
219 */
220 static void __exit
delete_client(void)221 delete_client(void)
222 {
223 if (my_client >= 0)
224 snd_seq_delete_kernel_client(my_client);
225 }
226
227 /*
228 * Init part
229 */
230
alsa_seq_dummy_init(void)231 static int __init alsa_seq_dummy_init(void)
232 {
233 return register_client();
234 }
235
alsa_seq_dummy_exit(void)236 static void __exit alsa_seq_dummy_exit(void)
237 {
238 delete_client();
239 }
240
241 module_init(alsa_seq_dummy_init)
242 module_exit(alsa_seq_dummy_exit)
243