control: action to override a single fan in a zone - ability to lock Fan

This change modifies Fan to accept one or more override targets which
prevent Fan from accepting regular target updates until all locks have
been cleared using unlockTarget().

Multiple locks are supported, however only the higest locked target will
ever be active. For instance, if targets are locked at 9000 and 10000
(in either order), the 10000 lock will be active until it is cleared, at
which point the 2nd lowest lock (9000) will become active. When it is
cleared, Fan will become unlocked and resume its target from temperature
control. If the first locked target happens to be lower than its current
target, the lock will take precedence even though it's lower.

Signed-off-by: Mike Capps <mikepcapps@gmail.com>
Change-Id: Iba73aa06eed25452117eb138768ed300bc45b0d9
diff --git a/control/json/fan.cpp b/control/json/fan.cpp
index 3641279..cf5a7e3 100644
--- a/control/json/fan.cpp
+++ b/control/json/fan.cpp
@@ -1,5 +1,5 @@
 /**
- * Copyright © 2020 IBM Corporation
+ * Copyright © 2022 IBM Corporation
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -89,7 +89,7 @@
 
 void Fan::setTarget(uint64_t target)
 {
-    if (_target == target)
+    if ((_target == target) || !_lockedTargets.empty())
     {
         return;
     }
@@ -113,4 +113,47 @@
     _target = target;
 }
 
+void Fan::lockTarget(uint64_t target)
+{
+    // if multiple locks, take highest, else allow only the
+    // first lock to lower the target
+    if (target >= _target || _lockedTargets.empty())
+    {
+        // setTarget wont work if any locked targets exist
+        decltype(_lockedTargets) temp;
+        _lockedTargets.swap(temp);
+
+        setTarget(target);
+        _lockedTargets.swap(temp);
+    }
+
+    _lockedTargets.push_back(target);
+}
+
+void Fan::unlockTarget(uint64_t target)
+{
+    // find and remove the requested lock
+    auto itr(std::find_if(
+        _lockedTargets.begin(), _lockedTargets.end(),
+        [target](auto lockedTarget) { return target == lockedTarget; }));
+
+    if (_lockedTargets.end() != itr)
+    {
+        _lockedTargets.erase(itr);
+
+        // if additional locks, re-lock at next-highest target
+        if (!_lockedTargets.empty())
+        {
+            itr =
+                std::max_element(_lockedTargets.begin(), _lockedTargets.end());
+
+            // setTarget wont work if any locked targets exist
+            decltype(_lockedTargets) temp;
+            _lockedTargets.swap(temp);
+            setTarget(*itr);
+            _lockedTargets.swap(temp);
+        }
+    }
+}
+
 } // namespace phosphor::fan::control::json