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
usage(char * name)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 */
getReg(stdplus::fd::MMap & map,size_t regOffset)86 static inline volatile uint32_t* getReg(stdplus::fd::MMap& map,
87 size_t regOffset)
88 {
89 uintptr_t regPtr =
90 reinterpret_cast<uintptr_t>(map.get().data()) + 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
modifyESPIRegisters(bool disable)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 =
116 getReg(pdidMap, 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 =
148 getReg(espiMap, 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 =
171 espicfgValue & 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
main(int argc,char ** argv)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