xref: /openbmc/skeleton/fanctl/fan_control.c (revision 6d2f6c3d)
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 =  "org.openbmc.ObjectMapper";
27 const char *objectmapper_object_name  =  "/org/openbmc/ObjectMapper";
28 const char *objectmapper_intf_name    =  "org.openbmc.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 */
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 				"s",
56 				obj_path);
57 	if (rc < 0) {
58 		fprintf(stderr,
59 			"Failed to GetObject: %s\n", bus_error.message);
60 		goto finish;
61 	}
62 
63 	/* Get the key, aka, the bus name */
64 	sd_bus_message_read(m, "a{sas}", 1, &temp_buf, 1, &intf);
65 	strncpy(connection, temp_buf, DBUS_MAX_NAME_LEN);
66 
67 finish:
68 	sd_bus_error_free(&bus_error);
69 	sd_bus_message_unref(m);
70 	sd_bus_flush(bus);
71 
72 	return rc;
73 }
74 
75 
76 int set_dbus_sensor(sd_bus *bus, const char *obj_path, int val)
77 {
78 	char connection[DBUS_MAX_NAME_LEN];
79 	sd_bus_error bus_error = SD_BUS_ERROR_NULL;
80 	sd_bus_message *response = NULL;
81 	int rc;
82 
83 	if (!bus || !obj_path)
84 		return -1;
85 
86 	rc = get_connection(bus, connection, obj_path);
87 	if (rc < 0) {
88 		fprintf(stderr,
89 			"fanctl: Failed to get bus name for %s\n", obj_path);
90 		goto finish;
91 	}
92 
93 	rc = sd_bus_call_method(bus,
94 				connection,
95 				obj_path,
96 				"org.openbmc.SensorValue",
97 				"setValue",
98 				&bus_error,
99 				&response,
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 "org.openbmc.Sensors" */
116 int read_dbus_sensor(sd_bus *bus, const char *obj_path)
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_call_method(bus,
136 				connection,
137 				obj_path,
138 				"org.openbmc.SensorValue",
139 				"getValue",
140 				&bus_error,
141 				&response,
142 				NULL);
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, "v","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 /org/openbmc/sensors/speed/fan* object */
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 		"/org/openbmc/sensors/speed/fan%d", 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 
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 Higer 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
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 	/* The object path is specific to Barreleye */
219 	snprintf(obj_path, sizeof(obj_path),
220 		"/org/openbmc/sensors/tach/fan%dH", fan_id);
221 	fan_tach_H = read_dbus_sensor(bus, obj_path);
222 	snprintf(obj_path, sizeof(obj_path),
223 		"/org/openbmc/sensors/tach/fan%dL", fan_id);
224 	fan_tach_L = read_dbus_sensor(bus, obj_path);
225 
226 	/* invalid sensor value is -1 */
227 	if (fan_tach_H <= 0 || fan_tach_L <= 0)
228 		fan_speed = 0;
229 	else
230 		fan_speed = fan_tach_H << FAN_TACH_OFFSET | fan_tach_L;
231 
232 	fprintf(stderr, "fan%d speed: %d\n", fan_id, fan_speed);
233 	return fan_speed;
234 }
235 
236 /* set Fan Inventory 'Present' status */
237 int fan_set_present(sd_bus *bus, int fan_id, int val)
238 {
239 	sd_bus_error bus_error = SD_BUS_ERROR_NULL;
240 	sd_bus_message *response = NULL;
241 	int rc;
242 	char obj_path[DBUS_MAX_NAME_LEN];
243 	char connection[DBUS_MAX_NAME_LEN];
244 
245 	snprintf(obj_path, sizeof(obj_path),
246 		"/org/openbmc/inventory/system/chassis/fan%d", fan_id);
247 
248 	rc = get_connection(bus, connection, obj_path);
249 	if (rc < 0) {
250 		fprintf(stderr,
251 			"fanctl: Failed to get bus name for %s\n", obj_path);
252 		goto finish;
253 	}
254 
255 	rc = sd_bus_call_method(bus,
256 				connection,
257 				obj_path,
258 				"org.openbmc.InventoryItem",
259 				"setPresent",
260 				&bus_error,
261 				&response,
262 				"s",
263 				(val == 1 ? "True" : "False"));
264 	if(rc < 0)
265 		fprintf(stderr,
266 			"fanctl: Failed to update fan presence via dbus: %s\n",
267 			bus_error.message);
268 
269 	fprintf(stderr, "fanctl: Set fan%d present status to: %s\n",
270 			fan_id, (val == 1 ? "True" : "False"));
271 
272 finish:
273 	sd_bus_error_free(&bus_error);
274 	sd_bus_message_unref(response);
275 	sd_bus_flush(bus);
276 
277 	return rc;
278 }
279 
280 /*
281  * Update Fan Invertory 'Present' status by first reading fan speed.
282  * If fan speed is '0', the fan is considerred not 'Present'.
283  */
284 static int fan_update_present(fan_info_t *info)
285 {
286 	int i;
287 	int rc = -1;
288 	int fan_speed;
289 
290 	if (!info)
291 		return -1;
292 
293 	for (i = 0; i < info->fan_num; i++) {
294 		fan_speed = fan_get_speed(info->bus, i);
295 		if (fan_speed > 0)
296 			rc = fan_set_present(info->bus, i, 1);
297 		else
298 			rc = fan_set_present(info->bus, i, 0);
299 
300 		if (rc < 0) {
301 			fprintf(stderr,
302 				"fanctl: Failed to set fan present status\n");
303 			break;
304 		}
305 	}
306 
307 	return rc;
308 }
309 /*
310  * Router function for any FAN operations that come via dbus
311  */
312 static int fan_function_router(sd_bus_message *msg, void *user_data,
313 		sd_bus_error *ret_error)
314 {
315 	/* Generic error reporter. */
316 	int rc = -1;
317 	fan_info_t *info = user_data;
318 
319 	/* Get the Operation. */
320 	const char *fan_function = sd_bus_message_get_member(msg);
321 	if (fan_function == NULL) {
322 		fprintf(stderr, "fanctl: Null FAN function specificed\n");
323 		return sd_bus_reply_method_return(msg, "i", rc);
324 	}
325 
326 	/* Route the user action to appropriate handlers. */
327 	if ((strcmp(fan_function, "setMax") == 0)) {
328 		rc = fan_set_max_speed(info);
329 		return sd_bus_reply_method_return(msg, "i", rc);
330 	}
331 	if ((strcmp(fan_function, "updatePresent") == 0)) {
332 		rc = fan_update_present(info);
333 		return sd_bus_reply_method_return(msg, "i", rc);
334 	}
335 
336 	return sd_bus_reply_method_return(msg, "i", rc);
337 }
338 
339 /* Dbus Services offered by this FAN controller */
340 static const sd_bus_vtable fan_control_vtable[] =
341 {
342 	SD_BUS_VTABLE_START(0),
343 	SD_BUS_METHOD("setMax", "", "i", &fan_function_router,
344 			SD_BUS_VTABLE_UNPRIVILEGED),
345 	SD_BUS_METHOD("updatePresent", "", "i", &fan_function_router,
346 			SD_BUS_VTABLE_UNPRIVILEGED),
347 	SD_BUS_VTABLE_END,
348 };
349 
350 int start_fan_services(fan_info_t *info)
351 {
352 	/* Generic error reporter. */
353 	int rc = -1;
354 	/* slot where we are offering the FAN dbus service. */
355 	sd_bus_slot *fan_slot = NULL;
356 	const char *fan_object = "/org/openbmc/control/fans";
357 
358 	info->bus = NULL;
359 	/* Get a hook onto system bus. */
360 	rc = sd_bus_open_system(&info->bus);
361 	if (rc < 0) {
362 		fprintf(stderr,"fanctl: Error opening system bus.\n");
363 		return rc;
364 	}
365 
366 	/* Install the object */
367 	rc = sd_bus_add_object_vtable(info->bus,
368 			&fan_slot,
369 			fan_object, /* object path */
370 			"org.openbmc.control.Fans", /* interface name */
371 			fan_control_vtable,
372 			info);
373 	if (rc < 0) {
374 		fprintf(stderr, "fanctl: Failed to add object to dbus: %s\n",
375 				strerror(-rc));
376 		return rc;
377 	}
378 
379 	/* If we had success in adding the providers, request for a bus name. */
380 	rc = sd_bus_request_name(info->bus,
381 			"org.openbmc.control.Fans", 0);
382 	if (rc < 0) {
383 		fprintf(stderr, "fanctl: Failed to acquire service name: %s\n",
384 				strerror(-rc));
385 		return rc;
386 	}
387 
388 	for (;;) {
389 		/* Process requests */
390 		rc = sd_bus_process(info->bus, NULL);
391 		if (rc < 0) {
392 			fprintf(stderr, "fanctl: Failed to process bus: %s\n",
393 					strerror(-rc));
394 			break;
395 		}
396 		if (rc > 0) {
397 			continue;
398 		}
399 
400 		rc = sd_bus_wait(info->bus, (uint64_t) - 1);
401 		if (rc < 0) {
402 			fprintf(stderr, "fanctl: Failed to wait on bus: %s\n",
403 				strerror(-rc));
404 			break;
405 		}
406 	}
407 
408 	sd_bus_slot_unref(fan_slot);
409 	sd_bus_unref(info->bus);
410 
411 	return rc;
412 }
413 
414 static int str_to_int(char *str)
415 {
416 	long val;
417 	char *temp;
418 
419 	val = strtol(str, &temp, 10);
420 	if (temp == str || *temp != '\0' ||
421 		((val == LONG_MIN || val == LONG_MAX) && errno == ERANGE))
422 		return -1;
423 	if (val < 0)
424 		return -1;
425 
426 	return (int)val;
427 }
428 
429 static int parse_argument(int argc, char **argv, fan_info_t *info)
430 {
431 	int c;
432 	struct option long_options[] =
433 	{
434 		{"fan_num",  required_argument, 0, 'f'},
435 		{"core_num",    required_argument, 0, 'c'},
436 		{"cpu_num",    required_argument, 0, 'p'},
437 		{"dimm_num",    required_argument, 0, 'd'},
438 		{0, 0, 0, 0}
439 	};
440 
441 	while (1) {
442 		c = getopt_long (argc, argv, "c:d:f:p:", long_options, NULL);
443 
444 		/* Detect the end of the options. */
445 		if (c == -1)
446 			break;
447 
448 		switch (c) {
449 		case 'f':
450 			info->fan_num = str_to_int(optarg);
451 			if (info->fan_num == -1) {
452 				fprintf(stderr, "fanctl: Wrong fan_num: %s\n", optarg);
453 				return -1;
454 			}
455 			break;
456 		case 'c':
457 			info->core_num = str_to_int(optarg);
458 			if (info->core_num == -1) {
459 				fprintf(stderr, "fanctl: Wrong core_num: %s\n", optarg);
460 				return -1;
461 			}
462 			break;
463 		case 'p':
464 			info->cpu_num = str_to_int(optarg);
465 			if (info->cpu_num == -1) {
466 				fprintf(stderr, "fanctl: Wrong cpu_num: %s\n", optarg);
467 				return -1;
468 			}
469 			break;
470 		case 'd':
471 			info->dimm_num = str_to_int(optarg);
472 			if (info->dimm_num == -1) {
473 				fprintf(stderr, "fanctl: Wrong dimm_num: %s\n", optarg);
474 				return -1;
475 			}
476 			break;
477 		default:
478 			fprintf(stderr, "fanctl: Wrong argument\n");
479 			return -1;
480 		}
481 	}
482 
483 	return 0;
484 }
485 
486 int main(int argc, char **argv)
487 {
488 	int rc = 0;
489 	fan_info_t fan_info;
490 
491 	memset(&fan_info, 0, sizeof(fan_info));
492 	rc = parse_argument(argc, argv, &fan_info);
493 	if (rc < 0) {
494 		fprintf(stderr, "fanctl: Error parse argument\n");
495 		return rc;
496 	}
497 	/* This call is not supposed to return. If it does, then an error */
498 	rc = start_fan_services(&fan_info);
499 	if (rc < 0) {
500 		fprintf(stderr, "fanctl: Error starting FAN Services. Exiting");
501 	}
502 
503 	return rc;
504 }
505