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