espi-control: Add program to disable eSPI
This commit introduces a program that can disable and re-enable eSPI on
a Nuvoton BMC.
Tested:
- Ran "npcm7xx_espi_control -d", and confirmed that IPMI stopped
working.
- Ran "npcm7xx_espi_control", and confirmed that IPMI started working
again.
Change-Id: I9637ac4d84b82931f107946ff53e7ef16a965d25
Signed-off-by: John Wedig <johnwedig@google.com>
diff --git a/subprojects/espi-control/meson.build b/subprojects/espi-control/meson.build
new file mode 100644
index 0000000..9e45228
--- /dev/null
+++ b/subprojects/espi-control/meson.build
@@ -0,0 +1,23 @@
+project(
+ 'espi_control',
+ 'cpp',
+ version: '0.1',
+ meson_version: '>=1.1.1',
+ default_options: [
+ 'warning_level=3',
+ 'werror=true',
+ 'cpp_std=c++23',
+ ],
+)
+
+executable(
+ 'npcm7xx-espi-control',
+ 'npcm7xx_espi_control.cpp',
+ implicit_include_directories: false,
+ dependencies:
+ [
+ dependency('stdplus'),
+ ],
+ install: true,
+ install_dir: get_option('libexecdir'),
+)
diff --git a/subprojects/espi-control/npcm7xx_espi_control.cpp b/subprojects/espi-control/npcm7xx_espi_control.cpp
new file mode 100644
index 0000000..661a48c
--- /dev/null
+++ b/subprojects/espi-control/npcm7xx_espi_control.cpp
@@ -0,0 +1,227 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/*
+ * Disclaimer: This binary is only intended to be used on Nuvoton NPCM7xx BMCs.
+ *
+ * It could also be extended to support NPCM8xx, but it hasn't been tested with
+ * that model BMC.
+ *
+ * This binary is NOT intended to support Aspeed BMCs.
+ */
+
+#include <unistd.h>
+
+#include <stdplus/fd/create.hpp>
+#include <stdplus/fd/intf.hpp>
+#include <stdplus/fd/managed.hpp>
+#include <stdplus/fd/mmap.hpp>
+
+#include <cassert>
+#include <cstdint>
+#include <cstdio>
+#include <format>
+#include <stdexcept>
+
+/* Base address for Nuvoton's global control register space. */
+#define NPCM7XX_GLOBAL_CTRL_BASE_ADDR 0xF0800000
+/* Offset of the PDID register and expected PDID value. */
+#define PDID_OFFSET 0x00
+#define NPCM7XX_PDID 0x04A92750
+
+/* Register width in bytes. */
+#define REGISTER_WIDTH 4
+
+/* Base address for Nuvoton's eSPI register space. */
+#define NPCM7XX_ESPI_BASE_ADDR 0xF009F000
+/*
+ * Offset of the eSPI config (ESPICFG) register, along with host channel enable
+ * mask and core channel enable mask.
+ */
+#define ESPICFG_OFFSET 0x4
+#define ESPICFG_HOST_CHANNEL_ENABLE_MASK 0xF0
+#define ESPICFG_CORE_CHANNEL_ENABLE_MASK 0x0F
+/*
+ * Offset of the host independence (ESPIHINDP) register and automatic ready bit
+ * mask.
+ */
+#define ESPIHINDP_OFFSET 0x80
+#define ESPI_AUTO_READY_MASK 0xF
+
+namespace
+{
+
+using stdplus::fd::MMapAccess;
+using stdplus::fd::MMapFlags;
+using stdplus::fd::OpenAccess;
+using stdplus::fd::OpenFlag;
+using stdplus::fd::OpenFlags;
+using stdplus::fd::ProtFlag;
+using stdplus::fd::ProtFlags;
+
+static void usage(char* name)
+{
+ std::fprintf(stderr, "Usage: %s [-d]\n", name);
+ std::fprintf(stderr, "Enable or disable eSPI bus on NPCM7XX BMC\n");
+ std::fprintf(stderr, "This program will enable eSPI by default, unless "
+ "the -d option is used.\n");
+ std::fprintf(stderr, " -d Disable eSPI\n");
+}
+
+/*
+ * Get a pointer to the register at the given offset, within the provided
+ * memory-mapped I/O space.
+ */
+static inline volatile uint32_t* getReg(stdplus::fd::MMap& map,
+ size_t regOffset)
+{
+ uintptr_t regPtr = reinterpret_cast<uintptr_t>(map.get().data()) +
+ regOffset;
+ /* Make sure the register pointer is properly aligned. */
+ assert((regPtr & ~(REGISTER_WIDTH - 1)) == regPtr);
+
+ return reinterpret_cast<volatile uint32_t*>(regPtr);
+}
+
+static void modifyESPIRegisters(bool disable)
+{
+ /*
+ * We need to make sure this is running on a Nuvoton BMC. To do that, we'll
+ * read the product identification (PDID) register.
+ */
+
+ /* Find the page that includes the Product ID register. */
+ size_t pageSize = sysconf(_SC_PAGE_SIZE);
+ size_t pageBase = NPCM7XX_GLOBAL_CTRL_BASE_ADDR / pageSize * pageSize;
+ size_t pageOffset = NPCM7XX_GLOBAL_CTRL_BASE_ADDR - pageBase;
+ size_t mapLength = pageOffset + PDID_OFFSET + REGISTER_WIDTH;
+
+ auto fd = stdplus::fd::open(
+ "/dev/mem", OpenFlags(OpenAccess::ReadWrite).set(OpenFlag::Sync));
+ stdplus::fd::MMap pdidMap(fd, mapLength, ProtFlags().set(ProtFlag::Read),
+ MMapFlags(MMapAccess::Shared), pageBase);
+
+ volatile uint32_t* const pdidReg = getReg(pdidMap,
+ pageOffset + PDID_OFFSET);
+
+ /*
+ * Read the PDID register to make sure we're running on a Nuvoton NPCM7xx
+ * BMC.
+ * Note: This binary would probably work on NPCM8xx, as well, if we also
+ * allowed the NPCM8xx PDID, since the register addresses are the same. But
+ * that hasn't been tested.
+ */
+ if (*pdidReg != NPCM7XX_PDID)
+ {
+ throw std::runtime_error(
+ std::format("Unexpected product ID {:#x} != {:#x}", *pdidReg,
+ NPCM7XX_PDID)
+ .data());
+ }
+
+ /*
+ * Find the start of the page that includes the start of the eSPI register
+ * space.
+ */
+ pageBase = NPCM7XX_ESPI_BASE_ADDR / pageSize * pageSize;
+ pageOffset = NPCM7XX_ESPI_BASE_ADDR - pageBase;
+
+ mapLength = pageOffset + ESPIHINDP_OFFSET + REGISTER_WIDTH;
+
+ stdplus::fd::MMap espiMap(
+ fd, mapLength, ProtFlags().set(ProtFlag::Read).set(ProtFlag::Write),
+ MMapFlags(MMapAccess::Shared), pageBase);
+
+ /* Read the ESPICFG register. */
+ volatile uint32_t* const espicfgReg = getReg(espiMap,
+ pageOffset + ESPICFG_OFFSET);
+ uint32_t espicfgValue = *espicfgReg;
+
+ if (disable)
+ {
+ /*
+ * Check if the automatic ready bits are set in the eSPI host
+ * independence register (ESPIHINDP).
+ */
+ volatile uint32_t* const espihindpReg =
+ getReg(espiMap, pageOffset + ESPIHINDP_OFFSET);
+ uint32_t espihindpValue = *espihindpReg;
+ if (espihindpValue & ESPI_AUTO_READY_MASK)
+ {
+ /*
+ * If any of the automatic ready bits are set, we need to disable
+ * them, using several steps:
+ * - Make sure the host channel enable and core channel bits are
+ * consistent, in the ESPICFG register, i.e. copy the host
+ * channel enable bits to the core channel enable bits.
+ * - Clear the automatic ready bits in ESPIHINDP.
+ */
+ uint32_t hostChannelEnableBits = espicfgValue &
+ ESPICFG_HOST_CHANNEL_ENABLE_MASK;
+ espicfgValue |= (hostChannelEnableBits >> 4);
+ *espicfgReg = espicfgValue;
+
+ espihindpValue &= ~ESPI_AUTO_READY_MASK;
+ *espihindpReg = espihindpValue;
+ }
+
+ /* Now disable the core channel enable bits in ESPICFG. */
+ espicfgValue &= ~ESPICFG_CORE_CHANNEL_ENABLE_MASK;
+ *espicfgReg = espicfgValue;
+
+ fprintf(stderr, "Disabled eSPI bus\n");
+ }
+ else
+ {
+ /* Enable eSPI by setting the core channel enable bits in ESPICFG. */
+ espicfgValue |= ESPICFG_CORE_CHANNEL_ENABLE_MASK;
+ *espicfgReg = espicfgValue;
+
+ fprintf(stderr, "Enabled eSPI bus\n");
+ }
+}
+
+} // namespace
+
+int main(int argc, char** argv)
+{
+ int opt;
+ bool disable = false;
+ while ((opt = getopt(argc, argv, "d")) != -1)
+ {
+ switch (opt)
+ {
+ case 'd':
+ disable = true;
+ break;
+ default:
+ usage(argv[0]);
+ return -1;
+ }
+ }
+
+ try
+ {
+ /* Update registers to enable or disable eSPI. */
+ modifyESPIRegisters(disable);
+ }
+ catch (const std::exception& e)
+ {
+ fprintf(stderr, "Failed to %s eSPI bus: %s\n",
+ disable ? "disable" : "enable", e.what());
+ return -1;
+ }
+
+ return 0;
+}