ALSA: seq: Add ioctls for client UMP info query and setup

Add new ioctls for sequencer clients to query and set the UMP endpoint
and block information.

As a sequencer client corresponds to a UMP Endpoint, one UMP Endpoint
information can be assigned at most to a single sequencer client while
multiple UMP block infos can be assigned by passing the type with the
offset of block id (i.e. type = block_id + 1).

For the kernel client, only SNDRV_SEQ_IOCTL_GET_CLIENT_UMP_INFO is
allowed.

Reviewed-by: Jaroslav Kysela <perex@perex.cz>
Link: https://lore.kernel.org/r/20230523075358.9672-35-tiwai@suse.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
Takashi Iwai 2023-05-23 09:53:55 +02:00
parent 4025f0e627
commit d2d247e35e
5 changed files with 153 additions and 2 deletions

View File

@ -585,6 +585,18 @@ struct snd_seq_query_subs {
char reserved[64]; /* for future use */
};
/*
* UMP-specific information
*/
/* type of UMP info query */
#define SNDRV_SEQ_CLIENT_UMP_INFO_ENDPOINT 0
#define SNDRV_SEQ_CLIENT_UMP_INFO_BLOCK 1
struct snd_seq_client_ump_info {
int client; /* client number to inquire/set */
int type; /* type to inquire/set */
unsigned char info[512]; /* info (either UMP ep or block info) */
} __packed;
/*
* IOCTL commands
@ -598,6 +610,8 @@ struct snd_seq_query_subs {
#define SNDRV_SEQ_IOCTL_GET_CLIENT_INFO _IOWR('S', 0x10, struct snd_seq_client_info)
#define SNDRV_SEQ_IOCTL_SET_CLIENT_INFO _IOW ('S', 0x11, struct snd_seq_client_info)
#define SNDRV_SEQ_IOCTL_GET_CLIENT_UMP_INFO _IOWR('S', 0x12, struct snd_seq_client_ump_info)
#define SNDRV_SEQ_IOCTL_SET_CLIENT_UMP_INFO _IOWR('S', 0x13, struct snd_seq_client_ump_info)
#define SNDRV_SEQ_IOCTL_CREATE_PORT _IOWR('S', 0x20, struct snd_seq_port_info)
#define SNDRV_SEQ_IOCTL_DELETE_PORT _IOW ('S', 0x21, struct snd_seq_port_info)

View File

@ -14,6 +14,7 @@
#include <linux/kmod.h>
#include <sound/seq_kernel.h>
#include <sound/ump.h>
#include "seq_clientmgr.h"
#include "seq_memory.h"
#include "seq_queue.h"
@ -71,6 +72,10 @@ static int snd_seq_deliver_single_event(struct snd_seq_client *client,
struct snd_seq_event *event,
int filter, int atomic, int hop);
#if IS_ENABLED(CONFIG_SND_SEQ_UMP)
static void free_ump_info(struct snd_seq_client *client);
#endif
/*
*/
static inline unsigned short snd_seq_file_flags(struct file *file)
@ -382,6 +387,9 @@ static int snd_seq_release(struct inode *inode, struct file *file)
seq_free_client(client);
if (client->data.user.fifo)
snd_seq_fifo_delete(&client->data.user.fifo);
#if IS_ENABLED(CONFIG_SND_SEQ_UMP)
free_ump_info(client);
#endif
put_pid(client->data.user.owner);
kfree(client);
}
@ -1282,7 +1290,6 @@ static int snd_seq_ioctl_set_client_info(struct snd_seq_client *client,
if (client->user_pversion >= SNDRV_PROTOCOL_VERSION(1, 0, 3))
client->midi_version = client_info->midi_version;
memcpy(client->event_filter, client_info->event_filter, 32);
return 0;
}
@ -2087,6 +2094,108 @@ static int snd_seq_ioctl_query_next_port(struct snd_seq_client *client,
return 0;
}
#if IS_ENABLED(CONFIG_SND_SEQ_UMP)
#define NUM_UMP_INFOS (SNDRV_UMP_MAX_BLOCKS + 1)
static void free_ump_info(struct snd_seq_client *client)
{
int i;
if (!client->ump_info)
return;
for (i = 0; i < NUM_UMP_INFOS; i++)
kfree(client->ump_info[i]);
kfree(client->ump_info);
client->ump_info = NULL;
}
static void terminate_ump_info_strings(void *p, int type)
{
if (type == SNDRV_SEQ_CLIENT_UMP_INFO_ENDPOINT) {
struct snd_ump_endpoint_info *ep = p;
ep->name[sizeof(ep->name) - 1] = 0;
} else {
struct snd_ump_block_info *bp = p;
bp->name[sizeof(bp->name) - 1] = 0;
}
}
/* UMP-specific ioctls -- called directly without data copy */
static int snd_seq_ioctl_client_ump_info(struct snd_seq_client *caller,
unsigned int cmd,
unsigned long arg)
{
struct snd_seq_client_ump_info __user *argp =
(struct snd_seq_client_ump_info __user *)arg;
struct snd_seq_client *cptr;
int client, type, err = 0;
size_t size;
void *p;
if (get_user(client, &argp->client) || get_user(type, &argp->type))
return -EFAULT;
if (cmd == SNDRV_SEQ_IOCTL_SET_CLIENT_UMP_INFO &&
caller->number != client)
return -EPERM;
if (type < 0 || type >= NUM_UMP_INFOS)
return -EINVAL;
if (type == SNDRV_SEQ_CLIENT_UMP_INFO_ENDPOINT)
size = sizeof(struct snd_ump_endpoint_info);
else
size = sizeof(struct snd_ump_block_info);
cptr = snd_seq_client_use_ptr(client);
if (!cptr)
return -ENOENT;
mutex_lock(&cptr->ioctl_mutex);
if (!cptr->midi_version) {
err = -EBADFD;
goto error;
}
if (cmd == SNDRV_SEQ_IOCTL_GET_CLIENT_UMP_INFO) {
if (!cptr->ump_info)
p = NULL;
else
p = cptr->ump_info[type];
if (!p) {
err = -ENODEV;
goto error;
}
if (copy_to_user(argp->info, p, size)) {
err = -EFAULT;
goto error;
}
} else {
if (cptr->type != USER_CLIENT) {
err = -EBADFD;
goto error;
}
if (!cptr->ump_info) {
cptr->ump_info = kcalloc(NUM_UMP_INFOS,
sizeof(void *), GFP_KERNEL);
if (!cptr->ump_info) {
err = -ENOMEM;
goto error;
}
}
p = memdup_user(argp->info, size);
if (IS_ERR(p)) {
err = PTR_ERR(p);
goto error;
}
kfree(cptr->ump_info[type]);
terminate_ump_info_strings(p, type);
cptr->ump_info[type] = p;
}
error:
mutex_unlock(&cptr->ioctl_mutex);
snd_seq_client_unlock(cptr);
return err;
}
#endif
/* -------------------------------------------------------- */
static const struct ioctl_handler {
@ -2157,6 +2266,15 @@ static long snd_seq_ioctl(struct file *file, unsigned int cmd,
if (snd_BUG_ON(!client))
return -ENXIO;
#if IS_ENABLED(CONFIG_SND_SEQ_UMP)
/* exception - handling large data */
switch (cmd) {
case SNDRV_SEQ_IOCTL_GET_CLIENT_UMP_INFO:
case SNDRV_SEQ_IOCTL_SET_CLIENT_UMP_INFO:
return snd_seq_ioctl_client_ump_info(client, cmd, arg);
}
#endif
for (handler = ioctl_handlers; handler->cmd > 0; ++handler) {
if (handler->cmd == cmd)
break;

View File

@ -12,7 +12,6 @@
#include "seq_ports.h"
#include "seq_lock.h"
/* client manager */
struct snd_seq_user_client {
@ -59,6 +58,9 @@ struct snd_seq_client {
struct snd_seq_user_client user;
struct snd_seq_kernel_client kernel;
} data;
/* for UMP */
void **ump_info;
};
/* usage statistics */

View File

@ -86,6 +86,8 @@ static long snd_seq_ioctl_compat(struct file *file, unsigned int cmd, unsigned l
case SNDRV_SEQ_IOCTL_SYSTEM_INFO:
case SNDRV_SEQ_IOCTL_GET_CLIENT_INFO:
case SNDRV_SEQ_IOCTL_SET_CLIENT_INFO:
case SNDRV_SEQ_IOCTL_GET_CLIENT_UMP_INFO:
case SNDRV_SEQ_IOCTL_SET_CLIENT_UMP_INFO:
case SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT:
case SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT:
case SNDRV_SEQ_IOCTL_CREATE_QUEUE:

View File

@ -47,6 +47,7 @@ struct seq_ump_client {
struct snd_rawmidi_file out_rfile; /* rawmidi for output */
struct seq_ump_input_buffer input; /* input parser context */
struct seq_ump_group groups[SNDRV_UMP_MAX_GROUPS]; /* table of groups */
void *ump_info[SNDRV_UMP_MAX_BLOCKS + 1]; /* shadow of seq client ump_info */
};
/* number of 32bit words for each UMP message type */
@ -384,6 +385,8 @@ static int snd_seq_ump_probe(struct device *_dev)
struct snd_ump_endpoint *ump = dev->private_data;
struct snd_card *card = dev->card;
struct seq_ump_client *client;
struct snd_ump_block *fb;
struct snd_seq_client *cptr;
int p, err;
client = kzalloc(sizeof(*client), GFP_KERNEL);
@ -400,6 +403,10 @@ static int snd_seq_ump_probe(struct device *_dev)
goto error;
}
client->ump_info[0] = &ump->info;
list_for_each_entry(fb, &ump->block_list, list)
client->ump_info[fb->info.block_id + 1] = &fb->info;
setup_client_midi_version(client);
update_group_attrs(client);
@ -413,6 +420,14 @@ static int snd_seq_ump_probe(struct device *_dev)
if (err < 0)
goto error;
cptr = snd_seq_kernel_client_get(client->seq_client);
if (!cptr) {
err = -EINVAL;
goto error;
}
cptr->ump_info = client->ump_info;
snd_seq_kernel_client_put(cptr);
ump->seq_client = client;
ump->seq_ops = &seq_ump_ops;
return 0;