Unverified Commit 2d284768 authored by Jungseung Lee's avatar Jungseung Lee Committed by Tudor Ambarus
Browse files

mtd: spi-nor: Add generic formula for SR block protection handling

The current mainline locking was restricted and could only be applied
to flashes that have 3 block protection bits and fixed locking ratio.

A new method of normalization was reached at the end of the discussion [1].

    (1) - if bp slot is insufficient.
    (2) - if bp slot is sufficient.

    if (bp_slots_needed > bp_slots)    // (1)
        min_prot_length = sector_size << (bp_slots_needed - bp_slots);
    else                               // (2)
        min_prot_length = sector_size;

This patch changes logic to handle block protection based on min_prot_length.
It is suitable for the overall flashes with exception of some corner cases
(see EON and catalyst) and easy to extend and apply for the case of 2bit or
4bit block protection.

[1] http://lists.infradead.org/pipermail/linux-mtd/2020-February/093934.html



Signed-off-by: default avatarJungseung Lee <js07.lee@samsung.com>
Reviewed-by: default avatarMichael Walle <michael@walle.cc>
Tested-by: default avatarMichael Walle <michael@walle.cc>
Signed-off-by: default avatarTudor Ambarus <tudor.ambarus@microchip.com>
parent b0e2d252
Loading
Loading
Loading
Loading
+41 −31
Original line number Diff line number Diff line
@@ -1536,30 +1536,52 @@ erase_err:
	return ret;
}

static u64 spi_nor_get_min_prot_length_sr(struct spi_nor *nor)
{
	unsigned int bp_slots, bp_slots_needed;
	u8 mask = SR_BP2 | SR_BP1 | SR_BP0;

	/* Reserved one for "protect none" and one for "protect all". */
	bp_slots = (mask >> SR_BP_SHIFT) + 1 - 2;
	bp_slots_needed = ilog2(nor->info->n_sectors);

	if (bp_slots_needed > bp_slots)
		return nor->info->sector_size <<
			(bp_slots_needed - bp_slots);
	else
		return nor->info->sector_size;
}

static void spi_nor_get_locked_range_sr(struct spi_nor *nor, u8 sr, loff_t *ofs,
					uint64_t *len)
{
	struct mtd_info *mtd = &nor->mtd;
	u64 min_prot_len;
	u8 mask = SR_BP2 | SR_BP1 | SR_BP0;
	u8 tb_mask = SR_TB_BIT5;
	int pow;
	u8 bp = (sr & mask) >> SR_BP_SHIFT;

	if (nor->flags & SNOR_F_HAS_SR_TB_BIT6)
		tb_mask = SR_TB_BIT6;

	if (!(sr & mask)) {
	if (!bp) {
		/* No protection */
		*ofs = 0;
		*len = 0;
	} else {
		pow = ((sr & mask) ^ mask) >> SR_BP_SHIFT;
		*len = mtd->size >> pow;
		return;
	}

	min_prot_len = spi_nor_get_min_prot_length_sr(nor);
	*len = min_prot_len << (bp - 1);

	if (*len > mtd->size)
		*len = mtd->size;

	if (nor->flags & SNOR_F_HAS_SR_TB && sr & tb_mask)
		*ofs = 0;
	else
		*ofs = mtd->size - *len;
}
}

/*
 * Return 1 if the entire region is locked (if @locked is true) or unlocked (if
@@ -1631,6 +1653,7 @@ static int spi_nor_is_unlocked_sr(struct spi_nor *nor, loff_t ofs, uint64_t len,
static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, uint64_t len)
{
	struct mtd_info *mtd = &nor->mtd;
	u64 min_prot_len;
	int ret, status_old, status_new;
	u8 mask = SR_BP2 | SR_BP1 | SR_BP0;
	u8 tb_mask = SR_TB_BIT5;
@@ -1673,20 +1696,12 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, uint64_t len)
	if (nor->flags & SNOR_F_HAS_SR_TB_BIT6)
		tb_mask = SR_TB_BIT6;

	/*
	 * Need smallest pow such that:
	 *
	 *   1 / (2^pow) <= (len / size)
	 *
	 * so (assuming power-of-2 size) we do:
	 *
	 *   pow = ceil(log2(size / len)) = log2(size) - floor(log2(len))
	 */
	if (lock_len == mtd->size) {
		val = mask;
	} else {
		pow = ilog2(mtd->size) - ilog2(lock_len);
		val = mask - (pow << SR_BP_SHIFT);
		min_prot_len = spi_nor_get_min_prot_length_sr(nor);
		pow = ilog2(lock_len) - ilog2(min_prot_len) + 1;
		val = pow << SR_BP_SHIFT;

		if (val & ~mask)
			return -EINVAL;
@@ -1723,6 +1738,7 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, uint64_t len)
static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len)
{
	struct mtd_info *mtd = &nor->mtd;
	u64 min_prot_len;
	int ret, status_old, status_new;
	u8 mask = SR_BP2 | SR_BP1 | SR_BP0;
	u8 tb_mask = SR_TB_BIT5;
@@ -1764,20 +1780,14 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len)

	if (nor->flags & SNOR_F_HAS_SR_TB_BIT6)
		tb_mask = SR_TB_BIT6;
	/*
	 * Need largest pow such that:
	 *
	 *   1 / (2^pow) >= (len / size)
	 *
	 * so (assuming power-of-2 size) we do:
	 *
	 *   pow = floor(log2(size / len)) = log2(size) - ceil(log2(len))
	 */
	pow = ilog2(mtd->size) - order_base_2(lock_len);

	if (lock_len == 0) {
		val = 0; /* fully unlocked */
	} else {
		val = mask - (pow << SR_BP_SHIFT);
		min_prot_len = spi_nor_get_min_prot_length_sr(nor);
		pow = ilog2(lock_len) - ilog2(min_prot_len) + 1;
		val = pow << SR_BP_SHIFT;

		/* Some power-of-two sizes are not supported */
		if (val & ~mask)
			return -EINVAL;