ALSA: scarlett2: Add support for uploading new firmware
authorGeoffrey D. Bennett <g@b4.vu>
Fri, 29 Dec 2023 12:19:23 +0000 (22:49 +1030)
committerTakashi Iwai <tiwai@suse.de>
Fri, 29 Dec 2023 14:39:27 +0000 (15:39 +0100)
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>
Link: https://lore.kernel.org/r/ZY65S0ojShSNSeRQ@m.b4.vu
Signed-off-by: Takashi Iwai <tiwai@suse.de>
sound/usb/mixer_scarlett2.c

index 2d60fa607bd8b0abb417feb3da38aeb141efc9a5..6a9e8e2e246f795b2af9681cc5b08ed80a4bc02a 100644 (file)
@@ -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;