Add fanctl to skeleton

fanctl replaces pyfanctl. It provides fan control service with
dbus object "/org/openbmc/control/fans".

Currently there are two methods: setMax() to set all fans' speed to maximum,
and updatePresent() to update the "Present" status of inventory objects:
"/org/openbmc/inventory/system/chassis/fan*".

In future, more methods will be added, like setting fan speed based on
temperature sensor reading.

Fixes openbmc/openbmc#93

Change-Id: Ic65089f5d0538cd5e17cfcd9f27e652ae6080ec5
Signed-off-by: Yi Li <adamliyi@msn.com>
diff --git a/Makefile b/Makefile
index b79675d..8190633 100644
--- a/Makefile
+++ b/Makefile
@@ -9,12 +9,12 @@
 	     pwrbutton \
 	     rstbutton
 
-SUBDIRS = hacks \
+SUBDIRS = fanctl \
+	  hacks \
 	  ledctl \
 	  libopenbmc_intf \
 	  pychassisctl \
 	  pydownloadmgr \
-	  pyfanctl \
 	  pyflashbmc \
 	  pyhwmon \
 	  pyinventorymgr \
diff --git a/configs/Barreleye.py b/configs/Barreleye.py
index 4b6a15c..cbcd2d8 100644
--- a/configs/Barreleye.py
+++ b/configs/Barreleye.py
@@ -374,6 +374,12 @@
 			'pwm1' : { 'object_path' : 'speed/fan0','poll_interval' : 10000,'scale' : 1,'units' : '' },
 			'pwm2' : { 'object_path' : 'speed/fan1','poll_interval' : 10000,'scale' : 1,'units' : '' },
                         'pwm3' : { 'object_path' : 'speed/fan2','poll_interval' : 10000,'scale' : 1,'units' : '' },
+                        'fan1_input' : { 'object_path' : 'tach/fan5L','poll_interval' : 10000,'scale' : 1,'units' : '' },
+                        'fan2_input' : { 'object_path' : 'tach/fan5H','poll_interval' : 10000,'scale' : 1,'units' : '' },
+                        'fan3_input' : { 'object_path' : 'tach/fan4L','poll_interval' : 10000,'scale' : 1,'units' : '' },
+                        'fan4_input' : { 'object_path' : 'tach/fan4H','poll_interval' : 10000,'scale' : 1,'units' : '' },
+                        'fan5_input' : { 'object_path' : 'tach/fan3L','poll_interval' : 10000,'scale' : 1,'units' : '' },
+                        'fan6_input' : { 'object_path' : 'tach/fan3H','poll_interval' : 10000,'scale' : 1,'units' : '' },
                         'in1_input' : { 'object_path' : 'voltage/P1V35_CPU0_BUF4','poll_interval' : 10000,'scale' : 1,'units' : '' },
                         'in2_input' : { 'object_path' : 'voltage/P0V9_CPU0_BUF1','poll_interval' : 10000,'scale' : 1,'units' : '' },
                         'in3_input' : { 'object_path' : 'voltage/P0V9_CPU0_BUF2','poll_interval' : 10000,'scale' : 1,'units' : '' },
@@ -395,6 +401,12 @@
 			'pwm1' : { 'object_path' : 'speed/fan3','poll_interval' : 10000,'scale' : 1,'units' : '' },
 			'pwm2' : { 'object_path' : 'speed/fan4','poll_interval' : 10000,'scale' : 1,'units' : '' },
 			'pwm3' : { 'object_path' : 'speed/fan5','poll_interval' : 10000,'scale' : 1,'units' : '' },
+                        'fan1_input' : { 'object_path' : 'tach/fan2L','poll_interval' : 10000,'scale' : 1,'units' : '' },
+                        'fan2_input' : { 'object_path' : 'tach/fan2H','poll_interval' : 10000,'scale' : 1,'units' : '' },
+                        'fan3_input' : { 'object_path' : 'tach/fan1L','poll_interval' : 10000,'scale' : 1,'units' : '' },
+                        'fan4_input' : { 'object_path' : 'tach/fan1H','poll_interval' : 10000,'scale' : 1,'units' : '' },
+                        'fan5_input' : { 'object_path' : 'tach/fan0L','poll_interval' : 10000,'scale' : 1,'units' : '' },
+                        'fan6_input' : { 'object_path' : 'tach/fan0H','poll_interval' : 10000,'scale' : 1,'units' : '' },
    			'in1_input' : { 'object_path' : 'voltage/P1V35_CPU1_BUF4','poll_interval' : 10000,'scale' : 1,'units' : '' },
                         'in2_input' : { 'object_path' : 'voltage/P0V9_CPU1_BUF1','poll_interval' : 10000,'scale' : 1,'units' : '' },
                         'in3_input' : { 'object_path' : 'voltage/P0V9_CPU1_BUF2','poll_interval' : 10000,'scale' : 1,'units' : '' },
diff --git a/fanctl/Makefile b/fanctl/Makefile
new file mode 100644
index 0000000..a02b1a6
--- /dev/null
+++ b/fanctl/Makefile
@@ -0,0 +1,3 @@
+BINS=fan_control
+include ../sdbus.mk
+include ../rules.mk
diff --git a/fanctl/fan_control.c b/fanctl/fan_control.c
new file mode 100644
index 0000000..54b96b1
--- /dev/null
+++ b/fanctl/fan_control.c
@@ -0,0 +1,504 @@
+/**
+ * Copyright © 2016 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <errno.h>
+#include <string.h>
+#include <getopt.h>
+#include <systemd/sd-bus.h>
+
+#define DBUS_MAX_NAME_LEN 256
+
+const char *objectmapper_service_name =  "org.openbmc.ObjectMapper";
+const char *objectmapper_object_name  =  "/org/openbmc/ObjectMapper";
+const char *objectmapper_intf_name    =  "org.openbmc.ObjectMapper";
+
+typedef struct {
+	int fan_num;
+	int cpu_num;
+	int core_num;
+	int dimm_num;
+	sd_bus *bus;
+	char sensor_service[DBUS_MAX_NAME_LEN];
+	char inventory_service[DBUS_MAX_NAME_LEN];
+} fan_info_t;
+
+/* Get an object's bus name from ObjectMapper */
+int get_connection(sd_bus *bus, char *connection, const char *obj_path)
+{
+	sd_bus_error bus_error = SD_BUS_ERROR_NULL;
+	sd_bus_message *m = NULL;
+	char *temp_buf = NULL, *intf = NULL;
+	int rc;
+
+	rc = sd_bus_call_method(bus,
+				objectmapper_service_name,
+				objectmapper_object_name,
+				objectmapper_intf_name,
+				"GetObject",
+				&bus_error,
+				&m,
+				"s",
+				obj_path);
+	if (rc < 0) {
+		fprintf(stderr,
+			"Failed to GetObject: %s\n", bus_error.message);
+		goto finish;
+	}
+
+	/* Get the key, aka, the bus name */
+	sd_bus_message_read(m, "a{sas}", 1, &temp_buf, 1, &intf);
+	strncpy(connection, temp_buf, DBUS_MAX_NAME_LEN);
+
+finish:
+	sd_bus_error_free(&bus_error);
+	sd_bus_message_unref(m);
+	sd_bus_flush(bus);
+
+	return rc;
+}
+
+
+int set_dbus_sensor(sd_bus *bus, const char *obj_path, int val)
+{
+	char connection[DBUS_MAX_NAME_LEN];
+	sd_bus_error bus_error = SD_BUS_ERROR_NULL;
+	sd_bus_message *response = NULL;
+	int rc;
+
+	if (!bus || !obj_path)
+		return -1;
+
+	rc = get_connection(bus, connection, obj_path);
+	if (rc < 0) {
+		fprintf(stderr,
+			"fanctl: Failed to get bus name for %s\n", obj_path);
+		goto finish;
+	}
+
+	rc = sd_bus_call_method(bus,
+				connection,
+				obj_path,
+				"org.openbmc.SensorValue",
+				"setValue",
+				&bus_error,
+				&response,
+				"i",
+				val);
+	if (rc < 0)
+		fprintf(stderr,
+			"fanctl: Failed to set sensor %s:[%s]\n",
+			obj_path, strerror(-rc));
+
+finish:
+	sd_bus_error_free(&bus_error);
+	sd_bus_message_unref(response);
+	sd_bus_flush(bus);
+
+	return rc;
+}
+
+/* Read sensor value from "org.openbmc.Sensors" */
+int read_dbus_sensor(sd_bus *bus, const char *obj_path)
+{
+	char connection[DBUS_MAX_NAME_LEN];
+	sd_bus_error bus_error = SD_BUS_ERROR_NULL;
+	sd_bus_message *response = NULL;
+	int rc;
+	int val = 0;
+
+	if (!bus || !obj_path)
+		return 0;
+
+	rc = get_connection(bus, connection, obj_path);
+	if (rc < 0) {
+		val = 0;
+		fprintf(stderr,
+			"fanctl: Failed to get bus name for %s\n", obj_path);
+		goto finish;
+	}
+
+	rc = sd_bus_call_method(bus,
+				connection,
+				obj_path,
+				"org.openbmc.SensorValue",
+				"getValue",
+				&bus_error,
+				&response,
+				NULL);
+	if (rc < 0) {
+		val = 0;
+		fprintf(stderr,
+			"fanctl: Failed to read sensor value from %s:[%s]\n",
+			obj_path, strerror(-rc));
+		goto finish;
+	}
+
+	rc = sd_bus_message_read(response, "v","i", &val);
+	if (rc < 0) {
+		val = 0;
+		fprintf(stderr,
+			"fanctl: Failed to parse sensor value "
+			"response message from %s:[%s]\n",
+			obj_path, strerror(-rc));
+	}
+
+finish:
+	sd_bus_error_free(&bus_error);
+	sd_bus_message_unref(response);
+	sd_bus_flush(bus);
+
+	return val;
+}
+
+/* set fan speed with /org/openbmc/sensors/speed/fan* object */
+static int fan_set_speed(sd_bus *bus, int fan_id, uint8_t fan_speed)
+{
+	char obj_path[DBUS_MAX_NAME_LEN];
+	int rc;
+
+	if (!bus)
+		return -1;
+
+	snprintf(obj_path, sizeof(obj_path),
+		"/org/openbmc/sensors/speed/fan%d", fan_id);
+	rc = set_dbus_sensor(bus, obj_path, fan_speed);
+	if (rc < 0)
+		fprintf(stderr, "fanctl: Failed to set fan[%d] speed[%d]\n",
+				fan_id, fan_speed);
+
+	return rc;
+}
+
+static int fan_set_max_speed(fan_info_t *info)
+{
+	int i;
+	int rc = -1;
+
+	if (!info)
+		return -1;
+	for (i = 0; i < info->fan_num; i++) {
+		rc = fan_set_speed(info->bus, i, 255);
+		if (rc < 0)
+			break;
+		fprintf(stderr, "fanctl: Set fan%d to max speed\n", i);
+	}
+
+	return rc;
+}
+/*
+ * FAN_TACH_OFFSET is specific to Barreleye.
+ * Barreleye uses NTC7904D HW Monitor as Fan tachometoer.
+ * The 13-bit FANIN value is made up Higer part: [12:5],
+ * and Lower part: [4:0], which are read from two sensors.
+ * see: https://www.nuvoton.com/resource-files/NCT7904D_Datasheet_V1.44.pdf
+ */
+#define FAN_TACH_OFFSET 5
+static int fan_get_speed(sd_bus *bus, int fan_id)
+{
+	int fan_tach_H = 0, fan_tach_L = 0;
+	char obj_path[DBUS_MAX_NAME_LEN];
+	int fan_speed;
+
+	/* get fan tach */
+	/* The object path is specific to Barreleye */
+	snprintf(obj_path, sizeof(obj_path),
+		"/org/openbmc/sensors/tach/fan%dH", fan_id);
+	fan_tach_H = read_dbus_sensor(bus, obj_path);
+	snprintf(obj_path, sizeof(obj_path),
+		"/org/openbmc/sensors/tach/fan%dL", fan_id);
+	fan_tach_L = read_dbus_sensor(bus, obj_path);
+
+	/* invalid sensor value is -1 */
+	if (fan_tach_H <= 0 || fan_tach_L <= 0)
+		fan_speed = 0;
+	else
+		fan_speed = fan_tach_H << FAN_TACH_OFFSET | fan_tach_L;
+
+	fprintf(stderr, "fan%d speed: %d\n", fan_id, fan_speed);
+	return fan_speed;
+}
+
+/* set Fan Inventory 'Present' status */
+int fan_set_present(sd_bus *bus, int fan_id, int val)
+{
+	sd_bus_error bus_error = SD_BUS_ERROR_NULL;
+	sd_bus_message *response = NULL;
+	int rc;
+	char obj_path[DBUS_MAX_NAME_LEN];
+	char connection[DBUS_MAX_NAME_LEN];
+
+	snprintf(obj_path, sizeof(obj_path),
+		"/org/openbmc/inventory/system/chassis/fan%d", fan_id);
+
+	rc = get_connection(bus, connection, obj_path);
+	if (rc < 0) {
+		fprintf(stderr,
+			"fanctl: Failed to get bus name for %s\n", obj_path);
+		goto finish;
+	}
+
+	rc = sd_bus_call_method(bus,
+				connection,
+				obj_path,
+				"org.openbmc.InventoryItem",
+				"setPresent",
+				&bus_error,
+				&response,
+				"s",
+				(val == 1 ? "True" : "False"));
+	if(rc < 0)
+		fprintf(stderr,
+			"fanctl: Failed to update fan presence via dbus: %s\n",
+			bus_error.message);
+
+	fprintf(stderr, "fanctl: Set fan%d present status to: %s\n",
+			fan_id, (val == 1 ? "True" : "False"));
+
+finish:
+	sd_bus_error_free(&bus_error);
+	sd_bus_message_unref(response);
+	sd_bus_flush(bus);
+
+	return rc;
+}
+
+/*
+ * Update Fan Invertory 'Present' status by first reading fan speed.
+ * If fan speed is '0', the fan is considerred not 'Present'.
+ */
+static int fan_update_present(fan_info_t *info)
+{
+	int i;
+	int rc = -1;
+	int fan_speed;
+
+	if (!info)
+		return -1;
+
+	for (i = 0; i < info->fan_num; i++) {
+		fan_speed = fan_get_speed(info->bus, i);
+		if (fan_speed > 0)
+			rc = fan_set_present(info->bus, i, 1);
+		else
+			rc = fan_set_present(info->bus, i, 0);
+
+		if (rc < 0) {
+			fprintf(stderr,
+				"fanctl: Failed to set fan present status\n");
+			break;
+		}
+	}
+
+	return rc;
+}
+/*
+ * Router function for any FAN operations that come via dbus
+ */
+static int fan_function_router(sd_bus_message *msg, void *user_data,
+		sd_bus_error *ret_error)
+{
+	/* Generic error reporter. */
+	int rc = -1;
+	fan_info_t *info = user_data;
+
+	/* Get the Operation. */
+	const char *fan_function = sd_bus_message_get_member(msg);
+	if (fan_function == NULL) {
+		fprintf(stderr, "fanctl: Null FAN function specificed\n");
+		return sd_bus_reply_method_return(msg, "i", rc);
+	}
+
+	/* Route the user action to appropriate handlers. */
+	if ((strcmp(fan_function, "setMax") == 0)) {
+		rc = fan_set_max_speed(info);
+		return sd_bus_reply_method_return(msg, "i", rc);
+	}
+	if ((strcmp(fan_function, "updatePresent") == 0)) {
+		rc = fan_update_present(info);
+		return sd_bus_reply_method_return(msg, "i", rc);
+	}
+
+	return sd_bus_reply_method_return(msg, "i", rc);
+}
+
+/* Dbus Services offered by this FAN controller */
+static const sd_bus_vtable fan_control_vtable[] =
+{
+	SD_BUS_VTABLE_START(0),
+	SD_BUS_METHOD("setMax", "", "i", &fan_function_router,
+			SD_BUS_VTABLE_UNPRIVILEGED),
+	SD_BUS_METHOD("updatePresent", "", "i", &fan_function_router,
+			SD_BUS_VTABLE_UNPRIVILEGED),
+	SD_BUS_VTABLE_END,
+};
+
+int start_fan_services(fan_info_t *info)
+{
+	/* Generic error reporter. */
+	int rc = -1;
+	/* slot where we are offering the FAN dbus service. */
+	sd_bus_slot *fan_slot = NULL;
+	const char *fan_object = "/org/openbmc/control/fans";
+
+	info->bus = NULL;
+	/* Get a hook onto system bus. */
+	rc = sd_bus_open_system(&info->bus);
+	if (rc < 0) {
+		fprintf(stderr,"fanctl: Error opening system bus.\n");
+		return rc;
+	}
+
+	/* Install the object */
+	rc = sd_bus_add_object_vtable(info->bus,
+			&fan_slot,
+			fan_object, /* object path */
+			"org.openbmc.control.Fans", /* interface name */
+			fan_control_vtable,
+			info);
+	if (rc < 0) {
+		fprintf(stderr, "fanctl: Failed to add object to dbus: %s\n",
+				strerror(-rc));
+		return rc;
+	}
+
+	/* If we had success in adding the providers, request for a bus name. */
+	rc = sd_bus_request_name(info->bus,
+			"org.openbmc.control.Fans", 0);
+	if (rc < 0) {
+		fprintf(stderr, "fanctl: Failed to acquire service name: %s\n",
+				strerror(-rc));
+		return rc;
+	}
+
+	for (;;) {
+		/* Process requests */
+		rc = sd_bus_process(info->bus, NULL);
+		if (rc < 0) {
+			fprintf(stderr, "fanctl: Failed to process bus: %s\n",
+					strerror(-rc));
+			break;
+		}
+		if (rc > 0) {
+			continue;
+		}
+
+		rc = sd_bus_wait(info->bus, (uint64_t) - 1);
+		if (rc < 0) {
+			fprintf(stderr, "fanctl: Failed to wait on bus: %s\n",
+				strerror(-rc));
+			break;
+		}
+	}
+
+	sd_bus_slot_unref(fan_slot);
+	sd_bus_unref(info->bus);
+
+	return rc;
+}
+
+static int str_to_int(char *str)
+{
+	long val;
+	char *temp;
+
+	val = strtol(str, &temp, 10);
+	if (temp == str || *temp != '\0' ||
+		((val == LONG_MIN || val == LONG_MAX) && errno == ERANGE))
+		return -1;
+	if (val < 0)
+		return -1;
+
+	return (int)val;
+}
+
+static int parse_argument(int argc, char **argv, fan_info_t *info)
+{
+	int c;
+	struct option long_options[] =
+	{
+		{"fan_num",  required_argument, 0, 'f'},
+		{"core_num",    required_argument, 0, 'c'},
+		{"cpu_num",    required_argument, 0, 'p'},
+		{"dimm_num",    required_argument, 0, 'd'},
+		{0, 0, 0, 0}
+	};
+
+	while (1) {
+		c = getopt_long (argc, argv, "c:d:f:p:", long_options, NULL);
+
+		/* Detect the end of the options. */
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case 'f':
+			info->fan_num = str_to_int(optarg);
+			if (info->fan_num == -1) {
+				fprintf(stderr, "fanctl: Wrong fan_num: %s\n", optarg);
+				return -1;
+			}
+			break;
+		case 'c':
+			info->core_num = str_to_int(optarg);
+			if (info->core_num == -1) {
+				fprintf(stderr, "fanctl: Wrong core_num: %s\n", optarg);
+				return -1;
+			}
+			break;
+		case 'p':
+			info->cpu_num = str_to_int(optarg);
+			if (info->cpu_num == -1) {
+				fprintf(stderr, "fanctl: Wrong cpu_num: %s\n", optarg);
+				return -1;
+			}
+			break;
+		case 'd':
+			info->dimm_num = str_to_int(optarg);
+			if (info->dimm_num == -1) {
+				fprintf(stderr, "fanctl: Wrong dimm_num: %s\n", optarg);
+				return -1;
+			}
+			break;
+		default:
+			fprintf(stderr, "fanctl: Wrong argument\n");
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+int main(int argc, char **argv)
+{
+	int rc = 0;
+	fan_info_t fan_info;
+
+	memset(&fan_info, 0, sizeof(fan_info));
+	rc = parse_argument(argc, argv, &fan_info);
+	if (rc < 0) {
+		fprintf(stderr, "fanctl: Error parse argument\n");
+		return rc;
+	}
+	/* This call is not supposed to return. If it does, then an error */
+	rc = start_fan_services(&fan_info);
+	if (rc < 0) {
+		fprintf(stderr, "fanctl: Error starting FAN Services. Exiting");
+	}
+
+	return rc;
+}
diff --git a/pyfanctl/Makefile b/pyfanctl/Makefile
deleted file mode 120000
index 76a90fc..0000000
--- a/pyfanctl/Makefile
+++ /dev/null
@@ -1 +0,0 @@
-../Makefile.python
\ No newline at end of file
diff --git a/pyfanctl/fan_control.py b/pyfanctl/fan_control.py
deleted file mode 100644
index 2e7b968..0000000
--- a/pyfanctl/fan_control.py
+++ /dev/null
@@ -1,59 +0,0 @@
-#!/usr/bin/env python
-
-import gobject
-import dbus
-import dbus.service
-import dbus.mainloop.glib
-from obmc.dbuslib.bindings import get_dbus, DbusProperties, DbusObjectManager
-
-DBUS_NAME = 'org.openbmc.control.Fans'
-OBJ_PATH = '/org/openbmc/control/fans'
-IFACE_NAME = 'org.openbmc.control.Fans'
-
-FAN_BUS = 'org.openbmc.Sensors'
-FAN_OBJS = [
-    '/org/openbmc/sensors/speed/fan0',
-    '/org/openbmc/sensors/speed/fan1',
-    '/org/openbmc/sensors/speed/fan2',
-    '/org/openbmc/sensors/speed/fan3',
-    '/org/openbmc/sensors/speed/fan4',
-    '/org/openbmc/sensors/speed/fan5',
-]
-FAN_IFACE = 'org.openbmc.SensorValue'
-
-
-class FanControl(DbusProperties, DbusObjectManager):
-    def __init__(self, bus, name):
-        super(FanControl, self).__init__(
-            conn=bus,
-            object_path=name)
-        self.Set(IFACE_NAME, "floor", 250)
-        self.Set(IFACE_NAME, "ceiling", 255)
-        self.fan_intf = []
-        ## create interface proxies to all fans
-        for fan in FAN_OBJS:
-            print "Initializing fan: "+fan
-            obj = bus.get_object(FAN_BUS, fan, introspect=False)
-            self.fan_intf.append(dbus.Interface(obj, FAN_IFACE))
-
-    @dbus.service.method(DBUS_NAME, in_signature='', out_signature='')
-    def setMax(self):
-        print "Setting fans to max"
-        for intf in self.fan_intf:
-            intf.setValue(dbus.UInt32(255))
-
-
-if __name__ == '__main__':
-    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
-    bus = get_dbus()
-    fan_control = FanControl(bus, OBJ_PATH)
-    mainloop = gobject.MainLoop()
-
-    print "Starting fan control"
-    fan_control.setMax()
-
-    fan_control.unmask_signals()
-    name = dbus.service.BusName(DBUS_NAME, bus)
-    mainloop.run()
-
-# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
diff --git a/pyfanctl/setup.cfg b/pyfanctl/setup.cfg
deleted file mode 120000
index 29939b5..0000000
--- a/pyfanctl/setup.cfg
+++ /dev/null
@@ -1 +0,0 @@
-../setup.cfg
\ No newline at end of file
diff --git a/pyfanctl/setup.py b/pyfanctl/setup.py
deleted file mode 100644
index 655808d..0000000
--- a/pyfanctl/setup.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from distutils.core import setup
-
-setup(name='pyfanctl',
-      version='1.0',
-      scripts=['fan_control.py'],
-      )