brintos

brintos / linux-shallow public Read only

0
0
Text · 14.0 KiB · e5d3f4d Raw
506 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/* ALSA sequencer binding for UMP device */3 4#include <linux/init.h>5#include <linux/slab.h>6#include <linux/errno.h>7#include <linux/mutex.h>8#include <linux/string.h>9#include <linux/module.h>10#include <asm/byteorder.h>11#include <sound/core.h>12#include <sound/ump.h>13#include <sound/seq_kernel.h>14#include <sound/seq_device.h>15#include "seq_clientmgr.h"16#include "seq_system.h"17 18struct seq_ump_client;19struct seq_ump_group;20 21enum {22	STR_IN = SNDRV_RAWMIDI_STREAM_INPUT,23	STR_OUT = SNDRV_RAWMIDI_STREAM_OUTPUT24};25 26/* context for UMP input parsing, per EP */27struct seq_ump_input_buffer {28	unsigned char len;		/* total length in words */29	unsigned char pending;		/* pending words */30	unsigned char type;		/* parsed UMP packet type */31	unsigned char group;		/* parsed UMP packet group */32	u32 buf[4];			/* incoming UMP packet */33};34 35/* sequencer client, per UMP EP (rawmidi) */36struct seq_ump_client {37	struct snd_ump_endpoint *ump;	/* assigned endpoint */38	int seq_client;			/* sequencer client id */39	int opened[2];			/* current opens for each direction */40	struct snd_rawmidi_file out_rfile; /* rawmidi for output */41	struct seq_ump_input_buffer input; /* input parser context */42	void *ump_info[SNDRV_UMP_MAX_BLOCKS + 1]; /* shadow of seq client ump_info */43	struct work_struct group_notify_work; /* FB change notification */44};45 46/* number of 32bit words for each UMP message type */47static unsigned char ump_packet_words[0x10] = {48	1, 1, 1, 2, 2, 4, 1, 1, 2, 2, 2, 3, 3, 4, 4, 449};50 51/* conversion between UMP group and seq port;52 * assume the port number is equal with UMP group number (1-based)53 */54static unsigned char ump_group_to_seq_port(unsigned char group)55{56	return group + 1;57}58 59/* process the incoming rawmidi stream */60static void seq_ump_input_receive(struct snd_ump_endpoint *ump,61				  const u32 *val, int words)62{63	struct seq_ump_client *client = ump->seq_client;64	struct snd_seq_ump_event ev = {};65 66	if (!client->opened[STR_IN])67		return;68 69	if (ump_is_groupless_msg(ump_message_type(*val)))70		ev.source.port = 0; /* UMP EP port */71	else72		ev.source.port = ump_group_to_seq_port(ump_message_group(*val));73	ev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;74	ev.flags = SNDRV_SEQ_EVENT_UMP;75	memcpy(ev.ump, val, words << 2);76	snd_seq_kernel_client_dispatch(client->seq_client,77				       (struct snd_seq_event *)&ev,78				       true, 0);79}80 81/* process an input sequencer event; only deal with UMP types */82static int seq_ump_process_event(struct snd_seq_event *ev, int direct,83				 void *private_data, int atomic, int hop)84{85	struct seq_ump_client *client = private_data;86	struct snd_rawmidi_substream *substream;87	struct snd_seq_ump_event *ump_ev;88	unsigned char type;89	int len;90 91	substream = client->out_rfile.output;92	if (!substream)93		return -ENODEV;94	if (!snd_seq_ev_is_ump(ev))95		return 0; /* invalid event, skip */96	ump_ev = (struct snd_seq_ump_event *)ev;97	type = ump_message_type(ump_ev->ump[0]);98	len = ump_packet_words[type];99	if (len > 4)100		return 0; // invalid - skip101	snd_rawmidi_kernel_write(substream, ev->data.raw8.d, len << 2);102	return 0;103}104 105/* open the rawmidi */106static int seq_ump_client_open(struct seq_ump_client *client, int dir)107{108	struct snd_ump_endpoint *ump = client->ump;109	int err;110 111	guard(mutex)(&ump->open_mutex);112	if (dir == STR_OUT && !client->opened[dir]) {113		err = snd_rawmidi_kernel_open(&ump->core, 0,114					      SNDRV_RAWMIDI_LFLG_OUTPUT |115					      SNDRV_RAWMIDI_LFLG_APPEND,116					      &client->out_rfile);117		if (err < 0)118			return err;119	}120	client->opened[dir]++;121	return 0;122}123 124/* close the rawmidi */125static int seq_ump_client_close(struct seq_ump_client *client, int dir)126{127	struct snd_ump_endpoint *ump = client->ump;128 129	guard(mutex)(&ump->open_mutex);130	if (!--client->opened[dir])131		if (dir == STR_OUT)132			snd_rawmidi_kernel_release(&client->out_rfile);133	return 0;134}135 136/* sequencer subscription ops for each client */137static int seq_ump_subscribe(void *pdata, struct snd_seq_port_subscribe *info)138{139	struct seq_ump_client *client = pdata;140 141	return seq_ump_client_open(client, STR_IN);142}143 144static int seq_ump_unsubscribe(void *pdata, struct snd_seq_port_subscribe *info)145{146	struct seq_ump_client *client = pdata;147 148	return seq_ump_client_close(client, STR_IN);149}150 151static int seq_ump_use(void *pdata, struct snd_seq_port_subscribe *info)152{153	struct seq_ump_client *client = pdata;154 155	return seq_ump_client_open(client, STR_OUT);156}157 158static int seq_ump_unuse(void *pdata, struct snd_seq_port_subscribe *info)159{160	struct seq_ump_client *client = pdata;161 162	return seq_ump_client_close(client, STR_OUT);163}164 165/* fill port_info from the given UMP EP and group info */166static void fill_port_info(struct snd_seq_port_info *port,167			   struct seq_ump_client *client,168			   struct snd_ump_group *group)169{170	unsigned int rawmidi_info = client->ump->core.info_flags;171 172	port->addr.client = client->seq_client;173	port->addr.port = ump_group_to_seq_port(group->group);174	port->capability = 0;175	if (rawmidi_info & SNDRV_RAWMIDI_INFO_OUTPUT)176		port->capability |= SNDRV_SEQ_PORT_CAP_WRITE |177			SNDRV_SEQ_PORT_CAP_SYNC_WRITE |178			SNDRV_SEQ_PORT_CAP_SUBS_WRITE;179	if (rawmidi_info & SNDRV_RAWMIDI_INFO_INPUT)180		port->capability |= SNDRV_SEQ_PORT_CAP_READ |181			SNDRV_SEQ_PORT_CAP_SYNC_READ |182			SNDRV_SEQ_PORT_CAP_SUBS_READ;183	if (rawmidi_info & SNDRV_RAWMIDI_INFO_DUPLEX)184		port->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;185	if (group->dir_bits & (1 << STR_IN))186		port->direction |= SNDRV_SEQ_PORT_DIR_INPUT;187	if (group->dir_bits & (1 << STR_OUT))188		port->direction |= SNDRV_SEQ_PORT_DIR_OUTPUT;189	port->ump_group = group->group + 1;190	if (!group->active)191		port->capability |= SNDRV_SEQ_PORT_CAP_INACTIVE;192	if (group->is_midi1)193		port->flags |= SNDRV_SEQ_PORT_FLG_IS_MIDI1;194	port->type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC |195		SNDRV_SEQ_PORT_TYPE_MIDI_UMP |196		SNDRV_SEQ_PORT_TYPE_HARDWARE |197		SNDRV_SEQ_PORT_TYPE_PORT;198	port->midi_channels = 16;199	if (*group->name)200		snprintf(port->name, sizeof(port->name), "Group %d (%.53s)",201			 group->group + 1, group->name);202	else203		sprintf(port->name, "Group %d", group->group + 1);204}205 206/* skip non-existing group for static blocks */207static bool skip_group(struct seq_ump_client *client, struct snd_ump_group *group)208{209	return !group->valid &&210		(client->ump->info.flags & SNDRV_UMP_EP_INFO_STATIC_BLOCKS);211}212 213/* create a new sequencer port per UMP group */214static int seq_ump_group_init(struct seq_ump_client *client, int group_index)215{216	struct snd_ump_group *group = &client->ump->groups[group_index];217	struct snd_seq_port_info *port __free(kfree) = NULL;218	struct snd_seq_port_callback pcallbacks;219 220	if (skip_group(client, group))221		return 0;222 223	port = kzalloc(sizeof(*port), GFP_KERNEL);224	if (!port)225		return -ENOMEM;226 227	fill_port_info(port, client, group);228	port->flags |= SNDRV_SEQ_PORT_FLG_GIVEN_PORT;229	memset(&pcallbacks, 0, sizeof(pcallbacks));230	pcallbacks.owner = THIS_MODULE;231	pcallbacks.private_data = client;232	pcallbacks.subscribe = seq_ump_subscribe;233	pcallbacks.unsubscribe = seq_ump_unsubscribe;234	pcallbacks.use = seq_ump_use;235	pcallbacks.unuse = seq_ump_unuse;236	pcallbacks.event_input = seq_ump_process_event;237	port->kernel = &pcallbacks;238	return snd_seq_kernel_client_ctl(client->seq_client,239					 SNDRV_SEQ_IOCTL_CREATE_PORT,240					 port);241}242 243/* update the sequencer ports; called from notify_fb_change callback */244static void update_port_infos(struct seq_ump_client *client)245{246	struct snd_seq_port_info *old __free(kfree) = NULL;247	struct snd_seq_port_info *new __free(kfree) = NULL;248	int i, err;249 250	old = kzalloc(sizeof(*old), GFP_KERNEL);251	new = kzalloc(sizeof(*new), GFP_KERNEL);252	if (!old || !new)253		return;254 255	for (i = 0; i < SNDRV_UMP_MAX_GROUPS; i++) {256		if (skip_group(client, &client->ump->groups[i]))257			continue;258 259		old->addr.client = client->seq_client;260		old->addr.port = i;261		err = snd_seq_kernel_client_ctl(client->seq_client,262						SNDRV_SEQ_IOCTL_GET_PORT_INFO,263						old);264		if (err < 0)265			return;266		fill_port_info(new, client, &client->ump->groups[i]);267		if (old->capability == new->capability &&268		    !strcmp(old->name, new->name))269			continue;270		err = snd_seq_kernel_client_ctl(client->seq_client,271						SNDRV_SEQ_IOCTL_SET_PORT_INFO,272						new);273		if (err < 0)274			return;275		/* notify to system port */276		snd_seq_system_client_ev_port_change(client->seq_client, i);277	}278}279 280/* create a UMP Endpoint port */281static int create_ump_endpoint_port(struct seq_ump_client *client)282{283	struct snd_seq_port_info *port __free(kfree) = NULL;284	struct snd_seq_port_callback pcallbacks;285	unsigned int rawmidi_info = client->ump->core.info_flags;286	int err;287 288	port = kzalloc(sizeof(*port), GFP_KERNEL);289	if (!port)290		return -ENOMEM;291 292	port->addr.client = client->seq_client;293	port->addr.port = 0; /* fixed */294	port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT;295	port->capability = SNDRV_SEQ_PORT_CAP_UMP_ENDPOINT;296	if (rawmidi_info & SNDRV_RAWMIDI_INFO_INPUT) {297		port->capability |= SNDRV_SEQ_PORT_CAP_READ |298			SNDRV_SEQ_PORT_CAP_SYNC_READ |299			SNDRV_SEQ_PORT_CAP_SUBS_READ;300		port->direction |= SNDRV_SEQ_PORT_DIR_INPUT;301	}302	if (rawmidi_info & SNDRV_RAWMIDI_INFO_OUTPUT) {303		port->capability |= SNDRV_SEQ_PORT_CAP_WRITE |304			SNDRV_SEQ_PORT_CAP_SYNC_WRITE |305			SNDRV_SEQ_PORT_CAP_SUBS_WRITE;306		port->direction |= SNDRV_SEQ_PORT_DIR_OUTPUT;307	}308	if (rawmidi_info & SNDRV_RAWMIDI_INFO_DUPLEX)309		port->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;310	port->ump_group = 0; /* no associated group, no conversion */311	port->type = SNDRV_SEQ_PORT_TYPE_MIDI_UMP |312		SNDRV_SEQ_PORT_TYPE_HARDWARE |313		SNDRV_SEQ_PORT_TYPE_PORT;314	port->midi_channels = 16;315	strcpy(port->name, "MIDI 2.0");316	memset(&pcallbacks, 0, sizeof(pcallbacks));317	pcallbacks.owner = THIS_MODULE;318	pcallbacks.private_data = client;319	if (rawmidi_info & SNDRV_RAWMIDI_INFO_INPUT) {320		pcallbacks.subscribe = seq_ump_subscribe;321		pcallbacks.unsubscribe = seq_ump_unsubscribe;322	}323	if (rawmidi_info & SNDRV_RAWMIDI_INFO_OUTPUT) {324		pcallbacks.use = seq_ump_use;325		pcallbacks.unuse = seq_ump_unuse;326		pcallbacks.event_input = seq_ump_process_event;327	}328	port->kernel = &pcallbacks;329	err = snd_seq_kernel_client_ctl(client->seq_client,330					SNDRV_SEQ_IOCTL_CREATE_PORT,331					port);332	return err;333}334 335/* release the client resources */336static void seq_ump_client_free(struct seq_ump_client *client)337{338	cancel_work_sync(&client->group_notify_work);339 340	if (client->seq_client >= 0)341		snd_seq_delete_kernel_client(client->seq_client);342 343	client->ump->seq_ops = NULL;344	client->ump->seq_client = NULL;345 346	kfree(client);347}348 349/* update the MIDI version for the given client */350static void setup_client_midi_version(struct seq_ump_client *client)351{352	struct snd_seq_client *cptr;353 354	cptr = snd_seq_kernel_client_get(client->seq_client);355	if (!cptr)356		return;357	if (client->ump->info.protocol & SNDRV_UMP_EP_INFO_PROTO_MIDI2)358		cptr->midi_version = SNDRV_SEQ_CLIENT_UMP_MIDI_2_0;359	else360		cptr->midi_version = SNDRV_SEQ_CLIENT_UMP_MIDI_1_0;361	snd_seq_kernel_client_put(cptr);362}363 364/* set up client's group_filter bitmap */365static void setup_client_group_filter(struct seq_ump_client *client)366{367	struct snd_seq_client *cptr;368	unsigned int filter;369	int p;370 371	cptr = snd_seq_kernel_client_get(client->seq_client);372	if (!cptr)373		return;374	filter = ~(1U << 0); /* always allow groupless messages */375	for (p = 0; p < SNDRV_UMP_MAX_GROUPS; p++) {376		if (client->ump->groups[p].active)377			filter &= ~(1U << (p + 1));378	}379	cptr->group_filter = filter;380	snd_seq_kernel_client_put(cptr);381}382 383/* UMP group change notification */384static void handle_group_notify(struct work_struct *work)385{386	struct seq_ump_client *client =387		container_of(work, struct seq_ump_client, group_notify_work);388 389	update_port_infos(client);390	setup_client_group_filter(client);391}392 393/* UMP FB change notification */394static int seq_ump_notify_fb_change(struct snd_ump_endpoint *ump,395				    struct snd_ump_block *fb)396{397	struct seq_ump_client *client = ump->seq_client;398 399	if (!client)400		return -ENODEV;401	schedule_work(&client->group_notify_work);402	return 0;403}404 405/* UMP protocol change notification; just update the midi_version field */406static int seq_ump_switch_protocol(struct snd_ump_endpoint *ump)407{408	if (!ump->seq_client)409		return -ENODEV;410	setup_client_midi_version(ump->seq_client);411	return 0;412}413 414static const struct snd_seq_ump_ops seq_ump_ops = {415	.input_receive = seq_ump_input_receive,416	.notify_fb_change = seq_ump_notify_fb_change,417	.switch_protocol = seq_ump_switch_protocol,418};419 420/* create a sequencer client and ports for the given UMP endpoint */421static int snd_seq_ump_probe(struct device *_dev)422{423	struct snd_seq_device *dev = to_seq_dev(_dev);424	struct snd_ump_endpoint *ump = dev->private_data;425	struct snd_card *card = dev->card;426	struct seq_ump_client *client;427	struct snd_ump_block *fb;428	struct snd_seq_client *cptr;429	int p, err;430 431	client = kzalloc(sizeof(*client), GFP_KERNEL);432	if (!client)433		return -ENOMEM;434 435	INIT_WORK(&client->group_notify_work, handle_group_notify);436	client->ump = ump;437 438	client->seq_client =439		snd_seq_create_kernel_client(card, ump->core.device,440					     ump->core.name);441	if (client->seq_client < 0) {442		err = client->seq_client;443		goto error;444	}445 446	client->ump_info[0] = &ump->info;447	list_for_each_entry(fb, &ump->block_list, list)448		client->ump_info[fb->info.block_id + 1] = &fb->info;449 450	setup_client_midi_version(client);451 452	for (p = 0; p < SNDRV_UMP_MAX_GROUPS; p++) {453		err = seq_ump_group_init(client, p);454		if (err < 0)455			goto error;456	}457 458	setup_client_group_filter(client);459 460	err = create_ump_endpoint_port(client);461	if (err < 0)462		goto error;463 464	cptr = snd_seq_kernel_client_get(client->seq_client);465	if (!cptr) {466		err = -EINVAL;467		goto error;468	}469	cptr->ump_info = client->ump_info;470	snd_seq_kernel_client_put(cptr);471 472	ump->seq_client = client;473	ump->seq_ops = &seq_ump_ops;474	return 0;475 476 error:477	seq_ump_client_free(client);478	return err;479}480 481/* remove a sequencer client */482static int snd_seq_ump_remove(struct device *_dev)483{484	struct snd_seq_device *dev = to_seq_dev(_dev);485	struct snd_ump_endpoint *ump = dev->private_data;486 487	if (ump->seq_client)488		seq_ump_client_free(ump->seq_client);489	return 0;490}491 492static struct snd_seq_driver seq_ump_driver = {493	.driver = {494		.name = KBUILD_MODNAME,495		.probe = snd_seq_ump_probe,496		.remove = snd_seq_ump_remove,497	},498	.id = SNDRV_SEQ_DEV_ID_UMP,499	.argsize = 0,500};501 502module_snd_seq_driver(seq_ump_driver);503 504MODULE_DESCRIPTION("ALSA sequencer client for UMP rawmidi");505MODULE_LICENSE("GPL");506