Scripts and main daemon
This includes the scripts for the YAML parsing and the
main execution point.
Change-Id: If42154c621353b23370b63d4e58f6c75bca8b356
Signed-off-by: Patrick Venture <venture@google.com>
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..a23eb91
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,73 @@
+AM_DEFAULT_SOURCE_EXT = .cpp
+
+sbin_PROGRAMS = swampd setsensor
+
+BUILT_SOURCES = sensorlist-gen.cpp pidlist-gen.cpp zoneinfo-gen.cpp
+CLEANFILES = $(BUILT_SOURCES)
+
+sensorlist-gen.cpp:
+ $(AM_V_GEN)@SENSORGEN@ -o $(top_builddir) generate-cpp
+
+pidlist-gen.cpp:
+ $(AM_V_GEN)@PIDGEN@ -o $(top_builddir) generate-cpp
+
+zoneinfo-gen.cpp:
+ $(AM_V_GEN)@ZONEGEN@ -o $(top_builddir) generate-cpp
+
+setsensor_SOURCES = setsensor.cpp
+setsensor_LDADD = $(SDBUSPLUS_LIBS) $(PHOSPHOR_DBUS_INTERFACES_LIBS) $(PHOSPHOR_LOGGING_LIBS)
+setsensor_CXXFLAGS = $(SDBUSPLUS_CFLAGS) $(PHOSPHOR_DBUS_INTERFACES_CFLAGS) $(PHOSPHOR_LOGGING_CFLAGS)
+
+swampd_SOURCES = main.cpp util.cpp
+swampd_LDADD = $(SDBUSPLUS_LIBS) libswampd.la
+swampd_CXXFLAGS = $(SDBUSPLUS_CFLAGS)
+
+noinst_LTLIBRARIES = libswampd.la
+libswampd_la_LDFLAGS = -static
+libswampd_la_LIBADD = \
+ -lstdc++fs \
+ -lconfig++ \
+ $(SDBUSPLUS_LIBS) \
+ $(PHOSPHOR_DBUS_INTERFACES_LIBS) \
+ $(PHOSPHOR_LOGGING_LIBS)
+libswampd_la_CXXFLAGS = \
+ $(SDBUSPLUS_CFLAGS) \
+ $(PHOSPHOR_DBUS_INTERFACES_CFLAGS) \
+ $(PHOSPHOR_LOGGING_CFLAGS)
+
+libswampd_la_SOURCES = \
+ notimpl/readonly.cpp \
+ notimpl/writeonly.cpp \
+ dbus/util.cpp \
+ dbus/dbuspassive.cpp \
+ dbus/dbusactiveread.cpp \
+ sysfs/sysfsread.cpp \
+ sysfs/sysfswrite.cpp \
+ sysfs/util.cpp \
+ sensors/pluggable.cpp \
+ sensors/host.cpp \
+ sensors/manager.cpp \
+ pid/ec/pid.cpp \
+ pid/controller.cpp \
+ pid/fancontroller.cpp \
+ pid/thermalcontroller.cpp \
+ pid/zone.cpp \
+ pid/util.cpp \
+ pid/pidthread.cpp \
+ threads/busthread.cpp \
+ experiments/drive.cpp \
+ $(BUILT_SOURCES)
+
+libmanualcmdsdir = ${libdir}/ipmid-providers
+libmanualcmds_LTLIBRARIES = libmanualcmds.la
+libmanualcmds_la_SOURCES = \
+ ipmi/manualcmds.cpp
+libmanualcmds_la_LDFLAGS = $(SYSTEMD_LIBS) \
+ $(PHOSPHOR_DBUS_INTERFACES_LIBS) \
+ $(PHOSPHOR_LOGGING_LIBS) \
+ -version-info 0:0:0 -shared
+libmanualcmds_la_CXXFLAGS = $(SYSTEMD_CFLAGS) \
+ $(PHOSPHOR_DBUS_INTERFACES_CFLAGS) \
+ $(PHOSPHOR_LOGGING_CFLAGS)
+
+SUBDIRS = .
diff --git a/README b/README
new file mode 100644
index 0000000..968e06d
--- /dev/null
+++ b/README
@@ -0,0 +1,47 @@
+# Introduction
+
+Swampd's primary function is to drive the fans of a system given various inputs.
+
+# Layout
+
+The code is broken out into modules as follows:
+
+* `dbus` - Any read or write interface that uses dbus primarily.
+* `experiments` - Small execution paths that allow for fan examination
+including how quickly fans respond to changes.
+* `ipmi` - Manual control for any zone is handled by receiving an IPMI message.
+This holds the ipmid provider for receiving those messages and sending them
+onto swampd.
+* `notimpl` - These are read-only and write-only interface implementations that
+can be dropped into a pluggable sensor to make it complete.
+* `pid` - This contains all the PID associated code, including the zone
+defintion, controller definition, and the PID computational code.
+* `scripts` - This contains the scripts that convert YAML into C++.
+* `sensors` - This contains a couple of sensor types including the pluggable
+sensor's definition. It also holds the sensor manager.
+* `sysfs` - This contains code that reads from or writes to sysfs.
+* `threads` - Most of swampd's threads run in this method where there's just a
+dbus bus that we manage.
+
+
+# Design
+
+One defines a series of sensors and a series of PIDs that utilize those sensors
+as inputs (and outputs).
+
+ The thermal PID
+
+ set-point -------->|---|---> RPM
+ current value ---->|___|
+
+ The fan PID
+
+ set-point -------->|---|---> pwm%
+ current value ---->|___|
+
+ How to get set-point for fan PIDs
+
+ thermal-out ----->|-----|
+ thermal-out ----->| |-----> RPM
+ thermal-out ----->| MAX |
+ thermal-out ----->|_____|
diff --git a/bootstrap.sh b/bootstrap.sh
new file mode 100644
index 0000000..fce7cfa
--- /dev/null
+++ b/bootstrap.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+AUTOCONF_FILES="Makefile.in aclocal.m4 ar-lib autom4te.cache compile \
+ config.guess config.h.in config.sub configure depcomp install-sh \
+ ltmain.sh missing *libtool test-driver"
+
+case $1 in
+ clean)
+ test -f Makefile && make maintainer-clean
+ for file in ${AUTOCONF_FILES}; do
+ find -name "$file" | xargs -r rm -rf
+ done
+ exit 0
+ ;;
+esac
+
+autoreconf -i
+echo 'Run "./configure ${CONFIGURE_FLAGS} && make"'
+
diff --git a/conf.hpp b/conf.hpp
new file mode 100644
index 0000000..ad8a1a9
--- /dev/null
+++ b/conf.hpp
@@ -0,0 +1,51 @@
+#pragma once
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "pid/ec/pid.hpp"
+
+
+/*
+ * General sensor structure used for configuration.
+ */
+struct sensor
+{
+ /* Used for listen if readpath is passive. */
+ std::string type;
+ /* Can be a sensor path or a dbus path. */
+ std::string readpath;
+ std::string writepath;
+ /* min/max values for writing a percentage or error checking. */
+ int64_t min;
+ int64_t max;
+ int64_t timeout;
+};
+
+/*
+ * Structure for holding the configuration of a PID.
+ */
+struct controller_info
+{
+ std::string type; // fan or margin or temp?
+ std::vector<std::string> inputs; // one or more sensors.
+ float setpoint; // initial setpoint for thermal.
+ ec::pidinfo info; // pid details
+};
+
+/*
+ * General zone structure used for configuration. A zone is a list of PIDs
+ * and a set of configuration settings. This structure gets filled out with
+ * the zone configuration settings and not the PID details.
+ */
+struct zone
+{
+ /* The minimum RPM value we would ever want. */
+ float minthermalrpm;
+
+ /* If the sensors are in fail-safe mode, this is the percentage to use. */
+ float failsafepercent;
+};
+
+using PIDConf = std::map<std::string, struct controller_info>;
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..b86d0c6
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,70 @@
+# Initialization
+AC_PREREQ([2.69])
+AC_INIT([swampd], [1.0], [https://www.google.com])
+AC_LANG([C++])
+AC_CONFIG_HEADERS([config.h])
+AM_INIT_AUTOMAKE([subdir-objects -Wall -Werror foreign dist-xz])
+AM_SILENT_RULES([yes])
+
+# Checks for programs.
+AC_PROG_CXX
+AM_PROG_AR
+AC_PROG_INSTALL
+AC_PROG_MAKE_SET
+
+# Python
+AM_PATH_PYTHON([2.7], [AC_SUBST([PYTHON], [echo "$PYTHON"])],
+[AC_MSG_ERROR([Could not find python-2.7 installed...python-2.7 is required])])
+
+# Checks for typedefs, structures, and compiler characteristics.
+AX_CXX_COMPILE_STDCXX_14([noext])
+AX_APPEND_COMPILE_FLAGS([-Wall -Werror], [CXXFLAGS])
+
+# Checks for libraries.
+# AC_CHECK_LIB([mapper], [mapper_get_service], ,[AC_MSG_ERROR([Could not find libmapper...openbmc/phosphor-objmgr package required])])
+PKG_CHECK_MODULES([SYSTEMD], [libsystemd >= 221], [], [AC_MSG_ERROR(["systemd required and not found"])])
+PKG_CHECK_MODULES([SDBUSPLUS], [sdbusplus], ,[AC_MSG_ERROR([The openbmc/sdbusplus package is required])])
+PKG_CHECK_MODULES([PHOSPHOR_LOGGING], [phosphor-logging], ,[AC_MSG_ERROR([The openbmc/phosphor-logging package is required])])
+PKG_CHECK_MODULES([PHOSPHOR_DBUS_INTERFACES], [phosphor-dbus-interfaces], [], [AC_MSG_ERROR(["phosphor-dbus-interfaces required and not found."])])
+AC_CHECK_HEADER(experimental/any, ,[AC_MSG_ERROR([Could not find experimental/any...libstdc++fs developement package required])])
+
+# Checks for library functions.
+LT_INIT # Required for systemd linking
+
+# Check/set gtest specific functions.
+AX_PTHREAD([GTEST_CPPFLAGS="-DGTEST_HAS_PTHREAD=1"],[GTEST_CPPFLAGS="-DGTEST_HAS_PTHREAD=0"])
+AC_SUBST(GTEST_CPPFLAGS)
+
+AC_ARG_ENABLE([oe-sdk],
+ AS_HELP_STRING([--enable-oe-sdk], [Link testcases absolutely against OE SDK so they can be ran within it.])
+)
+AC_ARG_VAR(OECORE_TARGET_SYSROOT,
+ [Path to the OE SDK SYSROOT])
+AS_IF([test "x$enable_oe_sdk" == "xyes"],
+ AS_IF([test "x$OECORE_TARGET_SYSROOT" == "x"],
+ AC_MSG_ERROR([OECORE_TARGET_SYSROOT must be set with --enable-oe-sdk])
+ )
+ AC_MSG_NOTICE([Enabling OE-SDK at $OECORE_TARGET_SYSROOT])
+ [
+ testcase_flags="-Wl,-rpath,\${OECORE_TARGET_SYSROOT}/lib"
+ testcase_flags="${testcase_flags} -Wl,-rpath,\${OECORE_TARGET_SYSROOT}/usr/lib"
+ testcase_flags="${testcase_flags} -Wl,-dynamic-linker,`find \${OECORE_TARGET_SYSROOT}/lib/ld-*.so | sort -r -n | head -n1`"
+ ]
+ AC_SUBST([OESDK_TESTCASE_FLAGS], [$testcase_flags])
+)
+
+AS_IF([test "x$SENSOR_YAML_GEN" == "x"], [SENSOR_YAML_GEN="sensor-example.yaml"])
+SENSORGEN="$PYTHON ${srcdir}/scripts/sensor_gen.py -i $SENSOR_YAML_GEN"
+AC_SUBST(SENSORGEN)
+
+AS_IF([test "x$PID_YAML_GEN" == "x"], [PID_YAML_GEN="pid-example.yaml"])
+PIDGEN="$PYTHON ${srcdir}/scripts/pid_gen.py -i $PID_YAML_GEN"
+AC_SUBST(PIDGEN)
+
+AS_IF([test "x$ZONE_YAML_GEN" == "x"], [ZONE_YAML_GEN="zone-example.yaml"])
+ZONEGEN="$PYTHON ${srcdir}/scripts/zone_gen.py -i $ZONE_YAML_GEN"
+AC_SUBST(ZONEGEN)
+
+# Create configured output
+AC_CONFIG_FILES([Makefile])
+AC_OUTPUT
diff --git a/examples/set-point b/examples/set-point
new file mode 100644
index 0000000..a77fd92
--- /dev/null
+++ b/examples/set-point
@@ -0,0 +1 @@
+6000
diff --git a/examples/swampd.conf b/examples/swampd.conf
new file mode 100644
index 0000000..c17a75c
--- /dev/null
+++ b/examples/swampd.conf
@@ -0,0 +1,97 @@
+
+sensors = (
+ { name = "fan1"
+ type = "fan"
+ readpath = "/xyz/openbmc_project/sensors/fan_tach/fan1"
+ writepath = "/sys/class/hwmon/hwmon0/pwm0"
+ min = 0
+ { name = "fan2"
+ type = "fan"
+ readpath = "/xyz/openbmc_project/sensors/fan_tach/fan2"
+ writepath = "/sys/class/hwmon/hwmon0/pwm1"
+ min = 0
+ max = 255
+ timeout = 0 },
+ { name = "fan3"
+ type = "fan"
+ readpath = "/xyz/openbmc_project/sensors/fan_tach/fan3"
+ writepath = "/sys/class/hwmon/hwmon0/pwm2"
+ min = 0
+ max = 255
+ timeout = 0 },
+ { name = "fan4"
+ type = "fan"
+ readpath = "/xyz/openbmc_project/sensors/fan_tach/fan4"
+ writepath = "/sys/class/hwmon/hwmon0/pwm3"
+ min = 0
+ max = 255
+ timeout = 0 },
+ { name = "fan5"
+ type = "fan"
+ readpath = "/xyz/openbmc_project/sensors/fan_tach/fan5"
+ writepath = ""
+ min = 0
+ max = 0
+ timeout = 0 },
+ { name = "fan6"
+ type = "fan"
+ readpath = "/xyz/openbmc_project/sensors/fan_tach/fan6"
+ writepath = ""
+ min = 0
+ max = 0
+ timeout = 0 },
+ { name = "fan7"
+ type = "fan"
+ readpath = "/xyz/openbmc_project/sensors/fan_tach/fan7"
+ writepath = ""
+ min = 0
+ max = 0
+ timeout = 0 },
+ { name = "fan8"
+ type = "fan"
+ readpath = "/xyz/openbmc_project/sensors/fan_tach/fan8"
+ writepath = ""
+ min = 0
+ max = 0
+ timeout = 0 }
+);
+
+zones = (
+ { id = 0x01
+ minthermalrpm = 3000.0
+ failsafepwm = 90.0
+ pids = (
+ { name = "allfans"
+ type = "fan"
+ inputs = (
+ "fan1",
+ "fan2",
+ "fan3",
+ "fan4",
+ "fan5",
+ "fan6",
+ "fan7",
+ "fan8"
+ )
+ set-point = 90.0
+ pid = {
+ sampleperiod = 0.1
+ p_coefficient = 0.01
+ i_coefficient = 0.001
+ ff_off_coefficient = 0.0
+ ff_gain_coefficient = 0.0
+ i_limit = {
+ min: 0.0
+ max: 100.0
+ }
+ out_limit = {
+ min: 0.0
+ max: 100.0
+ }
+ slew_neg = 0.0
+ slew_pos = 0.0
+ }
+ }
+ )
+ }
+);
diff --git a/experiments/README b/experiments/README
new file mode 100644
index 0000000..6ed6285
--- /dev/null
+++ b/experiments/README
@@ -0,0 +1,4 @@
+To determine the fan's ability to change speed we ran several experiments.
+These files are kept here not simply for posterity but also if we need to run
+them again or test new platforms.
+
diff --git a/experiments/drive.cpp b/experiments/drive.cpp
new file mode 100644
index 0000000..a6fbd9c
--- /dev/null
+++ b/experiments/drive.cpp
@@ -0,0 +1,273 @@
+/**
+ * Copyright 2017 Google Inc.
+ *
+ * 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 <iostream>
+#include <memory>
+#include <tuple>
+
+#include "drive.hpp"
+
+#include "interfaces.hpp"
+#include "sensors/pluggable.hpp"
+#include "sysfs/sysfswrite.hpp"
+#include "sysfs/sysfsread.hpp"
+
+using tstamp = std::chrono::high_resolution_clock::time_point;
+
+#define DRIVE_TIME 1
+#define DRIVE_GOAL 2
+#define DRIVE DRIVE_TIME
+#define MAX_PWM 255
+
+static std::unique_ptr<Sensor> Create(
+ std::string readpath,
+ std::string writepath)
+{
+ return std::make_unique<PluggableSensor>(
+ readpath,
+ 0, /* default the timeout to disabled */
+ std::make_unique<SysFsRead>(readpath),
+ std::make_unique<SysFsWrite>(writepath, 0, MAX_PWM));
+}
+
+int64_t getAverage(std::tuple<tstamp, int64_t, int64_t>& values)
+{
+ return (std::get<1>(values) + std::get<2>(values)) / 2;
+}
+
+bool valueClose(int64_t value, int64_t goal)
+{
+#if 0
+ int64_t delta = 100; /* within 100 */
+ if (value < (goal + delta) &&
+ value > (goal - delta))
+ {
+ return true;
+ }
+#endif
+
+ /* let's make sure it's below goal. */
+ if (value < goal)
+ {
+ return true;
+ }
+
+ return false;
+}
+
+static void driveGoal(
+ int64_t& seriesCnt,
+ int64_t setPwm,
+ int64_t goal,
+ std::vector<std::tuple<tstamp, int64_t, int64_t>>& series,
+ std::vector<std::unique_ptr<Sensor>>& fanSensors)
+{
+ bool reading = true;
+
+ auto& fan0 = fanSensors.at(0);
+ auto& fan1 = fanSensors.at(1);
+
+ fan0->write(setPwm);
+ fan1->write(setPwm);
+
+ while (reading)
+ {
+ bool check = false;
+ ReadReturn r0 = fan0->read();
+ ReadReturn r1 = fan1->read();
+ int64_t n0 = static_cast<int64_t>(r0.value);
+ int64_t n1 = static_cast<int64_t>(r1.value);
+
+ tstamp t1 = std::chrono::high_resolution_clock::now();
+
+ series.push_back(std::make_tuple(t1, n0, n1));
+ seriesCnt += 1;
+
+ int64_t avgn = (n0 + n1) / 2;
+ /* check last three values against goal if this is close */
+ check = valueClose(avgn, goal);
+
+ /* We know the last entry is within range. */
+ if (check && seriesCnt > 3)
+ {
+ /* n-2 values */
+ std::tuple<tstamp, int64_t, int64_t> nm2 = series.at(seriesCnt - 3);
+ /* n-1 values */
+ std::tuple<tstamp, int64_t, int64_t> nm1 = series.at(seriesCnt - 2);
+
+ int64_t avgnm2 = getAverage(nm2);
+ int64_t avgnm1 = getAverage(nm1);
+
+ int64_t together = (avgnm2 + avgnm1) / 2;
+
+ reading = !valueClose(together, goal);
+
+ if (!reading)
+ {
+ std::cerr << "finished reaching goal\n";
+ }
+ }
+
+ /* Early abort for testing. */
+ if (seriesCnt > 150000)
+ {
+ std::cerr << "aborting after 150000 reads.\n";
+ reading = false;
+ }
+ }
+
+ return;
+}
+
+static void driveTime(
+ int64_t& seriesCnt,
+ int64_t setPwm,
+ int64_t goal,
+ std::vector<std::tuple<tstamp, int64_t, int64_t>>& series,
+ std::vector<std::unique_ptr<Sensor>>& fanSensors)
+{
+ using namespace std::literals::chrono_literals;
+
+ bool reading = true;
+
+ auto& fan0 = fanSensors.at(0);
+ auto& fan1 = fanSensors.at(1);
+
+ auto& s0 = series.at(0);
+ tstamp t0 = std::get<0>(s0);
+
+ fan0->write(setPwm);
+ fan1->write(setPwm);
+
+ while (reading)
+ {
+ ReadReturn r0 = fan0->read();
+ ReadReturn r1 = fan1->read();
+ int64_t n0 = static_cast<int64_t>(r0.value);
+ int64_t n1 = static_cast<int64_t>(r1.value);
+ tstamp t1 = std::chrono::high_resolution_clock::now();
+
+ series.push_back(std::make_tuple(t1, n0, n1));
+
+ auto duration = std::chrono::duration_cast<std::chrono::microseconds>
+ (t1 - t0).count();
+ if (duration >= (20000000us).count())
+ {
+ reading = false;
+ }
+ }
+
+ return;
+}
+
+int driveMain(void)
+{
+ /* Time series of the data, the timestamp after both are read and the values. */
+ std::vector<std::tuple<tstamp, int64_t, int64_t>> series;
+ int64_t seriesCnt = 0; /* in case vector count isn't constant time */
+ int drive = DRIVE;
+
+ /*
+ * The fan map:
+ * --> 0 | 4
+ * --> 1 | 5
+ * --> 2 | 6
+ * --> 3 | 7
+ */
+ std::vector<std::string> fans =
+ {
+ "/sys/class/hwmon/hwmon0/fan0_input",
+ "/sys/class/hwmon/hwmon0/fan4_input"
+ };
+
+ std::vector<std::string> pwms =
+ {
+ "/sys/class/hwmon/hwmon0/pwm0",
+ "/sys/class/hwmon/hwmon0/pwm4"
+ };
+
+ std::vector<std::unique_ptr<Sensor>> fanSensors;
+
+ auto fan0 = Create(fans[0], pwms[0]);
+ auto fan1 = Create(fans[1], pwms[1]);
+
+ ReadReturn r0 = fan0->read();
+ ReadReturn r1 = fan1->read();
+ int64_t pwm0_value = static_cast<int64_t>(r0.value);
+ int64_t pwm1_value = static_cast<int64_t>(r1.value);
+
+ if (MAX_PWM != pwm0_value || MAX_PWM != pwm1_value)
+ {
+ std::cerr << "bad PWM starting point.\n";
+ return -EINVAL;
+ }
+
+ r0 = fan0->read();
+ r1 = fan1->read();
+ int64_t fan0_start = r0.value;
+ int64_t fan1_start = r1.value;
+ tstamp t1 = std::chrono::high_resolution_clock::now();
+
+ /*
+ * I've done experiments, and seen 9080,10243 as a starting point
+ * which leads to a 50% goal of 4830.5, which is higher than the
+ * average that they reach, 4668. -- i guess i could try to figure out
+ * a good increase from one to the other, but how fast they're going
+ * actually influences how much they influence, so at slower speeds the
+ * improvement is less.
+ */
+
+ series.push_back(std::make_tuple(t1, fan0_start, fan1_start));
+ seriesCnt += 1;
+
+ int64_t average = (fan0_start + fan1_start) / 2;
+ int64_t goal = 0.5 * average;
+
+ std::cerr << "goal: " << goal << "\n";
+
+ // fan0 @ 128: 4691
+ // fan4 @ 128: 4707
+
+ fanSensors.push_back(std::move(fan0));
+ fanSensors.push_back(std::move(fan1));
+
+ if (DRIVE_TIME == drive)
+ {
+ driveTime(seriesCnt, 128, goal, series, fanSensors);
+ }
+ else if (DRIVE_GOAL == drive)
+ {
+ driveGoal(seriesCnt, 128, goal, series, fanSensors);
+ }
+ tstamp tp = t1;
+
+ /* Output the values and the timepoints as a time series for review. */
+ for (auto& t : series)
+ {
+ tstamp ts = std::get<0>(t);
+ int64_t n0 = std::get<1>(t);
+ int64_t n1 = std::get<2>(t);
+
+ auto duration = std::chrono::duration_cast<std::chrono::microseconds>
+ (ts - tp).count();
+ std::cout << duration << "us, " << n0 << ", " << n1 << "\n";
+
+ tp = ts;
+ }
+
+ return 0;
+}
+
diff --git a/experiments/drive.hpp b/experiments/drive.hpp
new file mode 100644
index 0000000..94e74f3
--- /dev/null
+++ b/experiments/drive.hpp
@@ -0,0 +1,3 @@
+#pragma once
+
+int driveMain(void);
diff --git a/interfaces.hpp b/interfaces.hpp
new file mode 100644
index 0000000..8e7fc0b
--- /dev/null
+++ b/interfaces.hpp
@@ -0,0 +1,57 @@
+#pragma once
+
+#include <chrono>
+
+
+struct ReadReturn {
+ double value;
+ std::chrono::high_resolution_clock::time_point updated;
+};
+
+
+/*
+ * A ReadInterface is a plug-in for the PluggableSensor and anyone implementing
+ * this basically is providing a way to read a sensor.
+ */
+class ReadInterface
+{
+ public:
+ ReadInterface() { }
+
+ virtual ~ReadInterface() { }
+
+ virtual ReadReturn read(void) = 0;
+};
+
+/*
+ * A WriteInterface is a plug-in for the PluggableSensor and anyone implementing
+ * this basically is providing a way to write a sensor.
+ */
+class WriteInterface
+{
+ public:
+ WriteInterface(int64_t min, int64_t max)
+ : _min(min),
+ _max(max)
+ { }
+
+ virtual ~WriteInterface() { }
+
+ virtual void write(double value) = 0;
+
+ /*
+ * All WriteInterfaces have min/max available in case they want to error
+ * check.
+ */
+ int64_t getMin(void)
+ {
+ return _min;
+ }
+ int64_t getMax(void)
+ {
+ return _max;
+ }
+ private:
+ int64_t _min;
+ int64_t _max;
+};
diff --git a/main.cpp b/main.cpp
new file mode 100644
index 0000000..6f8f070
--- /dev/null
+++ b/main.cpp
@@ -0,0 +1,181 @@
+/**
+ * Copyright 2017 Google Inc.
+ *
+ * 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 <chrono>
+#include <experimental/any>
+#include <getopt.h>
+#include <iostream>
+#include <map>
+#include <memory>
+#include <mutex> /* not yet used. */
+#include <thread>
+#include <vector>
+
+#include <sdbusplus/bus.hpp>
+
+/* Configuration. */
+#include "conf.hpp"
+
+/* Misc. */
+#include "util.hpp"
+
+/* Controllers & Sensors. */
+#include "interfaces.hpp"
+#include "pid/zone.hpp"
+#include "sensors/manager.hpp"
+
+/* Threads. */
+#include "pid/pidthread.hpp"
+#include "threads/busthread.hpp"
+
+
+/* The YAML converted sensor list. */
+extern std::map<std::string, struct sensor> SensorConfig;
+/* The YAML converted PID list. */
+extern std::map<int64_t, PIDConf> ZoneConfig;
+/* The YAML converted Zone configuration. */
+extern std::map<int64_t, struct zone> ZoneDetailsConfig;
+
+int main(int argc, char* argv[])
+{
+ int rc = 0;
+ int c;
+ std::string configPath = "";
+
+ while (1)
+ {
+ static struct option long_options[] =
+ {
+ {"conf", required_argument, 0, 'c'},
+ {0, 0, 0, 0}
+ };
+
+ int option_index = 0;
+ c = getopt_long(argc, argv, "c:", long_options, &option_index);
+
+ if (c == -1)
+ {
+ break;
+ }
+
+ switch (c)
+ {
+ case 'c':
+ configPath = std::string {optarg};
+ break;
+ default:
+ /* skip garbage. */
+ continue;
+ }
+ }
+
+ auto ModeControlBus = sdbusplus::bus::new_default();
+ std::shared_ptr<SensorManager> mgmr;
+ std::map<int64_t, std::shared_ptr<PIDZone>> zones;
+
+ // Create a manger for the ModeBus because we own it.
+ static constexpr auto modeRoot = "/xyz/openbmc_project/settings/fanctrl";
+ sdbusplus::server::manager::manager(ModeControlBus, modeRoot);
+
+ /*
+ * When building the sensors, if any of the dbus passive ones aren't on the
+ * bus, it'll fail immediately.
+ */
+ if (configPath.length() > 0)
+ {
+ try
+ {
+ mgmr = BuildSensorsFromConfig(configPath);
+ zones = BuildZonesFromConfig(configPath, mgmr, ModeControlBus);
+ }
+ catch (const std::exception& e)
+ {
+ std::cerr << "Failed during building: " << e.what() << "\n";
+ exit(EXIT_FAILURE); /* fatal error. */
+ }
+ }
+ else
+ {
+ mgmr = BuildSensors(SensorConfig);
+ zones = BuildZones(ZoneConfig, ZoneDetailsConfig, mgmr, ModeControlBus);
+ }
+
+ if (0 == zones.size())
+ {
+ std::cerr << "No zones defined, exiting.\n";
+ return rc;
+ }
+
+ /*
+ * All sensors are managed by one manager, but each zone has a pointer to
+ * it.
+ */
+
+ auto& HostSensorBus = mgmr->getHostBus();
+ auto& PassiveListeningBus = mgmr->getPassiveBus();
+
+ std::cerr << "Starting threads\n";
+
+ /* TODO(venture): Ask SensorManager if we have any passive sensors. */
+ struct ThreadParams p =
+ {
+ std::ref(PassiveListeningBus),
+ ""
+ };
+ std::thread l(BusThread, std::ref(p));
+
+ /* TODO(venture): Ask SensorManager if we have any host sensors. */
+ static constexpr auto hostBus = "xyz.openbmc_project.Hwmon.external";
+ struct ThreadParams e =
+ {
+ std::ref(HostSensorBus),
+ hostBus
+ };
+ std::thread te(BusThread, std::ref(e));
+
+ static constexpr auto modeBus = "xyz.openbmc_project.State.FanCtrl";
+ struct ThreadParams m =
+ {
+ std::ref(ModeControlBus),
+ modeBus
+ };
+ std::thread tm(BusThread, std::ref(m));
+
+ std::vector<std::thread> zoneThreads;
+
+ /* TODO(venture): This was designed to have one thread per zone, but really
+ * it could have one thread for all the zones and iterate through each
+ * sequentially as it goes -- and it'd probably be fast enough to do that,
+ * however, a system isn't likely going to have more than a couple zones.
+ * If it only has a couple zones, then this is fine.
+ */
+ for (auto& i : zones)
+ {
+ std::cerr << "pushing zone" << std::endl;
+ zoneThreads.push_back(std::thread(PIDControlThread, i.second));
+ }
+
+ l.join();
+ te.join();
+ tm.join();
+ for (auto& t : zoneThreads)
+ {
+ t.join();
+ }
+
+ return rc;
+}
+
diff --git a/scripts/README b/scripts/README
new file mode 100644
index 0000000..3b732e8
--- /dev/null
+++ b/scripts/README
@@ -0,0 +1,25 @@
+# Sensor Config
+This program is only meant to control fans given thermal sensor readings.
+
+All sensors in phosphor-dbus-interfaces for OpenBMC use Sensor.Value as their
+accessor. This provides read-only access to information. The goal of the
+configuration is to specify how it can be read and if it's a fan, how the PID
+output can be written. Initially there'll only be sysfs and passive dbus
+access. If a writepath for a sensor is a dbus path, then the system will need
+to verify which Control.Fan* interfaces is registered and send values to the
+Target property of that interface.
+
+The min/max specified are to range a writePercent to the sensor. The current
+FanController object outputs the new fan speed goal as a PWM percentage. Other
+fan PID control objects may not, and they can leave the fields as 0 & 0.
+
+The only requirement for a sensor is that it isn't writeonly. Only fans are
+expected to have a writepath set, and in this current version non-fan sensors
+are assumed readonly.
+
+The sensor names are unique across all zones.
+
+# PID Config
+
+The PID configuation is a list of PIDs per zone.
+
diff --git a/scripts/pid-example.txt b/scripts/pid-example.txt
new file mode 100644
index 0000000..3549c8d
--- /dev/null
+++ b/scripts/pid-example.txt
@@ -0,0 +1,24 @@
+0x01: /* zone ID */
+ fan2-6: /* PID name */
+ type: fan /* Type of PID, fan, temp, or margin. */
+ inputs: /* Sensor names that are inputs for the PID */
+ fan2
+ fan6
+ /* For temp/margin PIDs this is the set-point, ignored otherwise (float) */
+ set-point: 90.0
+ pid: /* The PID calculation configuration. */
+ sampleperiod: 0.1 /* The input sample period. (float) */
+ p_coefficient: 0.01 /* The proportional coefficient. (float) */
+ i_coefficient: 0.001 /* The integral coefficient. (float) */
+ /* The feed-forward offset coefficient. (float) */
+ ff_off_coefficient: 0.0
+ /* The feed-forward gain coefficient. (float) */
+ ff_gain_coefficient: 0.0
+ i_limit: /* The integral limit clamp, min, max (float) */
+ min: 0
+ max: 100
+ out_limit: /* the PID output clamp, min, max (float) */
+ min: 0
+ max: 100
+ slew_neg: -100 /* The slew negative value. (float) */
+ slew_pos: 0 /* The slew positive value. (float) */
diff --git a/scripts/pid-example.yaml b/scripts/pid-example.yaml
new file mode 100644
index 0000000..ea89b26
--- /dev/null
+++ b/scripts/pid-example.yaml
@@ -0,0 +1,59 @@
+0x01:
+ fan2-6:
+ type: fan
+ inputs:
+ fan2
+ fan6
+ set-point: 90.0
+ pid:
+ sampleperiod: 0.1
+ p_coefficient: 0.01
+ i_coefficient: 0.001
+ ff_off_coefficient: 0.0
+ ff_gain_coefficient: 0.0
+ i_limit:
+ min: 0
+ max: 100
+ out_limit:
+ min: 0
+ max: 100
+ slew_neg: 0
+ slew_pos: 0
+ temp1:
+ type: temp
+ inputs:
+ temp1
+ set-point: 30.0
+ pid:
+ sampleperiod: 1
+ p_coefficient: 94.0
+ i_coefficient: 2.0
+ ff_off_coefficient: 0.0
+ ff_gain_coefficient: 0.0
+ i_limit:
+ min: 3000
+ max: 10000
+ out_limit:
+ min: 3000
+ max: 10000
+ slew_neg: 0
+ slew_pos: 0
+ sluggish0:
+ type: margin
+ inputs:
+ sluggish0
+ set-point: 50
+ pid:
+ sampleperiod: 1
+ p_coefficient: 94.0
+ i_coefficient: 2.0
+ ff_off_coefficient: 0.0
+ ff_gain_coefficient: 0.0
+ i_limit:
+ min: 3000
+ max: 10000
+ out_limit:
+ min: 3000
+ max: 10000
+ slew_neg: 0
+ slew_pos: 0
diff --git a/scripts/pid_gen.py b/scripts/pid_gen.py
new file mode 100644
index 0000000..cd62199
--- /dev/null
+++ b/scripts/pid_gen.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+
+import os
+import sys
+import yaml
+import argparse
+from mako.template import Template
+
+
+def generate_cpp(sensor_yaml, output_dir):
+ with open(os.path.join(script_dir, sensor_yaml), 'r') as f:
+ ifile = yaml.safe_load(f)
+ if not isinstance(ifile, dict):
+ ifile = {}
+
+ # Render the mako template
+
+ t = Template(filename=os.path.join(
+ script_dir,
+ "writepid.mako.cpp"))
+
+ output_cpp = os.path.join(output_dir, "pidlist-gen.cpp")
+ with open(output_cpp, 'w') as fd:
+ fd.write(t.render(PIDDict=ifile))
+
+
+def main():
+
+ valid_commands = {
+ 'generate-cpp': generate_cpp
+ }
+ parser = argparse.ArgumentParser(
+ description="IPMI Sensor parser and code generator")
+
+ parser.add_argument(
+ '-i', '--sensor_yaml', dest='sensor_yaml',
+ default='example.yaml', help='input sensor yaml file to parse')
+
+ parser.add_argument(
+ "-o", "--output-dir", dest="outputdir",
+ default=".",
+ help="output directory")
+
+ parser.add_argument(
+ 'command', metavar='COMMAND', type=str,
+ choices=valid_commands.keys(),
+ help='Command to run.')
+
+ args = parser.parse_args()
+
+ if (not (os.path.isfile(os.path.join(script_dir, args.sensor_yaml)))):
+ sys.exit("Can not find input yaml file " + args.sensor_yaml)
+
+ function = valid_commands[args.command]
+ function(args.sensor_yaml, args.outputdir)
+
+
+if __name__ == '__main__':
+ script_dir = os.path.dirname(os.path.realpath(__file__))
+ main()
diff --git a/scripts/sensor-example.txt b/scripts/sensor-example.txt
new file mode 100644
index 0000000..0915163
--- /dev/null
+++ b/scripts/sensor-example.txt
@@ -0,0 +1,29 @@
+fan2: /* Name of the sensor. */
+ type: fan /* Type of sensor, fan, temp, margin */
+ /* How the sensor can be read[1] */
+ readpath: /xyz/openbmc_project/sensors/fan_tach/fan2
+ /* How the sensor can be set[2] */
+ writepath: /sys/class/hwmon/hwmon0/pwm1
+ /* The minimum value used for scaling writes (int64) */
+ min: 0
+ /* The maximum value used for scaling writes (int64) */
+ max: 255
+ /* The timeout value for the sensor, used for failsafe, 0 means no timeout
+ * (int64)
+ */
+ timeout: 0
+
+[1] readpath has multiple options:
+* If it has "/xyz/openbmc_project/extsensors/" in it, it's an EXTERNAL or
+ host-provided sensor.
+* If it has "/xyz/openbmc_project/" in it, it's a sensor whose value is
+ received passively over dbus.
+* If it has "/sys/" in it, it's a sensor read directly from sysfs.
+
+[2]
+* This can be left blank if the sensor is read-only.
+* If it has "/sys/" in it, it's a sensor written to sysfs.
+ * If min and max are non-zero, it'll convert the value to within the range.
+ and output that modified value. So, if it receives a value of .90 and min
+ is 0, and max is 255, it'll convert that to a value of 229.5 that is then
+ cast to int64_t.
diff --git a/scripts/sensor-example.yaml b/scripts/sensor-example.yaml
new file mode 100644
index 0000000..7378bff
--- /dev/null
+++ b/scripts/sensor-example.yaml
@@ -0,0 +1,31 @@
+fan2:
+ type: fan
+ readpath: /xyz/openbmc_project/sensors/fan_tach/fan2
+ writepath: /sys/class/hwmon/hwmon0/pwm1
+ min: 0
+ max: 255
+ timeout: 0
+
+fan6:
+ type: fan
+ readpath: /xyz/openbmc_project/sensors/fan_tach/fan6
+ writepath:
+ min: 0
+ max: 0
+ timeout: 0
+
+temp1:
+ type: temp
+ readpath: /xyz/openbmc_project/sensors/temperature/temp1
+ writepath:
+ min: 0
+ max: 0
+ timeout: 0
+
+sluggish0:
+ type: margin
+ readpath: /xyz/openbmc_project/extsensors/margin/sluggish0
+ writepath:
+ min: 0
+ max: 0
+ timeout: 0
diff --git a/scripts/sensor_gen.py b/scripts/sensor_gen.py
new file mode 100644
index 0000000..7e9c6ca
--- /dev/null
+++ b/scripts/sensor_gen.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+
+import os
+import sys
+import yaml
+import argparse
+from mako.template import Template
+
+
+def generate_cpp(sensor_yaml, output_dir):
+ with open(os.path.join(script_dir, sensor_yaml), 'r') as f:
+ ifile = yaml.safe_load(f)
+ if not isinstance(ifile, dict):
+ ifile = {}
+
+ # Render the mako template
+
+ t = Template(filename=os.path.join(
+ script_dir,
+ "writesensor.mako.cpp"))
+
+ output_cpp = os.path.join(output_dir, "sensorlist-gen.cpp")
+ with open(output_cpp, 'w') as fd:
+ fd.write(t.render(sensorDict=ifile))
+
+
+def main():
+
+ valid_commands = {
+ 'generate-cpp': generate_cpp
+ }
+ parser = argparse.ArgumentParser(
+ description="IPMI Sensor parser and code generator")
+
+ parser.add_argument(
+ '-i', '--sensor_yaml', dest='sensor_yaml',
+ default='example.yaml', help='input sensor yaml file to parse')
+
+ parser.add_argument(
+ "-o", "--output-dir", dest="outputdir",
+ default=".",
+ help="output directory")
+
+ parser.add_argument(
+ 'command', metavar='COMMAND', type=str,
+ choices=valid_commands.keys(),
+ help='Command to run.')
+
+ args = parser.parse_args()
+
+ if (not (os.path.isfile(os.path.join(script_dir, args.sensor_yaml)))):
+ sys.exit("Can not find input yaml file " + args.sensor_yaml)
+
+ function = valid_commands[args.command]
+ function(args.sensor_yaml, args.outputdir)
+
+
+if __name__ == '__main__':
+ script_dir = os.path.dirname(os.path.realpath(__file__))
+ main()
\ No newline at end of file
diff --git a/scripts/writepid.mako.cpp b/scripts/writepid.mako.cpp
new file mode 100644
index 0000000..f168e10
--- /dev/null
+++ b/scripts/writepid.mako.cpp
@@ -0,0 +1,48 @@
+## This file is a template. The comment below is emitted
+## into the rendered file; feel free to edit this file.
+
+// !!! WARNING: This is GENERATED Code... Please do NOT edit !!!
+
+#include <map>
+#include "conf.hpp"
+
+std::map<int64_t, PIDConf> ZoneConfig = {
+% for zone in PIDDict.iterkeys():
+ % if zone:
+ {${zone},
+ {
+ % for key, details in PIDDict[zone].iteritems():
+ {"${key}",
+ {"${details['type']}",
+ {
+ % for item in details['inputs'].split():
+ "${item}",
+ % endfor
+ },
+ <%
+ # If the PID type is a fan, set-point field is unused,
+ # so just use a default of 0. If the PID is not a type
+ # of fan, require the set-point field.
+ if 'fan' == details['type']:
+ setpoint = 0
+ else:
+ setpoint = details['set-point']
+ %>
+ ${setpoint},
+ {${details['pid']['sampleperiod']},
+ ${details['pid']['p_coefficient']},
+ ${details['pid']['i_coefficient']},
+ ${details['pid']['ff_off_coefficient']},
+ ${details['pid']['ff_gain_coefficient']},
+ {${details['pid']['i_limit']['min']}, ${details['pid']['i_limit']['max']}},
+ {${details['pid']['out_limit']['min']}, ${details['pid']['out_limit']['max']}},
+ ${details['pid']['slew_neg']},
+ ${details['pid']['slew_pos']}},
+ },
+ },
+ % endfor
+ },
+ },
+ % endif
+% endfor
+};
diff --git a/scripts/writesensor.mako.cpp b/scripts/writesensor.mako.cpp
new file mode 100644
index 0000000..8be30bb
--- /dev/null
+++ b/scripts/writesensor.mako.cpp
@@ -0,0 +1,36 @@
+## This file is a template. The comment below is emitted
+## into the rendered file; feel free to edit this file.
+
+// !!! WARNING: This is GENERATED Code... Please do NOT edit !!!
+
+#include <map>
+#include "conf.hpp"
+
+std::map<std::string, struct sensor> SensorConfig = {
+% for key in sensorDict.iterkeys():
+ % if key:
+ <%
+ sensor = sensorDict[key]
+ type = sensor["type"]
+ readpath = sensor["readpath"]
+ writepath = sensor.get("writepath", "")
+ min = sensor.get("min", 0)
+ max = sensor.get("max", 0)
+ # Presently only thermal inputs have their timeout
+ # checked, but we should default it as 2s, which is
+ # the previously hard-coded value.
+ # If it's a fan sensor though, let's set the default
+ # to 0.
+ if type == "fan":
+ timeout = sensor.get("timeout", 0)
+ else:
+ timeout = sensor.get("timeout", 2)
+ %>
+ {"${key}",
+ {"${type}","${readpath}","${writepath}", ${min}, ${max}, ${timeout}},
+ },
+ % endif
+% endfor
+};
+
+
diff --git a/scripts/writezone.mako.cpp b/scripts/writezone.mako.cpp
new file mode 100644
index 0000000..57b9908
--- /dev/null
+++ b/scripts/writezone.mako.cpp
@@ -0,0 +1,22 @@
+## This file is a template. The comment below is emitted
+## into the rendered file; feel free to edit this file.
+
+// !!! WARNING: This is GENERATED Code... Please do NOT edit !!!
+
+#include <map>
+#include "conf.hpp"
+
+std::map<int64_t, struct zone> ZoneDetailsConfig = {
+% for zone in ZoneDict.iterkeys():
+ % if zone:
+ <%
+ zConf = ZoneDict[zone]
+ min = zConf["minthermalrpm"]
+ percent = zConf["failsafepercent"]
+ %>
+ {${zone},
+ {${min}, ${percent}},
+ },
+ % endif
+% endfor
+};
diff --git a/scripts/zone-example.txt b/scripts/zone-example.txt
new file mode 100644
index 0000000..752fc01
--- /dev/null
+++ b/scripts/zone-example.txt
@@ -0,0 +1,4 @@
+0x01: /* The zone ID */
+ minthermalrpm: 3000.0 /* The minimum thermal RPM value. (float) */
+ /* The percent to use when the zone is in fail-safe mode. (float) */
+ failsafepercent: 90.0
diff --git a/scripts/zone-example.yaml b/scripts/zone-example.yaml
new file mode 100644
index 0000000..ef422a5
--- /dev/null
+++ b/scripts/zone-example.yaml
@@ -0,0 +1,3 @@
+0x01:
+ minthermalrpm: 3000.0
+ failsafepercent: 90.0
diff --git a/scripts/zone_gen.py b/scripts/zone_gen.py
new file mode 100644
index 0000000..a98a3b5
--- /dev/null
+++ b/scripts/zone_gen.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+
+import os
+import sys
+import yaml
+import argparse
+from mako.template import Template
+
+
+def generate_cpp(zoneinfo_yaml, output_dir):
+ with open(os.path.join(script_dir, zoneinfo_yaml), 'r') as f:
+ ifile = yaml.safe_load(f)
+ if not isinstance(ifile, dict):
+ ifile = {}
+
+ # Render the mako template
+
+ t = Template(filename=os.path.join(
+ script_dir,
+ "writezone.mako.cpp"))
+
+ output_cpp = os.path.join(output_dir, "zoneinfo-gen.cpp")
+ with open(output_cpp, 'w') as fd:
+ fd.write(t.render(ZoneDict=ifile))
+
+
+def main():
+
+ valid_commands = {
+ 'generate-cpp': generate_cpp
+ }
+ parser = argparse.ArgumentParser(
+ description="IPMI Zone info parser and code generator")
+
+ parser.add_argument(
+ '-i', '--zoneinfo_yaml', dest='zoneinfo_yaml',
+ default='example.yaml', help='input zone yaml file to parse')
+
+ parser.add_argument(
+ "-o", "--output-dir", dest="outputdir",
+ default=".",
+ help="output directory")
+
+ parser.add_argument(
+ 'command', metavar='COMMAND', type=str,
+ choices=valid_commands.keys(),
+ help='Command to run.')
+
+ args = parser.parse_args()
+
+ if (not (os.path.isfile(os.path.join(script_dir, args.zoneinfo_yaml)))):
+ sys.exit("Can not find input yaml file " + args.zoneinfo_yaml)
+
+ function = valid_commands[args.command]
+ function(args.zoneinfo_yaml, args.outputdir)
+
+
+if __name__ == '__main__':
+ script_dir = os.path.dirname(os.path.realpath(__file__))
+ main()
diff --git a/setsensor.cpp b/setsensor.cpp
new file mode 100644
index 0000000..7daa762
--- /dev/null
+++ b/setsensor.cpp
@@ -0,0 +1,100 @@
+#include <iostream>
+#include <string>
+
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/message.hpp>
+
+/* Fan Control */
+static constexpr auto objectPath = "/xyz/openbmc_project/settings/fanctrl/zone";
+static constexpr auto busName = "xyz.openbmc_project.State.FanCtrl";
+static constexpr auto intf = "xyz.openbmc_project.Control.FanCtrl.Mode";
+static constexpr auto property = "Manual";
+using Value = sdbusplus::message::variant<bool>;
+
+/* Host Sensor. */
+static constexpr auto sobjectPath =
+ "/xyz/openbmc_project/extsensors/margin/sluggish0";
+static constexpr auto sbusName = "xyz.openbmc_project.Hwmon.external";
+static constexpr auto sintf = "xyz.openbmc_project.Sensor.Value";
+static constexpr auto sproperty = "Value";
+using sValue = sdbusplus::message::variant<int64_t>;
+
+
+static constexpr auto propertiesintf = "org.freedesktop.DBus.Properties";
+
+static void SetHostSensor(void)
+{
+ int64_t value = 300;
+ sValue v {value};
+
+ std::string busname {sbusName};
+ auto PropertyWriteBus = sdbusplus::bus::new_default();
+ std::string path {sobjectPath};
+
+ auto pimMsg = PropertyWriteBus.new_method_call(
+ busname.c_str(),
+ path.c_str(),
+ propertiesintf,
+ "Set");
+
+ pimMsg.append(sintf);
+ pimMsg.append(sproperty);
+ pimMsg.append(v);
+
+ auto responseMsg = PropertyWriteBus.call(pimMsg);
+ if (responseMsg.is_method_error())
+ {
+ fprintf(stderr, "call to Set the host sensor value failed.\n");
+ }
+ else
+ {
+ fprintf(stderr, "call to Set the host sensor value succeeded.\n");
+ }
+}
+
+static std::string GetControlPath(int8_t zone)
+{
+ return std::string(objectPath) + std::to_string(zone);
+}
+
+static void SetManualMode(int8_t zone)
+{
+ bool setValue = (bool)0x01;
+
+ Value v {setValue};
+
+ std::string busname {busName};
+ auto PropertyWriteBus = sdbusplus::bus::new_default();
+ std::string path = GetControlPath(zone);
+
+ auto pimMsg = PropertyWriteBus.new_method_call(
+ busname.c_str(),
+ path.c_str(),
+ propertiesintf,
+ "Set");
+
+ pimMsg.append(intf);
+ pimMsg.append(property);
+ pimMsg.append(v);
+
+ auto responseMsg = PropertyWriteBus.call(pimMsg);
+ if (responseMsg.is_method_error())
+ {
+ fprintf(stderr, "call to Set the manual mode failed.\n");
+ }
+ else
+ {
+ fprintf(stderr, "call to Set the manual mode succeeded.\n");
+ }
+}
+
+int main(int argc, char* argv[])
+{
+ int rc = 0;
+
+ int64_t zone = 0x01;
+
+ SetManualMode(zone);
+ SetHostSensor();
+ return rc;
+}
diff --git a/threads/busthread.cpp b/threads/busthread.cpp
new file mode 100644
index 0000000..f007bf9
--- /dev/null
+++ b/threads/busthread.cpp
@@ -0,0 +1,37 @@
+/**
+ * Copyright 2017 Google Inc.
+ *
+ * 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 <string>
+
+#include "busthread.hpp"
+
+
+void BusThread(struct ThreadParams& params)
+{
+ if (params.name.length() > 0)
+ {
+ params.bus.request_name(params.name.c_str());
+ }
+
+ while (true)
+ {
+ params.bus.process_discard();
+ /* block indefinitely for updates. */
+ params.bus.wait();
+ }
+
+ return;
+}
diff --git a/threads/busthread.hpp b/threads/busthread.hpp
new file mode 100644
index 0000000..a3234b8
--- /dev/null
+++ b/threads/busthread.hpp
@@ -0,0 +1,11 @@
+#pragma once
+
+#include <sdbusplus/bus.hpp>
+
+struct ThreadParams
+{
+ sdbusplus::bus::bus& bus;
+ std::string name;
+};
+
+void BusThread(struct ThreadParams& params);
diff --git a/util.cpp b/util.cpp
new file mode 100644
index 0000000..cf9ef81
--- /dev/null
+++ b/util.cpp
@@ -0,0 +1,72 @@
+/**
+ * Copyright 2017 Google Inc.
+ *
+ * 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 <string>
+
+#include "util.hpp"
+
+
+static constexpr auto external_sensor =
+ "/xyz/openbmc_project/extsensors/"; // type/
+static constexpr auto openbmc_sensor = "/xyz/openbmc_project/"; // type/
+static constexpr auto sysfs = "/sys/";
+
+
+IOInterfaceType GetWriteInterfaceType(const std::string& path)
+{
+ std::string::size_type n;
+
+ if (path.empty() || "None" == path)
+ {
+ return IOInterfaceType::NONE;
+ }
+
+ if (path.find(sysfs) != std::string::npos)
+ {
+ // A sysfs read sensor.
+ return IOInterfaceType::SYSFS;
+ }
+
+ return IOInterfaceType::UNKNOWN;
+}
+
+IOInterfaceType GetReadInterfaceType(const std::string& path)
+{
+ std::string::size_type n;
+
+ if (path.empty() || "None" == path)
+ {
+ return IOInterfaceType::NONE;
+ }
+
+ if (path.find(external_sensor) != std::string::npos)
+ {
+ return IOInterfaceType::EXTERNAL;
+ }
+
+ if (path.find(openbmc_sensor) != std::string::npos)
+ {
+ return IOInterfaceType::DBUSPASSIVE;
+ }
+
+ if (path.find(sysfs) != std::string::npos)
+ {
+ return IOInterfaceType::SYSFS;
+ }
+
+ return IOInterfaceType::UNKNOWN;
+}
+
diff --git a/util.hpp b/util.hpp
new file mode 100644
index 0000000..2c5eccd
--- /dev/null
+++ b/util.hpp
@@ -0,0 +1,25 @@
+#pragma once
+
+/* This program assumes sensors use the Sensor.Value interface
+ * and for sensor->write() I only implemented sysfs as a type,
+ * but -- how would it know whether to use Control.FanSpeed or Control.FanPwm?
+ *
+ * One could get the interface list for the object and search for Control.*
+ * but, it needs to know the maximum, minimum. The only sensors it wants to write
+ * in this code base are Fans...
+ */
+enum class IOInterfaceType
+{
+ NONE, // There is no interface.
+ EXTERNAL,
+ DBUSPASSIVE,
+ DBUSACTIVE, // This means for write that it needs to look up the interface.
+ SYSFS,
+ UNKNOWN
+};
+
+/* WriteInterfaceType is different because Dbusactive/passive. how to know... */
+IOInterfaceType GetWriteInterfaceType(const std::string& path);
+
+IOInterfaceType GetReadInterfaceType(const std::string& path);
+