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