Re: [RFC 1/1] hw: tpmtisspi: add SPI support to QEMU TPM implementation

2023-12-14 Thread Iris Chen
Hi Guenter,

Not from me at this moment :)

Thanks,
Iris

On Wed, Dec 13, 2023 at 9:39 AM Guenter Roeck  wrote:

> Hi,
>
> On Tue, Aug 02, 2022 at 07:32:41PM -0700, Iris Chen wrote:
> > From: Iris Chen 
> >
> > Signed-off-by: Iris Chen 
> > ---
>
> Are there any plans to submit a new version of this patch ?
>
> Thanks,
> Guenter
>
> >  configs/devices/arm-softmmu/default.mak |   1 +
> >  hw/arm/Kconfig  |   5 +
> >  hw/tpm/Kconfig  |   5 +
> >  hw/tpm/meson.build  |   1 +
> >  hw/tpm/tpm_tis_spi.c| 311 
> >  include/sysemu/tpm.h|   3 +
> >  6 files changed, 326 insertions(+)
> >  create mode 100644 hw/tpm/tpm_tis_spi.c
> >
> > diff --git a/configs/devices/arm-softmmu/default.mak
> b/configs/devices/arm-softmmu/default.mak
> > index 6985a25377..80d2841568 100644
> > --- a/configs/devices/arm-softmmu/default.mak
> > +++ b/configs/devices/arm-softmmu/default.mak
> > @@ -42,3 +42,4 @@ CONFIG_FSL_IMX6UL=y
> >  CONFIG_SEMIHOSTING=y
> >  CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
> >  CONFIG_ALLWINNER_H3=y
> > +CONFIG_FBOBMC_AST=y
> > diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
> > index 15fa79afd3..193decaec1 100644
> > --- a/hw/arm/Kconfig
> > +++ b/hw/arm/Kconfig
> > @@ -458,6 +458,11 @@ config ASPEED_SOC
> >  select PMBUS
> >  select MAX31785
> >
> > +config FBOBMC_AST
> > +bool
> > +select ASPEED_SOC
> > +select TPM_TIS_SPI
> > +
> >  config MPS2
> >  bool
> >  imply I2C_DEVICES
> > diff --git a/hw/tpm/Kconfig b/hw/tpm/Kconfig
> > index 29e82f3c92..370a43f045 100644
> > --- a/hw/tpm/Kconfig
> > +++ b/hw/tpm/Kconfig
> > @@ -8,6 +8,11 @@ config TPM_TIS_SYSBUS
> >  depends on TPM
> >  select TPM_TIS
> >
> > +config TPM_TIS_SPI
> > +bool
> > +depends on TPM
> > +select TPM_TIS
> > +
> >  config TPM_TIS
> >  bool
> >  depends on TPM
> > diff --git a/hw/tpm/meson.build b/hw/tpm/meson.build
> > index 1c68d81d6a..1a057f4e36 100644
> > --- a/hw/tpm/meson.build
> > +++ b/hw/tpm/meson.build
> > @@ -2,6 +2,7 @@ softmmu_ss.add(when: 'CONFIG_TPM_TIS', if_true:
> files('tpm_tis_common.c'))
> >  softmmu_ss.add(when: 'CONFIG_TPM_TIS_ISA', if_true:
> files('tpm_tis_isa.c'))
> >  softmmu_ss.add(when: 'CONFIG_TPM_TIS_SYSBUS', if_true:
> files('tpm_tis_sysbus.c'))
> >  softmmu_ss.add(when: 'CONFIG_TPM_CRB', if_true: files('tpm_crb.c'))
> > +softmmu_ss.add(when: 'CONFIG_TPM_TIS_SPI', if_true:
> files('tpm_tis_spi.c'))
> >
> >  specific_ss.add(when: ['CONFIG_SOFTMMU', 'CONFIG_TPM_TIS'], if_true:
> files('tpm_ppi.c'))
> >  specific_ss.add(when: ['CONFIG_SOFTMMU', 'CONFIG_TPM_CRB'], if_true:
> files('tpm_ppi.c'))
> > diff --git a/hw/tpm/tpm_tis_spi.c b/hw/tpm/tpm_tis_spi.c
> > new file mode 100644
> > index 00..c98ddcfddb
> > --- /dev/null
> > +++ b/hw/tpm/tpm_tis_spi.c
> > @@ -0,0 +1,311 @@
> > +#include "qemu/osdep.h"
> > +#include "hw/qdev-properties.h"
> > +#include "migration/vmstate.h"
> > +#include "hw/acpi/tpm.h"
> > +#include "tpm_prop.h"
> > +#include "tpm_tis.h"
> > +#include "qom/object.h"
> > +#include "hw/ssi/ssi.h"
> > +#include "hw/ssi/spi_gpio.h"
> > +
> > +#define TPM_TIS_SPI_ADDR_BYTES 3
> > +#define SPI_WRITE 0
> > +
> > +typedef enum {
> > +TIS_SPI_PKT_STATE_DEACTIVATED = 0,
> > +TIS_SPI_PKT_STATE_START,
> > +TIS_SPI_PKT_STATE_ADDRESS,
> > +TIS_SPI_PKT_STATE_DATA_WR,
> > +TIS_SPI_PKT_STATE_DATA_RD,
> > +TIS_SPI_PKT_STATE_DONE,
> > +} TpmTisSpiPktState;
> > +
> > +union TpmTisRWSizeByte {
> > +uint8_t byte;
> > +struct {
> > +uint8_t data_expected_size:6;
> > +uint8_t resv:1;
> > +uint8_t rwflag:1;
> > +};
> > +};
> > +
> > +union TpmTisSpiHwAddr {
> > +hwaddr addr;
> > +uint8_t bytes[sizeof(hwaddr)];
> > +};
> > +
> > +union TpmTisSpiData {
> > +uint32_t data;
> > +uint8_t bytes[64];
> > +};
> > +
> > +struct TpmTisSpiState {
> > +/*< private >*/
> > +SSIPeripheral parent_obj;
> > +
> > +/*< public >*/
> > +TPMState tpm_state; /* not a QOM object */
> >

[RFC 0/1] SPI support in QEMU TPM

2022-08-03 Thread Iris Chen
From: Iris Chen 

Hey everyone,

Thanks for all your comments on the SPI GPIO model. I am working through them.

As for adding support for SPI-based TPMs in QEMU, this RFC patch adds SPI
support in the QEMU TPM implementation via tpm_tis_spi.c.

The QEMU tree already has support for two connection methods to the TPM:
mmio (isa for x86, sysbus for arm) and “spapr”. This patch adds a new SPI
bus implementation for the TPM. Specifically, this SPI bus implementation
connects to the Yosemite-v3.5 model using the SPI-GPIO model sent earlier
last week. I have already tested these implementations locally together
and can verify that the Linux kernel can successfully probe the TPM device
on Yosemite-v3.5 and we can run the TPM command line tools to interact with it.

Please let me know what you think about this!

Thanks,
Iris

Iris Chen (1):
  hw: tpmtisspi: add SPI support to QEMU TPM implementation

 configs/devices/arm-softmmu/default.mak |   1 +
 hw/arm/Kconfig  |   5 +
 hw/tpm/Kconfig  |   5 +
 hw/tpm/meson.build  |   1 +
 hw/tpm/tpm_tis_spi.c| 311 
 include/sysemu/tpm.h|   3 +
 6 files changed, 326 insertions(+)
 create mode 100644 hw/tpm/tpm_tis_spi.c

--
2.30.2



[RFC 1/1] hw: tpmtisspi: add SPI support to QEMU TPM implementation

2022-08-03 Thread Iris Chen
From: Iris Chen 

Signed-off-by: Iris Chen 
---
 configs/devices/arm-softmmu/default.mak |   1 +
 hw/arm/Kconfig  |   5 +
 hw/tpm/Kconfig  |   5 +
 hw/tpm/meson.build  |   1 +
 hw/tpm/tpm_tis_spi.c| 311 
 include/sysemu/tpm.h|   3 +
 6 files changed, 326 insertions(+)
 create mode 100644 hw/tpm/tpm_tis_spi.c

diff --git a/configs/devices/arm-softmmu/default.mak 
b/configs/devices/arm-softmmu/default.mak
index 6985a25377..80d2841568 100644
--- a/configs/devices/arm-softmmu/default.mak
+++ b/configs/devices/arm-softmmu/default.mak
@@ -42,3 +42,4 @@ CONFIG_FSL_IMX6UL=y
 CONFIG_SEMIHOSTING=y
 CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
 CONFIG_ALLWINNER_H3=y
+CONFIG_FBOBMC_AST=y
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 15fa79afd3..193decaec1 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -458,6 +458,11 @@ config ASPEED_SOC
 select PMBUS
 select MAX31785
 
+config FBOBMC_AST
+bool
+select ASPEED_SOC
+select TPM_TIS_SPI
+
 config MPS2
 bool
 imply I2C_DEVICES
diff --git a/hw/tpm/Kconfig b/hw/tpm/Kconfig
index 29e82f3c92..370a43f045 100644
--- a/hw/tpm/Kconfig
+++ b/hw/tpm/Kconfig
@@ -8,6 +8,11 @@ config TPM_TIS_SYSBUS
 depends on TPM
 select TPM_TIS
 
+config TPM_TIS_SPI
+bool
+depends on TPM
+select TPM_TIS
+
 config TPM_TIS
 bool
 depends on TPM
diff --git a/hw/tpm/meson.build b/hw/tpm/meson.build
index 1c68d81d6a..1a057f4e36 100644
--- a/hw/tpm/meson.build
+++ b/hw/tpm/meson.build
@@ -2,6 +2,7 @@ softmmu_ss.add(when: 'CONFIG_TPM_TIS', if_true: 
files('tpm_tis_common.c'))
 softmmu_ss.add(when: 'CONFIG_TPM_TIS_ISA', if_true: files('tpm_tis_isa.c'))
 softmmu_ss.add(when: 'CONFIG_TPM_TIS_SYSBUS', if_true: 
files('tpm_tis_sysbus.c'))
 softmmu_ss.add(when: 'CONFIG_TPM_CRB', if_true: files('tpm_crb.c'))
+softmmu_ss.add(when: 'CONFIG_TPM_TIS_SPI', if_true: files('tpm_tis_spi.c'))
 
 specific_ss.add(when: ['CONFIG_SOFTMMU', 'CONFIG_TPM_TIS'], if_true: 
files('tpm_ppi.c'))
 specific_ss.add(when: ['CONFIG_SOFTMMU', 'CONFIG_TPM_CRB'], if_true: 
files('tpm_ppi.c'))
diff --git a/hw/tpm/tpm_tis_spi.c b/hw/tpm/tpm_tis_spi.c
new file mode 100644
index 00..c98ddcfddb
--- /dev/null
+++ b/hw/tpm/tpm_tis_spi.c
@@ -0,0 +1,311 @@
+#include "qemu/osdep.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "hw/acpi/tpm.h"
+#include "tpm_prop.h"
+#include "tpm_tis.h"
+#include "qom/object.h"
+#include "hw/ssi/ssi.h"
+#include "hw/ssi/spi_gpio.h"
+
+#define TPM_TIS_SPI_ADDR_BYTES 3
+#define SPI_WRITE 0
+
+typedef enum {
+TIS_SPI_PKT_STATE_DEACTIVATED = 0,
+TIS_SPI_PKT_STATE_START,
+TIS_SPI_PKT_STATE_ADDRESS,
+TIS_SPI_PKT_STATE_DATA_WR,
+TIS_SPI_PKT_STATE_DATA_RD,
+TIS_SPI_PKT_STATE_DONE,
+} TpmTisSpiPktState;
+
+union TpmTisRWSizeByte {
+uint8_t byte;
+struct {
+uint8_t data_expected_size:6;
+uint8_t resv:1;
+uint8_t rwflag:1;
+};
+};
+
+union TpmTisSpiHwAddr {
+hwaddr addr;
+uint8_t bytes[sizeof(hwaddr)];
+};
+
+union TpmTisSpiData {
+uint32_t data;
+uint8_t bytes[64];
+};
+
+struct TpmTisSpiState {
+/*< private >*/
+SSIPeripheral parent_obj;
+
+/*< public >*/
+TPMState tpm_state; /* not a QOM object */
+TpmTisSpiPktState tpm_tis_spi_state;
+
+union TpmTisRWSizeByte first_byte;
+union TpmTisSpiHwAddr addr;
+union TpmTisSpiData data;
+
+uint32_t data_size;
+uint8_t data_idx;
+uint8_t addr_idx;
+};
+
+struct TpmTisSpiClass {
+SSIPeripheralClass parent_class;
+};
+
+OBJECT_DECLARE_TYPE(TpmTisSpiState, TpmTisSpiClass, TPM_TIS_SPI)
+
+static void tpm_tis_spi_mmio_read(TpmTisSpiState *tts)
+{
+uint16_t offset = tts->addr.addr & 0xffc;
+
+switch (offset) {
+case TPM_TIS_REG_DATA_FIFO:
+for (uint8_t i = 0; i < tts->data_size; i++) {
+tts->data.bytes[i] = (uint8_t)tpm_tis_memory_ops.read(
+  >tpm_state,
+  tts->addr.addr,
+  1);
+}
+break;
+default:
+tts->data.data = (uint32_t)tpm_tis_memory_ops.read(
+   >tpm_state,
+   tts->addr.addr,
+   tts->data_size);
+}
+}
+
+static void tpm_tis_spi_mmio_write(TpmTisSpiState *tts)
+{
+uint16_t offset = tts->addr.addr & 0xffc;
+
+switch (offset) {
+case TPM_TIS_REG_DATA_FIFO:
+for (uint8_t i = 0; i < tts->data_size; i++) {
+tpm_tis_memory_ops.write(>tpm_state,
+ tts->addr.addr,
+ tts->data.bytes[i],
+  

Re: [RFC 0/3] Add Generic SPI GPIO model

2022-08-02 Thread Iris Chen
Thanks everyone for the insightful feedback! This is really helpful for me. 

I am taking a look at all the comments now and will investigate into it. 

Best, 
Iris 


[RFC 2/3] hw: spi_gpio: add spi gpio model

2022-07-28 Thread Iris Chen
Signed-off-by: Iris Chen 
---
 hw/ssi/spi_gpio.c | 166 ++
 include/hw/ssi/spi_gpio.h |  38 +
 2 files changed, 204 insertions(+)
 create mode 100644 hw/ssi/spi_gpio.c
 create mode 100644 include/hw/ssi/spi_gpio.h

diff --git a/hw/ssi/spi_gpio.c b/hw/ssi/spi_gpio.c
new file mode 100644
index 00..1e99c55933
--- /dev/null
+++ b/hw/ssi/spi_gpio.c
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates. (http://www.meta.com)
+ *
+ * This code is licensed under the GPL version 2 or later. See the COPYING
+ * file in the top-level directory.
+ */
+
+#include "hw/ssi/spi_gpio.h"
+#include "hw/irq.h"
+
+#define SPI_CPHA BIT(0) /* clock phase (1 = SPI_CLOCK_PHASE_SECOND) */
+#define SPI_CPOL BIT(1) /* clock polarity (1 = SPI_POLARITY_HIGH) */
+
+static void do_leading_edge(SpiGpioState *s)
+{
+if (!s->CPHA) {
+s->input_bits |= object_property_get_bool(OBJECT(s->controller_state),
+  "gpioX4", NULL);
+/*
+ * According to SPI protocol:
+ * CPHA=0 leading half clock cycle is sampling phase
+ * We technically should not drive out miso
+ * However, when the kernel bitbang driver is setting the clk pin,
+ * it will overwrite the miso value, so we are driving out miso in
+ * the sampling half clock cycle as well to workaround this issue
+ */
+s->miso = !!(s->output_bits & 0x80);
+object_property_set_bool(OBJECT(s->controller_state), "gpioX5", 
s->miso,
+ NULL);
+}
+}
+
+static void do_trailing_edge(SpiGpioState *s)
+{
+if (s->CPHA) {
+s->input_bits |= object_property_get_bool(OBJECT(s->controller_state),
+  "gpioX4", NULL);
+/*
+ * According to SPI protocol:
+ * CPHA=1 trailing half clock cycle is sampling phase
+ * We technically should not drive out miso
+ * However, when the kernel bitbang driver is setting the clk pin,
+ * it will overwrite the miso value, so we are driving out miso in
+ * the sampling half clock cycle as well to workaround this issue
+ */
+s->miso = !!(s->output_bits & 0x80);
+object_property_set_bool(OBJECT(s->controller_state), "gpioX5", 
s->miso,
+ NULL);
+}
+}
+
+static void cs_set_level(void *opaque, int n, int level)
+{
+SpiGpioState *s = SPI_GPIO(opaque);
+s->cs = !!level;
+
+/* relay the CS value to the CS output pin */
+qemu_set_irq(s->cs_output_pin, s->cs);
+
+s->miso = !!(s->output_bits & 0x80);
+object_property_set_bool(OBJECT(s->controller_state),
+ "gpioX5", s->miso, NULL);
+
+s->clk = !!(s->mode & SPI_CPOL);
+}
+
+static void clk_set_level(void *opaque, int n, int level)
+{
+SpiGpioState *s = SPI_GPIO(opaque);
+
+bool cur = !!level;
+
+/* CS# is high/not selected, do nothing */
+if (s->cs) {
+return;
+}
+
+/* When the lock has not changed, do nothing */
+if (s->clk == cur) {
+return;
+}
+
+s->clk = cur;
+
+/* Leading edge */
+if (s->clk != s->CIDLE) {
+do_leading_edge(s);
+}
+
+/* Trailing edge */
+if (s->clk == s->CIDLE) {
+do_trailing_edge(s);
+s->clk_counter++;
+
+/*
+ * Deliver the input to and
+ * get the next output byte
+ * from the SPI device
+ */
+if (s->clk_counter == 8) {
+s->output_bits = ssi_transfer(s->spi, s->input_bits);
+s->clk_counter = 0;
+s->input_bits = 0;
+ } else {
+s->input_bits <<= 1;
+s->output_bits <<= 1;
+ }
+}
+}
+
+static void spi_gpio_realize(DeviceState *dev, Error **errp)
+{
+SpiGpioState *s = SPI_GPIO(dev);
+
+s->spi = ssi_create_bus(dev, "spi");
+s->spi->preread = true;
+
+s->mode = 0;
+s->clk_counter = 0;
+
+s->cs = true;
+s->clk = true;
+
+/* Assuming the first output byte is 0 */
+s->output_bits = 0;
+s->CIDLE = !!(s->mode & SPI_CPOL);
+s->CPHA = !!(s->mode & SPI_CPHA);
+
+/* init the input GPIO lines */
+/* SPI_CS_in connects to the Aspeed GPIO */
+qdev_init_gpio_in_named(dev, cs_set_level, "SPI_CS_in", 1);
+qdev_init_gpio_in_named(dev, clk_set_level, "SPI_CLK", 1);
+
+/* init the output GPIO lines */
+/* SPI_CS_out connects to the SSI_GPIO_CS */
+qdev_init_gpio_out_named(dev, >cs_output_pin, "SPI_CS_out", 1);
+
+qdev_connect_gpio_out_named(s->controller_

[RFC 1/3] hw: m25p80: add prereading ability in transfer8

2022-07-28 Thread Iris Chen
With SPI-GPIO we don't have the input bits until
all 8 bits of the output have been shifted out,
so we have to prime the MISO with bogus values (0xFF).

Signed-off-by: Iris Chen 
---
 hw/block/m25p80.c| 29 -
 hw/ssi/ssi.c |  4 
 include/hw/ssi/ssi.h |  5 +
 3 files changed, 33 insertions(+), 5 deletions(-)

diff --git a/hw/block/m25p80.c b/hw/block/m25p80.c
index a8d2519141..9b26bb96e5 100644
--- a/hw/block/m25p80.c
+++ b/hw/block/m25p80.c
@@ -1462,7 +1462,7 @@ static int m25p80_cs(SSIPeripheral *ss, bool select)
 return 0;
 }
 
-static uint32_t m25p80_transfer8(SSIPeripheral *ss, uint32_t tx)
+static uint32_t m25p80_transfer8_ex(SSIPeripheral *ss, uint32_t tx)
 {
 Flash *s = M25P80(ss);
 uint32_t r = 0;
@@ -1548,6 +1548,33 @@ static uint32_t m25p80_transfer8(SSIPeripheral *ss, 
uint32_t tx)
 return r;
 }
 
+/*
+ * Pre-reading logic for m25p80_transfer8:
+ * The existing SPI model assumes the output byte is fully formed,
+ * can be passed to the SPI device, and the input byte can be returned,
+ * all in one operation. With SPI-GPIO, we don't have the input byte
+ * until all 8 bits of the output have been shifted out, so we have
+ * to prime the MISO with bogus values (0xFF).
+ */
+static uint32_t m25p80_transfer8(SSIPeripheral *ss, uint32_t tx)
+{
+Flash *s = M25P80(ss);
+SSIBus *ssibus = (SSIBus *)qdev_get_parent_bus(DEVICE(s));
+
+uint8_t prev_state = s->state;
+uint32_t r = m25p80_transfer8_ex(ss, tx);
+uint8_t curr_state = s->state;
+
+if (ssibus->preread &&
+   /* cmd state has changed into DATA reading state */
+   (!(prev_state == STATE_READ || prev_state == STATE_READING_DATA) &&
+   (curr_state == STATE_READ || curr_state == STATE_READING_DATA))) {
+r = m25p80_transfer8_ex(ss, 0xFF);
+}
+
+return r;
+}
+
 static void m25p80_write_protect_pin_irq_handler(void *opaque, int n, int 
level)
 {
 Flash *s = M25P80(opaque);
diff --git a/hw/ssi/ssi.c b/hw/ssi/ssi.c
index 003931fb50..21570fe25f 100644
--- a/hw/ssi/ssi.c
+++ b/hw/ssi/ssi.c
@@ -19,10 +19,6 @@
 #include "qapi/error.h"
 #include "qom/object.h"
 
-struct SSIBus {
-BusState parent_obj;
-};
-
 #define TYPE_SSI_BUS "SSI"
 OBJECT_DECLARE_SIMPLE_TYPE(SSIBus, SSI_BUS)
 
diff --git a/include/hw/ssi/ssi.h b/include/hw/ssi/ssi.h
index f411858ab0..e54073d822 100644
--- a/include/hw/ssi/ssi.h
+++ b/include/hw/ssi/ssi.h
@@ -30,6 +30,11 @@ enum SSICSMode {
 SSI_CS_HIGH,
 };
 
+struct SSIBus {
+BusState parent_obj;
+bool preread;
+};
+
 /* Peripherals.  */
 struct SSIPeripheralClass {
 DeviceClass parent_class;
-- 
2.30.2




[RFC 0/3] Add Generic SPI GPIO model

2022-07-28 Thread Iris Chen
Hey everyone,

I have been working on a project to add support for SPI-based TPMs in QEMU.
Currently, most of our vboot platforms using a SPI-based TPM use the Linux 
SPI-GPIO driver to "bit-bang" the SPI protocol. This is because the Aspeed 
SPI controller (modeled in QEMU under hw/ssi/aspeed_smc.c) has an 
implementation 
deficiency that prevents bi-directional operations. Thus, in order to connect 
a TPM to this bus, my patch implements a QEMU SPI-GPIO driver (as the QEMU
counterpart of the Linux SPI-GPIO driver).

As we use SPI-based TPMs on many of our BMCs for the secure-boot 
implementation,  
I have already tested this implementation locally with our Yosemite-v3.5 
platform 
and Facebook-OpenBMC. This model was tested by connecting a generic SPI-NOR 
(m25p80 
for example) to the Yosemite-v3.5 SPI bus containing the TPM.

This patch is an RFC because I have several questions about design. Although the
model is working, I understand there are many areas where the design decision
is not deal (ie. abstracting hard coded GPIO values). Below are some details of 
the 
patch and specific areas where I would appreciate feedback on how to make this 
better:
 
hw/arm/aspeed.c: 
I am not sure where the best place is to instantiate the spi_gpio besides the
aspeed_machine_init. Could we add the ability to instantiate it on the command 
line?

m25p80_transfer8_ex in hw/block/m25p80.c: 
Existing SPI model assumed that the output byte is fully formed, can be passed 
to 
the SPI device, and the input byte can be returned, all in one operation. With 
SPI-GPIO we don't have the input byte until all 8 bits of the output have been 
shifted out, so we have to prime the MISO with bogus values (0xFF).

MOSI pin in spi_gpio: the mosi pin is not included and we poll the realtime 
value
of the gpio for input bits to prevent bugs with caching the mosi value. It was 
discovered 
during testing that when using the mosi pin as the input pin, the mosi value 
was not 
being updated due to a kernel and aspeed_gpio model optimization. Thus, here we 
are 
reading the value directly from the gpio controller instead of waiting for the 
push.

I realize there are Aspeed specifics in the spi_gpio model. To make this 
extensible, 
is it preferred to make this into a base class and have our Aspeed SPI GPIO 
extend 
this or we could set up params to pass in the constructor?

Thanks for your review and any direction here would be helpful :) 

Iris Chen (3):
  hw: m25p80: add prereading ability in transfer8
  hw: spi_gpio: add spi gpio model
  hw: aspeed: hook up the spi gpio model

 hw/arm/Kconfig|   1 +
 hw/arm/aspeed.c   |   5 ++
 hw/block/m25p80.c |  29 ++-
 hw/ssi/Kconfig|   4 +
 hw/ssi/meson.build|   1 +
 hw/ssi/spi_gpio.c | 166 ++
 hw/ssi/ssi.c  |   4 -
 include/hw/ssi/spi_gpio.h |  38 +
 include/hw/ssi/ssi.h  |   5 ++
 9 files changed, 248 insertions(+), 5 deletions(-)
 create mode 100644 hw/ssi/spi_gpio.c
 create mode 100644 include/hw/ssi/spi_gpio.h

-- 
2.30.2




[PATCH v3] hw: m25p80: Add Block Protect and Top Bottom bits for write protect

2022-07-08 Thread Iris Chen
Signed-off-by: Iris Chen 
---
Cosmetic suggestions addressed. 

 hw/block/m25p80.c | 102 --
 1 file changed, 90 insertions(+), 12 deletions(-)

diff --git a/hw/block/m25p80.c b/hw/block/m25p80.c
index 50b523e5b1..f3b401cf90 100644
--- a/hw/block/m25p80.c
+++ b/hw/block/m25p80.c
@@ -38,21 +38,19 @@
 #include "trace.h"
 #include "qom/object.h"
 
-/* Fields for FlashPartInfo->flags */
-
-/* erase capabilities */
-#define ER_4K 1
-#define ER_32K 2
-/* set to allow the page program command to write 0s back to 1. Useful for
- * modelling EEPROM with SPI flash command set
- */
-#define EEPROM 0x100
-
 /* 16 MiB max in 3 byte address mode */
 #define MAX_3BYTES_SIZE 0x100
-
 #define SPI_NOR_MAX_ID_LEN 6
 
+/* Fields for FlashPartInfo->flags */
+enum spi_flash_option_flags {
+ER_4K  = BIT(0),
+ER_32K = BIT(1),
+EEPROM = BIT(2),
+HAS_SR_TB  = BIT(3),
+HAS_SR_BP3_BIT6= BIT(4),
+};
+
 typedef struct FlashPartInfo {
 const char *part_name;
 /*
@@ -253,7 +251,8 @@ static const FlashPartInfo known_devices[] = {
 { INFO("n25q512a11",  0x20bb20,  0,  64 << 10, 1024, ER_4K) },
 { INFO("n25q512a13",  0x20ba20,  0,  64 << 10, 1024, ER_4K) },
 { INFO("n25q128", 0x20ba18,  0,  64 << 10, 256, 0) },
-{ INFO("n25q256a",0x20ba19,  0,  64 << 10, 512, ER_4K) },
+{ INFO("n25q256a",0x20ba19,  0,  64 << 10, 512,
+   ER_4K | HAS_SR_BP3_BIT6 | HAS_SR_TB) },
 { INFO("n25q512a",0x20ba20,  0,  64 << 10, 1024, ER_4K) },
 { INFO("n25q512ax3",  0x20ba20,  0x1000,  64 << 10, 1024, ER_4K) },
 { INFO("mt25ql512ab", 0x20ba20, 0x1044, 64 << 10, 1024, ER_4K | ER_32K) },
@@ -480,6 +479,11 @@ struct Flash {
 bool reset_enable;
 bool quad_enable;
 bool aai_enable;
+bool block_protect0;
+bool block_protect1;
+bool block_protect2;
+bool block_protect3;
+bool top_bottom_bit;
 bool status_register_write_disabled;
 uint8_t ear;
 
@@ -625,12 +629,36 @@ void flash_write8(Flash *s, uint32_t addr, uint8_t data)
 {
 uint32_t page = addr / s->pi->page_size;
 uint8_t prev = s->storage[s->cur_addr];
+uint32_t block_protect_value = (s->block_protect3 << 3) |
+   (s->block_protect2 << 2) |
+   (s->block_protect1 << 1) |
+   (s->block_protect0 << 0);
 
 if (!s->write_enable) {
 qemu_log_mask(LOG_GUEST_ERROR, "M25P80: write with write protect!\n");
 return;
 }
 
+if (block_protect_value > 0) {
+uint32_t num_protected_sectors = 1 << (block_protect_value - 1);
+uint32_t sector = addr / s->pi->sector_size;
+
+/* top_bottom_bit == 0 means TOP */
+if (!s->top_bottom_bit) {
+if (s->pi->n_sectors <= sector + num_protected_sectors) {
+qemu_log_mask(LOG_GUEST_ERROR,
+  "M25P80: write with write protect!\n");
+return;
+}
+} else {
+if (sector < num_protected_sectors) {
+qemu_log_mask(LOG_GUEST_ERROR,
+  "M25P80: write with write protect!\n");
+return;
+}
+}
+}
+
 if ((prev ^ data) & data) {
 trace_m25p80_programming_zero_to_one(s, addr, prev, data);
 }
@@ -728,6 +756,15 @@ static void complete_collecting_data(Flash *s)
 break;
 case WRSR:
 s->status_register_write_disabled = extract32(s->data[0], 7, 1);
+s->block_protect0 = extract32(s->data[0], 2, 1);
+s->block_protect1 = extract32(s->data[0], 3, 1);
+s->block_protect2 = extract32(s->data[0], 4, 1);
+if (s->pi->flags & HAS_SR_TB) {
+s->top_bottom_bit = extract32(s->data[0], 5, 1);
+}
+if (s->pi->flags & HAS_SR_BP3_BIT6) {
+s->block_protect3 = extract32(s->data[0], 6, 1);
+}
 
 switch (get_man(s)) {
 case MAN_SPANSION:
@@ -1213,6 +1250,15 @@ static void decode_new_cmd(Flash *s, uint32_t value)
 case RDSR:
 s->data[0] = (!!s->write_enable) << 1;
 s->data[0] |= (!!s->status_register_write_disabled) << 7;
+s->data[0] |= (!!s->block_protect0) << 2;
+s->data[0] |= (!!s->block_protect1) << 3;
+s->data[0] |= (!!s->block_protect2) << 4;
+if (s->pi->flags & HAS_SR_TB) {
+s->data[0] |= (!!s->top_bottom_bit) << 5;
+}
+ 

[PATCH v2] hw: m25p80: Add Block Protect and Top Bottom bits for write protect

2022-07-06 Thread Iris Chen
Signed-off-by: Iris Chen 
---
Addressing all comments. 
In reponse to this comment:
"Something wrong will occur if all block_protect[0123] are zeroes",
the code actually ignores num_protected_sectors when block_protect_value = 0
which happens when block_protect[0123] are zeroes.
 
You can refer to the bottom block in v1, where we only look at cases when 
block_protect_value > 0 so it is actually handled.
Regardless, since we were setting num_protected_sectors
in either case, I have refactored the code to make it more clear. 

 hw/block/m25p80.c | 103 --
 1 file changed, 91 insertions(+), 12 deletions(-)

diff --git a/hw/block/m25p80.c b/hw/block/m25p80.c
index 50b523e5b1..bddea9853b 100644
--- a/hw/block/m25p80.c
+++ b/hw/block/m25p80.c
@@ -38,21 +38,19 @@
 #include "trace.h"
 #include "qom/object.h"
 
-/* Fields for FlashPartInfo->flags */
-
-/* erase capabilities */
-#define ER_4K 1
-#define ER_32K 2
-/* set to allow the page program command to write 0s back to 1. Useful for
- * modelling EEPROM with SPI flash command set
- */
-#define EEPROM 0x100
-
 /* 16 MiB max in 3 byte address mode */
 #define MAX_3BYTES_SIZE 0x100
-
 #define SPI_NOR_MAX_ID_LEN 6
 
+/* Fields for FlashPartInfo->flags */
+enum spi_option_flags {
+ER_4K  = BIT(0),
+ER_32K = BIT(1),
+EEPROM = BIT(2),
+HAS_SR_TB  = BIT(3),
+HAS_SR_BP3_BIT6= BIT(4),
+};
+
 typedef struct FlashPartInfo {
 const char *part_name;
 /*
@@ -253,7 +251,8 @@ static const FlashPartInfo known_devices[] = {
 { INFO("n25q512a11",  0x20bb20,  0,  64 << 10, 1024, ER_4K) },
 { INFO("n25q512a13",  0x20ba20,  0,  64 << 10, 1024, ER_4K) },
 { INFO("n25q128", 0x20ba18,  0,  64 << 10, 256, 0) },
-{ INFO("n25q256a",0x20ba19,  0,  64 << 10, 512, ER_4K) },
+{ INFO("n25q256a",0x20ba19,  0,  64 << 10, 512,
+   ER_4K | HAS_SR_BP3_BIT6 | HAS_SR_TB) },
 { INFO("n25q512a",0x20ba20,  0,  64 << 10, 1024, ER_4K) },
 { INFO("n25q512ax3",  0x20ba20,  0x1000,  64 << 10, 1024, ER_4K) },
 { INFO("mt25ql512ab", 0x20ba20, 0x1044, 64 << 10, 1024, ER_4K | ER_32K) },
@@ -480,6 +479,11 @@ struct Flash {
 bool reset_enable;
 bool quad_enable;
 bool aai_enable;
+bool block_protect0;
+bool block_protect1;
+bool block_protect2;
+bool block_protect3;
+bool top_bottom_bit;
 bool status_register_write_disabled;
 uint8_t ear;
 
@@ -626,11 +630,36 @@ void flash_write8(Flash *s, uint32_t addr, uint8_t data)
 uint32_t page = addr / s->pi->page_size;
 uint8_t prev = s->storage[s->cur_addr];
 
+uint32_t block_protect_value = (s->block_protect3 << 3) |
+   (s->block_protect2 << 2) |
+   (s->block_protect1 << 1) |
+   (s->block_protect0 << 0);
+
 if (!s->write_enable) {
 qemu_log_mask(LOG_GUEST_ERROR, "M25P80: write with write protect!\n");
 return;
 }
 
+if (block_protect_value > 0) {
+uint32_t num_protected_sectors = 1 << (block_protect_value - 1);
+uint32_t sector = addr / s->pi->sector_size;
+
+/* top_bottom_bit == 0 means TOP */
+if (!s->top_bottom_bit) {
+if (s->pi->n_sectors <= sector + num_protected_sectors) {
+qemu_log_mask(LOG_GUEST_ERROR,
+  "M25P80: write with write protect!\n");
+return;
+}
+} else {
+if (sector < num_protected_sectors) {
+qemu_log_mask(LOG_GUEST_ERROR,
+  "M25P80: write with write protect!\n");
+return;
+}
+}
+}
+
 if ((prev ^ data) & data) {
 trace_m25p80_programming_zero_to_one(s, addr, prev, data);
 }
@@ -728,6 +757,15 @@ static void complete_collecting_data(Flash *s)
 break;
 case WRSR:
 s->status_register_write_disabled = extract32(s->data[0], 7, 1);
+s->block_protect0 = extract32(s->data[0], 2, 1);
+s->block_protect1 = extract32(s->data[0], 3, 1);
+s->block_protect2 = extract32(s->data[0], 4, 1);
+if (s->pi->flags & HAS_SR_TB) {
+s->top_bottom_bit = extract32(s->data[0], 5, 1);
+}
+if (s->pi->flags & HAS_SR_BP3_BIT6) {
+s->block_protect3 = extract32(s->data[0], 6, 1);
+}
 
 switch (get_man(s)) {
 case MAN_SPANSION:
@@ -1213,6 +1251,15 @@ static void decode_new_cmd(Flash *s, uint32_t value)
 case

[PATCH 1/2] hw: m25p80: Add Block Protect and Top Bottom bits for write protect

2022-06-27 Thread Iris Chen
Signed-off-by: Iris Chen 
---
 hw/block/m25p80.c | 74 +++
 1 file changed, 62 insertions(+), 12 deletions(-)

diff --git a/hw/block/m25p80.c b/hw/block/m25p80.c
index 50b523e5b1..0156a70f5e 100644
--- a/hw/block/m25p80.c
+++ b/hw/block/m25p80.c
@@ -38,21 +38,19 @@
 #include "trace.h"
 #include "qom/object.h"
 
-/* Fields for FlashPartInfo->flags */
-
-/* erase capabilities */
-#define ER_4K 1
-#define ER_32K 2
-/* set to allow the page program command to write 0s back to 1. Useful for
- * modelling EEPROM with SPI flash command set
- */
-#define EEPROM 0x100
-
 /* 16 MiB max in 3 byte address mode */
 #define MAX_3BYTES_SIZE 0x100
-
 #define SPI_NOR_MAX_ID_LEN 6
 
+/* Fields for FlashPartInfo->flags */
+enum spi_nor_option_flags {
+ER_4K  = BIT(0),
+ER_32K = BIT(1),
+EEPROM = BIT(2),
+SNOR_F_HAS_SR_TB   = BIT(3),
+SNOR_F_HAS_SR_BP3_BIT6 = BIT(4),
+};
+
 typedef struct FlashPartInfo {
 const char *part_name;
 /*
@@ -253,7 +251,8 @@ static const FlashPartInfo known_devices[] = {
 { INFO("n25q512a11",  0x20bb20,  0,  64 << 10, 1024, ER_4K) },
 { INFO("n25q512a13",  0x20ba20,  0,  64 << 10, 1024, ER_4K) },
 { INFO("n25q128", 0x20ba18,  0,  64 << 10, 256, 0) },
-{ INFO("n25q256a",0x20ba19,  0,  64 << 10, 512, ER_4K) },
+{ INFO("n25q256a",0x20ba19,  0,  64 << 10, 512,
+   ER_4K | SNOR_F_HAS_SR_BP3_BIT6 | SNOR_F_HAS_SR_TB) },
 { INFO("n25q512a",0x20ba20,  0,  64 << 10, 1024, ER_4K) },
 { INFO("n25q512ax3",  0x20ba20,  0x1000,  64 << 10, 1024, ER_4K) },
 { INFO("mt25ql512ab", 0x20ba20, 0x1044, 64 << 10, 1024, ER_4K | ER_32K) },
@@ -480,6 +479,11 @@ struct Flash {
 bool reset_enable;
 bool quad_enable;
 bool aai_enable;
+bool block_protect0;
+bool block_protect1;
+bool block_protect2;
+bool block_protect3;
+bool top_bottom_bit;
 bool status_register_write_disabled;
 uint8_t ear;
 
@@ -630,6 +634,29 @@ void flash_write8(Flash *s, uint32_t addr, uint8_t data)
 qemu_log_mask(LOG_GUEST_ERROR, "M25P80: write with write protect!\n");
 return;
 }
+uint32_t block_protect_value = (s->block_protect3 << 3) |
+   (s->block_protect2 << 2) |
+   (s->block_protect1 << 1) |
+   (s->block_protect0 << 0);
+
+ uint32_t num_protected_sectors = 1 << (block_protect_value - 1);
+ uint32_t sector = addr / s->pi->sector_size;
+
+ /* top_bottom_bit == 0 means TOP */
+if (!s->top_bottom_bit) {
+if (block_protect_value > 0 &&
+s->pi->n_sectors <= sector + num_protected_sectors) {
+qemu_log_mask(LOG_GUEST_ERROR,
+  "M25P80: write with write protect!\n");
+return;
+}
+} else {
+if (block_protect_value > 0 && sector < num_protected_sectors) {
+qemu_log_mask(LOG_GUEST_ERROR,
+  "M25P80: write with write protect!\n");
+return;
+}
+}
 
 if ((prev ^ data) & data) {
 trace_m25p80_programming_zero_to_one(s, addr, prev, data);
@@ -728,6 +755,15 @@ static void complete_collecting_data(Flash *s)
 break;
 case WRSR:
 s->status_register_write_disabled = extract32(s->data[0], 7, 1);
+s->block_protect0 = extract32(s->data[0], 2, 1);
+s->block_protect1 = extract32(s->data[0], 3, 1);
+s->block_protect2 = extract32(s->data[0], 4, 1);
+if (s->pi->flags & SNOR_F_HAS_SR_TB) {
+s->top_bottom_bit = extract32(s->data[0], 5, 1);
+}
+if (s->pi->flags & SNOR_F_HAS_SR_BP3_BIT6) {
+s->block_protect3 = extract32(s->data[0], 6, 1);
+}
 
 switch (get_man(s)) {
 case MAN_SPANSION:
@@ -1213,6 +1249,15 @@ static void decode_new_cmd(Flash *s, uint32_t value)
 case RDSR:
 s->data[0] = (!!s->write_enable) << 1;
 s->data[0] |= (!!s->status_register_write_disabled) << 7;
+s->data[0] |= (!!s->block_protect0) << 2;
+s->data[0] |= (!!s->block_protect1) << 3;
+s->data[0] |= (!!s->block_protect2) << 4;
+if (s->pi->flags & SNOR_F_HAS_SR_TB) {
+s->data[0] |= (!!s->top_bottom_bit) << 5;
+}
+if (s->pi->flags & SNOR_F_HAS_SR_BP3_BIT6) {
+s->data[0] |= (!!s->block_protect3) << 6;
+}
 
 if (get_man(s) == MAN_MACRONIX || get_man(s) == MAN_ISSI) {
 s->data[0] |= (!!s->quad_enable) << 6;
@@ -1553,6 +1598,11 @@ static void m25p80_reset(DeviceState *d)
 
 s->wp_level = true;
 s->status_register_write_disabled = false;
+s->block_protect0 = false;
+s->block_protect1 = false;
+s->block_protect2 = false;
+s->block_protect3 = false;
+s->top_bottom_bit = false;
 
 reset_memory(s);
 }
-- 
2.30.2




[PATCH 2/2] hw: m25p80: add tests for BP and TB bit write protect

2022-06-27 Thread Iris Chen
Signed-off-by: Iris Chen 
---
 tests/qtest/aspeed_smc-test.c | 111 ++
 1 file changed, 111 insertions(+)

diff --git a/tests/qtest/aspeed_smc-test.c b/tests/qtest/aspeed_smc-test.c
index 1258687eac..05ce941566 100644
--- a/tests/qtest/aspeed_smc-test.c
+++ b/tests/qtest/aspeed_smc-test.c
@@ -192,6 +192,24 @@ static void read_page_mem(uint32_t addr, uint32_t *page)
 }
 }
 
+static void write_page_mem(uint32_t addr, uint32_t write_value)
+{
+spi_ctrl_setmode(CTRL_WRITEMODE, PP);
+
+for (int i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
+writel(ASPEED_FLASH_BASE + addr + i * 4, write_value);
+}
+}
+
+static void assert_page_mem(uint32_t addr, uint32_t expected_value)
+{
+uint32_t page[FLASH_PAGE_SIZE / 4];
+read_page_mem(addr, page);
+for (int i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
+g_assert_cmphex(page[i], ==, expected_value);
+}
+}
+
 static void test_erase_sector(void)
 {
 uint32_t some_page_addr = 0x600 * FLASH_PAGE_SIZE;
@@ -501,6 +519,95 @@ static void test_status_reg_write_protection(void)
 flash_reset();
 }
 
+static void test_write_block_protect(void)
+{
+uint32_t sector_size = 65536;
+uint32_t n_sectors = 512;
+
+spi_ce_ctrl(1 << CRTL_EXTENDED0);
+spi_conf(CONF_ENABLE_W0);
+
+uint32_t bp_bits = 0b0;
+
+for (int i = 0; i < 16; i++) {
+bp_bits = ((i & 0b1000) << 3) | ((i & 0b0111) << 2);
+
+spi_ctrl_start_user();
+writeb(ASPEED_FLASH_BASE, WREN);
+writeb(ASPEED_FLASH_BASE, BULK_ERASE);
+writeb(ASPEED_FLASH_BASE, WREN);
+writeb(ASPEED_FLASH_BASE, WRSR);
+writeb(ASPEED_FLASH_BASE, bp_bits);
+writeb(ASPEED_FLASH_BASE, EN_4BYTE_ADDR);
+writeb(ASPEED_FLASH_BASE, WREN);
+spi_ctrl_stop_user();
+
+uint32_t num_protected_sectors = i ? MIN(1 << (i - 1), n_sectors) : 0;
+uint32_t protection_start = n_sectors - num_protected_sectors;
+uint32_t protection_end = n_sectors;
+
+for (int sector = 0; sector < n_sectors; sector++) {
+uint32_t addr = sector * sector_size;
+
+assert_page_mem(addr, 0x);
+write_page_mem(addr, make_be32(0xabcdef12));
+
+uint32_t expected_value = protection_start <= sector
+  && sector < protection_end
+  ? 0x : 0xabcdef12;
+
+assert_page_mem(addr, expected_value);
+}
+}
+
+flash_reset();
+}
+
+static void test_write_block_protect_bottom_bit(void)
+{
+uint32_t sector_size = 65536;
+uint32_t n_sectors = 512;
+
+spi_ce_ctrl(1 << CRTL_EXTENDED0);
+spi_conf(CONF_ENABLE_W0);
+
+/* top bottom bit is enabled */
+uint32_t bp_bits = 0b00100 << 3;
+
+for (int i = 0; i < 16; i++) {
+bp_bits = (((i & 0b1000) | 0b0100) << 3) | ((i & 0b0111) << 2);
+
+spi_ctrl_start_user();
+writeb(ASPEED_FLASH_BASE, WREN);
+writeb(ASPEED_FLASH_BASE, BULK_ERASE);
+writeb(ASPEED_FLASH_BASE, WREN);
+writeb(ASPEED_FLASH_BASE, WRSR);
+writeb(ASPEED_FLASH_BASE, bp_bits);
+writeb(ASPEED_FLASH_BASE, EN_4BYTE_ADDR);
+writeb(ASPEED_FLASH_BASE, WREN);
+spi_ctrl_stop_user();
+
+uint32_t num_protected_sectors = i ? MIN(1 << (i - 1), n_sectors) : 0;
+uint32_t protection_start = 0;
+uint32_t protection_end = num_protected_sectors;
+
+for (int sector = 0; sector < n_sectors; sector++) {
+uint32_t addr = sector * sector_size;
+
+assert_page_mem(addr, 0x);
+write_page_mem(addr, make_be32(0xabcdef12));
+
+uint32_t expected_value = protection_start <= sector
+  && sector < protection_end
+  ? 0x : 0xabcdef12;
+
+assert_page_mem(addr, expected_value);
+}
+}
+
+flash_reset();
+}
+
 static char tmp_path[] = "/tmp/qtest.m25p80.XX";
 
 int main(int argc, char **argv)
@@ -529,6 +636,10 @@ int main(int argc, char **argv)
 qtest_add_func("/ast2400/smc/read_status_reg", test_read_status_reg);
 qtest_add_func("/ast2400/smc/status_reg_write_protection",
test_status_reg_write_protection);
+qtest_add_func("/ast2400/smc/write_block_protect",
+   test_write_block_protect);
+qtest_add_func("/ast2400/smc/write_block_protect_bottom_bit",
+   test_write_block_protect_bottom_bit);
 
 flash_reset();
 ret = g_test_run();
-- 
2.30.2




[PATCH 0/2] Add Block Protect (BP) and Top Bottom (TB) bits for write protect

2022-06-27 Thread Iris Chen
Hey everyone,

Adding the 3 block protection bits and top bottom bits which configure which 
parts
of the flash are writable and read-only.

These patches are based on previous patches I just submitted:
hw: m25p80: fixing individual test failure when tests are running in isolation
hw: m25p80: add WP# pin and SRWD bit for write protection
hw: m25p80: add tests for write protect (WP# and SRWD bit)

The tests that were added iterates through every different memory protection
case so it takes a little longer to run. Let me know what you think about that
and if that's okay or not.

Thanks,
Iris

Iris Chen (2):
  hw: m25p80: Add Block Protect and Top Bottom bits for write protect
  hw: m25p80: add tests for BP and TB bit write protect

 hw/block/m25p80.c |  74 +++
 tests/qtest/aspeed_smc-test.c | 111 ++
 2 files changed, 173 insertions(+), 12 deletions(-)

--
2.30.2




[PATCH v4] hw: m25p80: add tests for write protect (WP# and SRWD bit)

2022-06-24 Thread Iris Chen
Signed-off-by: Iris Chen 
---
Adding Signed Off By tag -- sorry I missed that !

 tests/qtest/aspeed_smc-test.c | 62 +++
 1 file changed, 62 insertions(+)

diff --git a/tests/qtest/aspeed_smc-test.c b/tests/qtest/aspeed_smc-test.c
index ec233315e6..7786addfb8 100644
--- a/tests/qtest/aspeed_smc-test.c
+++ b/tests/qtest/aspeed_smc-test.c
@@ -56,7 +56,9 @@ enum {
 BULK_ERASE = 0xc7,
 READ = 0x03,
 PP = 0x02,
+WRSR = 0x1,
 WREN = 0x6,
+SRWD = 0x80,
 RESET_ENABLE = 0x66,
 RESET_MEMORY = 0x99,
 EN_4BYTE_ADDR = 0xB7,
@@ -390,6 +392,64 @@ static void test_read_status_reg(void)
 flash_reset();
 }
 
+static void test_status_reg_write_protection(void)
+{
+uint8_t r;
+
+spi_conf(CONF_ENABLE_W0);
+
+/* default case: WP# is high and SRWD is low -> status register writable */
+spi_ctrl_start_user();
+writeb(ASPEED_FLASH_BASE, WREN);
+/* test ability to write SRWD */
+writeb(ASPEED_FLASH_BASE, WRSR);
+writeb(ASPEED_FLASH_BASE, SRWD);
+writeb(ASPEED_FLASH_BASE, RDSR);
+r = readb(ASPEED_FLASH_BASE);
+spi_ctrl_stop_user();
+g_assert_cmphex(r & SRWD, ==, SRWD);
+
+/* WP# high and SRWD high -> status register writable */
+spi_ctrl_start_user();
+writeb(ASPEED_FLASH_BASE, WREN);
+/* test ability to write SRWD */
+writeb(ASPEED_FLASH_BASE, WRSR);
+writeb(ASPEED_FLASH_BASE, 0);
+writeb(ASPEED_FLASH_BASE, RDSR);
+r = readb(ASPEED_FLASH_BASE);
+spi_ctrl_stop_user();
+g_assert_cmphex(r & SRWD, ==, 0);
+
+/* WP# low and SRWD low -> status register writable */
+qtest_set_irq_in(global_qtest,
+ "/machine/soc/fmc/ssi.0/child[0]", "WP#", 0, 0);
+spi_ctrl_start_user();
+writeb(ASPEED_FLASH_BASE, WREN);
+/* test ability to write SRWD */
+writeb(ASPEED_FLASH_BASE, WRSR);
+writeb(ASPEED_FLASH_BASE, SRWD);
+writeb(ASPEED_FLASH_BASE, RDSR);
+r = readb(ASPEED_FLASH_BASE);
+spi_ctrl_stop_user();
+g_assert_cmphex(r & SRWD, ==, SRWD);
+
+/* WP# low and SRWD high -> status register NOT writable */
+spi_ctrl_start_user();
+writeb(ASPEED_FLASH_BASE, WREN);
+/* test ability to write SRWD */
+writeb(ASPEED_FLASH_BASE, WRSR);
+writeb(ASPEED_FLASH_BASE, 0);
+writeb(ASPEED_FLASH_BASE, RDSR);
+r = readb(ASPEED_FLASH_BASE);
+spi_ctrl_stop_user();
+/* write is not successful */
+g_assert_cmphex(r & SRWD, ==, SRWD);
+
+qtest_set_irq_in(global_qtest,
+ "/machine/soc/fmc/ssi.0/child[0]", "WP#", 0, 1);
+flash_reset();
+}
+
 static char tmp_path[] = "/tmp/qtest.m25p80.XX";
 
 int main(int argc, char **argv)
@@ -416,6 +476,8 @@ int main(int argc, char **argv)
 qtest_add_func("/ast2400/smc/read_page_mem", test_read_page_mem);
 qtest_add_func("/ast2400/smc/write_page_mem", test_write_page_mem);
 qtest_add_func("/ast2400/smc/read_status_reg", test_read_status_reg);
+qtest_add_func("/ast2400/smc/status_reg_write_protection",
+   test_status_reg_write_protection);
 
 ret = g_test_run();
 
-- 
2.30.2




[PATCH v4] hw: m25p80: add WP# pin and SRWD bit for write protection

2022-06-21 Thread Iris Chen
From: Iris Chen 

Signed-off-by: Iris Chen 
---
Fixed .needed for subsection and suggestions from Francisco 

 hw/block/m25p80.c | 82 ++-
 1 file changed, 67 insertions(+), 15 deletions(-)

diff --git a/hw/block/m25p80.c b/hw/block/m25p80.c
index 81ba3da4df..3045dda53b 100644
--- a/hw/block/m25p80.c
+++ b/hw/block/m25p80.c
@@ -472,11 +472,13 @@ struct Flash {
 uint8_t spansion_cr2v;
 uint8_t spansion_cr3v;
 uint8_t spansion_cr4v;
+bool wp_level;
 bool write_enable;
 bool four_bytes_address_mode;
 bool reset_enable;
 bool quad_enable;
 bool aai_enable;
+bool status_register_write_disabled;
 uint8_t ear;
 
 int64_t dirty_page;
@@ -723,6 +725,8 @@ static void complete_collecting_data(Flash *s)
 flash_erase(s, s->cur_addr, s->cmd_in_progress);
 break;
 case WRSR:
+s->status_register_write_disabled = extract32(s->data[0], 7, 1);
+
 switch (get_man(s)) {
 case MAN_SPANSION:
 s->quad_enable = !!(s->data[1] & 0x02);
@@ -1165,22 +1169,34 @@ static void decode_new_cmd(Flash *s, uint32_t value)
 break;
 
 case WRSR:
-if (s->write_enable) {
-switch (get_man(s)) {
-case MAN_SPANSION:
-s->needed_bytes = 2;
-s->state = STATE_COLLECTING_DATA;
-break;
-case MAN_MACRONIX:
-s->needed_bytes = 2;
-s->state = STATE_COLLECTING_VAR_LEN_DATA;
-break;
-default:
-s->needed_bytes = 1;
-s->state = STATE_COLLECTING_DATA;
-}
-s->pos = 0;
+/*
+ * If WP# is low and status_register_write_disabled is high,
+ * status register writes are disabled.
+ * This is also called "hardware protected mode" (HPM). All other
+ * combinations of the two states are called "software protected mode"
+ * (SPM), and status register writes are permitted.
+ */
+if ((s->wp_level == 0 && s->status_register_write_disabled)
+|| !s->write_enable) {
+qemu_log_mask(LOG_GUEST_ERROR,
+  "M25P80: Status register write is disabled!\n");
+break;
+}
+
+switch (get_man(s)) {
+case MAN_SPANSION:
+s->needed_bytes = 2;
+s->state = STATE_COLLECTING_DATA;
+break;
+case MAN_MACRONIX:
+s->needed_bytes = 2;
+s->state = STATE_COLLECTING_VAR_LEN_DATA;
+break;
+default:
+s->needed_bytes = 1;
+s->state = STATE_COLLECTING_DATA;
 }
+s->pos = 0;
 break;
 
 case WRDI:
@@ -1195,6 +1211,8 @@ static void decode_new_cmd(Flash *s, uint32_t value)
 
 case RDSR:
 s->data[0] = (!!s->write_enable) << 1;
+s->data[0] |= (!!s->status_register_write_disabled) << 7;
+
 if (get_man(s) == MAN_MACRONIX || get_man(s) == MAN_ISSI) {
 s->data[0] |= (!!s->quad_enable) << 6;
 }
@@ -1484,6 +1502,14 @@ static uint32_t m25p80_transfer8(SSIPeripheral *ss, 
uint32_t tx)
 return r;
 }
 
+static void m25p80_write_protect_pin_irq_handler(void *opaque, int n, int 
level)
+{
+Flash *s = M25P80(opaque);
+/* WP# is just a single pin. */
+assert(n == 0);
+s->wp_level = !!level;
+}
+
 static void m25p80_realize(SSIPeripheral *ss, Error **errp)
 {
 Flash *s = M25P80(ss);
@@ -1515,12 +1541,18 @@ static void m25p80_realize(SSIPeripheral *ss, Error 
**errp)
 s->storage = blk_blockalign(NULL, s->size);
 memset(s->storage, 0xFF, s->size);
 }
+
+qdev_init_gpio_in_named(DEVICE(s),
+m25p80_write_protect_pin_irq_handler, "WP#", 1);
 }
 
 static void m25p80_reset(DeviceState *d)
 {
 Flash *s = M25P80(d);
 
+s->wp_level = true;
+s->status_register_write_disabled = false;
+
 reset_memory(s);
 }
 
@@ -1587,6 +1619,25 @@ static const VMStateDescription 
vmstate_m25p80_aai_enable = {
 }
 };
 
+static bool m25p80_wp_level_srwd_needed(void *opaque)
+{
+Flash *s = (Flash *)opaque;
+
+return !s->wp_level || s->status_register_write_disabled;
+}
+
+static const VMStateDescription vmstate_m25p80_write_protect = {
+.name = "m25p80/write_protect",
+.version_id = 1,
+.minimum_version_id = 1,
+.needed = m25p80_wp_level_srwd_needed,
+.fields = (VMStateField[]) {
+VMSTATE_BOOL(wp_level, Flash),
+VMSTATE_BOOL(status_register_write_disabled, Flash),
+VMSTATE_END_OF_LIST()
+}
+};
+
 static const VMStateDescription vmstate_m25p80 = {
 .name = "m25p80",
 .version_id = 0,
@@ -1618,6 +1669,7 @@ stati

[PATCH 1/1] hw: m25p80: fixing individual test failure when tests are running in isolation

2022-06-17 Thread Iris Chen
Signed-off-by: Iris Chen 
---
 tests/qtest/aspeed_smc-test.c | 74 +--
 1 file changed, 63 insertions(+), 11 deletions(-)

diff --git a/tests/qtest/aspeed_smc-test.c b/tests/qtest/aspeed_smc-test.c
index ec233315e6..b1e682db65 100644
--- a/tests/qtest/aspeed_smc-test.c
+++ b/tests/qtest/aspeed_smc-test.c
@@ -135,6 +135,9 @@ static void flash_reset(void)
 spi_ctrl_start_user();
 writeb(ASPEED_FLASH_BASE, RESET_ENABLE);
 writeb(ASPEED_FLASH_BASE, RESET_MEMORY);
+writeb(ASPEED_FLASH_BASE, WREN);
+writeb(ASPEED_FLASH_BASE, BULK_ERASE);
+writeb(ASPEED_FLASH_BASE, WRDI);
 spi_ctrl_stop_user();
 
 spi_conf_remove(CONF_ENABLE_W0);
@@ -195,21 +198,41 @@ static void test_erase_sector(void)
 
 spi_conf(CONF_ENABLE_W0);
 
+/*
+ * Previous page should be full of 0xffs after backend is
+ * initialized
+ */
+read_page(some_page_addr - FLASH_PAGE_SIZE, page);
+for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
+g_assert_cmphex(page[i], ==, 0x);
+}
+
 spi_ctrl_start_user();
-writeb(ASPEED_FLASH_BASE, WREN);
 writeb(ASPEED_FLASH_BASE, EN_4BYTE_ADDR);
-writeb(ASPEED_FLASH_BASE, ERASE_SECTOR);
+writeb(ASPEED_FLASH_BASE, WREN);
+writeb(ASPEED_FLASH_BASE, PP);
 writel(ASPEED_FLASH_BASE, make_be32(some_page_addr));
+
+/* Fill the page with its own addresses */
+for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
+writel(ASPEED_FLASH_BASE, make_be32(some_page_addr + i * 4));
+}
 spi_ctrl_stop_user();
 
-/* Previous page should be full of zeroes as backend is not
- * initialized */
-read_page(some_page_addr - FLASH_PAGE_SIZE, page);
+/* Check the page is correctly written */
+read_page(some_page_addr, page);
 for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
-g_assert_cmphex(page[i], ==, 0x0);
+g_assert_cmphex(page[i], ==, some_page_addr + i * 4);
 }
 
-/* But this one was erased */
+spi_ctrl_start_user();
+writeb(ASPEED_FLASH_BASE, WREN);
+writeb(ASPEED_FLASH_BASE, EN_4BYTE_ADDR);
+writeb(ASPEED_FLASH_BASE, ERASE_SECTOR);
+writel(ASPEED_FLASH_BASE, make_be32(some_page_addr));
+spi_ctrl_stop_user();
+
+/* Check the page is erased */
 read_page(some_page_addr, page);
 for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
 g_assert_cmphex(page[i], ==, 0x);
@@ -226,11 +249,31 @@ static void test_erase_all(void)
 
 spi_conf(CONF_ENABLE_W0);
 
-/* Check some random page. Should be full of zeroes as backend is
- * not initialized */
+/*
+ * Previous page should be full of 0xffs after backend is
+ * initialized
+ */
+read_page(some_page_addr - FLASH_PAGE_SIZE, page);
+for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
+g_assert_cmphex(page[i], ==, 0x);
+}
+
+spi_ctrl_start_user();
+writeb(ASPEED_FLASH_BASE, EN_4BYTE_ADDR);
+writeb(ASPEED_FLASH_BASE, WREN);
+writeb(ASPEED_FLASH_BASE, PP);
+writel(ASPEED_FLASH_BASE, make_be32(some_page_addr));
+
+/* Fill the page with its own addresses */
+for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
+writel(ASPEED_FLASH_BASE, make_be32(some_page_addr + i * 4));
+}
+spi_ctrl_stop_user();
+
+/* Check the page is correctly written */
 read_page(some_page_addr, page);
 for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
-g_assert_cmphex(page[i], ==, 0x0);
+g_assert_cmphex(page[i], ==, some_page_addr + i * 4);
 }
 
 spi_ctrl_start_user();
@@ -238,7 +281,7 @@ static void test_erase_all(void)
 writeb(ASPEED_FLASH_BASE, BULK_ERASE);
 spi_ctrl_stop_user();
 
-/* Recheck that some random page */
+/* Check the page is erased */
 read_page(some_page_addr, page);
 for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
 g_assert_cmphex(page[i], ==, 0x);
@@ -299,6 +342,14 @@ static void test_read_page_mem(void)
 spi_conf(CONF_ENABLE_W0);
 spi_ctrl_start_user();
 writeb(ASPEED_FLASH_BASE, EN_4BYTE_ADDR);
+writeb(ASPEED_FLASH_BASE, WREN);
+writeb(ASPEED_FLASH_BASE, PP);
+writel(ASPEED_FLASH_BASE, make_be32(my_page_addr));
+
+/* Fill the page with its own addresses */
+for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
+writel(ASPEED_FLASH_BASE, make_be32(my_page_addr + i * 4));
+}
 spi_ctrl_stop_user();
 spi_conf_remove(CONF_ENABLE_W0);
 
@@ -417,6 +468,7 @@ int main(int argc, char **argv)
 qtest_add_func("/ast2400/smc/write_page_mem", test_write_page_mem);
 qtest_add_func("/ast2400/smc/read_status_reg", test_read_status_reg);
 
+flash_reset();
 ret = g_test_run();
 
 qtest_quit(global_qtest);
-- 
2.30.2




[PATCH 0/1] hw: m25p80: fix aspeed_smc tests failure when run in isolation

2022-06-17 Thread Iris Chen
Hey everyone,

I discovered that some of the tests in tests/qtest/aspeed_smc-test.c were
failing when run in isolation due to dependencies between the tests. For
example, one test would test the reading of a block of memory written
in the test before it.

I think it would make sense to add flash_reset() between running the
tests and make sure the tests do not rely on each other. Thus, I have
made changes to the tests so that they now pass individually with
no dependencies on each other.

Thanks,
Iris

Iris Chen (1):
  hw: m25p80: fixing individual test failure when tests are running in
isolation

 tests/qtest/aspeed_smc-test.c | 74 +--
 1 file changed, 63 insertions(+), 11 deletions(-)

--
2.30.2



[PATCH v3 2/2] hw: m25p80: add tests for write protect (WP# and SRWD bit)

2022-06-17 Thread Iris Chen
---
Fixing suggestions to move testing related code to a different commit.

 tests/qtest/aspeed_smc-test.c | 62 +++
 1 file changed, 62 insertions(+)

diff --git a/tests/qtest/aspeed_smc-test.c b/tests/qtest/aspeed_smc-test.c
index ec233315e6..7786addfb8 100644
--- a/tests/qtest/aspeed_smc-test.c
+++ b/tests/qtest/aspeed_smc-test.c
@@ -56,7 +56,9 @@ enum {
 BULK_ERASE = 0xc7,
 READ = 0x03,
 PP = 0x02,
+WRSR = 0x1,
 WREN = 0x6,
+SRWD = 0x80,
 RESET_ENABLE = 0x66,
 RESET_MEMORY = 0x99,
 EN_4BYTE_ADDR = 0xB7,
@@ -390,6 +392,64 @@ static void test_read_status_reg(void)
 flash_reset();
 }
 
+static void test_status_reg_write_protection(void)
+{
+uint8_t r;
+
+spi_conf(CONF_ENABLE_W0);
+
+/* default case: WP# is high and SRWD is low -> status register writable */
+spi_ctrl_start_user();
+writeb(ASPEED_FLASH_BASE, WREN);
+/* test ability to write SRWD */
+writeb(ASPEED_FLASH_BASE, WRSR);
+writeb(ASPEED_FLASH_BASE, SRWD);
+writeb(ASPEED_FLASH_BASE, RDSR);
+r = readb(ASPEED_FLASH_BASE);
+spi_ctrl_stop_user();
+g_assert_cmphex(r & SRWD, ==, SRWD);
+
+/* WP# high and SRWD high -> status register writable */
+spi_ctrl_start_user();
+writeb(ASPEED_FLASH_BASE, WREN);
+/* test ability to write SRWD */
+writeb(ASPEED_FLASH_BASE, WRSR);
+writeb(ASPEED_FLASH_BASE, 0);
+writeb(ASPEED_FLASH_BASE, RDSR);
+r = readb(ASPEED_FLASH_BASE);
+spi_ctrl_stop_user();
+g_assert_cmphex(r & SRWD, ==, 0);
+
+/* WP# low and SRWD low -> status register writable */
+qtest_set_irq_in(global_qtest,
+ "/machine/soc/fmc/ssi.0/child[0]", "WP#", 0, 0);
+spi_ctrl_start_user();
+writeb(ASPEED_FLASH_BASE, WREN);
+/* test ability to write SRWD */
+writeb(ASPEED_FLASH_BASE, WRSR);
+writeb(ASPEED_FLASH_BASE, SRWD);
+writeb(ASPEED_FLASH_BASE, RDSR);
+r = readb(ASPEED_FLASH_BASE);
+spi_ctrl_stop_user();
+g_assert_cmphex(r & SRWD, ==, SRWD);
+
+/* WP# low and SRWD high -> status register NOT writable */
+spi_ctrl_start_user();
+writeb(ASPEED_FLASH_BASE, WREN);
+/* test ability to write SRWD */
+writeb(ASPEED_FLASH_BASE, WRSR);
+writeb(ASPEED_FLASH_BASE, 0);
+writeb(ASPEED_FLASH_BASE, RDSR);
+r = readb(ASPEED_FLASH_BASE);
+spi_ctrl_stop_user();
+/* write is not successful */
+g_assert_cmphex(r & SRWD, ==, SRWD);
+
+qtest_set_irq_in(global_qtest,
+ "/machine/soc/fmc/ssi.0/child[0]", "WP#", 0, 1);
+flash_reset();
+}
+
 static char tmp_path[] = "/tmp/qtest.m25p80.XX";
 
 int main(int argc, char **argv)
@@ -416,6 +476,8 @@ int main(int argc, char **argv)
 qtest_add_func("/ast2400/smc/read_page_mem", test_read_page_mem);
 qtest_add_func("/ast2400/smc/write_page_mem", test_write_page_mem);
 qtest_add_func("/ast2400/smc/read_status_reg", test_read_status_reg);
+qtest_add_func("/ast2400/smc/status_reg_write_protection",
+   test_status_reg_write_protection);
 
 ret = g_test_run();
 
-- 
2.30.2




[PATCH v3 1/2] hw: m25p80: add WP# pin and SRWD bit for write protection

2022-06-17 Thread Iris Chen
Signed-off-by: Iris Chen 
---
Thanks everyone for your comments. This is a v3 patch that addresses all 
suggestions (moving write_enable to decode_new_cmd). 
I am waiting on some feedback from Dan's (dz4l...@gmail.com) patch
regarding adding a STATE_STANDBY state. 

Currently, all tests are passing. 
 
 hw/block/m25p80.c | 77 ++-
 1 file changed, 62 insertions(+), 15 deletions(-)

diff --git a/hw/block/m25p80.c b/hw/block/m25p80.c
index 81ba3da4df..12a59ca57c 100644
--- a/hw/block/m25p80.c
+++ b/hw/block/m25p80.c
@@ -27,12 +27,14 @@
 #include "hw/qdev-properties.h"
 #include "hw/qdev-properties-system.h"
 #include "hw/ssi/ssi.h"
+#include "hw/irq.h"
 #include "migration/vmstate.h"
 #include "qemu/bitops.h"
 #include "qemu/log.h"
 #include "qemu/module.h"
 #include "qemu/error-report.h"
 #include "qapi/error.h"
+#include "qapi/visitor.h"
 #include "trace.h"
 #include "qom/object.h"
 
@@ -472,11 +474,13 @@ struct Flash {
 uint8_t spansion_cr2v;
 uint8_t spansion_cr3v;
 uint8_t spansion_cr4v;
+bool wp_level;
 bool write_enable;
 bool four_bytes_address_mode;
 bool reset_enable;
 bool quad_enable;
 bool aai_enable;
+bool status_register_write_disabled;
 uint8_t ear;
 
 int64_t dirty_page;
@@ -723,6 +727,8 @@ static void complete_collecting_data(Flash *s)
 flash_erase(s, s->cur_addr, s->cmd_in_progress);
 break;
 case WRSR:
+s->status_register_write_disabled = extract32(s->data[0], 7, 1);
+
 switch (get_man(s)) {
 case MAN_SPANSION:
 s->quad_enable = !!(s->data[1] & 0x02);
@@ -1165,22 +1171,34 @@ static void decode_new_cmd(Flash *s, uint32_t value)
 break;
 
 case WRSR:
-if (s->write_enable) {
-switch (get_man(s)) {
-case MAN_SPANSION:
-s->needed_bytes = 2;
-s->state = STATE_COLLECTING_DATA;
-break;
-case MAN_MACRONIX:
-s->needed_bytes = 2;
-s->state = STATE_COLLECTING_VAR_LEN_DATA;
-break;
-default:
-s->needed_bytes = 1;
-s->state = STATE_COLLECTING_DATA;
-}
-s->pos = 0;
+/*
+ * If WP# is low and status_register_write_disabled is high,
+ * status register writes are disabled.
+ * This is also called "hardware protected mode" (HPM). All other
+ * combinations of the two states are called "software protected mode"
+ * (SPM), and status register writes are permitted.
+ */
+if ((s->wp_level == 0 && s->status_register_write_disabled)
+|| !s->write_enable) {
+qemu_log_mask(LOG_GUEST_ERROR,
+  "M25P80: Status register write is disabled!\n");
+break;
 }
+
+switch (get_man(s)) {
+case MAN_SPANSION:
+s->needed_bytes = 2;
+s->state = STATE_COLLECTING_DATA;
+break;
+case MAN_MACRONIX:
+s->needed_bytes = 2;
+s->state = STATE_COLLECTING_VAR_LEN_DATA;
+break;
+default:
+s->needed_bytes = 1;
+s->state = STATE_COLLECTING_DATA;
+}
+s->pos = 0;
 break;
 
 case WRDI:
@@ -1195,6 +1213,8 @@ static void decode_new_cmd(Flash *s, uint32_t value)
 
 case RDSR:
 s->data[0] = (!!s->write_enable) << 1;
+s->data[0] |= (!!s->status_register_write_disabled) << 7;
+
 if (get_man(s) == MAN_MACRONIX || get_man(s) == MAN_ISSI) {
 s->data[0] |= (!!s->quad_enable) << 6;
 }
@@ -1484,6 +1504,14 @@ static uint32_t m25p80_transfer8(SSIPeripheral *ss, 
uint32_t tx)
 return r;
 }
 
+static void m25p80_write_protect_pin_irq_handler(void *opaque, int n, int 
level)
+{
+Flash *s = M25P80(opaque);
+/* WP# is just a single pin. */
+assert(n == 0);
+s->wp_level = !!level;
+}
+
 static void m25p80_realize(SSIPeripheral *ss, Error **errp)
 {
 Flash *s = M25P80(ss);
@@ -1515,12 +1543,18 @@ static void m25p80_realize(SSIPeripheral *ss, Error 
**errp)
 s->storage = blk_blockalign(NULL, s->size);
 memset(s->storage, 0xFF, s->size);
 }
+
+qdev_init_gpio_in_named(DEVICE(s),
+m25p80_write_protect_pin_irq_handler, "WP#", 1);
 }
 
 static void m25p80_reset(DeviceState *d)
 {
 Flash *s = M25P80(d);
 
+s->wp_level = true;
+s->status_register_write_disabled = false;
+
 reset_memory(s);
 }
 
@@ -1587,6 +1621,18 @@ static const VMStateDescription 
vmstate_m25p80_aai_enable = {
 

[PATCH v2 2/2] hw: m25p80: add tests for write protect

2022-06-08 Thread Iris Chen
Signed-off-by: Iris Chen 
---
Include the tests in a separate patch. Using qtest_set_irq_in() as per review.

 tests/qtest/aspeed_smc-test.c | 60 +++
 1 file changed, 60 insertions(+)

diff --git a/tests/qtest/aspeed_smc-test.c b/tests/qtest/aspeed_smc-test.c
index c5d97d4410..7786addfb8 100644
--- a/tests/qtest/aspeed_smc-test.c
+++ b/tests/qtest/aspeed_smc-test.c
@@ -392,6 +392,64 @@ static void test_read_status_reg(void)
 flash_reset();
 }
 
+static void test_status_reg_write_protection(void)
+{
+uint8_t r;
+
+spi_conf(CONF_ENABLE_W0);
+
+/* default case: WP# is high and SRWD is low -> status register writable */
+spi_ctrl_start_user();
+writeb(ASPEED_FLASH_BASE, WREN);
+/* test ability to write SRWD */
+writeb(ASPEED_FLASH_BASE, WRSR);
+writeb(ASPEED_FLASH_BASE, SRWD);
+writeb(ASPEED_FLASH_BASE, RDSR);
+r = readb(ASPEED_FLASH_BASE);
+spi_ctrl_stop_user();
+g_assert_cmphex(r & SRWD, ==, SRWD);
+
+/* WP# high and SRWD high -> status register writable */
+spi_ctrl_start_user();
+writeb(ASPEED_FLASH_BASE, WREN);
+/* test ability to write SRWD */
+writeb(ASPEED_FLASH_BASE, WRSR);
+writeb(ASPEED_FLASH_BASE, 0);
+writeb(ASPEED_FLASH_BASE, RDSR);
+r = readb(ASPEED_FLASH_BASE);
+spi_ctrl_stop_user();
+g_assert_cmphex(r & SRWD, ==, 0);
+
+/* WP# low and SRWD low -> status register writable */
+qtest_set_irq_in(global_qtest,
+ "/machine/soc/fmc/ssi.0/child[0]", "WP#", 0, 0);
+spi_ctrl_start_user();
+writeb(ASPEED_FLASH_BASE, WREN);
+/* test ability to write SRWD */
+writeb(ASPEED_FLASH_BASE, WRSR);
+writeb(ASPEED_FLASH_BASE, SRWD);
+writeb(ASPEED_FLASH_BASE, RDSR);
+r = readb(ASPEED_FLASH_BASE);
+spi_ctrl_stop_user();
+g_assert_cmphex(r & SRWD, ==, SRWD);
+
+/* WP# low and SRWD high -> status register NOT writable */
+spi_ctrl_start_user();
+writeb(ASPEED_FLASH_BASE, WREN);
+/* test ability to write SRWD */
+writeb(ASPEED_FLASH_BASE, WRSR);
+writeb(ASPEED_FLASH_BASE, 0);
+writeb(ASPEED_FLASH_BASE, RDSR);
+r = readb(ASPEED_FLASH_BASE);
+spi_ctrl_stop_user();
+/* write is not successful */
+g_assert_cmphex(r & SRWD, ==, SRWD);
+
+qtest_set_irq_in(global_qtest,
+ "/machine/soc/fmc/ssi.0/child[0]", "WP#", 0, 1);
+flash_reset();
+}
+
 static char tmp_path[] = "/tmp/qtest.m25p80.XX";
 
 int main(int argc, char **argv)
@@ -418,6 +476,8 @@ int main(int argc, char **argv)
 qtest_add_func("/ast2400/smc/read_page_mem", test_read_page_mem);
 qtest_add_func("/ast2400/smc/write_page_mem", test_write_page_mem);
 qtest_add_func("/ast2400/smc/read_status_reg", test_read_status_reg);
+qtest_add_func("/ast2400/smc/status_reg_write_protection",
+   test_status_reg_write_protection);
 
 ret = g_test_run();
 
-- 
2.30.2




[PATCH v2 1/2] hw: m25p80: add WP# pin and SRWD bit for write protection

2022-06-08 Thread Iris Chen
From: Iris Chen 

Signed-off-by: Iris Chen 
---
Addressed all comments from V1. The biggest change: removed 
object_class_property_add.

 hw/block/m25p80.c | 37 +++
 tests/qtest/aspeed_smc-test.c |  2 ++
 2 files changed, 39 insertions(+)

diff --git a/hw/block/m25p80.c b/hw/block/m25p80.c
index 81ba3da4df..1a20bd55d4 100644
--- a/hw/block/m25p80.c
+++ b/hw/block/m25p80.c
@@ -27,12 +27,14 @@
 #include "hw/qdev-properties.h"
 #include "hw/qdev-properties-system.h"
 #include "hw/ssi/ssi.h"
+#include "hw/irq.h"
 #include "migration/vmstate.h"
 #include "qemu/bitops.h"
 #include "qemu/log.h"
 #include "qemu/module.h"
 #include "qemu/error-report.h"
 #include "qapi/error.h"
+#include "qapi/visitor.h"
 #include "trace.h"
 #include "qom/object.h"
 
@@ -472,11 +474,13 @@ struct Flash {
 uint8_t spansion_cr2v;
 uint8_t spansion_cr3v;
 uint8_t spansion_cr4v;
+bool wp_level;
 bool write_enable;
 bool four_bytes_address_mode;
 bool reset_enable;
 bool quad_enable;
 bool aai_enable;
+bool status_register_write_disabled;
 uint8_t ear;
 
 int64_t dirty_page;
@@ -723,6 +727,21 @@ static void complete_collecting_data(Flash *s)
 flash_erase(s, s->cur_addr, s->cmd_in_progress);
 break;
 case WRSR:
+/*
+ * If WP# is low and status_register_write_disabled is high,
+ * status register writes are disabled.
+ * This is also called "hardware protected mode" (HPM). All other
+ * combinations of the two states are called "software protected mode"
+ * (SPM), and status register writes are permitted.
+ */
+if ((s->wp_level == 0 && s->status_register_write_disabled)
+|| !s->write_enable) {
+qemu_log_mask(LOG_GUEST_ERROR,
+  "M25P80: Status register write is disabled!\n");
+break;
+}
+s->status_register_write_disabled = extract32(s->data[0], 7, 1);
+
 switch (get_man(s)) {
 case MAN_SPANSION:
 s->quad_enable = !!(s->data[1] & 0x02);
@@ -1195,6 +1214,8 @@ static void decode_new_cmd(Flash *s, uint32_t value)
 
 case RDSR:
 s->data[0] = (!!s->write_enable) << 1;
+s->data[0] |= (!!s->status_register_write_disabled) << 7;
+
 if (get_man(s) == MAN_MACRONIX || get_man(s) == MAN_ISSI) {
 s->data[0] |= (!!s->quad_enable) << 6;
 }
@@ -1484,6 +1505,14 @@ static uint32_t m25p80_transfer8(SSIPeripheral *ss, 
uint32_t tx)
 return r;
 }
 
+static void m25p80_write_protect_pin_irq_handler(void *opaque, int n, int 
level)
+{
+Flash *s = M25P80(opaque);
+/* WP# is just a single pin. */
+assert(n == 0);
+s->wp_level = !!level;
+}
+
 static void m25p80_realize(SSIPeripheral *ss, Error **errp)
 {
 Flash *s = M25P80(ss);
@@ -1515,12 +1544,18 @@ static void m25p80_realize(SSIPeripheral *ss, Error 
**errp)
 s->storage = blk_blockalign(NULL, s->size);
 memset(s->storage, 0xFF, s->size);
 }
+
+qdev_init_gpio_in_named(DEVICE(s),
+m25p80_write_protect_pin_irq_handler, "WP#", 1);
 }
 
 static void m25p80_reset(DeviceState *d)
 {
 Flash *s = M25P80(d);
 
+s->wp_level = true;
+s->status_register_write_disabled = false;
+
 reset_memory(s);
 }
 
@@ -1601,6 +1636,8 @@ static const VMStateDescription vmstate_m25p80 = {
 VMSTATE_UINT8(needed_bytes, Flash),
 VMSTATE_UINT8(cmd_in_progress, Flash),
 VMSTATE_UINT32(cur_addr, Flash),
+VMSTATE_BOOL(wp_level, Flash),
+VMSTATE_BOOL(status_register_write_disabled, Flash),
 VMSTATE_BOOL(write_enable, Flash),
 VMSTATE_BOOL(reset_enable, Flash),
 VMSTATE_UINT8(ear, Flash),
diff --git a/tests/qtest/aspeed_smc-test.c b/tests/qtest/aspeed_smc-test.c
index ec233315e6..c5d97d4410 100644
--- a/tests/qtest/aspeed_smc-test.c
+++ b/tests/qtest/aspeed_smc-test.c
@@ -56,7 +56,9 @@ enum {
 BULK_ERASE = 0xc7,
 READ = 0x03,
 PP = 0x02,
+WRSR = 0x1,
 WREN = 0x6,
+SRWD = 0x80,
 RESET_ENABLE = 0x66,
 RESET_MEMORY = 0x99,
 EN_4BYTE_ADDR = 0xB7,
-- 
2.30.2




[PATCH 1/1] hw: m25p80: add W# pin and SRWD bit for write protection

2022-05-25 Thread Iris Chen
From: Iris Chen 

Add the W# pin and SRWD bit which control the status register write
ability.

Signed-off-by: Iris Chen 
---
 hw/block/m25p80.c | 72 +++
 tests/qtest/aspeed_smc-test.c | 62 ++
 2 files changed, 134 insertions(+)

diff --git a/hw/block/m25p80.c b/hw/block/m25p80.c
index 81ba3da4df..c845fa08d4 100644
--- a/hw/block/m25p80.c
+++ b/hw/block/m25p80.c
@@ -27,12 +27,14 @@
 #include "hw/qdev-properties.h"
 #include "hw/qdev-properties-system.h"
 #include "hw/ssi/ssi.h"
+#include "hw/irq.h"
 #include "migration/vmstate.h"
 #include "qemu/bitops.h"
 #include "qemu/log.h"
 #include "qemu/module.h"
 #include "qemu/error-report.h"
 #include "qapi/error.h"
+#include "qapi/visitor.h"
 #include "trace.h"
 #include "qom/object.h"
 
@@ -472,11 +474,13 @@ struct Flash {
 uint8_t spansion_cr2v;
 uint8_t spansion_cr3v;
 uint8_t spansion_cr4v;
+bool write_protect_pin;
 bool write_enable;
 bool four_bytes_address_mode;
 bool reset_enable;
 bool quad_enable;
 bool aai_enable;
+bool status_register_write_disabled;
 uint8_t ear;
 
 int64_t dirty_page;
@@ -723,6 +727,21 @@ static void complete_collecting_data(Flash *s)
 flash_erase(s, s->cur_addr, s->cmd_in_progress);
 break;
 case WRSR:
+/*
+ * If W# is low and status_register_write_disabled is high,
+ * status register writes are disabled.
+ * This is also called "hardware protected mode" (HPM). All other
+ * combinations of the two states are called "software protected mode"
+ * (SPM), and status register writes are permitted.
+ */
+if ((s->write_protect_pin == 0 && s->status_register_write_disabled)
+|| !s->write_enable) {
+qemu_log_mask(LOG_GUEST_ERROR,
+  "M25P80: Status register write is disabled!\n");
+break;
+}
+s->status_register_write_disabled = extract32(s->data[0], 7, 1);
+
 switch (get_man(s)) {
 case MAN_SPANSION:
 s->quad_enable = !!(s->data[1] & 0x02);
@@ -1195,6 +1214,8 @@ static void decode_new_cmd(Flash *s, uint32_t value)
 
 case RDSR:
 s->data[0] = (!!s->write_enable) << 1;
+s->data[0] |= (!!s->status_register_write_disabled) << 7;
+
 if (get_man(s) == MAN_MACRONIX || get_man(s) == MAN_ISSI) {
 s->data[0] |= (!!s->quad_enable) << 6;
 }
@@ -1484,6 +1505,15 @@ static uint32_t m25p80_transfer8(SSIPeripheral *ss, 
uint32_t tx)
 return r;
 }
 
+static void m25p80_write_protect_pin_irq_handler(void *opaque, int n, int 
level)
+{
+Flash *s = M25P80(opaque);
+bool wp = !!level;
+/* W# is just a single pin. */
+assert(n == 0);
+s->write_protect_pin = wp;
+}
+
 static void m25p80_realize(SSIPeripheral *ss, Error **errp)
 {
 Flash *s = M25P80(ss);
@@ -1515,12 +1545,18 @@ static void m25p80_realize(SSIPeripheral *ss, Error 
**errp)
 s->storage = blk_blockalign(NULL, s->size);
 memset(s->storage, 0xFF, s->size);
 }
+
+qdev_init_gpio_in_named(DEVICE(s),
+m25p80_write_protect_pin_irq_handler, "W#", 1);
 }
 
 static void m25p80_reset(DeviceState *d)
 {
 Flash *s = M25P80(d);
 
+s->write_protect_pin = true;
+s->status_register_write_disabled = false;
+
 reset_memory(s);
 }
 
@@ -1601,6 +1637,7 @@ static const VMStateDescription vmstate_m25p80 = {
 VMSTATE_UINT8(needed_bytes, Flash),
 VMSTATE_UINT8(cmd_in_progress, Flash),
 VMSTATE_UINT32(cur_addr, Flash),
+VMSTATE_BOOL(write_protect_pin, Flash),
 VMSTATE_BOOL(write_enable, Flash),
 VMSTATE_BOOL(reset_enable, Flash),
 VMSTATE_UINT8(ear, Flash),
@@ -1622,6 +1659,38 @@ static const VMStateDescription vmstate_m25p80 = {
 }
 };
 
+static void m25p80_get_write_protect_pin(Object *obj,
+   Visitor *v,
+   const char *name,
+   void *opaque,
+   Error **errp)
+{
+Flash *s = M25P80(obj);
+bool value;
+
+value = s->write_protect_pin;
+
+visit_type_bool(v, name, , errp);
+}
+
+static void m25p80_set_write_protect_pin(Object *obj,
+   Visitor *v,
+   const char *name,
+   void *opaque,
+   Error **errp)
+{
+Flash *s = M25P80(obj);
+bool value;
+qemu_irq w;
+
+if (!visit_type_bool(v, name, , errp)) {
+return;
+}
+
+w = qd

[PATCH 0/1] hw: m25p80: add W# pin and SRWD bit for write protection

2022-05-25 Thread Iris Chen
From: Iris Chen 

Hey everyone, 

My patch adds the W# pin and SRWD bit which work together to control the
status register write ability. 

Accordingly, when W# is low and SRWD bit is high, hardware protection
mode (HPM) is initiated. All other cases result in software protection. 

Acceptance tests have been added to verify all four scenarios: it tests
the ability to write to SRWD depending on whether write protection is
set. 

Thanks, 
Iris

Iris Chen (1):
  hw: m25p80: add W# pin and SRWD bit for write protection

 hw/block/m25p80.c | 72 +++
 tests/qtest/aspeed_smc-test.c | 62 ++
 2 files changed, 134 insertions(+)

-- 
2.30.2




[PATCH v3] hw: m25p80: allow write_enable latch get/set

2022-05-13 Thread Iris Chen via
The write_enable latch property is not currently exposed.
This commit makes it a modifiable property.

Signed-off-by: Iris Chen 
---
v3: Addressed comments by Peter and Cedric.
v2: Ran ./scripts/checkpatch.pl on the patch and added a description. Fixed 
comments regarding DEFINE_PROP_BOOL.

 hw/block/m25p80.c  |  1 +
 tests/qtest/aspeed_gpio-test.c | 40 +++
 tests/qtest/aspeed_smc-test.c  | 43 ++
 tests/qtest/libqtest.c | 24 +++
 tests/qtest/libqtest.h | 22 +
 5 files changed, 98 insertions(+), 32 deletions(-)

diff --git a/hw/block/m25p80.c b/hw/block/m25p80.c
index 430d1298a8..4283b990af 100644
--- a/hw/block/m25p80.c
+++ b/hw/block/m25p80.c
@@ -1558,6 +1558,7 @@ static int m25p80_pre_save(void *opaque)
 
 static Property m25p80_properties[] = {
 /* This is default value for Micron flash */
+DEFINE_PROP_BOOL("write-enable", Flash, write_enable, false),
 DEFINE_PROP_UINT32("nonvolatile-cfg", Flash, nonvolatile_cfg, 0x8FFF),
 DEFINE_PROP_UINT8("spansion-cr1nv", Flash, spansion_cr1nv, 0x0),
 DEFINE_PROP_UINT8("spansion-cr2nv", Flash, spansion_cr2nv, 0x8),
diff --git a/tests/qtest/aspeed_gpio-test.c b/tests/qtest/aspeed_gpio-test.c
index c1003f2d1b..bac63e8742 100644
--- a/tests/qtest/aspeed_gpio-test.c
+++ b/tests/qtest/aspeed_gpio-test.c
@@ -28,30 +28,6 @@
 #include "qapi/qmp/qdict.h"
 #include "libqtest-single.h"
 
-static bool qom_get_bool(QTestState *s, const char *path, const char *property)
-{
-QDict *r;
-bool b;
-
-r = qtest_qmp(s, "{ 'execute': 'qom-get', 'arguments': "
- "{ 'path': %s, 'property': %s } }", path, property);
-b = qdict_get_bool(r, "return");
-qobject_unref(r);
-
-return b;
-}
-
-static void qom_set_bool(QTestState *s, const char *path, const char *property,
- bool value)
-{
-QDict *r;
-
-r = qtest_qmp(s, "{ 'execute': 'qom-set', 'arguments': "
- "{ 'path': %s, 'property': %s, 'value': %i } }",
- path, property, value);
-qobject_unref(r);
-}
-
 static void test_set_colocated_pins(const void *data)
 {
 QTestState *s = (QTestState *)data;
@@ -60,14 +36,14 @@ static void test_set_colocated_pins(const void *data)
  * gpioV4-7 occupy bits within a single 32-bit value, so we want to make
  * sure that modifying one doesn't affect the other.
  */
-qom_set_bool(s, "/machine/soc/gpio", "gpioV4", true);
-qom_set_bool(s, "/machine/soc/gpio", "gpioV5", false);
-qom_set_bool(s, "/machine/soc/gpio", "gpioV6", true);
-qom_set_bool(s, "/machine/soc/gpio", "gpioV7", false);
-g_assert(qom_get_bool(s, "/machine/soc/gpio", "gpioV4"));
-g_assert(!qom_get_bool(s, "/machine/soc/gpio", "gpioV5"));
-g_assert(qom_get_bool(s, "/machine/soc/gpio", "gpioV6"));
-g_assert(!qom_get_bool(s, "/machine/soc/gpio", "gpioV7"));
+qtest_qom_set_bool(s, "/machine/soc/gpio", "gpioV4", true);
+qtest_qom_set_bool(s, "/machine/soc/gpio", "gpioV5", false);
+qtest_qom_set_bool(s, "/machine/soc/gpio", "gpioV6", true);
+qtest_qom_set_bool(s, "/machine/soc/gpio", "gpioV7", false);
+g_assert(qtest_qom_get_bool(s, "/machine/soc/gpio", "gpioV4"));
+g_assert(!qtest_qom_get_bool(s, "/machine/soc/gpio", "gpioV5"));
+g_assert(qtest_qom_get_bool(s, "/machine/soc/gpio", "gpioV6"));
+g_assert(!qtest_qom_get_bool(s, "/machine/soc/gpio", "gpioV7"));
 }
 
 int main(int argc, char **argv)
diff --git a/tests/qtest/aspeed_smc-test.c b/tests/qtest/aspeed_smc-test.c
index 87b40a0ef1..ec233315e6 100644
--- a/tests/qtest/aspeed_smc-test.c
+++ b/tests/qtest/aspeed_smc-test.c
@@ -26,6 +26,7 @@
 #include "qemu/osdep.h"
 #include "qemu/bswap.h"
 #include "libqtest-single.h"
+#include "qemu/bitops.h"
 
 /*
  * ASPEED SPI Controller registers
@@ -40,6 +41,7 @@
 #define   CTRL_FREADMODE   0x1
 #define   CTRL_WRITEMODE   0x2
 #define   CTRL_USERMODE0x3
+#define SR_WEL BIT(1)
 
 #define ASPEED_FMC_BASE0x1E62
 #define ASPEED_FLASH_BASE  0x2000
@@ -49,6 +51,8 @@
  */
 enum {
 JEDEC_READ = 0x9f,
+RDSR = 0x5,
+WRDI = 0x4,
 BULK_ERASE = 0xc7,
 READ = 0x03,
 PP = 0x02,
@@ -348,6 +352,44 @@ static void test_write_page_mem(void)
 flash_reset();
 }
 
+static void test_read_status_reg(void)
+{
+uint8_t r;
+
+spi_conf(CONF_ENABLE_W0);
+
+spi_ctrl_start_user();
+writeb(ASPEED_FLASH_BASE, RDSR)

[PATCH v2] hw: m25p80: allow write_enable latch get/set

2022-05-11 Thread Iris Chen via
The write_enable latch property is not currently exposed.
This commit makes it a modifiable property using get/set methods.

Signed-off-by: Iris Chen 
---
Ran ./scripts/checkpatch.pl on the patch and added a description. Fixed 
comments regarding DEFINE_PROP_BOOL.

 hw/block/m25p80.c |  2 ++
 tests/qtest/aspeed_smc-test.c | 23 +++
 2 files changed, 25 insertions(+)

diff --git a/hw/block/m25p80.c b/hw/block/m25p80.c
index 430d1298a8..019beb5b78 100644
--- a/hw/block/m25p80.c
+++ b/hw/block/m25p80.c
@@ -35,6 +35,7 @@
 #include "qapi/error.h"
 #include "trace.h"
 #include "qom/object.h"
+#include "qapi/visitor.h"
 
 /* Fields for FlashPartInfo->flags */
 
@@ -1558,6 +1559,7 @@ static int m25p80_pre_save(void *opaque)
 
 static Property m25p80_properties[] = {
 /* This is default value for Micron flash */
+DEFINE_PROP_BOOL("write-enable", Flash, write_enable, false),
 DEFINE_PROP_UINT32("nonvolatile-cfg", Flash, nonvolatile_cfg, 0x8FFF),
 DEFINE_PROP_UINT8("spansion-cr1nv", Flash, spansion_cr1nv, 0x0),
 DEFINE_PROP_UINT8("spansion-cr2nv", Flash, spansion_cr2nv, 0x8),
diff --git a/tests/qtest/aspeed_smc-test.c b/tests/qtest/aspeed_smc-test.c
index 87b40a0ef1..fcc156bc00 100644
--- a/tests/qtest/aspeed_smc-test.c
+++ b/tests/qtest/aspeed_smc-test.c
@@ -26,6 +26,7 @@
 #include "qemu/osdep.h"
 #include "qemu/bswap.h"
 #include "libqtest-single.h"
+#include "qemu/bitops.h"
 
 /*
  * ASPEED SPI Controller registers
@@ -40,6 +41,7 @@
 #define   CTRL_FREADMODE   0x1
 #define   CTRL_WRITEMODE   0x2
 #define   CTRL_USERMODE0x3
+#define SR_WEL BIT(1)
 
 #define ASPEED_FMC_BASE0x1E62
 #define ASPEED_FLASH_BASE  0x2000
@@ -49,6 +51,7 @@
  */
 enum {
 JEDEC_READ = 0x9f,
+RDSR = 0x5,
 BULK_ERASE = 0xc7,
 READ = 0x03,
 PP = 0x02,
@@ -348,6 +351,25 @@ static void test_write_page_mem(void)
 flash_reset();
 }
 
+static void test_read_status_reg(void)
+{
+uint8_t r;
+
+qmp("{ 'execute': 'qom-set', 'arguments': "
+   "{'path': '/machine/soc/fmc/ssi.0/child[0]', 'property': 'WEL', 
'value': true}}");
+
+
+spi_conf(CONF_ENABLE_W0);
+spi_ctrl_start_user();
+writeb(ASPEED_FLASH_BASE, RDSR);
+r = readb(ASPEED_FLASH_BASE);
+spi_ctrl_stop_user();
+
+g_assert_cmphex(r & SR_WEL, ==, SR_WEL);
+
+flash_reset();
+}
+
 static char tmp_path[] = "/tmp/qtest.m25p80.XX";
 
 int main(int argc, char **argv)
@@ -373,6 +395,7 @@ int main(int argc, char **argv)
 qtest_add_func("/ast2400/smc/write_page", test_write_page);
 qtest_add_func("/ast2400/smc/read_page_mem", test_read_page_mem);
 qtest_add_func("/ast2400/smc/write_page_mem", test_write_page_mem);
+qtest_add_func("/ast2400/smc/read_status_reg", test_read_status_reg);
 
 ret = g_test_run();
 
-- 
2.30.2




[PATCH 0/1] hw: allow write_enable latch get/set

2022-05-11 Thread Iris Chen via
Hey everyone, 

My patch adds the get/set property for the write_enable latch. Currently, it is 
not an exposed property but this change adds QOM visibility and makes this 
property modifiable. 

Acceptance tests have also been added to verify the value in the status 
register (after the value is set). 

Thanks, 
Iris

Iris Chen (1):
  hw: allow write_enable latch get/set

 hw/block/m25p80.c | 30 ++
 tests/qtest/aspeed_smc-test.c | 20 
 2 files changed, 50 insertions(+)

-- 
2.30.2




[PATCH 1/1] hw: allow write_enable latch get/set

2022-05-11 Thread Iris Chen via
---
 hw/block/m25p80.c | 30 ++
 tests/qtest/aspeed_smc-test.c | 20 
 2 files changed, 50 insertions(+)

diff --git a/hw/block/m25p80.c b/hw/block/m25p80.c
index 430d1298a8..fb72704e5a 100644
--- a/hw/block/m25p80.c
+++ b/hw/block/m25p80.c
@@ -35,6 +35,7 @@
 #include "qapi/error.h"
 #include "trace.h"
 #include "qom/object.h"
+#include "qapi/visitor.h"
 
 /* Fields for FlashPartInfo->flags */
 
@@ -1646,6 +1647,31 @@ static const VMStateDescription vmstate_m25p80 = {
 }
 };
 
+static void m25p80_get_wel(Object *obj, Visitor *v, const char *name,
+void *opaque, Error **errp)
+{
+Flash *s = M25P80(obj);
+
+assert(strcmp(name, "WEL") == 0);
+
+visit_type_bool(v, name, >write_enable, errp);
+}
+
+static void m25p80_set_wel(Object *obj, Visitor *v, const char *name,
+void *opaque, Error **errp)
+{
+Flash *s = M25P80(obj);
+bool value;
+
+assert(strcmp(name, "WEL") == 0);
+
+if (!visit_type_bool(v, name, , errp)) {
+return;
+}
+
+s->write_enable = value;
+}
+
 static void m25p80_class_init(ObjectClass *klass, void *data)
 {
 DeviceClass *dc = DEVICE_CLASS(klass);
@@ -1660,6 +1686,10 @@ static void m25p80_class_init(ObjectClass *klass, void 
*data)
 device_class_set_props(dc, m25p80_properties);
 dc->reset = m25p80_reset;
 mc->pi = data;
+
+object_class_property_add(klass, "WEL", "bool",
+m25p80_get_wel,
+m25p80_set_wel, NULL, NULL);
 }
 
 static const TypeInfo m25p80_info = {
diff --git a/tests/qtest/aspeed_smc-test.c b/tests/qtest/aspeed_smc-test.c
index 87b40a0ef1..af885a9c9d 100644
--- a/tests/qtest/aspeed_smc-test.c
+++ b/tests/qtest/aspeed_smc-test.c
@@ -49,6 +49,7 @@
  */
 enum {
 JEDEC_READ = 0x9f,
+RDSR = 0x5,
 BULK_ERASE = 0xc7,
 READ = 0x03,
 PP = 0x02,
@@ -348,6 +349,24 @@ static void test_write_page_mem(void)
 flash_reset();
 }
 
+static void test_read_status_reg(void)
+{
+uint8_t r;
+
+   qmp("{ 'execute': 'qom-set', 'arguments': "
+   "{'path': '/machine/soc/fmc/ssi.0/child[0]', 'property': 'WEL', 
'value': true}}");
+
+spi_conf(CONF_ENABLE_W0);
+   spi_ctrl_start_user();
+   writeb(ASPEED_FLASH_BASE, RDSR);
+   r = readb(ASPEED_FLASH_BASE);
+   spi_ctrl_stop_user();
+
+   g_assert_cmphex(r, ==, 0x2);
+
+   flash_reset();
+}
+
 static char tmp_path[] = "/tmp/qtest.m25p80.XX";
 
 int main(int argc, char **argv)
@@ -373,6 +392,7 @@ int main(int argc, char **argv)
 qtest_add_func("/ast2400/smc/write_page", test_write_page);
 qtest_add_func("/ast2400/smc/read_page_mem", test_read_page_mem);
 qtest_add_func("/ast2400/smc/write_page_mem", test_write_page_mem);
+qtest_add_func("/ast2400/smc/read_status_reg", test_read_status_reg);
 
 ret = g_test_run();
 
-- 
2.30.2




[PATCH 1/1] hw: allow write_enable latch get/set

2022-05-11 Thread Iris Chen via
---
 hw/block/m25p80.c | 30 ++
 tests/qtest/aspeed_smc-test.c | 20 
 2 files changed, 50 insertions(+)

diff --git a/hw/block/m25p80.c b/hw/block/m25p80.c
index 430d1298a8..fb72704e5a 100644
--- a/hw/block/m25p80.c
+++ b/hw/block/m25p80.c
@@ -35,6 +35,7 @@
 #include "qapi/error.h"
 #include "trace.h"
 #include "qom/object.h"
+#include "qapi/visitor.h"
 
 /* Fields for FlashPartInfo->flags */
 
@@ -1646,6 +1647,31 @@ static const VMStateDescription vmstate_m25p80 = {
 }
 };
 
+static void m25p80_get_wel(Object *obj, Visitor *v, const char *name,
+void *opaque, Error **errp)
+{
+Flash *s = M25P80(obj);
+
+assert(strcmp(name, "WEL") == 0);
+
+visit_type_bool(v, name, >write_enable, errp);
+}
+
+static void m25p80_set_wel(Object *obj, Visitor *v, const char *name,
+void *opaque, Error **errp)
+{
+Flash *s = M25P80(obj);
+bool value;
+
+assert(strcmp(name, "WEL") == 0);
+
+if (!visit_type_bool(v, name, , errp)) {
+return;
+}
+
+s->write_enable = value;
+}
+
 static void m25p80_class_init(ObjectClass *klass, void *data)
 {
 DeviceClass *dc = DEVICE_CLASS(klass);
@@ -1660,6 +1686,10 @@ static void m25p80_class_init(ObjectClass *klass, void 
*data)
 device_class_set_props(dc, m25p80_properties);
 dc->reset = m25p80_reset;
 mc->pi = data;
+
+object_class_property_add(klass, "WEL", "bool",
+m25p80_get_wel,
+m25p80_set_wel, NULL, NULL);
 }
 
 static const TypeInfo m25p80_info = {
diff --git a/tests/qtest/aspeed_smc-test.c b/tests/qtest/aspeed_smc-test.c
index 87b40a0ef1..af885a9c9d 100644
--- a/tests/qtest/aspeed_smc-test.c
+++ b/tests/qtest/aspeed_smc-test.c
@@ -49,6 +49,7 @@
  */
 enum {
 JEDEC_READ = 0x9f,
+RDSR = 0x5,
 BULK_ERASE = 0xc7,
 READ = 0x03,
 PP = 0x02,
@@ -348,6 +349,24 @@ static void test_write_page_mem(void)
 flash_reset();
 }
 
+static void test_read_status_reg(void)
+{
+uint8_t r;
+
+   qmp("{ 'execute': 'qom-set', 'arguments': "
+   "{'path': '/machine/soc/fmc/ssi.0/child[0]', 'property': 'WEL', 
'value': true}}");
+
+spi_conf(CONF_ENABLE_W0);
+   spi_ctrl_start_user();
+   writeb(ASPEED_FLASH_BASE, RDSR);
+   r = readb(ASPEED_FLASH_BASE);
+   spi_ctrl_stop_user();
+
+   g_assert_cmphex(r, ==, 0x2);
+
+   flash_reset();
+}
+
 static char tmp_path[] = "/tmp/qtest.m25p80.XX";
 
 int main(int argc, char **argv)
@@ -373,6 +392,7 @@ int main(int argc, char **argv)
 qtest_add_func("/ast2400/smc/write_page", test_write_page);
 qtest_add_func("/ast2400/smc/read_page_mem", test_read_page_mem);
 qtest_add_func("/ast2400/smc/write_page_mem", test_write_page_mem);
+qtest_add_func("/ast2400/smc/read_status_reg", test_read_status_reg);
 
 ret = g_test_run();
 
-- 
2.30.2




[PATCH 0/1] hw: allow write_enable latch get/set

2022-05-11 Thread Iris Chen via
Hey everyone, 

My patch adds the get/set property for the write_enable latch. Currently, it is 
not an exposed property but this change adds QOM visibility and makes this 
property modifiable. 

Acceptance tests have also been added to verify the value in the status 
register (after the value is set). 

Thanks, 
Iris

Iris Chen (1):
  hw: allow write_enable latch get/set

 hw/block/m25p80.c | 30 ++
 tests/qtest/aspeed_smc-test.c | 20 
 2 files changed, 50 insertions(+)

-- 
2.30.2