1 /**
2 * Copyright © 2016 IBM Corporation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <limits.h>
19 #include <errno.h>
20 #include <string.h>
21 #include <getopt.h>
22 #include <systemd/sd-bus.h>
23
24 #define DBUS_MAX_NAME_LEN 256
25
26 const char *objectmapper_service_name = "xyz.openbmc_project.ObjectMapper";
27 const char *objectmapper_object_name = "/xyz/openbmc_project/object_mapper";
28 const char *objectmapper_intf_name = "xyz.openbmc_project.ObjectMapper";
29
30 typedef struct {
31 int fan_num;
32 int cpu_num;
33 int core_num;
34 int dimm_num;
35 sd_bus *bus;
36 char sensor_service[DBUS_MAX_NAME_LEN];
37 char inventory_service[DBUS_MAX_NAME_LEN];
38 } fan_info_t;
39
40 /* Get an object's bus name from ObjectMapper */
get_connection(sd_bus * bus,char * connection,const char * obj_path)41 int get_connection(sd_bus *bus, char *connection, const char *obj_path)
42 {
43 sd_bus_error bus_error = SD_BUS_ERROR_NULL;
44 sd_bus_message *m = NULL;
45 char *temp_buf = NULL, *intf = NULL;
46 int rc;
47
48 rc = sd_bus_call_method(bus,
49 objectmapper_service_name,
50 objectmapper_object_name,
51 objectmapper_intf_name,
52 "GetObject",
53 &bus_error,
54 &m,
55 "ss",
56 obj_path,
57 "");
58 if (rc < 0) {
59 fprintf(stderr,
60 "Failed to GetObject: %s\n", bus_error.message);
61 goto finish;
62 }
63
64 /* Get the key, aka, the bus name */
65 sd_bus_message_read(m, "a{sas}", 1, &temp_buf, 1, &intf);
66 strncpy(connection, temp_buf, DBUS_MAX_NAME_LEN);
67
68 finish:
69 sd_bus_error_free(&bus_error);
70 sd_bus_message_unref(m);
71 sd_bus_flush(bus);
72
73 return rc;
74 }
75
76
set_dbus_sensor(sd_bus * bus,const char * obj_path,int val)77 int set_dbus_sensor(sd_bus *bus, const char *obj_path, int val)
78 {
79 char connection[DBUS_MAX_NAME_LEN];
80 sd_bus_error bus_error = SD_BUS_ERROR_NULL;
81 sd_bus_message *response = NULL;
82 int rc;
83
84 if (!bus || !obj_path)
85 return -1;
86
87 rc = get_connection(bus, connection, obj_path);
88 if (rc < 0) {
89 fprintf(stderr,
90 "fanctl: Failed to get bus name for %s\n", obj_path);
91 goto finish;
92 }
93
94 rc = sd_bus_set_property(bus,
95 connection,
96 obj_path,
97 "xyz.openbmc_project.Control.FanSpeed",
98 "Target",
99 &bus_error,
100 "i",
101 val);
102 if (rc < 0)
103 fprintf(stderr,
104 "fanctl: Failed to set sensor %s:[%s]\n",
105 obj_path, strerror(-rc));
106
107 finish:
108 sd_bus_error_free(&bus_error);
109 sd_bus_message_unref(response);
110 sd_bus_flush(bus);
111
112 return rc;
113 }
114
115 /* Read sensor value from "xyz.openbmc_projects.Sensors" */
read_dbus_sensor(sd_bus * bus,const char * obj_path,const char * property)116 int read_dbus_sensor(sd_bus *bus, const char *obj_path, const char *property)
117 {
118 char connection[DBUS_MAX_NAME_LEN];
119 sd_bus_error bus_error = SD_BUS_ERROR_NULL;
120 sd_bus_message *response = NULL;
121 int rc;
122 int val = 0;
123
124 if (!bus || !obj_path)
125 return 0;
126
127 rc = get_connection(bus, connection, obj_path);
128 if (rc < 0) {
129 val = 0;
130 fprintf(stderr,
131 "fanctl: Failed to get bus name for %s\n", obj_path);
132 goto finish;
133 }
134
135 rc = sd_bus_get_property(bus,
136 connection,
137 obj_path,
138 "xyz.openbmc_project.Sensor.Value",
139 property,
140 &bus_error,
141 &response,
142 "i");
143 if (rc < 0) {
144 val = 0;
145 fprintf(stderr,
146 "fanctl: Failed to read sensor value from %s:[%s]\n",
147 obj_path, strerror(-rc));
148 goto finish;
149 }
150
151 rc = sd_bus_message_read(response, "i", &val);
152 if (rc < 0) {
153 val = 0;
154 fprintf(stderr,
155 "fanctl: Failed to parse sensor value "
156 "response message from %s:[%s]\n",
157 obj_path, strerror(-rc));
158 }
159
160 finish:
161 sd_bus_error_free(&bus_error);
162 sd_bus_message_unref(response);
163 sd_bus_flush(bus);
164
165 return val;
166 }
167
168 /* set fan speed with /xyz/openbmc_project/sensors/fan_tach/fan* object */
fan_set_speed(sd_bus * bus,int fan_id,uint8_t fan_speed)169 static int fan_set_speed(sd_bus *bus, int fan_id, uint8_t fan_speed)
170 {
171 char obj_path[DBUS_MAX_NAME_LEN];
172 int rc;
173
174 if (!bus)
175 return -1;
176
177 snprintf(obj_path, sizeof(obj_path),
178 "/xyz/openbmc_project/sensors/fan_tach/fan%d_0", fan_id);
179 rc = set_dbus_sensor(bus, obj_path, fan_speed);
180 if (rc < 0)
181 fprintf(stderr, "fanctl: Failed to set fan[%d] speed[%d]\n",
182 fan_id, fan_speed);
183
184 return rc;
185 }
186
fan_set_max_speed(fan_info_t * info)187 static int fan_set_max_speed(fan_info_t *info)
188 {
189 int i;
190 int rc = -1;
191
192 if (!info)
193 return -1;
194 for (i = 0; i < info->fan_num; i++) {
195 rc = fan_set_speed(info->bus, i, 255);
196 if (rc < 0)
197 break;
198 fprintf(stderr, "fanctl: Set fan%d to max speed\n", i);
199 }
200
201 return rc;
202 }
203 /*
204 * FAN_TACH_OFFSET is specific to Barreleye.
205 * Barreleye uses NTC7904D HW Monitor as Fan tachometoer.
206 * The 13-bit FANIN value is made up Higher part: [12:5],
207 * and Lower part: [4:0], which are read from two sensors.
208 * see: https://www.nuvoton.com/resource-files/NCT7904D_Datasheet_V1.44.pdf
209 */
210 #define FAN_TACH_OFFSET 5
fan_get_speed(sd_bus * bus,int fan_id)211 static int fan_get_speed(sd_bus *bus, int fan_id)
212 {
213 int fan_tach_H = 0, fan_tach_L = 0;
214 char obj_path[DBUS_MAX_NAME_LEN];
215 int fan_speed;
216
217 /* get fan tach */
218 snprintf(obj_path, sizeof(obj_path),
219 "/xyz/openbmc_project/sensors/fan_tach/fan%d_0", fan_id);
220 fan_tach_H = read_dbus_sensor(bus, obj_path, "MaxValue");
221 fan_tach_L = read_dbus_sensor(bus, obj_path, "MinValue");
222
223 /* invalid sensor value is -1 */
224 if (fan_tach_H <= 0 || fan_tach_L <= 0)
225 fan_speed = 0;
226 else
227 fan_speed = fan_tach_H << FAN_TACH_OFFSET | fan_tach_L;
228
229 fprintf(stderr, "fan%d speed: %d\n", fan_id, fan_speed);
230 return fan_speed;
231 }
232
233 /* set Fan Inventory 'Present' status */
fan_set_present(sd_bus * bus,int fan_id,int val)234 int fan_set_present(sd_bus *bus, int fan_id, int val)
235 {
236 sd_bus_error bus_error = SD_BUS_ERROR_NULL;
237 sd_bus_message *response = NULL;
238 int rc;
239 char obj_path[DBUS_MAX_NAME_LEN];
240 char connection[DBUS_MAX_NAME_LEN];
241
242 snprintf(obj_path, sizeof(obj_path),
243 "/xyz/openbmc_project/inventory/system/chassis/motherboard/fan%d",
244 fan_id);
245
246 rc = get_connection(bus, connection, obj_path);
247 if (rc < 0) {
248 fprintf(stderr,
249 "fanctl: Failed to get bus name for %s\n", obj_path);
250 goto finish;
251 }
252
253 rc = sd_bus_set_property(bus,
254 connection,
255 obj_path,
256 "xyz.openbmc_project.Inventory.Item",
257 "Present",
258 &bus_error,
259 "b",
260 val);
261 if(rc < 0)
262 fprintf(stderr,
263 "fanctl: Failed to update fan presence via dbus: %s\n",
264 bus_error.message);
265
266 fprintf(stderr, "fanctl: Set fan%d present status to: %s\n",
267 fan_id, (val == 1 ? "True" : "False"));
268
269 finish:
270 sd_bus_error_free(&bus_error);
271 sd_bus_message_unref(response);
272 sd_bus_flush(bus);
273
274 return rc;
275 }
276
277 /*
278 * Update Fan Invertory 'Present' status by first reading fan speed.
279 * If fan speed is '0', the fan is considerred not 'Present'.
280 */
fan_update_present(fan_info_t * info)281 static int fan_update_present(fan_info_t *info)
282 {
283 int i;
284 int rc = -1;
285 int fan_speed;
286
287 if (!info)
288 return -1;
289
290 for (i = 0; i < info->fan_num; i++) {
291 fan_speed = fan_get_speed(info->bus, i);
292 if (fan_speed > 0)
293 rc = fan_set_present(info->bus, i, 1);
294 else
295 rc = fan_set_present(info->bus, i, 0);
296
297 if (rc < 0) {
298 fprintf(stderr,
299 "fanctl: Failed to set fan present status\n");
300 break;
301 }
302 }
303
304 return rc;
305 }
306 /*
307 * Router function for any FAN operations that come via dbus
308 */
fan_function_router(sd_bus_message * msg,void * user_data,sd_bus_error * ret_error)309 static int fan_function_router(sd_bus_message *msg, void *user_data,
310 sd_bus_error *ret_error)
311 {
312 /* Generic error reporter. */
313 int rc = -1;
314 fan_info_t *info = user_data;
315
316 /* Get the Operation. */
317 const char *fan_function = sd_bus_message_get_member(msg);
318 if (fan_function == NULL) {
319 fprintf(stderr, "fanctl: Null FAN function specified\n");
320 return sd_bus_reply_method_return(msg, "i", rc);
321 }
322
323 /* Route the user action to appropriate handlers. */
324 if ((strcmp(fan_function, "setMax") == 0)) {
325 rc = fan_set_max_speed(info);
326 return sd_bus_reply_method_return(msg, "i", rc);
327 }
328 if ((strcmp(fan_function, "updatePresent") == 0)) {
329 rc = fan_update_present(info);
330 return sd_bus_reply_method_return(msg, "i", rc);
331 }
332
333 return sd_bus_reply_method_return(msg, "i", rc);
334 }
335
336 /* Dbus Services offered by this FAN controller */
337 static const sd_bus_vtable fan_control_vtable[] =
338 {
339 SD_BUS_VTABLE_START(0),
340 SD_BUS_METHOD("setMax", "", "i", &fan_function_router,
341 SD_BUS_VTABLE_UNPRIVILEGED),
342 SD_BUS_METHOD("updatePresent", "", "i", &fan_function_router,
343 SD_BUS_VTABLE_UNPRIVILEGED),
344 SD_BUS_VTABLE_END,
345 };
346
start_fan_services(fan_info_t * info)347 int start_fan_services(fan_info_t *info)
348 {
349 /* Generic error reporter. */
350 int rc = -1;
351 /* slot where we are offering the FAN dbus service. */
352 sd_bus_slot *fan_slot = NULL;
353 const char *fan_object = "/org/openbmc/control/fans";
354
355 info->bus = NULL;
356 /* Get a hook onto system bus. */
357 rc = sd_bus_open_system(&info->bus);
358 if (rc < 0) {
359 fprintf(stderr,"fanctl: Error opening system bus.\n");
360 return rc;
361 }
362
363 /* Install the object */
364 rc = sd_bus_add_object_vtable(info->bus,
365 &fan_slot,
366 fan_object, /* object path */
367 "org.openbmc.control.Fans", /* interface name */
368 fan_control_vtable,
369 info);
370 if (rc < 0) {
371 fprintf(stderr, "fanctl: Failed to add object to dbus: %s\n",
372 strerror(-rc));
373 return rc;
374 }
375
376 /* If we had success in adding the providers, request for a bus name. */
377 rc = sd_bus_request_name(info->bus,
378 "org.openbmc.control.Fans", 0);
379 if (rc < 0) {
380 fprintf(stderr, "fanctl: Failed to acquire service name: %s\n",
381 strerror(-rc));
382 return rc;
383 }
384
385 for (;;) {
386 /* Process requests */
387 rc = sd_bus_process(info->bus, NULL);
388 if (rc < 0) {
389 fprintf(stderr, "fanctl: Failed to process bus: %s\n",
390 strerror(-rc));
391 break;
392 }
393 if (rc > 0) {
394 continue;
395 }
396
397 rc = sd_bus_wait(info->bus, (uint64_t) - 1);
398 if (rc < 0) {
399 fprintf(stderr, "fanctl: Failed to wait on bus: %s\n",
400 strerror(-rc));
401 break;
402 }
403 }
404
405 sd_bus_slot_unref(fan_slot);
406 sd_bus_unref(info->bus);
407
408 return rc;
409 }
410
str_to_int(char * str)411 static int str_to_int(char *str)
412 {
413 long val;
414 char *temp;
415
416 val = strtol(str, &temp, 10);
417 if (temp == str || *temp != '\0' ||
418 ((val == LONG_MIN || val == LONG_MAX) && errno == ERANGE))
419 return -1;
420 if (val < 0)
421 return -1;
422
423 return (int)val;
424 }
425
parse_argument(int argc,char ** argv,fan_info_t * info)426 static int parse_argument(int argc, char **argv, fan_info_t *info)
427 {
428 int c;
429 struct option long_options[] =
430 {
431 {"fan_num", required_argument, 0, 'f'},
432 {"core_num", required_argument, 0, 'c'},
433 {"cpu_num", required_argument, 0, 'p'},
434 {"dimm_num", required_argument, 0, 'd'},
435 {0, 0, 0, 0}
436 };
437
438 while (1) {
439 c = getopt_long (argc, argv, "c:d:f:p:", long_options, NULL);
440
441 /* Detect the end of the options. */
442 if (c == -1)
443 break;
444
445 switch (c) {
446 case 'f':
447 info->fan_num = str_to_int(optarg);
448 if (info->fan_num == -1) {
449 fprintf(stderr, "fanctl: Wrong fan_num: %s\n", optarg);
450 return -1;
451 }
452 break;
453 case 'c':
454 info->core_num = str_to_int(optarg);
455 if (info->core_num == -1) {
456 fprintf(stderr, "fanctl: Wrong core_num: %s\n", optarg);
457 return -1;
458 }
459 break;
460 case 'p':
461 info->cpu_num = str_to_int(optarg);
462 if (info->cpu_num == -1) {
463 fprintf(stderr, "fanctl: Wrong cpu_num: %s\n", optarg);
464 return -1;
465 }
466 break;
467 case 'd':
468 info->dimm_num = str_to_int(optarg);
469 if (info->dimm_num == -1) {
470 fprintf(stderr, "fanctl: Wrong dimm_num: %s\n", optarg);
471 return -1;
472 }
473 break;
474 default:
475 fprintf(stderr, "fanctl: Wrong argument\n");
476 return -1;
477 }
478 }
479
480 return 0;
481 }
482
main(int argc,char ** argv)483 int main(int argc, char **argv)
484 {
485 int rc = 0;
486 fan_info_t fan_info;
487
488 memset(&fan_info, 0, sizeof(fan_info));
489 rc = parse_argument(argc, argv, &fan_info);
490 if (rc < 0) {
491 fprintf(stderr, "fanctl: Error parse argument\n");
492 return rc;
493 }
494 /* This call is not supposed to return. If it does, then an error */
495 rc = start_fan_services(&fan_info);
496 if (rc < 0) {
497 fprintf(stderr, "fanctl: Error starting FAN Services. Exiting");
498 }
499
500 return rc;
501 }
502