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 
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  */
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 
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 
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