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 = -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