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