projects/linux·blob master · refs

245 lines · 5.9 KiB

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 "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
49MODULE_AUTHOR("Takashi Iwai <[email protected]>");
50MODULE_DESCRIPTION("ALSA sequencer MIDI-through client");
51MODULE_LICENSE("GPL");
52MODULE_ALIAS("snd-seq-client-" __stringify(SNDRV_SEQ_CLIENT_DUMMY));
53
54static int ports = 1;
55static bool duplex;
56
57module_param(ports, int, 0444);
58MODULE_PARM_DESC(ports, "number of ports to be created");
59module_param(duplex, bool, 0444);
60MODULE_PARM_DESC(duplex, "create DUPLEX ports");
61
62#if IS_ENABLED(CONFIG_SND_SEQ_UMP)
63static int ump;
64module_param(ump, int, 0444);
65MODULE_PARM_DESC(ump, "UMP conversion (0: no convert, 1: MIDI 1.0, 2: MIDI 2.0)");
66#endif
67
68struct snd_seq_dummy_port {
69 int client;
70 int port;
71 int duplex;
72 int connect;
73};
74
75static int my_client __ro_after_init = -1;
76
77/*
78 * event input callback - just redirect events to subscribers
79 */
80static int
81dummy_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 */
105static void
106dummy_free(void *private_data)
107{
108 kfree(private_data);
109}
110
111/*
112 * create a port
113 */
114static struct snd_seq_dummy_port __init *
115create_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 */
160static int __init
161register_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 */
223static void __exit
224delete_client(void)
225{
226 if (my_client >= 0)
227 snd_seq_delete_kernel_client(my_client);
228}
229
230/*
231 * Init part
232 */
233
234static int __init alsa_seq_dummy_init(void)
235{
236 return register_client();
237}
238
239static void __exit alsa_seq_dummy_exit(void)
240{
241 delete_client();
242}
243
244module_init(alsa_seq_dummy_init)
245module_exit(alsa_seq_dummy_exit)