162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Universal MIDI Packet (UMP) support 462306a36Sopenharmony_ci */ 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci#include <linux/list.h> 762306a36Sopenharmony_ci#include <linux/slab.h> 862306a36Sopenharmony_ci#include <linux/module.h> 962306a36Sopenharmony_ci#include <linux/export.h> 1062306a36Sopenharmony_ci#include <linux/mm.h> 1162306a36Sopenharmony_ci#include <sound/core.h> 1262306a36Sopenharmony_ci#include <sound/rawmidi.h> 1362306a36Sopenharmony_ci#include <sound/ump.h> 1462306a36Sopenharmony_ci#include <sound/ump_convert.h> 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci#define ump_err(ump, fmt, args...) dev_err((ump)->core.dev, fmt, ##args) 1762306a36Sopenharmony_ci#define ump_warn(ump, fmt, args...) dev_warn((ump)->core.dev, fmt, ##args) 1862306a36Sopenharmony_ci#define ump_info(ump, fmt, args...) dev_info((ump)->core.dev, fmt, ##args) 1962306a36Sopenharmony_ci#define ump_dbg(ump, fmt, args...) dev_dbg((ump)->core.dev, fmt, ##args) 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_cistatic int snd_ump_dev_register(struct snd_rawmidi *rmidi); 2262306a36Sopenharmony_cistatic int snd_ump_dev_unregister(struct snd_rawmidi *rmidi); 2362306a36Sopenharmony_cistatic long snd_ump_ioctl(struct snd_rawmidi *rmidi, unsigned int cmd, 2462306a36Sopenharmony_ci void __user *argp); 2562306a36Sopenharmony_cistatic void snd_ump_proc_read(struct snd_info_entry *entry, 2662306a36Sopenharmony_ci struct snd_info_buffer *buffer); 2762306a36Sopenharmony_cistatic int snd_ump_rawmidi_open(struct snd_rawmidi_substream *substream); 2862306a36Sopenharmony_cistatic int snd_ump_rawmidi_close(struct snd_rawmidi_substream *substream); 2962306a36Sopenharmony_cistatic void snd_ump_rawmidi_trigger(struct snd_rawmidi_substream *substream, 3062306a36Sopenharmony_ci int up); 3162306a36Sopenharmony_cistatic void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream); 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_cistatic void ump_handle_stream_msg(struct snd_ump_endpoint *ump, 3462306a36Sopenharmony_ci const u32 *buf, int size); 3562306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) 3662306a36Sopenharmony_cistatic int process_legacy_output(struct snd_ump_endpoint *ump, 3762306a36Sopenharmony_ci u32 *buffer, int count); 3862306a36Sopenharmony_cistatic void process_legacy_input(struct snd_ump_endpoint *ump, const u32 *src, 3962306a36Sopenharmony_ci int words); 4062306a36Sopenharmony_ci#else 4162306a36Sopenharmony_cistatic inline int process_legacy_output(struct snd_ump_endpoint *ump, 4262306a36Sopenharmony_ci u32 *buffer, int count) 4362306a36Sopenharmony_ci{ 4462306a36Sopenharmony_ci return 0; 4562306a36Sopenharmony_ci} 4662306a36Sopenharmony_cistatic inline void process_legacy_input(struct snd_ump_endpoint *ump, 4762306a36Sopenharmony_ci const u32 *src, int words) 4862306a36Sopenharmony_ci{ 4962306a36Sopenharmony_ci} 5062306a36Sopenharmony_ci#endif 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_cistatic const struct snd_rawmidi_global_ops snd_ump_rawmidi_ops = { 5362306a36Sopenharmony_ci .dev_register = snd_ump_dev_register, 5462306a36Sopenharmony_ci .dev_unregister = snd_ump_dev_unregister, 5562306a36Sopenharmony_ci .ioctl = snd_ump_ioctl, 5662306a36Sopenharmony_ci .proc_read = snd_ump_proc_read, 5762306a36Sopenharmony_ci}; 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_cistatic const struct snd_rawmidi_ops snd_ump_rawmidi_input_ops = { 6062306a36Sopenharmony_ci .open = snd_ump_rawmidi_open, 6162306a36Sopenharmony_ci .close = snd_ump_rawmidi_close, 6262306a36Sopenharmony_ci .trigger = snd_ump_rawmidi_trigger, 6362306a36Sopenharmony_ci}; 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_cistatic const struct snd_rawmidi_ops snd_ump_rawmidi_output_ops = { 6662306a36Sopenharmony_ci .open = snd_ump_rawmidi_open, 6762306a36Sopenharmony_ci .close = snd_ump_rawmidi_close, 6862306a36Sopenharmony_ci .trigger = snd_ump_rawmidi_trigger, 6962306a36Sopenharmony_ci .drain = snd_ump_rawmidi_drain, 7062306a36Sopenharmony_ci}; 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_cistatic void snd_ump_endpoint_free(struct snd_rawmidi *rmidi) 7362306a36Sopenharmony_ci{ 7462306a36Sopenharmony_ci struct snd_ump_endpoint *ump = rawmidi_to_ump(rmidi); 7562306a36Sopenharmony_ci struct snd_ump_block *fb; 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci while (!list_empty(&ump->block_list)) { 7862306a36Sopenharmony_ci fb = list_first_entry(&ump->block_list, struct snd_ump_block, 7962306a36Sopenharmony_ci list); 8062306a36Sopenharmony_ci list_del(&fb->list); 8162306a36Sopenharmony_ci if (fb->private_free) 8262306a36Sopenharmony_ci fb->private_free(fb); 8362306a36Sopenharmony_ci kfree(fb); 8462306a36Sopenharmony_ci } 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci if (ump->private_free) 8762306a36Sopenharmony_ci ump->private_free(ump); 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) 9062306a36Sopenharmony_ci kfree(ump->out_cvts); 9162306a36Sopenharmony_ci#endif 9262306a36Sopenharmony_ci} 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci/** 9562306a36Sopenharmony_ci * snd_ump_endpoint_new - create a UMP Endpoint object 9662306a36Sopenharmony_ci * @card: the card instance 9762306a36Sopenharmony_ci * @id: the id string for rawmidi 9862306a36Sopenharmony_ci * @device: the device index for rawmidi 9962306a36Sopenharmony_ci * @output: 1 for enabling output 10062306a36Sopenharmony_ci * @input: 1 for enabling input 10162306a36Sopenharmony_ci * @ump_ret: the pointer to store the new UMP instance 10262306a36Sopenharmony_ci * 10362306a36Sopenharmony_ci * Creates a new UMP Endpoint object. A UMP Endpoint is tied with one rawmidi 10462306a36Sopenharmony_ci * instance with one input and/or one output rawmidi stream (either uni- 10562306a36Sopenharmony_ci * or bi-directional). A UMP Endpoint may contain one or multiple UMP Blocks 10662306a36Sopenharmony_ci * that consist of one or multiple UMP Groups. 10762306a36Sopenharmony_ci * 10862306a36Sopenharmony_ci * Use snd_rawmidi_set_ops() to set the operators to the new instance. 10962306a36Sopenharmony_ci * Unlike snd_rawmidi_new(), this function sets up the info_flags by itself 11062306a36Sopenharmony_ci * depending on the given @output and @input. 11162306a36Sopenharmony_ci * 11262306a36Sopenharmony_ci * The device has SNDRV_RAWMIDI_INFO_UMP flag set and a different device 11362306a36Sopenharmony_ci * file ("umpCxDx") than a standard MIDI 1.x device ("midiCxDx") is 11462306a36Sopenharmony_ci * created. 11562306a36Sopenharmony_ci * 11662306a36Sopenharmony_ci * Return: Zero if successful, or a negative error code on failure. 11762306a36Sopenharmony_ci */ 11862306a36Sopenharmony_ciint snd_ump_endpoint_new(struct snd_card *card, char *id, int device, 11962306a36Sopenharmony_ci int output, int input, 12062306a36Sopenharmony_ci struct snd_ump_endpoint **ump_ret) 12162306a36Sopenharmony_ci{ 12262306a36Sopenharmony_ci unsigned int info_flags = SNDRV_RAWMIDI_INFO_UMP; 12362306a36Sopenharmony_ci struct snd_ump_endpoint *ump; 12462306a36Sopenharmony_ci int err; 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci if (input) 12762306a36Sopenharmony_ci info_flags |= SNDRV_RAWMIDI_INFO_INPUT; 12862306a36Sopenharmony_ci if (output) 12962306a36Sopenharmony_ci info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; 13062306a36Sopenharmony_ci if (input && output) 13162306a36Sopenharmony_ci info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci ump = kzalloc(sizeof(*ump), GFP_KERNEL); 13462306a36Sopenharmony_ci if (!ump) 13562306a36Sopenharmony_ci return -ENOMEM; 13662306a36Sopenharmony_ci INIT_LIST_HEAD(&ump->block_list); 13762306a36Sopenharmony_ci mutex_init(&ump->open_mutex); 13862306a36Sopenharmony_ci init_waitqueue_head(&ump->stream_wait); 13962306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) 14062306a36Sopenharmony_ci spin_lock_init(&ump->legacy_locks[0]); 14162306a36Sopenharmony_ci spin_lock_init(&ump->legacy_locks[1]); 14262306a36Sopenharmony_ci#endif 14362306a36Sopenharmony_ci err = snd_rawmidi_init(&ump->core, card, id, device, 14462306a36Sopenharmony_ci output, input, info_flags); 14562306a36Sopenharmony_ci if (err < 0) { 14662306a36Sopenharmony_ci snd_rawmidi_free(&ump->core); 14762306a36Sopenharmony_ci return err; 14862306a36Sopenharmony_ci } 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci ump->info.card = card->number; 15162306a36Sopenharmony_ci ump->info.device = device; 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci ump->core.private_free = snd_ump_endpoint_free; 15462306a36Sopenharmony_ci ump->core.ops = &snd_ump_rawmidi_ops; 15562306a36Sopenharmony_ci if (input) 15662306a36Sopenharmony_ci snd_rawmidi_set_ops(&ump->core, SNDRV_RAWMIDI_STREAM_INPUT, 15762306a36Sopenharmony_ci &snd_ump_rawmidi_input_ops); 15862306a36Sopenharmony_ci if (output) 15962306a36Sopenharmony_ci snd_rawmidi_set_ops(&ump->core, SNDRV_RAWMIDI_STREAM_OUTPUT, 16062306a36Sopenharmony_ci &snd_ump_rawmidi_output_ops); 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci ump_dbg(ump, "Created a UMP EP #%d (%s)\n", device, id); 16362306a36Sopenharmony_ci *ump_ret = ump; 16462306a36Sopenharmony_ci return 0; 16562306a36Sopenharmony_ci} 16662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_ump_endpoint_new); 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci/* 16962306a36Sopenharmony_ci * Device register / unregister hooks; 17062306a36Sopenharmony_ci * do nothing, placeholders for avoiding the default rawmidi handling 17162306a36Sopenharmony_ci */ 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_SND_SEQUENCER) 17462306a36Sopenharmony_cistatic void snd_ump_dev_seq_free(struct snd_seq_device *device) 17562306a36Sopenharmony_ci{ 17662306a36Sopenharmony_ci struct snd_ump_endpoint *ump = device->private_data; 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci ump->seq_dev = NULL; 17962306a36Sopenharmony_ci} 18062306a36Sopenharmony_ci#endif 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_cistatic int snd_ump_dev_register(struct snd_rawmidi *rmidi) 18362306a36Sopenharmony_ci{ 18462306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_SND_SEQUENCER) 18562306a36Sopenharmony_ci struct snd_ump_endpoint *ump = rawmidi_to_ump(rmidi); 18662306a36Sopenharmony_ci int err; 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci err = snd_seq_device_new(ump->core.card, ump->core.device, 18962306a36Sopenharmony_ci SNDRV_SEQ_DEV_ID_UMP, 0, &ump->seq_dev); 19062306a36Sopenharmony_ci if (err < 0) 19162306a36Sopenharmony_ci return err; 19262306a36Sopenharmony_ci ump->seq_dev->private_data = ump; 19362306a36Sopenharmony_ci ump->seq_dev->private_free = snd_ump_dev_seq_free; 19462306a36Sopenharmony_ci snd_device_register(ump->core.card, ump->seq_dev); 19562306a36Sopenharmony_ci#endif 19662306a36Sopenharmony_ci return 0; 19762306a36Sopenharmony_ci} 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_cistatic int snd_ump_dev_unregister(struct snd_rawmidi *rmidi) 20062306a36Sopenharmony_ci{ 20162306a36Sopenharmony_ci return 0; 20262306a36Sopenharmony_ci} 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_cistatic struct snd_ump_block * 20562306a36Sopenharmony_cisnd_ump_get_block(struct snd_ump_endpoint *ump, unsigned char id) 20662306a36Sopenharmony_ci{ 20762306a36Sopenharmony_ci struct snd_ump_block *fb; 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci list_for_each_entry(fb, &ump->block_list, list) { 21062306a36Sopenharmony_ci if (fb->info.block_id == id) 21162306a36Sopenharmony_ci return fb; 21262306a36Sopenharmony_ci } 21362306a36Sopenharmony_ci return NULL; 21462306a36Sopenharmony_ci} 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci/* 21762306a36Sopenharmony_ci * rawmidi ops for UMP endpoint 21862306a36Sopenharmony_ci */ 21962306a36Sopenharmony_cistatic int snd_ump_rawmidi_open(struct snd_rawmidi_substream *substream) 22062306a36Sopenharmony_ci{ 22162306a36Sopenharmony_ci struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi); 22262306a36Sopenharmony_ci int dir = substream->stream; 22362306a36Sopenharmony_ci int err; 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci if (ump->substreams[dir]) 22662306a36Sopenharmony_ci return -EBUSY; 22762306a36Sopenharmony_ci err = ump->ops->open(ump, dir); 22862306a36Sopenharmony_ci if (err < 0) 22962306a36Sopenharmony_ci return err; 23062306a36Sopenharmony_ci ump->substreams[dir] = substream; 23162306a36Sopenharmony_ci return 0; 23262306a36Sopenharmony_ci} 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_cistatic int snd_ump_rawmidi_close(struct snd_rawmidi_substream *substream) 23562306a36Sopenharmony_ci{ 23662306a36Sopenharmony_ci struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi); 23762306a36Sopenharmony_ci int dir = substream->stream; 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci ump->substreams[dir] = NULL; 24062306a36Sopenharmony_ci ump->ops->close(ump, dir); 24162306a36Sopenharmony_ci return 0; 24262306a36Sopenharmony_ci} 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_cistatic void snd_ump_rawmidi_trigger(struct snd_rawmidi_substream *substream, 24562306a36Sopenharmony_ci int up) 24662306a36Sopenharmony_ci{ 24762306a36Sopenharmony_ci struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi); 24862306a36Sopenharmony_ci int dir = substream->stream; 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci ump->ops->trigger(ump, dir, up); 25162306a36Sopenharmony_ci} 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_cistatic void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream) 25462306a36Sopenharmony_ci{ 25562306a36Sopenharmony_ci struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi); 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci if (ump->ops->drain) 25862306a36Sopenharmony_ci ump->ops->drain(ump, SNDRV_RAWMIDI_STREAM_OUTPUT); 25962306a36Sopenharmony_ci} 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci/* number of 32bit words per message type */ 26262306a36Sopenharmony_cistatic unsigned char ump_packet_words[0x10] = { 26362306a36Sopenharmony_ci 1, 1, 1, 2, 2, 4, 1, 1, 2, 2, 2, 3, 3, 4, 4, 4 26462306a36Sopenharmony_ci}; 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci/** 26762306a36Sopenharmony_ci * snd_ump_receive_ump_val - parse the UMP packet data 26862306a36Sopenharmony_ci * @ump: UMP endpoint 26962306a36Sopenharmony_ci * @val: UMP packet data 27062306a36Sopenharmony_ci * 27162306a36Sopenharmony_ci * The data is copied onto ump->input_buf[]. 27262306a36Sopenharmony_ci * When a full packet is completed, returns the number of words (from 1 to 4). 27362306a36Sopenharmony_ci * OTOH, if the packet is incomplete, returns 0. 27462306a36Sopenharmony_ci */ 27562306a36Sopenharmony_ciint snd_ump_receive_ump_val(struct snd_ump_endpoint *ump, u32 val) 27662306a36Sopenharmony_ci{ 27762306a36Sopenharmony_ci int words; 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ci if (!ump->input_pending) 28062306a36Sopenharmony_ci ump->input_pending = ump_packet_words[ump_message_type(val)]; 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci ump->input_buf[ump->input_buf_head++] = val; 28362306a36Sopenharmony_ci ump->input_pending--; 28462306a36Sopenharmony_ci if (!ump->input_pending) { 28562306a36Sopenharmony_ci words = ump->input_buf_head; 28662306a36Sopenharmony_ci ump->input_buf_head = 0; 28762306a36Sopenharmony_ci return words; 28862306a36Sopenharmony_ci } 28962306a36Sopenharmony_ci return 0; 29062306a36Sopenharmony_ci} 29162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_ump_receive_ump_val); 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci/** 29462306a36Sopenharmony_ci * snd_ump_receive - transfer UMP packets from the device 29562306a36Sopenharmony_ci * @ump: the UMP endpoint 29662306a36Sopenharmony_ci * @buffer: the buffer pointer to transfer 29762306a36Sopenharmony_ci * @count: byte size to transfer 29862306a36Sopenharmony_ci * 29962306a36Sopenharmony_ci * Called from the driver to submit the received UMP packets from the device 30062306a36Sopenharmony_ci * to user-space. It's essentially a wrapper of rawmidi_receive(). 30162306a36Sopenharmony_ci * The data to receive is in CPU-native endianness. 30262306a36Sopenharmony_ci */ 30362306a36Sopenharmony_ciint snd_ump_receive(struct snd_ump_endpoint *ump, const u32 *buffer, int count) 30462306a36Sopenharmony_ci{ 30562306a36Sopenharmony_ci struct snd_rawmidi_substream *substream; 30662306a36Sopenharmony_ci const u32 *p = buffer; 30762306a36Sopenharmony_ci int n, words = count >> 2; 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ci while (words--) { 31062306a36Sopenharmony_ci n = snd_ump_receive_ump_val(ump, *p++); 31162306a36Sopenharmony_ci if (!n) 31262306a36Sopenharmony_ci continue; 31362306a36Sopenharmony_ci ump_handle_stream_msg(ump, ump->input_buf, n); 31462306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_SND_SEQUENCER) 31562306a36Sopenharmony_ci if (ump->seq_ops) 31662306a36Sopenharmony_ci ump->seq_ops->input_receive(ump, ump->input_buf, n); 31762306a36Sopenharmony_ci#endif 31862306a36Sopenharmony_ci process_legacy_input(ump, ump->input_buf, n); 31962306a36Sopenharmony_ci } 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_ci substream = ump->substreams[SNDRV_RAWMIDI_STREAM_INPUT]; 32262306a36Sopenharmony_ci if (!substream) 32362306a36Sopenharmony_ci return 0; 32462306a36Sopenharmony_ci return snd_rawmidi_receive(substream, (const char *)buffer, count); 32562306a36Sopenharmony_ci} 32662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_ump_receive); 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_ci/** 32962306a36Sopenharmony_ci * snd_ump_transmit - transmit UMP packets 33062306a36Sopenharmony_ci * @ump: the UMP endpoint 33162306a36Sopenharmony_ci * @buffer: the buffer pointer to transfer 33262306a36Sopenharmony_ci * @count: byte size to transfer 33362306a36Sopenharmony_ci * 33462306a36Sopenharmony_ci * Called from the driver to obtain the UMP packets from user-space to the 33562306a36Sopenharmony_ci * device. It's essentially a wrapper of rawmidi_transmit(). 33662306a36Sopenharmony_ci * The data to transmit is in CPU-native endianness. 33762306a36Sopenharmony_ci */ 33862306a36Sopenharmony_ciint snd_ump_transmit(struct snd_ump_endpoint *ump, u32 *buffer, int count) 33962306a36Sopenharmony_ci{ 34062306a36Sopenharmony_ci struct snd_rawmidi_substream *substream = 34162306a36Sopenharmony_ci ump->substreams[SNDRV_RAWMIDI_STREAM_OUTPUT]; 34262306a36Sopenharmony_ci int err; 34362306a36Sopenharmony_ci 34462306a36Sopenharmony_ci if (!substream) 34562306a36Sopenharmony_ci return -ENODEV; 34662306a36Sopenharmony_ci err = snd_rawmidi_transmit(substream, (char *)buffer, count); 34762306a36Sopenharmony_ci /* received either data or an error? */ 34862306a36Sopenharmony_ci if (err) 34962306a36Sopenharmony_ci return err; 35062306a36Sopenharmony_ci return process_legacy_output(ump, buffer, count); 35162306a36Sopenharmony_ci} 35262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_ump_transmit); 35362306a36Sopenharmony_ci 35462306a36Sopenharmony_ci/** 35562306a36Sopenharmony_ci * snd_ump_block_new - Create a UMP block 35662306a36Sopenharmony_ci * @ump: UMP object 35762306a36Sopenharmony_ci * @blk: block ID number to create 35862306a36Sopenharmony_ci * @direction: direction (in/out/bidirection) 35962306a36Sopenharmony_ci * @first_group: the first group ID (0-based) 36062306a36Sopenharmony_ci * @num_groups: the number of groups in this block 36162306a36Sopenharmony_ci * @blk_ret: the pointer to store the resultant block object 36262306a36Sopenharmony_ci */ 36362306a36Sopenharmony_ciint snd_ump_block_new(struct snd_ump_endpoint *ump, unsigned int blk, 36462306a36Sopenharmony_ci unsigned int direction, unsigned int first_group, 36562306a36Sopenharmony_ci unsigned int num_groups, struct snd_ump_block **blk_ret) 36662306a36Sopenharmony_ci{ 36762306a36Sopenharmony_ci struct snd_ump_block *fb, *p; 36862306a36Sopenharmony_ci 36962306a36Sopenharmony_ci if (blk < 0 || blk >= SNDRV_UMP_MAX_BLOCKS) 37062306a36Sopenharmony_ci return -EINVAL; 37162306a36Sopenharmony_ci 37262306a36Sopenharmony_ci if (snd_ump_get_block(ump, blk)) 37362306a36Sopenharmony_ci return -EBUSY; 37462306a36Sopenharmony_ci 37562306a36Sopenharmony_ci fb = kzalloc(sizeof(*fb), GFP_KERNEL); 37662306a36Sopenharmony_ci if (!fb) 37762306a36Sopenharmony_ci return -ENOMEM; 37862306a36Sopenharmony_ci 37962306a36Sopenharmony_ci fb->ump = ump; 38062306a36Sopenharmony_ci fb->info.card = ump->info.card; 38162306a36Sopenharmony_ci fb->info.device = ump->info.device; 38262306a36Sopenharmony_ci fb->info.block_id = blk; 38362306a36Sopenharmony_ci if (blk >= ump->info.num_blocks) 38462306a36Sopenharmony_ci ump->info.num_blocks = blk + 1; 38562306a36Sopenharmony_ci fb->info.direction = direction; 38662306a36Sopenharmony_ci fb->info.active = 1; 38762306a36Sopenharmony_ci fb->info.first_group = first_group; 38862306a36Sopenharmony_ci fb->info.num_groups = num_groups; 38962306a36Sopenharmony_ci /* fill the default name, may be overwritten to a better name */ 39062306a36Sopenharmony_ci snprintf(fb->info.name, sizeof(fb->info.name), "Group %d-%d", 39162306a36Sopenharmony_ci first_group + 1, first_group + num_groups); 39262306a36Sopenharmony_ci 39362306a36Sopenharmony_ci /* put the entry in the ordered list */ 39462306a36Sopenharmony_ci list_for_each_entry(p, &ump->block_list, list) { 39562306a36Sopenharmony_ci if (p->info.block_id > blk) { 39662306a36Sopenharmony_ci list_add_tail(&fb->list, &p->list); 39762306a36Sopenharmony_ci goto added; 39862306a36Sopenharmony_ci } 39962306a36Sopenharmony_ci } 40062306a36Sopenharmony_ci list_add_tail(&fb->list, &ump->block_list); 40162306a36Sopenharmony_ci 40262306a36Sopenharmony_ci added: 40362306a36Sopenharmony_ci ump_dbg(ump, "Created a UMP Block #%d (%s)\n", blk, fb->info.name); 40462306a36Sopenharmony_ci *blk_ret = fb; 40562306a36Sopenharmony_ci return 0; 40662306a36Sopenharmony_ci} 40762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_ump_block_new); 40862306a36Sopenharmony_ci 40962306a36Sopenharmony_cistatic int snd_ump_ioctl_block(struct snd_ump_endpoint *ump, 41062306a36Sopenharmony_ci struct snd_ump_block_info __user *argp) 41162306a36Sopenharmony_ci{ 41262306a36Sopenharmony_ci struct snd_ump_block *fb; 41362306a36Sopenharmony_ci unsigned char id; 41462306a36Sopenharmony_ci 41562306a36Sopenharmony_ci if (get_user(id, &argp->block_id)) 41662306a36Sopenharmony_ci return -EFAULT; 41762306a36Sopenharmony_ci fb = snd_ump_get_block(ump, id); 41862306a36Sopenharmony_ci if (!fb) 41962306a36Sopenharmony_ci return -ENOENT; 42062306a36Sopenharmony_ci if (copy_to_user(argp, &fb->info, sizeof(fb->info))) 42162306a36Sopenharmony_ci return -EFAULT; 42262306a36Sopenharmony_ci return 0; 42362306a36Sopenharmony_ci} 42462306a36Sopenharmony_ci 42562306a36Sopenharmony_ci/* 42662306a36Sopenharmony_ci * Handle UMP-specific ioctls; called from snd_rawmidi_ioctl() 42762306a36Sopenharmony_ci */ 42862306a36Sopenharmony_cistatic long snd_ump_ioctl(struct snd_rawmidi *rmidi, unsigned int cmd, 42962306a36Sopenharmony_ci void __user *argp) 43062306a36Sopenharmony_ci{ 43162306a36Sopenharmony_ci struct snd_ump_endpoint *ump = rawmidi_to_ump(rmidi); 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci switch (cmd) { 43462306a36Sopenharmony_ci case SNDRV_UMP_IOCTL_ENDPOINT_INFO: 43562306a36Sopenharmony_ci if (copy_to_user(argp, &ump->info, sizeof(ump->info))) 43662306a36Sopenharmony_ci return -EFAULT; 43762306a36Sopenharmony_ci return 0; 43862306a36Sopenharmony_ci case SNDRV_UMP_IOCTL_BLOCK_INFO: 43962306a36Sopenharmony_ci return snd_ump_ioctl_block(ump, argp); 44062306a36Sopenharmony_ci default: 44162306a36Sopenharmony_ci ump_dbg(ump, "rawmidi: unknown command = 0x%x\n", cmd); 44262306a36Sopenharmony_ci return -ENOTTY; 44362306a36Sopenharmony_ci } 44462306a36Sopenharmony_ci} 44562306a36Sopenharmony_ci 44662306a36Sopenharmony_cistatic const char *ump_direction_string(int dir) 44762306a36Sopenharmony_ci{ 44862306a36Sopenharmony_ci switch (dir) { 44962306a36Sopenharmony_ci case SNDRV_UMP_DIR_INPUT: 45062306a36Sopenharmony_ci return "input"; 45162306a36Sopenharmony_ci case SNDRV_UMP_DIR_OUTPUT: 45262306a36Sopenharmony_ci return "output"; 45362306a36Sopenharmony_ci case SNDRV_UMP_DIR_BIDIRECTION: 45462306a36Sopenharmony_ci return "bidirection"; 45562306a36Sopenharmony_ci default: 45662306a36Sopenharmony_ci return "unknown"; 45762306a36Sopenharmony_ci } 45862306a36Sopenharmony_ci} 45962306a36Sopenharmony_ci 46062306a36Sopenharmony_cistatic const char *ump_ui_hint_string(int dir) 46162306a36Sopenharmony_ci{ 46262306a36Sopenharmony_ci switch (dir) { 46362306a36Sopenharmony_ci case SNDRV_UMP_BLOCK_UI_HINT_RECEIVER: 46462306a36Sopenharmony_ci return "receiver"; 46562306a36Sopenharmony_ci case SNDRV_UMP_BLOCK_UI_HINT_SENDER: 46662306a36Sopenharmony_ci return "sender"; 46762306a36Sopenharmony_ci case SNDRV_UMP_BLOCK_UI_HINT_BOTH: 46862306a36Sopenharmony_ci return "both"; 46962306a36Sopenharmony_ci default: 47062306a36Sopenharmony_ci return "unknown"; 47162306a36Sopenharmony_ci } 47262306a36Sopenharmony_ci} 47362306a36Sopenharmony_ci 47462306a36Sopenharmony_ci/* Additional proc file output */ 47562306a36Sopenharmony_cistatic void snd_ump_proc_read(struct snd_info_entry *entry, 47662306a36Sopenharmony_ci struct snd_info_buffer *buffer) 47762306a36Sopenharmony_ci{ 47862306a36Sopenharmony_ci struct snd_rawmidi *rmidi = entry->private_data; 47962306a36Sopenharmony_ci struct snd_ump_endpoint *ump = rawmidi_to_ump(rmidi); 48062306a36Sopenharmony_ci struct snd_ump_block *fb; 48162306a36Sopenharmony_ci 48262306a36Sopenharmony_ci snd_iprintf(buffer, "EP Name: %s\n", ump->info.name); 48362306a36Sopenharmony_ci snd_iprintf(buffer, "EP Product ID: %s\n", ump->info.product_id); 48462306a36Sopenharmony_ci snd_iprintf(buffer, "UMP Version: 0x%04x\n", ump->info.version); 48562306a36Sopenharmony_ci snd_iprintf(buffer, "Protocol Caps: 0x%08x\n", ump->info.protocol_caps); 48662306a36Sopenharmony_ci snd_iprintf(buffer, "Protocol: 0x%08x\n", ump->info.protocol); 48762306a36Sopenharmony_ci if (ump->info.version) { 48862306a36Sopenharmony_ci snd_iprintf(buffer, "Manufacturer ID: 0x%08x\n", 48962306a36Sopenharmony_ci ump->info.manufacturer_id); 49062306a36Sopenharmony_ci snd_iprintf(buffer, "Family ID: 0x%04x\n", ump->info.family_id); 49162306a36Sopenharmony_ci snd_iprintf(buffer, "Model ID: 0x%04x\n", ump->info.model_id); 49262306a36Sopenharmony_ci snd_iprintf(buffer, "SW Revision: 0x%02x%02x%02x%02x\n", 49362306a36Sopenharmony_ci ump->info.sw_revision[0], 49462306a36Sopenharmony_ci ump->info.sw_revision[1], 49562306a36Sopenharmony_ci ump->info.sw_revision[2], 49662306a36Sopenharmony_ci ump->info.sw_revision[3]); 49762306a36Sopenharmony_ci } 49862306a36Sopenharmony_ci snd_iprintf(buffer, "Static Blocks: %s\n", 49962306a36Sopenharmony_ci (ump->info.flags & SNDRV_UMP_EP_INFO_STATIC_BLOCKS) ? "Yes" : "No"); 50062306a36Sopenharmony_ci snd_iprintf(buffer, "Num Blocks: %d\n\n", ump->info.num_blocks); 50162306a36Sopenharmony_ci 50262306a36Sopenharmony_ci list_for_each_entry(fb, &ump->block_list, list) { 50362306a36Sopenharmony_ci snd_iprintf(buffer, "Block %d (%s)\n", fb->info.block_id, 50462306a36Sopenharmony_ci fb->info.name); 50562306a36Sopenharmony_ci snd_iprintf(buffer, " Direction: %s\n", 50662306a36Sopenharmony_ci ump_direction_string(fb->info.direction)); 50762306a36Sopenharmony_ci snd_iprintf(buffer, " Active: %s\n", 50862306a36Sopenharmony_ci fb->info.active ? "Yes" : "No"); 50962306a36Sopenharmony_ci snd_iprintf(buffer, " Groups: %d-%d\n", 51062306a36Sopenharmony_ci fb->info.first_group + 1, 51162306a36Sopenharmony_ci fb->info.first_group + fb->info.num_groups); 51262306a36Sopenharmony_ci snd_iprintf(buffer, " Is MIDI1: %s%s\n", 51362306a36Sopenharmony_ci (fb->info.flags & SNDRV_UMP_BLOCK_IS_MIDI1) ? "Yes" : "No", 51462306a36Sopenharmony_ci (fb->info.flags & SNDRV_UMP_BLOCK_IS_LOWSPEED) ? " (Low Speed)" : ""); 51562306a36Sopenharmony_ci if (ump->info.version) { 51662306a36Sopenharmony_ci snd_iprintf(buffer, " MIDI-CI Version: %d\n", 51762306a36Sopenharmony_ci fb->info.midi_ci_version); 51862306a36Sopenharmony_ci snd_iprintf(buffer, " Sysex8 Streams: %d\n", 51962306a36Sopenharmony_ci fb->info.sysex8_streams); 52062306a36Sopenharmony_ci snd_iprintf(buffer, " UI Hint: %s\n", 52162306a36Sopenharmony_ci ump_ui_hint_string(fb->info.ui_hint)); 52262306a36Sopenharmony_ci } 52362306a36Sopenharmony_ci snd_iprintf(buffer, "\n"); 52462306a36Sopenharmony_ci } 52562306a36Sopenharmony_ci} 52662306a36Sopenharmony_ci 52762306a36Sopenharmony_ci/* 52862306a36Sopenharmony_ci * UMP endpoint and function block handling 52962306a36Sopenharmony_ci */ 53062306a36Sopenharmony_ci 53162306a36Sopenharmony_ci/* open / close UMP streams for the internal stream msg communication */ 53262306a36Sopenharmony_cistatic int ump_request_open(struct snd_ump_endpoint *ump) 53362306a36Sopenharmony_ci{ 53462306a36Sopenharmony_ci return snd_rawmidi_kernel_open(&ump->core, 0, 53562306a36Sopenharmony_ci SNDRV_RAWMIDI_LFLG_OUTPUT, 53662306a36Sopenharmony_ci &ump->stream_rfile); 53762306a36Sopenharmony_ci} 53862306a36Sopenharmony_ci 53962306a36Sopenharmony_cistatic void ump_request_close(struct snd_ump_endpoint *ump) 54062306a36Sopenharmony_ci{ 54162306a36Sopenharmony_ci snd_rawmidi_kernel_release(&ump->stream_rfile); 54262306a36Sopenharmony_ci} 54362306a36Sopenharmony_ci 54462306a36Sopenharmony_ci/* request a command and wait for the given response; 54562306a36Sopenharmony_ci * @req1 and @req2 are u32 commands 54662306a36Sopenharmony_ci * @reply is the expected UMP stream status 54762306a36Sopenharmony_ci */ 54862306a36Sopenharmony_cistatic int ump_req_msg(struct snd_ump_endpoint *ump, u32 req1, u32 req2, 54962306a36Sopenharmony_ci u32 reply) 55062306a36Sopenharmony_ci{ 55162306a36Sopenharmony_ci u32 buf[4]; 55262306a36Sopenharmony_ci 55362306a36Sopenharmony_ci ump_dbg(ump, "%s: request %08x %08x, wait-for %08x\n", 55462306a36Sopenharmony_ci __func__, req1, req2, reply); 55562306a36Sopenharmony_ci memset(buf, 0, sizeof(buf)); 55662306a36Sopenharmony_ci buf[0] = req1; 55762306a36Sopenharmony_ci buf[1] = req2; 55862306a36Sopenharmony_ci ump->stream_finished = 0; 55962306a36Sopenharmony_ci ump->stream_wait_for = reply; 56062306a36Sopenharmony_ci snd_rawmidi_kernel_write(ump->stream_rfile.output, 56162306a36Sopenharmony_ci (unsigned char *)&buf, 16); 56262306a36Sopenharmony_ci wait_event_timeout(ump->stream_wait, ump->stream_finished, 56362306a36Sopenharmony_ci msecs_to_jiffies(500)); 56462306a36Sopenharmony_ci if (!READ_ONCE(ump->stream_finished)) { 56562306a36Sopenharmony_ci ump_dbg(ump, "%s: request timed out\n", __func__); 56662306a36Sopenharmony_ci return -ETIMEDOUT; 56762306a36Sopenharmony_ci } 56862306a36Sopenharmony_ci ump->stream_finished = 0; 56962306a36Sopenharmony_ci ump_dbg(ump, "%s: reply: %08x %08x %08x %08x\n", 57062306a36Sopenharmony_ci __func__, buf[0], buf[1], buf[2], buf[3]); 57162306a36Sopenharmony_ci return 0; 57262306a36Sopenharmony_ci} 57362306a36Sopenharmony_ci 57462306a36Sopenharmony_ci/* append the received letters via UMP packet to the given string buffer; 57562306a36Sopenharmony_ci * return 1 if the full string is received or 0 to continue 57662306a36Sopenharmony_ci */ 57762306a36Sopenharmony_cistatic int ump_append_string(struct snd_ump_endpoint *ump, char *dest, 57862306a36Sopenharmony_ci int maxsize, const u32 *buf, int offset) 57962306a36Sopenharmony_ci{ 58062306a36Sopenharmony_ci unsigned char format; 58162306a36Sopenharmony_ci int c; 58262306a36Sopenharmony_ci 58362306a36Sopenharmony_ci format = ump_stream_message_format(buf[0]); 58462306a36Sopenharmony_ci if (format == UMP_STREAM_MSG_FORMAT_SINGLE || 58562306a36Sopenharmony_ci format == UMP_STREAM_MSG_FORMAT_START) { 58662306a36Sopenharmony_ci c = 0; 58762306a36Sopenharmony_ci } else { 58862306a36Sopenharmony_ci c = strlen(dest); 58962306a36Sopenharmony_ci if (c >= maxsize - 1) 59062306a36Sopenharmony_ci return 1; 59162306a36Sopenharmony_ci } 59262306a36Sopenharmony_ci 59362306a36Sopenharmony_ci for (; offset < 16; offset++) { 59462306a36Sopenharmony_ci dest[c] = buf[offset / 4] >> (3 - (offset % 4)) * 8; 59562306a36Sopenharmony_ci if (!dest[c]) 59662306a36Sopenharmony_ci break; 59762306a36Sopenharmony_ci if (++c >= maxsize - 1) 59862306a36Sopenharmony_ci break; 59962306a36Sopenharmony_ci } 60062306a36Sopenharmony_ci dest[c] = 0; 60162306a36Sopenharmony_ci return (format == UMP_STREAM_MSG_FORMAT_SINGLE || 60262306a36Sopenharmony_ci format == UMP_STREAM_MSG_FORMAT_END); 60362306a36Sopenharmony_ci} 60462306a36Sopenharmony_ci 60562306a36Sopenharmony_ci/* handle EP info stream message; update the UMP attributes */ 60662306a36Sopenharmony_cistatic int ump_handle_ep_info_msg(struct snd_ump_endpoint *ump, 60762306a36Sopenharmony_ci const union snd_ump_stream_msg *buf) 60862306a36Sopenharmony_ci{ 60962306a36Sopenharmony_ci ump->info.version = (buf->ep_info.ump_version_major << 8) | 61062306a36Sopenharmony_ci buf->ep_info.ump_version_minor; 61162306a36Sopenharmony_ci ump->info.num_blocks = buf->ep_info.num_function_blocks; 61262306a36Sopenharmony_ci if (ump->info.num_blocks > SNDRV_UMP_MAX_BLOCKS) { 61362306a36Sopenharmony_ci ump_info(ump, "Invalid function blocks %d, fallback to 1\n", 61462306a36Sopenharmony_ci ump->info.num_blocks); 61562306a36Sopenharmony_ci ump->info.num_blocks = 1; 61662306a36Sopenharmony_ci } 61762306a36Sopenharmony_ci 61862306a36Sopenharmony_ci if (buf->ep_info.static_function_block) 61962306a36Sopenharmony_ci ump->info.flags |= SNDRV_UMP_EP_INFO_STATIC_BLOCKS; 62062306a36Sopenharmony_ci 62162306a36Sopenharmony_ci ump->info.protocol_caps = (buf->ep_info.protocol << 8) | 62262306a36Sopenharmony_ci buf->ep_info.jrts; 62362306a36Sopenharmony_ci 62462306a36Sopenharmony_ci ump_dbg(ump, "EP info: version=%x, num_blocks=%x, proto_caps=%x\n", 62562306a36Sopenharmony_ci ump->info.version, ump->info.num_blocks, ump->info.protocol_caps); 62662306a36Sopenharmony_ci return 1; /* finished */ 62762306a36Sopenharmony_ci} 62862306a36Sopenharmony_ci 62962306a36Sopenharmony_ci/* handle EP device info stream message; update the UMP attributes */ 63062306a36Sopenharmony_cistatic int ump_handle_device_info_msg(struct snd_ump_endpoint *ump, 63162306a36Sopenharmony_ci const union snd_ump_stream_msg *buf) 63262306a36Sopenharmony_ci{ 63362306a36Sopenharmony_ci ump->info.manufacturer_id = buf->device_info.manufacture_id & 0x7f7f7f; 63462306a36Sopenharmony_ci ump->info.family_id = (buf->device_info.family_msb << 8) | 63562306a36Sopenharmony_ci buf->device_info.family_lsb; 63662306a36Sopenharmony_ci ump->info.model_id = (buf->device_info.model_msb << 8) | 63762306a36Sopenharmony_ci buf->device_info.model_lsb; 63862306a36Sopenharmony_ci ump->info.sw_revision[0] = (buf->device_info.sw_revision >> 24) & 0x7f; 63962306a36Sopenharmony_ci ump->info.sw_revision[1] = (buf->device_info.sw_revision >> 16) & 0x7f; 64062306a36Sopenharmony_ci ump->info.sw_revision[2] = (buf->device_info.sw_revision >> 8) & 0x7f; 64162306a36Sopenharmony_ci ump->info.sw_revision[3] = buf->device_info.sw_revision & 0x7f; 64262306a36Sopenharmony_ci ump_dbg(ump, "EP devinfo: manid=%08x, family=%04x, model=%04x, sw=%02x%02x%02x%02x\n", 64362306a36Sopenharmony_ci ump->info.manufacturer_id, 64462306a36Sopenharmony_ci ump->info.family_id, 64562306a36Sopenharmony_ci ump->info.model_id, 64662306a36Sopenharmony_ci ump->info.sw_revision[0], 64762306a36Sopenharmony_ci ump->info.sw_revision[1], 64862306a36Sopenharmony_ci ump->info.sw_revision[2], 64962306a36Sopenharmony_ci ump->info.sw_revision[3]); 65062306a36Sopenharmony_ci return 1; /* finished */ 65162306a36Sopenharmony_ci} 65262306a36Sopenharmony_ci 65362306a36Sopenharmony_ci/* handle EP name stream message; update the UMP name string */ 65462306a36Sopenharmony_cistatic int ump_handle_ep_name_msg(struct snd_ump_endpoint *ump, 65562306a36Sopenharmony_ci const union snd_ump_stream_msg *buf) 65662306a36Sopenharmony_ci{ 65762306a36Sopenharmony_ci return ump_append_string(ump, ump->info.name, sizeof(ump->info.name), 65862306a36Sopenharmony_ci buf->raw, 2); 65962306a36Sopenharmony_ci} 66062306a36Sopenharmony_ci 66162306a36Sopenharmony_ci/* handle EP product id stream message; update the UMP product_id string */ 66262306a36Sopenharmony_cistatic int ump_handle_product_id_msg(struct snd_ump_endpoint *ump, 66362306a36Sopenharmony_ci const union snd_ump_stream_msg *buf) 66462306a36Sopenharmony_ci{ 66562306a36Sopenharmony_ci return ump_append_string(ump, ump->info.product_id, 66662306a36Sopenharmony_ci sizeof(ump->info.product_id), 66762306a36Sopenharmony_ci buf->raw, 2); 66862306a36Sopenharmony_ci} 66962306a36Sopenharmony_ci 67062306a36Sopenharmony_ci/* notify the protocol change to sequencer */ 67162306a36Sopenharmony_cistatic void seq_notify_protocol(struct snd_ump_endpoint *ump) 67262306a36Sopenharmony_ci{ 67362306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_SND_SEQUENCER) 67462306a36Sopenharmony_ci if (ump->seq_ops && ump->seq_ops->switch_protocol) 67562306a36Sopenharmony_ci ump->seq_ops->switch_protocol(ump); 67662306a36Sopenharmony_ci#endif /* CONFIG_SND_SEQUENCER */ 67762306a36Sopenharmony_ci} 67862306a36Sopenharmony_ci 67962306a36Sopenharmony_ci/** 68062306a36Sopenharmony_ci * snd_ump_switch_protocol - switch MIDI protocol 68162306a36Sopenharmony_ci * @ump: UMP endpoint 68262306a36Sopenharmony_ci * @protocol: protocol to switch to 68362306a36Sopenharmony_ci * 68462306a36Sopenharmony_ci * Returns 1 if the protocol is actually switched, 0 if unchanged 68562306a36Sopenharmony_ci */ 68662306a36Sopenharmony_ciint snd_ump_switch_protocol(struct snd_ump_endpoint *ump, unsigned int protocol) 68762306a36Sopenharmony_ci{ 68862306a36Sopenharmony_ci protocol &= ump->info.protocol_caps; 68962306a36Sopenharmony_ci if (protocol == ump->info.protocol) 69062306a36Sopenharmony_ci return 0; 69162306a36Sopenharmony_ci 69262306a36Sopenharmony_ci ump->info.protocol = protocol; 69362306a36Sopenharmony_ci ump_dbg(ump, "New protocol = %x (caps = %x)\n", 69462306a36Sopenharmony_ci protocol, ump->info.protocol_caps); 69562306a36Sopenharmony_ci seq_notify_protocol(ump); 69662306a36Sopenharmony_ci return 1; 69762306a36Sopenharmony_ci} 69862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_ump_switch_protocol); 69962306a36Sopenharmony_ci 70062306a36Sopenharmony_ci/* handle EP stream config message; update the UMP protocol */ 70162306a36Sopenharmony_cistatic int ump_handle_stream_cfg_msg(struct snd_ump_endpoint *ump, 70262306a36Sopenharmony_ci const union snd_ump_stream_msg *buf) 70362306a36Sopenharmony_ci{ 70462306a36Sopenharmony_ci unsigned int protocol = 70562306a36Sopenharmony_ci (buf->stream_cfg.protocol << 8) | buf->stream_cfg.jrts; 70662306a36Sopenharmony_ci 70762306a36Sopenharmony_ci snd_ump_switch_protocol(ump, protocol); 70862306a36Sopenharmony_ci return 1; /* finished */ 70962306a36Sopenharmony_ci} 71062306a36Sopenharmony_ci 71162306a36Sopenharmony_ci/* Extract Function Block info from UMP packet */ 71262306a36Sopenharmony_cistatic void fill_fb_info(struct snd_ump_endpoint *ump, 71362306a36Sopenharmony_ci struct snd_ump_block_info *info, 71462306a36Sopenharmony_ci const union snd_ump_stream_msg *buf) 71562306a36Sopenharmony_ci{ 71662306a36Sopenharmony_ci info->direction = buf->fb_info.direction; 71762306a36Sopenharmony_ci info->ui_hint = buf->fb_info.ui_hint; 71862306a36Sopenharmony_ci info->first_group = buf->fb_info.first_group; 71962306a36Sopenharmony_ci info->num_groups = buf->fb_info.num_groups; 72062306a36Sopenharmony_ci info->flags = buf->fb_info.midi_10; 72162306a36Sopenharmony_ci info->active = buf->fb_info.active; 72262306a36Sopenharmony_ci info->midi_ci_version = buf->fb_info.midi_ci_version; 72362306a36Sopenharmony_ci info->sysex8_streams = buf->fb_info.sysex8_streams; 72462306a36Sopenharmony_ci 72562306a36Sopenharmony_ci ump_dbg(ump, "FB %d: dir=%d, active=%d, first_gp=%d, num_gp=%d, midici=%d, sysex8=%d, flags=0x%x\n", 72662306a36Sopenharmony_ci info->block_id, info->direction, info->active, 72762306a36Sopenharmony_ci info->first_group, info->num_groups, info->midi_ci_version, 72862306a36Sopenharmony_ci info->sysex8_streams, info->flags); 72962306a36Sopenharmony_ci} 73062306a36Sopenharmony_ci 73162306a36Sopenharmony_ci/* check whether the FB info gets updated by the current message */ 73262306a36Sopenharmony_cistatic bool is_fb_info_updated(struct snd_ump_endpoint *ump, 73362306a36Sopenharmony_ci struct snd_ump_block *fb, 73462306a36Sopenharmony_ci const union snd_ump_stream_msg *buf) 73562306a36Sopenharmony_ci{ 73662306a36Sopenharmony_ci char tmpbuf[offsetof(struct snd_ump_block_info, name)]; 73762306a36Sopenharmony_ci 73862306a36Sopenharmony_ci if (ump->info.flags & SNDRV_UMP_EP_INFO_STATIC_BLOCKS) { 73962306a36Sopenharmony_ci ump_info(ump, "Skipping static FB info update (blk#%d)\n", 74062306a36Sopenharmony_ci fb->info.block_id); 74162306a36Sopenharmony_ci return 0; 74262306a36Sopenharmony_ci } 74362306a36Sopenharmony_ci 74462306a36Sopenharmony_ci memcpy(tmpbuf, &fb->info, sizeof(tmpbuf)); 74562306a36Sopenharmony_ci fill_fb_info(ump, (struct snd_ump_block_info *)tmpbuf, buf); 74662306a36Sopenharmony_ci return memcmp(&fb->info, tmpbuf, sizeof(tmpbuf)) != 0; 74762306a36Sopenharmony_ci} 74862306a36Sopenharmony_ci 74962306a36Sopenharmony_ci/* notify the FB info/name change to sequencer */ 75062306a36Sopenharmony_cistatic void seq_notify_fb_change(struct snd_ump_endpoint *ump, 75162306a36Sopenharmony_ci struct snd_ump_block *fb) 75262306a36Sopenharmony_ci{ 75362306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_SND_SEQUENCER) 75462306a36Sopenharmony_ci if (ump->seq_ops && ump->seq_ops->notify_fb_change) 75562306a36Sopenharmony_ci ump->seq_ops->notify_fb_change(ump, fb); 75662306a36Sopenharmony_ci#endif 75762306a36Sopenharmony_ci} 75862306a36Sopenharmony_ci 75962306a36Sopenharmony_ci/* handle FB info message; update FB info if the block is present */ 76062306a36Sopenharmony_cistatic int ump_handle_fb_info_msg(struct snd_ump_endpoint *ump, 76162306a36Sopenharmony_ci const union snd_ump_stream_msg *buf) 76262306a36Sopenharmony_ci{ 76362306a36Sopenharmony_ci unsigned char blk; 76462306a36Sopenharmony_ci struct snd_ump_block *fb; 76562306a36Sopenharmony_ci 76662306a36Sopenharmony_ci blk = buf->fb_info.function_block_id; 76762306a36Sopenharmony_ci fb = snd_ump_get_block(ump, blk); 76862306a36Sopenharmony_ci 76962306a36Sopenharmony_ci /* complain only if updated after parsing */ 77062306a36Sopenharmony_ci if (!fb && ump->parsed) { 77162306a36Sopenharmony_ci ump_info(ump, "Function Block Info Update for non-existing block %d\n", 77262306a36Sopenharmony_ci blk); 77362306a36Sopenharmony_ci return -ENODEV; 77462306a36Sopenharmony_ci } 77562306a36Sopenharmony_ci 77662306a36Sopenharmony_ci /* When updated after the initial parse, check the FB info update */ 77762306a36Sopenharmony_ci if (ump->parsed && !is_fb_info_updated(ump, fb, buf)) 77862306a36Sopenharmony_ci return 1; /* no content change */ 77962306a36Sopenharmony_ci 78062306a36Sopenharmony_ci if (fb) { 78162306a36Sopenharmony_ci fill_fb_info(ump, &fb->info, buf); 78262306a36Sopenharmony_ci if (ump->parsed) 78362306a36Sopenharmony_ci seq_notify_fb_change(ump, fb); 78462306a36Sopenharmony_ci } 78562306a36Sopenharmony_ci 78662306a36Sopenharmony_ci return 1; /* finished */ 78762306a36Sopenharmony_ci} 78862306a36Sopenharmony_ci 78962306a36Sopenharmony_ci/* handle FB name message; update the FB name string */ 79062306a36Sopenharmony_cistatic int ump_handle_fb_name_msg(struct snd_ump_endpoint *ump, 79162306a36Sopenharmony_ci const union snd_ump_stream_msg *buf) 79262306a36Sopenharmony_ci{ 79362306a36Sopenharmony_ci unsigned char blk; 79462306a36Sopenharmony_ci struct snd_ump_block *fb; 79562306a36Sopenharmony_ci int ret; 79662306a36Sopenharmony_ci 79762306a36Sopenharmony_ci blk = buf->fb_name.function_block_id; 79862306a36Sopenharmony_ci fb = snd_ump_get_block(ump, blk); 79962306a36Sopenharmony_ci if (!fb) 80062306a36Sopenharmony_ci return -ENODEV; 80162306a36Sopenharmony_ci 80262306a36Sopenharmony_ci ret = ump_append_string(ump, fb->info.name, sizeof(fb->info.name), 80362306a36Sopenharmony_ci buf->raw, 3); 80462306a36Sopenharmony_ci /* notify the FB name update to sequencer, too */ 80562306a36Sopenharmony_ci if (ret > 0 && ump->parsed) 80662306a36Sopenharmony_ci seq_notify_fb_change(ump, fb); 80762306a36Sopenharmony_ci return ret; 80862306a36Sopenharmony_ci} 80962306a36Sopenharmony_ci 81062306a36Sopenharmony_cistatic int create_block_from_fb_info(struct snd_ump_endpoint *ump, int blk) 81162306a36Sopenharmony_ci{ 81262306a36Sopenharmony_ci struct snd_ump_block *fb; 81362306a36Sopenharmony_ci unsigned char direction, first_group, num_groups; 81462306a36Sopenharmony_ci const union snd_ump_stream_msg *buf = 81562306a36Sopenharmony_ci (const union snd_ump_stream_msg *)ump->input_buf; 81662306a36Sopenharmony_ci u32 msg; 81762306a36Sopenharmony_ci int err; 81862306a36Sopenharmony_ci 81962306a36Sopenharmony_ci /* query the FB info once */ 82062306a36Sopenharmony_ci msg = ump_stream_compose(UMP_STREAM_MSG_STATUS_FB_DISCOVERY, 0) | 82162306a36Sopenharmony_ci (blk << 8) | UMP_STREAM_MSG_REQUEST_FB_INFO; 82262306a36Sopenharmony_ci err = ump_req_msg(ump, msg, 0, UMP_STREAM_MSG_STATUS_FB_INFO); 82362306a36Sopenharmony_ci if (err < 0) { 82462306a36Sopenharmony_ci ump_dbg(ump, "Unable to get FB info for block %d\n", blk); 82562306a36Sopenharmony_ci return err; 82662306a36Sopenharmony_ci } 82762306a36Sopenharmony_ci 82862306a36Sopenharmony_ci /* the last input must be the FB info */ 82962306a36Sopenharmony_ci if (buf->fb_info.status != UMP_STREAM_MSG_STATUS_FB_INFO) { 83062306a36Sopenharmony_ci ump_dbg(ump, "Inconsistent input: 0x%x\n", *buf->raw); 83162306a36Sopenharmony_ci return -EINVAL; 83262306a36Sopenharmony_ci } 83362306a36Sopenharmony_ci 83462306a36Sopenharmony_ci direction = buf->fb_info.direction; 83562306a36Sopenharmony_ci first_group = buf->fb_info.first_group; 83662306a36Sopenharmony_ci num_groups = buf->fb_info.num_groups; 83762306a36Sopenharmony_ci 83862306a36Sopenharmony_ci err = snd_ump_block_new(ump, blk, direction, first_group, num_groups, 83962306a36Sopenharmony_ci &fb); 84062306a36Sopenharmony_ci if (err < 0) 84162306a36Sopenharmony_ci return err; 84262306a36Sopenharmony_ci 84362306a36Sopenharmony_ci fill_fb_info(ump, &fb->info, buf); 84462306a36Sopenharmony_ci 84562306a36Sopenharmony_ci msg = ump_stream_compose(UMP_STREAM_MSG_STATUS_FB_DISCOVERY, 0) | 84662306a36Sopenharmony_ci (blk << 8) | UMP_STREAM_MSG_REQUEST_FB_NAME; 84762306a36Sopenharmony_ci err = ump_req_msg(ump, msg, 0, UMP_STREAM_MSG_STATUS_FB_NAME); 84862306a36Sopenharmony_ci if (err) 84962306a36Sopenharmony_ci ump_dbg(ump, "Unable to get UMP FB name string #%d\n", blk); 85062306a36Sopenharmony_ci 85162306a36Sopenharmony_ci return 0; 85262306a36Sopenharmony_ci} 85362306a36Sopenharmony_ci 85462306a36Sopenharmony_ci/* handle stream messages, called from snd_ump_receive() */ 85562306a36Sopenharmony_cistatic void ump_handle_stream_msg(struct snd_ump_endpoint *ump, 85662306a36Sopenharmony_ci const u32 *buf, int size) 85762306a36Sopenharmony_ci{ 85862306a36Sopenharmony_ci const union snd_ump_stream_msg *msg; 85962306a36Sopenharmony_ci unsigned int status; 86062306a36Sopenharmony_ci int ret; 86162306a36Sopenharmony_ci 86262306a36Sopenharmony_ci /* UMP stream message suppressed (for gadget UMP)? */ 86362306a36Sopenharmony_ci if (ump->no_process_stream) 86462306a36Sopenharmony_ci return; 86562306a36Sopenharmony_ci 86662306a36Sopenharmony_ci BUILD_BUG_ON(sizeof(*msg) != 16); 86762306a36Sopenharmony_ci ump_dbg(ump, "Stream msg: %08x %08x %08x %08x\n", 86862306a36Sopenharmony_ci buf[0], buf[1], buf[2], buf[3]); 86962306a36Sopenharmony_ci 87062306a36Sopenharmony_ci if (size != 4 || ump_message_type(*buf) != UMP_MSG_TYPE_STREAM) 87162306a36Sopenharmony_ci return; 87262306a36Sopenharmony_ci 87362306a36Sopenharmony_ci msg = (const union snd_ump_stream_msg *)buf; 87462306a36Sopenharmony_ci status = ump_stream_message_status(*buf); 87562306a36Sopenharmony_ci switch (status) { 87662306a36Sopenharmony_ci case UMP_STREAM_MSG_STATUS_EP_INFO: 87762306a36Sopenharmony_ci ret = ump_handle_ep_info_msg(ump, msg); 87862306a36Sopenharmony_ci break; 87962306a36Sopenharmony_ci case UMP_STREAM_MSG_STATUS_DEVICE_INFO: 88062306a36Sopenharmony_ci ret = ump_handle_device_info_msg(ump, msg); 88162306a36Sopenharmony_ci break; 88262306a36Sopenharmony_ci case UMP_STREAM_MSG_STATUS_EP_NAME: 88362306a36Sopenharmony_ci ret = ump_handle_ep_name_msg(ump, msg); 88462306a36Sopenharmony_ci break; 88562306a36Sopenharmony_ci case UMP_STREAM_MSG_STATUS_PRODUCT_ID: 88662306a36Sopenharmony_ci ret = ump_handle_product_id_msg(ump, msg); 88762306a36Sopenharmony_ci break; 88862306a36Sopenharmony_ci case UMP_STREAM_MSG_STATUS_STREAM_CFG: 88962306a36Sopenharmony_ci ret = ump_handle_stream_cfg_msg(ump, msg); 89062306a36Sopenharmony_ci break; 89162306a36Sopenharmony_ci case UMP_STREAM_MSG_STATUS_FB_INFO: 89262306a36Sopenharmony_ci ret = ump_handle_fb_info_msg(ump, msg); 89362306a36Sopenharmony_ci break; 89462306a36Sopenharmony_ci case UMP_STREAM_MSG_STATUS_FB_NAME: 89562306a36Sopenharmony_ci ret = ump_handle_fb_name_msg(ump, msg); 89662306a36Sopenharmony_ci break; 89762306a36Sopenharmony_ci default: 89862306a36Sopenharmony_ci return; 89962306a36Sopenharmony_ci } 90062306a36Sopenharmony_ci 90162306a36Sopenharmony_ci /* when the message has been processed fully, wake up */ 90262306a36Sopenharmony_ci if (ret > 0 && ump->stream_wait_for == status) { 90362306a36Sopenharmony_ci WRITE_ONCE(ump->stream_finished, 1); 90462306a36Sopenharmony_ci wake_up(&ump->stream_wait); 90562306a36Sopenharmony_ci } 90662306a36Sopenharmony_ci} 90762306a36Sopenharmony_ci 90862306a36Sopenharmony_ci/** 90962306a36Sopenharmony_ci * snd_ump_parse_endpoint - parse endpoint and create function blocks 91062306a36Sopenharmony_ci * @ump: UMP object 91162306a36Sopenharmony_ci * 91262306a36Sopenharmony_ci * Returns 0 for successful parse, -ENODEV if device doesn't respond 91362306a36Sopenharmony_ci * (or the query is unsupported), or other error code for serious errors. 91462306a36Sopenharmony_ci */ 91562306a36Sopenharmony_ciint snd_ump_parse_endpoint(struct snd_ump_endpoint *ump) 91662306a36Sopenharmony_ci{ 91762306a36Sopenharmony_ci int blk, err; 91862306a36Sopenharmony_ci u32 msg; 91962306a36Sopenharmony_ci 92062306a36Sopenharmony_ci if (!(ump->core.info_flags & SNDRV_RAWMIDI_INFO_DUPLEX)) 92162306a36Sopenharmony_ci return -ENODEV; 92262306a36Sopenharmony_ci 92362306a36Sopenharmony_ci err = ump_request_open(ump); 92462306a36Sopenharmony_ci if (err < 0) { 92562306a36Sopenharmony_ci ump_dbg(ump, "Unable to open rawmidi device: %d\n", err); 92662306a36Sopenharmony_ci return err; 92762306a36Sopenharmony_ci } 92862306a36Sopenharmony_ci 92962306a36Sopenharmony_ci /* Check Endpoint Information */ 93062306a36Sopenharmony_ci msg = ump_stream_compose(UMP_STREAM_MSG_STATUS_EP_DISCOVERY, 0) | 93162306a36Sopenharmony_ci 0x0101; /* UMP version 1.1 */ 93262306a36Sopenharmony_ci err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_EP_INFO, 93362306a36Sopenharmony_ci UMP_STREAM_MSG_STATUS_EP_INFO); 93462306a36Sopenharmony_ci if (err < 0) { 93562306a36Sopenharmony_ci ump_dbg(ump, "Unable to get UMP EP info\n"); 93662306a36Sopenharmony_ci goto error; 93762306a36Sopenharmony_ci } 93862306a36Sopenharmony_ci 93962306a36Sopenharmony_ci /* Request Endpoint Device Info */ 94062306a36Sopenharmony_ci err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_DEVICE_INFO, 94162306a36Sopenharmony_ci UMP_STREAM_MSG_STATUS_DEVICE_INFO); 94262306a36Sopenharmony_ci if (err < 0) 94362306a36Sopenharmony_ci ump_dbg(ump, "Unable to get UMP EP device info\n"); 94462306a36Sopenharmony_ci 94562306a36Sopenharmony_ci /* Request Endpoint Name */ 94662306a36Sopenharmony_ci err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_EP_NAME, 94762306a36Sopenharmony_ci UMP_STREAM_MSG_STATUS_EP_NAME); 94862306a36Sopenharmony_ci if (err < 0) 94962306a36Sopenharmony_ci ump_dbg(ump, "Unable to get UMP EP name string\n"); 95062306a36Sopenharmony_ci 95162306a36Sopenharmony_ci /* Request Endpoint Product ID */ 95262306a36Sopenharmony_ci err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_PRODUCT_ID, 95362306a36Sopenharmony_ci UMP_STREAM_MSG_STATUS_PRODUCT_ID); 95462306a36Sopenharmony_ci if (err < 0) 95562306a36Sopenharmony_ci ump_dbg(ump, "Unable to get UMP EP product ID string\n"); 95662306a36Sopenharmony_ci 95762306a36Sopenharmony_ci /* Get the current stream configuration */ 95862306a36Sopenharmony_ci err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_STREAM_CFG, 95962306a36Sopenharmony_ci UMP_STREAM_MSG_STATUS_STREAM_CFG); 96062306a36Sopenharmony_ci if (err < 0) 96162306a36Sopenharmony_ci ump_dbg(ump, "Unable to get UMP EP stream config\n"); 96262306a36Sopenharmony_ci 96362306a36Sopenharmony_ci /* Query and create blocks from Function Blocks */ 96462306a36Sopenharmony_ci for (blk = 0; blk < ump->info.num_blocks; blk++) { 96562306a36Sopenharmony_ci err = create_block_from_fb_info(ump, blk); 96662306a36Sopenharmony_ci if (err < 0) 96762306a36Sopenharmony_ci continue; 96862306a36Sopenharmony_ci } 96962306a36Sopenharmony_ci 97062306a36Sopenharmony_ci error: 97162306a36Sopenharmony_ci ump->parsed = true; 97262306a36Sopenharmony_ci ump_request_close(ump); 97362306a36Sopenharmony_ci if (err == -ETIMEDOUT) 97462306a36Sopenharmony_ci err = -ENODEV; 97562306a36Sopenharmony_ci return err; 97662306a36Sopenharmony_ci} 97762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_ump_parse_endpoint); 97862306a36Sopenharmony_ci 97962306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) 98062306a36Sopenharmony_ci/* 98162306a36Sopenharmony_ci * Legacy rawmidi support 98262306a36Sopenharmony_ci */ 98362306a36Sopenharmony_cistatic int snd_ump_legacy_open(struct snd_rawmidi_substream *substream) 98462306a36Sopenharmony_ci{ 98562306a36Sopenharmony_ci struct snd_ump_endpoint *ump = substream->rmidi->private_data; 98662306a36Sopenharmony_ci int dir = substream->stream; 98762306a36Sopenharmony_ci int group = ump->legacy_mapping[substream->number]; 98862306a36Sopenharmony_ci int err = 0; 98962306a36Sopenharmony_ci 99062306a36Sopenharmony_ci mutex_lock(&ump->open_mutex); 99162306a36Sopenharmony_ci if (ump->legacy_substreams[dir][group]) { 99262306a36Sopenharmony_ci err = -EBUSY; 99362306a36Sopenharmony_ci goto unlock; 99462306a36Sopenharmony_ci } 99562306a36Sopenharmony_ci if (dir == SNDRV_RAWMIDI_STREAM_OUTPUT) { 99662306a36Sopenharmony_ci if (!ump->legacy_out_opens) { 99762306a36Sopenharmony_ci err = snd_rawmidi_kernel_open(&ump->core, 0, 99862306a36Sopenharmony_ci SNDRV_RAWMIDI_LFLG_OUTPUT | 99962306a36Sopenharmony_ci SNDRV_RAWMIDI_LFLG_APPEND, 100062306a36Sopenharmony_ci &ump->legacy_out_rfile); 100162306a36Sopenharmony_ci if (err < 0) 100262306a36Sopenharmony_ci goto unlock; 100362306a36Sopenharmony_ci } 100462306a36Sopenharmony_ci ump->legacy_out_opens++; 100562306a36Sopenharmony_ci snd_ump_convert_reset(&ump->out_cvts[group]); 100662306a36Sopenharmony_ci } 100762306a36Sopenharmony_ci spin_lock_irq(&ump->legacy_locks[dir]); 100862306a36Sopenharmony_ci ump->legacy_substreams[dir][group] = substream; 100962306a36Sopenharmony_ci spin_unlock_irq(&ump->legacy_locks[dir]); 101062306a36Sopenharmony_ci unlock: 101162306a36Sopenharmony_ci mutex_unlock(&ump->open_mutex); 101262306a36Sopenharmony_ci return err; 101362306a36Sopenharmony_ci} 101462306a36Sopenharmony_ci 101562306a36Sopenharmony_cistatic int snd_ump_legacy_close(struct snd_rawmidi_substream *substream) 101662306a36Sopenharmony_ci{ 101762306a36Sopenharmony_ci struct snd_ump_endpoint *ump = substream->rmidi->private_data; 101862306a36Sopenharmony_ci int dir = substream->stream; 101962306a36Sopenharmony_ci int group = ump->legacy_mapping[substream->number]; 102062306a36Sopenharmony_ci 102162306a36Sopenharmony_ci mutex_lock(&ump->open_mutex); 102262306a36Sopenharmony_ci spin_lock_irq(&ump->legacy_locks[dir]); 102362306a36Sopenharmony_ci ump->legacy_substreams[dir][group] = NULL; 102462306a36Sopenharmony_ci spin_unlock_irq(&ump->legacy_locks[dir]); 102562306a36Sopenharmony_ci if (dir == SNDRV_RAWMIDI_STREAM_OUTPUT) { 102662306a36Sopenharmony_ci if (!--ump->legacy_out_opens) 102762306a36Sopenharmony_ci snd_rawmidi_kernel_release(&ump->legacy_out_rfile); 102862306a36Sopenharmony_ci } 102962306a36Sopenharmony_ci mutex_unlock(&ump->open_mutex); 103062306a36Sopenharmony_ci return 0; 103162306a36Sopenharmony_ci} 103262306a36Sopenharmony_ci 103362306a36Sopenharmony_cistatic void snd_ump_legacy_trigger(struct snd_rawmidi_substream *substream, 103462306a36Sopenharmony_ci int up) 103562306a36Sopenharmony_ci{ 103662306a36Sopenharmony_ci struct snd_ump_endpoint *ump = substream->rmidi->private_data; 103762306a36Sopenharmony_ci int dir = substream->stream; 103862306a36Sopenharmony_ci 103962306a36Sopenharmony_ci ump->ops->trigger(ump, dir, up); 104062306a36Sopenharmony_ci} 104162306a36Sopenharmony_ci 104262306a36Sopenharmony_cistatic void snd_ump_legacy_drain(struct snd_rawmidi_substream *substream) 104362306a36Sopenharmony_ci{ 104462306a36Sopenharmony_ci struct snd_ump_endpoint *ump = substream->rmidi->private_data; 104562306a36Sopenharmony_ci 104662306a36Sopenharmony_ci if (ump->ops->drain) 104762306a36Sopenharmony_ci ump->ops->drain(ump, SNDRV_RAWMIDI_STREAM_OUTPUT); 104862306a36Sopenharmony_ci} 104962306a36Sopenharmony_ci 105062306a36Sopenharmony_cistatic int snd_ump_legacy_dev_register(struct snd_rawmidi *rmidi) 105162306a36Sopenharmony_ci{ 105262306a36Sopenharmony_ci /* dummy, just for avoiding create superfluous seq clients */ 105362306a36Sopenharmony_ci return 0; 105462306a36Sopenharmony_ci} 105562306a36Sopenharmony_ci 105662306a36Sopenharmony_cistatic const struct snd_rawmidi_ops snd_ump_legacy_input_ops = { 105762306a36Sopenharmony_ci .open = snd_ump_legacy_open, 105862306a36Sopenharmony_ci .close = snd_ump_legacy_close, 105962306a36Sopenharmony_ci .trigger = snd_ump_legacy_trigger, 106062306a36Sopenharmony_ci}; 106162306a36Sopenharmony_ci 106262306a36Sopenharmony_cistatic const struct snd_rawmidi_ops snd_ump_legacy_output_ops = { 106362306a36Sopenharmony_ci .open = snd_ump_legacy_open, 106462306a36Sopenharmony_ci .close = snd_ump_legacy_close, 106562306a36Sopenharmony_ci .trigger = snd_ump_legacy_trigger, 106662306a36Sopenharmony_ci .drain = snd_ump_legacy_drain, 106762306a36Sopenharmony_ci}; 106862306a36Sopenharmony_ci 106962306a36Sopenharmony_cistatic const struct snd_rawmidi_global_ops snd_ump_legacy_ops = { 107062306a36Sopenharmony_ci .dev_register = snd_ump_legacy_dev_register, 107162306a36Sopenharmony_ci}; 107262306a36Sopenharmony_ci 107362306a36Sopenharmony_cistatic int process_legacy_output(struct snd_ump_endpoint *ump, 107462306a36Sopenharmony_ci u32 *buffer, int count) 107562306a36Sopenharmony_ci{ 107662306a36Sopenharmony_ci struct snd_rawmidi_substream *substream; 107762306a36Sopenharmony_ci struct ump_cvt_to_ump *ctx; 107862306a36Sopenharmony_ci const int dir = SNDRV_RAWMIDI_STREAM_OUTPUT; 107962306a36Sopenharmony_ci unsigned char c; 108062306a36Sopenharmony_ci int group, size = 0; 108162306a36Sopenharmony_ci unsigned long flags; 108262306a36Sopenharmony_ci 108362306a36Sopenharmony_ci if (!ump->out_cvts || !ump->legacy_out_opens) 108462306a36Sopenharmony_ci return 0; 108562306a36Sopenharmony_ci 108662306a36Sopenharmony_ci spin_lock_irqsave(&ump->legacy_locks[dir], flags); 108762306a36Sopenharmony_ci for (group = 0; group < SNDRV_UMP_MAX_GROUPS; group++) { 108862306a36Sopenharmony_ci substream = ump->legacy_substreams[dir][group]; 108962306a36Sopenharmony_ci if (!substream) 109062306a36Sopenharmony_ci continue; 109162306a36Sopenharmony_ci ctx = &ump->out_cvts[group]; 109262306a36Sopenharmony_ci while (!ctx->ump_bytes && 109362306a36Sopenharmony_ci snd_rawmidi_transmit(substream, &c, 1) > 0) 109462306a36Sopenharmony_ci snd_ump_convert_to_ump(ctx, group, ump->info.protocol, c); 109562306a36Sopenharmony_ci if (ctx->ump_bytes && ctx->ump_bytes <= count) { 109662306a36Sopenharmony_ci size = ctx->ump_bytes; 109762306a36Sopenharmony_ci memcpy(buffer, ctx->ump, size); 109862306a36Sopenharmony_ci ctx->ump_bytes = 0; 109962306a36Sopenharmony_ci break; 110062306a36Sopenharmony_ci } 110162306a36Sopenharmony_ci } 110262306a36Sopenharmony_ci spin_unlock_irqrestore(&ump->legacy_locks[dir], flags); 110362306a36Sopenharmony_ci return size; 110462306a36Sopenharmony_ci} 110562306a36Sopenharmony_ci 110662306a36Sopenharmony_cistatic void process_legacy_input(struct snd_ump_endpoint *ump, const u32 *src, 110762306a36Sopenharmony_ci int words) 110862306a36Sopenharmony_ci{ 110962306a36Sopenharmony_ci struct snd_rawmidi_substream *substream; 111062306a36Sopenharmony_ci unsigned char buf[16]; 111162306a36Sopenharmony_ci unsigned char group; 111262306a36Sopenharmony_ci unsigned long flags; 111362306a36Sopenharmony_ci const int dir = SNDRV_RAWMIDI_STREAM_INPUT; 111462306a36Sopenharmony_ci int size; 111562306a36Sopenharmony_ci 111662306a36Sopenharmony_ci size = snd_ump_convert_from_ump(src, buf, &group); 111762306a36Sopenharmony_ci if (size <= 0) 111862306a36Sopenharmony_ci return; 111962306a36Sopenharmony_ci spin_lock_irqsave(&ump->legacy_locks[dir], flags); 112062306a36Sopenharmony_ci substream = ump->legacy_substreams[dir][group]; 112162306a36Sopenharmony_ci if (substream) 112262306a36Sopenharmony_ci snd_rawmidi_receive(substream, buf, size); 112362306a36Sopenharmony_ci spin_unlock_irqrestore(&ump->legacy_locks[dir], flags); 112462306a36Sopenharmony_ci} 112562306a36Sopenharmony_ci 112662306a36Sopenharmony_ci/* Fill ump->legacy_mapping[] for groups to be used for legacy rawmidi */ 112762306a36Sopenharmony_cistatic int fill_legacy_mapping(struct snd_ump_endpoint *ump) 112862306a36Sopenharmony_ci{ 112962306a36Sopenharmony_ci struct snd_ump_block *fb; 113062306a36Sopenharmony_ci unsigned int group_maps = 0; 113162306a36Sopenharmony_ci int i, num; 113262306a36Sopenharmony_ci 113362306a36Sopenharmony_ci if (ump->info.flags & SNDRV_UMP_EP_INFO_STATIC_BLOCKS) { 113462306a36Sopenharmony_ci list_for_each_entry(fb, &ump->block_list, list) { 113562306a36Sopenharmony_ci for (i = 0; i < fb->info.num_groups; i++) 113662306a36Sopenharmony_ci group_maps |= 1U << (fb->info.first_group + i); 113762306a36Sopenharmony_ci } 113862306a36Sopenharmony_ci if (!group_maps) 113962306a36Sopenharmony_ci ump_info(ump, "No UMP Group is found in FB\n"); 114062306a36Sopenharmony_ci } 114162306a36Sopenharmony_ci 114262306a36Sopenharmony_ci /* use all groups for non-static case */ 114362306a36Sopenharmony_ci if (!group_maps) 114462306a36Sopenharmony_ci group_maps = (1U << SNDRV_UMP_MAX_GROUPS) - 1; 114562306a36Sopenharmony_ci 114662306a36Sopenharmony_ci num = 0; 114762306a36Sopenharmony_ci for (i = 0; i < SNDRV_UMP_MAX_GROUPS; i++) 114862306a36Sopenharmony_ci if (group_maps & (1U << i)) 114962306a36Sopenharmony_ci ump->legacy_mapping[num++] = i; 115062306a36Sopenharmony_ci 115162306a36Sopenharmony_ci return num; 115262306a36Sopenharmony_ci} 115362306a36Sopenharmony_ci 115462306a36Sopenharmony_cistatic void fill_substream_names(struct snd_ump_endpoint *ump, 115562306a36Sopenharmony_ci struct snd_rawmidi *rmidi, int dir) 115662306a36Sopenharmony_ci{ 115762306a36Sopenharmony_ci struct snd_rawmidi_substream *s; 115862306a36Sopenharmony_ci 115962306a36Sopenharmony_ci list_for_each_entry(s, &rmidi->streams[dir].substreams, list) 116062306a36Sopenharmony_ci snprintf(s->name, sizeof(s->name), "Group %d (%.16s)", 116162306a36Sopenharmony_ci ump->legacy_mapping[s->number] + 1, ump->info.name); 116262306a36Sopenharmony_ci} 116362306a36Sopenharmony_ci 116462306a36Sopenharmony_ciint snd_ump_attach_legacy_rawmidi(struct snd_ump_endpoint *ump, 116562306a36Sopenharmony_ci char *id, int device) 116662306a36Sopenharmony_ci{ 116762306a36Sopenharmony_ci struct snd_rawmidi *rmidi; 116862306a36Sopenharmony_ci bool input, output; 116962306a36Sopenharmony_ci int err, num; 117062306a36Sopenharmony_ci 117162306a36Sopenharmony_ci ump->out_cvts = kcalloc(SNDRV_UMP_MAX_GROUPS, 117262306a36Sopenharmony_ci sizeof(*ump->out_cvts), GFP_KERNEL); 117362306a36Sopenharmony_ci if (!ump->out_cvts) 117462306a36Sopenharmony_ci return -ENOMEM; 117562306a36Sopenharmony_ci 117662306a36Sopenharmony_ci num = fill_legacy_mapping(ump); 117762306a36Sopenharmony_ci 117862306a36Sopenharmony_ci input = ump->core.info_flags & SNDRV_RAWMIDI_INFO_INPUT; 117962306a36Sopenharmony_ci output = ump->core.info_flags & SNDRV_RAWMIDI_INFO_OUTPUT; 118062306a36Sopenharmony_ci err = snd_rawmidi_new(ump->core.card, id, device, 118162306a36Sopenharmony_ci output ? num : 0, input ? num : 0, 118262306a36Sopenharmony_ci &rmidi); 118362306a36Sopenharmony_ci if (err < 0) { 118462306a36Sopenharmony_ci kfree(ump->out_cvts); 118562306a36Sopenharmony_ci return err; 118662306a36Sopenharmony_ci } 118762306a36Sopenharmony_ci 118862306a36Sopenharmony_ci if (input) 118962306a36Sopenharmony_ci snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, 119062306a36Sopenharmony_ci &snd_ump_legacy_input_ops); 119162306a36Sopenharmony_ci if (output) 119262306a36Sopenharmony_ci snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, 119362306a36Sopenharmony_ci &snd_ump_legacy_output_ops); 119462306a36Sopenharmony_ci snprintf(rmidi->name, sizeof(rmidi->name), "%.68s (MIDI 1.0)", 119562306a36Sopenharmony_ci ump->info.name); 119662306a36Sopenharmony_ci rmidi->info_flags = ump->core.info_flags & ~SNDRV_RAWMIDI_INFO_UMP; 119762306a36Sopenharmony_ci rmidi->ops = &snd_ump_legacy_ops; 119862306a36Sopenharmony_ci rmidi->private_data = ump; 119962306a36Sopenharmony_ci ump->legacy_rmidi = rmidi; 120062306a36Sopenharmony_ci if (input) 120162306a36Sopenharmony_ci fill_substream_names(ump, rmidi, SNDRV_RAWMIDI_STREAM_INPUT); 120262306a36Sopenharmony_ci if (output) 120362306a36Sopenharmony_ci fill_substream_names(ump, rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT); 120462306a36Sopenharmony_ci 120562306a36Sopenharmony_ci ump_dbg(ump, "Created a legacy rawmidi #%d (%s)\n", device, id); 120662306a36Sopenharmony_ci return 0; 120762306a36Sopenharmony_ci} 120862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_ump_attach_legacy_rawmidi); 120962306a36Sopenharmony_ci#endif /* CONFIG_SND_UMP_LEGACY_RAWMIDI */ 121062306a36Sopenharmony_ci 121162306a36Sopenharmony_ciMODULE_DESCRIPTION("Universal MIDI Packet (UMP) Core Driver"); 121262306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 1213