diff mbox

[U-Boot,v4] cmd_sf: add "update" subcommand to do smart SPI flash update

Message ID 1313879751-1093-1-git-send-email-vapier@gentoo.org
State Changes Requested
Headers show

Commit Message

Mike Frysinger Aug. 20, 2011, 10:35 p.m. UTC
From: Simon Glass <sjg@chromium.org>

This adds a new SPI flash command which only rewrites blocks if the contents
need to change. This can speed up SPI flash programming when much of the
data is unchanged from what is already there.

Signed-off-by: Simon Glass <sjg@chromium.org>
Signed-off-by: Mike Frysinger <vapier@gentoo.org>
---
v4
	- tweak summary
	- fix printf warnings with %d vs %zu
	- fix help string and missing/extra newlines

TODO: it'd be nice if we supported +len like we do with erase ...

 common/cmd_sf.c |   84 +++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 81 insertions(+), 3 deletions(-)

Comments

Marek Vasut Aug. 20, 2011, 11:04 p.m. UTC | #1
On Sunday, August 21, 2011 12:35:51 AM Mike Frysinger wrote:
> From: Simon Glass <sjg@chromium.org>
> 
> This adds a new SPI flash command which only rewrites blocks if the
> contents need to change. This can speed up SPI flash programming when much
> of the data is unchanged from what is already there.
> 
> Signed-off-by: Simon Glass <sjg@chromium.org>
> Signed-off-by: Mike Frysinger <vapier@gentoo.org>
> ---
> v4
> 	- tweak summary
> 	- fix printf warnings with %d vs %zu
> 	- fix help string and missing/extra newlines
> 
> TODO: it'd be nice if we supported +len like we do with erase ...
> 
>  common/cmd_sf.c |   84
> +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 files changed,
> 81 insertions(+), 3 deletions(-)
> 
> diff --git a/common/cmd_sf.c b/common/cmd_sf.c
> index 11a491d..9b7d61b 100644
> --- a/common/cmd_sf.c
> +++ b/common/cmd_sf.c
> @@ -6,6 +6,7 @@
>   */
> 
>  #include <common.h>
> +#include <malloc.h>
>  #include <spi_flash.h>
> 
>  #include <asm/io.h>
> @@ -109,6 +110,78 @@ static int do_spi_flash_probe(int argc, char * const
> argv[]) return 0;
>  }
> 
> +/**
> + * Write a block of data to SPI flash, first checking if it is different
> from + * what is already there.
> + *
> + * If the data being written is the same, then *skipped is incremented by
> len. + *
> + * @param flash		flash context pointer
> + * @param offset	flash offset to write
> + * @param len		number of bytes to write
> + * @param buf		buffer to write from
> + * @param cmp_buf	read buffer to use to compare data
> + * @param skipped	Count of skipped data (incremented by this function)
> + * @return NULL if OK, else a string containing the stage which failed
> + */
> +static const char *spi_flash_update_block(struct spi_flash *flash, u32
> offset, +		size_t len, const char *buf, char *cmp_buf, size_t *skipped)

Can't you just pass here a structure instead of this wicked pointer alchemy ?

> +{
> +	debug("offset=%#x, sector_size=%#x, len=%#x\n",
> +		offset, flash->sector_size, len);
> +	if (spi_flash_read(flash, offset, len, cmp_buf))
> +		return "read";
> +	if (memcmp(cmp_buf, buf, len) == 0) {
> +		debug("Skip region %x size %x: no change\n",
> +			offset, len);
> +		*skipped += len;
> +		return NULL;
> +	}
> +	if (spi_flash_erase(flash, offset, len))
> +		return "erase";
> +	if (spi_flash_write(flash, offset, len, buf))
> +		return "write";

Numeric value won't be ok ? You can have these in the calling function instead 
of returning a char *.

> +	return NULL;
> +}
> +
> +/**
> + * Update an area of SPI flash by erasing and writing any blocks which
> need + * to change. Existing blocks with the correct data are left
> unchanged. + *
> + * @param flash		flash context pointer
> + * @param offset	flash offset to write
> + * @param len		number of bytes to write
> + * @param buf		buffer to write from
> + * @return 0 if ok, 1 on error
> + */
> +static int spi_flash_update(struct spi_flash *flash, u32 offset,
> +		size_t len, const char *buf)
> +{
> +	const char *err_oper = NULL;
> +	char *cmp_buf;
> +	const char *end = buf + len;
> +	size_t todo;		/* number of bytes to do in this pass */
> +	size_t skipped;		/* statistics */

You can allocate a structure holding the internal state of the "update" command, 
which I mentioned above, here, on stack.

> +
> +	cmp_buf = malloc(flash->sector_size);
> +	if (cmp_buf) {

if (!cmp_buf)
	goto err;

... rest of code ...

Don't be afraid of goto and failpaths.

> +		for (skipped = 0; buf < end; buf += todo, offset += todo) {
> +			todo = min(end - buf, flash->sector_size);
> +			err_oper = spi_flash_update_block(flash, offset, todo,
> +					buf, cmp_buf, &skipped);
> +		}
> +	} else {
> +		err_oper = "malloc";
> +	}
> +	free(cmp_buf);
> +	if (err_oper) {
> +		printf("SPI flash failed in %s step\n", err_oper);
> +		return 1;
> +	}
> +	printf("%zu bytes written, %zu bytes skipped\n", len - skipped, skipped);
> +	return 0;
> +}
> +
>  static int do_spi_flash_read_write(int argc, char * const argv[])
>  {
>  	unsigned long addr;
> @@ -137,7 +210,9 @@ static int do_spi_flash_read_write(int argc, char *
> const argv[]) return 1;
>  	}
> 
> -	if (strcmp(argv[0], "read") == 0)
> +	if (strcmp(argv[0], "update") == 0)
> +		ret = spi_flash_update(flash, offset, len, buf);
> +	else if (strcmp(argv[0], "read") == 0)
>  		ret = spi_flash_read(flash, offset, len, buf);
>  	else
>  		ret = spi_flash_write(flash, offset, len, buf);
> @@ -203,7 +278,8 @@ static int do_spi_flash(cmd_tbl_t *cmdtp, int flag, int
> argc, char * const argv[ return 1;
>  	}
> 
> -	if (strcmp(cmd, "read") == 0 || strcmp(cmd, "write") == 0)
> +	if (strcmp(cmd, "read") == 0 || strcmp(cmd, "write") == 0 ||
> +	    strcmp(cmd, "update") == 0)
>  		ret = do_spi_flash_read_write(argc, argv);
>  	else if (strcmp(cmd, "erase") == 0)
>  		ret = do_spi_flash_erase(argc, argv);
> @@ -228,5 +304,7 @@ U_BOOT_CMD(
>  	"sf write addr offset len	- write `len' bytes from memory\n"
>  	"				  at `addr' to flash at `offset'\n"
>  	"sf erase offset [+]len		- erase `len' bytes from `offset'\n"
> -	"				  `+len' round up `len' to block size"
> +	"				  `+len' round up `len' to block size\n"
> +	"sf update addr offset len	- erase and write `len' bytes from memory\n"
> +	"				  at `addr' to flash at `offset'"
>  );

I like this patch!

Cheers
Simon Glass Aug. 21, 2011, 10:27 a.m. UTC | #2
On Sat, Aug 20, 2011 at 4:35 PM, Mike Frysinger <vapier@gentoo.org> wrote:
> From: Simon Glass <sjg@chromium.org>
>
> This adds a new SPI flash command which only rewrites blocks if the contents
> need to change. This can speed up SPI flash programming when much of the
> data is unchanged from what is already there.
>
> Signed-off-by: Simon Glass <sjg@chromium.org>
> Signed-off-by: Mike Frysinger <vapier@gentoo.org>
> ---
> v4
>        - tweak summary
>        - fix printf warnings with %d vs %zu
>        - fix help string and missing/extra newlines
>
> TODO: it'd be nice if we supported +len like we do with erase ...
...
> +static int spi_flash_update(struct spi_flash *flash, u32 offset,
> +               size_t len, const char *buf)
> +{
> +       const char *err_oper = NULL;
> +       char *cmp_buf;
> +       const char *end = buf + len;
> +       size_t todo;            /* number of bytes to do in this pass */
> +       size_t skipped;         /* statistics */
> +
> +       cmp_buf = malloc(flash->sector_size);
> +       if (cmp_buf) {
> +               for (skipped = 0; buf < end; buf += todo, offset += todo) {

Oops I got this wrong:

for (skipped = 0; buf < end && !err_oper; buf += todo, offset += todo) {

(or if (err_oper) break in the loop)

> +                       todo = min(end - buf, flash->sector_size);
> +                       err_oper = spi_flash_update_block(flash, offset, todo,
> +                                       buf, cmp_buf, &skipped);
> +               }
> +       } else {
> +               err_oper = "malloc";
> +       }
> +       free(cmp_buf);
> +       if (err_oper) {
> +               printf("SPI flash failed in %s step\n", err_oper);
> +               return 1;
> +       }
> +       printf("%zu bytes written, %zu bytes skipped\n", len - skipped, skipped);
> +       return 0;
> +}
> +
>  static int do_spi_flash_read_write(int argc, char * const argv[])
>  {
>        unsigned long addr;
> @@ -137,7 +210,9 @@ static int do_spi_flash_read_write(int argc, char * const argv[])
>                return 1;
>        }
>
> -       if (strcmp(argv[0], "read") == 0)
> +       if (strcmp(argv[0], "update") == 0)
> +               ret = spi_flash_update(flash, offset, len, buf);
> +       else if (strcmp(argv[0], "read") == 0)
>                ret = spi_flash_read(flash, offset, len, buf);
>        else
>                ret = spi_flash_write(flash, offset, len, buf);
> @@ -203,7 +278,8 @@ static int do_spi_flash(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[
>                return 1;
>        }
>
> -       if (strcmp(cmd, "read") == 0 || strcmp(cmd, "write") == 0)
> +       if (strcmp(cmd, "read") == 0 || strcmp(cmd, "write") == 0 ||
> +           strcmp(cmd, "update") == 0)
>                ret = do_spi_flash_read_write(argc, argv);
>        else if (strcmp(cmd, "erase") == 0)
>                ret = do_spi_flash_erase(argc, argv);
> @@ -228,5 +304,7 @@ U_BOOT_CMD(
>        "sf write addr offset len       - write `len' bytes from memory\n"
>        "                                 at `addr' to flash at `offset'\n"
>        "sf erase offset [+]len         - erase `len' bytes from `offset'\n"
> -       "                                 `+len' round up `len' to block size"
> +       "                                 `+len' round up `len' to block size\n"
> +       "sf update addr offset len      - erase and write `len' bytes from memory\n"
> +       "                                 at `addr' to flash at `offset'"
>  );
> --
> 1.7.6
>
>
Simon Glass Aug. 21, 2011, 10:37 a.m. UTC | #3
Hi Marek,

On Sat, Aug 20, 2011 at 5:04 PM, Marek Vasut <marek.vasut@gmail.com> wrote:
> On Sunday, August 21, 2011 12:35:51 AM Mike Frysinger wrote:
>> From: Simon Glass <sjg@chromium.org>
>>
>> This adds a new SPI flash command which only rewrites blocks if the
>> contents need to change. This can speed up SPI flash programming when much
>> of the data is unchanged from what is already there.
>>
>> Signed-off-by: Simon Glass <sjg@chromium.org>
>> Signed-off-by: Mike Frysinger <vapier@gentoo.org>
>> ---
>> v4
>>       - tweak summary
>>       - fix printf warnings with %d vs %zu
>>       - fix help string and missing/extra newlines
>>
>> TODO: it'd be nice if we supported +len like we do with erase ...
>>
>>  common/cmd_sf.c |   84
>> +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 files changed,
>> 81 insertions(+), 3 deletions(-)
>>
>> diff --git a/common/cmd_sf.c b/common/cmd_sf.c
>> index 11a491d..9b7d61b 100644
>> --- a/common/cmd_sf.c
>> +++ b/common/cmd_sf.c
>> @@ -6,6 +6,7 @@
>>   */
>>
>>  #include <common.h>
>> +#include <malloc.h>
>>  #include <spi_flash.h>
>>
>>  #include <asm/io.h>
>> @@ -109,6 +110,78 @@ static int do_spi_flash_probe(int argc, char * const
>> argv[]) return 0;
>>  }
>>
>> +/**
>> + * Write a block of data to SPI flash, first checking if it is different
>> from + * what is already there.
>> + *
>> + * If the data being written is the same, then *skipped is incremented by
>> len. + *
>> + * @param flash              flash context pointer
>> + * @param offset     flash offset to write
>> + * @param len                number of bytes to write
>> + * @param buf                buffer to write from
>> + * @param cmp_buf    read buffer to use to compare data
>> + * @param skipped    Count of skipped data (incremented by this function)
>> + * @return NULL if OK, else a string containing the stage which failed
>> + */
>> +static const char *spi_flash_update_block(struct spi_flash *flash, u32
>> offset, +             size_t len, const char *buf, char *cmp_buf, size_t *skipped)
>
> Can't you just pass here a structure instead of this wicked pointer alchemy ?

Do you mean create a structure with the things that don't change in it
(flash, cmp_buf and skipped)? Is the problem too many parameters?

>
>> +{
>> +     debug("offset=%#x, sector_size=%#x, len=%#x\n",
>> +             offset, flash->sector_size, len);
>> +     if (spi_flash_read(flash, offset, len, cmp_buf))
>> +             return "read";
>> +     if (memcmp(cmp_buf, buf, len) == 0) {
>> +             debug("Skip region %x size %x: no change\n",
>> +                     offset, len);
>> +             *skipped += len;
>> +             return NULL;
>> +     }
>> +     if (spi_flash_erase(flash, offset, len))
>> +             return "erase";
>> +     if (spi_flash_write(flash, offset, len, buf))
>> +             return "write";
>
> Numeric value won't be ok ? You can have these in the calling function instead
> of returning a char *.

Yes it's a bit odd, but the alternative is quite a bit more verbose:

enum {
   OPER_MALLOC,
   OPER_READ,
   OPER_ERASE,
   ...
};

static const char *names[OPER...] = {
   "malloc",
   "read",
   "erase"
...
};

Is that better?

>
>> +     return NULL;
>> +}
>> +
>> +/**
>> + * Update an area of SPI flash by erasing and writing any blocks which
>> need + * to change. Existing blocks with the correct data are left
>> unchanged. + *
>> + * @param flash              flash context pointer
>> + * @param offset     flash offset to write
>> + * @param len                number of bytes to write
>> + * @param buf                buffer to write from
>> + * @return 0 if ok, 1 on error
>> + */
>> +static int spi_flash_update(struct spi_flash *flash, u32 offset,
>> +             size_t len, const char *buf)
>> +{
>> +     const char *err_oper = NULL;
>> +     char *cmp_buf;
>> +     const char *end = buf + len;
>> +     size_t todo;            /* number of bytes to do in this pass */
>> +     size_t skipped;         /* statistics */
>
> You can allocate a structure holding the internal state of the "update" command,
> which I mentioned above, here, on stack.

Please see question above.

>
>> +
>> +     cmp_buf = malloc(flash->sector_size);
>> +     if (cmp_buf) {
>
> if (!cmp_buf)
>        goto err;
>
> ... rest of code ...
>
> Don't be afraid of goto and failpaths.

OK, will try.

>
> I like this patch!

Thanks!

Regards,
Simon

>
> Cheers
> _______________________________________________
> U-Boot mailing list
> U-Boot@lists.denx.de
> http://lists.denx.de/mailman/listinfo/u-boot
>
Mike Frysinger Aug. 21, 2011, 4:34 p.m. UTC | #4
On Sunday, August 21, 2011 06:27:16 Simon Glass wrote:
> On Sat, Aug 20, 2011 at 4:35 PM, Mike Frysinger wrote:
> > +static int spi_flash_update(struct spi_flash *flash, u32 offset,
> > +               size_t len, const char *buf)
> > +{
> > +       const char *err_oper = NULL;
> > +       char *cmp_buf;
> > +       const char *end = buf + len;
> > +       size_t todo;            /* number of bytes to do in this pass */
> > +       size_t skipped;         /* statistics */
> > +
> > +       cmp_buf = malloc(flash->sector_size);
> > +       if (cmp_buf) {
> > +               for (skipped = 0; buf < end; buf += todo, offset += todo)
> > {
> 
> Oops I got this wrong:
> 
> for (skipped = 0; buf < end && !err_oper; buf += todo, offset += todo) {
> 
> (or if (err_oper) break in the loop)

you can send a v5 and i'll just replace it in my tree :)

or a diff and i'll just merge it locally ...
-mike
Mike Frysinger Aug. 21, 2011, 4:35 p.m. UTC | #5
On Sunday, August 21, 2011 06:37:30 Simon Glass wrote:
> On Sat, Aug 20, 2011 at 5:04 PM, Marek Vasut wrote:
> > On Sunday, August 21, 2011 12:35:51 AM Mike Frysinger wrote:
> >> +{
> >> +     debug("offset=%#x, sector_size=%#x, len=%#x\n",
> >> +             offset, flash->sector_size, len);
> >> +     if (spi_flash_read(flash, offset, len, cmp_buf))
> >> +             return "read";
> >> +     if (memcmp(cmp_buf, buf, len) == 0) {
> >> +             debug("Skip region %x size %x: no change\n",
> >> +                     offset, len);
> >> +             *skipped += len;
> >> +             return NULL;
> >> +     }
> >> +     if (spi_flash_erase(flash, offset, len))
> >> +             return "erase";
> >> +     if (spi_flash_write(flash, offset, len, buf))
> >> +             return "write";
> > 
> > Numeric value won't be ok ? You can have these in the calling function
> > instead of returning a char *.
> 
> Yes it's a bit odd, but the alternative is quite a bit more verbose:
> 
> enum {
>    OPER_MALLOC,
>    OPER_READ,
>    OPER_ERASE,
>    ...
> };
> 
> static const char *names[OPER...] = {

static const char * const names[] = {

>    "malloc",
>    "read",
>    "erase"
> ...
> };
> 
> Is that better?

only if the code size is smaller ;)
-mike
Simon Glass Aug. 22, 2011, 10:53 p.m. UTC | #6
On Sun, Aug 21, 2011 at 9:35 AM, Mike Frysinger <vapier@gentoo.org> wrote:
> On Sunday, August 21, 2011 06:37:30 Simon Glass wrote:
>> On Sat, Aug 20, 2011 at 5:04 PM, Marek Vasut wrote:
>> > On Sunday, August 21, 2011 12:35:51 AM Mike Frysinger wrote:
>> >> +{
>> >> +     debug("offset=%#x, sector_size=%#x, len=%#x\n",
>> >> +             offset, flash->sector_size, len);
>> >> +     if (spi_flash_read(flash, offset, len, cmp_buf))
>> >> +             return "read";
>> >> +     if (memcmp(cmp_buf, buf, len) == 0) {
>> >> +             debug("Skip region %x size %x: no change\n",
>> >> +                     offset, len);
>> >> +             *skipped += len;
>> >> +             return NULL;
>> >> +     }
>> >> +     if (spi_flash_erase(flash, offset, len))
>> >> +             return "erase";
>> >> +     if (spi_flash_write(flash, offset, len, buf))
>> >> +             return "write";
>> >
>> > Numeric value won't be ok ? You can have these in the calling function
>> > instead of returning a char *.
>>
>> Yes it's a bit odd, but the alternative is quite a bit more verbose:
>>
>> enum {
>>    OPER_MALLOC,
>>    OPER_READ,
>>    OPER_ERASE,
>>    ...
>> };
>>
>> static const char *names[OPER...] = {
>
> static const char * const names[] = {
>
>>    "malloc",
>>    "read",
>>    "erase"
>> ...
>> };
>>
>> Is that better?
>
> only if the code size is smaller ;)

Hi Mike,

Well it is 4 bytes larger, or 8 if I avoid char * for the array and
fix the string length. So I will leave that as is for now, and send a
new v5 patch.

Regards,
Simon

> -mike
>
diff mbox

Patch

diff --git a/common/cmd_sf.c b/common/cmd_sf.c
index 11a491d..9b7d61b 100644
--- a/common/cmd_sf.c
+++ b/common/cmd_sf.c
@@ -6,6 +6,7 @@ 
  */
 
 #include <common.h>
+#include <malloc.h>
 #include <spi_flash.h>
 
 #include <asm/io.h>
@@ -109,6 +110,78 @@  static int do_spi_flash_probe(int argc, char * const argv[])
 	return 0;
 }
 
+/**
+ * Write a block of data to SPI flash, first checking if it is different from
+ * what is already there.
+ *
+ * If the data being written is the same, then *skipped is incremented by len.
+ *
+ * @param flash		flash context pointer
+ * @param offset	flash offset to write
+ * @param len		number of bytes to write
+ * @param buf		buffer to write from
+ * @param cmp_buf	read buffer to use to compare data
+ * @param skipped	Count of skipped data (incremented by this function)
+ * @return NULL if OK, else a string containing the stage which failed
+ */
+static const char *spi_flash_update_block(struct spi_flash *flash, u32 offset,
+		size_t len, const char *buf, char *cmp_buf, size_t *skipped)
+{
+	debug("offset=%#x, sector_size=%#x, len=%#x\n",
+		offset, flash->sector_size, len);
+	if (spi_flash_read(flash, offset, len, cmp_buf))
+		return "read";
+	if (memcmp(cmp_buf, buf, len) == 0) {
+		debug("Skip region %x size %x: no change\n",
+			offset, len);
+		*skipped += len;
+		return NULL;
+	}
+	if (spi_flash_erase(flash, offset, len))
+		return "erase";
+	if (spi_flash_write(flash, offset, len, buf))
+		return "write";
+	return NULL;
+}
+
+/**
+ * Update an area of SPI flash by erasing and writing any blocks which need
+ * to change. Existing blocks with the correct data are left unchanged.
+ *
+ * @param flash		flash context pointer
+ * @param offset	flash offset to write
+ * @param len		number of bytes to write
+ * @param buf		buffer to write from
+ * @return 0 if ok, 1 on error
+ */
+static int spi_flash_update(struct spi_flash *flash, u32 offset,
+		size_t len, const char *buf)
+{
+	const char *err_oper = NULL;
+	char *cmp_buf;
+	const char *end = buf + len;
+	size_t todo;		/* number of bytes to do in this pass */
+	size_t skipped;		/* statistics */
+
+	cmp_buf = malloc(flash->sector_size);
+	if (cmp_buf) {
+		for (skipped = 0; buf < end; buf += todo, offset += todo) {
+			todo = min(end - buf, flash->sector_size);
+			err_oper = spi_flash_update_block(flash, offset, todo,
+					buf, cmp_buf, &skipped);
+		}
+	} else {
+		err_oper = "malloc";
+	}
+	free(cmp_buf);
+	if (err_oper) {
+		printf("SPI flash failed in %s step\n", err_oper);
+		return 1;
+	}
+	printf("%zu bytes written, %zu bytes skipped\n", len - skipped, skipped);
+	return 0;
+}
+
 static int do_spi_flash_read_write(int argc, char * const argv[])
 {
 	unsigned long addr;
@@ -137,7 +210,9 @@  static int do_spi_flash_read_write(int argc, char * const argv[])
 		return 1;
 	}
 
-	if (strcmp(argv[0], "read") == 0)
+	if (strcmp(argv[0], "update") == 0)
+		ret = spi_flash_update(flash, offset, len, buf);
+	else if (strcmp(argv[0], "read") == 0)
 		ret = spi_flash_read(flash, offset, len, buf);
 	else
 		ret = spi_flash_write(flash, offset, len, buf);
@@ -203,7 +278,8 @@  static int do_spi_flash(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[
 		return 1;
 	}
 
-	if (strcmp(cmd, "read") == 0 || strcmp(cmd, "write") == 0)
+	if (strcmp(cmd, "read") == 0 || strcmp(cmd, "write") == 0 ||
+	    strcmp(cmd, "update") == 0)
 		ret = do_spi_flash_read_write(argc, argv);
 	else if (strcmp(cmd, "erase") == 0)
 		ret = do_spi_flash_erase(argc, argv);
@@ -228,5 +304,7 @@  U_BOOT_CMD(
 	"sf write addr offset len	- write `len' bytes from memory\n"
 	"				  at `addr' to flash at `offset'\n"
 	"sf erase offset [+]len		- erase `len' bytes from `offset'\n"
-	"				  `+len' round up `len' to block size"
+	"				  `+len' round up `len' to block size\n"
+	"sf update addr offset len	- erase and write `len' bytes from memory\n"
+	"				  at `addr' to flash at `offset'"
 );