blob: 661a48cf5273ce088987511ae012332fdc46a573 [file] [log] [blame]
John Wedigcc45be72023-07-06 16:30:34 -07001// 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
62namespace
63{
64
65using stdplus::fd::MMapAccess;
66using stdplus::fd::MMapFlags;
67using stdplus::fd::OpenAccess;
68using stdplus::fd::OpenFlag;
69using stdplus::fd::OpenFlags;
70using stdplus::fd::ProtFlag;
71using stdplus::fd::ProtFlags;
72
73static 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 */
86static inline volatile uint32_t* getReg(stdplus::fd::MMap& map,
87 size_t regOffset)
88{
89 uintptr_t regPtr = reinterpret_cast<uintptr_t>(map.get().data()) +
90 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
97static 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 = getReg(pdidMap,
116 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 = getReg(espiMap,
148 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 = espicfgValue &
171 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
197int 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}