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  */
52 static int gsensors_temp_count = 0;
53 static struct list_head gsensors_temp_list;
54 static mtx_t gsensor_temp_mutex = _MTX_INITIALIZER_NP;
55 
56 struct 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 
76 static double
get_value(const sensors_chip_name * name,const sensors_subfeature * sub)77 get_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 
90 static void
get_sensor_values(struct sensors_temp_info * sti)91 get_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 
148 static struct sensors_temp_info *
find_sti_by_name(const char * n,unsigned int mode)149 find_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 
160 static void
query_sti_load(struct hud_graph * gr,struct pipe_context * pipe)161 query_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   */
204 void
hud_sensors_temp_graph_install(struct hud_pane * pane,const char * dev_name,unsigned int mode)205 hud_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 
253 static void
create_object(const char * chipname,const char * featurename,const sensors_chip_name * chip,const sensors_feature * feature,int mode)254 create_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 
272 static void
build_sensor_list(void)273 build_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   */
327 int
hud_get_num_sensors(bool displayhelp)328 hud_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