On Mon, 2015-06-22 at 11:43 +0200, Johan Hovold wrote:
> On Thu, Jun 18, 2015 at 06:43:35AM -0500, Peter E. Berger wrote:
> > From: "Peter E. Berger" <[email protected]>
> > 
> > The io_ti driver fails to download firmware to Edgeport
> > devices such as the EP/416, even when the on-disk firmware image
> > (/lib/firmware/edgeport/down3.bin) is more current than the version
> > on the EP/416.  The current download code is broken in a few ways.
> > Notably it mis-uses global variables OperationalMajorVersion and
> > OperationalMinorVersion (reading their values before they've been
> > properly initialized and subsequently initializing them multiple times
> > without synchronization).  This patch drops the global variables and
> > replaces the redundant calls to request_firmware()/release_firmware()
> > in download_fw() with a single call pair in edge_startup(); the firmware
> > image pointer is then passed to download_fw() and build_i2c_fw_hdr().
> >
> > Also, the firmware has a "build_number" field, though it apparently is
> > unused (according to observations of the three firmware images I have
> > seen and confirmed by Digi Tech Support).  This comment describes its
> > structure, in case it is populated in a future release.
> >
> > Signed-off-by: Peter E. Berger <[email protected]>
> > ---
> >  drivers/usb/serial/io_ti.c | 100 
> > +++++++++++++++++++++++----------------------
> >  1 file changed, 51 insertions(+), 49 deletions(-)
> > 
> > diff --git a/drivers/usb/serial/io_ti.c b/drivers/usb/serial/io_ti.c
> > index 69378a7..c76820b 100644
> > --- a/drivers/usb/serial/io_ti.c
> > +++ b/drivers/usb/serial/io_ti.c
> > @@ -101,6 +101,7 @@ struct edgeport_serial {
> >     struct mutex es_lock;
> >     int num_ports_open;
> >     struct usb_serial *serial;
> > +   int fw_version;
> >  };
> >  
> >  
> > @@ -187,10 +188,6 @@ static const struct usb_device_id id_table_combined[] 
> > = {
> >  
> >  MODULE_DEVICE_TABLE(usb, id_table_combined);
> >  
> > -static unsigned char OperationalMajorVersion;
> > -static unsigned char OperationalMinorVersion;
> > -static unsigned short OperationalBuildNumber;
> > -
> >  static int closing_wait = EDGE_CLOSING_WAIT;
> >  static bool ignore_cpu_rev;
> >  static int default_uart_mode;              /* RS232 */
> > @@ -751,18 +748,18 @@ exit:
> >  }
> >  
> >  /* Build firmware header used for firmware update */
> > -static int build_i2c_fw_hdr(__u8 *header, struct device *dev)
> > +static int build_i2c_fw_hdr(u8 *header, struct device *dev,
> > +           const struct firmware *fw)
> >  {
> >     __u8 *buffer;
> >     int buffer_size;
> >     int i;
> > -   int err;
> >     __u8 cs = 0;
> >     struct ti_i2c_desc *i2c_header;
> >     struct ti_i2c_image_header *img_header;
> >     struct ti_i2c_firmware_rec *firmware_rec;
> > -   const struct firmware *fw;
> > -   const char *fw_name = "edgeport/down3.bin";
> > +   u8 fw_major_version = fw->data[0];
> > +   u8 fw_minor_version = fw->data[1];
> >  
> >     /* In order to update the I2C firmware we must change the type 2 record
> >      * to type 0xF2.  This will force the UMP to come up in Boot Mode.
> > @@ -785,24 +782,11 @@ static int build_i2c_fw_hdr(__u8 *header, struct 
> > device *dev)
> >     // Set entire image of 0xffs
> >     memset(buffer, 0xff, buffer_size);
> >  
> > -   err = request_firmware(&fw, fw_name, dev);
> > -   if (err) {
> > -           dev_err(dev, "Failed to load image \"%s\" err %d\n",
> > -                   fw_name, err);
> > -           kfree(buffer);
> > -           return err;
> > -   }
> > -
> > -   /* Save Download Version Number */
> > -   OperationalMajorVersion = fw->data[0];
> > -   OperationalMinorVersion = fw->data[1];
> > -   OperationalBuildNumber = fw->data[2] | (fw->data[3] << 8);
> > -
> >     /* Copy version number into firmware record */
> >     firmware_rec = (struct ti_i2c_firmware_rec *)buffer;
> >  
> > -   firmware_rec->Ver_Major = OperationalMajorVersion;
> > -   firmware_rec->Ver_Minor = OperationalMinorVersion;
> > +   firmware_rec->Ver_Major = fw_major_version;
> > +   firmware_rec->Ver_Minor = fw_minor_version;
> >  
> >     /* Pointer to fw_down memory image */
> >     img_header = (struct ti_i2c_image_header *)&fw->data[4];
> 
> Note that sanity checks on the firmware size are missing here; you could
> fix that as a follow up.

I did some digging and it looks like we can actually do several sanity
checks on the firmware image.  It seems that the Edgeport firmware
images have a 7-byte header:
     u8 major_version;
     u8 minor_version;
     le16 build_number;
     le16 length;
     u8 checksum;
 
"length" appears to be the number of bytes of actual data following the
header.
 
"checksum" is apparently (from the images I have seen) the low order
byte resulting from adding the values of all the data bytes.

So, I'm testing a new ck_fw_sanity() function that checks for these
error conditions:
  1. NULL (missing) firmware image
  2. Incomplete firmware header:
       #define FW_HEADER_SIZE 7
       fw->size < FW_HEADER_SIZE    
  3. Bad firmware size:
       fw->size != FW_HEADER_SIZE + length_data
  4. Bad firmware checksum:
       compute checksum and compare to the stored version

I call ck_fw_sanity() at the top of download_fw(), so, by the time we
call build_i2c_fw_hdr(), the firmware image has already been sanity
checked.  Initial testing looks good to me.  Do you agree that I should
include my new ck_fw_sanity() function and its invocations (as a new,
separate 4th patch) in the upcoming v7 patchset?

> 
> > @@ -811,8 +795,6 @@ static int build_i2c_fw_hdr(__u8 *header, struct device 
> > *dev)
> >             &fw->data[4 + sizeof(struct ti_i2c_image_header)],
> >             le16_to_cpu(img_header->Length));
> >  
> > -   release_firmware(fw);
> > -
> >     for (i=0; i < buffer_size; i++) {
> >             cs = (__u8)(cs + buffer[i]);
> >     }
> > @@ -826,8 +808,8 @@ static int build_i2c_fw_hdr(__u8 *header, struct device 
> > *dev)
> >     i2c_header->Type        = I2C_DESC_TYPE_FIRMWARE_BLANK;
> >     i2c_header->Size        = cpu_to_le16(buffer_size);
> >     i2c_header->CheckSum    = cs;
> > -   firmware_rec->Ver_Major = OperationalMajorVersion;
> > -   firmware_rec->Ver_Minor = OperationalMinorVersion;
> > +   firmware_rec->Ver_Major = fw_major_version;
> > +   firmware_rec->Ver_Minor = fw_minor_version;
> >  
> >     return 0;
> >  }
> > @@ -934,7 +916,8 @@ static int ti_cpu_rev(struct edge_ti_manuf_descriptor 
> > *desc)
> >   * This routine downloads the main operating code into the TI5052, using 
> > the
> >   * boot code already burned into E2PROM or ROM.
> >   */
> > -static int download_fw(struct edgeport_serial *serial)
> > +static int download_fw(struct edgeport_serial *serial,
> > +           const struct firmware *fw)
> >  {
> >     struct device *dev = &serial->serial->dev->dev;
> >     int status = 0;
> > @@ -943,6 +926,8 @@ static int download_fw(struct edgeport_serial *serial)
> >     struct usb_interface_descriptor *interface;
> >     int download_cur_ver;
> >     int download_new_ver;
> > +   u8 fw_major_version = fw->data[0];
> > +   u8 fw_minor_version = fw->data[1];
> >
> >     /* This routine is entered by both the BOOT mode and the Download mode
> >      * We can determine which code is running by the reading the config
> 
> If it wasn't for the horrible error handling in this function, you'd
> really want to request the firmware here before checking the boot mode
> (but releasing it would then currently involve changing just about every
> error path).

Agreed!  My colleague, Al Borchers, and I submitted some minor patches
for this driver in the early days of usb-serial (after we wrote
digi_acceleport.c), and I recall this function being frightful even
then.  Given its current state, I think our best approach is to leave
the request_firmware() and release_firmware() calls in edge_startup(),
which I think agrees with your opinion.

> 
> You should at least add the missing sanity checks here, though. That is,
> verify the firmware size before parsing the firmware header. Note that
> fw->size >= 4 is assumed in one of the branches below.

Agreed.  In my current test implementation I call the new
check_fw_sanity() function (described above) here, before parsing the
firmware header.  Sound OK?
> 
> You should also set the serial->fw_version here rather than in
> edge_startup.

Done.

> 
> > @@ -1050,14 +1035,13 @@ static int download_fw(struct edgeport_serial 
> > *serial)
> >                        version in I2c */
> >                     download_cur_ver = (firmware_version->Ver_Major << 8) +
> >                                        (firmware_version->Ver_Minor);
> > -                   download_new_ver = (OperationalMajorVersion << 8) +
> > -                                      (OperationalMinorVersion);
> > +                   download_new_ver = (fw_major_version << 8) +
> > +                                      (fw_minor_version);
> >  
> >                     dev_dbg(dev, "%s - >> FW Versions Device %d.%d  Driver 
> > %d.%d\n",
> >                             __func__, firmware_version->Ver_Major,
> >                             firmware_version->Ver_Minor,
> > -                           OperationalMajorVersion,
> > -                           OperationalMinorVersion);
> > +                           fw_major_version, fw_minor_version);
> >  
> >                     /* Check if we have an old version in the I2C and
> >                        update if necessary */
> > @@ -1066,8 +1050,7 @@ static int download_fw(struct edgeport_serial *serial)
> >                                     __func__,
> >                                     firmware_version->Ver_Major,
> >                                     firmware_version->Ver_Minor,
> > -                                   OperationalMajorVersion,
> > -                                   OperationalMinorVersion);
> > +                                   fw_major_version, fw_minor_version);
> >  
> >                             record = kmalloc(1, GFP_KERNEL);
> >                             if (!record) {
> > @@ -1143,6 +1126,9 @@ static int download_fw(struct edgeport_serial *serial)
> >                             kfree(rom_desc);
> >                             kfree(ti_manuf_desc);
> >                             return -ENODEV;
> > +                   } else {
> > +                           /* Same or newer fw version is already loaded */
> > +                           serial->fw_version = download_cur_ver;
> >                     }
> >                     kfree(firmware_version);
> >             }
> > @@ -1181,7 +1167,7 @@ static int download_fw(struct edgeport_serial *serial)
> >                      * UMP Ram to I2C and the firmware will update the
> >                      * record type from 0xf2 to 0x02.
> >                      */
> > -                   status = build_i2c_fw_hdr(header, dev);
> > +                   status = build_i2c_fw_hdr(header, dev, fw);
> >                     if (status) {
> >                             kfree(vheader);
> >                             kfree(header);
> > @@ -1284,9 +1270,6 @@ static int download_fw(struct edgeport_serial *serial)
> >             __u8 cs = 0;
> >             __u8 *buffer;
> >             int buffer_size;
> > -           int err;
> > -           const struct firmware *fw;
> > -           const char *fw_name = "edgeport/down3.bin";
> >  
> >             /* Validate Hardware version number
> >              * Read Manufacturing Descriptor from TI Based Edgeport
> > @@ -1334,16 +1317,7 @@ static int download_fw(struct edgeport_serial 
> > *serial)
> >  
> >             /* Initialize the buffer to 0xff (pad the buffer) */
> >             memset(buffer, 0xff, buffer_size);
> > -
> > -           err = request_firmware(&fw, fw_name, dev);
> > -           if (err) {
> > -                   dev_err(dev, "Failed to load image \"%s\" err %d\n",
> > -                           fw_name, err);
> > -                   kfree(buffer);
> > -                   return err;
> > -           }
> >             memcpy(buffer, &fw->data[4], fw->size - 4);
> 
> Sanity check on fw->size has always been missing...

Done (via the check_fw_sanity() call above.

> 
> > -           release_firmware(fw);
> >  
> >             for (i = sizeof(struct ti_i2c_image_header);
> >                             i < buffer_size; i++) {
> > @@ -1358,7 +1332,8 @@ static int download_fw(struct edgeport_serial *serial)
> >             header->CheckSum = cs;
> >  
> >             /* Download the operational code  */
> > -           dev_dbg(dev, "%s - Downloading operational code image (TI 
> > UMP)\n", __func__);
> > +           dev_dbg(dev, "%s - Downloading operational code image version 
> > %d.%d (TI UMP)\n",
> > +                           __func__, fw_major_version, fw_minor_version);
> >             status = download_code(serial, buffer, buffer_size);
> >  
> >             kfree(buffer);
> > @@ -2383,6 +2358,12 @@ static int edge_startup(struct usb_serial *serial)
> >  {
> >     struct edgeport_serial *edge_serial;
> >     int status;
> > +   int err;
> 
> You forgot to drop err (use status instead).

Done.

> 
> > +   const struct firmware *fw;
> > +   const char *fw_name = "edgeport/down3.bin";
> > +   struct device *dev = &serial->dev->dev;
> > +   u8 fw_major_version;
> > +   u8 fw_minor_version;
> >  
> >     /* create our private serial structure */
> >     edge_serial = kzalloc(sizeof(struct edgeport_serial), GFP_KERNEL);
> > @@ -2393,7 +2374,28 @@ static int edge_startup(struct usb_serial *serial)
> >     edge_serial->serial = serial;
> >     usb_set_serial_data(serial, edge_serial);
> >  
> > -   status = download_fw(edge_serial);
> > +   err = request_firmware(&fw, fw_name, dev);
> > +   if (err) {
> > +           dev_err(&serial->interface->dev,
> > +                           "%s - Failed to load image \"%s\" err %d\n",
> > +                           __func__, fw_name, err);
> > +           kfree(edge_serial);
> > +           return err;
> > +   }
> > +
> > +   /*
> > +    * If the on-board firmware version is newer, download_fw()
> > +    * will retain it and revise edge_serial->fw_version.  The
> > +    * firmware also includes an le16 "build_number" at data+2, but
> > +    * it has been set to 0 in all three of the images I have seen,
> > +    * and Digi Tech Support suggests that it is safe to ignore it.
> > +    */
> > +   fw_major_version = fw->data[0];
> > +   fw_minor_version = fw->data[1];
> > +   edge_serial->fw_version = (fw_major_version << 8) + fw_minor_version;
> 
> So just move this bit to download_fw.

Done.

I think I'm close to being able to submit a v7 patchset.

Thanks.
     --Peter

> 
> > +
> > +   status = download_fw(edge_serial, fw);
> > +   release_firmware(fw);
> >     if (status) {
> >             kfree(edge_serial);
> >             return status;
> 
> Thanks,
> Johan

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to