[PATCH 00/11] ALSA: scarlett2: Firmware Upgrade and Error Handling Improvements
Hi Takashi,
11 patches to improve error handling and add firmware upgrade support to the scarlett2 driver. The notes below incorporate your feedback & include answers to your questions in response to my previous RFC v2 email.
Patch 1 adds GitHub links for the repo where I share this code before submission here, and for issue reporting.
Patches 2-5 add missing error/bounds checks.
Patch 6 adds a missing lock.
Patches 7-11 add support for firmware upgrades.
The usage of the hwdep interface is as per my previous proposal:
- ioctl pversion to check the protocol version - ioctl select_flash_segment SCARLETT2_SEGMENT_ID_SETTINGS - ioctl erase_flash_segment - ioctl get_erase_progress (optional) - ioctl select_flash_segment SCARLETT2_SEGMENT_ID_FIRMWARE - ioctl erase_flash_segment - ioctl get_erase_progress (optional) - write() the firmware - ioctl reboot
Notes on the hwdep interface:
- Conflicting mixer operations are denied with EBUSY.
- The hwdep interface is marked as exclusive so there can't be conflicting hwdep operations.
- Invalid sequences of operations (e.g. erase before select, write before erase) return an error.
- The erase operation is asynchronous, and the progress can optionally be monitored by calling get_erase_progress.
- If the erase progress is not monitored then subsequent hwdep operations will wait until the erase is complete.
- The write operation is synchronous, but <1KB can be written per call, and it returns very quickly. On the user-side it looks like this with error checking omitted:
while (offset < len) offset += snd_hwdep_write(hwdep, buf + offset, len - offset);
- By using a subset of the firmware-upgrade steps, other useful high-level operations can be performed: reset-to-factory-defaults, reset-to-factory-firmware, or just reboot.
- I considered combining the select and erase operations, but that would prevent a future read-flash operation if that ever become possible.
- I considered separate one-shot ioctls for reset-to-factory, etc., but that prevents providing progress feedback.
- In case the purpose of the other firmware segments is discovered in the future, this implementation would be able to support erase/write/read of them with minimal changes.
Here is a user-space implementation of all of the above: https://github.com/geoffreybennett/scarlett2
This has been tested by me on every supported Scarlett 2nd, 3rd, 4th Gen and Clarett+ device, and tested by a handful of others on their Scarlett 4th Gen devices.
Thanks, Geoffrey.
Geoffrey D. Bennett (11): ALSA: scarlett2: Update maintainer info ALSA: scarlett2: Add missing error check to scarlett2_config_save() ALSA: scarlett2: Add missing error check to scarlett2_usb_set_config() ALSA: scarlett2: Add missing error checks to *_ctl_get() ALSA: scarlett2: Add clamp() in scarlett2_mixer_ctl_put() ALSA: scarlett2: Add missing mutex lock around get meter levels ALSA: scarlett2: Add #defines for firmware upgrade ALSA: scarlett2: Retrieve useful flash segment numbers ALSA: scarlett2: Add skeleton hwdep/ioctl interface ALSA: scarlett2: Add ioctl commands to erase flash segments ALSA: scarlett2: Add support for uploading new firmware
MAINTAINERS | 7 +- include/uapi/sound/scarlett2.h | 54 ++ sound/usb/mixer_scarlett2.c | 914 ++++++++++++++++++++++++++++++--- 3 files changed, 901 insertions(+), 74 deletions(-) create mode 100644 include/uapi/sound/scarlett2.h
Update MAINTAINERS and "enabled" message with GitHub repository links.
Signed-off-by: Geoffrey D. Bennett g@b4.vu --- MAINTAINERS | 6 ++++-- sound/usb/mixer_scarlett2.c | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS index ea790149af79..ae3f72f57854 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8272,11 +8272,13 @@ L: linux-input@vger.kernel.org S: Maintained F: drivers/input/joystick/fsia6b.c
-FOCUSRITE SCARLETT GEN 2/3 MIXER DRIVER +FOCUSRITE SCARLETT2 MIXER DRIVER (Scarlett Gen 2+ and Clarett) M: Geoffrey D. Bennett g@b4.vu L: alsa-devel@alsa-project.org (moderated for non-subscribers) S: Maintained -T: git git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound.git +W: https://github.com/geoffreybennett/scarlett-gen2 +B: https://github.com/geoffreybennett/scarlett-gen2/issues +T: git https://github.com/geoffreybennett/scarlett-gen2.git F: sound/usb/mixer_scarlett2.c
FORCEDETH GIGABIT ETHERNET DRIVER diff --git a/sound/usb/mixer_scarlett2.c b/sound/usb/mixer_scarlett2.c index f7c57a2c3028..a0dbdf921745 100644 --- a/sound/usb/mixer_scarlett2.c +++ b/sound/usb/mixer_scarlett2.c @@ -4534,7 +4534,8 @@ int snd_scarlett2_init(struct usb_mixer_interface *mixer)
usb_audio_info(chip, "Focusrite %s Mixer Driver enabled (pid=0x%04x); " - "report any issues to g@b4.vu", + "report any issues to " + "https://github.com/geoffreybennett/scarlett-gen2/issues", entry->series_name, USB_ID_PRODUCT(chip->usb_id));
scarlett2_config_save() was ignoring the return value from scarlett2_usb(). As this function is not called from user-space we can't return the error, so call usb_audio_err() instead.
Signed-off-by: Geoffrey D. Bennett g@b4.vu Fixes: 9e4d5c1be21f ("ALSA: usb-audio: Scarlett Gen 2 mixer interface") --- sound/usb/mixer_scarlett2.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/sound/usb/mixer_scarlett2.c b/sound/usb/mixer_scarlett2.c index a0dbdf921745..a0ba53372f7b 100644 --- a/sound/usb/mixer_scarlett2.c +++ b/sound/usb/mixer_scarlett2.c @@ -1524,9 +1524,11 @@ static void scarlett2_config_save(struct usb_mixer_interface *mixer) { __le32 req = cpu_to_le32(SCARLETT2_USB_CONFIG_SAVE);
- scarlett2_usb(mixer, SCARLETT2_USB_DATA_CMD, - &req, sizeof(u32), - NULL, 0); + int err = scarlett2_usb(mixer, SCARLETT2_USB_DATA_CMD, + &req, sizeof(u32), + NULL, 0); + if (err < 0) + usb_audio_err(mixer->chip, "config save failed: %d\n", err); }
/* Delayed work to save config */
scarlett2_usb_set_config() calls scarlett2_usb_get() but was not checking the result. Return the error if it fails rather than continuing with an invalid value.
Signed-off-by: Geoffrey D. Bennett g@b4.vu Fixes: 9e15fae6c51a ("ALSA: usb-audio: scarlett2: Allow bit-level access to config") --- sound/usb/mixer_scarlett2.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/sound/usb/mixer_scarlett2.c b/sound/usb/mixer_scarlett2.c index a0ba53372f7b..22285d8038c1 100644 --- a/sound/usb/mixer_scarlett2.c +++ b/sound/usb/mixer_scarlett2.c @@ -1577,7 +1577,10 @@ static int scarlett2_usb_set_config( size = 1; offset = config_item->offset;
- scarlett2_usb_get(mixer, offset, &tmp, 1); + err = scarlett2_usb_get(mixer, offset, &tmp, 1); + if (err < 0) + return err; + if (value) tmp |= (1 << index); else
The *_ctl_get() functions which call scarlett2_update_*() were not checking the return value. Fix to check the return value and pass to the caller.
Signed-off-by: Geoffrey D. Bennett g@b4.vu Fixes: 9e4d5c1be21f ("ALSA: usb-audio: Scarlett Gen 2 mixer interface") --- sound/usb/mixer_scarlett2.c | 182 +++++++++++++++++++++++++----------- 1 file changed, 130 insertions(+), 52 deletions(-)
diff --git a/sound/usb/mixer_scarlett2.c b/sound/usb/mixer_scarlett2.c index 22285d8038c1..373911937487 100644 --- a/sound/usb/mixer_scarlett2.c +++ b/sound/usb/mixer_scarlett2.c @@ -2100,14 +2100,20 @@ static int scarlett2_sync_ctl_get(struct snd_kcontrol *kctl, struct usb_mixer_elem_info *elem = kctl->private_data; struct usb_mixer_interface *mixer = elem->head.mixer; struct scarlett2_data *private = mixer->private_data; + int err = 0;
mutex_lock(&private->data_mutex); - if (private->sync_updated) - scarlett2_update_sync(mixer); + + if (private->sync_updated) { + err = scarlett2_update_sync(mixer); + if (err < 0) + goto unlock; + } ucontrol->value.enumerated.item[0] = private->sync; - mutex_unlock(&private->data_mutex);
- return 0; +unlock: + mutex_unlock(&private->data_mutex); + return err; }
static const struct snd_kcontrol_new scarlett2_sync_ctl = { @@ -2190,14 +2196,20 @@ static int scarlett2_master_volume_ctl_get(struct snd_kcontrol *kctl, struct usb_mixer_elem_info *elem = kctl->private_data; struct usb_mixer_interface *mixer = elem->head.mixer; struct scarlett2_data *private = mixer->private_data; + int err = 0;
mutex_lock(&private->data_mutex); - if (private->vol_updated) - scarlett2_update_volumes(mixer); - mutex_unlock(&private->data_mutex);
+ if (private->vol_updated) { + err = scarlett2_update_volumes(mixer); + if (err < 0) + goto unlock; + } ucontrol->value.integer.value[0] = private->master_vol; - return 0; + +unlock: + mutex_unlock(&private->data_mutex); + return err; }
static int line_out_remap(struct scarlett2_data *private, int index) @@ -2223,14 +2235,20 @@ static int scarlett2_volume_ctl_get(struct snd_kcontrol *kctl, struct usb_mixer_interface *mixer = elem->head.mixer; struct scarlett2_data *private = mixer->private_data; int index = line_out_remap(private, elem->control); + int err = 0;
mutex_lock(&private->data_mutex); - if (private->vol_updated) - scarlett2_update_volumes(mixer); - mutex_unlock(&private->data_mutex);
+ if (private->vol_updated) { + err = scarlett2_update_volumes(mixer); + if (err < 0) + goto unlock; + } ucontrol->value.integer.value[0] = private->vol[index]; - return 0; + +unlock: + mutex_unlock(&private->data_mutex); + return err; }
static int scarlett2_volume_ctl_put(struct snd_kcontrol *kctl, @@ -2297,14 +2315,20 @@ static int scarlett2_mute_ctl_get(struct snd_kcontrol *kctl, struct usb_mixer_interface *mixer = elem->head.mixer; struct scarlett2_data *private = mixer->private_data; int index = line_out_remap(private, elem->control); + int err = 0;
mutex_lock(&private->data_mutex); - if (private->vol_updated) - scarlett2_update_volumes(mixer); - mutex_unlock(&private->data_mutex);
+ if (private->vol_updated) { + err = scarlett2_update_volumes(mixer); + if (err < 0) + goto unlock; + } ucontrol->value.integer.value[0] = private->mute_switch[index]; - return 0; + +unlock: + mutex_unlock(&private->data_mutex); + return err; }
static int scarlett2_mute_ctl_put(struct snd_kcontrol *kctl, @@ -2550,14 +2574,20 @@ static int scarlett2_level_enum_ctl_get(struct snd_kcontrol *kctl, const struct scarlett2_device_info *info = private->info;
int index = elem->control + info->level_input_first; + int err = 0;
mutex_lock(&private->data_mutex); - if (private->input_other_updated) - scarlett2_update_input_other(mixer); + + if (private->input_other_updated) { + err = scarlett2_update_input_other(mixer); + if (err < 0) + goto unlock; + } ucontrol->value.enumerated.item[0] = private->level_switch[index]; - mutex_unlock(&private->data_mutex);
- return 0; +unlock: + mutex_unlock(&private->data_mutex); + return err; }
static int scarlett2_level_enum_ctl_put(struct snd_kcontrol *kctl, @@ -2608,15 +2638,21 @@ static int scarlett2_pad_ctl_get(struct snd_kcontrol *kctl, struct usb_mixer_elem_info *elem = kctl->private_data; struct usb_mixer_interface *mixer = elem->head.mixer; struct scarlett2_data *private = mixer->private_data; + int err = 0;
mutex_lock(&private->data_mutex); - if (private->input_other_updated) - scarlett2_update_input_other(mixer); + + if (private->input_other_updated) { + err = scarlett2_update_input_other(mixer); + if (err < 0) + goto unlock; + } ucontrol->value.integer.value[0] = private->pad_switch[elem->control]; - mutex_unlock(&private->data_mutex);
- return 0; +unlock: + mutex_unlock(&private->data_mutex); + return err; }
static int scarlett2_pad_ctl_put(struct snd_kcontrol *kctl, @@ -2666,14 +2702,20 @@ static int scarlett2_air_ctl_get(struct snd_kcontrol *kctl, struct usb_mixer_elem_info *elem = kctl->private_data; struct usb_mixer_interface *mixer = elem->head.mixer; struct scarlett2_data *private = mixer->private_data; + int err = 0;
mutex_lock(&private->data_mutex); - if (private->input_other_updated) - scarlett2_update_input_other(mixer); + + if (private->input_other_updated) { + err = scarlett2_update_input_other(mixer); + if (err < 0) + goto unlock; + } ucontrol->value.integer.value[0] = private->air_switch[elem->control]; - mutex_unlock(&private->data_mutex);
- return 0; +unlock: + mutex_unlock(&private->data_mutex); + return err; }
static int scarlett2_air_ctl_put(struct snd_kcontrol *kctl, @@ -2723,15 +2765,21 @@ static int scarlett2_phantom_ctl_get(struct snd_kcontrol *kctl, struct usb_mixer_elem_info *elem = kctl->private_data; struct usb_mixer_interface *mixer = elem->head.mixer; struct scarlett2_data *private = mixer->private_data; + int err = 0;
mutex_lock(&private->data_mutex); - if (private->input_other_updated) - scarlett2_update_input_other(mixer); + + if (private->input_other_updated) { + err = scarlett2_update_input_other(mixer); + if (err < 0) + goto unlock; + } ucontrol->value.integer.value[0] = private->phantom_switch[elem->control]; - mutex_unlock(&private->data_mutex);
- return 0; +unlock: + mutex_unlock(&private->data_mutex); + return err; }
static int scarlett2_phantom_ctl_put(struct snd_kcontrol *kctl, @@ -2903,14 +2951,20 @@ static int scarlett2_direct_monitor_ctl_get( struct usb_mixer_elem_info *elem = kctl->private_data; struct usb_mixer_interface *mixer = elem->head.mixer; struct scarlett2_data *private = elem->head.mixer->private_data; + int err = 0;
mutex_lock(&private->data_mutex); - if (private->monitor_other_updated) - scarlett2_update_monitor_other(mixer); + + if (private->monitor_other_updated) { + err = scarlett2_update_monitor_other(mixer); + if (err < 0) + goto unlock; + } ucontrol->value.enumerated.item[0] = private->direct_monitor_switch; - mutex_unlock(&private->data_mutex);
- return 0; +unlock: + mutex_unlock(&private->data_mutex); + return err; }
static int scarlett2_direct_monitor_ctl_put( @@ -3010,14 +3064,20 @@ static int scarlett2_speaker_switch_enum_ctl_get( struct usb_mixer_elem_info *elem = kctl->private_data; struct usb_mixer_interface *mixer = elem->head.mixer; struct scarlett2_data *private = mixer->private_data; + int err = 0;
mutex_lock(&private->data_mutex); - if (private->monitor_other_updated) - scarlett2_update_monitor_other(mixer); + + if (private->monitor_other_updated) { + err = scarlett2_update_monitor_other(mixer); + if (err < 0) + goto unlock; + } ucontrol->value.enumerated.item[0] = private->speaker_switching_switch; - mutex_unlock(&private->data_mutex);
- return 0; +unlock: + mutex_unlock(&private->data_mutex); + return err; }
/* when speaker switching gets enabled, switch the main/alt speakers @@ -3165,14 +3225,20 @@ static int scarlett2_talkback_enum_ctl_get( struct usb_mixer_elem_info *elem = kctl->private_data; struct usb_mixer_interface *mixer = elem->head.mixer; struct scarlett2_data *private = mixer->private_data; + int err = 0;
mutex_lock(&private->data_mutex); - if (private->monitor_other_updated) - scarlett2_update_monitor_other(mixer); + + if (private->monitor_other_updated) { + err = scarlett2_update_monitor_other(mixer); + if (err < 0) + goto unlock; + } ucontrol->value.enumerated.item[0] = private->talkback_switch; - mutex_unlock(&private->data_mutex);
- return 0; +unlock: + mutex_unlock(&private->data_mutex); + return err; }
static int scarlett2_talkback_enum_ctl_put( @@ -3320,14 +3386,20 @@ static int scarlett2_dim_mute_ctl_get(struct snd_kcontrol *kctl, struct usb_mixer_elem_info *elem = kctl->private_data; struct usb_mixer_interface *mixer = elem->head.mixer; struct scarlett2_data *private = mixer->private_data; + int err = 0;
mutex_lock(&private->data_mutex); - if (private->vol_updated) - scarlett2_update_volumes(mixer); - mutex_unlock(&private->data_mutex);
+ if (private->vol_updated) { + err = scarlett2_update_volumes(mixer); + if (err < 0) + goto unlock; + } ucontrol->value.integer.value[0] = private->dim_mute[elem->control]; - return 0; + +unlock: + mutex_unlock(&private->data_mutex); + return err; }
static int scarlett2_dim_mute_ctl_put(struct snd_kcontrol *kctl, @@ -3698,14 +3770,20 @@ static int scarlett2_mux_src_enum_ctl_get(struct snd_kcontrol *kctl, struct usb_mixer_interface *mixer = elem->head.mixer; struct scarlett2_data *private = mixer->private_data; int index = line_out_remap(private, elem->control); + int err = 0;
mutex_lock(&private->data_mutex); - if (private->mux_updated) - scarlett2_usb_get_mux(mixer); + + if (private->mux_updated) { + err = scarlett2_usb_get_mux(mixer); + if (err < 0) + goto unlock; + } ucontrol->value.enumerated.item[0] = private->mux[index]; - mutex_unlock(&private->data_mutex);
- return 0; +unlock: + mutex_unlock(&private->data_mutex); + return err; }
static int scarlett2_mux_src_enum_ctl_put(struct snd_kcontrol *kctl,
Ensure the value passed to scarlett2_mixer_ctl_put() is between 0 and SCARLETT2_MIXER_MAX_VALUE so we don't attempt to access outside scarlett2_mixer_values[].
Signed-off-by: Geoffrey D. Bennett g@b4.vu Fixes: 9e4d5c1be21f ("ALSA: usb-audio: Scarlett Gen 2 mixer interface") --- sound/usb/mixer_scarlett2.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/sound/usb/mixer_scarlett2.c b/sound/usb/mixer_scarlett2.c index 373911937487..f1636a1614da 100644 --- a/sound/usb/mixer_scarlett2.c +++ b/sound/usb/mixer_scarlett2.c @@ -3663,7 +3663,8 @@ static int scarlett2_mixer_ctl_put(struct snd_kcontrol *kctl, mutex_lock(&private->data_mutex);
oval = private->mix[index]; - val = ucontrol->value.integer.value[0]; + val = clamp(ucontrol->value.integer.value[0], + 0L, (long)SCARLETT2_MIXER_MAX_VALUE); num_mixer_in = port_count[SCARLETT2_PORT_TYPE_MIX][SCARLETT2_PORT_OUT]; mix_num = index / num_mixer_in;
As scarlett2_meter_ctl_get() uses meter_level_map[], the data_mutex should be locked while accessing it.
Signed-off-by: Geoffrey D. Bennett g@b4.vu Fixes: 2b17abb0dec4 ("ALSA: scarlett2: Remap Level Meter values") --- sound/usb/mixer_scarlett2.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/sound/usb/mixer_scarlett2.c b/sound/usb/mixer_scarlett2.c index f1636a1614da..73f5badceb19 100644 --- a/sound/usb/mixer_scarlett2.c +++ b/sound/usb/mixer_scarlett2.c @@ -3880,10 +3880,12 @@ static int scarlett2_meter_ctl_get(struct snd_kcontrol *kctl, u16 meter_levels[SCARLETT2_MAX_METERS]; int i, err;
+ mutex_lock(&private->data_mutex); + err = scarlett2_usb_get_meter_levels(elem->head.mixer, elem->channels, meter_levels); if (err < 0) - return err; + goto unlock;
/* copy & translate from meter_levels[] using meter_level_map[] */ for (i = 0; i < elem->channels; i++) { @@ -3898,7 +3900,10 @@ static int scarlett2_meter_ctl_get(struct snd_kcontrol *kctl, ucontrol->value.integer.value[i] = value; }
- return 0; +unlock: + mutex_unlock(&private->data_mutex); + + return err; }
static const struct snd_kcontrol_new scarlett2_meter_ctl = {
Add #defines for SCARLETT2_USB_* needed for firmware upgrade: reboot, info-flash, info-segment, erase-segment, get-erase, and write-segment.
Signed-off-by: Geoffrey D. Bennett g@b4.vu --- sound/usb/mixer_scarlett2.c | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-)
diff --git a/sound/usb/mixer_scarlett2.c b/sound/usb/mixer_scarlett2.c index 73f5badceb19..129b9c97871a 100644 --- a/sound/usb/mixer_scarlett2.c +++ b/sound/usb/mixer_scarlett2.c @@ -1137,17 +1137,23 @@ static int scarlett2_get_port_start_num( #define SCARLETT2_USB_CMD_REQ 2 #define SCARLETT2_USB_CMD_RESP 3
-#define SCARLETT2_USB_INIT_1 0x00000000 -#define SCARLETT2_USB_INIT_2 0x00000002 -#define SCARLETT2_USB_GET_METER 0x00001001 -#define SCARLETT2_USB_GET_MIX 0x00002001 -#define SCARLETT2_USB_SET_MIX 0x00002002 -#define SCARLETT2_USB_GET_MUX 0x00003001 -#define SCARLETT2_USB_SET_MUX 0x00003002 -#define SCARLETT2_USB_GET_SYNC 0x00006004 -#define SCARLETT2_USB_GET_DATA 0x00800000 -#define SCARLETT2_USB_SET_DATA 0x00800001 -#define SCARLETT2_USB_DATA_CMD 0x00800002 +#define SCARLETT2_USB_INIT_1 0x00000000 +#define SCARLETT2_USB_INIT_2 0x00000002 +#define SCARLETT2_USB_REBOOT 0x00000003 +#define SCARLETT2_USB_GET_METER 0x00001001 +#define SCARLETT2_USB_GET_MIX 0x00002001 +#define SCARLETT2_USB_SET_MIX 0x00002002 +#define SCARLETT2_USB_GET_MUX 0x00003001 +#define SCARLETT2_USB_SET_MUX 0x00003002 +#define SCARLETT2_USB_INFO_FLASH 0x00004000 +#define SCARLETT2_USB_INFO_SEGMENT 0x00004001 +#define SCARLETT2_USB_ERASE_SEGMENT 0x00004002 +#define SCARLETT2_USB_GET_ERASE 0x00004003 +#define SCARLETT2_USB_WRITE_SEGMENT 0x00004004 +#define SCARLETT2_USB_GET_SYNC 0x00006004 +#define SCARLETT2_USB_GET_DATA 0x00800000 +#define SCARLETT2_USB_SET_DATA 0x00800001 +#define SCARLETT2_USB_DATA_CMD 0x00800002
#define SCARLETT2_USB_CONFIG_SAVE 6
Call SCARLETT2_USB_INFO_FLASH and SCARLETT2_USB_INFO_SEGMENT to find the App_Settings and App_Upgrade flash segment numbers, and store them in the scarlett2_data struct. These will be used later to implement reset to factory defaults and firmware upgrade functions.
Signed-off-by: Geoffrey D. Bennett g@b4.vu --- sound/usb/mixer_scarlett2.c | 103 ++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+)
diff --git a/sound/usb/mixer_scarlett2.c b/sound/usb/mixer_scarlett2.c index 129b9c97871a..34878f9f9a55 100644 --- a/sound/usb/mixer_scarlett2.c +++ b/sound/usb/mixer_scarlett2.c @@ -190,6 +190,11 @@ static const u16 scarlett2_mixer_values[SCARLETT2_MIXER_VALUE_COUNT] = { 16345 };
+/* Flash segments that we may manipulate */ +#define SCARLETT2_SEGMENT_ID_SETTINGS 0 +#define SCARLETT2_SEGMENT_ID_FIRMWARE 1 +#define SCARLETT2_SEGMENT_ID_COUNT 2 + /* Maximum number of analogue outputs */ #define SCARLETT2_ANALOGUE_MAX 10
@@ -429,6 +434,8 @@ struct scarlett2_data { int num_mux_srcs; int num_mux_dsts; u32 firmware_version; + u8 flash_segment_nums[SCARLETT2_SEGMENT_ID_COUNT]; + u8 flash_segment_blocks[SCARLETT2_SEGMENT_ID_COUNT]; u16 scarlett2_seq; u8 sync_updated; u8 vol_updated; @@ -1160,6 +1167,13 @@ static int scarlett2_get_port_start_num( #define SCARLETT2_USB_VOLUME_STATUS_OFFSET 0x31 #define SCARLETT2_USB_METER_LEVELS_GET_MAGIC 1
+#define SCARLETT2_FLASH_BLOCK_SIZE 4096 +#define SCARLETT2_SEGMENT_NUM_MIN 1 +#define SCARLETT2_SEGMENT_NUM_MAX 4 + +#define SCARLETT2_SEGMENT_SETTINGS_NAME "App_Settings" +#define SCARLETT2_SEGMENT_FIRMWARE_NAME "App_Upgrade" + /* volume status is read together (matches scarlett2_config_items[1]) */ struct scarlett2_usb_volume_status { /* dim/mute buttons */ @@ -4202,6 +4216,90 @@ static int scarlett2_usb_init(struct usb_mixer_interface *mixer) return 0; }
+/* Get the flash segment numbers for the App_Settings and App_Upgrade + * segments and put them in the private data + */ +static int scarlett2_get_flash_segment_nums(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + int err, count, i; + + struct { + __le32 size; + __le32 count; + u8 unknown[8]; + } __packed flash_info; + + struct { + __le32 size; + __le32 flags; + char name[16]; + } __packed segment_info; + + err = scarlett2_usb(mixer, SCARLETT2_USB_INFO_FLASH, + NULL, 0, + &flash_info, sizeof(flash_info)); + if (err < 0) + return err; + + count = le32_to_cpu(flash_info.count); + + /* sanity check count */ + if (count < SCARLETT2_SEGMENT_NUM_MIN || + count > SCARLETT2_SEGMENT_NUM_MAX + 1) { + usb_audio_err(mixer->chip, + "invalid flash segment count: %d\n", count); + return -EINVAL; + } + + for (i = 0; i < count; i++) { + __le32 segment_num_req = cpu_to_le32(i); + int flash_segment_id; + + err = scarlett2_usb(mixer, SCARLETT2_USB_INFO_SEGMENT, + &segment_num_req, sizeof(segment_num_req), + &segment_info, sizeof(segment_info)); + if (err < 0) { + usb_audio_err(mixer->chip, + "failed to get flash segment info %d: %d\n", + i, err); + return err; + } + + if (!strncmp(segment_info.name, + SCARLETT2_SEGMENT_SETTINGS_NAME, 16)) + flash_segment_id = SCARLETT2_SEGMENT_ID_SETTINGS; + else if (!strncmp(segment_info.name, + SCARLETT2_SEGMENT_FIRMWARE_NAME, 16)) + flash_segment_id = SCARLETT2_SEGMENT_ID_FIRMWARE; + else + continue; + + private->flash_segment_nums[flash_segment_id] = i; + private->flash_segment_blocks[flash_segment_id] = + le32_to_cpu(segment_info.size) / + SCARLETT2_FLASH_BLOCK_SIZE; + } + + /* segment 0 is App_Gold and we never want to touch that, so + * use 0 as the "not-found" value + */ + if (!private->flash_segment_nums[SCARLETT2_SEGMENT_ID_SETTINGS]) { + usb_audio_err(mixer->chip, + "failed to find flash segment %s\n", + SCARLETT2_SEGMENT_SETTINGS_NAME); + return -EINVAL; + } + if (!private->flash_segment_nums[SCARLETT2_SEGMENT_ID_FIRMWARE]) { + usb_audio_err(mixer->chip, + "failed to find flash segment %s\n", + SCARLETT2_SEGMENT_FIRMWARE_NAME); + return -EINVAL; + } + + return 0; +} + /* Read configuration from the interface on start */ static int scarlett2_read_configs(struct usb_mixer_interface *mixer) { @@ -4517,6 +4615,11 @@ static int snd_scarlett2_controls_create( if (err < 0) return err;
+ /* Get the upgrade & settings flash segment numbers */ + err = scarlett2_get_flash_segment_nums(mixer); + if (err < 0) + return err; + /* Add firmware version control */ err = scarlett2_add_firmware_version_ctl(mixer); if (err < 0)
Add skeleton hwdep/ioctl interface, beginning with SCARLETT2_IOCTL_PVERSION and SCARLETT2_IOCTL_REBOOT.
Signed-off-by: Geoffrey D. Bennett g@b4.vu --- MAINTAINERS | 1 + include/uapi/sound/scarlett2.h | 34 +++++++++++++++++ sound/usb/mixer_scarlett2.c | 67 +++++++++++++++++++++++++++++++++- 3 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 include/uapi/sound/scarlett2.h
diff --git a/MAINTAINERS b/MAINTAINERS index ae3f72f57854..80c65096538c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8279,6 +8279,7 @@ S: Maintained W: https://github.com/geoffreybennett/scarlett-gen2 B: https://github.com/geoffreybennett/scarlett-gen2/issues T: git https://github.com/geoffreybennett/scarlett-gen2.git +F: include/uapi/sound/scarlett2.h F: sound/usb/mixer_scarlett2.c
FORCEDETH GIGABIT ETHERNET DRIVER diff --git a/include/uapi/sound/scarlett2.h b/include/uapi/sound/scarlett2.h new file mode 100644 index 000000000000..ec0b7da335ff --- /dev/null +++ b/include/uapi/sound/scarlett2.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * Focusrite Scarlett 2 Protocol Driver for ALSA + * (including Scarlett 2nd Gen, 3rd Gen, Clarett USB, and Clarett+ + * series products) + * + * Copyright (c) 2023 by Geoffrey D. Bennett <g at b4.vu> + */ +#ifndef __UAPI_SOUND_SCARLETT2_H +#define __UAPI_SOUND_SCARLETT2_H + +#include <linux/types.h> +#include <linux/ioctl.h> + +#define SCARLETT2_HWDEP_MAJOR 1 +#define SCARLETT2_HWDEP_MINOR 0 +#define SCARLETT2_HWDEP_SUBMINOR 0 + +#define SCARLETT2_HWDEP_VERSION \ + ((SCARLETT2_HWDEP_MAJOR << 16) | \ + (SCARLETT2_HWDEP_MINOR << 8) | \ + SCARLETT2_HWDEP_SUBMINOR) + +#define SCARLETT2_HWDEP_VERSION_MAJOR(v) (((v) >> 16) & 0xFF) +#define SCARLETT2_HWDEP_VERSION_MINOR(v) (((v) >> 8) & 0xFF) +#define SCARLETT2_HWDEP_VERSION_SUBMINOR(v) ((v) & 0xFF) + +/* Get protocol version */ +#define SCARLETT2_IOCTL_PVERSION _IOR('S', 0x60, int) + +/* Reboot */ +#define SCARLETT2_IOCTL_REBOOT _IO('S', 0x61) + +#endif /* __UAPI_SOUND_SCARLETT2_H */ diff --git a/sound/usb/mixer_scarlett2.c b/sound/usb/mixer_scarlett2.c index 34878f9f9a55..c0c96d7c19fb 100644 --- a/sound/usb/mixer_scarlett2.c +++ b/sound/usb/mixer_scarlett2.c @@ -146,6 +146,9 @@
#include <sound/control.h> #include <sound/tlv.h> +#include <sound/hwdep.h> + +#include <uapi/sound/scarlett2.h>
#include "usbaudio.h" #include "mixer.h" @@ -1439,6 +1442,16 @@ static int scarlett2_usb( /* validate the response */
if (err != resp_buf_size) { + + /* ESHUTDOWN and EPROTO are valid responses to a + * reboot request + */ + if (cmd == SCARLETT2_USB_REBOOT && + (err == -ESHUTDOWN || err == -EPROTO)) { + err = 0; + goto unlock; + } + usb_audio_err( mixer->chip, "%s USB response result cmd %x was %d expected %zu\n", @@ -4697,6 +4710,49 @@ static int snd_scarlett2_controls_create( return 0; }
+/*** hwdep interface ***/ + +/* Reboot the device. */ +static int scarlett2_reboot(struct usb_mixer_interface *mixer) +{ + return scarlett2_usb(mixer, SCARLETT2_USB_REBOOT, NULL, 0, NULL, 0); +} + +static int scarlett2_hwdep_ioctl(struct snd_hwdep *hw, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct usb_mixer_interface *mixer = hw->private_data; + + switch (cmd) { + + case SCARLETT2_IOCTL_PVERSION: + return put_user(SCARLETT2_HWDEP_VERSION, + (int __user *)arg) ? -EFAULT : 0; + + case SCARLETT2_IOCTL_REBOOT: + return scarlett2_reboot(mixer); + + default: + return -ENOIOCTLCMD; + } +} + +static int scarlett2_hwdep_init(struct usb_mixer_interface *mixer) +{ + struct snd_hwdep *hw; + int err; + + err = snd_hwdep_new(mixer->chip->card, "Focusrite Control", 0, &hw); + if (err < 0) + return err; + + hw->private_data = mixer; + hw->exclusive = 1; + hw->ops.ioctl = scarlett2_hwdep_ioctl; + + return 0; +} + int snd_scarlett2_init(struct usb_mixer_interface *mixer) { struct snd_usb_audio *chip = mixer->chip; @@ -4738,11 +4794,20 @@ int snd_scarlett2_init(struct usb_mixer_interface *mixer) USB_ID_PRODUCT(chip->usb_id));
err = snd_scarlett2_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 = scarlett2_hwdep_init(mixer); + if (err < 0) + usb_audio_err(mixer->chip, + "Error creating %s hwdep device: %d", + entry->series_name, + err);
return err; }
Add ioctls: - SCARLETT2_IOCTL_SELECT_FLASH_SEGMENT - SCARLETT2_IOCTL_ERASE_FLASH_SEGMENT - SCARLETT2_IOCTL_GET_ERASE_PROGRESS
The settings or the firmware flash segment can be selected and then erased (asynchronous operation), and the erase progress can be monitored.
If the erase progress is not monitored, then subsequent hwdep operations will block until the erase is complete.
Once the erase is started, ALSA controls that communicate with the device will all return -EBUSY, and the device must be rebooted.
Signed-off-by: Geoffrey D. Bennett g@b4.vu --- include/uapi/sound/scarlett2.h | 20 ++ sound/usb/mixer_scarlett2.c | 428 ++++++++++++++++++++++++++++++++- 2 files changed, 442 insertions(+), 6 deletions(-)
diff --git a/include/uapi/sound/scarlett2.h b/include/uapi/sound/scarlett2.h index ec0b7da335ff..d0ff38ffa154 100644 --- a/include/uapi/sound/scarlett2.h +++ b/include/uapi/sound/scarlett2.h @@ -31,4 +31,24 @@ /* Reboot */ #define SCARLETT2_IOCTL_REBOOT _IO('S', 0x61)
+/* Select flash segment */ +#define SCARLETT2_SEGMENT_ID_SETTINGS 0 +#define SCARLETT2_SEGMENT_ID_FIRMWARE 1 +#define SCARLETT2_SEGMENT_ID_COUNT 2 + +#define SCARLETT2_IOCTL_SELECT_FLASH_SEGMENT _IOW('S', 0x62, int) + +/* Erase selected flash segment */ +#define SCARLETT2_IOCTL_ERASE_FLASH_SEGMENT _IO('S', 0x63) + +/* Get selected flash segment erase progress + * 1 through to num_blocks, or 255 for complete + */ +struct scarlett2_flash_segment_erase_progress { + unsigned char progress; + unsigned char num_blocks; +}; +#define SCARLETT2_IOCTL_GET_ERASE_PROGRESS \ + _IOR('S', 0x64, struct scarlett2_flash_segment_erase_progress) + #endif /* __UAPI_SOUND_SCARLETT2_H */ diff --git a/sound/usb/mixer_scarlett2.c b/sound/usb/mixer_scarlett2.c index c0c96d7c19fb..ca09d0cd0cae 100644 --- a/sound/usb/mixer_scarlett2.c +++ b/sound/usb/mixer_scarlett2.c @@ -193,11 +193,6 @@ static const u16 scarlett2_mixer_values[SCARLETT2_MIXER_VALUE_COUNT] = { 16345 };
-/* Flash segments that we may manipulate */ -#define SCARLETT2_SEGMENT_ID_SETTINGS 0 -#define SCARLETT2_SEGMENT_ID_FIRMWARE 1 -#define SCARLETT2_SEGMENT_ID_COUNT 2 - /* Maximum number of analogue outputs */ #define SCARLETT2_ANALOGUE_MAX 10
@@ -267,6 +262,13 @@ enum { SCARLETT2_DIM_MUTE_COUNT = 2, };
+/* Flash Write State */ +enum { + SCARLETT2_FLASH_WRITE_STATE_IDLE = 0, + SCARLETT2_FLASH_WRITE_STATE_SELECTED = 1, + SCARLETT2_FLASH_WRITE_STATE_ERASING = 2 +}; + static const char *const scarlett2_dim_mute_names[SCARLETT2_DIM_MUTE_COUNT] = { "Mute Playback Switch", "Dim Playback Switch" }; @@ -427,6 +429,9 @@ struct scarlett2_data { struct usb_mixer_interface *mixer; struct mutex usb_mutex; /* prevent sending concurrent USB requests */ struct mutex data_mutex; /* lock access to this data */ + u8 hwdep_in_use; + u8 selected_flash_segment_id; + u8 flash_write_state; struct delayed_work work; const struct scarlett2_device_info *info; const char *series_name; @@ -2137,6 +2142,11 @@ static int scarlett2_sync_ctl_get(struct snd_kcontrol *kctl,
mutex_lock(&private->data_mutex);
+ if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + if (private->sync_updated) { err = scarlett2_update_sync(mixer); if (err < 0) @@ -2233,6 +2243,11 @@ static int scarlett2_master_volume_ctl_get(struct snd_kcontrol *kctl,
mutex_lock(&private->data_mutex);
+ if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + if (private->vol_updated) { err = scarlett2_update_volumes(mixer); if (err < 0) @@ -2272,6 +2287,11 @@ static int scarlett2_volume_ctl_get(struct snd_kcontrol *kctl,
mutex_lock(&private->data_mutex);
+ if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + if (private->vol_updated) { err = scarlett2_update_volumes(mixer); if (err < 0) @@ -2295,6 +2315,11 @@ static int scarlett2_volume_ctl_put(struct snd_kcontrol *kctl,
mutex_lock(&private->data_mutex);
+ if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + oval = private->vol[index]; val = ucontrol->value.integer.value[0];
@@ -2352,6 +2377,11 @@ static int scarlett2_mute_ctl_get(struct snd_kcontrol *kctl,
mutex_lock(&private->data_mutex);
+ if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + if (private->vol_updated) { err = scarlett2_update_volumes(mixer); if (err < 0) @@ -2375,6 +2405,11 @@ static int scarlett2_mute_ctl_put(struct snd_kcontrol *kctl,
mutex_lock(&private->data_mutex);
+ if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + oval = private->mute_switch[index]; val = !!ucontrol->value.integer.value[0];
@@ -2514,6 +2549,11 @@ static int scarlett2_sw_hw_enum_ctl_put(struct snd_kcontrol *kctl,
mutex_lock(&private->data_mutex);
+ if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + oval = private->vol_sw_hw_switch[index]; val = !!ucontrol->value.enumerated.item[0];
@@ -2611,6 +2651,11 @@ static int scarlett2_level_enum_ctl_get(struct snd_kcontrol *kctl,
mutex_lock(&private->data_mutex);
+ if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + if (private->input_other_updated) { err = scarlett2_update_input_other(mixer); if (err < 0) @@ -2636,6 +2681,11 @@ static int scarlett2_level_enum_ctl_put(struct snd_kcontrol *kctl,
mutex_lock(&private->data_mutex);
+ if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + oval = private->level_switch[index]; val = !!ucontrol->value.enumerated.item[0];
@@ -2675,6 +2725,11 @@ static int scarlett2_pad_ctl_get(struct snd_kcontrol *kctl,
mutex_lock(&private->data_mutex);
+ if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + if (private->input_other_updated) { err = scarlett2_update_input_other(mixer); if (err < 0) @@ -2700,6 +2755,11 @@ static int scarlett2_pad_ctl_put(struct snd_kcontrol *kctl,
mutex_lock(&private->data_mutex);
+ if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + oval = private->pad_switch[index]; val = !!ucontrol->value.integer.value[0];
@@ -2739,6 +2799,11 @@ static int scarlett2_air_ctl_get(struct snd_kcontrol *kctl,
mutex_lock(&private->data_mutex);
+ if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + if (private->input_other_updated) { err = scarlett2_update_input_other(mixer); if (err < 0) @@ -2763,6 +2828,11 @@ static int scarlett2_air_ctl_put(struct snd_kcontrol *kctl,
mutex_lock(&private->data_mutex);
+ if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + oval = private->air_switch[index]; val = !!ucontrol->value.integer.value[0];
@@ -2802,6 +2872,11 @@ static int scarlett2_phantom_ctl_get(struct snd_kcontrol *kctl,
mutex_lock(&private->data_mutex);
+ if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + if (private->input_other_updated) { err = scarlett2_update_input_other(mixer); if (err < 0) @@ -2827,6 +2902,11 @@ static int scarlett2_phantom_ctl_put(struct snd_kcontrol *kctl,
mutex_lock(&private->data_mutex);
+ if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + oval = private->phantom_switch[index]; val = !!ucontrol->value.integer.value[0];
@@ -2878,6 +2958,11 @@ static int scarlett2_phantom_persistence_ctl_put(
mutex_lock(&private->data_mutex);
+ if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + oval = private->phantom_persistence; val = !!ucontrol->value.integer.value[0];
@@ -2988,6 +3073,11 @@ static int scarlett2_direct_monitor_ctl_get(
mutex_lock(&private->data_mutex);
+ if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + if (private->monitor_other_updated) { err = scarlett2_update_monitor_other(mixer); if (err < 0) @@ -3012,6 +3102,11 @@ static int scarlett2_direct_monitor_ctl_put(
mutex_lock(&private->data_mutex);
+ if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + oval = private->direct_monitor_switch; val = min(ucontrol->value.enumerated.item[0], 2U);
@@ -3101,6 +3196,11 @@ static int scarlett2_speaker_switch_enum_ctl_get(
mutex_lock(&private->data_mutex);
+ if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + if (private->monitor_other_updated) { err = scarlett2_update_monitor_other(mixer); if (err < 0) @@ -3181,6 +3281,11 @@ static int scarlett2_speaker_switch_enum_ctl_put(
mutex_lock(&private->data_mutex);
+ if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + oval = private->speaker_switching_switch; val = min(ucontrol->value.enumerated.item[0], 2U);
@@ -3262,6 +3367,11 @@ static int scarlett2_talkback_enum_ctl_get(
mutex_lock(&private->data_mutex);
+ if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + if (private->monitor_other_updated) { err = scarlett2_update_monitor_other(mixer); if (err < 0) @@ -3285,6 +3395,11 @@ static int scarlett2_talkback_enum_ctl_put(
mutex_lock(&private->data_mutex);
+ if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + oval = private->talkback_switch; val = min(ucontrol->value.enumerated.item[0], 2U);
@@ -3349,6 +3464,11 @@ static int scarlett2_talkback_map_ctl_put(
mutex_lock(&private->data_mutex);
+ if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + oval = private->talkback_map[index]; val = !!ucontrol->value.integer.value[0];
@@ -3423,6 +3543,11 @@ static int scarlett2_dim_mute_ctl_get(struct snd_kcontrol *kctl,
mutex_lock(&private->data_mutex);
+ if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + if (private->vol_updated) { err = scarlett2_update_volumes(mixer); if (err < 0) @@ -3451,6 +3576,11 @@ static int scarlett2_dim_mute_ctl_put(struct snd_kcontrol *kctl,
mutex_lock(&private->data_mutex);
+ if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + oval = private->dim_mute[index]; val = !!ucontrol->value.integer.value[0];
@@ -3695,6 +3825,11 @@ static int scarlett2_mixer_ctl_put(struct snd_kcontrol *kctl,
mutex_lock(&private->data_mutex);
+ if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + oval = private->mix[index]; val = clamp(ucontrol->value.integer.value[0], 0L, (long)SCARLETT2_MIXER_MAX_VALUE); @@ -3808,6 +3943,11 @@ static int scarlett2_mux_src_enum_ctl_get(struct snd_kcontrol *kctl,
mutex_lock(&private->data_mutex);
+ if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + if (private->mux_updated) { err = scarlett2_usb_get_mux(mixer); if (err < 0) @@ -3831,6 +3971,11 @@ static int scarlett2_mux_src_enum_ctl_put(struct snd_kcontrol *kctl,
mutex_lock(&private->data_mutex);
+ if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + oval = private->mux[index]; val = min(ucontrol->value.enumerated.item[0], private->num_mux_srcs - 1U); @@ -3915,6 +4060,11 @@ static int scarlett2_meter_ctl_get(struct snd_kcontrol *kctl,
mutex_lock(&private->data_mutex);
+ if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + err = scarlett2_usb_get_meter_levels(elem->head.mixer, elem->channels, meter_levels); if (err < 0) @@ -3983,6 +4133,11 @@ static int scarlett2_msd_ctl_put(struct snd_kcontrol *kctl,
mutex_lock(&private->data_mutex);
+ if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + oval = private->msd_switch; val = !!ucontrol->value.integer.value[0];
@@ -4050,6 +4205,11 @@ static int scarlett2_standalone_ctl_put(struct snd_kcontrol *kctl,
mutex_lock(&private->data_mutex);
+ if (private->hwdep_in_use) { + err = -EBUSY; + goto unlock; + } + oval = private->standalone_switch; val = !!ucontrol->value.integer.value[0];
@@ -4712,12 +4872,241 @@ static int snd_scarlett2_controls_create(
/*** hwdep interface ***/
-/* Reboot the device. */ +/* Set private->hwdep_in_use; prevents access to the ALSA controls + * while doing a config erase/firmware upgrade. + */ +static void scarlett2_lock(struct scarlett2_data *private) +{ + mutex_lock(&private->data_mutex); + private->hwdep_in_use = 1; + mutex_unlock(&private->data_mutex); +} + +/* Call SCARLETT2_USB_GET_ERASE to get the erase progress */ +static int scarlett2_get_erase_progress(struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + int segment_id, segment_num, err; + u8 erase_resp; + + struct { + __le32 segment_num; + __le32 pad; + } __packed erase_req; + + segment_id = private->selected_flash_segment_id; + segment_num = private->flash_segment_nums[segment_id]; + + if (segment_num < SCARLETT2_SEGMENT_NUM_MIN || + segment_num > SCARLETT2_SEGMENT_NUM_MAX) + return -EFAULT; + + /* Send the erase progress request */ + erase_req.segment_num = cpu_to_le32(segment_num); + erase_req.pad = 0; + + err = scarlett2_usb(mixer, SCARLETT2_USB_GET_ERASE, + &erase_req, sizeof(erase_req), + &erase_resp, sizeof(erase_resp)); + if (err < 0) + return err; + + return erase_resp; +} + +/* Repeatedly call scarlett2_get_erase_progress() until it returns + * 0xff (erase complete) or we've waited 10 seconds (it usually takes + * <3 seconds). + */ +static int scarlett2_wait_for_erase(struct usb_mixer_interface *mixer) +{ + int i, err; + + for (i = 0; i < 100; i++) { + err = scarlett2_get_erase_progress(mixer); + if (err < 0) + return err; + + if (err == 0xff) + return 0; + + msleep(100); + } + + return -ETIMEDOUT; +} + +/* Reboot the device; wait for the erase to complete if one is in + * progress. + */ static int scarlett2_reboot(struct usb_mixer_interface *mixer) { + struct scarlett2_data *private = mixer->private_data; + + if (private->flash_write_state == + SCARLETT2_FLASH_WRITE_STATE_ERASING) { + int err = scarlett2_wait_for_erase(mixer); + + if (err < 0) + return err; + } + return scarlett2_usb(mixer, SCARLETT2_USB_REBOOT, NULL, 0, NULL, 0); }
+/* Select a flash segment for erasing (and possibly writing to) */ +static int scarlett2_ioctl_select_flash_segment( + struct usb_mixer_interface *mixer, + unsigned long arg) +{ + struct scarlett2_data *private = mixer->private_data; + int segment_id, segment_num; + + if (get_user(segment_id, (int __user *)arg)) + return -EFAULT; + + /* Check the segment ID and segment number */ + if (segment_id < 0 || segment_id >= SCARLETT2_SEGMENT_ID_COUNT) + return -EINVAL; + + segment_num = private->flash_segment_nums[segment_id]; + if (segment_num < SCARLETT2_SEGMENT_NUM_MIN || + segment_num > SCARLETT2_SEGMENT_NUM_MAX) { + usb_audio_err(mixer->chip, + "%s: invalid segment number %d\n", + __func__, segment_id); + return -EFAULT; + } + + /* If erasing, wait for it to complete */ + if (private->flash_write_state == SCARLETT2_FLASH_WRITE_STATE_ERASING) { + int err = scarlett2_wait_for_erase(mixer); + + if (err < 0) + return err; + } + + /* Save the selected segment ID and set the state to SELECTED */ + private->selected_flash_segment_id = segment_id; + private->flash_write_state = SCARLETT2_FLASH_WRITE_STATE_SELECTED; + + return 0; +} + +/* Erase the previously-selected flash segment */ +static int scarlett2_ioctl_erase_flash_segment( + struct usb_mixer_interface *mixer) +{ + struct scarlett2_data *private = mixer->private_data; + int segment_id, segment_num, err; + + struct { + __le32 segment_num; + __le32 pad; + } __packed erase_req; + + if (private->flash_write_state != SCARLETT2_FLASH_WRITE_STATE_SELECTED) + return -EINVAL; + + segment_id = private->selected_flash_segment_id; + segment_num = private->flash_segment_nums[segment_id]; + + if (segment_num < SCARLETT2_SEGMENT_NUM_MIN || + segment_num > SCARLETT2_SEGMENT_NUM_MAX) + return -EFAULT; + + /* Prevent access to ALSA controls that access the device from + * here on + */ + scarlett2_lock(private); + + /* Send the erase request */ + erase_req.segment_num = cpu_to_le32(segment_num); + erase_req.pad = 0; + + err = scarlett2_usb(mixer, SCARLETT2_USB_ERASE_SEGMENT, + &erase_req, sizeof(erase_req), + NULL, 0); + if (err < 0) + return err; + + /* On success, change the state from SELECTED to ERASING */ + private->flash_write_state = SCARLETT2_FLASH_WRITE_STATE_ERASING; + + return 0; +} + +/* Get the erase progress from the device */ +static int scarlett2_ioctl_get_erase_progress( + struct usb_mixer_interface *mixer, + unsigned long arg) +{ + struct scarlett2_data *private = mixer->private_data; + struct scarlett2_flash_segment_erase_progress progress; + int segment_id, segment_num, err; + u8 erase_resp; + + struct { + __le32 segment_num; + __le32 pad; + } __packed erase_req; + + /* Check that we're erasing */ + if (private->flash_write_state != SCARLETT2_FLASH_WRITE_STATE_ERASING) + return -EINVAL; + + segment_id = private->selected_flash_segment_id; + segment_num = private->flash_segment_nums[segment_id]; + + if (segment_num < SCARLETT2_SEGMENT_NUM_MIN || + segment_num > SCARLETT2_SEGMENT_NUM_MAX) + return -EFAULT; + + /* Send the erase progress request */ + erase_req.segment_num = cpu_to_le32(segment_num); + erase_req.pad = 0; + + err = scarlett2_usb(mixer, SCARLETT2_USB_GET_ERASE, + &erase_req, sizeof(erase_req), + &erase_resp, sizeof(erase_resp)); + if (err < 0) + return err; + + progress.progress = erase_resp; + progress.num_blocks = private->flash_segment_blocks[segment_id]; + + if (copy_to_user((void __user *)arg, &progress, sizeof(progress))) + return -EFAULT; + + /* If the erase is complete, change the state from ERASING to + * IDLE. + */ + if (progress.progress == 0xff) + private->flash_write_state = SCARLETT2_FLASH_WRITE_STATE_IDLE; + + return 0; +} + +static int scarlett2_hwdep_open(struct snd_hwdep *hw, struct file *file) +{ + struct usb_mixer_interface *mixer = hw->private_data; + struct scarlett2_data *private = mixer->private_data; + + /* If erasing, wait for it to complete */ + if (private->flash_write_state == + SCARLETT2_FLASH_WRITE_STATE_ERASING) { + int err = scarlett2_wait_for_erase(mixer); + + if (err < 0) + return err; + } + + /* Set the state to IDLE */ + private->flash_write_state = SCARLETT2_FLASH_WRITE_STATE_IDLE; + + return 0; +} + static int scarlett2_hwdep_ioctl(struct snd_hwdep *hw, struct file *file, unsigned int cmd, unsigned long arg) { @@ -4732,11 +5121,36 @@ static int scarlett2_hwdep_ioctl(struct snd_hwdep *hw, struct file *file, case SCARLETT2_IOCTL_REBOOT: return scarlett2_reboot(mixer);
+ case SCARLETT2_IOCTL_SELECT_FLASH_SEGMENT: + return scarlett2_ioctl_select_flash_segment(mixer, arg); + + case SCARLETT2_IOCTL_ERASE_FLASH_SEGMENT: + return scarlett2_ioctl_erase_flash_segment(mixer); + + case SCARLETT2_IOCTL_GET_ERASE_PROGRESS: + return scarlett2_ioctl_get_erase_progress(mixer, arg); + default: return -ENOIOCTLCMD; } }
+static int scarlett2_hwdep_release(struct snd_hwdep *hw, struct file *file) +{ + struct usb_mixer_interface *mixer = hw->private_data; + struct scarlett2_data *private = mixer->private_data; + + /* Return from the SELECTED or WRITE state to IDLE. + * The ERASING state is left as-is, and checked on next open. + */ + if (private && + private->hwdep_in_use && + private->flash_write_state != SCARLETT2_FLASH_WRITE_STATE_ERASING) + private->flash_write_state = SCARLETT2_FLASH_WRITE_STATE_IDLE; + + return 0; +} + static int scarlett2_hwdep_init(struct usb_mixer_interface *mixer) { struct snd_hwdep *hw; @@ -4748,7 +5162,9 @@ static int scarlett2_hwdep_init(struct usb_mixer_interface *mixer)
hw->private_data = mixer; hw->exclusive = 1; + hw->ops.open = scarlett2_hwdep_open; hw->ops.ioctl = scarlett2_hwdep_ioctl; + hw->ops.release = scarlett2_hwdep_release;
return 0; }
Add ops.write to the hwdep interface. Once the upgrade firmware flash segment has been erased, writes to the hwdep fd are permitted, and translated to SCARLETT2_USB_WRITE_SEGMENT commands to the device.
Signed-off-by: Geoffrey D. Bennett g@b4.vu --- sound/usb/mixer_scarlett2.c | 96 +++++++++++++++++++++++++++++++++++-- 1 file changed, 93 insertions(+), 3 deletions(-)
diff --git a/sound/usb/mixer_scarlett2.c b/sound/usb/mixer_scarlett2.c index ca09d0cd0cae..f1337a379833 100644 --- a/sound/usb/mixer_scarlett2.c +++ b/sound/usb/mixer_scarlett2.c @@ -266,7 +266,8 @@ enum { enum { SCARLETT2_FLASH_WRITE_STATE_IDLE = 0, SCARLETT2_FLASH_WRITE_STATE_SELECTED = 1, - SCARLETT2_FLASH_WRITE_STATE_ERASING = 2 + SCARLETT2_FLASH_WRITE_STATE_ERASING = 2, + SCARLETT2_FLASH_WRITE_STATE_WRITE = 3 };
static const char *const scarlett2_dim_mute_names[SCARLETT2_DIM_MUTE_COUNT] = { @@ -1176,6 +1177,7 @@ static int scarlett2_get_port_start_num( #define SCARLETT2_USB_METER_LEVELS_GET_MAGIC 1
#define SCARLETT2_FLASH_BLOCK_SIZE 4096 +#define SCARLETT2_FLASH_WRITE_MAX 1024 #define SCARLETT2_SEGMENT_NUM_MIN 1 #define SCARLETT2_SEGMENT_NUM_MAX 4
@@ -5079,10 +5081,10 @@ static int scarlett2_ioctl_get_erase_progress( return -EFAULT;
/* If the erase is complete, change the state from ERASING to - * IDLE. + * WRITE. */ if (progress.progress == 0xff) - private->flash_write_state = SCARLETT2_FLASH_WRITE_STATE_IDLE; + private->flash_write_state = SCARLETT2_FLASH_WRITE_STATE_WRITE;
return 0; } @@ -5135,6 +5137,93 @@ static int scarlett2_hwdep_ioctl(struct snd_hwdep *hw, struct file *file, } }
+static long scarlett2_hwdep_write(struct snd_hwdep *hw, + const char __user *buf, + long count, loff_t *offset) +{ + struct usb_mixer_interface *mixer = hw->private_data; + struct scarlett2_data *private = mixer->private_data; + int segment_id, segment_num, err, len; + int flash_size; + + /* SCARLETT2_USB_WRITE_SEGMENT request data */ + struct { + __le32 segment_num; + __le32 offset; + __le32 pad; + u8 data[]; + } __packed *req; + + /* Calculate the maximum permitted in data[] */ + const size_t max_data_size = SCARLETT2_FLASH_WRITE_MAX - + offsetof(typeof(*req), data); + + /* If erasing, wait for it to complete */ + if (private->flash_write_state == + SCARLETT2_FLASH_WRITE_STATE_ERASING) { + err = scarlett2_wait_for_erase(mixer); + if (err < 0) + return err; + private->flash_write_state = SCARLETT2_FLASH_WRITE_STATE_WRITE; + + /* Check that an erase has been done & completed */ + } else if (private->flash_write_state != + SCARLETT2_FLASH_WRITE_STATE_WRITE) { + return -EINVAL; + } + + /* Check that we're writing to the upgrade firmware */ + segment_id = private->selected_flash_segment_id; + if (segment_id != SCARLETT2_SEGMENT_ID_FIRMWARE) + return -EINVAL; + + segment_num = private->flash_segment_nums[segment_id]; + if (segment_num < SCARLETT2_SEGMENT_NUM_MIN || + segment_num > SCARLETT2_SEGMENT_NUM_MAX) + return -EFAULT; + + /* Validate the offset and count */ + flash_size = private->flash_segment_blocks[segment_id] * + SCARLETT2_FLASH_BLOCK_SIZE; + + if (count < 0 || *offset < 0 || *offset + count >= flash_size) + return -EINVAL; + + if (!count) + return 0; + + /* Limit the *req size to SCARLETT2_FLASH_WRITE_MAX */ + if (count > max_data_size) + count = max_data_size; + + /* Create and send the request */ + len = struct_size(req, data, count); + req = kzalloc(len, GFP_KERNEL); + if (!req) + return -ENOMEM; + + req->segment_num = cpu_to_le32(segment_num); + req->offset = cpu_to_le32(*offset); + req->pad = 0; + + if (copy_from_user(req->data, buf, count)) { + err = -EFAULT; + goto error; + } + + err = scarlett2_usb(mixer, SCARLETT2_USB_WRITE_SEGMENT, + req, len, NULL, 0); + if (err < 0) + goto error; + + *offset += count; + +error: + kfree(req); + + return count; +} + static int scarlett2_hwdep_release(struct snd_hwdep *hw, struct file *file) { struct usb_mixer_interface *mixer = hw->private_data; @@ -5164,6 +5253,7 @@ static int scarlett2_hwdep_init(struct usb_mixer_interface *mixer) hw->exclusive = 1; hw->ops.open = scarlett2_hwdep_open; hw->ops.ioctl = scarlett2_hwdep_ioctl; + hw->ops.write = scarlett2_hwdep_write; hw->ops.release = scarlett2_hwdep_release;
return 0;
Add ops.write to the hwdep interface. Once the upgrade firmware flash segment has been erased, writes to the hwdep fd are permitted, and translated to SCARLETT2_USB_WRITE_SEGMENT commands to the device.
Signed-off-by: Geoffrey D. Bennett g@b4.vu ---
Hi Takashi,
I noticed that scarlett2_hwdep_write() was returning the wrong value for errors encountered after the kzalloc(). Since I don't think you've taken this set of patches yet, can you replace the previous patch 11 with this version two? Sorry, I don't know if you'd prefer I resend all 11 patches but I erred on the side of fewer emails. Let me know if you would like me to do different this time or next time.
Thanks, Geoffrey.
sound/usb/mixer_scarlett2.c | 96 +++++++++++++++++++++++++++++++++++-- 1 file changed, 93 insertions(+), 3 deletions(-)
diff --git a/sound/usb/mixer_scarlett2.c b/sound/usb/mixer_scarlett2.c index ca09d0cd0cae..1f3b07bd7b3f 100644 --- a/sound/usb/mixer_scarlett2.c +++ b/sound/usb/mixer_scarlett2.c @@ -266,7 +266,8 @@ enum { enum { SCARLETT2_FLASH_WRITE_STATE_IDLE = 0, SCARLETT2_FLASH_WRITE_STATE_SELECTED = 1, - SCARLETT2_FLASH_WRITE_STATE_ERASING = 2 + SCARLETT2_FLASH_WRITE_STATE_ERASING = 2, + SCARLETT2_FLASH_WRITE_STATE_WRITE = 3 };
static const char *const scarlett2_dim_mute_names[SCARLETT2_DIM_MUTE_COUNT] = { @@ -1176,6 +1177,7 @@ static int scarlett2_get_port_start_num( #define SCARLETT2_USB_METER_LEVELS_GET_MAGIC 1
#define SCARLETT2_FLASH_BLOCK_SIZE 4096 +#define SCARLETT2_FLASH_WRITE_MAX 1024 #define SCARLETT2_SEGMENT_NUM_MIN 1 #define SCARLETT2_SEGMENT_NUM_MAX 4
@@ -5079,10 +5081,10 @@ static int scarlett2_ioctl_get_erase_progress( return -EFAULT;
/* If the erase is complete, change the state from ERASING to - * IDLE. + * WRITE. */ if (progress.progress == 0xff) - private->flash_write_state = SCARLETT2_FLASH_WRITE_STATE_IDLE; + private->flash_write_state = SCARLETT2_FLASH_WRITE_STATE_WRITE;
return 0; } @@ -5135,6 +5137,93 @@ static int scarlett2_hwdep_ioctl(struct snd_hwdep *hw, struct file *file, } }
+static long scarlett2_hwdep_write(struct snd_hwdep *hw, + const char __user *buf, + long count, loff_t *offset) +{ + struct usb_mixer_interface *mixer = hw->private_data; + struct scarlett2_data *private = mixer->private_data; + int segment_id, segment_num, err, len; + int flash_size; + + /* SCARLETT2_USB_WRITE_SEGMENT request data */ + struct { + __le32 segment_num; + __le32 offset; + __le32 pad; + u8 data[]; + } __packed *req; + + /* Calculate the maximum permitted in data[] */ + const size_t max_data_size = SCARLETT2_FLASH_WRITE_MAX - + offsetof(typeof(*req), data); + + /* If erasing, wait for it to complete */ + if (private->flash_write_state == + SCARLETT2_FLASH_WRITE_STATE_ERASING) { + err = scarlett2_wait_for_erase(mixer); + if (err < 0) + return err; + private->flash_write_state = SCARLETT2_FLASH_WRITE_STATE_WRITE; + + /* Check that an erase has been done & completed */ + } else if (private->flash_write_state != + SCARLETT2_FLASH_WRITE_STATE_WRITE) { + return -EINVAL; + } + + /* Check that we're writing to the upgrade firmware */ + segment_id = private->selected_flash_segment_id; + if (segment_id != SCARLETT2_SEGMENT_ID_FIRMWARE) + return -EINVAL; + + segment_num = private->flash_segment_nums[segment_id]; + if (segment_num < SCARLETT2_SEGMENT_NUM_MIN || + segment_num > SCARLETT2_SEGMENT_NUM_MAX) + return -EFAULT; + + /* Validate the offset and count */ + flash_size = private->flash_segment_blocks[segment_id] * + SCARLETT2_FLASH_BLOCK_SIZE; + + if (count < 0 || *offset < 0 || *offset + count >= flash_size) + return -EINVAL; + + if (!count) + return 0; + + /* Limit the *req size to SCARLETT2_FLASH_WRITE_MAX */ + if (count > max_data_size) + count = max_data_size; + + /* Create and send the request */ + len = struct_size(req, data, count); + req = kzalloc(len, GFP_KERNEL); + if (!req) + return -ENOMEM; + + req->segment_num = cpu_to_le32(segment_num); + req->offset = cpu_to_le32(*offset); + req->pad = 0; + + if (copy_from_user(req->data, buf, count)) { + err = -EFAULT; + goto error; + } + + err = scarlett2_usb(mixer, SCARLETT2_USB_WRITE_SEGMENT, + req, len, NULL, 0); + if (err < 0) + goto error; + + *offset += count; + err = count; + +error: + kfree(req); + return err; +} + static int scarlett2_hwdep_release(struct snd_hwdep *hw, struct file *file) { struct usb_mixer_interface *mixer = hw->private_data; @@ -5164,6 +5253,7 @@ static int scarlett2_hwdep_init(struct usb_mixer_interface *mixer) hw->exclusive = 1; hw->ops.open = scarlett2_hwdep_open; hw->ops.ioctl = scarlett2_hwdep_ioctl; + hw->ops.write = scarlett2_hwdep_write; hw->ops.release = scarlett2_hwdep_release;
return 0;
On Tue, 19 Dec 2023 18:36:04 +0100, Geoffrey D. Bennett wrote:
Hi Takashi,
11 patches to improve error handling and add firmware upgrade support to the scarlett2 driver. The notes below incorporate your feedback & include answers to your questions in response to my previous RFC v2 email.
Patch 1 adds GitHub links for the repo where I share this code before submission here, and for issue reporting.
Patches 2-5 add missing error/bounds checks.
Patch 6 adds a missing lock.
Patches 7-11 add support for firmware upgrades.
The usage of the hwdep interface is as per my previous proposal:
- ioctl pversion to check the protocol version
- ioctl select_flash_segment SCARLETT2_SEGMENT_ID_SETTINGS
- ioctl erase_flash_segment
- ioctl get_erase_progress (optional)
- ioctl select_flash_segment SCARLETT2_SEGMENT_ID_FIRMWARE
- ioctl erase_flash_segment
- ioctl get_erase_progress (optional)
- write() the firmware
- ioctl reboot
Notes on the hwdep interface:
Conflicting mixer operations are denied with EBUSY.
The hwdep interface is marked as exclusive so there can't be conflicting hwdep operations.
Invalid sequences of operations (e.g. erase before select, write before erase) return an error.
The erase operation is asynchronous, and the progress can optionally be monitored by calling get_erase_progress.
If the erase progress is not monitored then subsequent hwdep operations will wait until the erase is complete.
The write operation is synchronous, but <1KB can be written per call, and it returns very quickly. On the user-side it looks like this with error checking omitted:
while (offset < len) offset += snd_hwdep_write(hwdep, buf + offset, len - offset);
By using a subset of the firmware-upgrade steps, other useful high-level operations can be performed: reset-to-factory-defaults, reset-to-factory-firmware, or just reboot.
I considered combining the select and erase operations, but that would prevent a future read-flash operation if that ever become possible.
I considered separate one-shot ioctls for reset-to-factory, etc., but that prevents providing progress feedback.
In case the purpose of the other firmware segments is discovered in the future, this implementation would be able to support erase/write/read of them with minimal changes.
Here is a user-space implementation of all of the above: https://github.com/geoffreybennett/scarlett2
This has been tested by me on every supported Scarlett 2nd, 3rd, 4th Gen and Clarett+ device, and tested by a handful of others on their Scarlett 4th Gen devices.
Thanks, Geoffrey.
Geoffrey D. Bennett (11): ALSA: scarlett2: Update maintainer info ALSA: scarlett2: Add missing error check to scarlett2_config_save() ALSA: scarlett2: Add missing error check to scarlett2_usb_set_config() ALSA: scarlett2: Add missing error checks to *_ctl_get() ALSA: scarlett2: Add clamp() in scarlett2_mixer_ctl_put() ALSA: scarlett2: Add missing mutex lock around get meter levels ALSA: scarlett2: Add #defines for firmware upgrade ALSA: scarlett2: Retrieve useful flash segment numbers ALSA: scarlett2: Add skeleton hwdep/ioctl interface ALSA: scarlett2: Add ioctl commands to erase flash segments ALSA: scarlett2: Add support for uploading new firmware
Now all patches have been merged to topic/scarlett2 branch (merged to for-next branch for 6.8).
thanks,
Takashi
participants (2)
-
Geoffrey D. Bennett
-
Takashi Iwai