ALSA: seq: dummy: Allow UMP conversion
[linux-2.6-block.git] / sound / core / seq / seq_dummy.c
CommitLineData
1a59d1b8 1// SPDX-License-Identifier: GPL-2.0-or-later
1da177e4
LT
2/*
3 * ALSA sequencer MIDI-through client
4 * Copyright (c) 1999-2000 by Takashi Iwai <tiwai@suse.de>
1da177e4
LT
5 */
6
1da177e4
LT
7#include <linux/init.h>
8#include <linux/slab.h>
65a77217 9#include <linux/module.h>
1da177e4
LT
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
c5aa8277 23 Each client has a static client number 14 (= SNDRV_SEQ_CLIENT_DUMMY).
1da177e4
LT
24 If you want to auto-load this module, you may add the following alias
25 in your /etc/conf.modules file.
26
c5aa8277 27 alias snd-seq-client-14 snd-seq-dummy
1da177e4 28
c5aa8277 29 The module is loaded on demand for client 14, or /proc/asound/seq/
1da177e4 30 is accessed. If you don't need this module to be loaded, alias
c5aa8277 31 snd-seq-client-14 as "off". This will help modprobe.
1da177e4
LT
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
970e2486 35 following option in a configuration file under /etc/modprobe.d/:
1da177e4
LT
36
37 option snd-seq-dummy ports=4
38
25985edc 39 The model option "duplex=1" enables duplex operation to the port.
1da177e4
LT
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
48MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
49MODULE_DESCRIPTION("ALSA sequencer MIDI-through client");
50MODULE_LICENSE("GPL");
51MODULE_ALIAS("snd-seq-client-" __stringify(SNDRV_SEQ_CLIENT_DUMMY));
52
53static int ports = 1;
a67ff6a5 54static bool duplex;
1da177e4
LT
55
56module_param(ports, int, 0444);
57MODULE_PARM_DESC(ports, "number of ports to be created");
58module_param(duplex, bool, 0444);
59MODULE_PARM_DESC(duplex, "create DUPLEX ports");
60
32cb23a0
TI
61#if IS_ENABLED(CONFIG_SND_SEQ_UMP)
62static int ump;
63module_param(ump, int, 0444);
64MODULE_PARM_DESC(ump, "UMP conversion (0: no convert, 1: MIDI 1.0, 2: MIDI 2.0)");
65#endif
66
c7e0b5bf 67struct snd_seq_dummy_port {
1da177e4
LT
68 int client;
69 int port;
70 int duplex;
71 int connect;
c7e0b5bf 72};
1da177e4
LT
73
74static int my_client = -1;
75
1da177e4
LT
76/*
77 * event input callback - just redirect events to subscribers
78 */
79static int
c7e0b5bf
TI
80dummy_input(struct snd_seq_event *ev, int direct, void *private_data,
81 int atomic, int hop)
1da177e4 82{
c7e0b5bf
TI
83 struct snd_seq_dummy_port *p;
84 struct snd_seq_event tmpev;
1da177e4
LT
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 */
102static void
103dummy_free(void *private_data)
104{
4d572776 105 kfree(private_data);
1da177e4
LT
106}
107
108/*
109 * create a port
110 */
c7e0b5bf 111static struct snd_seq_dummy_port __init *
1da177e4
LT
112create_port(int idx, int type)
113{
c7e0b5bf
TI
114 struct snd_seq_port_info pinfo;
115 struct snd_seq_port_callback pcb;
116 struct snd_seq_dummy_port *rec;
1da177e4 117
f9a6bb84
TI
118 rec = kzalloc(sizeof(*rec), GFP_KERNEL);
119 if (!rec)
1da177e4
LT
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;
ff166a9d 136 pinfo.direction = SNDRV_SEQ_PORT_DIR_BIDIRECTION;
450047a7
CL
137 pinfo.type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC
138 | SNDRV_SEQ_PORT_TYPE_SOFTWARE
139 | SNDRV_SEQ_PORT_TYPE_PORT;
1da177e4
LT
140 memset(&pcb, 0, sizeof(pcb));
141 pcb.owner = THIS_MODULE;
1da177e4
LT
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 */
157static int __init
158register_client(void)
159{
c7e0b5bf 160 struct snd_seq_dummy_port *rec1, *rec2;
32cb23a0 161#if IS_ENABLED(CONFIG_SND_SEQ_UMP)
329ffe11 162 struct snd_seq_client *client;
32cb23a0 163#endif
1da177e4
LT
164 int i;
165
166 if (ports < 1) {
04cc79a0 167 pr_err("ALSA: seq_dummy: invalid number of ports %d\n", ports);
1da177e4
LT
168 return -EINVAL;
169 }
170
171 /* create client */
7b6d9245
CL
172 my_client = snd_seq_create_kernel_client(NULL, SNDRV_SEQ_CLIENT_DUMMY,
173 "Midi Through");
1da177e4
LT
174 if (my_client < 0)
175 return my_client;
176
32cb23a0 177#if IS_ENABLED(CONFIG_SND_SEQ_UMP)
329ffe11
TI
178 client = snd_seq_kernel_client_get(my_client);
179 if (!client)
180 return -EINVAL;
32cb23a0
TI
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 }
329ffe11 193 snd_seq_kernel_client_put(client);
32cb23a0 194#endif
329ffe11 195
1da177e4
LT
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 */
220static void __exit
221delete_client(void)
222{
223 if (my_client >= 0)
224 snd_seq_delete_kernel_client(my_client);
225}
226
227/*
228 * Init part
229 */
230
231static int __init alsa_seq_dummy_init(void)
232{
54a721ab 233 return register_client();
1da177e4
LT
234}
235
236static void __exit alsa_seq_dummy_exit(void)
237{
238 delete_client();
239}
240
241module_init(alsa_seq_dummy_init)
242module_exit(alsa_seq_dummy_exit)