return 0;
}
+static u8 spi_nor_get_sr_cmp_mask(struct spi_nor *nor)
+{
+ if (!(nor->flags & SNOR_F_NO_READ_CR) &&
+ nor->flags & SNOR_F_HAS_SR2_CMP_BIT6)
+ return SR2_CMP_BIT6;
+ else
+ return 0;
+}
+
u64 spi_nor_get_min_prot_length_sr(struct spi_nor *nor)
{
unsigned int bp_slots, bp_slots_needed;
u64 min_prot_len;
u8 bp_mask = spi_nor_get_sr_bp_mask(nor);
u8 tb_mask = spi_nor_get_sr_tb_mask(nor);
+ u8 cmp_mask = spi_nor_get_sr_cmp_mask(nor);
u8 bp, val = sr[0] & bp_mask;
bool tb = (nor->flags & SNOR_F_HAS_SR_TB) ? sr[0] & tb_mask : 0;
+ bool cmp = sr[1] & cmp_mask;
if (nor->flags & SNOR_F_HAS_SR_BP3_BIT6 && val & SR_BP3_BIT6)
val = (val & ~SR_BP3_BIT6) | SR_BP3;
bp = val >> SR_BP_SHIFT;
if (!bp) {
- /* No protection */
- *ofs = 0;
- *len = 0;
+ if (!cmp) {
+ /* No protection */
+ *ofs = 0;
+ *len = 0;
+ } else {
+ /* Full protection */
+ *ofs = 0;
+ *len = nor->params->size;
+ }
return;
}
min_prot_len = spi_nor_get_min_prot_length_sr(nor);
*len = min_prot_len << (bp - 1);
-
if (*len > nor->params->size)
*len = nor->params->size;
- if (tb)
- *ofs = 0;
- else
- *ofs = nor->params->size - *len;
+ if (cmp)
+ *len = nor->params->size - *len;
+
+ if (!cmp) {
+ if (tb)
+ *ofs = 0;
+ else
+ *ofs = nor->params->size - *len;
+ } else {
+ if (tb)
+ *ofs = nor->params->size - *len;
+ else
+ *ofs = 0;
+ }
}
/*
}
static int spi_nor_build_sr(struct spi_nor *nor, const u8 *old_sr, u8 *new_sr,
- u8 pow, bool use_top)
+ u8 pow, bool use_top, bool cmp)
{
u8 bp_mask = spi_nor_get_sr_bp_mask(nor);
u8 tb_mask = spi_nor_get_sr_tb_mask(nor);
+ u8 cmp_mask = spi_nor_get_sr_cmp_mask(nor);
int ret;
new_sr[0] = old_sr[0] & ~bp_mask & ~tb_mask;
+ new_sr[1] = old_sr[1] & ~cmp_mask;
/* Build BP field */
ret = spi_nor_sr_set_bp_mask(nor, &new_sr[0], pow);
return ret;
/* Build TB field */
- if (!use_top)
+ if ((!cmp && !use_top) || (cmp && use_top))
new_sr[0] |= tb_mask;
+ /* Build CMP field */
+ if (cmp)
+ new_sr[1] |= cmp_mask;
+
return 0;
}
{
u8 bp_mask = spi_nor_get_sr_bp_mask(nor);
u8 tb_mask = spi_nor_get_sr_tb_mask(nor);
+ u8 cmp_mask = spi_nor_get_sr_cmp_mask(nor);
+ u8 sr_cr[2] = {};
+
if (!sr) {
if (spi_nor_read_sr(nor, nor->bouncebuf))
return;
- sr = nor->bouncebuf;
+ sr_cr[0] = nor->bouncebuf[0];
+
+ if (!(nor->flags & SNOR_F_NO_READ_CR)) {
+ if (spi_nor_read_cr(nor, nor->bouncebuf))
+ return;
+ }
+
+ sr_cr[1] = nor->bouncebuf[0];
+ sr = sr_cr;
}
nor->dfs_sr_cache[0] = sr[0] & (bp_mask | tb_mask | SR_SRWD);
+ nor->dfs_sr_cache[1] = sr[1] & cmp_mask;
}
/*
* register
* (SR). Does not support these features found in newer SR bitfields:
* - SEC: sector/block protect - only handle SEC=0 (block protect)
- * - CMP: complement protect - only support CMP=0 (range is not complemented)
*
* Support for the following is provided conditionally for some flash:
* - TB: top/bottom protect
+ * - CMP: complement protect (BP and TP describe the unlocked part, while
+ * the reminder is locked)
*
* Sample table portion for 8MB flash (Winbond w25q64fw):
*
static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len)
{
u64 min_prot_len = spi_nor_get_min_prot_length_sr(nor);
- u8 status_old[1] = {}, status_new[1] = {};
- loff_t ofs_old, ofs_new;
- u64 len_old, len_new;
+ u8 status_old[2] = {}, status_new[2] = {}, status_new_cmp[2] = {};
+ u8 *best_status_new = status_new;
+ loff_t ofs_old, ofs_new, ofs_new_cmp;
+ u64 len_old, len_new, len_new_cmp;
loff_t lock_len;
- bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB;
+ bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB,
+ can_be_cmp = spi_nor_get_sr_cmp_mask(nor);
bool use_top;
int ret;
u8 pow;
status_old[0] = nor->bouncebuf[0];
+ if (!(nor->flags & SNOR_F_NO_READ_CR)) {
+ ret = spi_nor_read_cr(nor, nor->bouncebuf);
+ if (ret)
+ return ret;
+
+ status_old[1] = nor->bouncebuf[0];
+ }
+
/* If nothing in our range is unlocked, we don't need to do anything */
if (spi_nor_is_locked_sr(nor, ofs, len, status_old))
return 0;
else
pow = ilog2(lock_len) - ilog2(min_prot_len) + 1;
- ret = spi_nor_build_sr(nor, status_old, status_new, pow, use_top);
+ ret = spi_nor_build_sr(nor, status_old, status_new, pow, use_top, false);
if (ret)
return ret;
+ /*
+ * In case the region asked is not fully met, maybe we can try with the
+ * complement feature
+ */
+ spi_nor_get_locked_range_sr(nor, status_new, &ofs_new, &len_new);
+ if (can_be_cmp && len_new != lock_len) {
+ pow = ilog2(nor->params->size - lock_len) - ilog2(min_prot_len) + 1;
+ ret = spi_nor_build_sr(nor, status_old, status_new_cmp, pow, use_top, true);
+ if (ret)
+ return ret;
+
+ /*
+ * ilog2() "floors" the result, which means in some cases we may have to
+ * manually reduce the scope when the complement feature is used.
+ * The uAPI is to never lock more than what is requested, but less is accepted.
+ * Make sure we are not covering a too wide range, reduce it otherwise.
+ */
+ spi_nor_get_locked_range_sr(nor, status_new_cmp, &ofs_new_cmp, &len_new_cmp);
+ if (len_new_cmp > lock_len) {
+ pow++;
+ ret = spi_nor_build_sr(nor, status_old, status_new_cmp, pow, use_top, true);
+ if (ret)
+ return ret;
+ }
+
+ /* Pick the CMP configuration if we cover a closer range */
+ spi_nor_get_locked_range_sr(nor, status_new, &ofs_new, &len_new);
+ spi_nor_get_locked_range_sr(nor, status_new_cmp, &ofs_new_cmp, &len_new_cmp);
+ if (len_new_cmp <= lock_len &&
+ (lock_len - len_new_cmp) < (lock_len - len_new))
+ best_status_new = status_new_cmp;
+ }
+
/*
* Disallow further writes if WP# pin is neither left floating nor
* wrongly tied to GND (that includes internal pull-downs).
* WP# pin hard strapped to GND can be a valid use case.
*/
if (!(nor->flags & SNOR_F_NO_WP))
- status_new[0] |= SR_SRWD;
+ best_status_new[0] |= SR_SRWD;
spi_nor_get_locked_range_sr(nor, status_old, &ofs_old, &len_old);
- spi_nor_get_locked_range_sr(nor, status_new, &ofs_new, &len_new);
+ spi_nor_get_locked_range_sr(nor, best_status_new, &ofs_new, &len_new);
/* Don't "lock" with no region! */
if (!len_new)
return -EINVAL;
/* Don't bother if they're the same */
- if (status_new[0] == status_old[0])
+ if (best_status_new[0] == status_old[0] && best_status_new[1] == status_old[1])
return 0;
/* Only modify protection if it will not unlock other areas */
(ofs_old < ofs_new || (ofs_new + len_new) < (ofs_old + len_old)))
return -EINVAL;
- ret = spi_nor_write_sr_and_check(nor, status_new[0]);
+ if (nor->flags & SNOR_F_NO_READ_CR)
+ ret = spi_nor_write_sr_and_check(nor, best_status_new[0]);
+ else
+ ret = spi_nor_write_sr_cr_and_check(nor, best_status_new);
if (ret)
return ret;
- spi_nor_cache_sr_lock_bits(nor, status_new);
+ spi_nor_cache_sr_lock_bits(nor, best_status_new);
return 0;
}
static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len)
{
u64 min_prot_len = spi_nor_get_min_prot_length_sr(nor);
- u8 status_old[1], status_new[1];
- loff_t ofs_old, ofs_new;
- u64 len_old, len_new;
+ u8 status_old[2] = {}, status_new[2] = {}, status_new_cmp[2] = {};
+ u8 *best_status_new = status_new;
+ loff_t ofs_old, ofs_new, ofs_new_cmp;
+ u64 len_old, len_new, len_new_cmp;
loff_t lock_len;
- bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB;
+ bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB,
+ can_be_cmp = spi_nor_get_sr_cmp_mask(nor);
bool use_top;
int ret;
u8 pow;
status_old[0] = nor->bouncebuf[0];
+ if (!(nor->flags & SNOR_F_NO_READ_CR)) {
+ ret = spi_nor_read_cr(nor, nor->bouncebuf);
+ if (ret)
+ return ret;
+
+ status_old[1] = nor->bouncebuf[0];
+ }
+
/* If nothing in our range is locked, we don't need to do anything */
if (spi_nor_is_unlocked_sr(nor, ofs, len, status_old))
return 0;
else
pow = ilog2(lock_len) - ilog2(min_prot_len) + 1;
- ret = spi_nor_build_sr(nor, status_old, status_new, pow, use_top);
+ ret = spi_nor_build_sr(nor, status_old, status_new, pow, use_top, false);
if (ret)
return ret;
+ /*
+ * In case the region asked is not fully met, maybe we can try with the
+ * complement feature
+ */
+ spi_nor_get_locked_range_sr(nor, status_new, &ofs_new, &len_new);
+ if (can_be_cmp && len_new != lock_len) {
+ pow = ilog2(nor->params->size - lock_len) - ilog2(min_prot_len) + 1;
+ ret = spi_nor_build_sr(nor, status_old, status_new_cmp, pow, use_top, true);
+ if (ret)
+ return ret;
+
+ /*
+ * ilog2() "floors" the result, which means in some cases we may have to
+ * manually reduce the scope when the complement feature is used.
+ * The uAPI is to never unlock more than what is requested, but less is accepted.
+ * Make sure we are not covering a too small range, increase it otherwise.
+ */
+ spi_nor_get_locked_range_sr(nor, status_new_cmp, &ofs_new_cmp, &len_new_cmp);
+ if (len_new_cmp < lock_len) {
+ pow--;
+ ret = spi_nor_build_sr(nor, status_old, status_new_cmp, pow, use_top, true);
+ if (ret)
+ return ret;
+ }
+
+ /* Pick the CMP configuration if we cover a closer range */
+ spi_nor_get_locked_range_sr(nor, status_new, &ofs_new, &len_new);
+ spi_nor_get_locked_range_sr(nor, status_new_cmp, &ofs_new_cmp, &len_new_cmp);
+ if (len_new_cmp <= lock_len &&
+ (lock_len - len_new_cmp) < (lock_len - len_new))
+ best_status_new = status_new_cmp;
+ }
+
/* Don't protect status register if we're fully unlocked */
if (lock_len == 0)
- status_new[0] &= ~SR_SRWD;
+ best_status_new[0] &= ~SR_SRWD;
/* Don't bother if they're the same */
- if (status_new[0] == status_old[0])
+ if (best_status_new[0] == status_old[0] && best_status_new[1] == status_old[1])
return 0;
/* Only modify protection if it will not lock other areas */
spi_nor_get_locked_range_sr(nor, status_old, &ofs_old, &len_old);
- spi_nor_get_locked_range_sr(nor, status_new, &ofs_new, &len_new);
+ spi_nor_get_locked_range_sr(nor, best_status_new, &ofs_new, &len_new);
if (len_old && len_new &&
(ofs_new < ofs_old || (ofs_old + len_old) < (ofs_new + len_new)))
return -EINVAL;
- ret = spi_nor_write_sr_and_check(nor, status_new[0]);
+ if (nor->flags & SNOR_F_NO_READ_CR)
+ ret = spi_nor_write_sr_and_check(nor, best_status_new[0]);
+ else
+ ret = spi_nor_write_sr_cr_and_check(nor, best_status_new);
if (ret)
return ret;
- spi_nor_cache_sr_lock_bits(nor, status_new);
+ spi_nor_cache_sr_lock_bits(nor, best_status_new);
return 0;
}
*/
static int spi_nor_sr_is_locked(struct spi_nor *nor, loff_t ofs, u64 len)
{
+ u8 sr_cr[2] = {};
int ret;
ret = spi_nor_read_sr(nor, nor->bouncebuf);
if (ret)
return ret;
- return spi_nor_is_locked_sr(nor, ofs, len, nor->bouncebuf);
+ sr_cr[0] = nor->bouncebuf[0];
+
+ if (!(nor->flags & SNOR_F_NO_READ_CR)) {
+ ret = spi_nor_read_cr(nor, nor->bouncebuf);
+ if (ret)
+ return ret;
+
+ sr_cr[1] = nor->bouncebuf[0];
+ }
+
+ return spi_nor_is_locked_sr(nor, ofs, len, sr_cr);
}
static const struct spi_nor_locking_ops spi_nor_sr_locking_ops = {