julianoes commented on code in PR #18771:
URL: https://github.com/apache/nuttx/pull/18771#discussion_r3121439271
##########
drivers/mtd/w25n.c:
##########
@@ -603,6 +651,324 @@ static void w25n_unprotect(FAR struct w25n_dev_s *priv)
finfo("All blocks unprotected\n");
}
+/****************************************************************************
+ * Name: w25n_read_bbm_lut
+ *
+ * Description:
+ * Read all 20 entries of the hardware Bad Block Management Look-Up Table.
+ * Each entry is encoded as (lba_raw << 16) | pba_raw, where the high two
+ * LBA bits are status flags (Enable, Invalid).
+ * See datasheet section 8.2.8.
+ *
+ ****************************************************************************/
+
+static void w25n_read_bbm_lut(FAR struct w25n_dev_s *priv)
+{
+ int i;
+
+ SPI_SELECT(priv->spi, SPIDEV_FLASH(priv->spi_devid), true);
+ SPI_SEND(priv->spi, W25N_READ_BBM_LUT);
+ SPI_SEND(priv->spi, W25N_DUMMY);
+
+ /* Decode 4 bytes at a time straight into priv->bbm to avoid an 80-byte
+ * temporary on the stack. The runtime remap path is called from the
+ * logger thread and we have to keep this lean.
+ */
+
+ for (i = 0; i < W25N_BBM_LUT_ENTRIES; i++)
+ {
+ uint8_t b[4];
+
+ SPI_RECVBLOCK(priv->spi, b, 4);
+ priv->bbm[i] = ((uint32_t)b[0] << 24) |
+ ((uint32_t)b[1] << 16) |
+ ((uint32_t)b[2] << 8) |
+ (uint32_t)b[3];
+ }
+
+ SPI_SELECT(priv->spi, SPIDEV_FLASH(priv->spi_devid), false);
+}
+
+/****************************************************************************
+ * Name: w25n_bbm_swap
+ *
+ * Description:
+ * Add an LBA->PBA link to the chip's non-volatile BBM LUT. Subsequent
+ * accesses to the LBA are transparently routed to the PBA by the chip.
+ *
+ ****************************************************************************/
+
+static int w25n_bbm_swap(FAR struct w25n_dev_s *priv,
+ uint16_t lba, uint16_t pba)
+{
+ int ret;
+
+ /* Last-line sanity check: a bad LBA or PBA here would burn a LUT slot
+ * permanently. Refuse to issue A1h unless LBA is in the user area and
+ * PBA is in the spare pool.
+ */
+
+ if (lba >= W25N_USER_BLOCKS ||
+ pba < W25N_USER_BLOCKS || pba >= W25N01GV_BLOCKS)
+ {
+ ferr("ERROR: BBM swap rejected: LBA=%u PBA=%u\n", lba, pba);
+ return -EINVAL;
+ }
+
+ w25n_writeenable(priv);
+
+ SPI_SELECT(priv->spi, SPIDEV_FLASH(priv->spi_devid), true);
+ SPI_SEND(priv->spi, W25N_BBM_SWAP);
+ SPI_SEND(priv->spi, (lba >> 8) & 0xff);
+ SPI_SEND(priv->spi, lba & 0xff);
+ SPI_SEND(priv->spi, (pba >> 8) & 0xff);
+ SPI_SEND(priv->spi, pba & 0xff);
+ SPI_SELECT(priv->spi, SPIDEV_FLASH(priv->spi_devid), false);
+
+ ret = w25n_waitready(priv);
+ if (ret < 0)
+ {
+ return ret;
+ }
+
+ if (w25n_read_status(priv, W25N_SR3_ADDR) & W25N_SR3_LUTF)
+ {
+ finfo("BBM LUT is now full\n");
+ }
+
+ return OK;
+}
+
+/****************************************************************************
+ * Name: w25n_read_oob_marker
+ *
+ * Description:
+ * Read byte 0 of the spare area of the first page of `block`. The factory
+ * marks bad blocks with a non-FFh value here. Returns the marker value
+ * (0..255), or 0x00 if the read failed (treated as bad).
+ *
+ ****************************************************************************/
+
+static int w25n_read_oob_marker(FAR struct w25n_dev_s *priv, uint16_t block)
+{
+ uint32_t page = (uint32_t)block << 6;
+ uint8_t marker = 0xff;
+ int ret;
+
+ ret = w25n_read_page(priv, page);
+ if (ret < 0 && ret != -EIO)
+ {
+ return 0x00;
+ }
+
+ w25n_read_buffer(priv, W25N_OOB_BBM_COL, &marker, 1);
+
+ if (ret == -EIO)
+ {
+ /* Uncorrectable ECC on the first page of the block: treat as bad. */
+
+ return 0x00;
+ }
+
+ return marker;
+}
+
+/****************************************************************************
+ * Name: w25n_is_factory_bad
+ ****************************************************************************/
+
+static bool w25n_is_factory_bad(FAR struct w25n_dev_s *priv, uint16_t block)
+{
+ return w25n_read_oob_marker(priv, block) != 0xff;
+}
+
+/****************************************************************************
+ * Name: w25n_pick_free_spare
+ *
+ * Description:
+ * Find an unused, non-factory-bad block in the spare pool that is not
+ * already a remap target in the LUT. Returns the block index, or
+ * 0xffff if none is available.
+ *
+ ****************************************************************************/
+
+static uint16_t w25n_pick_free_spare(FAR struct w25n_dev_s *priv)
+{
+ uint16_t b;
+ int i;
+
+ for (b = W25N_USER_BLOCKS; b < W25N01GV_BLOCKS; b++)
+ {
+ bool taken = false;
+
+ for (i = 0; i < W25N_BBM_LUT_ENTRIES; i++)
+ {
+ uint16_t lba_raw = priv->bbm[i] >> 16;
+ uint16_t pba = priv->bbm[i] & W25N_BBM_BLOCK_MASK;
+
+ if ((lba_raw & W25N_BBM_LBA_ENABLE) &&
+ !(lba_raw & W25N_BBM_LBA_INVALID) &&
+ pba == b)
+ {
+ taken = true;
+ break;
+ }
+ }
+
+ if (taken)
+ {
+ continue;
+ }
+
+ if (w25n_is_factory_bad(priv, b))
+ {
+ continue;
+ }
+
+ /* Active proof that the block actually works: erase it. The factory
+ * OOB marker can be missing or corrupted on a block that is still
+ * unusable (worn-out, latent defect). Burning a precious LUT slot on
+ * a dead spare would be permanent, so we verify before committing.
+ * The spare is virgin space - erasing it has no data cost.
+ */
+
+ if (w25n_block_erase(priv, b) != OK)
+ {
+ syslog(LOG_WARNING,
+ "[w25n] spare %u failed erase test, skipping\n", b);
+ continue;
+ }
+
+ return b;
+ }
+
+ return 0xffff;
+}
+
+/****************************************************************************
+ * Name: w25n_remap_bad_block
+ *
+ * Description:
+ * Allocate a spare and add a BBM LUT entry mapping `block` to it.
+ * Returns OK if the chip will now route accesses to `block` to a good
+ * spare, or a negative errno if no spare is available or the LUT is full.
+ *
+ ****************************************************************************/
+
+static int w25n_remap_bad_block(FAR struct w25n_dev_s *priv, uint16_t block)
+{
+ uint16_t spare;
+
+ if (w25n_read_status(priv, W25N_SR3_ADDR) & W25N_SR3_LUTF)
+ {
+ ferr("ERROR: BBM LUT full, cannot remap block %u\n", block);
+ return -ENOSPC;
+ }
+
+ w25n_read_bbm_lut(priv);
+
+ spare = w25n_pick_free_spare(priv);
+ if (spare == 0xffff)
+ {
+ ferr("ERROR: No free spare available to remap block %u\n", block);
+ return -ENOSPC;
+ }
+
+ syslog(LOG_INFO, "[w25n] remap block %u -> spare %u\n", block, spare);
+ return w25n_bbm_swap(priv, block, spare);
+}
+
+/****************************************************************************
+ * Name: w25n_scan_factory_bad
+ *
+ * Description:
+ * Scan all blocks for factory bad markers and remap any user-area bad
+ * blocks via the chip's BBM LUT. Idempotent across reboots: blocks that
+ * are already remapped (present in the LUT) are skipped, so repeated
+ * scans don't consume LUT slots. Takes ~200 ms (1024 OOB reads).
+ *
+ ****************************************************************************/
+
+static void w25n_scan_factory_bad(FAR struct w25n_dev_s *priv)
+{
+ uint16_t block;
+ int factory_bad = 0;
+ int remapped = 0;
+ int unremapped = 0;
+ int already_in_lut = 0;
+ int lut_used = 0;
+ int i;
+
+ w25n_read_bbm_lut(priv);
+
+ for (i = 0; i < W25N_BBM_LUT_ENTRIES; i++)
+ {
+ uint16_t lba_raw = priv->bbm[i] >> 16;
+ if (lba_raw & W25N_BBM_LBA_ENABLE)
+ {
+ lut_used++;
+ if (!(lba_raw & W25N_BBM_LBA_INVALID) &&
+ (lba_raw & W25N_BBM_BLOCK_MASK) < W25N_USER_BLOCKS)
+ {
+ already_in_lut++;
+ }
+ }
+ }
+
+ for (block = 0; block < W25N_USER_BLOCKS; block++)
+ {
+ bool already_mapped = false;
+
+ for (i = 0; i < W25N_BBM_LUT_ENTRIES; i++)
+ {
+ uint16_t lba_raw = priv->bbm[i] >> 16;
+
+ if ((lba_raw & W25N_BBM_LBA_ENABLE) &&
+ !(lba_raw & W25N_BBM_LBA_INVALID) &&
+ (lba_raw & W25N_BBM_BLOCK_MASK) == block)
+ {
+ already_mapped = true;
+ break;
+ }
+ }
+
+ if (already_mapped)
+ {
+ /* Already remapped on a previous boot, the chip handles routing. */
+
+ continue;
+ }
+
+ if (!w25n_is_factory_bad(priv, block))
+ {
+ continue;
+ }
+
+ factory_bad++;
+
+ if (w25n_read_status(priv, W25N_SR3_ADDR) & W25N_SR3_LUTF)
+ {
+ unremapped++;
+ continue;
+ }
+
+ if (w25n_remap_bad_block(priv, block) == OK)
+ {
+ remapped++;
+ }
+ else
+ {
+ unremapped++;
+ }
+ }
+
+ syslog(LOG_INFO,
Review Comment:
Do drivers ever print information like that or is that only ever for
debugging with `finfo`?
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]