In order to support functions such as firmware upgrade from user-space, add ioctls for submitting arbitrary proprietary requests through scarlett2_usb() and requesting/releasing exclusive access. --- Hi Takashi, I recently figured how to update the firmware on Scarlett Gen 2+ devices. I think the best way to implement this is with an ioctl giving access to the scarlett2_usb() function from user-space, plus two ioctls to request/release exclusive access. Does something like this seem reasonable? What ioctl magic/command values should I use? Should lock/unlock be separate, or one ioctl takes an argument for which operation to do? Thanks, Geoffrey. MAINTAINERS | 1 + include/uapi/sound/scarlett_gen2.h | 28 +++++++++ sound/usb/mixer_scarlett_gen2.c | 94 +++++++++++++++++++++++++++++- 3 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 include/uapi/sound/scarlett_gen2.h diff --git a/MAINTAINERS b/MAINTAINERS index 4cc6bf79fdd8..25e5e40f7118 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8066,6 +8066,7 @@ M: Geoffrey D. Bennett <g@xxxxx> L: alsa-devel@xxxxxxxxxxxxxxxx (moderated for non-subscribers) S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound.git +F: include/uapi/sound/scarlett_gen2.h F: sound/usb/mixer_scarlett_gen2.c FORCEDETH GIGABIT ETHERNET DRIVER diff --git a/include/uapi/sound/scarlett_gen2.h b/include/uapi/sound/scarlett_gen2.h new file mode 100644 index 000000000000..0b51a9754ba2 --- /dev/null +++ b/include/uapi/sound/scarlett_gen2.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * Focusrite Scarlett Gen 2/3 and Clarett USB/Clarett+ Driver for ALSA + * + * Copyright (c) 2023 by Geoffrey D. Bennett <g at b4.vu> + */ +#ifndef __UAPI_SOUND_SCARLETT_GEN2_H +#define __UAPI_SOUND_SCARLETT_GEN2_H + +#include <linux/types.h> +#include <linux/ioctl.h> + +/* Send a proprietary format request to the interface */ +struct snd_scarlett_gen2_usb_cmd { + u32 cmd; + void *req_data; + u16 req_size; + void *resp_data; + u16 resp_size; +}; + +#define SCARLETT2_IOCTL_USB_CMD _IOWR('S', 0x60, struct snd_scarlett_gen2_usb_cmd) + +/* Request/release exclusive access */ +#define SCARLETT2_IOCTL_LOCK _IO('S', 0x61) +#define SCARLETT2_IOCTL_UNLOCK _IO('S', 0x62) + +#endif /* __UAPI_SOUND_SCARLETT_GEN2_H */ diff --git a/sound/usb/mixer_scarlett_gen2.c b/sound/usb/mixer_scarlett_gen2.c index ffd398f26d2c..f1ef87b54813 100644 --- a/sound/usb/mixer_scarlett_gen2.c +++ b/sound/usb/mixer_scarlett_gen2.c @@ -144,6 +144,9 @@ #include <sound/control.h> #include <sound/tlv.h> +#include <sound/hwdep.h> + +#include <uapi/sound/scarlett_gen2.h> #include "usbaudio.h" #include "mixer.h" @@ -4262,10 +4265,84 @@ static int snd_scarlett_gen2_controls_create( return 0; } +static int scarlett_gen2_ioctl_usb_cmd(struct snd_hwdep *hw, unsigned long arg) +{ + struct scarlett2_data *private = hw->private_data; + struct snd_scarlett_gen2_usb_cmd usb_cmd; + int err; + void *req_data = NULL; + void *resp_data = NULL; + + // get cmd & req/resp buffers + if (copy_from_user(&usb_cmd, (void __user *)arg, sizeof(usb_cmd))) + return -EFAULT; + + // allocate request buffer, copy data from user + if (usb_cmd.req_size > 0) { + req_data = kmalloc(usb_cmd.req_size, GFP_KERNEL); + if (!req_data) { + err = -ENOMEM; + goto exit; + } + if (copy_from_user(req_data, usb_cmd.req_data, + usb_cmd.req_size)) { + err = -EFAULT; + goto exit; + } + } + + // allocate response buffer + if (usb_cmd.resp_size > 0) { + resp_data = kmalloc(usb_cmd.resp_size, GFP_KERNEL); + if (!resp_data) { + err = -ENOMEM; + goto exit; + } + } + + // send request, get response + err = scarlett2_usb(private->mixer, usb_cmd.cmd, + req_data, usb_cmd.req_size, + resp_data, usb_cmd.resp_size); + if (err < 0) + goto exit; + + // copy response to user + if (usb_cmd.resp_size > 0) + if (copy_to_user(usb_cmd.resp_data, resp_data, + usb_cmd.resp_size)) + err = -EFAULT; + +exit: + kfree(req_data); + kfree(resp_data); + + return err; +} + +static int scarlett_gen2_hwdep_ioctl(struct snd_hwdep *hw, struct file *file, + unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + + case SCARLETT2_IOCTL_USB_CMD: + return scarlett_gen2_ioctl_usb_cmd(hw, arg); + + // TODO + case SCARLETT2_IOCTL_LOCK: + case SCARLETT2_IOCTL_UNLOCK: + return -EINVAL; + + default: + return -ENOIOCTLCMD; + } +} + int snd_scarlett_gen2_init(struct usb_mixer_interface *mixer) { struct snd_usb_audio *chip = mixer->chip; const struct scarlett2_device_entry *entry; + struct snd_hwdep *hw; int err; /* only use UAC_VERSION_2 */ @@ -4302,11 +4379,24 @@ int snd_scarlett_gen2_init(struct usb_mixer_interface *mixer) USB_ID_PRODUCT(chip->usb_id)); err = snd_scarlett_gen2_controls_create(mixer, entry); - if (err < 0) + if (err < 0) { usb_audio_err(mixer->chip, "Error initialising %s Mixer Driver: %d", entry->series_name, err); + return err; + } + + err = snd_hwdep_new(mixer->chip->card, "Focusrite Control", 0, &hw); + if (err < 0) { + usb_audio_err(mixer->chip, + "Error creating hwdep device: %d", + err); + return err; + } - return err; + hw->private_data = mixer->private_data; + hw->ops.ioctl = scarlett_gen2_hwdep_ioctl; + + return 0; } -- 2.41.0