Scripts and main daemon
This includes the scripts for the YAML parsing and the
main execution point.
Change-Id: If42154c621353b23370b63d4e58f6c75bca8b356
Signed-off-by: Patrick Venture <venture@google.com>
diff --git a/scripts/README b/scripts/README
new file mode 100644
index 0000000..3b732e8
--- /dev/null
+++ b/scripts/README
@@ -0,0 +1,25 @@
+# Sensor Config
+This program is only meant to control fans given thermal sensor readings.
+
+All sensors in phosphor-dbus-interfaces for OpenBMC use Sensor.Value as their
+accessor. This provides read-only access to information. The goal of the
+configuration is to specify how it can be read and if it's a fan, how the PID
+output can be written. Initially there'll only be sysfs and passive dbus
+access. If a writepath for a sensor is a dbus path, then the system will need
+to verify which Control.Fan* interfaces is registered and send values to the
+Target property of that interface.
+
+The min/max specified are to range a writePercent to the sensor. The current
+FanController object outputs the new fan speed goal as a PWM percentage. Other
+fan PID control objects may not, and they can leave the fields as 0 & 0.
+
+The only requirement for a sensor is that it isn't writeonly. Only fans are
+expected to have a writepath set, and in this current version non-fan sensors
+are assumed readonly.
+
+The sensor names are unique across all zones.
+
+# PID Config
+
+The PID configuation is a list of PIDs per zone.
+
diff --git a/scripts/pid-example.txt b/scripts/pid-example.txt
new file mode 100644
index 0000000..3549c8d
--- /dev/null
+++ b/scripts/pid-example.txt
@@ -0,0 +1,24 @@
+0x01: /* zone ID */
+ fan2-6: /* PID name */
+ type: fan /* Type of PID, fan, temp, or margin. */
+ inputs: /* Sensor names that are inputs for the PID */
+ fan2
+ fan6
+ /* For temp/margin PIDs this is the set-point, ignored otherwise (float) */
+ set-point: 90.0
+ pid: /* The PID calculation configuration. */
+ sampleperiod: 0.1 /* The input sample period. (float) */
+ p_coefficient: 0.01 /* The proportional coefficient. (float) */
+ i_coefficient: 0.001 /* The integral coefficient. (float) */
+ /* The feed-forward offset coefficient. (float) */
+ ff_off_coefficient: 0.0
+ /* The feed-forward gain coefficient. (float) */
+ ff_gain_coefficient: 0.0
+ i_limit: /* The integral limit clamp, min, max (float) */
+ min: 0
+ max: 100
+ out_limit: /* the PID output clamp, min, max (float) */
+ min: 0
+ max: 100
+ slew_neg: -100 /* The slew negative value. (float) */
+ slew_pos: 0 /* The slew positive value. (float) */
diff --git a/scripts/pid-example.yaml b/scripts/pid-example.yaml
new file mode 100644
index 0000000..ea89b26
--- /dev/null
+++ b/scripts/pid-example.yaml
@@ -0,0 +1,59 @@
+0x01:
+ fan2-6:
+ type: fan
+ inputs:
+ fan2
+ fan6
+ set-point: 90.0
+ pid:
+ sampleperiod: 0.1
+ p_coefficient: 0.01
+ i_coefficient: 0.001
+ ff_off_coefficient: 0.0
+ ff_gain_coefficient: 0.0
+ i_limit:
+ min: 0
+ max: 100
+ out_limit:
+ min: 0
+ max: 100
+ slew_neg: 0
+ slew_pos: 0
+ temp1:
+ type: temp
+ inputs:
+ temp1
+ set-point: 30.0
+ pid:
+ sampleperiod: 1
+ p_coefficient: 94.0
+ i_coefficient: 2.0
+ ff_off_coefficient: 0.0
+ ff_gain_coefficient: 0.0
+ i_limit:
+ min: 3000
+ max: 10000
+ out_limit:
+ min: 3000
+ max: 10000
+ slew_neg: 0
+ slew_pos: 0
+ sluggish0:
+ type: margin
+ inputs:
+ sluggish0
+ set-point: 50
+ pid:
+ sampleperiod: 1
+ p_coefficient: 94.0
+ i_coefficient: 2.0
+ ff_off_coefficient: 0.0
+ ff_gain_coefficient: 0.0
+ i_limit:
+ min: 3000
+ max: 10000
+ out_limit:
+ min: 3000
+ max: 10000
+ slew_neg: 0
+ slew_pos: 0
diff --git a/scripts/pid_gen.py b/scripts/pid_gen.py
new file mode 100644
index 0000000..cd62199
--- /dev/null
+++ b/scripts/pid_gen.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+
+import os
+import sys
+import yaml
+import argparse
+from mako.template import Template
+
+
+def generate_cpp(sensor_yaml, output_dir):
+ with open(os.path.join(script_dir, sensor_yaml), 'r') as f:
+ ifile = yaml.safe_load(f)
+ if not isinstance(ifile, dict):
+ ifile = {}
+
+ # Render the mako template
+
+ t = Template(filename=os.path.join(
+ script_dir,
+ "writepid.mako.cpp"))
+
+ output_cpp = os.path.join(output_dir, "pidlist-gen.cpp")
+ with open(output_cpp, 'w') as fd:
+ fd.write(t.render(PIDDict=ifile))
+
+
+def main():
+
+ valid_commands = {
+ 'generate-cpp': generate_cpp
+ }
+ parser = argparse.ArgumentParser(
+ description="IPMI Sensor parser and code generator")
+
+ parser.add_argument(
+ '-i', '--sensor_yaml', dest='sensor_yaml',
+ default='example.yaml', help='input sensor yaml file to parse')
+
+ parser.add_argument(
+ "-o", "--output-dir", dest="outputdir",
+ default=".",
+ help="output directory")
+
+ parser.add_argument(
+ 'command', metavar='COMMAND', type=str,
+ choices=valid_commands.keys(),
+ help='Command to run.')
+
+ args = parser.parse_args()
+
+ if (not (os.path.isfile(os.path.join(script_dir, args.sensor_yaml)))):
+ sys.exit("Can not find input yaml file " + args.sensor_yaml)
+
+ function = valid_commands[args.command]
+ function(args.sensor_yaml, args.outputdir)
+
+
+if __name__ == '__main__':
+ script_dir = os.path.dirname(os.path.realpath(__file__))
+ main()
diff --git a/scripts/sensor-example.txt b/scripts/sensor-example.txt
new file mode 100644
index 0000000..0915163
--- /dev/null
+++ b/scripts/sensor-example.txt
@@ -0,0 +1,29 @@
+fan2: /* Name of the sensor. */
+ type: fan /* Type of sensor, fan, temp, margin */
+ /* How the sensor can be read[1] */
+ readpath: /xyz/openbmc_project/sensors/fan_tach/fan2
+ /* How the sensor can be set[2] */
+ writepath: /sys/class/hwmon/hwmon0/pwm1
+ /* The minimum value used for scaling writes (int64) */
+ min: 0
+ /* The maximum value used for scaling writes (int64) */
+ max: 255
+ /* The timeout value for the sensor, used for failsafe, 0 means no timeout
+ * (int64)
+ */
+ timeout: 0
+
+[1] readpath has multiple options:
+* If it has "/xyz/openbmc_project/extsensors/" in it, it's an EXTERNAL or
+ host-provided sensor.
+* If it has "/xyz/openbmc_project/" in it, it's a sensor whose value is
+ received passively over dbus.
+* If it has "/sys/" in it, it's a sensor read directly from sysfs.
+
+[2]
+* This can be left blank if the sensor is read-only.
+* If it has "/sys/" in it, it's a sensor written to sysfs.
+ * If min and max are non-zero, it'll convert the value to within the range.
+ and output that modified value. So, if it receives a value of .90 and min
+ is 0, and max is 255, it'll convert that to a value of 229.5 that is then
+ cast to int64_t.
diff --git a/scripts/sensor-example.yaml b/scripts/sensor-example.yaml
new file mode 100644
index 0000000..7378bff
--- /dev/null
+++ b/scripts/sensor-example.yaml
@@ -0,0 +1,31 @@
+fan2:
+ type: fan
+ readpath: /xyz/openbmc_project/sensors/fan_tach/fan2
+ writepath: /sys/class/hwmon/hwmon0/pwm1
+ min: 0
+ max: 255
+ timeout: 0
+
+fan6:
+ type: fan
+ readpath: /xyz/openbmc_project/sensors/fan_tach/fan6
+ writepath:
+ min: 0
+ max: 0
+ timeout: 0
+
+temp1:
+ type: temp
+ readpath: /xyz/openbmc_project/sensors/temperature/temp1
+ writepath:
+ min: 0
+ max: 0
+ timeout: 0
+
+sluggish0:
+ type: margin
+ readpath: /xyz/openbmc_project/extsensors/margin/sluggish0
+ writepath:
+ min: 0
+ max: 0
+ timeout: 0
diff --git a/scripts/sensor_gen.py b/scripts/sensor_gen.py
new file mode 100644
index 0000000..7e9c6ca
--- /dev/null
+++ b/scripts/sensor_gen.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+
+import os
+import sys
+import yaml
+import argparse
+from mako.template import Template
+
+
+def generate_cpp(sensor_yaml, output_dir):
+ with open(os.path.join(script_dir, sensor_yaml), 'r') as f:
+ ifile = yaml.safe_load(f)
+ if not isinstance(ifile, dict):
+ ifile = {}
+
+ # Render the mako template
+
+ t = Template(filename=os.path.join(
+ script_dir,
+ "writesensor.mako.cpp"))
+
+ output_cpp = os.path.join(output_dir, "sensorlist-gen.cpp")
+ with open(output_cpp, 'w') as fd:
+ fd.write(t.render(sensorDict=ifile))
+
+
+def main():
+
+ valid_commands = {
+ 'generate-cpp': generate_cpp
+ }
+ parser = argparse.ArgumentParser(
+ description="IPMI Sensor parser and code generator")
+
+ parser.add_argument(
+ '-i', '--sensor_yaml', dest='sensor_yaml',
+ default='example.yaml', help='input sensor yaml file to parse')
+
+ parser.add_argument(
+ "-o", "--output-dir", dest="outputdir",
+ default=".",
+ help="output directory")
+
+ parser.add_argument(
+ 'command', metavar='COMMAND', type=str,
+ choices=valid_commands.keys(),
+ help='Command to run.')
+
+ args = parser.parse_args()
+
+ if (not (os.path.isfile(os.path.join(script_dir, args.sensor_yaml)))):
+ sys.exit("Can not find input yaml file " + args.sensor_yaml)
+
+ function = valid_commands[args.command]
+ function(args.sensor_yaml, args.outputdir)
+
+
+if __name__ == '__main__':
+ script_dir = os.path.dirname(os.path.realpath(__file__))
+ main()
\ No newline at end of file
diff --git a/scripts/writepid.mako.cpp b/scripts/writepid.mako.cpp
new file mode 100644
index 0000000..f168e10
--- /dev/null
+++ b/scripts/writepid.mako.cpp
@@ -0,0 +1,48 @@
+## This file is a template. The comment below is emitted
+## into the rendered file; feel free to edit this file.
+
+// !!! WARNING: This is GENERATED Code... Please do NOT edit !!!
+
+#include <map>
+#include "conf.hpp"
+
+std::map<int64_t, PIDConf> ZoneConfig = {
+% for zone in PIDDict.iterkeys():
+ % if zone:
+ {${zone},
+ {
+ % for key, details in PIDDict[zone].iteritems():
+ {"${key}",
+ {"${details['type']}",
+ {
+ % for item in details['inputs'].split():
+ "${item}",
+ % endfor
+ },
+ <%
+ # If the PID type is a fan, set-point field is unused,
+ # so just use a default of 0. If the PID is not a type
+ # of fan, require the set-point field.
+ if 'fan' == details['type']:
+ setpoint = 0
+ else:
+ setpoint = details['set-point']
+ %>
+ ${setpoint},
+ {${details['pid']['sampleperiod']},
+ ${details['pid']['p_coefficient']},
+ ${details['pid']['i_coefficient']},
+ ${details['pid']['ff_off_coefficient']},
+ ${details['pid']['ff_gain_coefficient']},
+ {${details['pid']['i_limit']['min']}, ${details['pid']['i_limit']['max']}},
+ {${details['pid']['out_limit']['min']}, ${details['pid']['out_limit']['max']}},
+ ${details['pid']['slew_neg']},
+ ${details['pid']['slew_pos']}},
+ },
+ },
+ % endfor
+ },
+ },
+ % endif
+% endfor
+};
diff --git a/scripts/writesensor.mako.cpp b/scripts/writesensor.mako.cpp
new file mode 100644
index 0000000..8be30bb
--- /dev/null
+++ b/scripts/writesensor.mako.cpp
@@ -0,0 +1,36 @@
+## This file is a template. The comment below is emitted
+## into the rendered file; feel free to edit this file.
+
+// !!! WARNING: This is GENERATED Code... Please do NOT edit !!!
+
+#include <map>
+#include "conf.hpp"
+
+std::map<std::string, struct sensor> SensorConfig = {
+% for key in sensorDict.iterkeys():
+ % if key:
+ <%
+ sensor = sensorDict[key]
+ type = sensor["type"]
+ readpath = sensor["readpath"]
+ writepath = sensor.get("writepath", "")
+ min = sensor.get("min", 0)
+ max = sensor.get("max", 0)
+ # Presently only thermal inputs have their timeout
+ # checked, but we should default it as 2s, which is
+ # the previously hard-coded value.
+ # If it's a fan sensor though, let's set the default
+ # to 0.
+ if type == "fan":
+ timeout = sensor.get("timeout", 0)
+ else:
+ timeout = sensor.get("timeout", 2)
+ %>
+ {"${key}",
+ {"${type}","${readpath}","${writepath}", ${min}, ${max}, ${timeout}},
+ },
+ % endif
+% endfor
+};
+
+
diff --git a/scripts/writezone.mako.cpp b/scripts/writezone.mako.cpp
new file mode 100644
index 0000000..57b9908
--- /dev/null
+++ b/scripts/writezone.mako.cpp
@@ -0,0 +1,22 @@
+## This file is a template. The comment below is emitted
+## into the rendered file; feel free to edit this file.
+
+// !!! WARNING: This is GENERATED Code... Please do NOT edit !!!
+
+#include <map>
+#include "conf.hpp"
+
+std::map<int64_t, struct zone> ZoneDetailsConfig = {
+% for zone in ZoneDict.iterkeys():
+ % if zone:
+ <%
+ zConf = ZoneDict[zone]
+ min = zConf["minthermalrpm"]
+ percent = zConf["failsafepercent"]
+ %>
+ {${zone},
+ {${min}, ${percent}},
+ },
+ % endif
+% endfor
+};
diff --git a/scripts/zone-example.txt b/scripts/zone-example.txt
new file mode 100644
index 0000000..752fc01
--- /dev/null
+++ b/scripts/zone-example.txt
@@ -0,0 +1,4 @@
+0x01: /* The zone ID */
+ minthermalrpm: 3000.0 /* The minimum thermal RPM value. (float) */
+ /* The percent to use when the zone is in fail-safe mode. (float) */
+ failsafepercent: 90.0
diff --git a/scripts/zone-example.yaml b/scripts/zone-example.yaml
new file mode 100644
index 0000000..ef422a5
--- /dev/null
+++ b/scripts/zone-example.yaml
@@ -0,0 +1,3 @@
+0x01:
+ minthermalrpm: 3000.0
+ failsafepercent: 90.0
diff --git a/scripts/zone_gen.py b/scripts/zone_gen.py
new file mode 100644
index 0000000..a98a3b5
--- /dev/null
+++ b/scripts/zone_gen.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+
+import os
+import sys
+import yaml
+import argparse
+from mako.template import Template
+
+
+def generate_cpp(zoneinfo_yaml, output_dir):
+ with open(os.path.join(script_dir, zoneinfo_yaml), 'r') as f:
+ ifile = yaml.safe_load(f)
+ if not isinstance(ifile, dict):
+ ifile = {}
+
+ # Render the mako template
+
+ t = Template(filename=os.path.join(
+ script_dir,
+ "writezone.mako.cpp"))
+
+ output_cpp = os.path.join(output_dir, "zoneinfo-gen.cpp")
+ with open(output_cpp, 'w') as fd:
+ fd.write(t.render(ZoneDict=ifile))
+
+
+def main():
+
+ valid_commands = {
+ 'generate-cpp': generate_cpp
+ }
+ parser = argparse.ArgumentParser(
+ description="IPMI Zone info parser and code generator")
+
+ parser.add_argument(
+ '-i', '--zoneinfo_yaml', dest='zoneinfo_yaml',
+ default='example.yaml', help='input zone yaml file to parse')
+
+ parser.add_argument(
+ "-o", "--output-dir", dest="outputdir",
+ default=".",
+ help="output directory")
+
+ parser.add_argument(
+ 'command', metavar='COMMAND', type=str,
+ choices=valid_commands.keys(),
+ help='Command to run.')
+
+ args = parser.parse_args()
+
+ if (not (os.path.isfile(os.path.join(script_dir, args.zoneinfo_yaml)))):
+ sys.exit("Can not find input yaml file " + args.zoneinfo_yaml)
+
+ function = valid_commands[args.command]
+ function(args.zoneinfo_yaml, args.outputdir)
+
+
+if __name__ == '__main__':
+ script_dir = os.path.dirname(os.path.realpath(__file__))
+ main()