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