From nobody Tue Nov 26 14:31:26 2024 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=reject dis=none) header.from=linux.ibm.com ARC-Seal: i=1; a=rsa-sha256; t=1707322226; cv=none; d=zohomail.com; s=zohoarc; b=VZdHCAXFFDkpFsC9HsHbNq7C9Z+DT+v4wv7S4b+avvye1c+sdQVp/jJSka8zK9dZenMmOzZE90igWvKxvJo6LrLNA3OkGQR0m0IIA6o+1sVAG/PESGWoB7XrXR7i0shsimmkUd1QxeGhEjiOFhM7w+RegBN/aGtw4mNgGRWMiS4= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1707322226; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=Gv1BfOnmXJQuk9c5ZetVzpZ8PktFhdTVVvoltBjaS+o=; b=Vr4cKTsx6m4LT5JgK8zI/PhQehzekAuCi6mxBeENJ8hOrzVoV6EhcIJ8nneabzuVZPFNzubxglykEC4pNOpyty57I5NRoSRRgNz/bwKQNS/I/DTihHhTOI0OHzPZO8onKTfWxwyR/nLNwrMygQ1EQCtl/W7Yf4CFUNB3xMRI9hY= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=reject dis=none) Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 17073222265991013.2430107739151; Wed, 7 Feb 2024 08:10:26 -0800 (PST) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1rXkUG-0003oR-4r; Wed, 07 Feb 2024 11:09:24 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1rXkUA-0003fl-Ft; Wed, 07 Feb 2024 11:09:19 -0500 Received: from mx0b-001b2d01.pphosted.com ([148.163.158.5]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1rXkU5-0000mK-DB; Wed, 07 Feb 2024 11:09:16 -0500 Received: from pps.filterd (m0353725.ppops.net [127.0.0.1]) by mx0a-001b2d01.pphosted.com (8.17.1.19/8.17.1.19) with ESMTP id 417FvV9Z020150; Wed, 7 Feb 2024 16:09:04 GMT Received: from pps.reinject (localhost [127.0.0.1]) by mx0a-001b2d01.pphosted.com (PPS) with ESMTPS id 3w4cu5rn2s-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Wed, 07 Feb 2024 16:09:03 +0000 Received: from m0353725.ppops.net (m0353725.ppops.net [127.0.0.1]) by pps.reinject (8.17.1.5/8.17.1.5) with ESMTP id 417FvvuL023175; Wed, 7 Feb 2024 16:09:03 GMT Received: from ppma11.dal12v.mail.ibm.com (db.9e.1632.ip4.static.sl-reverse.com [50.22.158.219]) by mx0a-001b2d01.pphosted.com (PPS) with ESMTPS id 3w4cu5rn2c-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Wed, 07 Feb 2024 16:09:03 +0000 Received: from pps.filterd (ppma11.dal12v.mail.ibm.com [127.0.0.1]) by ppma11.dal12v.mail.ibm.com (8.17.1.19/8.17.1.19) with ESMTP id 417FS9px016195; Wed, 7 Feb 2024 16:09:02 GMT Received: from smtprelay07.fra02v.mail.ibm.com ([9.218.2.229]) by ppma11.dal12v.mail.ibm.com (PPS) with ESMTPS id 3w22h26811-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Wed, 07 Feb 2024 16:09:02 +0000 Received: from smtpav01.fra02v.mail.ibm.com (smtpav01.fra02v.mail.ibm.com [10.20.54.100]) by smtprelay07.fra02v.mail.ibm.com (8.14.9/8.14.9/NCO v10.0) with ESMTP id 417G8xnu28705138 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK); Wed, 7 Feb 2024 16:08:59 GMT Received: from smtpav01.fra02v.mail.ibm.com (unknown [127.0.0.1]) by IMSVA (Postfix) with ESMTP id 20DFC20043; Wed, 7 Feb 2024 16:08:59 +0000 (GMT) Received: from smtpav01.fra02v.mail.ibm.com (unknown [127.0.0.1]) by IMSVA (Postfix) with ESMTP id 5110920040; Wed, 7 Feb 2024 16:08:57 +0000 (GMT) Received: from gfwr515.rchland.ibm.com (unknown [9.10.239.103]) by smtpav01.fra02v.mail.ibm.com (Postfix) with ESMTP; Wed, 7 Feb 2024 16:08:57 +0000 (GMT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=ibm.com; h=from : to : cc : subject : date : message-id : in-reply-to : references : mime-version : content-transfer-encoding; s=pp1; bh=Gv1BfOnmXJQuk9c5ZetVzpZ8PktFhdTVVvoltBjaS+o=; b=oYEnA3pnqJj7DVmg7h67EpcNIdV6GtwYuqW5h1lPfGWaxNRPNAiwnKHjzWxpyXmt5Uz9 jiDcHk3ac5rKOJ8DkincQZAPs0mpoZMcbmJ9ZmPmBsygthw75X+QEtKsP5ewnEjE2T9z H0vw916gjuhIhdQ4fjIpaBkt9gOli+QsGleZOxBkk2S5ARXnbIQeMET/dprSaQizTEU5 zabbSO2PeOOlRaDV7sjdJHcXOeZrCKPAERRuKIQHwHvRu0dzvTjJUvGzMJp+Bab6MxTF VRT6K95BJZSjG+8Yi9BV+BspY0u4+2I+vWhqCa4JrmaULhu549eixDLYhCRXubzYmWI+ ug== From: Chalapathi V To: qemu-devel@nongnu.org Cc: qemu-ppc@nongnu.org, fbarrat@linux.ibm.com, npiggin@gmail.com, clg@kaod.org, calebs@us.ibm.com, chalapathi.v@ibm.com, chalapathi.v@linux.ibm.com, saif.abrar@linux.vnet.ibm.com Subject: [PATCH v1 4/5] hw/ppc: SPI SEEPROM model Date: Wed, 7 Feb 2024 10:08:32 -0600 Message-Id: <20240207160833.3437779-5-chalapathi.v@linux.ibm.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20240207160833.3437779-1-chalapathi.v@linux.ibm.com> References: <20240207160833.3437779-1-chalapathi.v@linux.ibm.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-TM-AS-GCONF: 00 X-Proofpoint-GUID: X0YspNJluhAQpNZWYDzM3RGVBCGUtxhS X-Proofpoint-ORIG-GUID: q46Q9QbGFLSGBsYIsTJrq09wBcfJZV3P X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.272,Aquarius:18.0.1011,Hydra:6.0.619,FMLib:17.11.176.26 definitions=2024-02-07_06,2024-02-07_01,2023-05-22_02 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 phishscore=0 malwarescore=0 mlxscore=0 lowpriorityscore=0 adultscore=0 spamscore=0 impostorscore=0 mlxlogscore=999 bulkscore=0 priorityscore=1501 clxscore=1015 suspectscore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.12.0-2311290000 definitions=main-2402070119 Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=148.163.158.5; envelope-from=chalapathi.v@linux.ibm.com; helo=mx0b-001b2d01.pphosted.com X-Spam_score_int: -19 X-Spam_score: -2.0 X-Spam_bar: -- X-Spam_report: (-2.0 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_MSPIKE_H4=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, T_SCC_BODY_TEXT_LINE=-0.01 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @ibm.com) X-ZM-MESSAGEID: 1707322228311100003 Content-Type: text/plain; charset="utf-8" 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. Signed-off-by: Chalapathi V --- include/hw/ppc/pnv_spi_seeprom.h | 70 +++ hw/ppc/pnv_spi_seeprom.c | 989 +++++++++++++++++++++++++++++++ hw/ppc/meson.build | 1 + 3 files changed, 1060 insertions(+) create mode 100644 include/hw/ppc/pnv_spi_seeprom.h create mode 100644 hw/ppc/pnv_spi_seeprom.c diff --git a/include/hw/ppc/pnv_spi_seeprom.h b/include/hw/ppc/pnv_spi_seep= rom.h new file mode 100644 index 0000000000..9739e411b5 --- /dev/null +++ b/include/hw/ppc/pnv_spi_seeprom.h @@ -0,0 +1,70 @@ +/* + * QEMU PowerPC SPI SEEPROM model + * + * Copyright (c) 2024, IBM Corporation. + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * This model implements a Serial EEPROM utilizing the Serial Peripheral + * Interface (SPI) compatible bus. + * Currently supported variants: 25CSM04. + * The Microchip Technology Inc. 25CSM04 provides 4 Mbits of Serial EEPROM + * utilizing the Serial Peripheral Interface (SPI) compatible bus. The dev= ice + * 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 PPC_PNV_SPI_SEEPROM_H +#define PPC_PNV_SPI_SEEPROM_H + +#include "hw/ppc/pnv_spi_responder.h" +#include "qom/object.h" + +#define TYPE_PNV_SPI_SEEPROM "pnv-spi-seeprom" + +OBJECT_DECLARE_SIMPLE_TYPE(PnvSpiSeeprom, PNV_SPI_SEEPROM) + +typedef struct xfer_buffer xfer_buffer; + +typedef struct PnvSpiSeeprom { + PnvSpiResponder resp; + + char *file; /* SEEPROM image file */ + uint8_t opcode; /* SEEPROM Opcode */ + uint32_t addr; /* SEEPROM Command Address */ + uint8_t rd_state; /* READ State Machine state variable */ + bool locked; /* Security Register Locked */ + bool controller_connected; /* Flag for master connection */ + /* + * Device registers + * The 25CSM04 contains four types of registers that modulate device + * operation and/or report on the current status of the device. These + * registers are: + * STATUS register + * Security register + * Memory Partition registers (eight total) + * Identification register + */ + uint8_t status0; + uint8_t status1; + /* + * The Security register is split into + * 1. user-programmable lockable ID page section. + * 2. The read-only section contains a preprogrammed, globally unique, + * 128-bit serial number. + */ + uint8_t uplid[256]; + uint8_t dsn[16]; + uint8_t mpr[8]; + uint8_t idr[5]; +} PnvSpiSeeprom; + +xfer_buffer *seeprom_spi_request(PnvSpiResponder *resp, int first, int las= t, + int bits, xfer_buffer *payload); +void seeprom_connect_controller(PnvSpiResponder *resp, const char *port); +void seeprom_disconnect_controller(PnvSpiResponder *resp); +bool compute_addr(PnvSpiSeeprom *spi_resp, xfer_buffer *req_payload, + xfer_buffer *rsp_payload, int bits, uint32_t *data_offs= et); +bool validate_addr(PnvSpiSeeprom *spi_resp); +#endif /* PPC_PNV_SPI_SEEPROM_H */ diff --git a/hw/ppc/pnv_spi_seeprom.c b/hw/ppc/pnv_spi_seeprom.c new file mode 100644 index 0000000000..ae46610045 --- /dev/null +++ b/hw/ppc/pnv_spi_seeprom.c @@ -0,0 +1,989 @@ +/* + * QEMU PowerPC SPI SEEPROM model + * + * Copyright (c) 2024, IBM Corporation. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "hw/ppc/pnv_spi_seeprom.h" +#include + +#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 =3D Write-Protect pin is enabled, 0 =3D Write-Protect pin is ignored + * + * bit 3-2 BP<1:0>: Block Protection bits + * 00 =3D No array write protection + * 01 =3D Upper quarter memory array protection + * 10 =3D Upper half memory array protection + * 11 =3D Entire memory array protection + * + * bit 1 WEL: Write Enable Latch bit + * 1 =3D WREN has been executed and device is enabled for writing + * 0 =3D Device is not write-enabled + * + * bit 0 RDY/BSY: Ready/Busy Status Latch bit + * 1 =3D Device is busy with an internal write cycle + * 0 =3D 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 =3D Enhanced Write Protection mode selected (factory default) + * 0 =3D Legacy Write Protection mode selected + * + * bit 6 ECS: Error Correction State Latch bit + * 1 =3D The previously executed read sequence did require the ECC + * 0 =3D The previous executed read sequence did not require the ECC + * + * bit 5 FMPC: Freeze Memory Protection Configuration bit(2) + * 1 =3D Memory Partition registers and write protection mode are permanen= tly + * frozen and cannot be modified + * 0 =3D Memory Partition registers and write protection mode are not froz= en + * and are modifiable + * + * bit 4 PREL: Partition Register Write Enable Latch bit + * 1 =3D PRWE has been executed and WMPR, FRZR and PPAB instructions are e= nabled + * 0 =3D WMPR, FRZR and PPAB instructions are disabled + * + * bit 3 PABP: Partition Address Boundary Protection bit + * 1 =3D Partition Address Endpoints set in Memory Partition registers + * cannot be modified + * 0 =3D Partition Address Endpoints set in Memory Partition registers + * are modifiable + * + * bit 0 RDY/BSY: Ready/Busy Status Latch bit + * 1 =3D Device is busy with an internal write cycle + * 0 =3D 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 PA= BP + * bit has been set. + * + * bits 7-6 PB<1:0>: Partition Behavior bits(1) + * 00 =3D Partition is open and writing is permitted + * factory default is unprotected. + * 01 =3D Partition is always write-protected but can be reversed at a lat= er + * time (software write-protected). + * 10 =3D Partition is write-protected only when WP pin is asserted + * (hardware write-protected). + * 11 =3D Partition is software write-protected and MPR is permanently loc= ked + * + * bit 5-0 A<18:13>: Partition Endpoint Address bits(1, 2) + * 000000 =3D Endpoint address of partition is set to 01FFFh. + * 000001 =3D Endpoint address of partition is set to 03FFFh. + * ---- + * 111110 =3D Endpoint address of partition is set to 7DFFFh. + * 111111 =3D 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 0x0 +#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 + +xfer_buffer *seeprom_spi_request(PnvSpiResponder *resp, + int first, int last, int bits, xfer_buffer *payload) +{ + PnvSpiSeeprom *seeprom =3D PNV_SPI_SEEPROM(resp); + uint32_t data_offset =3D 0; + int data_len =3D 0; + xfer_buffer *rsp_payload =3D NULL; + uint8_t *read_buf =3D NULL; + uint8_t *buf =3D NULL; + uint16_t idx; + bool failed =3D false; + SPI_DEBUG(qemu_log("Received SPI request, first=3D%d, last=3D%d, bits= =3D%d, " + "payload length=3D%d\n", first, last, bits, payload->len)); + if (seeprom->controller_connected =3D=3D false) { + qemu_log_mask(LOG_GUEST_ERROR, "Controller is disconnected, invoke= " + "connect method of spi_responder interface\n"); + return rsp_payload; + } + if (rsp_payload =3D=3D NULL) { + rsp_payload =3D xfer_buffer_new(); + } + buf =3D xfer_buffer_write_ptr(rsp_payload, 0, payload->len); + memset(buf, 0xFF, payload->len); + /* + * SPI communication is always full-duplex, so the controller receives= as + * many bits as it sends, although often both the responder and contro= ller + * device ignores some incoming bits. To simulate half-duplex the cont= roller + * sends zeros to the responder when controller is receiving and ignor= es + * incoming data when the controller transmitting. So, a SPI response = should + * always have the same length in bits as the corresponding request. + */ + if ((payload->len !=3D ceil(bits / 8)) || (payload->len <=3D 0)) { + qemu_log_mask(LOG_GUEST_ERROR, "Incorrect Payload size bits(%d) " + "Payload_len(%d bytes)\n", bits, payload->len); + return rsp_payload; + } + if ((bits % 8) !=3D 0) { + qemu_log_mask(LOG_GUEST_ERROR, "non-8bit aligned SPI transfer is " + "unimplemented\n"); + return rsp_payload; + } + /* + * Different scenarios for first and last SPI interface method paramet= ers + * + * first(1) and last(1) + * SPI Controller can invoke spi_request with parameters first(1) and + * last(1), which indicates this is first and last spi_request in this + * transaction. This can be used when the valid data (excluding fake b= ytes) + * transmitted or received over SPI is less than or equal to 8 Bytes + * + * first(1) and last(0), # (required) first request + * first(0) and last(0), # (optional) in-between requests + * first(0) and last(0), # (optional) in-between requests + * .. + * .. + * first(0) and last(1), # (required) last request in the transaction + * SPI Controller can invoke spi_request multiple times with parameters + * first and last as shown in the sequence above for a transaction. Th= is + * can be used when the valid data(excluding fake bytes) transmitted or + * received over SPI is more than 8 Bytes, SPI controller splits the + * transaction into multiple requests, this is due to TDR and RDR size= (8B) + * restriction in SPI Controller. + */ + + /* + * check if first is "1", indicates a new incoming command sequence fe= tch + * the opcode and address from payload. + */ + if (first =3D=3D 1) { + /* Fetch opcode from offset 0 of payload */ + xfer_buffer_read_ptr(payload, &read_buf, 0, 1); + seeprom->opcode =3D read_buf[0]; + SPI_DEBUG(qemu_log("Command Opcode (0x%x)\n", seeprom->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. + */ + bool status0_busy =3D extract8(seeprom->status0, STATUS0_BUSY, 1); + bool status1_busy =3D extract8(seeprom->status1, STATUS1_BUSY, 1); + if (((status0_busy =3D=3D 1) || (status1_busy =3D=3D 1)) && + ((seeprom->opcode !=3D RDSR) || (seeprom->opcode !=3D WRBP))) { + qemu_log_mask(LOG_GUEST_ERROR, "Busy with Internal Write Cycle= , " + "opcode(0x%x) not executed\n", seeprom->opcode); + return rsp_payload; + } + /* + * Implement a state machine for READ sequence, to catch an error + * scenario when controller generates a new command sequence, with= out + * properly terminating the READ sequence, as shown below + * first(1) and last(0), # READ command + * first(0) and last(0), # READ command continues + * ... + * first(1) and last(0,1), # New command sequence + * Not required to implement a state machine for write sequence as + * we can leverage status register for it + */ + if (seeprom->rd_state !=3D ST_IDLE) { + qemu_log_mask(LOG_GUEST_ERROR, "New Command Sequence with " + "opcode(0x%x)is ignored Previous READ sequence = is " + "not terminated properly!!! Continuing the prev= ious " + "READ sequence\n", seeprom->opcode); + seeprom->opcode =3D (seeprom->rd_state =3D=3D ST_READ) ? READ : + RDEX_CHLK; + } else { + /* + * For a new command sequence compute Address and data offset = in + * xfer_buffer. + */ + failed =3D compute_addr(seeprom, payload, rsp_payload, bits, + &data_offset); + /* + * Address computation failed, nothing to do further, just send + * response and return from here. + */ + if (failed =3D=3D true) { + return rsp_payload; + } + } + } /* end of branch if (first =3D=3D 1) */ + switch (seeprom->opcode) { + case READ: + SPI_DEBUG(qemu_log("READ(0x%x), addr(0x%x)\n", + seeprom->opcode, seeprom->addr)); + seeprom->rd_state =3D ST_READ; + data_len =3D payload->len - data_offset; + /* Make sure data is at least 1 Byte */ + if (data_len <=3D 0) { + qemu_log_mask(LOG_GUEST_ERROR, "Insufficient Data Bytes(%dB), " + "should be at least 1 Byte\n", data_len); + break; + } + /* Fill the buffer with the data read from image */ + buf =3D xfer_buffer_write_ptr(rsp_payload, data_offset, data_len); + FILE *f; + if (seeprom->file) { + f =3D fopen(seeprom->file, "rb+"); + if (f) { + fseek(f, seeprom->addr, SEEK_SET); + int read_len =3D fread(buf, sizeof(uint8_t), data_len, f); + if (read_len =3D=3D data_len) { + SPI_DEBUG(qemu_log("Read %d bytes from seeprom\n", + read_len)); + } else { + if (ferror(f)) { + SPI_DEBUG(qemu_log("Error reading seeprom\n")); + } + } + } + fclose(f); + } + /* Check if last is 0 and increase address by data length */ + if (last =3D=3D 0) { + seeprom->addr =3D (seeprom->addr & 0x7FFFF) + data_len; + } else { + seeprom->rd_state =3D ST_IDLE; + } + break; + + case RDSR: + SPI_DEBUG(qemu_log("READ Status Register RDSR(0x%x)\n", + seeprom->opcode)); + data_len =3D payload->len - data_offset; + /* Make sure data is at least 1 Byte */ + if (data_len <=3D 0) { + qemu_log_mask(LOG_GUEST_ERROR, "Insufficient Data Bytes(%dB), " + "should be at least 1 Byte\n", data_len); + break; + } + buf =3D xfer_buffer_write_ptr(rsp_payload, data_offset, data_len); + buf[0] =3D seeprom->status0; + if (data_len =3D=3D 2) { + buf[1] =3D seeprom->status1; + } + break; + + case WRBP: + SPI_DEBUG(qemu_log("Write Ready/Busy Poll WRBP(0x%x)\n", + seeprom->opcode)); + data_len =3D payload->len - data_offset; + /* Make sure data is at least 1 Byte */ + if (data_len <=3D 0) { + qemu_log_mask(LOG_GUEST_ERROR, "Insufficient Data Bytes(%dB), " + "should be at least 1 Byte\n", data_len); + break; + } + buf =3D xfer_buffer_write_ptr(rsp_payload, data_offset, 0x1); + bool status0_busy =3D extract8(seeprom->status0, STATUS0_BUSY, 1); + bool status1_busy =3D extract8(seeprom->status1, STATUS1_BUSY, 1); + if ((status0_busy =3D=3D 1) || (status1_busy =3D=3D 1)) { + buf[0] =3D 0xFF; + } else { + buf[0] =3D 0x00; + } + break; + + case WREN: + SPI_DEBUG(qemu_log("Set Write Enable Latch (WEL) WREN(0x%x)\n", + seeprom->opcode)); + seeprom->status0 =3D deposit32(seeprom->status0, STATUS0_WEL, 1, 1= ); + break; + + case WRDI: + SPI_DEBUG(qemu_log("Set Write Enable Latch (WEL) WRDI(0x%x)\n", + seeprom->opcode)); + seeprom->status0 =3D deposit32(seeprom->status0, STATUS0_WEL, 1, 0= ); + break; + + case WRSR: + SPI_DEBUG(qemu_log("Write STATUS Register WRSR(0x%x)\n", + seeprom->opcode)); + if (extract8(seeprom->status0, STATUS0_WEL, 1) =3D=3D 1) { + data_len =3D payload->len - data_offset; + /* Make sure data is at least 1 Byte */ + if (data_len <=3D 0) { + qemu_log_mask(LOG_GUEST_ERROR, "Insufficient Data Bytes(%d= B)," + " should be at least 1 Byte\n", data_len); + break; + } + /* Mask and update status0/1 bytes */ + xfer_buffer_read_ptr(payload, &read_buf, 1, 2); + seeprom->status0 =3D read_buf[0] & 0x8C; + /* 2nd Status Byte is optional */ + if (data_len =3D=3D 2) { + seeprom->status1 =3D read_buf[1] & 0x80; + } + } else { + qemu_log_mask(LOG_GUEST_ERROR, "Set Write Enable Latch (WEL) " + "before doing WRSR\n"); + } + break; + + case SPID: + SPI_DEBUG(qemu_log("READ IDENTIFICATION REGISTER, SPID(0x%x)\n", + seeprom->opcode)); + data_len =3D payload->len - data_offset; + buf =3D xfer_buffer_write_ptr(rsp_payload, data_offset, data_len); + for (idx =3D 0; idx < data_len; idx++) { + buf[idx] =3D seeprom->idr[idx]; + } + break; + + case SRST: + SPI_DEBUG(qemu_log("Software Device Reset, SRST(0x%x)\n", + seeprom->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 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 =3D HIGH) + * - A high-to-low level transition on CS is required to enter the + * active state + `* - WEL bit in the STATUS register =3D 0 + * - ECS bit in the STATUS register =3D 0 + * - PREL bit in the STATUS register =3D 0 + * - Ready/Busy (RDY/BUSY) bit in the STATUS register =3D 0, indic= ating + * the device is ready to accept a new instruction. + */ + seeprom->status0 =3D deposit32(seeprom->status0, STATUS0_WEL, 1, 0= ); + seeprom->status1 =3D deposit32(seeprom->status1, STATUS1_ECS, 1, 0= ); + seeprom->status1 =3D deposit32(seeprom->status1, STATUS1_PREL, 1, = 0); + seeprom->status0 =3D deposit32(seeprom->status0, STATUS0_BUSY, 1, = 0); + seeprom->status1 =3D deposit32(seeprom->status1, STATUS1_BUSY, 1, = 0); + break; + + case WRITE: + SPI_DEBUG(qemu_log("WRITE(0x%x), addr(0x%x)\n", + seeprom->opcode, seeprom->addr)); + if (extract8(seeprom->status0, STATUS0_WEL, 1) !=3D 1) { + qemu_log_mask(LOG_GUEST_ERROR, "Device is not Write Enabled, " + "ignoring WRITE instruction\n"); + break; + } + /* Make sure data is at least 1 Byte */ + if (data_len <=3D 0) { + /* + * first last comment + * 0 0 data length cannot be 0 + * 0 1 data length cannot be 0 + * 1 0 data length can be 0, don't log error + * 1 1 data length cannot be 0 + */ + if (!(first =3D=3D 1 && (last =3D=3D 0))) { + qemu_log_mask(LOG_GUEST_ERROR, "Insufficient Data Bytes(%d= B)," + " should be at least 1 Byte\n", data_l= en); + break; + } + } + /* Write into SEEPROM Array */ + SPI_DEBUG(qemu_log("(%d)%s Write sequence\n", data_len, + (data_len =3D=3D 1) ? "Byte" : "Bytes Page")); + xfer_buffer_read_ptr(payload, &read_buf, data_offset, data_len); + if (seeprom->file) { + f =3D fopen(seeprom->file, "rb+"); + if (f) { + fseek(f, seeprom->addr, SEEK_SET); + int write_len =3D fwrite(read_buf, sizeof(uint8_t), data_l= en, f); + if (write_len =3D=3D data_len) { + SPI_DEBUG(qemu_log("Write %d bytes to seeprom\n", + write_len)); + } else { + SPI_DEBUG(qemu_log("Failed to write seeprom\n")); + } + } + fclose(f); + } + /* Increase offset in the page */ + seeprom->addr +=3D data_len; + /* Check if last is 1 and end the sequence */ + if (last =3D=3D 1) { + seeprom->status0 =3D deposit32(seeprom->status0, STATUS0_WEL, = 1, 0); + } + break; + + case RMPR: + SPI_DEBUG(qemu_log("RMPR(0x%x) for MPR[%d]\n", seeprom->opcode, + extract8(seeprom->addr, 16, 2))); + data_len =3D payload->len - data_offset; + /* Make sure data is at least 1 Byte */ + if (data_len <=3D 0) { + qemu_log_mask(LOG_GUEST_ERROR, "Insufficient Data Bytes(%dB)," + " should be at least 1 Byte\n", data_len); + break; + } + buf =3D xfer_buffer_write_ptr(rsp_payload, data_offset, 0x1); + buf[0] =3D seeprom->mpr[extract8(seeprom->addr, 16, 2)]; + break; + + case PRWE: + SPI_DEBUG(qemu_log("Set Memory Partition Write Enable Latch " + "PRWE(0x%x)\n", seeprom->opcode)); + seeprom->status1 =3D deposit32(seeprom->status1, STATUS1_PREL, 1, = 1); + break; + + case PRWD: + SPI_DEBUG(qemu_log("Reset Memory Partition Write Enable Latch " + "PRWD(0x%x)\n", seeprom->opcode)); + seeprom->status1 =3D deposit32(seeprom->status1, STATUS1_PREL, 1, = 0); + break; + + case WMPR: + SPI_DEBUG(qemu_log("Write Memory Partition Register[%d] WMPR(0x%x)= \n", + extract8(seeprom->addr, 16, 2), seeprom->opcode)); + /* + * Once the WEL and PREL bits in the STATUS register have been set= to + * 1, the Memory Partition registers can be programmed provided th= at + * the FMPC bit in the STATUS register has not already been set to= a + * logic 1. + */ + if ((extract8(seeprom->status0, STATUS0_WEL, 1) !=3D 1) || + (extract8(seeprom->status1, STATUS1_PREL, 1) !=3D 1) || + (extract8(seeprom->status1, STATUS1_FMPC, 1) =3D=3D 1)) { + qemu_log_mask(LOG_GUEST_ERROR, "ignoring Write to MPR\n"); + break; + } + data_len =3D payload->len - data_offset; + /* Make sure data is at least 1 Byte */ + if (data_len <=3D 0) { + qemu_log_mask(LOG_GUEST_ERROR, "Insufficient Data Bytes(%dB)," + " should be at least 1 Byte\n", data_len); + break; + } + xfer_buffer_read_ptr(payload, &read_buf, data_offset, 0x1); + if (extract8(seeprom->status1, STATUS1_PABP, 1) =3D=3D 1) { + /* Partition Address Boundaries Protected */ + seeprom->mpr[extract8(seeprom->addr, 16, 2)] =3D + ((read_buf[0] >> 6) & 0x3); + } else { + seeprom->mpr[extract8(seeprom->addr, 16, 2)] =3D read_buf[0]; + } + seeprom->status0 =3D deposit32(seeprom->status0, STATUS0_WEL, 1, 0= ); + seeprom->status1 =3D deposit32(seeprom->status1, STATUS1_PREL, 1, = 0); + break; + + case PPAB: + SPI_DEBUG(qemu_log("Protect Partition Address Boundaries PPAB(0x%x= )\n", + seeprom->opcode)); + if ((extract8(seeprom->status0, STATUS0_WEL, 1) !=3D 1) || + (extract8(seeprom->status1, STATUS1_PREL, 1) !=3D 1) || + (extract8(seeprom->status1, STATUS1_FMPC, 1) =3D=3D 1)) { + qemu_log_mask(LOG_GUEST_ERROR, "Ignoring PPAB command\n"); + break; + } + data_len =3D payload->len - data_offset; + /* Make sure data is at least 1 Byte */ + if (data_len <=3D 0) { + qemu_log_mask(LOG_GUEST_ERROR, "Insufficient Data Bytes(%dB)," + " should be at least 1 Byte\n", data_len); + break; + } + xfer_buffer_read_ptr(payload, &read_buf, data_offset, 0x1); + if (read_buf[0] =3D=3D 0xFF) { + seeprom->status1 =3D deposit32(seeprom->status1, + STATUS1_PABP, 1, 1); + } else if (read_buf[0] =3D=3D 0x0) { + seeprom->status1 =3D deposit32(seeprom->status1, + STATUS1_PABP, 1, 0); + } else { + qemu_log_mask(LOG_GUEST_ERROR, "Incorrect Data Byte(0x%x), " + "should be 0x0 or 0xFF\n", read_buf[0]); + } + seeprom->status0 =3D deposit32(seeprom->status0, STATUS0_WEL, 1, 0= ); + seeprom->status1 =3D deposit32(seeprom->status1, STATUS1_PREL, 1, = 0); + break; + + case FRZR: + SPI_DEBUG(qemu_log("Freeze Memory Protection Configuration " + "FRZR(0x%x)\n", seeprom->opcode)); + if ((extract8(seeprom->status0, STATUS0_WEL, 1) !=3D 1) || + (extract8(seeprom->status1, STATUS1_PREL, 1) !=3D 1) || + (extract8(seeprom->status1, STATUS1_FMPC, 1) =3D=3D 1)) { + qemu_log_mask(LOG_GUEST_ERROR, "ignoring FRZR command\n"); + break; + } + data_len =3D payload->len - data_offset; + /* Make sure data is at least 1 Byte */ + if (data_len <=3D 0) { + qemu_log_mask(LOG_GUEST_ERROR, "Insufficient Data Bytes(%dB)," + " should be at least 1 Byte\n", data_len); + break; + } + xfer_buffer_read_ptr(payload, &read_buf, data_offset, 0x1); + if (read_buf[0] =3D=3D 0xD2) { + seeprom->status1 =3D deposit32(seeprom->status1, + STATUS1_FMPC, 1, 1); + } else { + qemu_log_mask(LOG_GUEST_ERROR, "Invalid Confirmation Data " + "byte(0x%x), expecting 0xD2", read_buf[0]); + } + seeprom->status0 =3D deposit32(seeprom->status0, STATUS0_WEL, 1, 0= ); + seeprom->status1 =3D deposit32(seeprom->status1, STATUS1_PREL, 1, = 0); + break; + + case RDEX_CHLK: + SPI_DEBUG(qemu_log("OPCODE =3D (0x%x)\n", seeprom->opcode)); + data_len =3D payload->len - data_offset; + /* Make sure data is at least 1 Byte */ + if (data_len <=3D 0) { + qemu_log_mask(LOG_GUEST_ERROR, "Insufficient Data Bytes(%dB)," + " should be at least 1 Byte\n", data_len); + break; + } + buf =3D xfer_buffer_write_ptr(rsp_payload, data_offset, data_len); + if (extract8(seeprom->addr, 10, 1) =3D=3D 0) { + /* RDEX */ + seeprom->rd_state =3D ST_SEC_READ; + for (idx =3D 0; idx < data_len; idx++) { + if (extract32(seeprom->addr, 0, 9) <=3D 0xFF) { + buf[idx] =3D seeprom->dsn[extract8(seeprom->addr, 0, 8= )]; + } else { + buf[idx] =3D seeprom->uplid[extract8(seeprom->addr, 0,= 8)]; + } + seeprom->addr =3D deposit32(seeprom->addr, 0, 9, + ((extract32(seeprom->addr, 0, 9)) + 1)); + } + if (last =3D=3D 1) { + seeprom->rd_state =3D ST_IDLE; + } else { + /* CHLK */ + if (seeprom->locked =3D=3D true) { + buf[0] =3D 0x01; + } else { + buf[0] =3D 0x00; + } + } + } + break; + + case WREX_LOCK: + SPI_DEBUG(qemu_log("OPCODE =3D (0x%x)\n", seeprom->opcode)); + if (seeprom->locked =3D=3D true) { + qemu_log_mask(LOG_GUEST_ERROR, "Device is already Locked, " + "command is ignored\n"); + break; + } + if (extract8(seeprom->status0, STATUS0_WEL, 1) !=3D 1) { + qemu_log_mask(LOG_GUEST_ERROR, "Device is not Write Enabled, " + "command is ignored\n"); + break; + } + data_len =3D payload->len - data_offset; + /* Make sure data is at least 1 Byte */ + if (data_len <=3D 0) { + qemu_log_mask(LOG_GUEST_ERROR, "Insufficient Data Bytes(%dB)," + " should be at least 1 Byte\n", data_len); + break; + } + xfer_buffer_read_ptr(payload, &read_buf, data_offset, data_len); + if (extract8(seeprom->addr, 10, 1) =3D=3D 0) { + /* WREX */ + for (idx =3D 0; idx < data_len; idx++) { + seeprom->uplid[extract8(seeprom->addr, 0, 8)] =3D read_buf= [idx]; + /* Increase address with the page, and let it rool over */ + seeprom->addr =3D deposit32(seeprom->addr, 0, 8, + ((extract32(seeprom->addr, 0, 8)) + 1)); + } + } else { + /* + * LOCK (82h) instruction is clocked in on the SI line, follo= wed + * by a fake address where bits A[23:0] are don't care bits w= ith + * the exception that bit A10 must be set to 1. Finally, a + * confirmation data byte of xxxx_xx1xb is sent + */ + if ((buf[0] & 0x02) =3D=3D 0x2) { + seeprom->locked =3D true; + } + } + break; + + default: + qemu_log_mask(LOG_GUEST_ERROR, "Invalid instruction(0x%x)\n", + seeprom->opcode); + } /* end of switch */ + return rsp_payload; +} /* end of seeprom_spi_request */ + +void seeprom_connect_controller(PnvSpiResponder *resp, const char *port) +{ + PnvSpiSeeprom *seeprom =3D PNV_SPI_SEEPROM(resp); + seeprom->controller_connected =3D true; +} + +void seeprom_disconnect_controller(PnvSpiResponder *resp) +{ + PnvSpiSeeprom *seeprom =3D PNV_SPI_SEEPROM(resp); + /* This method is invoked when Controller wants to deslect responder */ + seeprom->controller_connected =3D false; + seeprom->rd_state =3D ST_IDLE; /* Reset Read state */ + if (seeprom->opcode =3D=3D WRITE) { /* Reset Write enable */ + seeprom->status0 =3D deposit32(seeprom->status0, STATUS0_WEL, 1, 0= ); + } +} + +/* + * 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(PnvSpiSeeprom *seeprom, xfer_buffer *req_payload, + xfer_buffer *rsp_payload, int bits, uint32_t *data_offs= et) +{ + bool addr_wr_protected =3D false; + uint8_t *read_buf =3D NULL; + *data_offset =3D 0; + bool failed =3D false; + + xfer_buffer_read_ptr(req_payload, &read_buf, 1, 3); + switch (seeprom->opcode) { + case READ: + case WRITE: + SPI_DEBUG(qemu_log("Compute address and payload buffer data offset= " + "for %s\n", (seeprom->opcode =3D=3D READ) ? "READ" : "WR= ITE")); + /* command size is 4 bytes for READ/WRITE, data_offset is 4 */ + *data_offset =3D 4; + + /* Make sure buffer length is at least 4 Bytes */ + if (req_payload->len >=3D 4) { + /* + * 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 512= KB + */ + seeprom->addr =3D deposit32(seeprom->addr, 0, 8, read_buf[2]); + seeprom->addr =3D deposit32(seeprom->addr, 8, 8, read_buf[1]); + seeprom->addr =3D deposit32(seeprom->addr, 16, 8, + (read_buf[0] & 0x7)); + } else { + qemu_log_mask(LOG_GUEST_ERROR, "Payload_len(0x%x) should be at= " + "least 4Bytes to fetch Address\n", req_payload->= len); + failed =3D true; + } + if (seeprom->opcode =3D=3D WRITE) { + addr_wr_protected =3D validate_addr(seeprom); + if (addr_wr_protected) { + qemu_log_mask(LOG_GUEST_ERROR, "SEEPROM Address(0x%x) is W= rite " + "protected\n", seeprom->addr); + failed =3D true; + } + } + break; + + case RDSR: + case WRBP: + case WRSR: + case SPID: + /* + * command size is 1 bytes for RDSR, WRBP, WRSR, SPID. So data_off= set + * is 1 + */ + *data_offset =3D 1; + break; + + case RMPR: + case WMPR: + SPI_DEBUG(qemu_log("Compute MPR address for %s MPR\n", + (seeprom->opcode =3D=3D RMPR) ? "READ" : "WRITE")); + /* command size is 4 bytes for WMPR/RMPR, data_offset is 4 */ + *data_offset =3D 4; + + /* Make sure buffer length is at least 4 Bytes */ + if (req_payload->len >=3D 4) { + /* + * The address for each Memory Partition register is embedded = into + * the first address byte sent to the device,in bit positions = A18 + * through A16. + */ + seeprom->addr =3D deposit32(seeprom->addr, 0, 15, 0); + seeprom->addr =3D deposit32(seeprom->addr, 16, 8, + (read_buf[0] & 0x7)); + } else { + qemu_log_mask(LOG_GUEST_ERROR, "Payload_len(0x%x) should be at= " + "least 4Bytes to fetch Address\n", req_payload->= len); + failed =3D true; + } + break; + + case PPAB: + case FRZR: + SPI_DEBUG(qemu_log("Validate if addr[15:0] is %s\n", + (seeprom->opcode =3D=3D PPAB) ? "0xCCFF for PPAB" : + "0xAA40 for FRZR")); + /* command size is 4 bytes for PPAB/FRZR, data_offset is 4 */ + *data_offset =3D 4; + /* Make sure buffer length is at least 4 Bytes */ + if (req_payload->len >=3D 4) { + /* Address bits A23-A16 are ignored. */ + seeprom->addr =3D deposit32(seeprom->addr, 0, 8, read_buf[2]); + seeprom->addr =3D deposit32(seeprom->addr, 8, 8, read_buf[1]); + seeprom->addr =3D deposit32(seeprom->addr, 16, 8, 0); + } else { + qemu_log_mask(LOG_GUEST_ERROR, "Payload_len(0x%x) should be at= " + "least 4Bytes to fetch Address\n", req_payload->= len); + failed =3D true; + break; + } + /* Address bits A15-A0 must be set to CC55h. */ + if ((seeprom->opcode =3D=3D PPAB) && + (extract32(seeprom->addr, 0, 15) !=3D 0xCC55)) { + qemu_log_mask(LOG_GUEST_ERROR, "Invalid addr[15:0] =3D 0x%x se= nt for " + "PPAB\n", extract32(seeprom->addr, 0, 15)); + failed =3D true; + } + /* Address bits A15-A0 must be set to AA40h. */ + if ((seeprom->opcode =3D=3D FRZR) && + (extract32(seeprom->addr, 0, 15) !=3D 0xAA40)) { + qemu_log_mask(LOG_GUEST_ERROR, "Invalid addr[15:0] =3D 0x%x se= nt for " + "FRZR\n", extract32(seeprom->addr, 0, 15)); + failed =3D true; + } + break; + + case RDEX_CHLK: + case WREX_LOCK: + SPI_DEBUG(qemu_log("Compute Address for Security reg command\n")); + /* command size is 4 bytes for PPAB/FRZR, data_offset is 4 */ + *data_offset =3D 4; + + /* Make sure buffer length is at least 4 Bytes */ + if (req_payload->len >=3D 4) { + /* + * RDEX : A[23:9] are don't care bits, except A10 which must b= e a + * logic 0. + * WREX : A[23:9] are don't care bits, except A10 which must b= e a + * logic 0 and A8 which must be a logic 1 to address the + * second Security register byte that is user programma= ble. + * CHLK : A[23:0] are don't care bits, except A10 which must b= e a + * logic 1. + * LOCK : A[23:0] are don't care bits, except A10 which must b= e a + * logic 1. + */ + seeprom->addr =3D deposit32(seeprom->addr, 0, 8, read_buf[2]); + seeprom->addr =3D deposit32(seeprom->addr, 8, 8, + (read_buf[1] & 0x05)); + seeprom->addr =3D deposit32(seeprom->addr, 16, 8, 0); + SPI_DEBUG(qemu_log("Received Command %s\n", + (seeprom->opcode =3D=3D RDEX_CHLK) + ? (extract32(seeprom->addr, 10, 1) ? + "CHLK : Check Lock Status of Security Register" : + "RDEX : Read from the Security Register") + : (extract32(seeprom->addr, 10, 1) ? + "LOCK : Lock the Security Register (permanent)" : + "WREX : Write to the Security Register"))); + } else { + qemu_log_mask(LOG_GUEST_ERROR, "Payload_len(0x%x) should be at= " + "least 4Bytes to fetch Address\n", req_payload->= len); + failed =3D true; + } + + if ((seeprom->opcode =3D=3D WREX_LOCK) && + (extract32(seeprom->addr, 10, 1) =3D=3D 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 =3D 11. + */ + if (extract8(seeprom->status1, STATUS1_WPM, 1) =3D=3D 0) { + addr_wr_protected =3D validate_addr(seeprom); + } else { + if (extract32(seeprom->addr, 0, 9) <=3D 0xFF) { + addr_wr_protected =3D true; + } + } + if (addr_wr_protected) { + qemu_log_mask(LOG_GUEST_ERROR, "SEEPROM Address(0x%x) is " + "Write protected\n", seeprom->addr); + failed =3D 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(PnvSpiSeeprom *seeprom) +{ + bool addr_wr_protected =3D false; + uint8_t mpr_idx =3D 0; + + if (extract8(seeprom->status1, STATUS1_WPM, 1) =3D=3D 1) { + /* + * enhanced write protection + * Memory partition register Bit5 through bit0 contain the Parti= tion + * 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 t= he + * previous MPR. If a higher order MPR sets a Partition Endpoint A= ddress + * 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 =3D 0; mpr_idx < 8; mpr_idx++) { + if ((extract32(seeprom->addr, 13, 6)) <=3D + (extract8(seeprom->mpr[mpr_idx], MPR_PEA, 1))) { + switch (extract8(seeprom->mpr[mpr_idx], MPR_PB, 1)) { + case 0: + /* + * 0b00 =3D Partition is open and writing is permitted + * (factory default is unprotected). + */ + addr_wr_protected =3D false; + break; + case 1: + /* + * 0b01 =3D Partition is always write-protected but ca= n be + * reversed at a later time (software write-protected). + */ + addr_wr_protected =3D true; + break; + case 2: + /* + * 0b10 =3D Partition is write-protected only when WP = pin is + * asserted (hardware write-protected). + */ + addr_wr_protected =3D false; + break; + case 3: + /* + * 0b11 =3D Partition is software write-protected and = Memory + * Partition register is permanently locked. + */ + addr_wr_protected =3D true; + break; + } /* end of switch */ + break; /* break from for loop. */ + } + } /* end of for loop */ + } else { + /* Legacy write protection mode */ + switch (extract8(seeprom->status0, STATUS0_BP, 2)) { + case 0: + /* + * 0b00 =3D No array write protection + * EEPROM None + * Security Register 00000h - 000FFh + */ + if ((seeprom->opcode =3D=3D WREX_LOCK) && + (extract32(seeprom->addr, 0, 9) <=3D 0xFF)) { + addr_wr_protected =3D true; + } + break; + case 1: + /* + * 0b01 =3D Upper quarter memory array protection + * EEPROM 60000h - 7FFFFh + * Security Register 00000h - 000FFh + */ + if ((seeprom->opcode =3D=3D WREX_LOCK) && + (extract32(seeprom->addr, 0, 9) <=3D 0xFF)) { + addr_wr_protected =3D true; + } else if ((seeprom->opcode =3D=3D WRITE) && + (extract32(seeprom->addr, 0, 19) <=3D 0x60000)) { + addr_wr_protected =3D true; + } + break; + case 2: + /* + * 0b10 =3D Upper half memory array protection + * EEPROM 40000h - 7FFFFh + * Security Register 00000h - 000FFh + */ + if ((seeprom->opcode =3D=3D WREX_LOCK) && + (extract32(seeprom->addr, 0, 9) <=3D 0xFF)) { + addr_wr_protected =3D true; + } else if ((seeprom->opcode =3D=3D WRITE) && + (extract32(seeprom->addr, 0, 19) <=3D 0x40000)) { + addr_wr_protected =3D true; + } + break; + case 3: + /* + * 0b11 =3D Entire memory array protection + * EEPROM 00000h - 7FFFFh + * Security Register 00000h - 001FFh + */ + addr_wr_protected =3D true; + break; + } /* end of switch */ + } + return addr_wr_protected; +} /* end of validate_addr */ + +static void pnv_spi_seeprom_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc =3D DEVICE_CLASS(klass); + PnvSpiResponderClass *resp_class =3D PNV_SPI_RESPONDER_CLASS(klass); + + resp_class->connect_controller =3D seeprom_connect_controller; + resp_class->disconnect_controller =3D seeprom_disconnect_controller; + resp_class->request =3D seeprom_spi_request; + + dc->desc =3D "PowerNV SPI SEEPROM"; + dc->bus_type =3D TYPE_SPI_BUS; +} + +static const TypeInfo pnv_spi_seeprom_info =3D { + .name =3D TYPE_PNV_SPI_SEEPROM, + .parent =3D TYPE_PNV_SPI_RESPONDER, + .instance_size =3D sizeof(PnvSpiSeeprom), + .class_init =3D pnv_spi_seeprom_class_init, +}; + +static void pnv_spi_seeprom_register_types(void) +{ + type_register_static(&pnv_spi_seeprom_info); +} + +type_init(pnv_spi_seeprom_register_types); diff --git a/hw/ppc/meson.build b/hw/ppc/meson.build index de25cac763..2c1ef0c937 100644 --- a/hw/ppc/meson.build +++ b/hw/ppc/meson.build @@ -55,6 +55,7 @@ ppc_ss.add(when: 'CONFIG_POWERNV', if_true: files( 'pnv_pnor.c', 'pnv_spi_responder.c', 'pnv_spi_controller.c', + 'pnv_spi_seeprom.c', )) # PowerPC 4xx boards ppc_ss.add(when: 'CONFIG_PPC405', if_true: files( --=20 2.31.1