phosphor-pid-control: Removing local patches
All local patches have been merged upstream! For the moment, all local
recipe modifications can be removed, as they are no longer needed.
Tested: Compiled and running on "platform15" machine.
Google-Bug-Id: 307599367
Google-Bug-Id: 266275164
Google-Bug-Id: 269219517
Change-Id: Icc660aa72f5e4c3bda6dbd8e45da162aa3941afb
Signed-off-by: Josh Lehan <krellan@google.com>
diff --git a/recipes-phosphor/fans/phosphor-pid-control/0001-Implementing-the-TempToMargin-feature.patch b/recipes-phosphor/fans/phosphor-pid-control/0001-Implementing-the-TempToMargin-feature.patch
deleted file mode 100644
index aae719b..0000000
--- a/recipes-phosphor/fans/phosphor-pid-control/0001-Implementing-the-TempToMargin-feature.patch
+++ /dev/null
@@ -1,767 +0,0 @@
-From 27ccf73dd408c79ff44770a0fee48fa6514b5199 Mon Sep 17 00:00:00 2001
-From: Josh Lehan <krellan@google.com>
-Date: Fri, 13 Jan 2023 11:06:16 -0800
-Subject: [PATCH] Implementing the TempToMargin feature
-
-Wrapping the input name std::string in a new structure SensorInput, so
-that the TempToMargin information can be cleanly carried along with
-it, all the way down to the PID input processing layer where it is
-needed. This allows the conversion to be done just-in-time before the
-temperature reading is interpreted, minimizing the blast radius of
-this change. Nonetheless, because of the type change, there was a
-somewhat large blast radius to implement this feature.
-
-The design, and the documentation, is already here:
-https://github.com/openbmc/phosphor-pid-control/issues/23
-
-Tested: Added unit tests for JSON parsing and for proper execution
-of the TempToMargin feature. They pass. Ran it locally, on our
-appropriately-configured system, and it seems to work for me.
-
-Patch Tracking Bug: b/266275164
-Upstream info / review: https://gerrit.openbmc.org/c/openbmc/phosphor-pid-control/+/60303
-Upstream-Status: Submitted
-Justification: Awaiting review
-
-Change-Id: I598ba485195aaa70c26e91a1da3ab88fff8c3a4c
-Signed-off-by: Josh Lehan <krellan@google.com>
----
- conf.hpp | 16 +++++-
- dbus/dbusconfiguration.cpp | 39 +++++++++++++--
- pid/builder.cpp | 22 ++++++---
- pid/buildjson.cpp | 18 ++++++-
- pid/ec/pid.hpp | 4 +-
- pid/pidcontroller.hpp | 1 +
- pid/thermalcontroller.cpp | 39 ++++++++++++---
- pid/thermalcontroller.hpp | 14 +++---
- test/meson.build | 3 +-
- test/pid_json_unittest.cpp | 65 +++++++++++++++++++++++++
- test/pid_thermalcontroller_unittest.cpp | 47 ++++++++++++++----
- util.cpp | 54 +++++++++++++++++++-
- util.hpp | 14 ++++++
- 13 files changed, 294 insertions(+), 42 deletions(-)
-
-diff --git a/conf.hpp b/conf.hpp
-index f3811e4..0e26f55 100644
---- a/conf.hpp
-+++ b/conf.hpp
-@@ -3,12 +3,14 @@
- #include "pid/ec/pid.hpp"
- #include "pid/ec/stepwise.hpp"
-
-+#include <limits>
- #include <map>
- #include <string>
- #include <vector>
-
- namespace pid_control
- {
-+
- namespace conf
- {
-
-@@ -30,13 +32,24 @@ struct SensorConfig
- bool unavailableAsFailed;
- };
-
-+/*
-+ * Structure for decorating an input sensor's name with additional
-+ * information, to help out with TempToMargin conversion.
-+ */
-+struct SensorInput
-+{
-+ std::string name;
-+ double convertMarginZero = std::numeric_limits<double>::quiet_NaN();
-+ bool convertTempToMargin = false;
-+};
-+
- /*
- * Structure for holding the configuration of a PID.
- */
- struct ControllerInfo
- {
- std::string type; // fan or margin or temp?
-- std::vector<std::string> inputs; // one or more sensors.
-+ std::vector<SensorInput> inputs; // one or more sensors.
- double setpoint; // initial setpoint for thermal.
- ec::pidinfo pidInfo; // pid details
- ec::StepwiseInfo stepwiseInfo;
-@@ -74,4 +87,5 @@ using PIDConf = std::map<std::string, ControllerInfo>;
- constexpr bool DEBUG = false; // enable to print found configuration
-
- } // namespace conf
-+
- } // namespace pid_control
-diff --git a/dbus/dbusconfiguration.cpp b/dbus/dbusconfiguration.cpp
-index d784b2c..2473252 100644
---- a/dbus/dbusconfiguration.cpp
-+++ b/dbus/dbusconfiguration.cpp
-@@ -338,7 +338,13 @@ void populatePidInfo(
- {
- interface = thresholds::criticalInterface;
- }
-- const std::string& path = sensorConfig.at(info.inputs.front()).readPath;
-+
-+ // Although this checks only the first vector element for the
-+ // named threshold, it is OK, because the SetPointOffset parser
-+ // splits up the input into individual vectors, each with only a
-+ // single element, if it detects that SetPointOffset is in use.
-+ const std::string& path =
-+ sensorConfig.at(info.inputs.front().name).readPath;
-
- DbusHelper helper(sdbusplus::bus::new_system());
- std::string service = helper.getService(interface, path);
-@@ -848,19 +854,32 @@ bool init(sdbusplus::bus_t& bus, boost::asio::steady_timer& timer,
- }
- }
-
-+ std::vector<double> inputTempToMargin;
-+
-+ auto findTempToMargin = base.find("TempToMargin");
-+ if (findTempToMargin != base.end())
-+ {
-+ inputTempToMargin =
-+ std::get<std::vector<double>>(findTempToMargin->second);
-+ }
-+
-+ std::vector<pid_control::conf::SensorInput> sensorInputs =
-+ spliceInputs(inputSensorNames, inputTempToMargin);
-+
- if (offsetType.empty())
- {
- conf::ControllerInfo& info = conf[pidName];
-- info.inputs = std::move(inputSensorNames);
-+ info.inputs = std::move(sensorInputs);
- populatePidInfo(bus, base, info, nullptr, sensorConfig);
- }
- else
- {
- // we have to split up the inputs, as in practice t-control
- // values will differ, making setpoints differ
-- for (const std::string& input : inputSensorNames)
-+ for (const pid_control::conf::SensorInput& input :
-+ sensorInputs)
- {
-- conf::ControllerInfo& info = conf[input];
-+ conf::ControllerInfo& info = conf[input.name];
- info.inputs.emplace_back(input);
- populatePidInfo(bus, base, info, &offsetType,
- sensorConfig);
-@@ -932,7 +951,17 @@ bool init(sdbusplus::bus_t& bus, boost::asio::steady_timer& timer,
- continue;
- }
- conf::ControllerInfo& info = conf[pidName];
-- info.inputs = std::move(inputs);
-+
-+ std::vector<double> inputTempToMargin;
-+
-+ auto findTempToMargin = base.find("TempToMargin");
-+ if (findTempToMargin != base.end())
-+ {
-+ inputTempToMargin =
-+ std::get<std::vector<double>>(findTempToMargin->second);
-+ }
-+
-+ info.inputs = spliceInputs(inputs, inputTempToMargin);
-
- info.type = "stepwise";
- info.stepwiseInfo.ts = 1.0; // currently unused
-diff --git a/pid/builder.cpp b/pid/builder.cpp
-index 8525073..bae0e81 100644
---- a/pid/builder.cpp
-+++ b/pid/builder.cpp
-@@ -23,6 +23,7 @@
- #include "pid/thermalcontroller.hpp"
- #include "pid/zone.hpp"
- #include "pid/zone_interface.hpp"
-+#include "util.hpp"
-
- #include <sdbusplus/bus.hpp>
-
-@@ -82,7 +83,7 @@ std::unordered_map<int64_t, std::shared_ptr<ZoneInterface>>
- // For each PID create a Controller and a Sensor.
- for (const auto& [name, info] : pidConfig)
- {
-- std::vector<std::string> inputs;
-+ std::vector<pid_control::conf::SensorInput> inputs;
- std::cerr << "PID name: " << name << "\n";
-
- /*
-@@ -94,11 +95,11 @@ std::unordered_map<int64_t, std::shared_ptr<ZoneInterface>>
- for (const auto& i : info.inputs)
- {
- inputs.push_back(i);
-- zone->addFanInput(i);
-+ zone->addFanInput(i.name);
- }
-
-- auto pid = FanController::createFanPid(zone.get(), name, inputs,
-- info.pidInfo);
-+ auto pid = FanController::createFanPid(
-+ zone.get(), name, splitNames(inputs), info.pidInfo);
- zone->addFanPID(std::move(pid));
- zone->addPidFailSafePercent(name, info.failSafePercent);
- }
-@@ -107,7 +108,7 @@ std::unordered_map<int64_t, std::shared_ptr<ZoneInterface>>
- for (const auto& i : info.inputs)
- {
- inputs.push_back(i);
-- zone->addThermalInput(i);
-+ zone->addThermalInput(i.name);
- }
-
- auto pid = ThermalController::createThermalPid(
-@@ -125,10 +126,10 @@ std::unordered_map<int64_t, std::shared_ptr<ZoneInterface>>
- for (const auto& i : info.inputs)
- {
- inputs.push_back(i);
-- zone->addThermalInput(i);
-+ zone->addThermalInput(i.name);
- }
- auto stepwise = StepwiseController::createStepwiseController(
-- zone.get(), name, inputs, info.stepwiseInfo);
-+ zone.get(), name, splitNames(inputs), info.stepwiseInfo);
- zone->addThermalPID(std::move(stepwise));
- zone->addPidControlProcess(
- name, info.type, info.setpoint, modeControlBus,
-@@ -139,7 +140,12 @@ std::unordered_map<int64_t, std::shared_ptr<ZoneInterface>>
- std::cerr << "inputs: ";
- for (const auto& i : inputs)
- {
-- std::cerr << i << ", ";
-+ std::cerr << i.name;
-+ if (i.convertTempToMargin)
-+ {
-+ std::cerr << "[" << i.convertMarginZero << "]";
-+ }
-+ std::cerr << ", ";
- }
- std::cerr << "\n";
- }
-diff --git a/pid/buildjson.cpp b/pid/buildjson.cpp
-index 57ea943..8396338 100644
---- a/pid/buildjson.cpp
-+++ b/pid/buildjson.cpp
-@@ -17,10 +17,12 @@
- #include "pid/buildjson.hpp"
-
- #include "conf.hpp"
-+#include "util.hpp"
-
- #include <nlohmann/json.hpp>
-
- #include <iostream>
-+#include <limits>
- #include <map>
- #include <tuple>
-
-@@ -31,12 +33,25 @@ using json = nlohmann::json;
-
- namespace conf
- {
-+
- void from_json(const json& j, conf::ControllerInfo& c)
- {
-+ std::vector<std::string> inputNames;
-+
- j.at("type").get_to(c.type);
-- j.at("inputs").get_to(c.inputs);
-+ j.at("inputs").get_to(inputNames);
- j.at("setpoint").get_to(c.setpoint);
-
-+ std::vector<double> inputTempToMargin;
-+
-+ auto findTempToMargin = j.find("tempToMargin");
-+ if (findTempToMargin != j.end())
-+ {
-+ findTempToMargin->get_to(inputTempToMargin);
-+ }
-+
-+ c.inputs = spliceInputs(inputNames, inputTempToMargin);
-+
- /* TODO: We need to handle parsing other PID controller configurations.
- * We can do that by checking for different keys and making the decision
- * accordingly.
-@@ -135,6 +150,7 @@ void from_json(const json& j, conf::ControllerInfo& c)
- c.stepwiseInfo.negativeHysteresis = negativeHysteresisValue;
- }
- }
-+
- } // namespace conf
-
- inline void getCycleTimeSetting(const auto& zone, const int id,
-diff --git a/pid/ec/pid.hpp b/pid/ec/pid.hpp
-index 378f93c..9dac6a4 100644
---- a/pid/ec/pid.hpp
-+++ b/pid/ec/pid.hpp
-@@ -15,14 +15,14 @@ typedef struct
- } limits_t;
-
- /* Note: If you update these structs you need to update the copy code in
-- * pid/util.cpp.
-+ * pid/util.cpp and the initialization code in pid/buildjson.hpp files.
- */
- typedef struct
- {
- bool initialized; // has pid been initialized
-
- double ts; // sample time in seconds
-- double integral; // intergal of error
-+ double integral; // integral of error
- double lastOutput; // value of last output
- double lastError; // value of last error
-
-diff --git a/pid/pidcontroller.hpp b/pid/pidcontroller.hpp
-index c3e9999..05cbd71 100644
---- a/pid/pidcontroller.hpp
-+++ b/pid/pidcontroller.hpp
-@@ -29,6 +29,7 @@ class PIDController : public Controller
- _pid_info.lastOutput = static_cast<double>(0.0);
- _pid_info.proportionalCoeff = static_cast<double>(0.0);
- _pid_info.integralCoeff = static_cast<double>(0.0);
-+ _pid_info.derivativeCoeff = static_cast<double>(0.0);
- _pid_info.feedFwdOffset = static_cast<double>(0.0);
- _pid_info.feedFwdGain = static_cast<double>(0.0);
- _pid_info.integralLimit.min = static_cast<double>(0.0);
-diff --git a/pid/thermalcontroller.cpp b/pid/thermalcontroller.cpp
-index 357437b..ddb5893 100644
---- a/pid/thermalcontroller.cpp
-+++ b/pid/thermalcontroller.cpp
-@@ -55,7 +55,7 @@ bool isThermalType(const std::string& typeString)
-
- std::unique_ptr<PIDController> ThermalController::createThermalPid(
- ZoneInterface* owner, const std::string& id,
-- const std::vector<std::string>& inputs, double setpoint,
-+ const std::vector<pid_control::conf::SensorInput>& inputs, double setpoint,
- const ec::pidinfo& initial, const ThermalType& type)
- {
- // ThermalController requires at least 1 input
-@@ -101,12 +101,12 @@ double ThermalController::inputProc(void)
- throw ControllerBuildException("Unrecognized ThermalType");
- }
-
-- std::string leaderName = *(_inputs.begin());
-+ std::string leaderName = _inputs.begin()->name;
-
- bool acceptable = false;
- for (const auto& in : _inputs)
- {
-- double cachedValue = _owner->getCachedValue(in);
-+ double cachedValue = _owner->getCachedValue(in.name);
-
- // Less than 0 is perfectly OK for temperature, but must not be NAN
- if (!(std::isfinite(cachedValue)))
-@@ -114,6 +114,30 @@ double ThermalController::inputProc(void)
- continue;
- }
-
-+ // Perform TempToMargin conversion before further processing
-+ if (type == ThermalType::margin)
-+ {
-+ if (in.convertTempToMargin)
-+ {
-+ if (!(std::isfinite(in.convertMarginZero)))
-+ {
-+ throw ControllerBuildException("Unrecognized TempToMargin");
-+ }
-+
-+ double marginValue = in.convertMarginZero - cachedValue;
-+
-+ if (debugEnabled)
-+ {
-+ std::cerr << "Converting temp to margin: temp "
-+ << cachedValue << ", Tjmax "
-+ << in.convertMarginZero << ", margin "
-+ << marginValue << "\n";
-+ }
-+
-+ cachedValue = marginValue;
-+ }
-+ }
-+
- double oldValue = value;
-
- if (doSummation)
-@@ -127,7 +151,7 @@ double ThermalController::inputProc(void)
-
- if (oldValue != value)
- {
-- leaderName = in;
-+ leaderName = in.name;
- _owner->updateThermalPowerDebugInterface(_id, leaderName, value, 0);
- }
-
-@@ -136,8 +160,11 @@ double ThermalController::inputProc(void)
-
- if (!acceptable)
- {
-- // While not optimal, zero is better than garbage
-- value = 0;
-+ // If none of the inputs were acceptable, use the setpoint as
-+ // the input value. This will continue to run the PID loop, but
-+ // make it a no-op, as the error will be zero. This provides safe
-+ // behavior until the inputs become acceptable.
-+ value = setptProc();
- }
-
- if (debugEnabled)
-diff --git a/pid/thermalcontroller.hpp b/pid/thermalcontroller.hpp
-index 752c105..ac551d2 100644
---- a/pid/thermalcontroller.hpp
-+++ b/pid/thermalcontroller.hpp
-@@ -1,5 +1,6 @@
- #pragma once
-
-+#include "conf.hpp"
- #include "ec/pid.hpp"
- #include "pidcontroller.hpp"
-
-@@ -44,14 +45,13 @@ bool isThermalType(const std::string& typeString);
- class ThermalController : public PIDController
- {
- public:
-- static std::unique_ptr<PIDController>
-- createThermalPid(ZoneInterface* owner, const std::string& id,
-- const std::vector<std::string>& inputs,
-- double setpoint, const ec::pidinfo& initial,
-- const ThermalType& type);
-+ static std::unique_ptr<PIDController> createThermalPid(
-+ ZoneInterface* owner, const std::string& id,
-+ const std::vector<pid_control::conf::SensorInput>& inputs,
-+ double setpoint, const ec::pidinfo& initial, const ThermalType& type);
-
- ThermalController(const std::string& id,
-- const std::vector<std::string>& inputs,
-+ const std::vector<pid_control::conf::SensorInput>& inputs,
- const ThermalType& type, ZoneInterface* owner) :
- PIDController(id, owner),
- _inputs(inputs), type(type)
-@@ -62,7 +62,7 @@ class ThermalController : public PIDController
- void outputProc(double value) override;
-
- private:
-- std::vector<std::string> _inputs;
-+ std::vector<pid_control::conf::SensorInput> _inputs;
- ThermalType type;
- };
-
-diff --git a/test/meson.build b/test/meson.build
-index 966334e..2513278 100644
---- a/test/meson.build
-+++ b/test/meson.build
-@@ -42,7 +42,8 @@ unittest_source = {
- '../dbus/dbusutil.cpp'],
- 'dbus_util_unittest': ['../dbus/dbusutil.cpp'],
- 'json_parse_unittest': ['../buildjson/buildjson.cpp'],
-- 'pid_json_unittest': ['../pid/buildjson.cpp'],
-+ 'pid_json_unittest': ['../pid/buildjson.cpp',
-+ '../util.cpp'],
- 'pid_fancontroller_unittest': ['../pid/ec/pid.cpp',
- '../pid/ec/logging.cpp',
- '../pid/fancontroller.cpp',
-diff --git a/test/pid_json_unittest.cpp b/test/pid_json_unittest.cpp
-index c76849a..59db343 100644
---- a/test/pid_json_unittest.cpp
-+++ b/test/pid_json_unittest.cpp
-@@ -73,6 +73,71 @@ TEST(ZoneFromJson, oneZoneOnePid)
- EXPECT_DOUBLE_EQ(zoneConfig[1].minThermalOutput, 3000.0);
- }
-
-+TEST(ZoneFromJson, marginZone)
-+{
-+ // Parse a valid configuration with one zone and one PID.
-+ // This is a margin zone, and has both kinds of temperature
-+ // sensors in it, absolute temperature and margin temperature.
-+ // Tests that TempToMargin is parsed correctly.
-+
-+ std::map<int64_t, conf::PIDConf> pidConfig;
-+ std::map<int64_t, conf::ZoneConfig> zoneConfig;
-+
-+ auto j2 = R"(
-+ {
-+ "zones" : [{
-+ "id": 1,
-+ "minThermalOutput": 3000.0,
-+ "failsafePercent": 75.0,
-+ "pids": [{
-+ "name": "myPid",
-+ "type": "margin",
-+ "inputs": ["absolute0", "absolute1", "margin0", "margin1"],
-+ "tempToMargin": [
-+ 85.0,
-+ 100.0
-+ ],
-+ "setpoint": 10.0,
-+ "pid": {
-+ "samplePeriod": 0.1,
-+ "proportionalCoeff": 0.0,
-+ "integralCoeff": 0.0,
-+ "feedFwdOffsetCoeff": 0.0,
-+ "feedFwdGainCoeff": 0.010,
-+ "integralLimit_min": 0.0,
-+ "integralLimit_max": 0.0,
-+ "outLim_min": 30.0,
-+ "outLim_max": 100.0,
-+ "slewNeg": 0.0,
-+ "slewPos": 0.0
-+ }
-+ }]
-+ }]
-+ }
-+ )"_json;
-+
-+ std::tie(pidConfig, zoneConfig) = buildPIDsFromJson(j2);
-+ EXPECT_EQ(pidConfig.size(), static_cast<u_int64_t>(1));
-+ EXPECT_EQ(zoneConfig.size(), static_cast<u_int64_t>(1));
-+
-+ EXPECT_EQ(pidConfig[1]["myPid"].type, "margin");
-+ EXPECT_DOUBLE_EQ(zoneConfig[1].minThermalOutput, 3000.0);
-+
-+ EXPECT_EQ(pidConfig[1]["myPid"].inputs[0].name, "absolute0");
-+ EXPECT_DOUBLE_EQ(pidConfig[1]["myPid"].inputs[0].convertMarginZero, 85.0);
-+ EXPECT_EQ(pidConfig[1]["myPid"].inputs[0].convertTempToMargin, true);
-+
-+ EXPECT_EQ(pidConfig[1]["myPid"].inputs[1].name, "absolute1");
-+ EXPECT_DOUBLE_EQ(pidConfig[1]["myPid"].inputs[1].convertMarginZero, 100.0);
-+ EXPECT_EQ(pidConfig[1]["myPid"].inputs[1].convertTempToMargin, true);
-+
-+ EXPECT_EQ(pidConfig[1]["myPid"].inputs[2].name, "margin0");
-+ EXPECT_EQ(pidConfig[1]["myPid"].inputs[2].convertTempToMargin, false);
-+
-+ EXPECT_EQ(pidConfig[1]["myPid"].inputs[3].name, "margin1");
-+ EXPECT_EQ(pidConfig[1]["myPid"].inputs[3].convertTempToMargin, false);
-+}
-+
- TEST(ZoneFromJson, oneZoneOnePidWithHysteresis)
- {
- // Parse a valid configuration with one zone and one PID and the PID uses
-diff --git a/test/pid_thermalcontroller_unittest.cpp b/test/pid_thermalcontroller_unittest.cpp
-index 6069154..7aee1e1 100644
---- a/test/pid_thermalcontroller_unittest.cpp
-+++ b/test/pid_thermalcontroller_unittest.cpp
-@@ -1,3 +1,4 @@
-+#include "conf.hpp"
- #include "pid/ec/logging.hpp"
- #include "pid/ec/pid.hpp"
- #include "pid/thermalcontroller.hpp"
-@@ -25,7 +26,7 @@ TEST(ThermalControllerTest, BoringFactoryTest)
-
- ZoneMock z;
-
-- std::vector<std::string> inputs = {"fleeting0"};
-+ std::vector<pid_control::conf::SensorInput> inputs = {{"fleeting0"}};
- double setpoint = 10.0;
- ec::pidinfo initial;
-
-@@ -41,7 +42,7 @@ TEST(ThermalControllerTest, VerifyFactoryFailsWithZeroInputs)
-
- ZoneMock z;
-
-- std::vector<std::string> inputs = {};
-+ std::vector<pid_control::conf::SensorInput> inputs = {};
- double setpoint = 10.0;
- ec::pidinfo initial;
- std::unique_ptr<PIDController> p;
-@@ -60,7 +61,7 @@ TEST(ThermalControllerTest, InputProc_BehavesAsExpected)
-
- ZoneMock z;
-
-- std::vector<std::string> inputs = {"fleeting0"};
-+ std::vector<pid_control::conf::SensorInput> inputs = {{"fleeting0"}};
- double setpoint = 10.0;
- ec::pidinfo initial;
-
-@@ -79,7 +80,7 @@ TEST(ThermalControllerTest, SetPtProc_BehavesAsExpected)
-
- ZoneMock z;
-
-- std::vector<std::string> inputs = {"fleeting0"};
-+ std::vector<pid_control::conf::SensorInput> inputs = {{"fleeting0"}};
- double setpoint = 10.0;
- ec::pidinfo initial;
-
-@@ -96,7 +97,7 @@ TEST(ThermalControllerTest, OutputProc_BehavesAsExpected)
-
- ZoneMock z;
-
-- std::vector<std::string> inputs = {"fleeting0"};
-+ std::vector<pid_control::conf::SensorInput> inputs = {{"fleeting0"}};
- double setpoint = 10.0;
- ec::pidinfo initial;
-
-@@ -117,7 +118,8 @@ TEST(ThermalControllerTest, InputProc_MultipleInputsAbsolute)
-
- ZoneMock z;
-
-- std::vector<std::string> inputs = {"fleeting0", "fleeting1"};
-+ std::vector<pid_control::conf::SensorInput> inputs = {{"fleeting0"},
-+ {"fleeting1"}};
- double setpoint = 10.0;
- ec::pidinfo initial;
-
-@@ -138,7 +140,8 @@ TEST(ThermalControllerTest, InputProc_MultipleInputsMargin)
-
- ZoneMock z;
-
-- std::vector<std::string> inputs = {"fleeting0", "fleeting1"};
-+ std::vector<pid_control::conf::SensorInput> inputs = {{"fleeting0"},
-+ {"fleeting1"}};
- double setpoint = 10.0;
- ec::pidinfo initial;
-
-@@ -159,7 +162,8 @@ TEST(ThermalControllerTest, InputProc_MultipleInputsSummation)
-
- ZoneMock z;
-
-- std::vector<std::string> inputs = {"fleeting0", "fleeting1"};
-+ std::vector<pid_control::conf::SensorInput> inputs = {{"fleeting0"},
-+ {"fleeting1"}};
- double setpoint = 10.0;
- ec::pidinfo initial;
-
-@@ -173,6 +177,29 @@ TEST(ThermalControllerTest, InputProc_MultipleInputsSummation)
- EXPECT_EQ(15.0, p->inputProc());
- }
-
-+TEST(ThermalControllerTest, InputProc_MultipleInputsTempToMargin)
-+{
-+ // This test verifies inputProc behaves as expected with multiple margin
-+ // inputs and TempToMargin in use.
-+
-+ ZoneMock z;
-+
-+ std::vector<pid_control::conf::SensorInput> inputs = {
-+ {"absolute0", 85.0, true}, {"margin1"}};
-+ double setpoint = 10.0;
-+ ec::pidinfo initial;
-+
-+ std::unique_ptr<PIDController> p = ThermalController::createThermalPid(
-+ &z, "therm1", inputs, setpoint, initial, ThermalType::margin);
-+ EXPECT_FALSE(p == nullptr);
-+
-+ EXPECT_CALL(z, getCachedValue(StrEq("absolute0"))).WillOnce(Return(82.0));
-+ EXPECT_CALL(z, getCachedValue(StrEq("margin1"))).WillOnce(Return(5.0));
-+
-+ // 82 degrees temp, 85 degrees Tjmax => 3 degrees of safety margin
-+ EXPECT_EQ(3.0, p->inputProc());
-+}
-+
- TEST(ThermalControllerTest, NegHysteresis_BehavesAsExpected)
- {
- // This test verifies Negative hysteresis behaves as expected by
-@@ -181,7 +208,7 @@ TEST(ThermalControllerTest, NegHysteresis_BehavesAsExpected)
-
- ZoneMock z;
-
-- std::vector<std::string> inputs = {"fleeting0"};
-+ std::vector<pid_control::conf::SensorInput> inputs = {{"fleeting0"}};
- double setpoint = 10.0;
- ec::pidinfo initial;
- initial.negativeHysteresis = 4.0;
-@@ -214,7 +241,7 @@ TEST(ThermalControllerTest, PosHysteresis_BehavesAsExpected)
-
- ZoneMock z;
-
-- std::vector<std::string> inputs = {"fleeting0"};
-+ std::vector<pid_control::conf::SensorInput> inputs = {{"fleeting0"}};
- double setpoint = 10.0;
- ec::pidinfo initial;
- initial.positiveHysteresis = 5.0;
-diff --git a/util.cpp b/util.cpp
-index 411b761..87c4deb 100644
---- a/util.cpp
-+++ b/util.cpp
-@@ -69,7 +69,12 @@ void debugPrint(const std::map<std::string, conf::SensorConfig>& sensorConfig,
- std::cout << "\t\t\t{";
- for (const auto& input : pidconf.second.inputs)
- {
-- std::cout << "\n\t\t\t" << input << ",\n";
-+ std::cout << "\n\t\t\t" << input.name;
-+ if (input.convertTempToMargin)
-+ {
-+ std::cout << "[" << input.convertMarginZero << "]";
-+ }
-+ std::cout << ",\n";
- }
- std::cout << "\t\t\t}\n";
- std::cout << "\t\t\t" << pidconf.second.setpoint << ",\n";
-@@ -96,4 +101,51 @@ void debugPrint(const std::map<std::string, conf::SensorConfig>& sensorConfig,
- std::cout << "}\n\n";
- }
-
-+std::vector<conf::SensorInput>
-+ spliceInputs(const std::vector<std::string>& inputNames,
-+ const std::vector<double>& inputTempToMargin)
-+{
-+ std::vector<conf::SensorInput> results;
-+
-+ // Default to the TempToMargin feature disabled
-+ for (const auto& inputName : inputNames)
-+ {
-+ conf::SensorInput newInput{
-+ inputName, std::numeric_limits<double>::quiet_NaN(), false};
-+
-+ results.emplace_back(newInput);
-+ }
-+
-+ size_t resultSize = results.size();
-+ size_t marginSize = inputTempToMargin.size();
-+
-+ for (size_t index = 0; index < resultSize; ++index)
-+ {
-+ // If fewer doubles than strings, and vice versa, ignore remainder
-+ if (index >= marginSize)
-+ {
-+ break;
-+ }
-+
-+ // Both vectors have this index, combine both into SensorInput
-+ results[index].convertMarginZero = inputTempToMargin[index];
-+ results[index].convertTempToMargin = true;
-+ }
-+
-+ return results;
-+}
-+
-+std::vector<std::string>
-+ splitNames(const std::vector<conf::SensorInput>& sensorInputs)
-+{
-+ std::vector<std::string> results;
-+
-+ for (const auto& sensorInput : sensorInputs)
-+ {
-+ results.emplace_back(sensorInput.name);
-+ }
-+
-+ return results;
-+}
-+
- } // namespace pid_control
-diff --git a/util.hpp b/util.hpp
-index 362d517..0588934 100644
---- a/util.hpp
-+++ b/util.hpp
-@@ -40,6 +40,20 @@ const std::string propertiesintf = "org.freedesktop.DBus.Properties";
- */
- std::string FixupPath(std::string original);
-
-+/*
-+ * Splice together two vectors, "Inputs" and "TempToMargin" from JSON,
-+ * into one vector of SensorInput structures containing info from both.
-+ */
-+std::vector<conf::SensorInput>
-+ spliceInputs(const std::vector<std::string>& inputNames,
-+ const std::vector<double>& inputTempToMargin);
-+
-+/*
-+ * Recovers the original "Inputs" vector from spliceInputs().
-+ */
-+std::vector<std::string>
-+ splitNames(const std::vector<conf::SensorInput>& sensorInputs);
-+
- /*
- * Dump active configuration.
- */
---
-2.42.0.655.g421f12c284-goog
-
diff --git a/recipes-phosphor/fans/phosphor-pid-control/0002-Add-MissingIsAcceptable-feature-to-avoid-failsafe.patch b/recipes-phosphor/fans/phosphor-pid-control/0002-Add-MissingIsAcceptable-feature-to-avoid-failsafe.patch
deleted file mode 100644
index 81bb228..0000000
--- a/recipes-phosphor/fans/phosphor-pid-control/0002-Add-MissingIsAcceptable-feature-to-avoid-failsafe.patch
+++ /dev/null
@@ -1,700 +0,0 @@
-From 235f4a1a259c4ce69e8c982abcd93439ad02d4d6 Mon Sep 17 00:00:00 2001
-From: Josh Lehan <krellan@google.com>
-Date: Mon, 13 Feb 2023 01:45:29 -0800
-Subject: [PATCH] Add MissingIsAcceptable feature to avoid failsafe
-
-Patch Tracking Bug: b/269219517
-Upstream info / review: https://gerrit.openbmc.org/c/openbmc/phosphor-pid-control/+/60888
-Upstream-Status: Submitted
-Justification: Awaiting upstream review
-
-This is a partial implementation of the ideas here:
-https://github.com/openbmc/phosphor-pid-control/issues/31
-
-A new configuration item is supported in the PID object, named
-"MissingIsAcceptable" (for D-Bus) or "missingIsAcceptable" (for the old
-config.json). The value is an array of strings. If these strings match
-sensor names, those sensors will be flagged as "missing is acceptable",
-that is, they can go missing and the zone will not be thrown into
-failsafe mode as a result.
-
-This can be handy for sensors that are not always available on your
-particular machine. It is independent of the existing Availability
-interface, because the decision to go into failsafe mode or not is a
-property of the PID loop, not of the sensor itself.
-
-Tested: It worked for me. Also, added a unit test case.
-
-Signed-off-by: Josh Lehan <krellan@google.com>
----
- conf.hpp | 6 +-
- dbus/dbusconfiguration.cpp | 91 ++++++++++++++++++++++++++--
- pid/builder.cpp | 10 ++-
- pid/buildjson.cpp | 10 ++-
- pid/fancontroller.cpp | 2 +-
- pid/zone.cpp | 38 +++++++++---
- pid/zone.hpp | 13 ++--
- test/pid_zone_unittest.cpp | 121 +++++++++++++++++++++++++++++++++----
- test/zone_mock.hpp | 2 +-
- util.cpp | 24 +++++++-
- util.hpp | 3 +-
- 11 files changed, 282 insertions(+), 38 deletions(-)
-
-diff --git a/conf.hpp b/conf.hpp
-index 0e26f55..265da09 100644
---- a/conf.hpp
-+++ b/conf.hpp
-@@ -34,13 +34,17 @@ struct SensorConfig
-
- /*
- * Structure for decorating an input sensor's name with additional
-- * information, to help out with TempToMargin conversion.
-+ * information, such as TempToMargin and MissingIsAcceptable.
-+ * This information comes from the PID loop configuration,
-+ * not from SensorConfig, in order for it to be able to work
-+ * with dynamic sensors from entity-manager.
- */
- struct SensorInput
- {
- std::string name;
- double convertMarginZero = std::numeric_limits<double>::quiet_NaN();
- bool convertTempToMargin = false;
-+ bool missingIsAcceptable = false;
- };
-
- /*
-diff --git a/dbus/dbusconfiguration.cpp b/dbus/dbusconfiguration.cpp
-index 2473252..7b78280 100644
---- a/dbus/dbusconfiguration.cpp
-+++ b/dbus/dbusconfiguration.cpp
-@@ -670,6 +670,15 @@ bool init(sdbusplus::bus_t& bus, boost::asio::steady_timer& timer,
- std::vector<std::string> inputSensorNames(
- std::get<std::vector<std::string>>(base.at("Inputs")));
- std::vector<std::string> outputSensorNames;
-+ std::vector<std::string> missingAcceptableSensorNames;
-+
-+ auto findMissingAcceptable = base.find("MissingIsAcceptable");
-+ if (findMissingAcceptable != base.end())
-+ {
-+ missingAcceptableSensorNames =
-+ std::get<std::vector<std::string>>(
-+ findMissingAcceptable->second);
-+ }
-
- // assumption: all fan pids must have at least one output
- if (pidClass == "fan")
-@@ -689,6 +698,9 @@ bool init(sdbusplus::bus_t& bus, boost::asio::steady_timer& timer,
-
- std::vector<SensorInterfaceType> inputSensorInterfaces;
- std::vector<SensorInterfaceType> outputSensorInterfaces;
-+ std::vector<SensorInterfaceType>
-+ missingAcceptableSensorInterfaces;
-+
- /* populate an interface list for different sensor direction
- * types (input,output)
- */
-@@ -707,6 +719,12 @@ bool init(sdbusplus::bus_t& bus, boost::asio::steady_timer& timer,
- findSensors(sensors, sensorNameToDbusName(sensorName),
- outputSensorInterfaces);
- }
-+ for (const std::string& sensorName :
-+ missingAcceptableSensorNames)
-+ {
-+ findSensors(sensors, sensorNameToDbusName(sensorName),
-+ missingAcceptableSensorInterfaces);
-+ }
-
- inputSensorNames.clear();
- for (const SensorInterfaceType& inputSensorInterface :
-@@ -752,6 +770,35 @@ bool init(sdbusplus::bus_t& bus, boost::asio::steady_timer& timer,
- }
- }
-
-+ // MissingIsAcceptable same postprocessing as Inputs
-+ missingAcceptableSensorNames.clear();
-+ for (const SensorInterfaceType&
-+ missingAcceptableSensorInterface :
-+ missingAcceptableSensorInterfaces)
-+ {
-+ const std::string& dbusInterface =
-+ missingAcceptableSensorInterface.second;
-+ const std::string& missingAcceptableSensorPath =
-+ missingAcceptableSensorInterface.first;
-+
-+ std::string missingAcceptableSensorName =
-+ getSensorNameFromPath(missingAcceptableSensorPath);
-+ missingAcceptableSensorNames.push_back(
-+ missingAcceptableSensorName);
-+
-+ if (dbusInterface != sensorInterface)
-+ {
-+ /* MissingIsAcceptable same error checking as Inputs
-+ */
-+ throw std::runtime_error(
-+ "sensor at dbus path [" +
-+ missingAcceptableSensorPath +
-+ "] has an interface [" + dbusInterface +
-+ "] that does not match the expected interface of " +
-+ sensorInterface);
-+ }
-+ }
-+
- /* fan pids need to pair up tach sensors with their pwm
- * counterparts
- */
-@@ -864,7 +911,8 @@ bool init(sdbusplus::bus_t& bus, boost::asio::steady_timer& timer,
- }
-
- std::vector<pid_control::conf::SensorInput> sensorInputs =
-- spliceInputs(inputSensorNames, inputTempToMargin);
-+ spliceInputs(inputSensorNames, inputTempToMargin,
-+ missingAcceptableSensorNames);
-
- if (offsetType.empty())
- {
-@@ -903,9 +951,19 @@ bool init(sdbusplus::bus_t& bus, boost::asio::steady_timer& timer,
- conf::PIDConf& conf = zoneConfig[index];
-
- std::vector<std::string> inputs;
-+ std::vector<std::string> missingAcceptableSensors;
-+ std::vector<std::string> missingAcceptableSensorNames;
- std::vector<std::string> sensorNames =
- std::get<std::vector<std::string>>(base.at("Inputs"));
-
-+ auto findMissingAcceptable = base.find("MissingIsAcceptable");
-+ if (findMissingAcceptable != base.end())
-+ {
-+ missingAcceptableSensorNames =
-+ std::get<std::vector<std::string>>(
-+ findMissingAcceptable->second);
-+ }
-+
- bool unavailableAsFailed = true;
- auto findUnavailableAsFailed =
- base.find("InputUnavailableAsFailed");
-@@ -928,10 +986,8 @@ bool init(sdbusplus::bus_t& bus, boost::asio::steady_timer& timer,
-
- for (const auto& sensorPathIfacePair : sensorPathIfacePairs)
- {
-- size_t idx =
-- sensorPathIfacePair.first.find_last_of("/") + 1;
- std::string shortName =
-- sensorPathIfacePair.first.substr(idx);
-+ getSensorNameFromPath(sensorPathIfacePair.first);
-
- inputs.push_back(shortName);
- auto& config = sensorConfig[shortName];
-@@ -950,6 +1006,30 @@ bool init(sdbusplus::bus_t& bus, boost::asio::steady_timer& timer,
- {
- continue;
- }
-+
-+ // MissingIsAcceptable same postprocessing as Inputs
-+ for (const std::string& missingAcceptableSensorName :
-+ missingAcceptableSensorNames)
-+ {
-+ std::vector<std::pair<std::string, std::string>>
-+ sensorPathIfacePairs;
-+ if (!findSensors(
-+ sensors,
-+ sensorNameToDbusName(missingAcceptableSensorName),
-+ sensorPathIfacePairs))
-+ {
-+ break;
-+ }
-+
-+ for (const auto& sensorPathIfacePair : sensorPathIfacePairs)
-+ {
-+ std::string shortName =
-+ getSensorNameFromPath(sensorPathIfacePair.first);
-+
-+ missingAcceptableSensors.push_back(shortName);
-+ }
-+ }
-+
- conf::ControllerInfo& info = conf[pidName];
-
- std::vector<double> inputTempToMargin;
-@@ -961,7 +1041,8 @@ bool init(sdbusplus::bus_t& bus, boost::asio::steady_timer& timer,
- std::get<std::vector<double>>(findTempToMargin->second);
- }
-
-- info.inputs = spliceInputs(inputs, inputTempToMargin);
-+ info.inputs = spliceInputs(inputs, inputTempToMargin,
-+ missingAcceptableSensors);
-
- info.type = "stepwise";
- info.stepwiseInfo.ts = 1.0; // currently unused
-diff --git a/pid/builder.cpp b/pid/builder.cpp
-index bae0e81..39d0076 100644
---- a/pid/builder.cpp
-+++ b/pid/builder.cpp
-@@ -95,7 +95,7 @@ std::unordered_map<int64_t, std::shared_ptr<ZoneInterface>>
- for (const auto& i : info.inputs)
- {
- inputs.push_back(i);
-- zone->addFanInput(i.name);
-+ zone->addFanInput(i.name, i.missingIsAcceptable);
- }
-
- auto pid = FanController::createFanPid(
-@@ -108,7 +108,7 @@ std::unordered_map<int64_t, std::shared_ptr<ZoneInterface>>
- for (const auto& i : info.inputs)
- {
- inputs.push_back(i);
-- zone->addThermalInput(i.name);
-+ zone->addThermalInput(i.name, i.missingIsAcceptable);
- }
-
- auto pid = ThermalController::createThermalPid(
-@@ -126,7 +126,7 @@ std::unordered_map<int64_t, std::shared_ptr<ZoneInterface>>
- for (const auto& i : info.inputs)
- {
- inputs.push_back(i);
-- zone->addThermalInput(i.name);
-+ zone->addThermalInput(i.name, i.missingIsAcceptable);
- }
- auto stepwise = StepwiseController::createStepwiseController(
- zone.get(), name, splitNames(inputs), info.stepwiseInfo);
-@@ -145,6 +145,10 @@ std::unordered_map<int64_t, std::shared_ptr<ZoneInterface>>
- {
- std::cerr << "[" << i.convertMarginZero << "]";
- }
-+ if (i.missingIsAcceptable)
-+ {
-+ std::cerr << "?";
-+ }
- std::cerr << ", ";
- }
- std::cerr << "\n";
-diff --git a/pid/buildjson.cpp b/pid/buildjson.cpp
-index 8396338..dd7d2a3 100644
---- a/pid/buildjson.cpp
-+++ b/pid/buildjson.cpp
-@@ -37,6 +37,7 @@ namespace conf
- void from_json(const json& j, conf::ControllerInfo& c)
- {
- std::vector<std::string> inputNames;
-+ std::vector<std::string> missingAcceptableNames;
-
- j.at("type").get_to(c.type);
- j.at("inputs").get_to(inputNames);
-@@ -50,7 +51,14 @@ void from_json(const json& j, conf::ControllerInfo& c)
- findTempToMargin->get_to(inputTempToMargin);
- }
-
-- c.inputs = spliceInputs(inputNames, inputTempToMargin);
-+ auto findMissingAcceptable = j.find("missingIsAcceptable");
-+ if (findMissingAcceptable != j.end())
-+ {
-+ findMissingAcceptable->get_to(missingAcceptableNames);
-+ }
-+
-+ c.inputs = spliceInputs(inputNames, inputTempToMargin,
-+ missingAcceptableNames);
-
- /* TODO: We need to handle parsing other PID controller configurations.
- * We can do that by checking for different keys and making the decision
-diff --git a/pid/fancontroller.cpp b/pid/fancontroller.cpp
-index 44852a0..ddfea20 100644
---- a/pid/fancontroller.cpp
-+++ b/pid/fancontroller.cpp
-@@ -175,7 +175,7 @@ void FanController::outputProc(double value)
- {
- auto sensor = _owner->getSensor(it);
- auto redundantWrite = _owner->getRedundantWrite();
-- int64_t rawWritten;
-+ int64_t rawWritten = -1;
- sensor->write(percent, redundantWrite, &rawWritten);
-
- // The outputCache will be used later,
-diff --git a/pid/zone.cpp b/pid/zone.cpp
-index 0b46841..e5eddca 100644
---- a/pid/zone.cpp
-+++ b/pid/zone.cpp
-@@ -96,6 +96,17 @@ bool DbusPidZone::getFailSafeMode(void) const
- return !_failSafeSensors.empty();
- }
-
-+void DbusPidZone::markSensorMissing(const std::string& name)
-+{
-+ if (_missingAcceptable.find(name) != _missingAcceptable.end())
-+ {
-+ // Disallow sensors in MissingIsAcceptable list from causing failsafe
-+ return;
-+ }
-+
-+ _failSafeSensors.emplace(name);
-+}
-+
- int64_t DbusPidZone::getZoneID(void) const
- {
- return _zoneId;
-@@ -184,14 +195,25 @@ void DbusPidZone::setOutputCache(std::string_view name,
- _cachedFanOutputs[std::string{name}] = values;
- }
-
--void DbusPidZone::addFanInput(const std::string& fan)
-+void DbusPidZone::addFanInput(const std::string& fan, bool missingAcceptable)
- {
- _fanInputs.push_back(fan);
-+
-+ if (missingAcceptable)
-+ {
-+ _missingAcceptable.emplace(fan);
-+ }
- }
-
--void DbusPidZone::addThermalInput(const std::string& therm)
-+void DbusPidZone::addThermalInput(const std::string& therm,
-+ bool missingAcceptable)
- {
- _thermalInputs.push_back(therm);
-+
-+ if (missingAcceptable)
-+ {
-+ _missingAcceptable.emplace(therm);
-+ }
- }
-
- // Updates desired RPM setpoint from optional text file
-@@ -389,21 +411,23 @@ void DbusPidZone::updateSensors(void)
-
- void DbusPidZone::initializeCache(void)
- {
-+ auto nan = std::numeric_limits<double>::quiet_NaN();
-+
- for (const auto& f : _fanInputs)
- {
-- _cachedValuesByName[f] = {0, 0};
-- _cachedFanOutputs[f] = {0, 0};
-+ _cachedValuesByName[f] = {nan, nan};
-+ _cachedFanOutputs[f] = {nan, nan};
-
- // Start all fans in fail-safe mode.
-- _failSafeSensors.insert(f);
-+ markSensorMissing(f);
- }
-
- for (const auto& t : _thermalInputs)
- {
-- _cachedValuesByName[t] = {0, 0};
-+ _cachedValuesByName[t] = {nan, nan};
-
- // Start all sensors in fail-safe mode.
-- _failSafeSensors.insert(t);
-+ markSensorMissing(t);
- }
- // Initialize Pid FailSafePercent
- initPidFailSafePercent();
-diff --git a/pid/zone.hpp b/pid/zone.hpp
-index 2854997..464e672 100644
---- a/pid/zone.hpp
-+++ b/pid/zone.hpp
-@@ -71,6 +71,7 @@ class DbusPidZone : public ZoneInterface, public ModeObject
- bool getRedundantWrite(void) const override;
- void setManualMode(bool mode);
- bool getFailSafeMode(void) const override;
-+ void markSensorMissing(const std::string& name);
-
- int64_t getZoneID(void) const override;
- void addSetPoint(double setPoint, const std::string& name) override;
-@@ -99,8 +100,8 @@ class DbusPidZone : public ZoneInterface, public ModeObject
- double getCachedValue(const std::string& name) override;
- ValueCacheEntry getCachedValues(const std::string& name) override;
-
-- void addFanInput(const std::string& fan);
-- void addThermalInput(const std::string& therm);
-+ void addFanInput(const std::string& fan, bool missingAcceptable);
-+ void addThermalInput(const std::string& therm, bool missingAcceptable);
-
- void initializeLog(void) override;
- void writeLog(const std::string& value) override;
-@@ -166,7 +167,8 @@ class DbusPidZone : public ZoneInterface, public ModeObject
- // check if fan fail.
- if (sensor->getFailed())
- {
-- _failSafeSensors.insert(sensorInput);
-+ markSensorMissing(sensorInput);
-+
- if (debugEnabled)
- {
- std::cerr << sensorInput << " sensor get failed\n";
-@@ -174,7 +176,8 @@ class DbusPidZone : public ZoneInterface, public ModeObject
- }
- else if (timeout != 0 && duration >= period)
- {
-- _failSafeSensors.insert(sensorInput);
-+ markSensorMissing(sensorInput);
-+
- if (debugEnabled)
- {
- std::cerr << sensorInput << " sensor timeout\n";
-@@ -191,6 +194,7 @@ class DbusPidZone : public ZoneInterface, public ModeObject
- std::cerr << sensorInput
- << " is erased from failsafe sensor set\n";
- }
-+
- _failSafeSensors.erase(kt);
- }
- }
-@@ -213,6 +217,7 @@ class DbusPidZone : public ZoneInterface, public ModeObject
- const conf::CycleTime _cycleTime;
-
- std::set<std::string> _failSafeSensors;
-+ std::set<std::string> _missingAcceptable;
-
- std::vector<double> _SetPoints;
- std::vector<double> _RPMCeilings;
-diff --git a/test/pid_zone_unittest.cpp b/test/pid_zone_unittest.cpp
-index 3a51d88..59ebf9a 100644
---- a/test/pid_zone_unittest.cpp
-+++ b/test/pid_zone_unittest.cpp
-@@ -376,8 +376,8 @@ TEST_F(PidZoneTest, ThermalInputs_FailsafeToValid_ReadsSensors)
- EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2);
-
- // Now that the sensors exist, add them to the zone.
-- zone->addThermalInput(name1);
-- zone->addThermalInput(name2);
-+ zone->addThermalInput(name1, false);
-+ zone->addThermalInput(name2, false);
-
- // Initialize Zone
- zone->initializeCache();
-@@ -428,8 +428,8 @@ TEST_F(PidZoneTest, FanInputTest_VerifiesFanValuesCached)
- EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2);
-
- // Now that the sensors exist, add them to the zone.
-- zone->addFanInput(name1);
-- zone->addFanInput(name2);
-+ zone->addFanInput(name1, false);
-+ zone->addFanInput(name2, false);
-
- // Initialize Zone
- zone->initializeCache();
-@@ -475,8 +475,8 @@ TEST_F(PidZoneTest, ThermalInput_ValueTimeoutEntersFailSafeMode)
- mgr.addSensor(type, name2, std::move(sensor2));
- EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2);
-
-- zone->addThermalInput(name1);
-- zone->addThermalInput(name2);
-+ zone->addThermalInput(name1, false);
-+ zone->addThermalInput(name2, false);
-
- // Initialize Zone
- zone->initializeCache();
-@@ -512,6 +512,105 @@ TEST_F(PidZoneTest, ThermalInput_ValueTimeoutEntersFailSafeMode)
- EXPECT_TRUE(zone->getFailSafeMode());
- }
-
-+TEST_F(PidZoneTest, ThermalInput_MissingIsAcceptableNoFailSafe)
-+{
-+ // This is similar to the above test, but because missingIsAcceptable
-+ // is set for sensor1, the zone should not enter failsafe mode when
-+ // only sensor1 goes missing.
-+ // However, sensor2 going missing should still trigger failsafe mode.
-+
-+ int64_t timeout = 1;
-+
-+ std::string name1 = "temp1";
-+ std::unique_ptr<Sensor> sensor1 = std::make_unique<SensorMock>(name1,
-+ timeout);
-+ SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get());
-+
-+ std::string name2 = "temp2";
-+ std::unique_ptr<Sensor> sensor2 = std::make_unique<SensorMock>(name2,
-+ timeout);
-+ SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get());
-+
-+ std::string type = "unchecked";
-+ mgr.addSensor(type, name1, std::move(sensor1));
-+ EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1);
-+ mgr.addSensor(type, name2, std::move(sensor2));
-+ EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2);
-+
-+ // Only sensor1 has MissingIsAcceptable enabled for it
-+ zone->addThermalInput(name1, true);
-+ zone->addThermalInput(name2, false);
-+
-+ // Initialize Zone
-+ zone->initializeCache();
-+
-+ // As sensors are not initialized, zone should be in failsafe mode
-+ EXPECT_TRUE(zone->getFailSafeMode());
-+
-+ // r1 not populated here, intentionally, to simulate a sensor that
-+ // is not available yet, perhaps takes a long time to start up.
-+ ReadReturn r1;
-+ EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
-+
-+ ReadReturn r2;
-+ r2.value = 11.0;
-+ r2.updated = std::chrono::high_resolution_clock::now();
-+ EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
-+
-+ zone->updateSensors();
-+
-+ // Only sensor2 has been initialized here. Failsafe should be false,
-+ // because sensor1 MissingIsAcceptable so it is OK for it to go missing.
-+ EXPECT_FALSE(zone->getFailSafeMode());
-+
-+ r1.value = 10.0;
-+ r1.updated = std::chrono::high_resolution_clock::now();
-+
-+ EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
-+ EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
-+ zone->updateSensors();
-+
-+ // Both sensors are now properly initialized
-+ EXPECT_FALSE(zone->getFailSafeMode());
-+
-+ // Ok, so we're not in failsafe mode, so let's set updated to the past.
-+ // sensor1 will have an updated field older than its timeout value, but
-+ // sensor2 will be fine. :D
-+ r1.updated -= std::chrono::seconds(3);
-+ r2.updated = std::chrono::high_resolution_clock::now();
-+
-+ EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
-+ EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
-+ zone->updateSensors();
-+
-+ // MissingIsAcceptable is true for sensor1, so the zone should not be
-+ // thrown into failsafe mode.
-+ EXPECT_FALSE(zone->getFailSafeMode());
-+
-+ // Do the same thing, but for the opposite sensors: r1 is good,
-+ // but r2 is set to some time in the past.
-+ r1.updated = std::chrono::high_resolution_clock::now();
-+ r2.updated -= std::chrono::seconds(3);
-+
-+ EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
-+ EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
-+ zone->updateSensors();
-+
-+ // Now, the zone should be in failsafe mode, because sensor2 does not
-+ // have MissingIsAcceptable set true, it is still subject to failsafe.
-+ EXPECT_TRUE(zone->getFailSafeMode());
-+
-+ r1.updated = std::chrono::high_resolution_clock::now();
-+ r2.updated = std::chrono::high_resolution_clock::now();
-+
-+ EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
-+ EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
-+ zone->updateSensors();
-+
-+ // The failsafe mode should cease, as both sensors are good again.
-+ EXPECT_FALSE(zone->getFailSafeMode());
-+}
-+
- TEST_F(PidZoneTest, FanInputTest_FailsafeToValid_ReadsSensors)
- {
- // This will add a couple fan inputs, and verify the values are cached.
-@@ -535,8 +634,8 @@ TEST_F(PidZoneTest, FanInputTest_FailsafeToValid_ReadsSensors)
- EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2);
-
- // Now that the sensors exist, add them to the zone.
-- zone->addFanInput(name1);
-- zone->addFanInput(name2);
-+ zone->addFanInput(name1, false);
-+ zone->addFanInput(name2, false);
-
- // Initialize Zone
- zone->initializeCache();
-@@ -588,8 +687,8 @@ TEST_F(PidZoneTest, FanInputTest_ValueTimeoutEntersFailSafeMode)
- EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2);
-
- // Now that the sensors exist, add them to the zone.
-- zone->addFanInput(name1);
-- zone->addFanInput(name2);
-+ zone->addFanInput(name1, false);
-+ zone->addFanInput(name2, false);
-
- // Initialize Zone
- zone->initializeCache();
-@@ -639,7 +738,7 @@ TEST_F(PidZoneTest, GetSensorTest_ReturnsExpected)
- mgr.addSensor(type, name1, std::move(sensor1));
- EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1);
-
-- zone->addThermalInput(name1);
-+ zone->addThermalInput(name1, false);
-
- // Verify method under test returns the pointer we expect.
- EXPECT_EQ(mgr.getSensor(name1), zone->getSensor(name1));
-diff --git a/test/zone_mock.hpp b/test/zone_mock.hpp
-index 8ba9c85..a3d6be9 100644
---- a/test/zone_mock.hpp
-+++ b/test/zone_mock.hpp
-@@ -23,7 +23,7 @@ class ZoneMock : public ZoneInterface
- ValueCacheEntry getCachedValues(const std::string& s)
- {
- auto v = getCachedValue(s);
-- return ValueCacheEntry(v, v);
-+ return {v, v};
- }
-
- MOCK_CONST_METHOD0(getRedundantWrite, bool(void));
-diff --git a/util.cpp b/util.cpp
-index 87c4deb..b83ed43 100644
---- a/util.cpp
-+++ b/util.cpp
-@@ -103,15 +103,16 @@ void debugPrint(const std::map<std::string, conf::SensorConfig>& sensorConfig,
-
- std::vector<conf::SensorInput>
- spliceInputs(const std::vector<std::string>& inputNames,
-- const std::vector<double>& inputTempToMargin)
-+ const std::vector<double>& inputTempToMargin,
-+ const std::vector<std::string>& missingAcceptableNames)
- {
- std::vector<conf::SensorInput> results;
-
-- // Default to the TempToMargin feature disabled
-+ // Default to TempToMargin and MissingIsAcceptable disabled
- for (const auto& inputName : inputNames)
- {
- conf::SensorInput newInput{
-- inputName, std::numeric_limits<double>::quiet_NaN(), false};
-+ inputName, std::numeric_limits<double>::quiet_NaN(), false, false};
-
- results.emplace_back(newInput);
- }
-@@ -132,6 +133,23 @@ std::vector<conf::SensorInput>
- results[index].convertTempToMargin = true;
- }
-
-+ std::set<std::string> acceptableSet;
-+
-+ // Copy vector to set, to avoid O(n^2) runtime below
-+ for (const auto& name : missingAcceptableNames)
-+ {
-+ acceptableSet.emplace(name);
-+ }
-+
-+ // Flag missingIsAcceptable true if name found in that set
-+ for (auto& result : results)
-+ {
-+ if (acceptableSet.find(result.name) != acceptableSet.end())
-+ {
-+ result.missingIsAcceptable = true;
-+ }
-+ }
-+
- return results;
- }
-
-diff --git a/util.hpp b/util.hpp
-index 0588934..7617562 100644
---- a/util.hpp
-+++ b/util.hpp
-@@ -46,7 +46,8 @@ std::string FixupPath(std::string original);
- */
- std::vector<conf::SensorInput>
- spliceInputs(const std::vector<std::string>& inputNames,
-- const std::vector<double>& inputTempToMargin);
-+ const std::vector<double>& inputTempToMargin,
-+ const std::vector<std::string>& missingAcceptableNames);
-
- /*
- * Recovers the original "Inputs" vector from spliceInputs().
---
-2.42.0.655.g421f12c284-goog
-
diff --git a/recipes-phosphor/fans/phosphor-pid-control_%.bbappend b/recipes-phosphor/fans/phosphor-pid-control_%.bbappend
deleted file mode 100644
index 6019858..0000000
--- a/recipes-phosphor/fans/phosphor-pid-control_%.bbappend
+++ /dev/null
@@ -1,6 +0,0 @@
-FILESEXTRAPATHS:prepend:gbmc := "${THISDIR}/${PN}:"
-
-SRC_URI:append:gbmc = " \
- file://0001-Implementing-the-TempToMargin-feature.patch \
- file://0002-Add-MissingIsAcceptable-feature-to-avoid-failsafe.patch \
-"