Implement power control for x86 based platforms
This power control module provides the capability to
power on/off the host via gpio.
And provides some interfaces for system to contorl the system
power like:
setPowerState
getPowerState
Change-Id: Icd6530c42f2bc7c4d84062be786d25710b53f434
Signed-off-by: Kuiying Wang <kuiying.wang@intel.com>
diff --git a/power-control/CMakeLists.txt b/power-control/CMakeLists.txt
new file mode 100644
index 0000000..282620d
--- /dev/null
+++ b/power-control/CMakeLists.txt
@@ -0,0 +1,48 @@
+cmake_minimum_required(VERSION 2.8.10 FATAL_ERROR)
+project(power-control CXX)
+set(CMAKE_CXX_STANDARD 14)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_RPATH}
+ ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR})
+set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
+include(GNUInstallDirs)
+
+include_directories(${CMAKE_CURRENT_SOURCE_DIR}/inc)
+include_directories(${CMAKE_CURRENT_BINARY_DIR})
+
+set(DBUS_OBJECT_NAME "xyz/openbmc_project/Chassis/Control/Power")
+set(DBUS_INTF_NAME "xyz.openbmc_project.Chassis.Control.Power")
+
+add_definitions(-DDBUS_OBJECT_NAME="/${DBUS_OBJECT_NAME}0")
+add_definitions(-DDBUS_INTF_NAME="${DBUS_INTF_NAME}")
+set(SRC_FILES
+ src/power_control.cpp
+ src/main.cpp
+)
+
+# import sdbusplus
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(SDBUSPLUSPLUS sdbusplus REQUIRED)
+include_directories(${SDBUSPLUSPLUS_INCLUDE_DIRS})
+link_directories(${SDBUSPLUSPLUS_LIBRARY_DIRS})
+find_program(SDBUSPLUSPLUS sdbus++)
+
+# import phosphor-logging
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(LOGGING phosphor-logging REQUIRED)
+include_directories(${LOGGING_INCLUDE_DIRS})
+link_directories(${LOGGING_LIBRARY_DIRS})
+
+# phosphor-dbus-interfaces
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(DBUSINTERFACE phosphor-dbus-interfaces REQUIRED)
+include_directories(${DBUSINTERFACE_INCLUDE_DIRS})
+link_directories(${DBUSINTERFACE_LIBRARY_DIRS})
+
+add_executable(${PROJECT_NAME} ${SRC_FILES})
+target_link_libraries(${PROJECT_NAME} ${DBUSINTERFACE_LIBRARIES}
+ chassisgpio )
+target_link_libraries(${PROJECT_NAME} "${SDBUSPLUSPLUS_LIBRARIES} -lstdc++fs -lphosphor_dbus")
+
+install (TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR})
diff --git a/power-control/inc/power_control.hpp b/power-control/inc/power_control.hpp
new file mode 100644
index 0000000..4983fb7
--- /dev/null
+++ b/power-control/inc/power_control.hpp
@@ -0,0 +1,169 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// 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.
+*/
+
+#pragma once
+#include <fcntl.h>
+#include <linux/aspeed-lpc-sio.h>
+#include <unistd.h>
+#include <phosphor-logging/elog-errors.hpp>
+#include <xyz/openbmc_project/Chassis/Common/error.hpp>
+#include <xyz/openbmc_project/Chassis/Control/Power/server.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+#include "gpio.hpp"
+
+static constexpr size_t POLLING_INTERVAL_MS = 500;
+
+const static constexpr char* LPC_SIO_DEVPATH = "/dev/lpc-sio";
+const static constexpr char* PGOOD_PIN = "PGOOD";
+const static constexpr char* POWER_UP_PIN = "POWER_UP_PIN";
+
+const static constexpr size_t PCH_DEVICE_BUS_ADDRESS = 3;
+const static constexpr size_t PCH_DEVICE_SLAVE_ADDRESS = 0x44;
+const static constexpr size_t PCH_CMD_REGISTER = 0;
+const static constexpr size_t PCH_POWER_DOWN_CMD = 0x02;
+
+const static constexpr size_t POWER_UP_PIN_PULSE_TIME_MS = 200;
+
+using pwr_control =
+ sdbusplus::xyz::openbmc_project::Chassis::Control::server::Power;
+
+struct PowerControl : sdbusplus::server::object_t<pwr_control>
+{
+ PowerControl(sdbusplus::bus::bus& bus, const char* path,
+ phosphor::watchdog::EventPtr event,
+ sd_event_io_handler_t handler = PowerControl::EventHandler) :
+ sdbusplus::server::object_t<pwr_control>(bus, path),
+ bus(bus), callbackHandler(handler)
+ {
+ int ret = -1;
+ char buf = '0';
+
+ // config gpio
+ ret = configGpio(PGOOD_PIN, &pgood_fd, bus);
+ if (ret < 0)
+ {
+ throw std::runtime_error("failed to config PGOOD_PIN");
+ }
+
+ ret = configGpio(POWER_UP_PIN, &power_up_fd, bus);
+ if (ret < 0)
+ {
+ closeGpio(pgood_fd);
+ throw std::runtime_error("failed to config POWER_UP_PIN");
+ }
+
+ ret = sd_event_add_io(event.get(), nullptr, pgood_fd, EPOLLPRI,
+ callbackHandler, this);
+ if (ret < 0)
+ {
+ closeGpio(pgood_fd);
+ closeGpio(power_up_fd);
+ throw std::runtime_error("failed to add to event loop");
+ }
+
+ timer.start(std::chrono::duration_cast<std::chrono::microseconds>(
+ std::chrono::milliseconds(POLLING_INTERVAL_MS)));
+ timer.setEnabled<std::true_type>();
+ phosphor::logging::log<phosphor::logging::level::DEBUG>("Enable timer");
+ }
+
+ ~PowerControl()
+ {
+ closeGpio(pgood_fd);
+ closeGpio(power_up_fd);
+ }
+
+ static int EventHandler(sd_event_source* es, int fd, uint32_t revents,
+ void* userdata)
+ {
+ // For the first event, only set the initial status, do not emit signal
+ // since is it not triggered by the real gpio change
+ static bool first_event = true;
+ int n = -1;
+ char buf = '0';
+
+ if (!userdata)
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(
+ "userdata null!");
+ return -1;
+ }
+
+ PowerControl* powercontrol = static_cast<PowerControl*>(userdata);
+
+ if (!powercontrol)
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(
+ "null pointer!");
+ return -1;
+ }
+
+ n = ::lseek(fd, 0, SEEK_SET);
+ if (n < 0)
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(
+ "lseek error!");
+ return n;
+ }
+
+ n = ::read(fd, &buf, sizeof(buf));
+ if (n < 0)
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(
+ "read error!");
+ return n;
+ }
+
+ if (buf == '0')
+ {
+ powercontrol->state(0);
+ powercontrol->pgood(0);
+
+ if (first_event)
+ {
+ first_event = false;
+ }
+ else
+ {
+ powercontrol->powerLost();
+ }
+ }
+ else
+ {
+ powercontrol->state(1);
+ powercontrol->pgood(1);
+ if (first_event)
+ {
+ first_event = false;
+ }
+ else
+ {
+ powercontrol->powerGood();
+ }
+ }
+
+ return 0;
+ }
+
+ int32_t setPowerState(int32_t newState) override;
+ int32_t getPowerState() override;
+
+ private:
+ int power_up_fd;
+ int pgood_fd;
+ sdbusplus::bus::bus& bus;
+ sd_event_io_handler_t callbackHandler;
+};
diff --git a/power-control/src/main.cpp b/power-control/src/main.cpp
new file mode 100644
index 0000000..695666e
--- /dev/null
+++ b/power-control/src/main.cpp
@@ -0,0 +1,64 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// 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.
+*/
+#include "power_control.hpp"
+
+int main(int argc, char* argv[])
+{
+ int ret = 0;
+
+ phosphor::logging::log<phosphor::logging::level::INFO>(
+ "Start Chassis power control service...");
+
+ sd_event* event = nullptr;
+ ret = sd_event_default(&event);
+ if (ret < 0)
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(
+ "Error creating a default sd_event handler");
+ return ret;
+ }
+ phosphor::watchdog::EventPtr eventP{event,
+ phosphor::watchdog::EventDeleter()};
+ event = nullptr;
+
+ sdbusplus::bus::bus bus = sdbusplus::bus::new_default();
+ sdbusplus::server::manager_t m{bus, DBUS_OBJECT_NAME};
+
+ bus.request_name(DBUS_INTF_NAME);
+
+ PowerControl powerControl{bus, DBUS_OBJECT_NAME, eventP};
+
+ auto now_ms = std::chrono::time_point_cast<std::chrono::milliseconds>(
+ std::chrono::system_clock::now());
+
+ try
+ {
+ bus.attach_event(eventP.get(), SD_EVENT_PRIORITY_NORMAL);
+ ret = sd_event_loop(eventP.get());
+ if (ret < 0)
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(
+ "Error occurred during the sd_event_loop",
+ phosphor::logging::entry("RET=%d", ret));
+ }
+ }
+ catch (std::exception& e)
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
+ return -1;
+ }
+ return 0;
+}
diff --git a/power-control/src/power_control.cpp b/power-control/src/power_control.cpp
new file mode 100644
index 0000000..74b64b8
--- /dev/null
+++ b/power-control/src/power_control.cpp
@@ -0,0 +1,82 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// 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.
+*/
+#include "power_control.hpp"
+
+int32_t PowerControl::setPowerState(int32_t newState)
+{
+ int ret = 0;
+ int count = 0;
+ char buf = '0';
+
+ phosphor::logging::log<phosphor::logging::level::DEBUG>(
+ "setPowerState", phosphor::logging::entry("NEWSTATE=%d", newState));
+
+ if (state() == newState)
+ {
+ phosphor::logging::log<phosphor::logging::level::WARNING>(
+ "Same powerstate",
+ phosphor::logging::entry("NEWSTATE=%d", newState));
+ return 0;
+ }
+
+ ret = ::lseek(power_up_fd, 0, SEEK_SET);
+ if (ret < 0)
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(
+ "lseek error!");
+ throw sdbusplus::xyz::openbmc_project::Chassis::Common::Error::
+ IOError();
+ }
+
+ /*
+ This power control just handle out pin "POWER_UP_PIN", change it
+ to low "0" form high "1" and set it back to high after over 200ms,
+ which will notify host (PCH) to switch power. Host to determine it
+ is power on or power off operation based on current power status.
+ For BMC (power control), just need to notify host (PCH) to switch
+ power, don't need to judge it should power on or off.
+ */
+ buf = '0';
+ ret = ::write(power_up_fd, &buf, sizeof(buf));
+ if (ret < 0)
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(
+ "write error for setting 0 !");
+ throw sdbusplus::xyz::openbmc_project::Chassis::Common::Error::
+ IOError();
+ }
+
+ std::this_thread::sleep_for(
+ std::chrono::milliseconds(POWER_UP_PIN_PULSE_TIME_MS));
+
+ buf = '1';
+ ret = ::write(power_up_fd, &buf, sizeof(buf));
+ if (ret < 0)
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(
+ "write error for setting 1 !");
+ throw sdbusplus::xyz::openbmc_project::Chassis::Common::Error::
+ IOError();
+ }
+
+ state(newState);
+ return 0;
+}
+
+int32_t PowerControl::getPowerState()
+{
+ return state();
+}