lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-ID: <20181016005726.26813-1-brett.grandbois@opengear.com>
Date:   Tue, 16 Oct 2018 00:57:41 +0000
From:   "Grandbois, Brett" <brett.grandbois@...ngear.com>
To:     Marek Vasut <marek.vasut@...il.com>,
        David Woodhouse <dwmw2@...radead.org>,
        Brian Norris <computersforpeace@...il.com>,
        Boris Brezillon <boris.brezillon@...tlin.com>,
        Richard Weinberger <richard@....at>,
        "linux-mtd@...ts.infradead.org" <linux-mtd@...ts.infradead.org>
CC:     "linux-kernel@...r.kernel.org" <linux-kernel@...r.kernel.org>
Subject: [PATCH v2] mtd: spi-nor: Add support for SPI boot flash access for
 AMD Family 16h

Add support to expose the SPI boot flash on AMD Family 16h CPUs as a
standard mtd device to give userspace BIOS updaters greater feature
support.  The BIOS and Kernel Developer's Guide refers to this as the
'SPI ROM' controller and so the driver follows that naming convention
for consistency.

Signed-off-by: Brett Grandbois <brett.grandbois@...ngear.com>
---

Changes for v2:
- address sparse warnings

 drivers/mtd/spi-nor/Kconfig      |  15 +
 drivers/mtd/spi-nor/Makefile     |   1 +
 drivers/mtd/spi-nor/amd-spirom.c | 805 +++++++++++++++++++++++++++++++
 3 files changed, 821 insertions(+)
 create mode 100644 drivers/mtd/spi-nor/amd-spirom.c

diff --git a/drivers/mtd/spi-nor/Kconfig b/drivers/mtd/spi-nor/Kconfig
index 6cc9c929ff57..f99b40ec0fef 100644
--- a/drivers/mtd/spi-nor/Kconfig
+++ b/drivers/mtd/spi-nor/Kconfig
@@ -129,4 +129,19 @@ config SPI_STM32_QUADSPI
 	  This enables support for the STM32 Quad SPI controller.
 	  We only connect the NOR to this controller.
 
+config SPI_AMD_SPIROM
+	tristate "AMD Hudson FCH SPI flash drvier (DANGEROUS)"
+	depends on X86 && PCI
+	help
+	  This enables support for the AMD Family 16h SPI flash controller to
+	  access the boot flash from Linux as an mtd device.
+
+	  Using this driver it is possible to upgrade BIOS directly from Linux.
+
+	  Say N here unless you know what you are doing. Overwriting the
+	  SPI flash may render the system unbootable.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called amd-spirom.
+
 endif # MTD_SPI_NOR
diff --git a/drivers/mtd/spi-nor/Makefile b/drivers/mtd/spi-nor/Makefile
index f4c61d282abd..e49ea4e619c1 100644
--- a/drivers/mtd/spi-nor/Makefile
+++ b/drivers/mtd/spi-nor/Makefile
@@ -11,3 +11,4 @@ obj-$(CONFIG_SPI_INTEL_SPI)	+= intel-spi.o
 obj-$(CONFIG_SPI_INTEL_SPI_PCI)	+= intel-spi-pci.o
 obj-$(CONFIG_SPI_INTEL_SPI_PLATFORM)	+= intel-spi-platform.o
 obj-$(CONFIG_SPI_STM32_QUADSPI)	+= stm32-quadspi.o
+obj-$(CONFIG_SPI_AMD_SPIROM)	+= amd-spirom.o
diff --git a/drivers/mtd/spi-nor/amd-spirom.c b/drivers/mtd/spi-nor/amd-spirom.c
new file mode 100644
index 000000000000..5ad985cd23b0
--- /dev/null
+++ b/drivers/mtd/spi-nor/amd-spirom.c
@@ -0,0 +1,805 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright (C) 2018, Opengear
+ *
+ * AMD Family 16h Hudson FCH SPI flash driver.
+ *
+ * When the FCH is strapped to SPI boot ROM mode 'SPIROM'
+ * the FCH will do a flash auto-probe and self-configure
+ * for read operations to the ROM address range(s).
+ * For any command outside of read/write (chip erase, etc)
+ * you need to go through the alternate program method.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/pci.h>
+#include <linux/mutex.h>
+#include <linux/iopoll.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/mtd/spi-nor.h>
+
+/* FCH Device LPC Bridge Configuration Registers */
+#define PCI_DEVICE_ID_AMD_FCH_LPC_BRIDGE	0x780E
+
+#define FCH_PCI_CONTROL				0x40
+#define FCH_INTEGRATED_EC_PRESENT		0x80
+#define FCH_EC_SEM				0x40
+#define FCH_BIOS_SEM				0x20
+#define FCH_LEGACY_DMA				0x04
+
+#define FCH_ROM_ADDR_RANGE_2			0x6C
+
+#define FCH_SPI_BASE_ADDR			0xA0
+#define FCH_SPI_BASE_ADDR_MASK			0xFFFFFFC0
+#define FCH_SPI_ROUTE_TPM_SPI			0x08
+#define FCH_SPI_ROM_ENABLE			0x02
+
+/* up through FIFO [C6:80] */
+#define SPI_IO_REGION_LEN			256
+
+/* SPI Registers, the labels come from the BKDG */
+#define SPI_CNTRL0				0x00
+#define SPI_CNTRL0_FIFO_PTR_CLEAR		0x00100000
+#define SPI_CNTRL0_FIFO_PTR_CLEAR_MASK		0xFFEFFFFF
+#define SPI_CNTRL0_SPI_ARB_ENABLE		0x00080000
+#define SPI_CNTRL0_SPI_ARB_ENABLE_MASK		0xFFF7FFFF
+
+#define ALT_SPI_CS				0x1D
+#define ALT_SPI_CS_MASK				0xFC
+#define ALT_SPI_CS_WR_BUF_EN			0x04
+
+#define SPI100_ENABLE				0x20
+#define SPI100_SPEED_CONFIG			0x22
+
+
+/* SPI control shadow registers */
+#define CMD_CODE				0x45
+
+#define CMD_TRIGGER				0x47
+#define CMD_TRIGGER_EXECUTE			0x80
+
+#define TX_BYTE_COUNT				0x48
+
+#define RX_BYTE_COUNT				0x4B
+
+#define SPI_STATUS				0x4C
+#define SPI_STATUS_BUSY_MASK			0x80000000
+
+#define SPI_FIFO				0x80
+
+static const struct pci_device_id amd_fch_lpc_pci_device_ids[] = {
+	{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_FCH_LPC_BRIDGE) },
+	{}
+};
+MODULE_DEVICE_TABLE(pci, amd_fch_lpc_pci_device_ids);
+
+static short norm_speed = -1;
+module_param(norm_speed, short, 0444);
+MODULE_PARM_DESC(norm_speed, "Specify SPI speed for normal read.  This sets NormSpeedNew[3:0] from BKDG. -1 means use existing (defaut).");
+
+static short fast_speed = -1;
+module_param(fast_speed, short, 0444);
+MODULE_PARM_DESC(fast_speed, "Specify SPI speed for fast/dual/quad read.  This sets FastSpeedNew[3:0] from BKDG. -1 means use existing (defaut).");
+
+static short alt_speed = -1;
+module_param(alt_speed, short, 0444);
+MODULE_PARM_DESC(alt_speed, "Specify alternate command SPI speed.  This sets AltSpeedNew[3:0] from BKDG. -1 means use existing (defaut).");
+
+static int read_mode = -1;
+module_param(read_mode, int, 0444);
+MODULE_PARM_DESC(read_mode, "Specify SPI read settings.  This sets SpiReadMode[2:0] from BKDG. -1 means use existing (defaut).");
+
+static char *flash_name;
+module_param(flash_name, charp, 0444);
+MODULE_PARM_DESC(flash_name, "Specify flash type name to spi_nor_scan(). Default (null) is auto-probe from JEDEC ID.");
+
+static short chip_select = -1;
+module_param(chip_select, short, 0444);
+MODULE_PARM_DESC(chip_select, "Specify the alternate SPI CS# [0-3]. -1 means use existing (defaut).");
+
+static char *part_name = "BIOS";
+module_param(part_name, charp, 0444);
+MODULE_PARM_DESC(part_name, "MTD partition label for SPIROM region.");
+
+static short write_buffer_enable = -1;
+module_param(write_buffer_enable, short, 0444);
+MODULE_PARM_DESC(write_buffer_enable, "Enable write buffer.  This sets WriteBufferEn from BKDG.  0 means disable, >0 means enable, <0 means use existing (default)");
+
+static short mac_arb_enable = -1;
+module_param(mac_arb_enable, short, 0444);
+MODULE_PARM_DESC(write_buffer_enable, "Enable MAC arbitration.  This sets SpiArbEnable from BKDG.  0 means disable, >0 means enable, <0 means use existing (default)");
+
+
+static bool accelerated_rd = true;
+module_param(accelerated_rd, bool, 0444);
+MODULE_PARM_DESC(hw_write, "Have read requests go via flash MMIO address space. This is a performance enhancement.");
+
+/*
+ * The SPIROM interface only supports 1 flash chip so that's all the driver
+ * supports.  Theoretically you could access up to 3 others via alt command
+ * and SPI_ALT_CS but that's a future expansion and likely not ever to be
+ * actually needed.
+ */
+struct amd_spirom {
+	void __iomem *spi;
+	void __iomem *rom;
+	struct spi_nor nor;
+	/*
+	 * pre-calculated delays for reg_xfer (up to 8 bytes data + opcode)
+	 * note that the delay includes the opcode byte and the value is double
+	 * because reg_xfer is using poll_timeout which actually quarters the
+	 * value for usleep_range.
+	 */
+	u32 reg_delay_us[9];
+	u16 spi_alt_speed;
+	bool mtd_registered;
+};
+
+/* note we're assuming no dual/quad here */
+static u32 amd_spirom_get_usecs_per_bytes(u16 speed_config, u32 bytes)
+{
+	static const u32 hz[6] = {	(u32)(6666 * 1000),
+					(u32)(3333 * 1000),
+					(u32)(2222 * 1000),
+					(u32)(1666 * 1000),
+					(u32)(100000 * 1000),
+					(u32)(800 * 1000) };
+
+	return mult_frac(bytes * 8, USEC_PER_SEC, hz[speed_config]);
+}
+
+static int amd_spirom_reg_xfer(struct amd_spirom *spirom, u8 opcode,
+				u8 rx_len, u8 tx_len, u8 *rx_buf, u8 *tx_buf)
+{
+	u32 delay_us;
+	u32 total_len;
+	u32 cntrl0;
+	u8 sts;
+	int rc;
+	uint i;
+
+	total_len = tx_len + rx_len + 1;
+
+	/* transfer FIFO is actually 70 bytes long so that's the hard limit */
+
+	/*
+	 * no irq here so just need to wait
+	 *
+	 * poll_timeout will actually quarter the delay so we'll double the
+	 * expected range and should then hit on the second or third iteration
+	 *
+	 * for reg_read/write transactions we can use the pre-calculated value
+	 *
+	 * Note we need to +1 for the opcode.
+	 */
+
+
+	if (total_len <= 9)
+		delay_us = spirom->reg_delay_us[total_len - 1];
+	else if (total_len <= 71)
+		delay_us = amd_spirom_get_usecs_per_bytes(spirom->spi_alt_speed,
+							  total_len << 1);
+	else
+		return -EINVAL;
+
+
+	iowrite8(opcode, spirom->spi + CMD_CODE);
+
+	iowrite8(tx_len, spirom->spi + TX_BYTE_COUNT);
+	iowrite8(rx_len, spirom->spi + RX_BYTE_COUNT);
+
+	/* reset the transfer fifo */
+	cntrl0 = ioread32(spirom->spi + SPI_CNTRL0);
+	cntrl0 &= SPI_CNTRL0_FIFO_PTR_CLEAR_MASK;
+	cntrl0 |= SPI_CNTRL0_FIFO_PTR_CLEAR;
+	iowrite32(cntrl0, spirom->spi + SPI_CNTRL0);
+
+	/* fill the FIFO */
+	for (i = 0; i < (uint)tx_len; i++)
+		iowrite8(tx_buf[i], spirom->spi + SPI_FIFO + i);
+
+	/* release the hounds... */
+	iowrite8(CMD_TRIGGER_EXECUTE, spirom->spi + CMD_TRIGGER);
+
+	rc = readb_poll_timeout(spirom->spi + SPI_STATUS, sts,
+				!(sts & SPI_STATUS_BUSY_MASK),
+				delay_us, 4 * delay_us);
+
+	if (!rc) {
+		/* drain the FIFO */
+		for (i = 0; i < (uint)rx_len; i++)
+			rx_buf[i] = ioread8(spirom->spi + SPI_FIFO + tx_len + i);
+	}
+
+	return rc;
+}
+
+static int amd_spirom_prepare(struct spi_nor *nor, enum spi_nor_ops ops)
+{
+	/* get the semaphore */
+	struct pci_dev *pcidev = to_pci_dev(nor->dev);
+	u32 pci_control;
+	int rc = -ETIMEDOUT;
+	int count;
+
+	/*
+	 * procedure from BKDG is to wait for EC_SEM to be 0, then write 1 to
+	 * BIOS_SEM, then read back to verify the 1 has been set by HW to grant
+	 * ownership
+	 *
+	 * this is only necessary when the IMC is active
+	 */
+	for (count = 0; count < 100 && rc; count++) {
+		pci_read_config_dword(pcidev, FCH_PCI_CONTROL, &pci_control);
+		if (pci_control & FCH_EC_SEM)
+			msleep(50);
+		else {
+			if (pci_control & FCH_BIOS_SEM)
+				rc = 0;
+			else
+				pci_write_config_dword(pcidev, FCH_PCI_CONTROL,
+						       pci_control | FCH_BIOS_SEM);
+		}
+	}
+
+	return rc;
+}
+
+static void amd_spirom_unprepare(struct spi_nor *nor, enum spi_nor_ops ops)
+{
+	/* we assume we own the semaphore at this point */
+	struct pci_dev *pcidev = to_pci_dev(nor->dev);
+	u32 pci_control;
+
+	pci_read_config_dword(pcidev, FCH_PCI_CONTROL, &pci_control);
+
+	pci_control &= FCH_LEGACY_DMA;
+
+	pci_write_config_dword(pcidev, FCH_PCI_CONTROL, pci_control);
+}
+
+static int amd_spirom_read_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len)
+{
+	struct amd_spirom *spirom = nor->priv;
+	int rc;
+
+	dev_dbg(nor->dev, "read_reg: op: 0x%02x  len: %d\n", opcode, len);
+
+	rc = amd_spirom_reg_xfer(spirom, opcode, len, 0, buf, NULL);
+	if (!rc)
+		dev_dbg(nor->dev, "read_reg: %*ph\n", len, buf);
+	else
+		dev_dbg(nor->dev, "read_reg: failed: %d\n", rc);
+
+	return rc;
+}
+
+static int amd_spirom_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len)
+{
+	struct amd_spirom *spirom = nor->priv;
+	int rc;
+
+	dev_dbg(nor->dev, "write_reg: op: 0x%02x  len: %d  data: %*ph\n",
+		opcode, len, len, buf);
+
+	rc = amd_spirom_reg_xfer(spirom, opcode, 0, len, NULL, buf);
+
+	if (rc)
+		dev_dbg(nor->dev, "write_reg: failed: %d\n", rc);
+
+	return rc;
+}
+
+/*
+ * fallback read op based on explicit read command
+ * ideally the HW io will be used but this needs to be available if it's not
+ * strapped, can't get the resources, etc.
+ * spi_nor_read() will loop on the actual bytes read so we can just limit
+ * ourselves to the 64-byte FIFO size.
+ */
+static ssize_t amd_spirom_read(struct spi_nor *nor, loff_t from,
+			       size_t len, u_char *read_buf)
+{
+	struct amd_spirom *spirom = nor->priv;
+	u32 *pbuf32;
+	size_t head;
+	size_t tail;
+	size_t left;
+	__be32 addr;
+	ssize_t rc;
+	size_t cur;
+	u8 *p_addr;
+	u8 rd_len;
+	size_t i;
+
+	dev_dbg(nor->dev, "read: from: %lld  len: %zu\n", from, len);
+
+	/*
+	 * We only allow READ in the HWCAPS so most read operations should come
+	 * with that opcode.  However, the SPI-NOR layer can swap out the read
+	 * opcode with something else (i.e. Read SFDP) which must be handled via
+	 * alt command and not the ROM IO
+	 */
+	if (spirom->rom && (nor->read_opcode == SPINOR_OP_READ)) {
+		/*
+		 * no need for status/busy as the HW controller will deal with
+		 * that, but memcpy_fromio can make out-of-order fetches so need
+		 * to do this explicitly
+		 */
+		left = len;
+		head = from & 3;
+		left -= head;
+		tail = left & 3;
+		left -= tail;
+		cur = 0;
+
+		for (i = 0; i < head; i++, cur++)
+			read_buf[cur] = ioread8(spirom->rom + from + cur);
+
+		pbuf32 = (u32 *)(read_buf + cur);
+		left /= 4;
+
+		for (i = 0; i < left; i++, cur += 4, pbuf32++)
+			*pbuf32 = ioread32(spirom->rom + from + cur);
+
+		for (i = 0; i < tail; i++, cur++)
+			read_buf[cur] = ioread8(spirom->rom + from + cur);
+
+		rc = (ssize_t)len;
+	} else {
+
+		addr = cpu_to_be32((u32)(from));
+		p_addr = (u8 *)(&addr);
+
+		rd_len = len > 64 ? 64 : (u8)len;
+
+		if (nor->addr_width == 3)
+			p_addr++;
+
+		rc = amd_spirom_reg_xfer(spirom, nor->read_opcode, rd_len,
+					 nor->addr_width, read_buf, p_addr);
+
+		if (!rc)
+			rc = (ssize_t)rd_len;
+	}
+
+	return rc;
+}
+
+static ssize_t amd_spirom_write(struct spi_nor *nor, loff_t to,
+				size_t len, const u_char *write_buf)
+{
+	/*
+	 * spi_nor_write() will have already segmented this into pages
+	 * and we will have overridden the flash page size to 64 (or less)
+	 * for the FIFO size so just need to serialize into one byte stream
+	 * for the addr + data.
+	 * Note Page Program takes a BE address.
+	 * Supposedly the ROM IO space should be able to deal with writes but
+	 * cant get it to work in practice.
+	 */
+	struct amd_spirom *spirom = nor->priv;
+	u8 buf[4 + 64];
+	u8 *p_buf = buf;
+	__be32 addr;
+	int rc;
+
+	dev_dbg(nor->dev, "write: to: %lld  len: %zu\n", to, len);
+
+	if (len > 64)
+		return -EINVAL;
+
+	addr = cpu_to_be32((u32)(to));
+
+	memcpy(buf, &addr, 4);
+
+	memcpy(&buf[4], write_buf, len);
+
+	if (nor->addr_width == 3)
+		p_buf++;
+
+	rc = amd_spirom_reg_xfer(spirom, nor->program_opcode, 0,
+				 len + nor->addr_width, NULL, p_buf);
+
+	return !rc ? (ssize_t)len : rc;
+}
+
+static int amd_spirom_spi_init(struct device *dev, struct amd_spirom *spirom,
+			       bool imc_active)
+{
+	struct spi_nor_hwcaps hwcaps;
+	bool update_speed_config = false;
+	u32 spi_read_mode;
+	u32 spi_cntrl0;
+	u16 speed_config;
+	u8 cs_val;
+	int i;
+
+	/*
+	 * Speed and Mode and CS settings should be configured by BIOS
+	 * but can be overridden by module param.
+	 */
+
+	cs_val = ioread8(spirom->spi + ALT_SPI_CS);
+	if (chip_select >= 0) {
+		if (chip_select < 4) {
+			iowrite8((cs_val & ALT_SPI_CS_MASK) | (u8)(chip_select),
+				 spirom->spi + ALT_SPI_CS);
+
+			dev_info(dev, "updated CS from: %d to: %d\n",
+				cs_val & 0xF, chip_select);
+
+		} else
+			dev_err(dev, "invalid chip_select value: %d\n",
+				chip_select);
+	} else
+		chip_select = (short)(cs_val & 0x3);
+
+	if (write_buffer_enable >= 0) {
+		cs_val = ioread8(spirom->spi + ALT_SPI_CS);
+		if (write_buffer_enable) {
+			iowrite8(cs_val | ALT_SPI_CS_WR_BUF_EN,
+				 spirom->spi + ALT_SPI_CS);
+			dev_info(dev, "enabled write buffer\n");
+		} else {
+			iowrite8(cs_val & ~ALT_SPI_CS_WR_BUF_EN,
+				 spirom->spi + ALT_SPI_CS);
+			dev_info(dev, "disabled write buffer\n");
+		}
+	} else
+		write_buffer_enable = (cs_val & ALT_SPI_CS_WR_BUF_EN) >> 2;
+
+	speed_config = ioread16(spirom->spi + SPI100_SPEED_CONFIG);
+	dev_info(dev, "SPI100 speed config: 0x%04x\n", speed_config);
+
+	if (alt_speed >= 0) {
+		if (alt_speed < 6) {
+			speed_config &= 0xFF0F;
+			speed_config |= alt_speed << 4;
+			update_speed_config = true;
+		} else
+			dev_err(dev, "invalid alt_speed value: %d\n",
+				alt_speed);
+	}
+
+	if (norm_speed >= 0) {
+		if (norm_speed < 6) {
+			speed_config &= 0x0FFF;
+			speed_config |= norm_speed << 12;
+			update_speed_config = true;
+		} else
+			dev_err(dev, "invalid norm_speed value: %d\n",
+				norm_speed);
+	}
+
+	if (fast_speed >= 0) {
+		if (fast_speed < 6) {
+			speed_config &= 0xF0FF;
+			speed_config |= fast_speed << 8;
+			update_speed_config = true;
+		} else
+			dev_err(dev, "invalid fast_speed value: %d\n",
+				fast_speed);
+	}
+
+	if (update_speed_config) {
+		dev_info(dev, "updated SPI100 speed config: 0x%04x\n",
+			 speed_config);
+		/* do we need to enable 100 mode? */
+		for (i = 0; i < 4; i++) {
+			if (((speed_config >> i) & 0xF) == 4) {
+				iowrite8(1, spirom->spi + SPI100_ENABLE);
+				dev_info(dev, "SPI100 enabled\n");
+				break;
+			}
+		}
+		iowrite16(speed_config, spirom->spi + SPI100_SPEED_CONFIG);
+	}
+
+	/* AltSpeed is [7:4] */
+	spirom->spi_alt_speed = (speed_config >> 4) & 0xF;
+
+	/* which mode are we in, and therefore which reg to read? */
+	spi_cntrl0 = ioread32(spirom->spi + SPI_CNTRL0);
+	dev_info(dev, "SPI CNTRL0: 0x%08x\n", spi_cntrl0);
+
+	if (mac_arb_enable >= 0) {
+		if (mac_arb_enable) {
+			spi_cntrl0 |= SPI_CNTRL0_SPI_ARB_ENABLE;
+			dev_info(dev, "enabled MAC arbritration\n");
+		} else {
+			spi_cntrl0 &= SPI_CNTRL0_SPI_ARB_ENABLE;
+			dev_info(dev, "disabled MAC arbitration\n");
+		}
+	} else
+		mac_arb_enable = (spi_cntrl0 & SPI_CNTRL0_SPI_ARB_ENABLE) >> 19;
+	/*
+	 * this is a 3 bit mashup with bits [2:1] being [30:29]
+	 * and bit [0] being [18] in the register
+	 */
+	spi_read_mode  = (spi_cntrl0 >> 28) & 0x6;
+	spi_read_mode |= (spi_cntrl0 & BIT(18)) ? 1 : 0;
+	if (read_mode >= 0) {
+		if (read_mode < 8) {
+			spi_read_mode = read_mode;
+			spi_cntrl0 &= 0x17F80000;
+			spi_cntrl0 |= (read_mode & 0x6) << 29;
+			spi_cntrl0 |= (read_mode & 0x1) << 18;
+			iowrite32(spi_cntrl0, spirom->spi + SPI_CNTRL0);
+			dev_info(dev, "updated SPI_CNTRL0: 0x%08x\n",
+				 spi_cntrl0);
+		} else
+			dev_err(dev, "invalid read_mode value %d\n", read_mode);
+	}
+
+	/* update the params */
+	read_mode = spi_read_mode;
+	alt_speed = spirom->spi_alt_speed;
+	norm_speed = (speed_config >> 12) & 0xF;
+	fast_speed = (speed_config >> 8) & 0xF;
+
+	for (i = 0; i < 10; i++)
+		spirom->reg_delay_us[i - 1] = amd_spirom_get_usecs_per_bytes(alt_speed,
+									     i << 1);
+
+	/* as far as the spi-nor layer is concerned we can only do READ */
+	hwcaps.mask = SNOR_HWCAPS_READ | SNOR_HWCAPS_PP;
+
+	spirom->nor.dev = dev;
+	spirom->nor.priv = spirom;
+
+	spirom->nor.read_reg = amd_spirom_read_reg;
+	spirom->nor.write_reg = amd_spirom_write_reg;
+	spirom->nor.read = amd_spirom_read;
+	spirom->nor.write = amd_spirom_write;
+
+	/*
+	 * for now the un/prepare functions only deal with IMC so can global
+	 * enable/disable here.  if it turns out later we need other
+	 * functionality we'll have to filter IMC presence in the actual
+	 * routines.
+	 */
+	if (imc_active) {
+		spirom->nor.prepare = amd_spirom_prepare;
+		spirom->nor.unprepare = amd_spirom_unprepare;
+	}
+
+	return spi_nor_scan(&spirom->nor, flash_name, &hwcaps);
+}
+
+static int amd_spirom_mtd_init(struct device *dev, struct amd_spirom *spirom)
+{
+	struct mtd_partition part;
+	int rc;
+
+	/* need to define a basic partition for the region */
+
+	memset(&part, 0, sizeof(part));
+
+	/* Eventually add a CBFS region parser? */
+
+	part.name = part_name;
+	part.size = MTDPART_SIZ_FULL;
+
+	/*
+	 * need to override the flash page size to our 64-byte FIFO
+	 * limit so we can let the spi-nor layer take care of the
+	 * segmentation for us
+	 */
+	if (spirom->nor.page_size > 64) {
+		spirom->nor.page_size = 64;
+		spirom->nor.mtd.writebufsize = 64;
+	}
+
+	rc = mtd_device_parse_register(&spirom->nor.mtd, NULL, NULL, &part, 1);
+
+	if (rc)
+		dev_err(dev, "failed MTD device register: %d\n", rc);
+
+	spirom->mtd_registered = !rc;
+
+	return rc;
+}
+
+static bool amd_spirom_imc_enabled(struct pci_dev *pcidev)
+{
+	bool imc_enabled = false;
+	void __iomem *acpi_misc;
+	u32 imc_present;
+	u8 imc_active;
+
+	pci_read_config_dword(pcidev, FCH_PCI_CONTROL, &imc_present);
+	if (imc_present & FCH_INTEGRATED_EC_PRESENT) {
+		/* this is the hard-coded AcpiMmio Misc region address */
+		acpi_misc = ioremap_nocache(0xFED80E00, 256);
+		if (acpi_misc) {
+			imc_active = ioread8(acpi_misc + 0x80);
+			imc_enabled = imc_active & 0x04;
+			iounmap(acpi_misc);
+			if (imc_enabled)
+				dev_info(&pcidev->dev, "IMC is enabled\n");
+		} else {
+			dev_warn(&pcidev->dev, "failed to map AcpiMmio Misc region, assuming IMC is enabled\n");
+			imc_enabled = true;
+		}
+	}
+
+	return imc_enabled;
+}
+
+static int amd_spirom_pci_probe(struct pci_dev *pcidev,
+				const struct pci_device_id *id)
+{
+	struct cpuinfo_x86 *cpu = &boot_cpu_data;
+	struct device *dev = &pcidev->dev;
+	struct amd_spirom *spirom;
+	struct resource *res;
+	u32 spi_base_addr;
+	u32 rom_addr_range;
+	u32 rom_addr_start;
+	u32 rom_addr_end;
+	u32 rom_len;
+	bool imc_active;
+	int rc;
+
+	/*
+	 * Shouldn't get here without an AMD vendor code so ignore that,
+	 * but there are slight variations in the family/model implementations
+	 * that we need to be aware of.
+	 * Right now we only support Family 16h, all models seem to work the
+	 * same.
+	 * Is there a device/function version number somewhere?  That would be
+	 * better to switch off of but can't seem to find anything suitable in
+	 * the BKDG.
+	 */
+	if (cpu->x86 != 0x16) {
+		dev_err(dev,
+			"unsupported CPU family: 0x%02x, only support 16h\n",
+			cpu->x86);
+		return -ENODEV;
+	}
+
+	rc = pcim_enable_device(pcidev);
+	if (rc) {
+		dev_err(dev, "failed pci enable: %d\n", rc);
+		return rc;
+	}
+
+	pci_set_master(pcidev);
+
+	spirom = devm_kzalloc(dev, sizeof(*spirom), GFP_KERNEL);
+	if (!spirom) {
+		rc = -ENOMEM;
+		dev_err(dev, "failed spirom alloc\n");
+		goto err_disable;
+	}
+
+	pci_set_drvdata(pcidev, spirom);
+
+	pci_read_config_dword(pcidev, FCH_SPI_BASE_ADDR, &spi_base_addr);
+
+	dev_info(dev, "SPI base addr: 0x%08x\n", spi_base_addr);
+
+	/*
+	 * set up the SPI region
+	 * the coreboot Hudson ACPI configuration sets this as a PNP0C02 BAR0
+	 * resource, which then gets claimed by the system driver. Problem is
+	 * that there's a bug there where they use the SPI_Base_Addr field
+	 * without masking the RouteTpm2Spi and SpiRomEnable bits so the whole
+	 * region can be incorrectly offset, which can then fail the resource
+	 * requests because of the bad overlap.  There doesn't seem to be any
+	 * ideal way to fix it in an external module so if detected just warn.
+	 */
+
+	res = devm_request_mem_region(dev,
+				      spi_base_addr & FCH_SPI_BASE_ADDR_MASK,
+				      SPI_IO_REGION_LEN, "amd-spirom-spi");
+	if (!res)
+		dev_warn(dev, "cannot claim SPI region, this is likely a harmless bug in BIOS and can usually be ignored\n");
+
+	spirom->spi = devm_ioremap_nocache(dev,
+					   spi_base_addr & FCH_SPI_BASE_ADDR_MASK,
+					   SPI_IO_REGION_LEN);
+	if (!spirom->spi) {
+		rc = -ENOMEM;
+		dev_err(dev, "failed to remap SPI region\n");
+		goto err_disable;
+	}
+
+	imc_active = amd_spirom_imc_enabled(pcidev);
+
+	rc = amd_spirom_spi_init(dev, spirom, imc_active);
+	if (rc)
+		goto err_disable;
+
+	/*
+	 * look to see if we can use the HW IO support
+	 * if the SPIROM interface is strapped then HW has mapped the SPI flash
+	 * to the ROM regsions 1 and 2, which are the BIOS regions
+	 * we want to use ROM ADDR 2 as that's the 4GB - size range
+	 * but the controller assumes a 24-bit flash so only enable in that case
+	 */
+
+	if (accelerated_rd && (spirom->nor.addr_width == 3) &&
+	    (spi_base_addr & FCH_SPI_ROM_ENABLE)) {
+
+		pci_read_config_dword(pcidev, FCH_ROM_ADDR_RANGE_2,
+				      &rom_addr_range);
+
+		dev_info(dev, "ROM address range 2: 0x%08x\n", rom_addr_range);
+
+		rom_addr_start = (rom_addr_range & 0xFFFF) << 16;
+		rom_addr_end = ((rom_addr_range & 0xFFFF0000) | 0xFFFF);
+		rom_len = rom_addr_end - rom_addr_start + 1;
+
+		dev_info(dev, "ROM base: 0x%08x  ROM len: %d\n", rom_addr_start,
+			 rom_len);
+
+		/*
+		 * ok now try to request the ROM regions.
+		 * maybe do a pci_quirk with this at some point to reserve
+		 * early?
+		 */
+
+		res = devm_request_mem_region(dev, rom_addr_start, rom_len,
+						"amd-spirom-rom");
+		if (res) {
+			spirom->rom = devm_ioremap_nocache(dev, res->start,
+							   rom_len);
+			if (!spirom->rom)
+				dev_warn(dev, "failed to map ROM region, cannot accelerate read\n");
+		} else
+			dev_warn(dev, "failed to request ROM region, cannot accelerate read\n");
+	}
+
+
+	rc = amd_spirom_mtd_init(dev, spirom);
+	if (rc)
+		goto err_disable;
+
+	dev_info(dev, "enabled\n");
+
+	return 0;
+
+err_disable:
+	pci_disable_device(pcidev);
+	return rc;
+}
+
+static void amd_spirom_pci_remove(struct pci_dev *pcidev)
+{
+	struct amd_spirom *spirom = pci_get_drvdata(pcidev);
+
+	if (spirom && spirom->mtd_registered) {
+		dev_dbg(&pcidev->dev, "mtd unregister\n");
+		mtd_device_unregister(&spirom->nor.mtd);
+	}
+
+	dev_info(&pcidev->dev, "exit\n");
+
+	/* devres should clean up everything else */
+
+	pci_disable_device(pcidev);
+}
+
+static struct pci_driver amd_spirom_pci_driver = {
+	.name =	"amd-spirom",
+	.id_table = amd_fch_lpc_pci_device_ids,
+	.probe =	amd_spirom_pci_probe,
+	.remove =	amd_spirom_pci_remove,
+
+	/*
+	 * no need to worry about power ops here,
+	 * this whole interface is idle until explicit request
+	 */
+};
+
+module_pci_driver(amd_spirom_pci_driver);
+
+MODULE_DESCRIPTION("MTD SPI-NOR driver for AMD Hudson FCH SPIROM");
+MODULE_AUTHOR("Brett Grandbois <brett.grandbois@...ngear.com>");
+MODULE_LICENSE("GPL");
-- 
2.17.1

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ