On a typical end product, a vendor may choose to secure some regions in
the NAND memory which are supposed to stay intact between FW upgrades.
The access to those regions will be blocked by a secure element like
Trustzone. So the normal world software like Linux kernel should not
touch these regions (including reading).
The regions are declared using a NAND chip DT property,
"secure-regions". So let's make use of this property in the raw NAND
core and skip access to the secure regions present in a system.
Signed-off-by: Manivannan Sadhasivam
---
drivers/mtd/nand/raw/nand_base.c | 111 +++
include/linux/mtd/rawnand.h | 4 ++
2 files changed, 115 insertions(+)
diff --git a/drivers/mtd/nand/raw/nand_base.c b/drivers/mtd/nand/raw/nand_base.c
index c33fa1b1847f..d2958549ba46 100644
--- a/drivers/mtd/nand/raw/nand_base.c
+++ b/drivers/mtd/nand/raw/nand_base.c
@@ -278,11 +278,47 @@ static int nand_block_bad(struct nand_chip *chip, loff_t
ofs)
return 0;
}
+/**
+ * nand_check_secure_region() - Check if the region is secured
+ * @chip: NAND chip object
+ * @offset: Offset of the region to check
+ * @size: Size of the region to check
+ *
+ * Checks if the region is secured by comparing the offset and size with the
+ * list of secure regions obtained from DT. Returns -EIO if the region is
+ * secured else 0.
+ */
+static int nand_check_secure_region(struct nand_chip *chip, loff_t offset, u32
size)
+{
+ int i, j;
+
+ /* Skip touching the secure regions if present */
+ for (i = 0, j = 0; i < chip->nr_secure_regions; i++, j += 2) {
+ /* First compare the start offset */
+ if (offset >= chip->secure_regions[j] &&
+ (offset < chip->secure_regions[j] + chip->secure_regions[j
+ 1]))
+ return -EIO;
+ /* ...then offset + size */
+ else if (offset < chip->secure_regions[i] &&
+(offset + size) >= chip->secure_regions[i])
+ return -EIO;
+ }
+
+ return 0;
+}
+
static int nand_isbad_bbm(struct nand_chip *chip, loff_t ofs)
{
+ int ret;
+
if (chip->options & NAND_NO_BBM_QUIRK)
return 0;
+ /* Check if the region is secured */
+ ret = nand_check_secure_region(chip, ofs, 0);
+ if (ret)
+ return ret;
+
if (chip->legacy.block_bad)
return chip->legacy.block_bad(chip, ofs);
@@ -397,6 +433,11 @@ static int nand_do_write_oob(struct nand_chip *chip,
loff_t to,
return -EINVAL;
}
+ /* Check if the region is secured */
+ ret = nand_check_secure_region(chip, to, ops->ooblen);
+ if (ret)
+ return ret;
+
chipnr = (int)(to >> chip->chip_shift);
/*
@@ -565,6 +606,11 @@ static int nand_block_isreserved(struct mtd_info *mtd,
loff_t ofs)
if (!chip->bbt)
return 0;
+
+ /* Check if the region is secured */
+ if (nand_check_secure_region(chip, ofs, 0))
+ return -EIO;
+
/* Return info from the table */
return nand_isreserved_bbt(chip, ofs);
}
@@ -2737,6 +2783,11 @@ static int nand_read_page_swecc(struct nand_chip *chip,
uint8_t *buf,
uint8_t *ecc_code = chip->ecc.code_buf;
unsigned int max_bitflips = 0;
+ /* Check if the region is secured */
+ ret = nand_check_secure_region(chip, ((loff_t)page <<
chip->page_shift), 0);
+ if (ret)
+ return ret;
+
chip->ecc.read_page_raw(chip, buf, 1, page);
for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize)
@@ -3127,6 +3178,11 @@ static int nand_do_read_ops(struct nand_chip *chip,
loff_t from,
int retry_mode = 0;
bool ecc_fail = false;
+ /* Check if the region is secured */
+ ret = nand_check_secure_region(chip, from, readlen);
+ if (ret)
+ return ret;
+
chipnr = (int)(from >> chip->chip_shift);
nand_select_target(chip, chipnr);
@@ -3458,6 +3514,11 @@ static int nand_do_read_oob(struct nand_chip *chip,
loff_t from,
pr_debug("%s: from = 0x%08Lx, len = %i\n",
__func__, (unsigned long long)from, readlen);
+ /* Check if the region is secured */
+ ret = nand_check_secure_region(chip, from, readlen);
+ if (ret)
+ return ret;
+
stats = mtd->ecc_stats;
len = mtd_oobavail(mtd, ops);
@@ -3709,6 +3770,11 @@ static int nand_write_page_swecc(struct nand_chip *chip,
const uint8_t *buf,
uint8_t *ecc_calc = chip->ecc.calc_buf;
const uint8_t *p = buf;
+ /* Check if the region is secured */
+ ret = nand_check_secure_region(chip, ((loff_t)page <<
chip->page_shift), 0);
+ if (ret)
+ return ret;
+
/* Software ECC calculation */
for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize)