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