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