1# How to Configure Phosphor-pid-control
2
3A system needs two groups of configurations: zones and sensors.
4
5They can come either from a dedicated config file or via D-Bus from
6e.g. `entity-manager`.
7
8## D-Bus Configuration
9
10If config file does not exist the configuration is obtained from a set of D-Bus
11interfaces. When using `entity-manager` to provide them refer to `Pid`,
12`Pid.Zone` and `Stepwise`
13[schemas](https://github.com/openbmc/entity-manager/tree/master/schemas). The
14key names are not identical to JSON but similar enough to see the
15correspondence.
16
17## JSON Configuration
18
19Default config file path `/usr/share/swampd/config.json` can be overridden by
20using `--conf` command line option.
21
22The JSON object should be a dictionary with two keys, `sensors` and `zones`.
23`sensors` is a list of the sensor dictionaries, whereas `zones` is a list of
24zones.
25
26### Sensors
27
28```
29"sensors" : [
30    {
31        "name": "fan1",
32        "type": "fan",
33        "readPath": "/xyz/openbmc_project/sensors/fan_tach/fan1",
34        "writePath": "/sys/devices/platform/ahb/ahb:apb/1e786000.pwm-tacho-controller/hwmon/**/pwm1",
35        "min": 0,
36        "max": 255,
37        "ignoreDbusMinMax": true
38        "unavailableAsFailed": true
39    },
40    {
41        "name": "fan2",
42        "type": "fan",
43        "readPath": "/xyz/openbmc_project/sensors/fan_tach/fan2",
44        "writePath": "/sys/devices/platform/ahb/ahb:apb/1e786000.pwm-tacho-controller/hwmon/**/pwm2",
45        "min": 0,
46        "max": 255,
47        "timeout": 4,
48    },
49...
50```
51
52A sensor has a `name`, a `type`, a `readPath`, a `writePath`, a `minimum` value,
53a `maximum` value, a `timeout`, a `ignoreDbusMinMax` and a `unavailableAsFailed` value.
54
55The `name` is used to reference the sensor in the zone portion of the
56configuration.
57
58The `type` is the type of sensor it is. This influences how its value is
59treated. Supported values are: `fan`, `temp`, and `margin`.
60
61The `readPath` is the path that tells the daemon how to read the value from this
62sensor. It is optional, allowing for write-only sensors. If the value is absent
63or `None` it'll be treated as a write-only sensor.
64
65If the `readPath` value contains: `/xyz/openbmc_project/extsensors/` it'll be
66treated as a sensor hosted by the daemon itself whose value is provided
67externally. The daemon will own the sensor and publish it to dbus. This is
68currently only supported for `temp` and `margin` sensor types.
69
70If the `readPath` value contains: `/xyz/openbmc_project/` (this is checked after
71external), then it's treated as a passive dbus sensor. A passive dbus sensor is
72one that listens for property updates to receive its value instead of actively
73reading the `Value` property.
74
75If the `readPath` value contains: `/sys/` this is treated as a directly read
76sysfs path. There are two supported paths:
77
78*   `/sys/class/hwmon/hwmon0/pwm1`
79*   `/sys/devices/platform/ahb/1e786000.pwm-tacho-controller/hwmon/<asterisk
80    asterisk>/pwm1`
81
82The `writePath` is the path to set the value for the sensor. This is only valid
83for a sensor of type `fan`. The path is optional. If can be empty or `None`. It
84then only supports two options.
85
86If the `writePath` value contains: `/sys/` this is treated as a directory
87written sysfs path. There are two support paths:
88
89*   `/sys/class/hwmon/hwmon0/pwm1`
90*   `/sys/devices/platform/ahb/1e786000.pwm-tacho-controller/hwmon/<asterisk
91    asterisk>/pwm1`
92
93If the `writePath` value contains: `/xyz/openbmc_project/sensors/fan_tach/fan{N}` it
94sets of a sensor object that writes over dbus to the
95`xyz.openbmc_project.Control.FanPwm` interface. The `writePath` should be the
96full object path.
97
98```
99busctl introspect xyz.openbmc_project.Hwmon-1644477290.Hwmon1 /xyz/openbmc_project/sensors/fan_tach/fan1 --no-pager
100NAME                                TYPE      SIGNATURE RESULT/VALUE                             FLAGS
101org.freedesktop.DBus.Introspectable interface -         -                                        -
102.Introspect                         method    -         s                                        -
103org.freedesktop.DBus.Peer           interface -         -                                        -
104.GetMachineId                       method    -         s                                        -
105.Ping                               method    -         -                                        -
106org.freedesktop.DBus.Properties     interface -         -                                        -
107.Get                                method    ss        v                                        -
108.GetAll                             method    s         a{sv}                                    -
109.Set                                method    ssv       -                                        -
110.PropertiesChanged                  signal    sa{sv}as  -                                        -
111xyz.openbmc_project.Control.FanPwm  interface -         -                                        -
112.Target                             property  t         255                                      emits-change writable
113xyz.openbmc_project.Sensor.Value    interface -         -                                        -
114.MaxValue                           property  x         0                                        emits-change writable
115.MinValue                           property  x         0                                        emits-change writable
116.Scale                              property  x         0                                        emits-change writable
117.Unit                               property  s         "xyz.openbmc_project.Sensor.Value.Uni... emits-change writable
118.Value                              property  x         2823                                     emits-change writable
119```
120
121The `minimum` and `maximum` values are optional. When `maximum` is non-zero it
122expects to write a percentage value converted to a value between the minimum and
123maximum.
124
125The `timeout` value is optional and controls the sensor failure behavior. If a
126sensor is a fan the default value is 2 seconds, otherwise it's 0. When a
127sensor's timeout is 0 it isn't checked against a read timeout failure case. If a
128sensor fails to be read within the timeout period, the zone goes into failsafe
129to handle the case where it doesn't know what to do -- as it doesn't have all
130its inputs.
131
132The `ignoreDbusMinMax` value is optional and defaults to false.  The dbus
133passive sensors check for a `MinValue` and `MaxValue` and scale the incoming
134values via these.  Setting this property to true will ignore `MinValue` and
135`MaxValue` from dbus and therefore won't call any passive value scaling.
136
137The `unavailableAsFailed` value is optional and defaults to true. However,
138some specific thermal sensors should not be treated as Failed when they are
139unavailable. For example, when a system is powered-off, its CPU/DIMM Temp sensors
140are unavailable, in such state these sensors should not be treated as Failed and
141trigger FailSafe. This is important for systems whose Fans are always on.
142For these specific sensors set this property to false.
143
144### Zones
145
146```
147"zones" : [
148        {
149            "id": 1,
150            "minThermalOutput": 3000.0,
151            "failsafePercent": 75.0,
152            "pids": [],
153...
154```
155
156Each zone has its own fields, and a list of controllers.
157
158| field              | type      | meaning                                   |
159| ------------------ | --------- | ----------------------------------------- |
160| `id`               | `int64_t` | This is a unique identifier for the zone. |
161| `minThermalOutput` | `double`  | This is the minimum value that should be considered from the thermal outputs.  Commonly used as the minimum fan RPM.|
162| `failsafePercent`  | `double`  | If there is a fan PID, it will use this value if the zone goes into fail-safe as the output value written to the fan's sensors.|
163| `pids`             | `list of strings` | Fan and thermal controllers used by the zone.|
164
165The `id` field here is used in the d-bus path to talk to the
166`xyz.openbmc_project.Control.Mode` interface.
167
168***TODO:*** Examine how the fan controller always treating its output as a
169percentage works for future cases.
170
171A zone collects all the setpoints and ceilings from the thermal
172controllers attached to it, selects the maximum setpoint, clamps it by
173the minimum ceiling and `minThermalOutput`; the result is used to
174control fans.
175
176### Controllers
177
178There are `fan`, `temp`, `margin` (PID), and `stepwise` (discrete steps)
179controllers.
180
181The `fan` PID is meant to drive fans or other cooling devices. It's
182expecting to get the setpoint value from the owning zone and then
183drive the fans to that value.
184
185A `temp` PID is meant to drive the setpoint given an absolute
186temperature value (higher value indicates warmer temperature).
187
188A `margin` PID is meant to drive the setpoint given a margin value
189(lower value indicates warmer temperature, in other words, it's the
190safety margin remaining expressed in degrees Celsius).
191
192The setpoint output from the thermal controllers is called `RPMSetpoint()`
193However, it doesn't need to be an RPM value.
194
195***TODO:*** Rename this method and others to not say necessarily RPM.
196
197Some PID configurations have fields in common, but may be interpreted
198differently.
199
200When using D-Bus, each configuration can have a list of strings called
201`Profiles`. In this case the controller will be loaded only if at
202least one of them is returned as `Current` from an object implementing
203`xyz.openbmc_project.Control.ThermalMode` interface (which can be
204anywhere on D-Bus). `swampd` will automatically reload full
205configuration whenever `Current` is changed.
206
207D-Bus `Name` attribute is used for indexing in certain cases so should
208be unique for all defined configurations.
209
210#### PID Field
211
212If the PID `type` is not `stepwise` then the PID field is defined as follows:
213
214| field                | type     | meaning                                   |
215| -------------------- | -------- | ----------------------------------------- |
216| `samplePeriod`       | `double` | How frequently the value is sampled. 0.1 for fans, 1.0 for temperatures.|
217| `proportionalCoeff`  | `double` | The proportional coefficient.             |
218| `integralCoeff`      | `double` | The integral coefficient.                 |
219| `feedFwdOffsetCoeff` | `double` | The feed forward offset coefficient.      |
220| `feedFwdGainCoeff`   | `double` | The feed forward gain coefficient.        |
221| `integralLimit_min`  | `double` | The integral minimum clamp value.         |
222| `integralLimit_max`  | `double` | The integral maximum clamp value.         |
223| `outLim_min`         | `double` | The output minimum clamp value.           |
224| `outLim_max`         | `double` | The output maximum clamp value.           |
225| `slewNeg`            | `double` | Negative slew value to dampen output.     |
226| `slewPos`            | `double` | Positive slew value to accelerate output. |
227
228The units for the coefficients depend on the configuration of the PIDs.
229
230If the PID is a `margin` controller and its `setpoint` is in centigrade and
231output in RPM: proportionalCoeff is your p value in units: RPM/C and integral
232coefficient: RPM/C sec
233
234If the PID is a fan controller whose output is pwm: proportionalCoeff is %/RPM
235and integralCoeff is %/RPM sec.
236
237***NOTE:*** The sample periods are specified in the configuration as they are
238used in the PID computations, however, they are not truly configurable as they
239are used for the update periods for the fan and thermal sensors.
240
241#### type == "fan"
242
243```
244"name": "fan1-5",
245"type": "fan",
246"inputs": ["fan1", "fan5"],
247"setpoint": 90.0,
248"pid": {
249...
250}
251```
252
253The type `fan` builds a `FanController` PID.
254
255| field      | type              | meaning                                     |
256| ---------- | ----------------- | ------------------------------------------- |
257| `name`     | `string`          | The name of the PID. This is just for humans and logging.|
258| `type`     | `string`          | `fan`                                       |
259| `inputs`   | `list of strings` | The names of the sensor(s) that are used as input and output for the PID loop.|
260| `setpoint` | `double`          | Presently UNUSED                            |
261| `pid`      | `dictionary`      | A PID dictionary detailed above.            |
262
263#### type == "margin"
264
265```
266"name": "fleetingpid0",
267"type": "margin",
268"inputs": ["fleeting0"],
269"setpoint": 10,
270"pid": {
271...
272}
273```
274
275The type `margin` builds a `ThermalController` PID.
276
277| field      | type              | meaning                                     |
278| ---------- | ----------------- | ------------------------------------------- |
279| `name`     | `string`          | The name of the PID. This is just for humans and logging.|
280| `type`     | `string`          | `margin`                                    |
281| `inputs`   | `list of strings` | The names of the sensor(s) that are used as input for the PID loop.|
282| `setpoint` | `double`          | The setpoint value for the thermal PID. The setpoint for the margin sensors.|
283| `pid`      | `dictionary`      | A PID dictionary detailed above.            |
284
285Each input is normally a temperature difference between some hardware
286threshold and the current state. E.g. a CPU sensor can be reporting
287that it's 20 degrees below the point when it starts thermal
288throttling. So the lower the margin temperature, the higher the
289corresponding absolute value.
290
291Out of all the `inputs` the minimal value is selected and used as an
292input for the PID loop.
293
294The output of a `margin` PID loop is that it sets the setpoint value for the
295zone. It does this by adding the value to a list of values. The value chosen by
296the fan PIDs (in this cascade configuration) is the maximum value.
297
298#### type == "temp"
299
300Exactly the same as `margin` but all the inputs are supposed to be
301absolute temperatures and so the maximal value is used to feed the PID
302loop.
303
304#### type == "stepwise"
305```
306"name": "temp1",
307"type": "stepwise",
308"inputs": ["temp1"],
309"setpoint": 30.0,
310"pid": {
311  "samplePeriod": 0.1,
312  "positiveHysteresis": 1.0,
313  "negativeHysteresis": 1.0,
314  "isCeiling": false,
315  "reading": {
316    "0": 45,
317    "1": 46,
318    "2": 47,
319  },
320  "output": {
321    "0": 5000,
322    "1": 2400,
323    "2": 2600,
324  }
325}
326```
327
328The type `stepwise` builds a `StepwiseController`.
329
330| field      | type              | meaning                                                                          |
331| ---------- | ----------------- | -------------------------------------------                                      |
332| `name`     | `string`          | The name of the controller. This is just for humans and logging.                 |
333| `type`     | `string`          | `stepwise`                                                                       |
334| `inputs`   | `list of strings` | The names of the sensor(s) that are used as input and output for the controller. |
335| `pid`      | `dictionary`      | A controller settings dictionary detailed below.                                 |
336
337The `pid` dictionary (confusingly named) is defined as follows:
338
339| field                | type         | meaning                                                                                              |
340| -------------------- | --------     | -----------------------------------------                                                            |
341| `samplePeriod`       | `double`     | Presently UNUSED.                                                                                    |
342| `reading`            | `dictionary` | Enumerated list of input values, indexed from 0, must be monotonically increasing, maximum 20 items. |
343| `output`             | `dictionary` | Enumerated list of output values, indexed from 0, must match the amount of `reading` items.          |
344| `positiveHysteresis` | `double`     | How much the input value must raise to allow the switch to the next step.                            |
345| `negativeHysteresis` | `double`     | How much the input value must drop to allow the switch to the previous step.                         |
346| `isCeiling`          | `bool`       | Whether this controller provides a setpoint or a ceiling for the zone                                |
347| `setpoint`           | `double`     | Presently UNUSED.                                                                                    |
348
349***NOTE:*** `reading` and `output` are normal arrays and not embedded
350in the dictionary in Entity Manager.
351
352Each measurement cycle out of all the `inputs` the maximum value is
353selected. Then it's compared to the list of `reading` values finding
354the largest that's still lower or equal the input (the very first item
355is used even if it's larger than the input). The corresponding
356`output` value is selected if hysteresis allows the switch (the
357current input value is compared with the input present at the moment
358of the previous switch). The result is added to the list of setpoints
359or ceilings for the zone depending on `isCeiling` setting.
360