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 \
-"