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 "util/u_string.h"
38#include <stdio.h>
39#include <unistd.h>
40#include <dirent.h>
41#include <stdlib.h>
42#include <unistd.h>
43#include <inttypes.h>
44#include <sys/types.h>
45#include <sys/stat.h>
46#include <unistd.h>
47#include <sensors/sensors.h>
48
49/* TODO: We don't handle dynamic sensor discovery / arrival or removal.
50 * Static globals specific to this HUD category.
51 */
52static int gsensors_temp_count = 0;
53static struct list_head gsensors_temp_list;
54static mtx_t gsensor_temp_mutex = _MTX_INITIALIZER_NP;
55
56struct sensors_temp_info
57{
58   struct list_head list;
59
60   /* Combined chip and feature name, human readable. */
61   char name[64];
62
63   /* The type of measurement, critical or current. */
64   unsigned int mode;
65
66   uint64_t last_time;
67
68   char chipname[64];
69   char featurename[128];
70
71   sensors_chip_name *chip;
72   const sensors_feature *feature;
73   double current, min, max, critical;
74};
75
76static double
77get_value(const sensors_chip_name *name, const sensors_subfeature *sub)
78{
79   double val;
80   int err;
81
82   err = sensors_get_value(name, sub->number, &val);
83   if (err) {
84      fprintf(stderr, "ERROR: Can't get value of subfeature %s\n", sub->name);
85      val = 0;
86   }
87   return val;
88}
89
90static void
91get_sensor_values(struct sensors_temp_info *sti)
92{
93   const sensors_subfeature *sf;
94
95   switch(sti->mode) {
96   case SENSORS_VOLTAGE_CURRENT:
97      sf = sensors_get_subfeature(sti->chip, sti->feature,
98                                  SENSORS_SUBFEATURE_IN_INPUT);
99      if (sf)
100         sti->current = get_value(sti->chip, sf);
101      break;
102   case SENSORS_CURRENT_CURRENT:
103      sf = sensors_get_subfeature(sti->chip, sti->feature,
104                                  SENSORS_SUBFEATURE_CURR_INPUT);
105      if (sf) {
106         /* Sensors API returns in AMPs, even though driver is reporting mA,
107          * convert back to mA */
108         sti->current = get_value(sti->chip, sf) * 1000;
109      }
110     break;
111   case SENSORS_TEMP_CURRENT:
112      sf = sensors_get_subfeature(sti->chip, sti->feature,
113                                  SENSORS_SUBFEATURE_TEMP_INPUT);
114      if (sf)
115         sti->current = get_value(sti->chip, sf);
116      break;
117   case SENSORS_TEMP_CRITICAL:
118      sf = sensors_get_subfeature(sti->chip, sti->feature,
119                                  SENSORS_SUBFEATURE_TEMP_CRIT);
120      if (sf)
121         sti->critical = get_value(sti->chip, sf);
122      break;
123   case SENSORS_POWER_CURRENT:
124      sf = sensors_get_subfeature(sti->chip, sti->feature,
125                                  SENSORS_SUBFEATURE_POWER_INPUT);
126      if (!sf)
127          sf = sensors_get_subfeature(sti->chip, sti->feature,
128                                      SENSORS_SUBFEATURE_POWER_AVERAGE);
129      if (sf) {
130         /* Sensors API returns in WATTs, even though driver is reporting mW,
131          * convert back to mW */
132         sti->current = get_value(sti->chip, sf) * 1000;
133      }
134      break;
135   }
136
137   sf = sensors_get_subfeature(sti->chip, sti->feature,
138                               SENSORS_SUBFEATURE_TEMP_MIN);
139   if (sf)
140      sti->min = get_value(sti->chip, sf);
141
142   sf = sensors_get_subfeature(sti->chip, sti->feature,
143                               SENSORS_SUBFEATURE_TEMP_MAX);
144   if (sf)
145      sti->max = get_value(sti->chip, sf);
146}
147
148static struct sensors_temp_info *
149find_sti_by_name(const char *n, unsigned int mode)
150{
151   list_for_each_entry(struct sensors_temp_info, sti, &gsensors_temp_list, list) {
152      if (sti->mode != mode)
153         continue;
154      if (strcasecmp(sti->name, n) == 0)
155         return sti;
156   }
157   return 0;
158}
159
160static void
161query_sti_load(struct hud_graph *gr, struct pipe_context *pipe)
162{
163   struct sensors_temp_info *sti = gr->query_data;
164   uint64_t now = os_time_get();
165
166   if (sti->last_time) {
167      if (sti->last_time + gr->pane->period <= now) {
168         get_sensor_values(sti);
169
170         switch (sti->mode) {
171         case SENSORS_TEMP_CURRENT:
172            hud_graph_add_value(gr, sti->current);
173            break;
174         case SENSORS_TEMP_CRITICAL:
175            hud_graph_add_value(gr, sti->critical);
176            break;
177         case SENSORS_VOLTAGE_CURRENT:
178            hud_graph_add_value(gr, sti->current * 1000);
179            break;
180         case SENSORS_CURRENT_CURRENT:
181            hud_graph_add_value(gr, sti->current);
182            break;
183         case SENSORS_POWER_CURRENT:
184            hud_graph_add_value(gr, sti->current);
185            break;
186         }
187
188         sti->last_time = now;
189      }
190   }
191   else {
192      /* initialize */
193      get_sensor_values(sti);
194      sti->last_time = now;
195   }
196}
197
198/**
199  * Create and initialize a new object for a specific sensor interface dev.
200  * \param  pane  parent context.
201  * \param  dev_name  device name, EG. 'coretemp-isa-0000.Core 1'
202  * \param  mode  query type (NIC_DIRECTION_RX/WR/RSSI) statistics.
203  */
204void
205hud_sensors_temp_graph_install(struct hud_pane *pane, const char *dev_name,
206                               unsigned int mode)
207{
208   struct hud_graph *gr;
209   struct sensors_temp_info *sti;
210
211   int num_devs = hud_get_num_sensors(0);
212   if (num_devs <= 0)
213      return;
214
215   sti = find_sti_by_name(dev_name, mode);
216   if (!sti)
217      return;
218
219   gr = CALLOC_STRUCT(hud_graph);
220   if (!gr)
221      return;
222
223   snprintf(gr->name, sizeof(gr->name), "%.6s..%s (%s)",
224           sti->chipname,
225           sti->featurename,
226           sti->mode == SENSORS_VOLTAGE_CURRENT ? "Volts" :
227           sti->mode == SENSORS_CURRENT_CURRENT ? "Amps" :
228           sti->mode == SENSORS_TEMP_CURRENT ? "Curr" :
229           sti->mode == SENSORS_POWER_CURRENT ? "Pow" :
230           sti->mode == SENSORS_TEMP_CRITICAL ? "Crit" : "Unkn");
231
232   gr->query_data = sti;
233   gr->query_new_value = query_sti_load;
234
235   hud_pane_add_graph(pane, gr);
236   switch (sti->mode) {
237   case SENSORS_TEMP_CURRENT:
238   case SENSORS_TEMP_CRITICAL:
239      hud_pane_set_max_value(pane, 120);
240      break;
241   case SENSORS_VOLTAGE_CURRENT:
242      hud_pane_set_max_value(pane, 12);
243      break;
244   case SENSORS_CURRENT_CURRENT:
245      hud_pane_set_max_value(pane, 5000);
246      break;
247   case SENSORS_POWER_CURRENT:
248      hud_pane_set_max_value(pane, 5000 /* mW */);
249      break;
250   }
251}
252
253static void
254create_object(const char *chipname, const char *featurename,
255             const sensors_chip_name *chip, const sensors_feature *feature,
256             int mode)
257{
258   struct sensors_temp_info *sti = CALLOC_STRUCT(sensors_temp_info);
259
260   sti->mode = mode;
261   sti->chip = (sensors_chip_name *) chip;
262   sti->feature = feature;
263   snprintf(sti->chipname, sizeof(sti->chipname), "%s", chipname);
264   snprintf(sti->featurename, sizeof(sti->featurename), "%s", featurename);
265   snprintf(sti->name, sizeof(sti->name), "%s.%s", sti->chipname,
266      sti->featurename);
267
268   list_addtail(&sti->list, &gsensors_temp_list);
269   gsensors_temp_count++;
270}
271
272static void
273build_sensor_list(void)
274{
275   const sensors_chip_name *chip;
276   const sensors_chip_name *match = 0;
277   const sensors_feature *feature;
278   int chip_nr = 0;
279
280   char name[256];
281   while ((chip = sensors_get_detected_chips(match, &chip_nr))) {
282      sensors_snprintf_chip_name(name, sizeof(name), chip);
283
284      /* Get all features and filter accordingly. */
285      int fnr = 0;
286      while ((feature = sensors_get_features(chip, &fnr))) {
287         char *featurename = sensors_get_label(chip, feature);
288         if (!featurename)
289            continue;
290
291         /* Create a 'current' and 'critical' object pair.
292          * Ignore sensor if its not temperature based.
293          */
294         switch(feature->type) {
295         case SENSORS_FEATURE_TEMP:
296            create_object(name, featurename, chip, feature,
297                          SENSORS_TEMP_CURRENT);
298            create_object(name, featurename, chip, feature,
299                          SENSORS_TEMP_CRITICAL);
300            break;
301         case SENSORS_FEATURE_IN:
302            create_object(name, featurename, chip, feature,
303                          SENSORS_VOLTAGE_CURRENT);
304            break;
305         case SENSORS_FEATURE_CURR:
306            create_object(name, featurename, chip, feature,
307                          SENSORS_CURRENT_CURRENT);
308            break;
309         case SENSORS_FEATURE_POWER:
310            create_object(name, featurename, chip, feature,
311                          SENSORS_POWER_CURRENT);
312            break;
313         default:
314            break;
315         }
316         free(featurename);
317      }
318   }
319}
320
321/**
322  * Initialize internal object arrays and display lmsensors HUD help.
323  * \param  displayhelp  true if the list of detected devices should be
324                         displayed on the console.
325  * \return  number of detected lmsensor devices.
326  */
327int
328hud_get_num_sensors(bool displayhelp)
329{
330   /* Return the number of sensors detected. */
331   mtx_lock(&gsensor_temp_mutex);
332   if (gsensors_temp_count) {
333      mtx_unlock(&gsensor_temp_mutex);
334      return gsensors_temp_count;
335   }
336
337   int ret = sensors_init(NULL);
338   if (ret) {
339      mtx_unlock(&gsensor_temp_mutex);
340      return 0;
341   }
342
343   list_inithead(&gsensors_temp_list);
344
345   /* Scan /sys/block, for every object type we support, create and
346    * persist an object to represent its different statistics.
347    */
348   build_sensor_list();
349
350   if (displayhelp) {
351      list_for_each_entry(struct sensors_temp_info, sti, &gsensors_temp_list, list) {
352         char line[64];
353         switch (sti->mode) {
354         case SENSORS_TEMP_CURRENT:
355            snprintf(line, sizeof(line), "    sensors_temp_cu-%s", sti->name);
356            break;
357         case SENSORS_TEMP_CRITICAL:
358            snprintf(line, sizeof(line), "    sensors_temp_cr-%s", sti->name);
359            break;
360         case SENSORS_VOLTAGE_CURRENT:
361            snprintf(line, sizeof(line), "    sensors_volt_cu-%s", sti->name);
362            break;
363         case SENSORS_CURRENT_CURRENT:
364            snprintf(line, sizeof(line), "    sensors_curr_cu-%s", sti->name);
365            break;
366         case SENSORS_POWER_CURRENT:
367            snprintf(line, sizeof(line), "    sensors_pow_cu-%s", sti->name);
368            break;
369         }
370
371         puts(line);
372      }
373   }
374
375   mtx_unlock(&gsensor_temp_mutex);
376   return gsensors_temp_count;
377}
378
379#endif /* HAVE_LIBSENSORS */
380