blob: 661a48cf5273ce088987511ae012332fdc46a573 [file] [log] [blame]
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*
* Disclaimer: This binary is only intended to be used on Nuvoton NPCM7xx BMCs.
*
* It could also be extended to support NPCM8xx, but it hasn't been tested with
* that model BMC.
*
* This binary is NOT intended to support Aspeed BMCs.
*/
#include <unistd.h>
#include <stdplus/fd/create.hpp>
#include <stdplus/fd/intf.hpp>
#include <stdplus/fd/managed.hpp>
#include <stdplus/fd/mmap.hpp>
#include <cassert>
#include <cstdint>
#include <cstdio>
#include <format>
#include <stdexcept>
/* Base address for Nuvoton's global control register space. */
#define NPCM7XX_GLOBAL_CTRL_BASE_ADDR 0xF0800000
/* Offset of the PDID register and expected PDID value. */
#define PDID_OFFSET 0x00
#define NPCM7XX_PDID 0x04A92750
/* Register width in bytes. */
#define REGISTER_WIDTH 4
/* Base address for Nuvoton's eSPI register space. */
#define NPCM7XX_ESPI_BASE_ADDR 0xF009F000
/*
* Offset of the eSPI config (ESPICFG) register, along with host channel enable
* mask and core channel enable mask.
*/
#define ESPICFG_OFFSET 0x4
#define ESPICFG_HOST_CHANNEL_ENABLE_MASK 0xF0
#define ESPICFG_CORE_CHANNEL_ENABLE_MASK 0x0F
/*
* Offset of the host independence (ESPIHINDP) register and automatic ready bit
* mask.
*/
#define ESPIHINDP_OFFSET 0x80
#define ESPI_AUTO_READY_MASK 0xF
namespace
{
using stdplus::fd::MMapAccess;
using stdplus::fd::MMapFlags;
using stdplus::fd::OpenAccess;
using stdplus::fd::OpenFlag;
using stdplus::fd::OpenFlags;
using stdplus::fd::ProtFlag;
using stdplus::fd::ProtFlags;
static void usage(char* name)
{
std::fprintf(stderr, "Usage: %s [-d]\n", name);
std::fprintf(stderr, "Enable or disable eSPI bus on NPCM7XX BMC\n");
std::fprintf(stderr, "This program will enable eSPI by default, unless "
"the -d option is used.\n");
std::fprintf(stderr, " -d Disable eSPI\n");
}
/*
* Get a pointer to the register at the given offset, within the provided
* memory-mapped I/O space.
*/
static inline volatile uint32_t* getReg(stdplus::fd::MMap& map,
size_t regOffset)
{
uintptr_t regPtr = reinterpret_cast<uintptr_t>(map.get().data()) +
regOffset;
/* Make sure the register pointer is properly aligned. */
assert((regPtr & ~(REGISTER_WIDTH - 1)) == regPtr);
return reinterpret_cast<volatile uint32_t*>(regPtr);
}
static void modifyESPIRegisters(bool disable)
{
/*
* We need to make sure this is running on a Nuvoton BMC. To do that, we'll
* read the product identification (PDID) register.
*/
/* Find the page that includes the Product ID register. */
size_t pageSize = sysconf(_SC_PAGE_SIZE);
size_t pageBase = NPCM7XX_GLOBAL_CTRL_BASE_ADDR / pageSize * pageSize;
size_t pageOffset = NPCM7XX_GLOBAL_CTRL_BASE_ADDR - pageBase;
size_t mapLength = pageOffset + PDID_OFFSET + REGISTER_WIDTH;
auto fd = stdplus::fd::open(
"/dev/mem", OpenFlags(OpenAccess::ReadWrite).set(OpenFlag::Sync));
stdplus::fd::MMap pdidMap(fd, mapLength, ProtFlags().set(ProtFlag::Read),
MMapFlags(MMapAccess::Shared), pageBase);
volatile uint32_t* const pdidReg = getReg(pdidMap,
pageOffset + PDID_OFFSET);
/*
* Read the PDID register to make sure we're running on a Nuvoton NPCM7xx
* BMC.
* Note: This binary would probably work on NPCM8xx, as well, if we also
* allowed the NPCM8xx PDID, since the register addresses are the same. But
* that hasn't been tested.
*/
if (*pdidReg != NPCM7XX_PDID)
{
throw std::runtime_error(
std::format("Unexpected product ID {:#x} != {:#x}", *pdidReg,
NPCM7XX_PDID)
.data());
}
/*
* Find the start of the page that includes the start of the eSPI register
* space.
*/
pageBase = NPCM7XX_ESPI_BASE_ADDR / pageSize * pageSize;
pageOffset = NPCM7XX_ESPI_BASE_ADDR - pageBase;
mapLength = pageOffset + ESPIHINDP_OFFSET + REGISTER_WIDTH;
stdplus::fd::MMap espiMap(
fd, mapLength, ProtFlags().set(ProtFlag::Read).set(ProtFlag::Write),
MMapFlags(MMapAccess::Shared), pageBase);
/* Read the ESPICFG register. */
volatile uint32_t* const espicfgReg = getReg(espiMap,
pageOffset + ESPICFG_OFFSET);
uint32_t espicfgValue = *espicfgReg;
if (disable)
{
/*
* Check if the automatic ready bits are set in the eSPI host
* independence register (ESPIHINDP).
*/
volatile uint32_t* const espihindpReg =
getReg(espiMap, pageOffset + ESPIHINDP_OFFSET);
uint32_t espihindpValue = *espihindpReg;
if (espihindpValue & ESPI_AUTO_READY_MASK)
{
/*
* If any of the automatic ready bits are set, we need to disable
* them, using several steps:
* - Make sure the host channel enable and core channel bits are
* consistent, in the ESPICFG register, i.e. copy the host
* channel enable bits to the core channel enable bits.
* - Clear the automatic ready bits in ESPIHINDP.
*/
uint32_t hostChannelEnableBits = espicfgValue &
ESPICFG_HOST_CHANNEL_ENABLE_MASK;
espicfgValue |= (hostChannelEnableBits >> 4);
*espicfgReg = espicfgValue;
espihindpValue &= ~ESPI_AUTO_READY_MASK;
*espihindpReg = espihindpValue;
}
/* Now disable the core channel enable bits in ESPICFG. */
espicfgValue &= ~ESPICFG_CORE_CHANNEL_ENABLE_MASK;
*espicfgReg = espicfgValue;
fprintf(stderr, "Disabled eSPI bus\n");
}
else
{
/* Enable eSPI by setting the core channel enable bits in ESPICFG. */
espicfgValue |= ESPICFG_CORE_CHANNEL_ENABLE_MASK;
*espicfgReg = espicfgValue;
fprintf(stderr, "Enabled eSPI bus\n");
}
}
} // namespace
int main(int argc, char** argv)
{
int opt;
bool disable = false;
while ((opt = getopt(argc, argv, "d")) != -1)
{
switch (opt)
{
case 'd':
disable = true;
break;
default:
usage(argv[0]);
return -1;
}
}
try
{
/* Update registers to enable or disable eSPI. */
modifyESPIRegisters(disable);
}
catch (const std::exception& e)
{
fprintf(stderr, "Failed to %s eSPI bus: %s\n",
disable ? "disable" : "enable", e.what());
return -1;
}
return 0;
}