diff mbox series

[v3] mtd: intel-spi: add is_protected and is_bios_locked knobs

Message ID 20210411143247.3045467-1-tomas.winkler@intel.com
State New
Delegated to: Ambarus Tudor
Headers show
Series [v3] mtd: intel-spi: add is_protected and is_bios_locked knobs | expand

Commit Message

Winkler, Tomas April 11, 2021, 2:32 p.m. UTC
From: Tamar Mashiah <tamar.mashiah@intel.com>

The manufacturing access to the PCH/SOC SPI device is traditionally
performed via user space driver accessing registers via /dev/mem
but due to security concerns /dev/mem access is being much restricted,
hence the reason for utilizing dedicated Intel PCH/SOC SPI controller
driver, which is already implemented in the Linux kernel.

Intel PCH/SOC SPI controller protects the flash storage via two
mechanisms one is the via region protection registers and second
via BIOS lock.

The device always boots with bios lock set, but during manufacturing
the bios lock has to be lifted in order to enable the write access.
So we add sysfs file (spi_bios_lock) to be able to do it on demand.
The bios lock is automatically attempted to be lifted by the driver
during initialization, this part is dropped.

Second, also the region protection status is exposed via sysfs file
as the manufacturing will need the both files in order to validate
that the device is properly sealed.

Cc: Joe Perches <joe@perches.com>
Cc: Mika Westerberg <mika.westerberg@linux.intel.com>
Signed-off-by: Tamar Mashiah <tamar.mashiah@intel.com>
Signed-off-by: Tomas Winkler <tomas.winkler@intel.com>
Reviewed-by: Mika Westerberg <mika.westerberg@linux.intel.com>
---
V2: Add a newline to the string format in sysfs_emit() (Joe Perches <joe@perches.com>)
V3: Resend adding new  MTD SPI-NOR maintainers

 .../ABI/testing/sysfs-devices-intel-spi       | 16 ++++
 MAINTAINERS                                   |  1 +
 drivers/mfd/lpc_ich.c                         | 37 ++++++-
 .../mtd/spi-nor/controllers/intel-spi-pci.c   | 38 ++++++--
 drivers/mtd/spi-nor/controllers/intel-spi.c   | 96 ++++++++++++++++---
 include/linux/platform_data/x86/intel-spi.h   |  6 +-
 6 files changed, 167 insertions(+), 27 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-devices-intel-spi
diff mbox series

Patch

diff --git a/Documentation/ABI/testing/sysfs-devices-intel-spi b/Documentation/ABI/testing/sysfs-devices-intel-spi
new file mode 100644
index 000000000000..515c36f8b5cf
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-devices-intel-spi
@@ -0,0 +1,16 @@ 
+What:		/sys/devices/.../is_protected
+Date:		April 2021
+Contact:	Tomas Winkler <tomas.winkler@intel.com>
+Description:
+		The /sys/devices/.../is_protected attribute allows the user
+		space to check if the intel spi controller is write protected
+		from the host.
+
+What:		/sys/devices/.../bios_lock
+Date:		April 2021
+Contact:	Tomas Winkler <tomas.winkler@intel.com>
+Description:
+		The /sys/devices/.../bios_lock attribute allows the user
+		space to check if the intel spi controller is locked by bios
+		for writes. It is possible to unlock the bios lock by writing
+		"unlock" to the file.
diff --git a/MAINTAINERS b/MAINTAINERS
index ba561e5bc6f0..37c934f34ed7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11571,6 +11571,7 @@  Q:	http://patchwork.ozlabs.org/project/linux-mtd/list/
 C:	irc://irc.oftc.net/mtd
 T:	git git://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux.git mtd/fixes
 T:	git git://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux.git mtd/next
+F:	Documentation/ABI/testing/sysfs-devices-intel-spi
 F:	Documentation/devicetree/bindings/mtd/
 F:	drivers/mtd/
 F:	include/linux/mtd/
diff --git a/drivers/mfd/lpc_ich.c b/drivers/mfd/lpc_ich.c
index 3bbb29a7e7a5..eceaf11bec93 100644
--- a/drivers/mfd/lpc_ich.c
+++ b/drivers/mfd/lpc_ich.c
@@ -1083,12 +1083,39 @@  static int lpc_ich_init_wdt(struct pci_dev *dev)
 	return ret;
 }
 
+static int lcp_ich_bios_unlock(struct device *dev)
+{
+	struct pci_dev *pdev = to_pci_dev(dev);
+	u32 bcr = 0;
+
+	pci_read_config_dword(pdev, BCR, &bcr);
+	if (!(bcr & BCR_WPD)) {
+		bcr |= BCR_WPD;
+		pci_write_config_dword(pdev, BCR, bcr);
+		pci_read_config_dword(pdev, BCR, &bcr);
+	}
+
+	if (!(bcr & BCR_WPD))
+		return -EIO;
+
+	return 0;
+}
+
+static bool lcp_ich_is_bios_locked(struct device *dev)
+{
+	struct pci_dev *pdev = to_pci_dev(dev);
+	u32 bcr = 0;
+
+	pci_read_config_dword(pdev, BCR, &bcr);
+	return !(bcr & BCR_WPD);
+}
+
 static int lpc_ich_init_spi(struct pci_dev *dev)
 {
 	struct lpc_ich_priv *priv = pci_get_drvdata(dev);
 	struct resource *res = &intel_spi_res[0];
 	struct intel_spi_boardinfo *info;
-	u32 spi_base, rcba, bcr;
+	u32 spi_base, rcba;
 
 	info = devm_kzalloc(&dev->dev, sizeof(*info), GFP_KERNEL);
 	if (!info)
@@ -1112,8 +1139,8 @@  static int lpc_ich_init_spi(struct pci_dev *dev)
 			res->start = spi_base + SPIBASE_LPT;
 			res->end = res->start + SPIBASE_LPT_SZ - 1;
 
-			pci_read_config_dword(dev, BCR, &bcr);
-			info->writeable = !!(bcr & BCR_WPD);
+			info->is_bios_locked = lcp_ich_is_bios_locked;
+			info->bios_unlock = lcp_ich_bios_unlock;
 		}
 		break;
 
@@ -1134,8 +1161,8 @@  static int lpc_ich_init_spi(struct pci_dev *dev)
 			res->start = spi_base & 0xfffffff0;
 			res->end = res->start + SPIBASE_APL_SZ - 1;
 
-			pci_bus_read_config_dword(bus, spi, BCR, &bcr);
-			info->writeable = !!(bcr & BCR_WPD);
+			info->is_bios_locked = lcp_ich_is_bios_locked;
+			info->bios_unlock = lcp_ich_bios_unlock;
 		}
 
 		pci_bus_write_config_byte(bus, p2sb, 0xe1, 0x1);
diff --git a/drivers/mtd/spi-nor/controllers/intel-spi-pci.c b/drivers/mtd/spi-nor/controllers/intel-spi-pci.c
index 825610a2e9dc..f83f352f874d 100644
--- a/drivers/mtd/spi-nor/controllers/intel-spi-pci.c
+++ b/drivers/mtd/spi-nor/controllers/intel-spi-pci.c
@@ -24,12 +24,38 @@  static const struct intel_spi_boardinfo cnl_info = {
 	.type = INTEL_SPI_CNL,
 };
 
+static int intel_spi_pci_bios_unlock(struct device *dev)
+{
+	struct pci_dev *pdev = to_pci_dev(dev);
+	u32 bcr = 0;
+
+	pci_read_config_dword(pdev, BCR, &bcr);
+	if (!(bcr & BCR_WPD)) {
+		bcr |= BCR_WPD;
+		pci_write_config_dword(pdev, BCR, bcr);
+		pci_read_config_dword(pdev, BCR, &bcr);
+	}
+
+	if (!(bcr & BCR_WPD))
+		return -EIO;
+
+	return 0;
+}
+
+static bool intel_spi_pci_is_bios_locked(struct device *dev)
+{
+	struct pci_dev *pdev = to_pci_dev(dev);
+	u32 bcr = 0;
+
+	pci_read_config_dword(pdev, BCR, &bcr);
+	return !(bcr & BCR_WPD);
+}
+
 static int intel_spi_pci_probe(struct pci_dev *pdev,
 			       const struct pci_device_id *id)
 {
 	struct intel_spi_boardinfo *info;
 	struct intel_spi *ispi;
-	u32 bcr;
 	int ret;
 
 	ret = pcim_enable_device(pdev);
@@ -41,14 +67,8 @@  static int intel_spi_pci_probe(struct pci_dev *pdev,
 	if (!info)
 		return -ENOMEM;
 
-	/* Try to make the chip read/write */
-	pci_read_config_dword(pdev, BCR, &bcr);
-	if (!(bcr & BCR_WPD)) {
-		bcr |= BCR_WPD;
-		pci_write_config_dword(pdev, BCR, bcr);
-		pci_read_config_dword(pdev, BCR, &bcr);
-	}
-	info->writeable = !!(bcr & BCR_WPD);
+	info->is_bios_locked = intel_spi_pci_is_bios_locked;
+	info->bios_unlock = intel_spi_pci_bios_unlock;
 
 	ispi = intel_spi_probe(&pdev->dev, &pdev->resource[0], info);
 	if (IS_ERR(ispi))
diff --git a/drivers/mtd/spi-nor/controllers/intel-spi.c b/drivers/mtd/spi-nor/controllers/intel-spi.c
index a413892ff449..bbe0639de702 100644
--- a/drivers/mtd/spi-nor/controllers/intel-spi.c
+++ b/drivers/mtd/spi-nor/controllers/intel-spi.c
@@ -131,7 +131,8 @@ 
  * @sregs: Start of software sequencer registers
  * @nregions: Maximum number of regions
  * @pr_num: Maximum number of protected range registers
- * @writeable: Is the chip writeable
+ * @is_protected: Whether the regions are write protected
+ * @is_bios_locked: Whether the spi is locked by BIOS
  * @locked: Is SPI setting locked
  * @swseq_reg: Use SW sequencer in register reads/writes
  * @swseq_erase: Use SW sequencer in erase operation
@@ -149,7 +150,8 @@  struct intel_spi {
 	void __iomem *sregs;
 	size_t nregions;
 	size_t pr_num;
-	bool writeable;
+	bool is_protected;
+	bool is_bios_locked;
 	bool locked;
 	bool swseq_reg;
 	bool swseq_erase;
@@ -326,7 +328,7 @@  static int intel_spi_init(struct intel_spi *ispi)
 				val = readl(ispi->base + BYT_BCR);
 			}
 
-			ispi->writeable = !!(val & BYT_BCR_WPD);
+			ispi->is_bios_locked = !(val & BYT_BCR_WPD);
 		}
 
 		break;
@@ -862,6 +864,7 @@  static void intel_spi_fill_partition(struct intel_spi *ispi,
 	int i;
 
 	memset(part, 0, sizeof(*part));
+	ispi->is_protected = false;
 
 	/* Start from the mandatory descriptor region */
 	part->size = 4096;
@@ -886,7 +889,7 @@  static void intel_spi_fill_partition(struct intel_spi *ispi,
 		 * whole partition read-only to be on the safe side.
 		 */
 		if (intel_spi_is_protected(ispi, base, limit))
-			ispi->writeable = false;
+			ispi->is_protected = true;
 
 		end = (limit << 12) + 4096;
 		if (end > part->size)
@@ -894,6 +897,60 @@  static void intel_spi_fill_partition(struct intel_spi *ispi,
 	}
 }
 
+static ssize_t intel_spi_is_protected_show(struct device *dev,
+					   struct device_attribute *attr, char *buf)
+{
+	struct intel_spi *ispi = dev_get_drvdata(dev);
+
+	return sysfs_emit(buf, "%d\n", ispi->is_protected);
+}
+static DEVICE_ATTR_ADMIN_RO(intel_spi_is_protected);
+
+static ssize_t intel_spi_bios_lock_show(struct device *dev,
+					struct device_attribute *attr, char *buf)
+{
+	struct intel_spi *ispi = dev_get_drvdata(dev);
+
+	return sysfs_emit(buf, "%d\n", ispi->is_bios_locked);
+}
+
+static ssize_t intel_spi_bios_lock_store(struct device *dev,
+					 struct device_attribute *attr,
+					 const char *buf, size_t len)
+{
+	struct intel_spi *ispi = dev_get_drvdata(dev);
+	struct mtd_info *child, *master = mtd_get_master(&ispi->nor.mtd);
+	int err;
+
+	if (!ispi->info->is_bios_locked)
+		return -EOPNOTSUPP;
+
+	if (strcmp(buf, "unlock") != 0)
+		return -EINVAL;
+
+	if (ispi->info->is_bios_locked(dev)) {
+		err = ispi->info->bios_unlock(dev);
+		if (err)
+			return err;
+	}
+
+	ispi->is_bios_locked = false;
+
+	if (ispi->is_protected || !writeable)
+		return -EPERM;
+
+	/* Device is now writable */
+	ispi->nor.mtd.flags |= MTD_WRITEABLE;
+
+	mutex_lock(&master->master.partitions_lock);
+	list_for_each_entry(child, &ispi->nor.mtd.partitions, part.node)
+		child->flags |= MTD_WRITEABLE;
+	mutex_unlock(&master->master.partitions_lock);
+
+	return len;
+}
+static DEVICE_ATTR_ADMIN_RW(intel_spi_bios_lock);
+
 static const struct spi_nor_controller_ops intel_spi_controller_ops = {
 	.read_reg = intel_spi_read_reg,
 	.write_reg = intel_spi_write_reg,
@@ -902,6 +959,15 @@  static const struct spi_nor_controller_ops intel_spi_controller_ops = {
 	.erase = intel_spi_erase,
 };
 
+int intel_spi_remove(struct intel_spi *ispi)
+{
+	device_remove_file(ispi->dev, &dev_attr_intel_spi_is_protected);
+	device_remove_file(ispi->dev, &dev_attr_intel_spi_bios_lock);
+
+	return mtd_device_unregister(&ispi->nor.mtd);
+}
+EXPORT_SYMBOL_GPL(intel_spi_remove);
+
 struct intel_spi *intel_spi_probe(struct device *dev,
 	struct resource *mem, const struct intel_spi_boardinfo *info)
 {
@@ -927,7 +993,9 @@  struct intel_spi *intel_spi_probe(struct device *dev,
 
 	ispi->dev = dev;
 	ispi->info = info;
-	ispi->writeable = info->writeable;
+	ispi->is_bios_locked = info->is_bios_locked(dev);
+	dev_dbg(ispi->dev, "is_bios_locked: %d writeable: %d\n",
+		ispi->is_bios_locked, writeable);
 
 	ret = intel_spi_init(ispi);
 	if (ret)
@@ -946,22 +1014,28 @@  struct intel_spi *intel_spi_probe(struct device *dev,
 	intel_spi_fill_partition(ispi, &part);
 
 	/* Prevent writes if not explicitly enabled */
-	if (!ispi->writeable || !writeable)
+	if (ispi->is_protected || ispi->is_bios_locked || !writeable)
 		ispi->nor.mtd.flags &= ~MTD_WRITEABLE;
 
 	ret = mtd_device_register(&ispi->nor.mtd, &part, 1);
 	if (ret)
 		return ERR_PTR(ret);
 
+	ret = device_create_file(ispi->dev, &dev_attr_intel_spi_is_protected);
+	if (ret)
+		goto err;
+	ret = device_create_file(ispi->dev, &dev_attr_intel_spi_bios_lock);
+	if (ret)
+		goto err;
+
 	return ispi;
+
+err:
+	intel_spi_remove(ispi);
+	return ERR_PTR(ret);
 }
 EXPORT_SYMBOL_GPL(intel_spi_probe);
 
-int intel_spi_remove(struct intel_spi *ispi)
-{
-	return mtd_device_unregister(&ispi->nor.mtd);
-}
-EXPORT_SYMBOL_GPL(intel_spi_remove);
 
 MODULE_DESCRIPTION("Intel PCH/PCU SPI flash core driver");
 MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>");
diff --git a/include/linux/platform_data/x86/intel-spi.h b/include/linux/platform_data/x86/intel-spi.h
index 7f53a5c6f35e..335dbb40620f 100644
--- a/include/linux/platform_data/x86/intel-spi.h
+++ b/include/linux/platform_data/x86/intel-spi.h
@@ -19,11 +19,13 @@  enum intel_spi_type {
 /**
  * struct intel_spi_boardinfo - Board specific data for Intel SPI driver
  * @type: Type which this controller is compatible with
- * @writeable: The chip is writeable
+ * @is_bios_locked: report if the device is locked by BIOS
+ * @bios_unlock: handler to unlock the bios
  */
 struct intel_spi_boardinfo {
 	enum intel_spi_type type;
-	bool writeable;
+	bool (*is_bios_locked)(struct device *dev);
+	int (*bios_unlock)(struct device *dev);
 };
 
 #endif /* INTEL_SPI_PDATA_H */