Signed-off-by: Chalapathi V <chalapath...@linux.ibm.com>
---
include/hw/misc/seeprom_25csm04.h | 48 ++
hw/misc/seeprom_25csm04.c | 780 ++++++++++++++++++++++++++++++
hw/misc/Kconfig | 3 +
hw/misc/meson.build | 1 +
hw/ppc/Kconfig | 1 +
5 files changed, 833 insertions(+)
create mode 100644 include/hw/misc/seeprom_25csm04.h
create mode 100644 hw/misc/seeprom_25csm04.c
diff --git a/include/hw/misc/seeprom_25csm04.h
b/include/hw/misc/seeprom_25csm04.h
new file mode 100644
index 0000000000..0343530354
--- /dev/null
+++ b/include/hw/misc/seeprom_25csm04.h
@@ -0,0 +1,48 @@
+/*
+ * 25CSM04 Serial EEPROM model
+ *
+ * Copyright (c) 2024, IBM Corporation.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * The Microchip Technology Inc. 25CSM04 provides 4 Mbits of Serial EEPROM
+ * utilizing the Serial Peripheral Interface (SPI) compatible bus. The device
+ * is organized as 524288 bytes of 8 bits each (512Kbyte) and is optimized
+ * for use in consumer and industrial applications where reliable and
+ * dependable nonvolatile memory storage is essential
+ */
+
+#ifndef SEEPROM_25CSM04_H
+#define SEEPROM_25CSM04_H
+
+#include "hw/ssi/ssi.h"
+#include "qom/object.h"
+
+#define TYPE_SEEPROM_25CSM04 "seeprom-25csm04"
+
+OBJECT_DECLARE_SIMPLE_TYPE(SeepromCsm04, SEEPROM_25CSM04)
+
+typedef struct SeepromCsm04 {
+ SSIPeripheral parent_object;
+
+ char *file;
+ char *file_name;
+ uint8_t opcode;
+ uint32_t addr;
+ uint8_t rd_state;
+ bool locked;
+ bool command_byte;
+ /* device registers */
+ uint8_t status0;
+ uint8_t status1;
+ uint8_t dsn[16];
+ uint8_t uplid[256];
+ uint8_t mpr[8];
+ uint8_t idr[5];
+} SeepromCsm04;
+
+uint32_t seeprom_transfer(SSIPeripheral *ss, uint32_t tx);
+void seeprom_realize(SSIPeripheral *dev, Error **errp);
+bool compute_addr(SeepromCsm04 *s, uint32_t tx);
+bool validate_addr(SeepromCsm04 *s);
+#endif /* PPC_PNV_SPI_SEEPROM_H */
diff --git a/hw/misc/seeprom_25csm04.c b/hw/misc/seeprom_25csm04.c
new file mode 100644
index 0000000000..45df66e4b0
--- /dev/null
+++ b/hw/misc/seeprom_25csm04.c
@@ -0,0 +1,780 @@
+/*
+ * 25CSM04 Serial EEPROM model
+ *
+ * Copyright (c) 2024, IBM Corporation.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "hw/misc/seeprom_25csm04.h"
+#include "hw/qdev-properties.h"
+#include "qemu/datadir.h"
+#include <math.h>
+
+#define SPI_DEBUG(x)
+
+/*
+ * 2-byte STATUS register which is a combination of six nonvolatile bits of
+ * EEPROM and five volatile latches.
+ *
+ * status 0:
+ * bit 7 WPEN: Write-Protect Enable bit
+ * 1 = Write-Protect pin is enabled, 0 = Write-Protect pin is ignored
+ *
+ * bit 3-2 BP<1:0>: Block Protection bits
+ * 00 = No array write protection
+ * 01 = Upper quarter memory array protection
+ * 10 = Upper half memory array protection
+ * 11 = Entire memory array protection
+ *
+ * bit 1 WEL: Write Enable Latch bit
+ * 1 = WREN has been executed and device is enabled for writing
+ * 0 = Device is not write-enabled
+ *
+ * bit 0 RDY/BSY: Ready/Busy Status Latch bit
+ * 1 = Device is busy with an internal write cycle
+ * 0 = Device is ready for a new sequence
+ */
+#define STATUS0_WPEN 0x7
+#define STATUS0_BP 0x2
+#define STATUS0_WEL 0x1
+#define STATUS0_BUSY 0x0
+
+/*
+ * status 1:
+ * bit 7 WPM: Write Protection Mode bit(1)
+ * 1 = Enhanced Write Protection mode selected (factory default)
+ * 0 = Legacy Write Protection mode selected
+ *
+ * bit 6 ECS: Error Correction State Latch bit
+ * 1 = The previously executed read sequence did require the ECC
+ * 0 = The previous executed read sequence did not require the ECC
+ *
+ * bit 5 FMPC: Freeze Memory Protection Configuration bit(2)
+ * 1 = Memory Partition registers and write protection mode are permanently
+ * frozen and cannot be modified
+ * 0 = Memory Partition registers and write protection mode are not frozen
+ * and are modifiable
+ *
+ * bit 4 PREL: Partition Register Write Enable Latch bit
+ * 1 = PRWE has been executed and WMPR, FRZR and PPAB instructions are enabled
+ * 0 = WMPR, FRZR and PPAB instructions are disabled
+ *
+ * bit 3 PABP: Partition Address Boundary Protection bit
+ * 1 = Partition Address Endpoints set in Memory Partition registers
+ * cannot be modified
+ * 0 = Partition Address Endpoints set in Memory Partition registers
+ * are modifiable
+ *
+ * bit 0 RDY/BSY: Ready/Busy Status Latch bit
+ * 1 = Device is busy with an internal write cycle
+ * 0 = Device is ready for a new sequence
+ */
+#define STATUS1_WPM 0x7
+#define STATUS1_ECS 0x6
+#define STATUS1_FMPC 0x5
+#define STATUS1_PREL 0x4
+#define STATUS1_PABP 0x3
+#define STATUS1_BUSY 0x0
+
+/*
+ * MEMORY PARTITION REGISTERS
+ * Note 1: The MPR cannot be written if the FMPC bit has been set.
+ * 2: The Partition Endpoint Address bits cannot be written if the PABP
+ * bit has been set.
+ *
+ * bits 7-6 PB<1:0>: Partition Behavior bits(1)
+ * 00 = Partition is open and writing is permitted
+ * factory default is unprotected.
+ * 01 = Partition is always write-protected but can be reversed at a later
+ * time (software write-protected).
+ * 10 = Partition is write-protected only when WP pin is asserted
+ * (hardware write-protected).
+ * 11 = Partition is software write-protected and MPR is permanently locked
+ *
+ * bit 5-0 A<18:13>: Partition Endpoint Address bits(1, 2)
+ * 000000 = Endpoint address of partition is set to 01FFFh.
+ * 000001 = Endpoint address of partition is set to 03FFFh.
+ * ----
+ * 111110 = Endpoint address of partition is set to 7DFFFh.
+ * 111111 = Endpoint address of partition is set to 7FFFFh.
+ */
+#define MPR_PB 0x6
+#define MPR_PEA 0x5
+
+/* INSTRUCTION SET FOR 25CSM04 */
+#define RDSR 0x05
+#define WRBP 0x08
+#define WREN 0x06
+#define WRDI 0x04
+#define WRSR 0x01
+#define READ 0x03
+#define WRITE 0x02
+#define RDEX_CHLK 0x83
+#define WREX_LOCK 0x82
+#define RMPR 0x31
+#define PRWE 0x07
+#define PRWD 0x0A
+#define WMPR 0x32
+#define PPAB 0x34
+#define FRZR 0x37
+#define SPID 0x9F
+#define SRST 0x7C
+
+/* READ FSM state */
+#define ST_IDLE 0
+#define ST_READ 1
+#define ST_SEC_READ 2
+
+#define DATA_LEN 4
+
+uint32_t seeprom_transfer(SSIPeripheral *ss, uint32_t tx)
+{
+ SeepromCsm04 *s = SEEPROM_25CSM04(ss);
+ uint16_t idx;
+ FILE *f;
+ bool status0_busy;
+ bool status1_busy;
+ uint32_t rx = -1;
+ uint32_t *buf = NULL;
+ bool failed = false;
+
+ buf = g_malloc0(sizeof(uint32_t));
+ SPI_DEBUG(qemu_log("Received SPI request, tx = 0x%x\n", tx));
+ if (s->command_byte) {
+ s->opcode = tx >> 24;
+ SPI_DEBUG(qemu_log("Command Opcode (0x%x)\n", s->opcode));
+ /*
+ * Check if device is busy with internal write cycle, During this
+ * time, only the Read STATUS Register (RDSR) and the Write Ready/Busy
+ * Poll (WRBP) instructions will be executed by the device.
+ */
+ status0_busy = extract8(s->status0, STATUS0_BUSY, 1);
+ status1_busy = extract8(s->status1, STATUS1_BUSY, 1);
+ if (((status0_busy == 1) || (status1_busy == 1)) &&
+ ((s->opcode != RDSR) || (s->opcode != WRBP))) {
+ qemu_log_mask(LOG_GUEST_ERROR, "Busy with internal write Cycle, "
+ "opcode(0x%x) not executed\n", s->opcode);
+ return rx;
+ }
+ /* For a new command sequence compute Address */
+ failed = compute_addr(s, tx);
+ /*
+ * Address computation failed, nothing to do further, just send
+ * response and return from here.
+ */
+ if (failed) {
+ return tx;
+ }
+ }
+
+ switch (s->opcode) {
+ case READ:
+ if (!s->command_byte) {
+ SPI_DEBUG(qemu_log("READ(0x%x), addr(0x%x)\n",
+ s->opcode, s->addr));
+ s->rd_state = ST_READ;
+ if (s->file) {
+ f = fopen(s->file, "rb+");
+ if (f) {
+ if (!fseek(f, s->addr, SEEK_SET)) {
+ if (fread(buf, sizeof(uint32_t), 1, f) == 1) {
+ SPI_DEBUG(qemu_log("Read 4 bytes from seeprom\n"));
+ rx = *buf;
+ } else {
+ if (ferror(f)) {
+ SPI_DEBUG(qemu_log("Error reading seeprom\n"));
+ }
+ }
+ }
+ }
+ fclose(f);
+ }
+ s->addr = (s->addr & 0x7FFFF) + DATA_LEN;
+ }
+ break;
+
+ case RDSR:
+ SPI_DEBUG(qemu_log("READ Status Register - RDSR(0x%x)\n",
+ s->opcode));
+ rx = 0;
+ rx = rx | (s->status0 << 24);
+ rx = rx | (s->status1 << 16);
+ break;
+
+ case WRBP:
+ SPI_DEBUG(qemu_log("Write Ready/Busy Poll - WRBP(0x%x)\n",
+ s->opcode));
+ status0_busy = extract8(s->status0, STATUS0_BUSY, 1);
+ status1_busy = extract8(s->status1, STATUS1_BUSY, 1);
+ rx = 0;
+ if ((status0_busy == 1) || (status1_busy == 1)) {
+ rx = rx | (0xFF << 24);
+ }
+ break;
+
+ case WREN:
+ SPI_DEBUG(qemu_log("Set Write Enable Latch (WEL) WREN(0x%x)\n",
+ s->opcode));
+ s->status0 = deposit32(s->status0, STATUS0_WEL, 1, 1);
+ break;
+
+ case WRDI:
+ SPI_DEBUG(qemu_log("Reset Write Enable Latch (WEL) WRDI(0x%x)\n",
+ s->opcode));
+ s->status0 = deposit32(s->status0, STATUS0_WEL, 1, 0);
+ break;
+
+ case WRSR:
+ SPI_DEBUG(qemu_log("Write STATUS Register WRSR(0x%x)\n",
+ s->opcode));
+ s->status0 = deposit32(s->status0, STATUS0_WEL, 1, 1);
+ if (extract8(s->status0, STATUS0_WEL, 1) == 1) {
+ /* Mask and update status0/1 bytes */
+ s->status0 = (tx >> 16) & 0x8C;
+ s->status1 = (tx >> 8) & 0x80;
+ }
+ break;
+
+ case SPID:
+ SPI_DEBUG(qemu_log("READ IDENTIFICATION REGISTER, SPID(0x%x)\n",
+ s->opcode));
+ rx = 0;
+ for (idx = 0; idx < DATA_LEN; idx++) {
+ rx = (rx << 8) | s->idr[idx];
+ }
+ break;
+
+ case SRST:
+ SPI_DEBUG(qemu_log("Software Device Reset, SRST(0x%x)\n",
+ s->opcode));
+ /*
+ * Note: The SRST instruction cannot interrupt the device while it is
+ * in a Busy state (Section 6.1.4 Ready/Busy Status Latch).
+ * This is already taken care of when the command opcode is fetched
+ *
+ * 1.2 Device Default State
+ * 1.2.1 POWER-UP DEFAULT STATE
+ * The 25CSM04 default state upon power-up consists of:
+ * - Standby Power mode (CS = HIGH)
+ * - A high-to-low level transition on CS is required to enter the
+ * active state
+ * - WEL bit in the STATUS register = 0
+ * - ECS bit in the STATUS register = 0
+ * - PREL bit in the STATUS register = 0
+ * - Ready/Busy (RDY/BUSY) bit in the STATUS register = 0, indicating
+ * the device is ready to accept a new instruction.
+ */
+ s->status0 = deposit32(s->status0, STATUS0_WEL, 1, 0);
+ s->status1 = deposit32(s->status1, STATUS1_ECS, 1, 0);
+ s->status1 = deposit32(s->status1, STATUS1_PREL, 1, 0);
+ s->status0 = deposit32(s->status0, STATUS0_BUSY, 1, 0);
+ s->status1 = deposit32(s->status1, STATUS1_BUSY, 1, 0);
+ break;
+
+ case WRITE:
+ if (!s->command_byte) {
+ SPI_DEBUG(qemu_log("WRITE(0x%x), addr(0x%x)\n",
+ s->opcode, s->addr));
+ if (extract8(s->status0, STATUS0_WEL, 1) != 1) {
+ qemu_log_mask(LOG_GUEST_ERROR, "Device is not Write Enabled, "
+ "ignoring WRITE instruction\n");
+ break;
+ }
+ /* Write into SEEPROM Array */
+ SPI_DEBUG(qemu_log("Write sequence\n"));
+ buf = &tx;
+ if (s->file) {
+ f = fopen(s->file, "rb+");
+ if (f) {
+ if (!fseek(f, s->addr, SEEK_SET)) {
+ if (fwrite(buf, sizeof(uint32_t), 1, f) == 1) {
+ SPI_DEBUG(qemu_log("Write 4 bytes to seeprom\n"));
+ } else {
+ SPI_DEBUG(qemu_log("Failed to write seeprom\n"));
+ }
+ }
+ }
+ fclose(f);
+ }
+ /* Increase offset in the page */
+ s->addr += DATA_LEN;
+ }
+ break;
+
+ case RMPR:
+ if (!s->command_byte) {
+ /*
+ * The address for each Memory Partition register is embedded into
+ * the first address byte sent to the device,in bit positions A18
+ * through A16.
+ */
+ SPI_DEBUG(qemu_log("RMPR(0x%x) for MPR[%d]\n", s->opcode,
+ extract8(s->addr, 16, 2)));
+ rx = 0;
+ rx = rx | (s->mpr[extract8(s->addr, 16, 2)] << 24);
+ }
+ break;
+
+ case PRWE:
+ SPI_DEBUG(qemu_log("Set Memory Partition Write Enable Latch "
+ "PRWE(0x%x)\n", s->opcode));
+ s->status1 = deposit32(s->status1, STATUS1_PREL, 1, 1);
+ break;
+
+ case PRWD:
+ SPI_DEBUG(qemu_log("Reset Memory Partition Write Enable Latch "
+ "PRWD(0x%x)\n", s->opcode));
+ s->status1 = deposit32(s->status1, STATUS1_PREL, 1, 0);
+ break;
+
+ case WMPR:
+ if (!s->command_byte) {
+ /*
+ * The address for each Memory Partition register is embedded into
+ * the first address byte sent to the device,in bit positions A18
+ * through A16.
+ */
+ SPI_DEBUG(qemu_log("Write Memory Partition Register[%d] "
+ "WMPR(0x%x)\n",
+ extract8(s->addr, 16, 2), s->opcode));
+ /*
+ * Once the WEL and PREL bits in the STATUS register have been
+ * set to 1, the Memory Partition registers can be programmed
+ * provided that the FMPC bit in the STATUS register has not
+ * already been set to a logic 1.
+ */
+ if ((extract8(s->status0, STATUS0_WEL, 1) != 1) ||
+ (extract8(s->status1, STATUS1_PREL, 1) != 1) ||
+ (extract8(s->status1, STATUS1_FMPC, 1) == 1)) {
+ qemu_log_mask(LOG_GUEST_ERROR, "ignoring write to MPR\n");
+ break;
+ }
+ if (extract8(s->status1, STATUS1_PABP, 1) == 1) {
+ /* Partition Address Boundaries Protected */
+ s->mpr[extract8(s->addr, 16, 2)] =
+ ((tx >> 30) & 0x3);
+ } else {
+ s->mpr[extract8(s->addr, 16, 2)] = (tx >> 24) & 0xFF;
+ }
+ s->status0 = deposit32(s->status0, STATUS0_WEL, 1, 0);
+ s->status1 = deposit32(s->status1, STATUS1_PREL, 1, 0);
+ }
+ break;
+
+ case PPAB:
+ if (!s->command_byte) {
+ SPI_DEBUG(qemu_log("Protect Partition Address Boundaries"
+ "PPAB(0x%x)\n", s->opcode));
+ if ((extract8(s->status0, STATUS0_WEL, 1) != 1) ||
+ (extract8(s->status1, STATUS1_PREL, 1) != 1) ||
+ (extract8(s->status1, STATUS1_FMPC, 1) == 1)) {
+ qemu_log_mask(LOG_GUEST_ERROR, "Ignoring PPAB command\n");
+ break;
+ }
+ tx = (tx >> 24) & 0xFF;
+ if (tx == 0xFF) {
+ s->status1 = deposit32(s->status1,
+ STATUS1_PABP, 1, 1);
+ } else if (tx == 0x0) {
+ s->status1 = deposit32(s->status1,
+ STATUS1_PABP, 1, 0);
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR, "Incorrect data byte(0x%x), "
+ "should be 0x0 or 0xFF\n", tx);
+ }
+ s->status0 = deposit32(s->status0, STATUS0_WEL, 1, 0);
+ s->status1 = deposit32(s->status1, STATUS1_PREL, 1, 0);
+ }
+ break;
+
+ case FRZR:
+ if (!s->command_byte) {
+ SPI_DEBUG(qemu_log("Freeze Memory Protection Configuration "
+ "FRZR(0x%x)\n", s->opcode));
+ if ((extract8(s->status0, STATUS0_WEL, 1) != 1) ||
+ (extract8(s->status1, STATUS1_PREL, 1) != 1) ||
+ (extract8(s->status1, STATUS1_FMPC, 1) == 1)) {
+ qemu_log_mask(LOG_GUEST_ERROR, "ignoring FRZR command\n");
+ break;
+ }
+ tx = (tx >> 24) & 0xFF;
+ if (tx == 0xD2) {
+ s->status1 = deposit32(s->status1,
+ STATUS1_FMPC, 1, 1);
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR, "Invalid confirmation data "
+ "byte(0x%x), expecting 0xD2", tx);
+ }
+ s->status0 = deposit32(s->status0, STATUS0_WEL, 1, 0);
+ s->status1 = deposit32(s->status1, STATUS1_PREL, 1, 0);
+ }
+ break;
+
+ case RDEX_CHLK:
+ if (!s->command_byte) {
+ SPI_DEBUG(qemu_log("OPCODE = (0x%x)\n", s->opcode));
+ rx = 0;
+ /* Address bit 10 must be 0 to read security register */
+ if (extract8(s->addr, 10, 1) == 0) {
+ uint16_t sidx;
+ /* RDEX */
+ s->rd_state = ST_SEC_READ;
+ for (idx = 0; idx < DATA_LEN; idx++) {
+ sidx = s->addr & 0x1FF;
+ if (sidx <= 0xFF) {
+ rx = (rx << 8) | s->dsn[sidx];
+ } else {
+ rx = (rx << 8) | s->uplid[sidx & 0xFF];
+ }
+ s->addr = (s->addr & ~0x1FF) | ((s->addr + 1) & 0x1FF);
+ }
+ } else {
+ /* CHLK */
+ if (s->locked) {
+ rx = rx | (0x01 << 24);
+ }
+ }
+ }
+ break;
+
+ case WREX_LOCK:
+ if (!s->command_byte) {
+ SPI_DEBUG(qemu_log("OPCODE = (0x%x)\n", s->opcode));
+ if (s->locked == true) {
+ qemu_log_mask(LOG_GUEST_ERROR, "Device is already Locked, "
+ "command is ignored\n");
+ break;
+ }
+ if (extract8(s->status0, STATUS0_WEL, 1) != 1) {
+ qemu_log_mask(LOG_GUEST_ERROR, "Device is not Write Enabled, "
+ "command is ignored\n");
+ break;
+ }
+ /* Address bit 10 must be 0 to write to security register */
+ if (extract8(s->addr, 10, 1) == 0) {
+ /* WREX */
+ for (idx = 0; idx < DATA_LEN; idx++) {
+ /*
+ * The Device Serial Number is factory programmed and
+ * read-only.
+ */
+ s->uplid[extract8(s->addr, 0, 8)] =
+ (tx >> (24 - idx * 8)) & 0xFF;
+ /* Increase address with the page, and let it rollover*/
+ s->addr = (s->addr & ~0xFF) | ((s->addr + 1) & 0xFF);
+ }
+ } else {
+ /*
+ * LOCK (82h) instruction is clocked in on the SI line,
+ * followed by a fake address where bits A[23:0] are don't
+ * care bits with the exception that bit A10 must be set to 1.
+ * Finally, a confirmation data byte of xxxx_xx1xb is sent
+ */
+ if (((tx >> 24) & 0x02) == 0x2) {
+ s->locked = true;
+ }
+ }
+ }
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "Invalid instruction(0x%x)\n",
+ s->opcode);
+ } /* end of switch */
+ if (s->command_byte) {
+ s->command_byte = false;
+ }
+ return rx;
+}
+
+/*
+ * Method : compute_addr
+ * This method is used to compute address and data offset for supported
+ * opcodes and only invoked when a valid new command sequence starts aka
+ * first is 1.
+ */
+bool compute_addr(SeepromCsm04 *s, uint32_t tx)
+{
+ bool addr_wr_protected = false;
+ bool failed = false;
+
+ switch (s->opcode) {
+ case READ:
+ case WRITE:
+ SPI_DEBUG(qemu_log("Compute address and payload buffer data offset "
+ "for %s\n", (s->opcode == READ) ? "READ" : "WRITE"));
+ /*
+ * Fetch address from size 24 bit from offset 1,2,3 of payload
+ * and mask of higher 5 bits as valid memory array size is 512KB
+ */
+ s->addr = tx & 0x7FFFF;
+ if (s->opcode == WRITE) {
+ addr_wr_protected = validate_addr(s);
+ if (addr_wr_protected) {
+ qemu_log_mask(LOG_GUEST_ERROR, "SEEPROM Address(0x%x) is Write
"
+ "protected\n", s->addr);
+ failed = true;
+ }
+ }
+ break;
+ case RMPR:
+ case WMPR:
+ SPI_DEBUG(qemu_log("Compute MPR address for %s MPR\n",
+ (s->opcode == RMPR) ? "READ" : "WRITE"));
+ /*
+ * The address for each Memory Partition register is embedded into
+ * the first address byte sent to the device,in bit positions A18
+ * through A16.
+ */
+ s->addr = tx & 0x70000;
+ break;
+
+ case PPAB:
+ case FRZR:
+ SPI_DEBUG(qemu_log("Validate if addr[15:0] is %s\n",
+ (s->opcode == PPAB) ? "0xCCFF for PPAB" :
+ "0xAA40 for FRZR"));
+ /* Address bits A23-A16 are ignored. */
+ s->addr = tx & 0xFFFF;
+ /* Address bits A15-A0 must be set to CC55h. */
+ if ((s->opcode == PPAB) && s->addr != 0xCC55) {
+ qemu_log_mask(LOG_GUEST_ERROR, "Invalid addr[15:0] = 0x%x sent for
"
+ "PPAB\n", s->addr);
+ failed = true;
+ }
+ /* Address bits A15-A0 must be set to AA40h. */
+ if ((s->opcode == FRZR) && s->addr != 0xAA40) {
+ qemu_log_mask(LOG_GUEST_ERROR, "Invalid addr[15:0] = 0x%x sent for
"
+ "FRZR\n", s->addr);
+ failed = true;
+ }
+ break;
+
+ case RDEX_CHLK:
+ case WREX_LOCK:
+ SPI_DEBUG(qemu_log("Compute Address for Security reg command\n"));
+ /*
+ * RDEX : A[23:9] are don't care bits, except A10 which must be a
+ * logic 0.
+ * WREX : A[23:9] are don't care bits, except A10 which must be a
+ * logic 0 and A8 which must be a logic 1 to address the
+ * second Security register byte that is user programmable.
+ * CHLK : A[23:0] are don't care bits, except A10 which must be a
+ * logic 1.
+ * LOCK : A[23:0] are don't care bits, except A10 which must be a
+ * logic 1.
+ */
+ s->addr = tx & 0x5FF;
+ SPI_DEBUG(qemu_log("Received Command %s\n",
+ (s->opcode == RDEX_CHLK)
+ ? (extract32(s->addr, 10, 1) ?
+ "CHLK : Check Lock Status of Security Register" :
+ "RDEX : Read from the Security Register")
+ : (extract32(s->addr, 10, 1) ?
+ "LOCK : Lock the Security Register (permanent)" :
+ "WREX : Write to the Security Register")));
+ if ((s->opcode == WREX_LOCK) &&
+ (extract32(s->addr, 10, 1) == 0)) {
+ /*
+ * WREX
+ * In Legacy Write Protection mode, the Security register is
+ * write-protected when the BP <1:0> bits (bits 3-2 byte0) of
+ * the STATUS register = 11.
+ */
+ if (extract8(s->status1, STATUS1_WPM, 1) == 0) {
+ addr_wr_protected = validate_addr(s);
+ } else {
+ if (extract32(s->addr, 0, 9) <= 0xFF) {
+ addr_wr_protected = true;
+ }
+ }
+ if (addr_wr_protected) {
+ qemu_log_mask(LOG_GUEST_ERROR, "SEEPROM Address(0x%x) is "
+ "Write protected\n", s->addr);
+ failed = true;
+ }
+ }
+ break;
+ } /* end of switch */
+ return failed;
+} /* end of method compute_addr */
+
+/*
+ * Method : validate_addr
+ * This method validates whether SEEPROM address is write protected or not
+ */
+
+bool validate_addr(SeepromCsm04 *s)
+{
+ bool addr_wr_protected = false;
+ uint8_t mpr_idx = 0;
+
+ if (extract8(s->status1, STATUS1_WPM, 1) == 1) {
+ /*
+ * enhanced write protection
+ * Memory partition register Bit5 through bit0 contain the Partition
+ * Endpoint Address of A18:A13, where A12:A0 are a logic "1". For
+ * example, if the first partition of the memory array is desired to
+ * stop after 128-Kbit of memory, that end point address is 03FFFh. The
+ * corresponding A18:A13 address bits to be loaded into MPR0 are
+ * therefore 000001b. The eight MPRs are each decoded sequentially by
+ * the device, starting with MPR0. Each MPR should be set to a
+ * Partition Endpoint Address greater than the ending address of the
+ * previous MPR. If a higher order MPR sets a Partition Endpoint
Address
+ * less than or equal to the ending address of a lower order MPR, that
+ * higher order MPR is ignored and no protection is set by it's
+ * contents.
+ */
+ for (mpr_idx = 0; mpr_idx < 8; mpr_idx++) {
+ if ((extract32(s->addr, 13, 6)) <=
+ (extract8(s->mpr[mpr_idx], MPR_PEA, 1))) {
+ switch (extract8(s->mpr[mpr_idx], MPR_PB, 2)) {
+ case 0:
+ /*
+ * 0b00 = Partition is open and writing is permitted
+ * (factory default is unprotected).
+ */
+ addr_wr_protected = false;
+ break;
+ case 1:
+ /*
+ * 0b01 = Partition is always write-protected but can be
+ * reversed at a later time (software write-protected).
+ */
+ addr_wr_protected = true;
+ break;
+ case 2:
+ /*
+ * 0b10 = Partition is write-protected only when WP pin is
+ * asserted (hardware write-protected).
+ */
+ addr_wr_protected = false;
+ break;
+ case 3:
+ /*
+ * 0b11 = Partition is software write-protected and Memory
+ * Partition register is permanently locked.
+ */
+ addr_wr_protected = true;
+ break;
+ } /* end of switch */
+ break; /* break from for loop. */
+ }
+ } /* end of for loop */
+ } else {
+ /* Legacy write protection mode */
+ switch (extract8(s->status0, STATUS0_BP, 2)) {
+ case 0:
+ /*
+ * 0b00 = No array write protection
+ * EEPROM None
+ * Security Register 00000h - 000FFh
+ */
+ if ((s->opcode == WREX_LOCK) &&
+ (extract32(s->addr, 0, 9) <= 0xFF)) {
+ addr_wr_protected = true;
+ }
+ break;
+ case 1:
+ /*
+ * 0b01 = Upper quarter memory array protection
+ * EEPROM 60000h - 7FFFFh
+ * Security Register 00000h - 000FFh
+ */
+ if ((s->opcode == WREX_LOCK) &&
+ (extract32(s->addr, 0, 9) <= 0xFF)) {
+ addr_wr_protected = true;
+ } else if ((s->opcode == WRITE) &&
+ (extract32(s->addr, 0, 19) <= 0x60000)) {
+ addr_wr_protected = true;
+ }
+ break;
+ case 2:
+ /*
+ * 0b10 = Upper half memory array protection
+ * EEPROM 40000h - 7FFFFh
+ * Security Register 00000h - 000FFh
+ */
+ if ((s->opcode == WREX_LOCK) &&
+ (extract32(s->addr, 0, 9) <= 0xFF)) {
+ addr_wr_protected = true;
+ } else if ((s->opcode == WRITE) &&
+ (extract32(s->addr, 0, 19) <= 0x40000)) {
+ addr_wr_protected = true;
+ }
+ break;
+ case 3:
+ /*
+ * 0b11 = Entire memory array protection
+ * EEPROM 00000h - 7FFFFh
+ * Security Register 00000h - 001FFh
+ */
+ addr_wr_protected = true;
+ break;
+ } /* end of switch */
+ }
+ return addr_wr_protected;
+} /* end of validate_addr */
+
+
+static int seeprom_cs(SSIPeripheral *ss, bool select)
+{
+ SeepromCsm04 *s = SEEPROM_25CSM04(ss);
+
+ if (select) {
+ s->command_byte = false;
+ s->rd_state = ST_IDLE;
+ s->status0 = deposit32(s->status0, STATUS0_WEL, 1, 0);
+ } else {
+ s->command_byte = true;
+ }
+ return 0;
+}
+
+
+void seeprom_realize(SSIPeripheral *dev, Error **errp)
+{
+ SeepromCsm04 *s = SEEPROM_25CSM04(dev);
+
+ s->command_byte = true;
+ s->rd_state = ST_IDLE;
+ if (s->file_name) {
+ s->file = qemu_find_file(QEMU_FILE_TYPE_BIOS, s->file_name);
+ }
+}
+
+static Property seeprom_props[] = {
+ DEFINE_PROP_STRING("filename", SeepromCsm04, file_name),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void seeprom_25csm04_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SSIPeripheralClass *k = SSI_PERIPHERAL_CLASS(klass);
+
+ k->transfer = seeprom_transfer;
+ k->realize = seeprom_realize;
+ k->set_cs = seeprom_cs;
+ k->cs_polarity = SSI_CS_LOW;
+ device_class_set_props(dc, seeprom_props);
+
+ dc->desc = "PowerNV SPI SEEPROM";
+}
+
+static const TypeInfo seeprom_25csm04_info = {
+ .name = TYPE_SEEPROM_25CSM04,
+ .parent = TYPE_SSI_PERIPHERAL,
+ .instance_size = sizeof(SeepromCsm04),
+ .class_init = seeprom_25csm04_class_init,
+};
+
+static void seeprom_25csm04_register_types(void)
+{
+ type_register_static(&seeprom_25csm04_info);
+}
+
+type_init(seeprom_25csm04_register_types);
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 1e08785b83..9442cc657d 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -38,6 +38,9 @@ config PCA9554
bool
depends on I2C
+config SEEPROM_25CSM04
+ bool
+
config I2C_ECHO
bool
default y if TEST_DEVICES
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 86596a3888..fd4d646f98 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -3,6 +3,7 @@ system_ss.add(when: 'CONFIG_EDU', if_true: files('edu.c'))
system_ss.add(when: 'CONFIG_FW_CFG_DMA', if_true: files('vmcoreinfo.c'))
system_ss.add(when: 'CONFIG_ISA_DEBUG', if_true: files('debugexit.c'))
system_ss.add(when: 'CONFIG_ISA_TESTDEV', if_true: files('pc-testdev.c'))
+system_ss.add(when: 'CONFIG_SEEPROM_25CSM04', if_true:
files('seeprom_25csm04.c'))
system_ss.add(when: 'CONFIG_PCI_TESTDEV', if_true: files('pci-testdev.c'))
system_ss.add(when: 'CONFIG_UNIMP', if_true: files('unimp.c'))
system_ss.add(when: 'CONFIG_EMPTY_SLOT', if_true: files('empty_slot.c'))
diff --git a/hw/ppc/Kconfig b/hw/ppc/Kconfig
index ea1178bd73..6a4803d4ec 100644
--- a/hw/ppc/Kconfig
+++ b/hw/ppc/Kconfig
@@ -36,6 +36,7 @@ config POWERNV
select PCA9552
select PCA9554
select SSI
+ select SEEPROM_25CSM04
config PPC405
bool