1 // Copyright 2023 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 /* 16 * Disclaimer: This binary is only intended to be used on Nuvoton NPCM7xx BMCs. 17 * 18 * It could also be extended to support NPCM8xx, but it hasn't been tested with 19 * that model BMC. 20 * 21 * This binary is NOT intended to support Aspeed BMCs. 22 */ 23 24 #include <unistd.h> 25 26 #include <stdplus/fd/create.hpp> 27 #include <stdplus/fd/intf.hpp> 28 #include <stdplus/fd/managed.hpp> 29 #include <stdplus/fd/mmap.hpp> 30 31 #include <cassert> 32 #include <cstdint> 33 #include <cstdio> 34 #include <format> 35 #include <stdexcept> 36 37 /* Base address for Nuvoton's global control register space. */ 38 #define NPCM7XX_GLOBAL_CTRL_BASE_ADDR 0xF0800000 39 /* Offset of the PDID register and expected PDID value. */ 40 #define PDID_OFFSET 0x00 41 #define NPCM7XX_PDID 0x04A92750 42 43 /* Register width in bytes. */ 44 #define REGISTER_WIDTH 4 45 46 /* Base address for Nuvoton's eSPI register space. */ 47 #define NPCM7XX_ESPI_BASE_ADDR 0xF009F000 48 /* 49 * Offset of the eSPI config (ESPICFG) register, along with host channel enable 50 * mask and core channel enable mask. 51 */ 52 #define ESPICFG_OFFSET 0x4 53 #define ESPICFG_HOST_CHANNEL_ENABLE_MASK 0xF0 54 #define ESPICFG_CORE_CHANNEL_ENABLE_MASK 0x0F 55 /* 56 * Offset of the host independence (ESPIHINDP) register and automatic ready bit 57 * mask. 58 */ 59 #define ESPIHINDP_OFFSET 0x80 60 #define ESPI_AUTO_READY_MASK 0xF 61 62 namespace 63 { 64 65 using stdplus::fd::MMapAccess; 66 using stdplus::fd::MMapFlags; 67 using stdplus::fd::OpenAccess; 68 using stdplus::fd::OpenFlag; 69 using stdplus::fd::OpenFlags; 70 using stdplus::fd::ProtFlag; 71 using stdplus::fd::ProtFlags; 72 73 static void usage(char* name) 74 { 75 std::fprintf(stderr, "Usage: %s [-d]\n", name); 76 std::fprintf(stderr, "Enable or disable eSPI bus on NPCM7XX BMC\n"); 77 std::fprintf(stderr, "This program will enable eSPI by default, unless " 78 "the -d option is used.\n"); 79 std::fprintf(stderr, " -d Disable eSPI\n"); 80 } 81 82 /* 83 * Get a pointer to the register at the given offset, within the provided 84 * memory-mapped I/O space. 85 */ 86 static inline volatile uint32_t* getReg(stdplus::fd::MMap& map, 87 size_t regOffset) 88 { 89 uintptr_t regPtr = reinterpret_cast<uintptr_t>(map.get().data()) + 90 regOffset; 91 /* Make sure the register pointer is properly aligned. */ 92 assert((regPtr & ~(REGISTER_WIDTH - 1)) == regPtr); 93 94 return reinterpret_cast<volatile uint32_t*>(regPtr); 95 } 96 97 static void modifyESPIRegisters(bool disable) 98 { 99 /* 100 * We need to make sure this is running on a Nuvoton BMC. To do that, we'll 101 * read the product identification (PDID) register. 102 */ 103 104 /* Find the page that includes the Product ID register. */ 105 size_t pageSize = sysconf(_SC_PAGE_SIZE); 106 size_t pageBase = NPCM7XX_GLOBAL_CTRL_BASE_ADDR / pageSize * pageSize; 107 size_t pageOffset = NPCM7XX_GLOBAL_CTRL_BASE_ADDR - pageBase; 108 size_t mapLength = pageOffset + PDID_OFFSET + REGISTER_WIDTH; 109 110 auto fd = stdplus::fd::open( 111 "/dev/mem", OpenFlags(OpenAccess::ReadWrite).set(OpenFlag::Sync)); 112 stdplus::fd::MMap pdidMap(fd, mapLength, ProtFlags().set(ProtFlag::Read), 113 MMapFlags(MMapAccess::Shared), pageBase); 114 115 volatile uint32_t* const pdidReg = getReg(pdidMap, 116 pageOffset + PDID_OFFSET); 117 118 /* 119 * Read the PDID register to make sure we're running on a Nuvoton NPCM7xx 120 * BMC. 121 * Note: This binary would probably work on NPCM8xx, as well, if we also 122 * allowed the NPCM8xx PDID, since the register addresses are the same. But 123 * that hasn't been tested. 124 */ 125 if (*pdidReg != NPCM7XX_PDID) 126 { 127 throw std::runtime_error( 128 std::format("Unexpected product ID {:#x} != {:#x}", *pdidReg, 129 NPCM7XX_PDID) 130 .data()); 131 } 132 133 /* 134 * Find the start of the page that includes the start of the eSPI register 135 * space. 136 */ 137 pageBase = NPCM7XX_ESPI_BASE_ADDR / pageSize * pageSize; 138 pageOffset = NPCM7XX_ESPI_BASE_ADDR - pageBase; 139 140 mapLength = pageOffset + ESPIHINDP_OFFSET + REGISTER_WIDTH; 141 142 stdplus::fd::MMap espiMap( 143 fd, mapLength, ProtFlags().set(ProtFlag::Read).set(ProtFlag::Write), 144 MMapFlags(MMapAccess::Shared), pageBase); 145 146 /* Read the ESPICFG register. */ 147 volatile uint32_t* const espicfgReg = getReg(espiMap, 148 pageOffset + ESPICFG_OFFSET); 149 uint32_t espicfgValue = *espicfgReg; 150 151 if (disable) 152 { 153 /* 154 * Check if the automatic ready bits are set in the eSPI host 155 * independence register (ESPIHINDP). 156 */ 157 volatile uint32_t* const espihindpReg = 158 getReg(espiMap, pageOffset + ESPIHINDP_OFFSET); 159 uint32_t espihindpValue = *espihindpReg; 160 if (espihindpValue & ESPI_AUTO_READY_MASK) 161 { 162 /* 163 * If any of the automatic ready bits are set, we need to disable 164 * them, using several steps: 165 * - Make sure the host channel enable and core channel bits are 166 * consistent, in the ESPICFG register, i.e. copy the host 167 * channel enable bits to the core channel enable bits. 168 * - Clear the automatic ready bits in ESPIHINDP. 169 */ 170 uint32_t hostChannelEnableBits = espicfgValue & 171 ESPICFG_HOST_CHANNEL_ENABLE_MASK; 172 espicfgValue |= (hostChannelEnableBits >> 4); 173 *espicfgReg = espicfgValue; 174 175 espihindpValue &= ~ESPI_AUTO_READY_MASK; 176 *espihindpReg = espihindpValue; 177 } 178 179 /* Now disable the core channel enable bits in ESPICFG. */ 180 espicfgValue &= ~ESPICFG_CORE_CHANNEL_ENABLE_MASK; 181 *espicfgReg = espicfgValue; 182 183 fprintf(stderr, "Disabled eSPI bus\n"); 184 } 185 else 186 { 187 /* Enable eSPI by setting the core channel enable bits in ESPICFG. */ 188 espicfgValue |= ESPICFG_CORE_CHANNEL_ENABLE_MASK; 189 *espicfgReg = espicfgValue; 190 191 fprintf(stderr, "Enabled eSPI bus\n"); 192 } 193 } 194 195 } // namespace 196 197 int main(int argc, char** argv) 198 { 199 int opt; 200 bool disable = false; 201 while ((opt = getopt(argc, argv, "d")) != -1) 202 { 203 switch (opt) 204 { 205 case 'd': 206 disable = true; 207 break; 208 default: 209 usage(argv[0]); 210 return -1; 211 } 212 } 213 214 try 215 { 216 /* Update registers to enable or disable eSPI. */ 217 modifyESPIRegisters(disable); 218 } 219 catch (const std::exception& e) 220 { 221 fprintf(stderr, "Failed to %s eSPI bus: %s\n", 222 disable ? "disable" : "enable", e.what()); 223 return -1; 224 } 225 226 return 0; 227 } 228