| // 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; |
| } |