Message ID | 20240409175700.27535-5-chalapathi.v@linux.ibm.com |
---|---|
State | New |
Headers | show |
Series | hw/ppc: SPI model | expand |
Hello Chalapathi On 4/9/24 19:56, Chalapathi V wrote: > This commit implements a Serial EEPROM utilizing the Serial Peripheral > Interface (SPI) compatible bus. > Currently implemented SEEPROM is Microchip's 25CSM04 which 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. > > This seeprom device is created from a parent "ssi-peripheral". Can the hw/block/m25p80c model be extented instead ? Thanks, C. > > Signed-off-by: Chalapathi V <chalapathi.v@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
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
This commit implements a Serial EEPROM utilizing the Serial Peripheral Interface (SPI) compatible bus. Currently implemented SEEPROM is Microchip's 25CSM04 which 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. This seeprom device is created from a parent "ssi-peripheral". Signed-off-by: Chalapathi V <chalapathi.v@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