xref: /linux/sound/core/seq/seq_dummy.c (revision c532de5a67a70f8533d495f8f2aaa9a0491c3ad0)
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 #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
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
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 *
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
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
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 
231 static int __init alsa_seq_dummy_init(void)
232 {
233 	return register_client();
234 }
235 
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