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