linux-gbmc: Add RoT mailbox backoff to everspin When writing to the 64KB RAM-backed RoT mailbox region on Google Hoth/Dauntless SPI NOR parts (at 0x700000 on 32MB hothb2-512 parts, or the top 64KB on 64MB/128MB parts), the RoT intercepts the write and holds the flash busy (SR_WIP) for several milliseconds while processing the mailbox command. Tight status register polling in spi_nor_wait_till_ready causes excessive SPI bus traffic and CPU usage. Override the MTD _write callback in everspin.c so that status polling on pages within the 64KB mailbox region uses an exponential backoff sleep (250us doubling up to 20ms), while non-mailbox flash writes continue using standard spi_nor_wait_till_ready() without backoff delay. Tested: Verified on hardware that hothd mailbox transactions complete with minimal SPI transfers instead of thousands of tight SR polls, while non-mailbox flash operations remain unaffected. Fusion-Link: fusion2 N/A Google-Bug-Id: 565629335 Change-Id: I17eb681995d38bbcdf4d6015159cb2d9f5eeb13d Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/recipes-kernel/linux/files/linux-gbmc/drivers/mtd/spi-nor/everspin.c b/recipes-kernel/linux/files/linux-gbmc/drivers/mtd/spi-nor/everspin.c index ae815af..58afbbd 100644 --- a/recipes-kernel/linux/files/linux-gbmc/drivers/mtd/spi-nor/everspin.c +++ b/recipes-kernel/linux/files/linux-gbmc/drivers/mtd/spi-nor/everspin.c
@@ -1,6 +1,10 @@ // We replace the everspin spi nor file as it has a part that we will // never use with gBMC systems. +#include <linux/delay.h> +#include <linux/jiffies.h> +#include <linux/mtd/mtd.h> #include <linux/mtd/spi-nor.h> +#include <linux/sizes.h> #include <linux/version.h> #include "core.h" @@ -26,8 +30,105 @@ GINFO("dauntlessd2-2048", SNOR_ID(0x26, 0x22, 0x1b), 2048), }; +#define HOTH_MBOX_SIZE 0x10000 +#define HOTH_MBOX_OFFSET_32M 0x700000 +#define MBOX_WAIT_JIFFIES (40UL * HZ) + +static inline bool google_is_mbox_addr(struct mtd_info *mtd, loff_t addr) +{ + loff_t mbox_offset; + + if (mtd->size == SZ_32M) + mbox_offset = HOTH_MBOX_OFFSET_32M; + else if (mtd->size > HOTH_MBOX_SIZE) + mbox_offset = mtd->size - HOTH_MBOX_SIZE; + else + return false; + + return addr >= mbox_offset && addr < mbox_offset + HOTH_MBOX_SIZE; +} + +static int google_nor_wait_till_ready_backoff(struct spi_nor *nor) +{ + unsigned long deadline = jiffies + MBOX_WAIT_JIFFIES; + unsigned int backoff_us = 250; + int timeout = 0, ret; + + while (!timeout) { + if (time_after_eq(jiffies, deadline)) + timeout = 1; + + ret = spi_nor_read_sr(nor, nor->bouncebuf); + if (ret) + return ret; + if (!(nor->bouncebuf[0] & SR_WIP)) + return 0; + if (timeout) + break; + + if (backoff_us < 20000) { + usleep_range(backoff_us, backoff_us * 3 / 2); + backoff_us = min(backoff_us * 2, 20000U); + } else { + msleep(20); + } + } + + dev_dbg(nor->dev, "flash mailbox operation timed out\n"); + return -ETIMEDOUT; +} + +static int google_nor_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + struct spi_nor *nor = mtd_to_spi_nor(mtd); + u32 page_size = nor->params->page_size; + size_t i; + ssize_t ret; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6,4,0) + ret = spi_nor_lock_and_prep(nor); +#else + ret = spi_nor_prep_and_lock(nor); +#endif + if (ret) + return ret; + + for (i = 0; i < len; ) { + ssize_t written; + loff_t addr = to + i; + size_t page_offset = addr & (page_size - 1); + size_t page_remain = min_t(size_t, page_size - page_offset, + len - i); + + ret = spi_nor_write_enable(nor); + if (ret) + goto write_err; + + ret = spi_nor_write_data(nor, addr, page_remain, buf + i); + if (ret < 0) + goto write_err; + written = ret; + + if (google_is_mbox_addr(mtd, addr)) + ret = google_nor_wait_till_ready_backoff(nor); + else + ret = spi_nor_wait_till_ready(nor); + if (ret) + goto write_err; + + *retlen += written; + i += written; + } + +write_err: + spi_nor_unlock_and_unprep(nor); + return ret; +} + static void google_nor_default_init(struct spi_nor *nor) { + nor->mtd._write = google_nor_write; nor->params->quad_enable = spi_nor_sr1_bit6_quad_enable; #if LINUX_VERSION_CODE < KERNEL_VERSION(6,4,0) nor->params->set_4byte_addr_mode = spi_nor_set_4byte_addr_mode;