1/**************************************************************************
2 *
3 * Copyright (C) 2016 Steven Toth <stoth@kernellabs.com>
4 * Copyright (C) 2016 Zodiac Inflight Innovations
5 * All Rights Reserved.
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a
8 * copy of this software and associated documentation files (the
9 * "Software"), to deal in the Software without restriction, including
10 * without limitation the rights to use, copy, modify, merge, publish,
11 * distribute, sub license, and/or sell copies of the Software, and to
12 * permit persons to whom the Software is furnished to do so, subject to
13 * the following conditions:
14 *
15 * The above copyright notice and this permission notice (including the
16 * next paragraph) shall be included in all copies or substantial portions
17 * of the Software.
18 *
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
20 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
22 * IN NO EVENT SHALL THE AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR
23 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
24 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
25 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 *
27 **************************************************************************/
28
29#ifdef HAVE_LIBSENSORS
30/* Purpose: Extract lm-sensors data, expose temperature, power, voltage. */
31
32#include "hud/hud_private.h"
33#include "util/list.h"
34#include "util/os_time.h"
35#include "os/os_thread.h"
36#include "util/u_memory.h"
37#include <stdio.h>
38#include <unistd.h>
39#include <dirent.h>
40#include <stdlib.h>
41#include <unistd.h>
42#include <inttypes.h>
43#include <sys/types.h>
44#include <sys/stat.h>
45#include <unistd.h>
46#include <sensors/sensors.h>
47
48/* TODO: We don't handle dynamic sensor discovery / arrival or removal.
49 * Static globals specific to this HUD category.
50 */
51static int gsensors_temp_count = 0;
52static struct list_head gsensors_temp_list;
53static mtx_t gsensor_temp_mutex = _MTX_INITIALIZER_NP;
54
55struct sensors_temp_info
56{
57   struct list_head list;
58
59   /* Combined chip and feature name, human readable. */
60   char name[64];
61
62   /* The type of measurement, critical or current. */
63   unsigned int mode;
64
65   uint64_t last_time;
66
67   char chipname[64];
68   char featurename[128];
69
70   sensors_chip_name *chip;
71   const sensors_feature *feature;
72   double current, min, max, critical;
73};
74
75static double
76get_value(const sensors_chip_name *name, const sensors_subfeature *sub)
77{
78   double val;
79   int err;
80
81   err = sensors_get_value(name, sub->number, &val);
82   if (err) {
83      fprintf(stderr, "ERROR: Can't get value of subfeature %s\n", sub->name);
84      val = 0;
85   }
86   return val;
87}
88
89static void
90get_sensor_values(struct sensors_temp_info *sti)
91{
92   const sensors_subfeature *sf;
93
94   switch(sti->mode) {
95   case SENSORS_VOLTAGE_CURRENT:
96      sf = sensors_get_subfeature(sti->chip, sti->feature,
97                                  SENSORS_SUBFEATURE_IN_INPUT);
98      if (sf)
99         sti->current = get_value(sti->chip, sf);
100      break;
101   case SENSORS_CURRENT_CURRENT:
102      sf = sensors_get_subfeature(sti->chip, sti->feature,
103                                  SENSORS_SUBFEATURE_CURR_INPUT);
104      if (sf) {
105         /* Sensors API returns in AMPs, even though driver is reporting mA,
106          * convert back to mA */
107         sti->current = get_value(sti->chip, sf) * 1000;
108      }
109     break;
110   case SENSORS_TEMP_CURRENT:
111      sf = sensors_get_subfeature(sti->chip, sti->feature,
112                                  SENSORS_SUBFEATURE_TEMP_INPUT);
113      if (sf)
114         sti->current = get_value(sti->chip, sf);
115      break;
116   case SENSORS_TEMP_CRITICAL:
117      sf = sensors_get_subfeature(sti->chip, sti->feature,
118                                  SENSORS_SUBFEATURE_TEMP_CRIT);
119      if (sf)
120         sti->critical = get_value(sti->chip, sf);
121      break;
122   case SENSORS_POWER_CURRENT:
123      sf = sensors_get_subfeature(sti->chip, sti->feature,
124                                  SENSORS_SUBFEATURE_POWER_INPUT);
125      if (!sf)
126          sf = sensors_get_subfeature(sti->chip, sti->feature,
127                                      SENSORS_SUBFEATURE_POWER_AVERAGE);
128      if (sf) {
129         /* Sensors API returns in WATTs, even though driver is reporting mW,
130          * convert back to mW */
131         sti->current = get_value(sti->chip, sf) * 1000;
132      }
133      break;
134   }
135
136   sf = sensors_get_subfeature(sti->chip, sti->feature,
137                               SENSORS_SUBFEATURE_TEMP_MIN);
138   if (sf)
139      sti->min = get_value(sti->chip, sf);
140
141   sf = sensors_get_subfeature(sti->chip, sti->feature,
142                               SENSORS_SUBFEATURE_TEMP_MAX);
143   if (sf)
144      sti->max = get_value(sti->chip, sf);
145}
146
147static struct sensors_temp_info *
148find_sti_by_name(const char *n, unsigned int mode)
149{
150   list_for_each_entry(struct sensors_temp_info, sti, &gsensors_temp_list, list) {
151      if (sti->mode != mode)
152         continue;
153      if (strcasecmp(sti->name, n) == 0)
154         return sti;
155   }
156   return 0;
157}
158
159static void
160query_sti_load(struct hud_graph *gr, struct pipe_context *pipe)
161{
162   struct sensors_temp_info *sti = gr->query_data;
163   uint64_t now = os_time_get();
164
165   if (sti->last_time) {
166      if (sti->last_time + gr->pane->period <= now) {
167         get_sensor_values(sti);
168
169         switch (sti->mode) {
170         case SENSORS_TEMP_CURRENT:
171            hud_graph_add_value(gr, sti->current);
172            break;
173         case SENSORS_TEMP_CRITICAL:
174            hud_graph_add_value(gr, sti->critical);
175            break;
176         case SENSORS_VOLTAGE_CURRENT:
177            hud_graph_add_value(gr, sti->current * 1000);
178            break;
179         case SENSORS_CURRENT_CURRENT:
180            hud_graph_add_value(gr, sti->current);
181            break;
182         case SENSORS_POWER_CURRENT:
183            hud_graph_add_value(gr, sti->current);
184            break;
185         }
186
187         sti->last_time = now;
188      }
189   }
190   else {
191      /* initialize */
192      get_sensor_values(sti);
193      sti->last_time = now;
194   }
195}
196
197/**
198  * Create and initialize a new object for a specific sensor interface dev.
199  * \param  pane  parent context.
200  * \param  dev_name  device name, EG. 'coretemp-isa-0000.Core 1'
201  * \param  mode  query type (NIC_DIRECTION_RX/WR/RSSI) statistics.
202  */
203void
204hud_sensors_temp_graph_install(struct hud_pane *pane, const char *dev_name,
205                               unsigned int mode)
206{
207   struct hud_graph *gr;
208   struct sensors_temp_info *sti;
209
210   int num_devs = hud_get_num_sensors(0);
211   if (num_devs <= 0)
212      return;
213
214   sti = find_sti_by_name(dev_name, mode);
215   if (!sti)
216      return;
217
218   gr = CALLOC_STRUCT(hud_graph);
219   if (!gr)
220      return;
221
222   snprintf(gr->name, sizeof(gr->name), "%.6s..%s (%s)",
223           sti->chipname,
224           sti->featurename,
225           sti->mode == SENSORS_VOLTAGE_CURRENT ? "Volts" :
226           sti->mode == SENSORS_CURRENT_CURRENT ? "Amps" :
227           sti->mode == SENSORS_TEMP_CURRENT ? "Curr" :
228           sti->mode == SENSORS_POWER_CURRENT ? "Pow" :
229           sti->mode == SENSORS_TEMP_CRITICAL ? "Crit" : "Unkn");
230
231   gr->query_data = sti;
232   gr->query_new_value = query_sti_load;
233
234   hud_pane_add_graph(pane, gr);
235   switch (sti->mode) {
236   case SENSORS_TEMP_CURRENT:
237   case SENSORS_TEMP_CRITICAL:
238      hud_pane_set_max_value(pane, 120);
239      break;
240   case SENSORS_VOLTAGE_CURRENT:
241      hud_pane_set_max_value(pane, 12);
242      break;
243   case SENSORS_CURRENT_CURRENT:
244      hud_pane_set_max_value(pane, 5000);
245      break;
246   case SENSORS_POWER_CURRENT:
247      hud_pane_set_max_value(pane, 5000 /* mW */);
248      break;
249   }
250}
251
252static void
253create_object(const char *chipname, const char *featurename,
254             const sensors_chip_name *chip, const sensors_feature *feature,
255             int mode)
256{
257   struct sensors_temp_info *sti = CALLOC_STRUCT(sensors_temp_info);
258
259   sti->mode = mode;
260   sti->chip = (sensors_chip_name *) chip;
261   sti->feature = feature;
262   snprintf(sti->chipname, sizeof(sti->chipname), "%s", chipname);
263   snprintf(sti->featurename, sizeof(sti->featurename), "%s", featurename);
264   snprintf(sti->name, sizeof(sti->name), "%s.%s", sti->chipname,
265      sti->featurename);
266
267   list_addtail(&sti->list, &gsensors_temp_list);
268   gsensors_temp_count++;
269}
270
271static void
272build_sensor_list(void)
273{
274   const sensors_chip_name *chip;
275   const sensors_chip_name *match = 0;
276   const sensors_feature *feature;
277   int chip_nr = 0;
278
279   char name[256];
280   while ((chip = sensors_get_detected_chips(match, &chip_nr))) {
281      sensors_snprintf_chip_name(name, sizeof(name), chip);
282
283      /* Get all features and filter accordingly. */
284      int fnr = 0;
285      while ((feature = sensors_get_features(chip, &fnr))) {
286         char *featurename = sensors_get_label(chip, feature);
287         if (!featurename)
288            continue;
289
290         /* Create a 'current' and 'critical' object pair.
291          * Ignore sensor if its not temperature based.
292          */
293         switch(feature->type) {
294         case SENSORS_FEATURE_TEMP:
295            create_object(name, featurename, chip, feature,
296                          SENSORS_TEMP_CURRENT);
297            create_object(name, featurename, chip, feature,
298                          SENSORS_TEMP_CRITICAL);
299            break;
300         case SENSORS_FEATURE_IN:
301            create_object(name, featurename, chip, feature,
302                          SENSORS_VOLTAGE_CURRENT);
303            break;
304         case SENSORS_FEATURE_CURR:
305            create_object(name, featurename, chip, feature,
306                          SENSORS_CURRENT_CURRENT);
307            break;
308         case SENSORS_FEATURE_POWER:
309            create_object(name, featurename, chip, feature,
310                          SENSORS_POWER_CURRENT);
311            break;
312         default:
313            break;
314         }
315         free(featurename);
316      }
317   }
318}
319
320/**
321  * Initialize internal object arrays and display lmsensors HUD help.
322  * \param  displayhelp  true if the list of detected devices should be
323                         displayed on the console.
324  * \return  number of detected lmsensor devices.
325  */
326int
327hud_get_num_sensors(bool displayhelp)
328{
329   /* Return the number of sensors detected. */
330   mtx_lock(&gsensor_temp_mutex);
331   if (gsensors_temp_count) {
332      mtx_unlock(&gsensor_temp_mutex);
333      return gsensors_temp_count;
334   }
335
336   int ret = sensors_init(NULL);
337   if (ret) {
338      mtx_unlock(&gsensor_temp_mutex);
339      return 0;
340   }
341
342   list_inithead(&gsensors_temp_list);
343
344   /* Scan /sys/block, for every object type we support, create and
345    * persist an object to represent its different statistics.
346    */
347   build_sensor_list();
348
349   if (displayhelp) {
350      list_for_each_entry(struct sensors_temp_info, sti, &gsensors_temp_list, list) {
351         char line[64];
352         switch (sti->mode) {
353         case SENSORS_TEMP_CURRENT:
354            snprintf(line, sizeof(line), "    sensors_temp_cu-%s", sti->name);
355            break;
356         case SENSORS_TEMP_CRITICAL:
357            snprintf(line, sizeof(line), "    sensors_temp_cr-%s", sti->name);
358            break;
359         case SENSORS_VOLTAGE_CURRENT:
360            snprintf(line, sizeof(line), "    sensors_volt_cu-%s", sti->name);
361            break;
362         case SENSORS_CURRENT_CURRENT:
363            snprintf(line, sizeof(line), "    sensors_curr_cu-%s", sti->name);
364            break;
365         case SENSORS_POWER_CURRENT:
366            snprintf(line, sizeof(line), "    sensors_pow_cu-%s", sti->name);
367            break;
368         }
369
370         puts(line);
371      }
372   }
373
374   mtx_unlock(&gsensor_temp_mutex);
375   return gsensors_temp_count;
376}
377
378#endif /* HAVE_LIBSENSORS */
379