#include "compiler.h" #include "zlib.h" #include "spiflash.h" #if 1 # include "console.h" # define MSG(...) con_printf(__VA_ARGS__) #else # define MSG(...) ((void)0) #endif struct spz_stream; typedef struct spz_stream spz_stream; struct spz_stream { z_stream zs; const struct spiflash *flash; int (*read_data)(spz_stream *); /* Routine to get more data */ int (*end_data)(z_stream *); /* Termination routine for zlib */ uint8_t *optr; /* Output data pointer into obuf */ /* Note: available output data ends at zs->next_out */ uint8_t *ibuf; /* Input buffer if compressed */ uint8_t *obuf; /* Output buffer */ uint8_t *dbuf; /* Block data buffer */ uint8_t *vbuf; /* Readback/verify buffer */ const struct spiflash_header *header; uint32_t crc32; /* Input data CRC32 */ unsigned int input_left; /* Input data unread */ int err; /* Error code to return */ bool eoi; /* Reached end of input */ bool free_header; /* header is malloc()'d */ }; static void *spz_malloc(spz_stream *spz, size_t bytes) { void *p = malloc(bytes); if (!p && !spz->err) { spz->err = Z_MEM_ERROR; } return p; } static int spiflash_read_data(spz_stream *spz) { int rv; int (*read_data)(void *, void *, unsigned int); unsigned int read_block_size; if (spz->eoi || spz->err) return 0; read_data = spz->flash->ops->read_data; if (!spz->input_left || !read_data) { spz->eoi = true; return 0; } read_block_size = min(spz->input_left, SPIFLASH_BLOCK_SIZE); if (!spz->ibuf) { spz->ibuf = spz_malloc(spz, SPIFLASH_BLOCK_SIZE); if (!spz->ibuf) { spz->eoi = true; return 0; } } spz->zs.next_in = spz->ibuf; spz->zs.avail_in = 0; rv = read_data(spz->flash->cookie, spz->ibuf, read_block_size); if (spz->err) { rv = 0; } else if (rv < 0) { spz->err = rv; rv = 0; } if (rv != (int)read_block_size) spz->eoi = true; if (rv) { spz->crc32 = crc32(spz->crc32, spz->ibuf, rv); spz->input_left -= rv; if (!spz->input_left) { if (spz->crc32 != spz->header->crc32) { spz->err = Z_STREAM_ERROR; rv = 0; } } } return spz->zs.avail_in = rv; } static int read_data_raw(spz_stream *spz) { int rlen; if (spz->eoi) return 0; rlen = spiflash_read_data(spz); if (rlen) { spz->optr = spz->ibuf; spz->zs.next_out = spz->ibuf + rlen; spz->zs.avail_out = SPIFLASH_BLOCK_SIZE - rlen; } return rlen; } static int read_data_inflate(spz_stream *spz) { int rv = Z_STREAM_END; spz->optr = spz->zs.next_out = spz->obuf; spz->zs.avail_out = SPIFLASH_BLOCK_SIZE; while (spz->zs.avail_out) { if (!spz->zs.avail_in && !spz->eoi) { int rlen = spiflash_read_data(spz); spz->zs.next_in = spz->ibuf; spz->zs.avail_in = rlen; } rv = inflate(&spz->zs, Z_SYNC_FLUSH); if (rv == Z_OK || (rv == Z_BUF_ERROR && !spz->eoi)) continue; spz->eoi = true; if (rv != Z_STREAM_END && !spz->err) spz->err = rv; break; } return spz->zs.next_out - spz->optr; } /* * spz needs to be initialized to zero except the flash, zs.next_in, * and zs.avail_in fields. * * Returns Z_STREAM_END on end of data. */ static int spiflash_data_init(spz_stream *spz) { int rv = Z_OK; uint8_t *rdbuf = NULL; int rlen; int (*read_data)(void *cookie, void *buf, unsigned int bufsize); uint32_t header_crc; MSG("update: "); spz->dbuf = spz_malloc(spz, SPIFLASH_BLOCK_SIZE); spz->vbuf = spz_malloc(spz, SPIFLASH_BLOCK_SIZE); if (!spz->dbuf || !spz->vbuf) goto err; rlen = spz->zs.avail_in; spz->header = (void *)spz->zs.next_in; read_data = spz->flash->ops->read_data; spz->eoi = !read_data; if (!rlen && read_data) { struct spiflash_header *header; spz->header = header = spz_malloc(spz, sizeof *spz->header); spz->free_header = true; spz->input_left = UINT_MAX; /* Unknown at this point */ rlen = read_data(spz->flash->cookie, header, sizeof *header); } if (spz->err) goto err; if (!rlen) { MSG("done"); spz->err = Z_STREAM_END; goto not_err; } if (rlen < (int)sizeof *spz->header) { MSG("input underrun"); spz->err = Z_STREAM_ERROR; goto err; } rlen -= sizeof *spz->header; spz->zs.next_in += sizeof *spz->header; /* * Check header magic and CRC */ if (spz->header->magic != SPIFLASH_MAGIC) { MSG("bad header magic"); spz->err = Z_STREAM_ERROR; goto err; } header_crc = crc32(0, NULL, 0); header_crc = crc32(header_crc, (const void *)spz->header, sizeof *spz->header - 4); if (header_crc != spz->header->header_crc32) { MSG("header CRC error (0x%08x vs 0x%08x)...", header_crc, spz->header->header_crc32); spz->err = Z_STREAM_ERROR; goto err; } if (!spz->header->dlen) { /* End of data */ spz->err = Z_STREAM_END; goto not_err; } if (spz->header->zlen > spz->header->dlen || (spz->eoi && (int)spz->header->zlen > rlen)) { spz->err = Z_STREAM_ERROR; goto err; } MSG("data 0x%06x..0x%06x (%u bytes)\n", spz->header->address, spz->header->address + spz->header->dlen - 1, spz->header->dlen); if (rlen > (int)spz->header->zlen) rlen = spz->header->zlen; spz->zs.avail_in = rlen; spz->crc32 = crc32(0, NULL, 0); if (rlen) { /* Received data in input buffer already */ spz->crc32 = crc32(spz->crc32, spz->zs.next_in, spz->zs.avail_in); } spz->input_left = spz->header->zlen - rlen; if (!spz->input_left) { if (spz->crc32 != spz->header->crc32) { spz->err = Z_STREAM_ERROR; goto err; } } if (spz->header->zlen == spz->header->dlen) { /* Assume it is a raw binary; input buffer is output buffer */ spz->read_data = read_data_raw; spz->optr = spz->zs.next_in; spz->zs.next_out = spz->zs.next_in + spz->zs.avail_in; } else { /* Compressed data? */ spz->obuf = spz_malloc(spz, SPIFLASH_BLOCK_SIZE); if (!spz->obuf) goto err; if (rlen >= 14 && !memcmp(spz->zs.next_in, "\37\213\10", 3)) { /* It is a gzip file */ spz->read_data = read_data_inflate; /* gzip, max window size */ rv = inflateInit2(&spz->zs, 16 + 15); if (rv != Z_OK && rv != Z_STREAM_END) { spz->err = rv; goto err; } spz->eoi = rv == Z_STREAM_END; spz->end_data = inflateEnd; } else { /* Unknown compression format */ spz->err = Z_STREAM_ERROR; goto err; } } err: if (spz->err) MSG(" failed (err %d)\n", spz->err); not_err: MSG("\n"); return spz->err; } static int spiflash_data_cleanup(spz_stream *spz) { int err = 0; if (!spz) return 0; err = spz->err; if (spz->flash->ops->close_data) { int rv = spz->flash->ops->close_data(spz->flash->cookie); if (!err) err = rv; } if (spz->end_data) spz->end_data(&spz->zs); if (spz->free_header) free((void *)spz->header); if (spz->vbuf) free(spz->vbuf); if (spz->dbuf) free(spz->dbuf); if (spz->obuf) free(spz->obuf); if (spz->ibuf) free(spz->ibuf); return err; } /* * Set up a command header with an address according to the SPI * addressing mode. Returns a pointer to the first byte past the * address. */ static void *spiflash_setup_addrcmd(const struct spiflash *flash, uint32_t addr, uint8_t cmd24, uint8_t cmd32, void *cmdbuf) { enum spiflash_addr_mode mode = flash->param->addr; uint8_t *cmd = cmdbuf; if (!mode) mode = addr < (1 << 24) ? SPIFLASH_ADDR_24BIT : SPIFLASH_ADDR_32BIT; if (mode == SPIFLASH_ADDR_24BIT) { *cmd++ = cmd24; } else { *cmd++ = cmd32; *cmd++ = addr >> 24; } *cmd++ = addr >> 16; *cmd++ = addr >> 8; *cmd++ = addr; return cmd; } static int spiflash_get_status(const struct spiflash *flash, uint8_t cmd, uint8_t *sr) { return flash->ops->spi_read(flash->cookie, &cmd, 1, sr, 1, flash->param->tshsl); } /* This needs a timeout function */ static int spiflash_wait_status(const struct spiflash *flash, int delay, uint8_t mask, uint8_t val) { uint8_t sr1; int rv; do { if (flash->ops->yield) flash->ops->yield(flash->cookie, delay); rv = spiflash_get_status(flash, ROM_READ_SR1, &sr1); if (rv) return rv; } while ((sr1 & mask) != val); /* Waiting... */ return 0; } int spiflash_read(const struct spiflash *flash, uint32_t addr, void *buffer, size_t len) { uint8_t cmdbuf[6]; uint8_t *cmd; cmd = spiflash_setup_addrcmd(flash, addr, ROM_FAST_READ, ROM_FAST_READ_32BIT, cmdbuf); *cmd++ = 0; /* Dummy cycles */ return flash->ops->spi_read(flash->cookie, cmdbuf, cmd - cmdbuf, buffer, len, flash->param->tshsl1); } static int spiflash_simple_command(const struct spiflash *flash, uint8_t cmd) { return flash->ops->spi_write(flash->cookie, &cmd, 1, NULL, 0, flash->param->tshsl); } static int spiflash_write_enable(const struct spiflash *flash) { uint8_t sr1; int rv; rv = spiflash_wait_status(flash, 0, 1, 0); if (rv) return rv; rv = spiflash_simple_command(flash, ROM_WRITE_ENABLE); if (rv) return rv; return spiflash_wait_status(flash, 0, 3, 2); } static int spiflash_program(const struct spiflash *flash, uint32_t addr, const void *buffer, size_t len) { uint8_t cmdbuf[5]; uint8_t *cmd; int rv; rv = spiflash_write_enable(flash); if (rv) return rv; cmd = spiflash_setup_addrcmd(flash, addr, ROM_PAGE_PROGRAM, ROM_PAGE_PROGRAM_32BIT, cmdbuf); rv = flash->ops->spi_write(flash->cookie, cmdbuf, cmd - cmdbuf, buffer, len, flash->param->tshsl2); if (rv) return rv; return spiflash_wait_status(flash, flash->param->tpp, 3, 0); } /* * Erase up to (long bits) sectors, using block erase if possible. */ static int spiflash_erase(const struct spiflash *flash, uint32_t addr, unsigned long sector_mask) { uint8_t cmdbuf[5]; uint8_t *cmd; uint8_t cmd24, cmd32; uint32_t erasesize; int rv; int delay; const uint32_t block_mask = SPIFLASH_BLOCK_SIZE - 1; const unsigned long block_sector_mask = block_mask >> SPIFLASH_SECTOR_SHIFT; if (!sector_mask) { MSG("update: nothing to erase\n"); return 0; } while (sector_mask) { if (!(addr & block_mask) && ((sector_mask & block_sector_mask) == block_sector_mask)) { cmd24 = ROM_ERASE_64K; cmd32 = ROM_ERASE_64K_32BIT; delay = flash->param->tbe2; erasesize = SPIFLASH_BLOCK_SIZE; } else { cmd24 = ROM_ERASE_4K; cmd32 = ROM_ERASE_4K_32BIT; delay = flash->param->tse; erasesize = SPIFLASH_SECTOR_SIZE; } if (sector_mask & 1) { MSG("\rupdate: erasing %2uK at 0x%06x... ", erasesize >> 10, addr); rv = spiflash_write_enable(flash); if (rv) return rv; cmd = spiflash_setup_addrcmd(flash, addr, cmd24, cmd32, cmdbuf); rv = flash->ops->spi_write(flash->cookie, cmdbuf, cmd - cmdbuf, NULL, 0, flash->param->tshsl2); if (rv) return rv; rv = spiflash_wait_status(flash, delay, 3, 0); if (rv) return rv; } addr += erasesize; sector_mask >>= (erasesize >> SPIFLASH_SECTOR_SHIFT); } MSG("ok\n"); return 0; } /* * from: current flash contents * to: desired flash contents * * These are assumed to be aligned full block buffers */ enum flashmem_status { FMS_DONE, /* All done, no programming needed */ FMS_PROGRAM, /* Can be programmed */ FMS_ERASE, /* Needs erase before programming */ FMS_NOTCHECKED /* Not checked yet */ }; static enum flashmem_status spiflash_memcmp(const void *from, const void *to, size_t len) { const uint32_t *pf = from; const uint32_t *pt = to; const uint32_t *pfend = (const uint32_t *)((const char *)from + len); uint32_t doprog = 0; uint32_t doerase = 0; while (pf < pfend) { uint32_t f = *pf++; uint32_t t = *pt++; doprog |= f ^ t; /* Need programming if any data mismatch */ doerase |= ~f & t; /* Need erasing if any 0 -> 1 */ } return doerase ? FMS_ERASE : doprog ? FMS_PROGRAM : FMS_DONE; } /* * Check a block for sectors which need erasing and pages which need * programming; the prog_mask is 256 bits long and so span multiple words. * * The desired input is spz->dbuf and the existing flash content is * written to spz->vbuf. * */ static int spiflash_check_block(spz_stream *spz, uint32_t addr, uint32_t *erase_mask, uint32_t *prog_mask) { int rv; const uint8_t *p, *q; unsigned int page; rv = spiflash_read(spz->flash, addr, spz->vbuf, SPIFLASH_BLOCK_SIZE); if (rv) { if (!spz->err) spz->err = rv; return rv; } *erase_mask = 0; memset(prog_mask, 0, SPIFLASH_BLOCK_SIZE/SPIFLASH_PAGE_SIZE/8); p = spz->vbuf; q = spz->dbuf; for (page = 0; page < SPIFLASH_BLOCK_SIZE/SPIFLASH_PAGE_SIZE; page++) { enum flashmem_status status; switch (spiflash_memcmp(p, q, SPIFLASH_PAGE_SIZE)) { case FMS_ERASE: *erase_mask |= UINT32_C(1) << (page >> (SPIFLASH_SECTOR_SHIFT-SPIFLASH_PAGE_SHIFT)); break; case FMS_PROGRAM: prog_mask[page >> 5] |= UINT32_C(1) << (page & 31); break; default: /* Nothing to do! */ break; } p += SPIFLASH_PAGE_SIZE; q += SPIFLASH_PAGE_SIZE; } return 0; } /* Serial Flash Discoverable Parameter Table, see JESD216 */ static int spiflash_get_sfdp(const struct spiflash *flash, void *sfdp) { static const uint8_t cmd_read_sfdp[] = { ROM_READ_SFDP, 0, 0, 0, 0 }; return flash->ops->spi_read(flash->cookie, cmd_read_sfdp, sizeof cmd_read_sfdp, sfdp, SPIFLASH_SFDP_SIZE, flash->param->tshsl); } int spiflash_flash_files(const struct spiflash *flash, void *buf, size_t buflen) { spz_stream _spz; spz_stream * const spz = &_spz; /* For consistency in notation */ int err = 0; enum flashmem_status fs; #if 0 static const uint8_t read_sr_cmd[2] = { ROM_READ_SR1, ROM_READ_SR2 }; uint8_t sr1; uint32_t *sfdp; sfdp = malloc(SPIFLASH_SFDP_SIZE); memset(sfdp, 0, SPIFLASH_SFDP_SIZE); /* Note: SFDP data is littleendian! */ err = spiflash_get_sfdp(flash, sfdp); if (err) return err; for (int i = 0; i < SPIFLASH_SFDP_SIZE; i += 16) { MSG("%04x :", i); for (int j = 0; j < 16; j += 4) { MSG(" %08x", sfdp[(i+j) >> 2]); } MSG("\n"); } if (sfdp[0] != 0x50444653) { MSG("update: invalid SFDP information read\n"); return SPIFLASH_ERR_DETECT; } /* * If the flash is busy, try to reset it */ err = spiflash_get_status(flash, ROM_READ_SR1, &sr1); if (err) return err; if (sr1 & 0x01) { udelay(60); err = spiflash_get_status(flash, ROM_READ_SR1, &sr1); if (err) return err; if (sr1 & 0x01) { MSG("update: flash busy, trying reset... "); err = spiflash_simple_command(flash, ROM_ENABLE_RESET); if (err) return err; err = spiflash_simple_command(flash, ROM_RESET); if (err) return err; udelay(60); err = spiflash_get_status(flash, ROM_READ_SR1, &sr1); if (err || (sr1 & 0x01)) { MSG("failed\n"); return SPIFLASH_ERR_NOT_READY; } MSG("ok\n"); } } #endif while (!err) { int rv; uint32_t addr; uint32_t data_left; memset(spz, 0, sizeof *spz); spz->zs.avail_in = buflen; spz->zs.next_in = buf; spz->flash = flash; if (spiflash_data_init(spz)) goto err; if (!spz->ibuf) { /* No ibuf allocated, feeding from raw data buffer */ unsigned int bufskip = (spz->header->zlen + sizeof *spz->header + 3) & ~3; if (bufskip >= buflen) { buflen = 0; buf = NULL; } else { buflen -= bufskip; buf += bufskip; } } else { /* Buffer exhausted, additional data read */ buflen = 0; buf = NULL; } data_left = spz->header->dlen; addr = spz->header->address; while (data_left && !spz->err) { unsigned int bytes = 0; unsigned int padding; while (data_left && bytes < SPIFLASH_BLOCK_SIZE) { unsigned int avail = spz->zs.next_out - spz->optr; unsigned int need = SPIFLASH_BLOCK_SIZE - bytes; int rv; if (need > data_left) need = data_left; if (avail) { if (avail > need) avail = need; memcpy(spz->dbuf + bytes, spz->optr, avail); spz->optr += avail; bytes += avail; data_left -= avail; continue; } rv = spz->read_data(spz); if (spz->err) goto err; if (!rv) { spz->err = Z_STREAM_ERROR; /* Input underrun */ goto err; } } if (bytes < SPIFLASH_BLOCK_SIZE) { memset(spz->dbuf + bytes, 0xff, SPIFLASH_BLOCK_SIZE - bytes); } MSG("update: flash block at 0x%06x (%5u bytes):\n", addr, bytes); uint32_t erase_mask; uint32_t prog_mask[SPIFLASH_BLOCK_SIZE >> (SPIFLASH_PAGE_SHIFT+5)]; rv = spiflash_check_block(spz, addr, &erase_mask, prog_mask); if (rv) goto err; if (erase_mask) { rv = spiflash_erase(spz->flash, addr, erase_mask); if (rv) { spz->err = rv; goto err; } rv = spiflash_check_block(spz, addr, &erase_mask, prog_mask); if (spz->err) goto err; if (erase_mask) { MSG("[erase mask = %04x] ", erase_mask); spz->err = SPIFLASH_ERR_ERASE_FAILED; goto err; } } unsigned int page; bool programmed = false; for (page = 0; page < (SPIFLASH_BLOCK_SIZE >> SPIFLASH_PAGE_SHIFT); page++) { uint32_t page_offs = page << SPIFLASH_PAGE_SHIFT; if (!(prog_mask[page >> 5] & (UINT32_C(1) << (page & 31)))) continue; /* No need to program */ programmed = true; udelay(10000); MSG("\rupdate: writing at 0x%06x... ", addr + page_offs); rv = spiflash_program(spz->flash, addr + page_offs, spz->dbuf + page_offs, SPIFLASH_PAGE_SIZE); if (rv) { spz->err = rv; goto err; } rv = spiflash_read(spz->flash, addr + page_offs, spz->vbuf + page_offs, SPIFLASH_PAGE_SIZE); if (rv) { MSG("readback "); goto err; } if (memcmp(spz->dbuf + page_offs, spz->vbuf + page_offs, SPIFLASH_PAGE_SIZE)) { MSG("verify @ 0x%06x ", addr + page_offs); spz->err = SPIFLASH_ERR_PROGRAM_FAILED; goto err; } } if (programmed) MSG("ok\n"); else MSG("update: nothing to write\n"); addr += SPIFLASH_BLOCK_SIZE; } err: err = spiflash_data_cleanup(spz); } if (err == Z_STREAM_END) err = 0; /* End of data is not an error */ if (err) MSG("failed (err %d)\n", err); return err; } /* * Read unique serial number from flash. Note: returns id in * bigendian ("network") byte order. */ int spiflash_read_id(const struct spiflash *flash, void *id) { static const uint8_t read_unique_id[] = { ROM_READ_UNIQUE_ID, 0, 0, 0, 0 }; return flash->ops->spi_read(flash->cookie, read_unique_id, sizeof read_unique_id, id, SPIFLASH_ID_LEN, flash->param->tshsl); } /* * Read vendor and device ID from flash. */ int spiflash_read_vdid(const struct spiflash *flash, void *vdid) { static const uint8_t read_vdid[] = { ROM_MANUFACTURER_DEVICE_ID, 0, 0, 0 }; return flash->ops->spi_read(flash->cookie, read_vdid, sizeof read_vdid, vdid, SPIFLASH_VDID_LEN, flash->param->tshsl); } /* * Flash data from memory buffer(s) */