diff options
Diffstat (limited to 'common/recipes-core')
40 files changed, 7310 insertions, 2 deletions
diff --git a/common/recipes-core/base-files/base-files_%.bbappend b/common/recipes-core/base-files/base-files_%.bbappend index 340ad2c..6a73f2b 100644 --- a/common/recipes-core/base-files/base-files_%.bbappend +++ b/common/recipes-core/base-files/base-files_%.bbappend @@ -15,8 +15,8 @@ do_install_bmc_issue () { dir=$(dirname $dir) done - if [ -d "$dir/meta-aspeed/.git" ]; then - srcdir="$dir/meta-aspeed" + if [ -d "$dir/meta-openbmc/.git" ]; then + srcdir="$dir/meta-openbmc" srcdir_git="${srcdir}/.git" version=$(git --git-dir=${srcdir_git} --work-tree=${srcdir} describe --dirty 2> /dev/null) else diff --git a/common/recipes-core/cfg-util/cfg-util_0.1.bb b/common/recipes-core/cfg-util/cfg-util_0.1.bb new file mode 100644 index 0000000..13b6568 --- /dev/null +++ b/common/recipes-core/cfg-util/cfg-util_0.1.bb @@ -0,0 +1,36 @@ +# Copyright 2015-present Facebook. All Rights Reserved. +SUMMARY = "Configuration Utility" +DESCRIPTION = "Utility for reading or writing configuration" +SECTION = "base" +PR = "r1" +LICENSE = "GPLv2" +LIC_FILES_CHKSUM = "file://cfg-util.c;beginline=4;endline=16;md5=b395943ba8a0717a83e62ca123a8d238" + +SRC_URI = "file://Makefile \ + file://cfg-util.c \ + " +S = "${WORKDIR}" + +LDFLAGS =+ " -lpal " + +DEPENDS =+ " libpal " + +binfiles = "cfg-util" + +pkgdir = "cfg-util" + +do_install() { + dst="${D}/usr/local/fbpackages/${pkgdir}" + bin="${D}/usr/local/bin" + install -d $dst + install -d $bin + install -m 755 cfg-util ${dst}/cfg-util + ln -snf ../fbpackages/${pkgdir}/cfg-util ${bin}/cfg-util +} + +FBPACKAGEDIR = "${prefix}/local/fbpackages" + +FILES_${PN} = "${FBPACKAGEDIR}/cfg-util ${prefix}/local/bin" + +INHIBIT_PACKAGE_DEBUG_SPLIT = "1" +INHIBIT_PACKAGE_STRIP = "1" diff --git a/common/recipes-core/cfg-util/files/Makefile b/common/recipes-core/cfg-util/files/Makefile new file mode 100644 index 0000000..05b9b16 --- /dev/null +++ b/common/recipes-core/cfg-util/files/Makefile @@ -0,0 +1,11 @@ +# Copyright 2015-present Facebook. All Rights Reserved. +all: cfg-util + + +cfg-util: cfg-util.o + $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) + +.PHONY: clean + +clean: + rm -rf *.o cfg-util diff --git a/common/recipes-core/cfg-util/files/cfg-util.c b/common/recipes-core/cfg-util/files/cfg-util.c new file mode 100644 index 0000000..99618f7 --- /dev/null +++ b/common/recipes-core/cfg-util/files/cfg-util.c @@ -0,0 +1,78 @@ +/* + * cfg-util + * + * Copyright 2015-present Facebook. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdint.h> +#include <string.h> +#include <openbmc/pal.h> + +static void +print_usage(void) { + printf("Usage: cfg-util <dump-all|key> <value>\n"); +} + +int +main(int argc, char **argv) { + + int ret; + uint8_t key[MAX_KEY_LEN] = {0}; + uint8_t val[MAX_VALUE_LEN] = {0}; + + // Handle boundary checks + if (argc < 2 || argc > 3) { + goto err_exit; + } + + // Handle dump of key value data base + if ((argc == 2) && (!strcmp(argv[1], "dump-all"))){ + pal_dump_key_value(); + return 0; + } + + // Handle Get the Configuration + if (argc == 2) { + snprintf(key, MAX_KEY_LEN, "%s", argv[1]); + + ret = pal_get_key_value(key, val); + if (ret) { + goto err_exit; + } + + printf("%s\n", val); + return 0; + } + + // Handle Set the configuration + snprintf(key, MAX_KEY_LEN, "%s", argv[1]); + snprintf(val, MAX_VALUE_LEN, "%s", argv[2]); + + ret = pal_set_key_value(key, val); + if (ret) { + goto err_exit; + } + + return 0; + +err_exit: + print_usage(); + exit(-1); +} diff --git a/common/recipes-core/consoled/consoled_0.1.bb b/common/recipes-core/consoled/consoled_0.1.bb new file mode 100644 index 0000000..5b6edd1 --- /dev/null +++ b/common/recipes-core/consoled/consoled_0.1.bb @@ -0,0 +1,36 @@ +# Copyright 2015-present Facebook. All Rights Reserved. +SUMMARY = "Serial Buffer" +DESCRIPTION = "Daemon for serial buffering" +SECTION = "base" +PR = "r1" +LICENSE = "GPLv2" +LIC_FILES_CHKSUM = "file://consoled.c;beginline=4;endline=16;md5=b395943ba8a0717a83e62ca123a8d238" + +SRC_URI = "file://Makefile \ + file://consoled.c \ + " +S = "${WORKDIR}" + +LDFLAGS =+ " -lpal " + +DEPENDS =+ " libpal " + +binfiles = "consoled" + +pkgdir = "consoled" + +do_install() { + dst="${D}/usr/local/fbpackages/${pkgdir}" + bin="${D}/usr/local/bin" + install -d $dst + install -d $bin + install -m 755 consoled ${dst}/consoled + ln -snf ../fbpackages/${pkgdir}/consoled ${bin}/consoled +} + +FBPACKAGEDIR = "${prefix}/local/fbpackages" + +FILES_${PN} = "${FBPACKAGEDIR}/consoled ${prefix}/local/bin" + +INHIBIT_PACKAGE_DEBUG_SPLIT = "1" +INHIBIT_PACKAGE_STRIP = "1" diff --git a/common/recipes-core/consoled/files/Makefile b/common/recipes-core/consoled/files/Makefile new file mode 100644 index 0000000..2b30ef2 --- /dev/null +++ b/common/recipes-core/consoled/files/Makefile @@ -0,0 +1,11 @@ +# Copyright 2015-present Facebook. All Rights Reserved. +all: consoled + + +consoled: consoled.o + $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) + +.PHONY: clean + +clean: + rm -rf *.o consoled diff --git a/common/recipes-core/consoled/files/consoled.c b/common/recipes-core/consoled/files/consoled.c new file mode 100644 index 0000000..9f17e31 --- /dev/null +++ b/common/recipes-core/consoled/files/consoled.c @@ -0,0 +1,284 @@ +/* + * consoled + * + * Copyright 2015-present Facebook. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <syslog.h> +#include <stdint.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <termios.h> +#include <signal.h> +#include <openbmc/pal.h> + +#define BAUDRATE B57600 +#define CTRL_X 0x18 +#define ASCII_ENTER 0x0D + +static sig_atomic_t sigexit = 0; + +static void +write_data(int file, char *buf, int len, char *fname) { + int wlen = 0; + char *tbuf = buf; + while(len > 0) { + errno = 0; + wlen = write(file, tbuf, len); + // In case wlen < 0, retry write() + if (wlen >= 0) { + len -= wlen; + tbuf = tbuf + wlen; + } else { + syslog(LOG_ALERT, "write_data: write() failed to file %s | errno: %d", + fname, errno); + return; + } + } +} + +static void +exit_session(int sig) +{ + sigexit = sig; +} + +static void +print_usage() { + printf("Usage: consoled [ %s ]\n", pal_server_list); +} + +static void +run_console(char* fru_name, int term) { + + int i; + int tty; // serial port + int buf_fd; // Buffer File + int blen; // len for + int nfd = 0; // For number of fd + int nevents; // For number of events in fd + int nline = 0; + //int pid_fd; + int flags; + pid_t pid; // For pid of the daemon + uint8_t fru; + char in; // For stdin character + char data[32]; + char pid_file[64]; + char devtty[32]; // For tty dev path + char bfname[32]; // For buffer file path + char old_bfname[32]; // For old buffer file path + char buf[256]; // For buffer data + struct termios ottytio, nttytio; // For the tty dev + + int stdi; // STDIN_FILENO + int stdo; // STDOUT_FILENO + struct termios ostditio, nstditio; // For STDIN_FILENO + struct termios ostdotio, nstdotio; // For STDOUT_FILENO + + struct pollfd pfd[2]; + + /* Start Daemon for the console buffering */ + if (!term) { + daemon(0,1); + openlog("consoled", LOG_CONS, LOG_DAEMON); + syslog(LOG_INFO, "consoled: daemon started"); + } + + if (pal_get_fru_id(fru_name, &fru)) { + exit(-1); + } + + if (pal_get_fru_devtty(fru, devtty)) { + exit(-1); + } + + /* Handling the few Signals differently */ + signal(SIGHUP, exit_session); + signal(SIGINT, exit_session); + signal(SIGTERM, exit_session); + signal(SIGPIPE, exit_session); + signal(SIGQUIT, exit_session); + + /* Different flag value for tty dev */ + flags = term == 1 ? (O_RDWR | O_NOCTTY | O_NONBLOCK) : + (O_RDONLY | O_NOCTTY | O_NONBLOCK); + + if ((tty = open(devtty, flags)) < 0) { + syslog(LOG_ALERT, "Cannot open the file %s", devtty); + exit(-1); + } + fcntl(tty, F_SETFL, O_RDWR); + + /* Changing the attributes of the tty dev */ + tcgetattr(tty, &ottytio); + memcpy(&nttytio, &ottytio, sizeof(struct termios)); + cfmakeraw(&nttytio); + cfsetspeed(&nttytio, BAUDRATE); + tcflush(tty, TCIFLUSH); + tcsetattr(tty, TCSANOW, &nttytio); + pfd[0].fd = tty; + pfd[0].events = POLLIN; + nfd++; + + /* Buffering the console data into a file */ + sprintf(old_bfname, "/tmp/consoled_%s_log-old", fru_name); + sprintf(bfname, "/tmp/consoled_%s_log", fru_name); + if ((buf_fd = open(bfname, O_RDWR | O_APPEND | O_CREAT, 0666)) < 0) { + syslog(LOG_ALERT, "Cannot open the file %s", bfname); + exit(-1); + } + + if (term) { + /* Changing the attributes of STDIN_FILENO */ + stdi = STDIN_FILENO; + tcgetattr(stdi, &ostditio); + memcpy(&nstditio, &ostditio, sizeof(struct termios)); + cfmakeraw(&nstditio); + tcflush(stdi, TCIFLUSH); + tcsetattr(stdi, TCSANOW, &nstditio); + + /* Changing the attributes of STDOUT_FILENO */ + stdo = STDOUT_FILENO; + tcgetattr(stdo, &ostdotio); + memcpy(&nstdotio, &ostdotio, sizeof(struct termios)); + cfmakeraw(&nstdotio); + tcflush(stdo, TCIFLUSH); + tcsetattr(stdo, TCSANOW, &nstdotio); + + /* Adding STDIN_FILENO to the poll fd set */ + pfd[1].fd = stdi; + pfd[1].events = POLLIN; + nfd++; + } + + /* Handling the input event from the terminal and tty dev */ + while (!sigexit && (nevents = poll(pfd, nfd, -1 /* Timeout */))) { + + /* Input to the terminal from the user */ + if (term && nevents && nfd > 1 && pfd[1].revents > 0) { + blen = read(stdi, &in, sizeof(in)); + if (blen < 1) { + nfd--; + } + + if (in == CTRL_X) { + break; + } + + write_data(tty, &in, sizeof(in), "tty"); + + nevents--; + } + + /* Input from the tty dev */ + if (nevents && pfd[0].revents > 0) { + blen = read(tty, buf, sizeof(buf)); + if (blen > 0) { + for (i = 0; i < blen; i++) { + if (buf[i] == 0xD) + nline++; + } + write_data(buf_fd, buf, blen, bfname); + fsync(buf_fd); + if (term) { + write_data(stdo, buf, blen, "STDOUT_FILENO"); + } + } else if (blen < 0) { + raise(SIGHUP); + } + + /* Log Rotation */ + if (nline >= 300) { + close(buf_fd); + remove(old_bfname); + rename(bfname, old_bfname); + if ((buf_fd = open(bfname, O_RDWR | O_APPEND | O_CREAT, 0666)) < 0) { + syslog(LOG_ALERT, "Cannot open the file %s", bfname); + exit(-1); + } + nline = 0; + } + nevents--; + } + } + + /* Close the console buffer file */ + close(buf_fd); + + /* Revert the tty dev to old attributes */ + tcflush(tty, TCIFLUSH); + tcsetattr(tty, TCSANOW, &ottytio); + + /* Revert STDIN to old attributes */ + if (term) { + tcflush(stdo, TCIFLUSH); + tcsetattr(stdo, TCSANOW, &ostdotio); + tcflush(stdi, TCIFLUSH); + tcsetattr(stdi, TCSANOW, &ostditio); + } + + /* Delete the pid file */ + if (!term) + remove(pid_file); +} + +int +main(int argc, void **argv) { + int dev, rc, lock_file; + char file[64]; + int term; + char *fru_name; + + if (argc != 3) { + print_usage(); + exit(1); + } + + // A lock file for one instance of consoled for each fru + sprintf(file, "/var/lock/consoled_%s", argv[1]); + lock_file = open(file, O_CREAT | O_RDWR, 0666); + rc = flock(lock_file, LOCK_EX | LOCK_NB); + if(rc) { + if(errno == EWOULDBLOCK) { + printf("Another consoled %s instance is running...\n", argv[1]); + exit(-1); + } + } else { + + fru_name = argv[1]; + + if (!strcmp(argv[2], "--buffer")) { + term = 0; + } else if (!strcmp(argv[2], "--term")) { + term = 1; + } else { + print_usage(); + exit(-1); + } + + run_console(fru_name, term); + } + + return sigexit; +} + diff --git a/common/recipes-core/fan-ctrl/fan-ctrl/Makefile b/common/recipes-core/fan-ctrl/fan-ctrl/Makefile new file mode 100644 index 0000000..da74a34 --- /dev/null +++ b/common/recipes-core/fan-ctrl/fan-ctrl/Makefile @@ -0,0 +1,26 @@ +# Copyright 2014-present Facebook. All Rights Reserved. +# +# This program file is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program in a file named COPYING; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, +# Boston, MA 02110-1301 USA + +all: fand + +fand: fand.cpp watchdog.cpp + $(CXX) $(CXXFLAGS) -pthread -o $@ $^ $(LDFLAGS) + +.PHONY: clean + +clean: + rm -rf *.o fand diff --git a/common/recipes-core/fan-ctrl/fan-ctrl/README b/common/recipes-core/fan-ctrl/fan-ctrl/README new file mode 100644 index 0000000..2a92b9d --- /dev/null +++ b/common/recipes-core/fan-ctrl/fan-ctrl/README @@ -0,0 +1,5 @@ +The AST PWM/Tach driver is in the kernel sources at drivers/hwmon/ast_pwm_fan.c + +There are 7 PWM output pins. Each PWM can be configured in one of 3 types (M, +N, or O). The clock settings for each type are configurable. See init_pwm.sh +for more comments about how we configure the settings. diff --git a/common/recipes-core/fan-ctrl/fan-ctrl/fand.cpp b/common/recipes-core/fan-ctrl/fan-ctrl/fand.cpp new file mode 100644 index 0000000..49c6f6b --- /dev/null +++ b/common/recipes-core/fan-ctrl/fan-ctrl/fand.cpp @@ -0,0 +1,1030 @@ +/* + * fand + * + * Copyright 2014-present Facebook. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Daemon to manage the fan speed to ensure that we stay within a reasonable + * temperature range. We're using a simplistic algorithm to get started: + * + * If the fan is already on high, we'll move it to medium if we fall below + * a top temperature. If we're on medium, we'll move it to high + * if the temperature goes over the top value, and to low if the + * temperature falls to a bottom level. If the fan is on low, + * we'll increase the speed if the temperature rises to the top level. + * + * To ensure that we're not just turning the fans up, then back down again, + * we'll require an extra few degrees of temperature drop before we lower + * the fan speed. + * + * We check the RPM of the fans against the requested RPMs to determine + * whether the fans are failing, in which case we'll turn up all of + * the other fans and report the problem.. + * + * TODO: Implement a PID algorithm to closely track the ideal temperature. + * TODO: Determine if the daemon is already started. + */ + +/* Yeah, the file ends in .cpp, but it's a C program. Deal. */ + +#if !defined(CONFIG_YOSEMITE) && !defined(CONFIG_WEDGE) +#error "No hardware platform defined!" +#endif +#if defined(CONFIG_YOSEMITE) && defined(CONFIG_WEDGE) +#error "Both hardware platform defined!" +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <signal.h> +#include <syslog.h> +#ifdef CONFIG_YOSEMITE +#include <openbmc/ipmi.h> +#include <facebook/bic.h> +#include <facebook/yosemite_sensor.h> +#endif +#ifdef CONFIG_WEDGE +#include <facebook/wedge_eeprom.h> +#endif + +#include "watchdog.h" + +/* Sensor definitions */ + +#ifdef CONFIG_WEDGE +#define INTERNAL_TEMPS(x) ((x) * 1000) // stored a C * 1000 +#define EXTERNAL_TEMPS(x) ((x) / 1000) +#elif CONFIG_YOSEMITE +#define INTERNAL_TEMPS(x) (x) +#define EXTERNAL_TEMPS(x) (x) +#define TOTAL_1S_SERVERS 4 +#endif + +#define I2C_BUS_3_DIR "/sys/class/i2c-adapter/i2c-3/" +#define I2C_BUS_4_DIR "/sys/class/i2c-adapter/i2c-4/" + +#define INTAKE_TEMP_DEVICE I2C_BUS_3_DIR "3-0048" +#define T2_TEMP_DEVICE I2C_BUS_3_DIR "3-0049" +#define EXHAUST_TEMP_DEVICE I2C_BUS_3_DIR "3-004a" +#define USERVER_TEMP_DEVICE I2C_BUS_4_DIR "4-0040" + +/* + * The sensor for the uServer CPU is not on the CPU itself, so it reads + * a little low. We are special casing this, but we should obviously + * be thinking about a way to generalize these tweaks, and perhaps + * the entire configuration. JSON file? + */ + +#ifdef CONFIG_WEDGE +#define USERVER_TEMP_FUDGE INTERNAL_TEMPS(10) +#else +#define USERVER_TEMP_FUDGE INTERNAL_TEMPS(1) +#endif + +#define BAD_TEMP INTERNAL_TEMPS(-60) + +#define BAD_READ_THRESHOLD 4 /* How many times can reads fail */ +#define FAN_FAILURE_THRESHOLD 4 /* How many times can a fan fail */ +#define FAN_SHUTDOWN_THRESHOLD 20 /* How long fans can be failed before */ + /* we just shut down the whole thing. */ + + +#define PWM_DIR "/sys/devices/platform/ast_pwm_tacho.0" + +#define PWM_UNIT_MAX 96 + +#define LARGEST_DEVICE_NAME 120 + +#define GPIO_USERVER_POWER_DIRECTION "/sys/class/gpio/gpio25/direction" +#define GPIO_USERVER_POWER "/sys/class/gpio/gpio25/value" +#define GPIO_T2_POWER_DIRECTION "/tmp/gpionames/T2_POWER_UP/direction" +#define GPIO_T2_POWER "/tmp/gpionames/T2_POWER_UP/value" + +#define GPIO_FAN0_LED "/sys/class/gpio/gpio53/value" +#define GPIO_FAN1_LED "/sys/class/gpio/gpio54/value" +#define GPIO_FAN2_LED "/sys/class/gpio/gpio55/value" +#define GPIO_FAN3_LED "/sys/class/gpio/gpio72/value" + +const char *fan_led[] = {GPIO_FAN0_LED, GPIO_FAN1_LED, + GPIO_FAN2_LED, GPIO_FAN3_LED}; + +#define FAN_LED_RED "0" +#define FAN_LED_BLUE "1" + +#define GPIO_PIN_ID "/sys/class/gpio/gpio%d/value" +#define REV_IDS 3 +#define GPIO_REV_ID_START 192 + +#define BOARD_IDS 4 +#define GPIO_BOARD_ID_START 160 + +/* + * With hardware revisions after 3, we use a different set of pins for + * the BOARD_ID. + */ + +#define REV_ID_NEW_BOARD_ID 3 +#define GPIO_BOARD_ID_START_NEW 166 + +#define REPORT_TEMP 720 /* Report temp every so many cycles */ + +/* Sensor limits and tuning parameters */ + +#define INTAKE_LIMIT INTERNAL_TEMPS(60) +#define T2_LIMIT INTERNAL_TEMPS(80) +#define USERVER_LIMIT INTERNAL_TEMPS(90) + +#define TEMP_TOP INTERNAL_TEMPS(70) +#define TEMP_BOTTOM INTERNAL_TEMPS(40) + +/* + * Toggling the fan constantly will wear it out (and annoy anyone who + * can hear it), so we'll only turn down the fan after the temperature + * has dipped a bit below the point at which we'd otherwise switch + * things up. + */ + +#define COOLDOWN_SLOP INTERNAL_TEMPS(6) + +#define WEDGE_FAN_LOW 35 +#define WEDGE_FAN_MEDIUM 50 +#define WEDGE_FAN_HIGH 70 +#define WEDGE_FAN_MAX 99 + +#define SIXPACK_FAN_LOW 35 +#define SIXPACK_FAN_MEDIUM 55 +#define SIXPACK_FAN_HIGH 75 +#define SIXPACK_FAN_MAX 99 + +/* + * Mapping physical to hardware addresses for fans; it's different for + * RPM measuring and PWM setting, naturally. Doh. + */ + +#ifdef CONFIG_WEDGE +int fan_to_rpm_map[] = {3, 2, 0, 1}; +int fan_to_pwm_map[] = {7, 6, 0, 1}; +#define FANS 4 +// Tacho offset between front and rear fans: +#define REAR_FAN_OFFSET 4 +#define BACK_TO_BACK_FANS +#else +int fan_to_rpm_map[] = {0, 1}; +int fan_to_pwm_map[] = {0, 1}; +#define FANS 2 +// Tacho offset between front and rear fans: +#define REAR_FAN_OFFSET 1 +#endif + + +/* + * The measured RPM of the fans doesn't match linearly to the requested + * rate. In addition, there are coaxially mounted fans, so the rear fans + * feed into the front fans. The rear fans will run slower since they're + * grabbing still air, and the front fants are getting an extra boost. + * + * We'd like to measure the fan RPM and compare it to the expected RPM + * so that we can detect failed fans, so we have a table (derived from + * hardware testing): + */ + +struct rpm_to_pct_map { + ushort pct; + ushort rpm; +}; + +#ifdef CONFIG_WEDGE +struct rpm_to_pct_map rpm_front_map[] = {{30, 6150}, + {35, 7208}, + {40, 8195}, + {45, 9133}, + {50, 10017}, + {55, 10847}, + {60, 11612}, + {65, 12342}, + {70, 13057}, + {75, 13717}, + {80, 14305}, + {85, 14869}, + {90, 15384}, + {95, 15871}, + {100, 16095}}; +#define FRONT_MAP_SIZE (sizeof(rpm_front_map) / sizeof(struct rpm_to_pct_map)) + +struct rpm_to_pct_map rpm_rear_map[] = {{30, 3911}, + {35, 4760}, + {40, 5587}, + {45, 6434}, + {50, 7295}, + {55, 8187}, + {60, 9093}, + {65, 10008}, + {70, 10949}, + {75, 11883}, + {80, 12822}, + {85, 13726}, + {90, 14690}, + {95, 15516}, + {100, 15897}}; +#define REAR_MAP_SIZE (sizeof(rpm_rear_map) / sizeof(struct rpm_to_pct_map)) +#else +struct rpm_to_pct_map rpm_map[] = {{30, 3413}, + {35, 3859}, + {40, 4305}, + {45, 4686}, + {50, 5032}, + {55, 5432}, + {60, 5991}, + {65, 6460}, + {70, 6927}, + {75, 7379}, + {80, 7733}, + {85, 8156}, + {90, 8599}, + {95, 9049}, + {100, 9265}}; +struct rpm_to_pct_map *rpm_front_map = rpm_map; +struct rpm_to_pct_map *rpm_rear_map = rpm_map; +#define MAP_SIZE (sizeof(rpm_map) / sizeof(struct rpm_to_pct_map)) +#define FRONT_MAP_SIZE MAP_SIZE +#define REAR_MAP_SIZE MAP_SIZE + +#endif + +#define FAN_FAILURE_OFFSET 30 + +int fan_low = WEDGE_FAN_LOW; +int fan_medium = WEDGE_FAN_MEDIUM; +int fan_high = WEDGE_FAN_HIGH; +int fan_max = WEDGE_FAN_MAX; +int total_fans = FANS; +int fan_offset = 0; + +int temp_bottom = TEMP_BOTTOM; +int temp_top = TEMP_TOP; + +int report_temp = REPORT_TEMP; +bool verbose = false; + +void usage() { + fprintf(stderr, + "fand [-v] [-l <low-pct>] [-m <medium-pct>] " + "[-h <high-pct>]\n" + "\t[-b <temp-bottom>] [-t <temp-top>] [-r <report-temp>]\n\n" + "\tlow-pct defaults to %d%% fan\n" + "\tmedium-pct defaults to %d%% fan\n" + "\thigh-pct defaults to %d%% fan\n" + "\ttemp-bottom defaults to %dC\n" + "\ttemp-top defaults to %dC\n" + "\treport-temp defaults to every %d measurements\n\n" + "fand compensates for uServer temperature reading %d degrees low\n" + "kill with SIGUSR1 to stop watchdog\n", + fan_low, + fan_medium, + fan_high, + EXTERNAL_TEMPS(temp_bottom), + EXTERNAL_TEMPS(temp_top), + report_temp, + EXTERNAL_TEMPS(USERVER_TEMP_FUDGE)); + exit(1); +} + +/* We need to open the device each time to read a value */ + +int read_device(const char *device, int *value) { + FILE *fp; + int rc; + + fp = fopen(device, "r"); + if (!fp) { + int err = errno; + + syslog(LOG_INFO, "failed to open device %s", device); + return err; + } + + rc = fscanf(fp, "%d", value); + fclose(fp); + + if (rc != 1) { + syslog(LOG_INFO, "failed to read device %s", device); + return ENOENT; + } else { + return 0; + } +} + +/* We need to open the device again each time to write a value */ + +int write_device(const char *device, const char *value) { + FILE *fp; + int rc; + + fp = fopen(device, "w"); + if (!fp) { + int err = errno; + + syslog(LOG_INFO, "failed to open device for write %s", device); + return err; + } + + rc = fputs(value, fp); + fclose(fp); + + if (rc < 0) { + syslog(LOG_INFO, "failed to write device %s", device); + return ENOENT; + } else { + return 0; + } +} + +#ifdef CONFIG_WEDGE +int read_temp(const char *device, int *value) { + char full_name[LARGEST_DEVICE_NAME + 1]; + + /* We set an impossible value to check for errors */ + *value = BAD_TEMP; + snprintf( + full_name, LARGEST_DEVICE_NAME, "%s/temp1_input", device); + return read_device(full_name, value); +} + +int read_gpio_value(const int id, const char *device, int *value) { + char full_name[LARGEST_DEVICE_NAME]; + + snprintf(full_name, LARGEST_DEVICE_NAME, device, id); + return read_device(full_name, value); +} + +int read_gpio_values(const int start, const int count, + const char *device, int *result) { + int status = 0; + int value; + + *result = 0; + for (int i = 0; i < count; i++) { + status |= read_gpio_value(start + i, GPIO_PIN_ID, &value); + *result |= value << i; + } + return status; +} + +int read_ids(int *rev_id, int *board_id) { + int status = 0; + int value; + + status = read_gpio_values(GPIO_REV_ID_START, REV_IDS, GPIO_PIN_ID, rev_id); + if (status != 0) { + syslog(LOG_INFO, "failed to read rev_id"); + return status; + } + + int board_id_start; + if (*rev_id >= REV_ID_NEW_BOARD_ID) { + board_id_start = GPIO_BOARD_ID_START_NEW; + } else { + board_id_start = GPIO_BOARD_ID_START; + } + + status = read_gpio_values(board_id_start, BOARD_IDS, GPIO_PIN_ID, board_id); + if (status != 0) { + syslog(LOG_INFO, "failed to read board_id"); + } + return status; +} + +bool is_two_fan_board(bool verbose) { + struct wedge_eeprom_st eeprom; + /* Retrieve the board type from EEPROM */ + if (wedge_eeprom_parse(NULL, &eeprom) == 0) { + /* able to parse EEPROM */ + if (verbose) { + syslog(LOG_INFO, "board type is %s", eeprom.fbw_location); + } + /* only WEDGE is NOT two-fan board */ + return strncasecmp(eeprom.fbw_location, "wedge", + sizeof(eeprom.fbw_location)); + } else { + int status; + int board_id = 0; + int rev_id = 0; + /* + * Could not parse EEPROM. Most likely, it is an old HW without EEPROM. + * In this case, use board ID to distinguish if it is wedge or 6-pack. + */ + status = read_ids(&rev_id, &board_id); + if (verbose) { + syslog(LOG_INFO, "rev ID %d, board id %d", rev_id, board_id); + } + if (status == 0 && board_id != 0xf) { + return true; + } else { + return false; + } + } +} +#endif + +int read_fan_value(const int fan, const char *device, int *value) { + char device_name[LARGEST_DEVICE_NAME]; + char output_value[LARGEST_DEVICE_NAME]; + char full_name[LARGEST_DEVICE_NAME]; + + snprintf(device_name, LARGEST_DEVICE_NAME, device, fan); + snprintf(full_name, LARGEST_DEVICE_NAME, "%s/%s", PWM_DIR, device_name); + return read_device(full_name, value); +} + +int write_fan_value(const int fan, const char *device, const int value) { + char full_name[LARGEST_DEVICE_NAME]; + char device_name[LARGEST_DEVICE_NAME]; + char output_value[LARGEST_DEVICE_NAME]; + + snprintf(device_name, LARGEST_DEVICE_NAME, device, fan); + snprintf(full_name, LARGEST_DEVICE_NAME, "%s/%s", PWM_DIR, device_name); + snprintf(output_value, LARGEST_DEVICE_NAME, "%d", value); + return write_device(full_name, output_value); +} + +/* Return fan speed as a percentage of maximum -- not necessarily linear. */ + +int fan_rpm_to_pct(const struct rpm_to_pct_map *table, + const int table_len, + int rpm) { + int i; + + for (i = 0; i < table_len; i++) { + if (table[i].rpm > rpm) { + break; + } + } + + /* + * If the fan RPM is lower than the lowest value in the table, + * we may have a problem -- fans can only go so slow, and it might + * have stopped. In this case, we'll return an interpolated + * percentage, as just returning zero is even more problematic. + */ + + if (i == 0) { + return (rpm * table[i].pct) / table[i].rpm; + } else if (i == table_len) { // Fell off the top? + return table[i - 1].pct; + } + + // Interpolate the right percentage value: + + int percent_diff = table[i].pct - table[i - 1].pct; + int rpm_diff = table[i].rpm - table[i - 1].rpm; + int fan_diff = table[i].rpm - rpm; + + return table[i].pct - (fan_diff * percent_diff / rpm_diff); +} + +int fan_speed_okay(const int fan, const int speed, const int slop) { + int front_fan, rear_fan; + int front_pct, rear_pct; + int real_fan; + int okay; + + /* + * The hardware fan numbers are different from the physical order + * in the box, so we have to map them: + */ + + real_fan = fan_to_rpm_map[fan]; + + front_fan = 0; + read_fan_value(real_fan, "tacho%d_rpm", &front_fan); + front_pct = fan_rpm_to_pct(rpm_front_map, FRONT_MAP_SIZE, front_fan); +#ifdef BACK_TO_BACK_FANS + rear_fan = 0; + read_fan_value(real_fan + REAR_FAN_OFFSET, "tacho%d_rpm", &rear_fan); + rear_pct = fan_rpm_to_pct(rpm_rear_map, REAR_MAP_SIZE, rear_fan); +#endif + + + /* + * If the fans are broken, the measured rate will be rather + * different from the requested rate, and we can turn up the + * rest of the fans to compensate. The slop is the percentage + * of error that we'll tolerate. + * + * XXX: I suppose that we should only measure negative values; + * running too fast isn't really a problem. + */ + +#ifdef BACK_TO_BACK_FANS + okay = (abs(front_pct - speed) * 100 / speed < slop && + abs(rear_pct - speed) * 100 / speed < slop); +#else + okay = (abs(front_pct - speed) * 100 / speed < slop); +#endif + + if (!okay || verbose) { + syslog(!okay ? LOG_ALERT : LOG_INFO, +#ifdef BACK_TO_BACK_FANS + "fan %d rear %d (%d%%), front %d (%d%%), expected %d", +#else + "fan %d %d RPM (%d%%), expected %d", +#endif + fan, +#ifdef BACK_TO_BACK_FANS + rear_fan, + rear_pct, +#endif + front_fan, + front_pct, + speed); + } + + return okay; +} + +/* Set fan speed as a percentage */ + +int write_fan_speed(const int fan, const int value) { + /* + * The hardware fan numbers for pwm control are different from + * both the physical order in the box, and the mapping for reading + * the RPMs per fan, above. + */ + + int real_fan = fan_to_pwm_map[fan]; + + if (value == 0) { + return write_fan_value(real_fan, "pwm%d_en", 0); + } else { + int unit = value * PWM_UNIT_MAX / 100; + int status; + + if (unit == PWM_UNIT_MAX) + unit = 0; + + if ((status = write_fan_value(real_fan, "pwm%d_type", 0)) != 0 || + (status = write_fan_value(real_fan, "pwm%d_rising", 0)) != 0 || + (status = write_fan_value(real_fan, "pwm%d_falling", unit)) != 0 || + (status = write_fan_value(real_fan, "pwm%d_en", 1)) != 0) { + return status; + } + } +} + +/* Set up fan LEDs */ + +int write_fan_led(const int fan, const char *color) +{ +#ifdef CONFIG_WEDGE + return write_device(fan_led[fan], color); +#else + return 0; +#endif +} + +int server_shutdown(const char *why) { + int fan; + for (fan = 0; fan < total_fans; fan++) { + write_fan_speed(fan + fan_offset, fan_max); + } + + syslog(LOG_EMERG, "Shutting down: %s", why); + write_device(GPIO_USERVER_POWER_DIRECTION, "out"); + write_device(GPIO_USERVER_POWER, "0"); +#ifdef CONFIG_WEDGE + /* + * Putting T2 in reset generates a non-maskable interrupt to uS, + * the kernel running on uS might panic depending on its version. + * sleep 5s here to make sure uS is completely down. + */ + sleep(5); + + if (write_device(GPIO_T2_POWER_DIRECTION, "out") || + write_device(GPIO_T2_POWER, "1")) { + /* + * We're here because something has gone badly wrong. If we + * didn't manage to shut down the T2, cut power to the whole box, + * using the PMBus OPERATION register. This will require a power + * cycle (removal of both power inputs) to recover. + */ + syslog(LOG_EMERG, "T2 power off failed; turning off via ADM1278"); + system("rmmod adm1275"); + system("i2cset -y 12 0x10 0x01 00"); + } + +#else + // TODO(7088822): try throttling, then shutting down server. + syslog(LOG_EMERG, "Need to implement actual shutdown!\n"); +#endif + + /* + * We have to stop the watchdog, or the system will be automatically + * rebooted some seconds after fand exits (and stops kicking the + * watchdog). + */ + + stop_watchdog(); + + sleep(2); + exit(2); +} + +/* Gracefully shut down on receipt of a signal */ + +void fand_interrupt(int sig) +{ + int fan; + for (fan = 0; fan < total_fans; fan++) { + write_fan_speed(fan + fan_offset, fan_max); + } + + syslog(LOG_ALERT, "Shutting down fand on signal %s", strsignal(sig)); + if (sig == SIGUSR1) { + stop_watchdog(); + } + exit(3); +} + +int main(int argc, char **argv) { + /* Sensor values */ + +#ifdef CONFIG_WEDGE + int intake_temp; + int exhaust_temp; + int t2_temp; + int userver_temp; +#else + float intake_temp; + float exhaust_temp; + float userver_temp; +#endif + + int fan_speed = fan_high; + int bad_reads = 0; + int fan_failure = 0; + int fan_speed_changes = 0; + int old_speed; + + int fan_bad[FANS]; + int fan; + + unsigned log_count = 0; // How many times have we logged our temps? + int opt; + int prev_fans_bad = 0; + + struct sigaction sa; + + sa.sa_handler = fand_interrupt; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + sigaction(SIGUSR1, &sa, NULL); + + // Start writing to syslog as early as possible for diag purposes. + + openlog("fand", LOG_CONS, LOG_DAEMON); + +#ifdef CONFIG_WEDGE + if (is_two_fan_board(false)) { + /* Alternate, two fan configuration */ + total_fans = 2; + fan_offset = 2; /* fan 3 is the first */ + + fan_low = SIXPACK_FAN_LOW; + fan_medium = SIXPACK_FAN_MEDIUM; + fan_high = SIXPACK_FAN_HIGH; + fan_max = SIXPACK_FAN_MAX; + fan_speed = fan_high; + } +#endif + + while ((opt = getopt(argc, argv, "l:m:h:b:t:r:v")) != -1) { + switch (opt) { + case 'l': + fan_low = atoi(optarg); + break; + case 'm': + fan_medium = atoi(optarg); + break; + case 'h': + fan_high = atoi(optarg); + break; + case 'b': + temp_bottom = INTERNAL_TEMPS(atoi(optarg)); + break; + case 't': + temp_top = INTERNAL_TEMPS(atoi(optarg)); + break; + case 'r': + report_temp = atoi(optarg); + break; + case 'v': + verbose = true; + break; + default: + usage(); + break; + } + } + + if (optind > argc) { + usage(); + } + + if (temp_bottom > temp_top) { + fprintf(stderr, + "Should temp-bottom (%d) be higher than " + "temp-top (%d)? Starting anyway.\n", + EXTERNAL_TEMPS(temp_bottom), + EXTERNAL_TEMPS(temp_top)); + } + + if (fan_low > fan_medium || fan_low > fan_high || fan_medium > fan_high) { + fprintf(stderr, + "fan RPMs not strictly increasing " + "-- %d, %d, %d, starting anyway\n", + fan_low, + fan_medium, + fan_high); + } + + daemon(1, 0); + + if (verbose) { + syslog(LOG_DEBUG, "Starting up; system should have %d fans.", + total_fans); + } + + for (fan = 0; fan < total_fans; fan++) { + fan_bad[fan] = 0; + write_fan_speed(fan + fan_offset, fan_speed); + write_fan_led(fan + fan_offset, FAN_LED_BLUE); + } + +#ifdef CONFIG_YOSEMITE + /* Ensure that we can read from sensors before proceeding. */ + + while (yosemite_sensor_read(1, BIC_SENSOR_SOC_TEMP, &userver_temp)) + syslog(LOG_DEBUG, "Failed reading of SOC_TEMP."); +#endif + + /* Start watchdog in manual mode */ + start_watchdog(0); + + /* Set watchdog to persistent mode so timer expiry will happen independent + * of this process's liveliness. */ + set_persistent_watchdog(WATCHDOG_SET_PERSISTENT); + + sleep(5); /* Give the fans time to come up to speed */ + + while (1) { + int max_temp; + old_speed = fan_speed; + + /* Read sensors */ + +#ifdef CONFIG_WEDGE + read_temp(INTAKE_TEMP_DEVICE, &intake_temp); + read_temp(EXHAUST_TEMP_DEVICE, &exhaust_temp); + read_temp(T2_TEMP_DEVICE, &t2_temp); + read_temp(USERVER_TEMP_DEVICE, &userver_temp); + + /* + * uServer can be powered down, but all of the rest of the sensors + * should be readable at any time. + */ + + if ((intake_temp == BAD_TEMP || exhaust_temp == BAD_TEMP || + t2_temp == BAD_TEMP)) { + bad_reads++; + } +#else + userver_temp = BAD_TEMP; + if (yosemite_sensor_read(1, SP_SENSOR_INLET_TEMP, &intake_temp) || + yosemite_sensor_read(1, SP_SENSOR_OUTLET_TEMP, &exhaust_temp)) + bad_reads++; + + /* + * There are a number of 1S servers; any or all of them + * could be powered off and returning no values. Ignore these + * invalid values. + */ + for (int node = 1; node <= TOTAL_1S_SERVERS; node++) { + float new_temp; + if (!yosemite_sensor_read(node, BIC_SENSOR_SOC_TEMP, &new_temp)) { + if (userver_temp < new_temp) { + userver_temp = new_temp; + } + } + } +#endif + + if (bad_reads > BAD_READ_THRESHOLD) { + server_shutdown("Some sensors couldn't be read"); + } + + if (log_count++ % report_temp == 0) { + syslog(LOG_DEBUG, +#ifdef CONFIG_WEDGE + "Temp intake %d, t2 %d, " + " userver %d, exhaust %d, " + "fan speed %d, speed changes %d", +#else + "Temp intake %f, max server %f, exhaust %f, " + "fan speed %d, speed changes %d", +#endif + intake_temp, +#ifdef CONFIG_WEDGE + t2_temp, +#endif + userver_temp, + exhaust_temp, + fan_speed, + fan_speed_changes); + } + + /* Protection heuristics */ + + if (intake_temp > INTAKE_LIMIT) { + server_shutdown("Intake temp limit reached"); + } + +#ifdef CONFIG_WEDGE + if (t2_temp > T2_LIMIT) { + server_shutdown("T2 temp limit reached"); + } +#endif + + if (userver_temp + USERVER_TEMP_FUDGE > USERVER_LIMIT) { + server_shutdown("uServer temp limit reached"); + } + + /* + * Calculate change needed -- we should eventually + * do something more sophisticated, like PID. + * + * We should use the intake temperature to adjust this + * as well. + */ + +#ifdef CONFIG_WEDGE + if (t2_temp > userver_temp + USERVER_TEMP_FUDGE) { + max_temp = t2_temp; + } else { + max_temp = userver_temp + USERVER_TEMP_FUDGE; + } +#else + /* Yosemite could have no servers turned on, so ignore that case. */ + if (userver_temp + USERVER_TEMP_FUDGE > exhaust_temp) { + max_temp = userver_temp + USERVER_TEMP_FUDGE; + } else { + max_temp = exhaust_temp; + } +#endif + + /* + * If recovering from a fan problem, spin down fans gradually in case + * temperatures are still high. Gradual spin down also reduces wear on + * the fans. + */ + if (fan_speed == fan_max) { + if (fan_failure == 0) { + fan_speed = fan_high; + } + } else if (fan_speed == fan_high) { + if (max_temp + COOLDOWN_SLOP < temp_top) { + fan_speed = fan_medium; + } + } else if (fan_speed == fan_medium) { + if (max_temp > temp_top) { + fan_speed = fan_high; + } else if (max_temp + COOLDOWN_SLOP < temp_bottom) { + fan_speed = fan_low; + } + } else {/* low */ + if (max_temp > temp_bottom) { + fan_speed = fan_medium; + } + } + + /* + * Update fans only if there are no failed ones. If any fans failed + * earlier, all remaining fans should continue to run at max speed. + */ + + if (fan_failure == 0 && fan_speed != old_speed) { + syslog(LOG_NOTICE, + "Fan speed changing from %d to %d", + old_speed, + fan_speed); + fan_speed_changes++; + for (fan = 0; fan < total_fans; fan++) { + write_fan_speed(fan + fan_offset, fan_speed); + } + } + + /* + * Wait for some change. Typical I2C temperature sensors + * only provide a new value every second and a half, so + * checking again more quickly than that is a waste. + * + * We also have to wait for the fan changes to take effect + * before measuring them. + */ + + sleep(5); + + /* Check fan RPMs */ + + for (fan = 0; fan < total_fans; fan++) { + /* + * Make sure that we're within some percentage + * of the requested speed. + */ + if (fan_speed_okay(fan + fan_offset, fan_speed, FAN_FAILURE_OFFSET)) { + if (fan_bad[fan] > FAN_FAILURE_THRESHOLD) { + write_fan_led(fan + fan_offset, FAN_LED_BLUE); + syslog(LOG_NOTICE, + "Fan %d has recovered", + fan); + } + fan_bad[fan] = 0; + } else { + fan_bad[fan]++; + } + } + + fan_failure = 0; + for (fan = 0; fan < total_fans; fan++) { + if (fan_bad[fan] > FAN_FAILURE_THRESHOLD) { + fan_failure++; + write_fan_led(fan + fan_offset, FAN_LED_RED); + } + } + + if (fan_failure > 0) { + if (prev_fans_bad != fan_failure) { + syslog(LOG_ALERT, "%d fans failed", fan_failure); + } + + /* + * If fans are bad, we need to blast all of the + * fans at 100%; we don't bother to turn off + * the bad fans, in case they are all that is left. + * + * Note that we have a temporary bug with setting fans to + * 100% so we only do fan_max = 99%. + */ + + fan_speed = fan_max; + for (fan = 0; fan < total_fans; fan++) { + write_fan_speed(fan + fan_offset, fan_speed); + } + + if (fan_failure == total_fans) { + int count = 0; + for (fan = 0; fan < total_fans; fan++) { + if (fan_bad[fan] > FAN_SHUTDOWN_THRESHOLD) + count++; + } + if (count == total_fans) { + server_shutdown("all fans are bad for more than 12 cycles"); + } + } + + + /* + * Fans can be hot swapped and replaced; in which case the fan daemon + * will automatically detect the new fan and (assuming the new fan isn't + * itself faulty), automatically readjust the speeds for all fans down + * to a more suitable rpm. The fan daemon does not need to be restarted. + */ + } + + /* Suppress multiple warnings for similar number of fan failures. */ + prev_fans_bad = fan_failure; + + /* if everything is fine, restart the watchdog countdown. If this process + * is terminated, the persistent watchdog setting will cause the system + * to reboot after the watchdog timeout. */ + kick_watchdog(); + } +} diff --git a/common/recipes-core/fan-ctrl/fan-ctrl/watchdog.cpp b/common/recipes-core/fan-ctrl/fan-ctrl/watchdog.cpp new file mode 100644 index 0000000..ebb390a --- /dev/null +++ b/common/recipes-core/fan-ctrl/fan-ctrl/watchdog.cpp @@ -0,0 +1,201 @@ +/* + * watchdog + * + * Copyright 2014-present Facebook. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "watchdog.h" + +#include <errno.h> +#include <fcntl.h> +#include <pthread.h> +#include <syslog.h> +#include <unistd.h> + +#define WATCHDOG_START_KEY "x" +#define WATCHDOG_STOP_KEY "X" +/* The "magic close" character (as defined in Linux watchdog specs). */ +#define WATCHDOG_PERSISTENT_KEY "V" +#define WATCHDOG_NON_PERSISTENT_KEY "a" + +static int watchdog_dev = -1; + +/* This is needed to prevent rapid consecutive stop/start watchdog calls from + * generating multiple threads. */ +static int watchdog_started = 0; + +static const char* watchdog_kick_key = WATCHDOG_PERSISTENT_KEY; +static pthread_t watchdog_tid; +static pthread_mutex_t watchdog_lock = PTHREAD_MUTEX_INITIALIZER; + +/* Forward declarations. */ +static void* watchdog_thread(void* args); +static int kick_watchdog_unsafe(); + +/* + * When started, this background thread will constantly reset the watchdog + * at every 5 second interval. + */ + +static void* watchdog_thread(void* args) { + + pthread_detach(pthread_self()); + + /* Make sure another instance of the thread hasn't already been started. */ + pthread_mutex_lock(&watchdog_lock); + if (watchdog_started) { + goto done; + } else { + watchdog_started = 1; + } + pthread_mutex_unlock(&watchdog_lock); + + /* Actual loop for refreshing the watchdog timer. */ + while (1) { + pthread_mutex_lock(&watchdog_lock); + if (watchdog_dev != -1) { + kick_watchdog_unsafe(); + } else { + break; + } + pthread_mutex_unlock(&watchdog_lock); + sleep(5); + } + + /* Broke out of loop because watchdog was stopped. */ + watchdog_started = 0; +done: + pthread_mutex_unlock(&watchdog_lock); + return NULL; +} + +/* + * Starts the watchdog timer. timer counts down and restarts the ARM chip + * upon timeout. use kick_watchdog() to restart the timer. + * + * Returns: 1 on success; 0 otherwise. + */ + +int start_watchdog(const int auto_mode) { + int status; + + pthread_mutex_lock(&watchdog_lock); + + /* Don't start the watchdog again if it has already been started. */ + if (watchdog_dev != -1) { + while ((status = write(watchdog_dev, WATCHDOG_START_KEY, 1)) == 0 + && errno == EINTR); + pthread_mutex_unlock(&watchdog_lock); + syslog(LOG_ALERT, "system watchdog already started.\n"); + return 0; + } + + while (((watchdog_dev = open("/dev/watchdog", O_WRONLY)) == -1) && + errno == EINTR); + + /* Fail if watchdog device is invalid or if the user asked for auto + * mode and the thread failed to spawn. */ + if ((watchdog_dev == -1) || + (auto_mode == 1 && watchdog_started == 0 && + pthread_create(&watchdog_tid, NULL, watchdog_thread, NULL) != 0)) { + goto fail; + } + + while ((status = write(watchdog_dev, WATCHDOG_START_KEY, 1)) == 0 + && errno == EINTR); + pthread_mutex_unlock(&watchdog_lock); + syslog(LOG_INFO, "system watchdog started.\n"); + return 1; + +fail: + if (watchdog_dev != -1) { + close(watchdog_dev); + watchdog_dev = -1; + } + + pthread_mutex_unlock(&watchdog_lock); + syslog(LOG_ALERT, "system watchdog failed to start!\n"); + return 0; +} + +/* + * Toggles between watchdog persistent modes. In persistent mode, the watchdog + * timer will continue to tick even after process shutdown. Under non- + * persistent mode, the watchdog timer will automatically be disabled when the + * process shuts down. + */ +void set_persistent_watchdog(enum watchdog_persistent_en persistent) { + switch (persistent) { + case WATCHDOG_SET_PERSISTENT: + watchdog_kick_key = WATCHDOG_PERSISTENT_KEY; + break; + default: + watchdog_kick_key = WATCHDOG_NON_PERSISTENT_KEY; + break; + } + kick_watchdog(); +} + +/* + * Restarts the countdown timer on the watchdog, delaying restart by another + * timeout period (default: 11 seconds as configured in the device driver). + * + * This function assumes the watchdog lock has already been acquired and is + * only used internally within the watchdog code. + * + * Returns 1 on success; 0 or -1 indicates failure (check errno). + */ + +static int kick_watchdog_unsafe() { + int status = 0; + if (watchdog_dev != -1) { + while ((status = write(watchdog_dev, watchdog_kick_key, 1)) == 0 + && errno == EINTR); + } + return status; +} + +/* + * Acquires the watchdog lock and resets the watchdog atomically. For use by + * library users. + */ + +int kick_watchdog() { + int result; + pthread_mutex_lock(&watchdog_lock); + result = kick_watchdog_unsafe(); + pthread_mutex_unlock(&watchdog_lock); + + return result; +} + +/* Shuts down the watchdog gracefully and disables the watchdog timer so that + * restarts no longer happen. + */ + +void stop_watchdog() { + int status; + pthread_mutex_lock(&watchdog_lock); + if (watchdog_dev != -1) { + while ((status = write(watchdog_dev, WATCHDOG_STOP_KEY, 1)) == 0 + && errno == EINTR); + close(watchdog_dev); + watchdog_dev = -1; + syslog(LOG_INFO, "system watchdog stopped.\n"); + } + pthread_mutex_unlock(&watchdog_lock); +} diff --git a/common/recipes-core/fan-ctrl/fan-ctrl/watchdog.h b/common/recipes-core/fan-ctrl/fan-ctrl/watchdog.h new file mode 100644 index 0000000..19b9944 --- /dev/null +++ b/common/recipes-core/fan-ctrl/fan-ctrl/watchdog.h @@ -0,0 +1,60 @@ +/* + * watchdog + * + * Copyright 2014-present Facebook. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Utility library to handle the aspeed watchdog. Only one watchdog + * should be in use at any time throughout the entire system (multiple users + * will not cause adverse effects but the behavior of the watchdog becomes + * undefined). + * + * The watchdog can be started in either manual or automatic mode. In manual + * mode, the watchdog has to be constantly reset by the user via the + * kick_watchdog() function. Under automatic mode, the watchdog will + * run in a separate thread and reset the timer on its own so no intervention + * is required from the user. + * + * In both modes, the watchdog timer will not stop when the process is + * terminated, unless a call to stop_watchdog() has been made beforehand, or + * if the user runs in manual mode and uses a non persistent watchdog kick. + * + * The default timeout for the watchdog is 11 seconds. When this time period + * elapses, the ARM chip is restarted and the kernel is rebooted. Other + * hardware state is not reset, so this may introduce strange behavior on + * reboot (example: an I2C bus may be left in the open state, triggering + * constant interrupts). In rare cases, this could result in the kernel + * failing to fully restart itself and thus preclude the possibility of + * reinitializing the watchdog timer. Someone will then have to go over and + * physically restart the machine. + * + * The alternative to the soft reset is to request the watchdog device driver + * for a hard reset on timeout. However this will stop the fans. If the + * kernel fails to fully boot and restart the fan daemon, the system could + * overheat. For this reason, we've chosen to take the risk of a stuck soft + * reset instead. + * + */ + +/* Forward declarations. */ +int start_watchdog(const int auto_mode); +enum watchdog_persistent_en { + WATCHDOG_SET_PERSISTENT, + WATCHDOG_SET_NONPERSISTENT, +}; +void set_persistent_watchdog(enum watchdog_persistent_en persistent); +int kick_watchdog(); +void stop_watchdog(); diff --git a/common/recipes-core/fan-ctrl/fan-ctrl_0.1.bb b/common/recipes-core/fan-ctrl/fan-ctrl_0.1.bb new file mode 100644 index 0000000..1abfaaa --- /dev/null +++ b/common/recipes-core/fan-ctrl/fan-ctrl_0.1.bb @@ -0,0 +1,61 @@ +# Copyright 2014-present Facebook. All Rights Reserved. +# +# This program file is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program in a file named COPYING; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, +# Boston, MA 02110-1301 USA +SUMMARY = "Fan controller" +DESCRIPTION = "The utilities to control fan." +SECTION = "base" +PR = "r1" +LICENSE = "GPLv2" +LIC_FILES_CHKSUM = "file://fand.cpp;beginline=6;endline=18;md5=da35978751a9d71b73679307c4d296ec" + +SRC_URI = "file://README \ + file://Makefile \ + file://fand.cpp \ + file://watchdog.h \ + file://watchdog.cpp \ + " + +S = "${WORKDIR}" + +binfiles = "fand \ + " + +otherfiles = "README" + +pkgdir = "fan_ctrl" + +do_install() { + dst="${D}/usr/local/fbpackages/${pkgdir}" + bin="${D}/usr/local/bin" + install -d $dst + install -d $bin + for f in ${binfiles}; do + install -m 755 $f ${dst}/$f + ln -snf ../fbpackages/${pkgdir}/$f ${bin}/$f + done + for f in ${otherfiles}; do + install -m 644 $f ${dst}/$f + done +} + +FBPACKAGEDIR = "${prefix}/local/fbpackages" + +FILES_${PN} = "${FBPACKAGEDIR}/fan_ctrl ${prefix}/local/bin" + +# Inhibit complaints about .debug directories for the fand binary: + +INHIBIT_PACKAGE_DEBUG_SPLIT = "1" +INHIBIT_PACKAGE_STRIP = "1" diff --git a/common/recipes-core/fruid/files/Makefile b/common/recipes-core/fruid/files/Makefile new file mode 100644 index 0000000..5c6aff4 --- /dev/null +++ b/common/recipes-core/fruid/files/Makefile @@ -0,0 +1,26 @@ +# Copyright 2015-present Facebook. All Rights Reserved. +# +# This program file is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program in a file named COPYING; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, +# Boston, MA 02110-1301 USA + +all: fruid-util + +fruid: fruid-util.c + $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) + +.PHONY: clean + +clean: + rm -rf *.o fruid-util diff --git a/common/recipes-core/fruid/files/fruid-util.c b/common/recipes-core/fruid/files/fruid-util.c new file mode 100644 index 0000000..3c8d2e1 --- /dev/null +++ b/common/recipes-core/fruid/files/fruid-util.c @@ -0,0 +1,145 @@ +/* + * Copyright 2015-present Facebook. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <openbmc/fruid.h> +#include <openbmc/pal.h> + +/* Print the FRUID in detail */ +void print_fruid_info(fruid_info_t *fruid, const char *name) +{ + /* Print format */ + printf("%-27s: %s", "\nFRU Information", + name /* Name of the FRU device */ ); + printf("%-27s: %s", "\n---------------", "------------------"); + + if (fruid->chassis.flag) { + printf("%-27s: %s", "\nChassis Type",fruid->chassis.type_str); + printf("%-27s: %s", "\nChassis Part",fruid->chassis.part); + printf("%-27s: %s", "\nChassis Serial Number",fruid->chassis.serial); + printf("%-27s: %s", "\nChassis Custom Data",fruid->chassis.custom); + } + + if (fruid->board.flag) { + printf("%-27s: %s", "\nBoard Mfg Time",fruid->board.mfg_time_str); + printf("%-27s: %s", "\nBoard Manufacturer",fruid->board.mfg); + printf("%-27s: %s", "\nBoard Name",fruid->board.name); + printf("%-27s: %s", "\nBoard Serial Number",fruid->board.serial); + printf("%-27s: %s", "\nBoard Part",fruid->board.part); + printf("%-27s: %s", "\nBoard FRU ID",fruid->board.fruid); + printf("%-27s: %s", "\nBoard Custom Data",fruid->board.custom); + } + + if (fruid->product.flag) { + printf("%-27s: %s", "\nProduct Manufacturer",fruid->product.mfg); + printf("%-27s: %s", "\nProduct Name",fruid->product.name); + printf("%-27s: %s", "\nProduct Part",fruid->product.part); + printf("%-27s: %s", "\nProduct Version",fruid->product.version); + printf("%-27s: %s", "\nProduct Serial Number",fruid->product.serial); + printf("%-27s: %s", "\nProduct Asset Tag",fruid->product.asset_tag); + printf("%-27s: %s", "\nProduct FRU ID",fruid->product.fruid); + printf("%-27s: %s", "\nProduct Custom Data",fruid->product.custom); + } + + printf("\n"); +} + +/* Populate and print fruid_info by parsing the fru's binary dump */ +void get_fruid_info(uint8_t fru, char *path, char* name) { + int ret; + fruid_info_t fruid; + + ret = fruid_parse(path, &fruid); + if (ret) { + fprintf(stderr, "Failed print FRUID for %s\nCheck syslog for errors!\n", + name); + } else { + print_fruid_info(&fruid, name); + free_fruid_info(&fruid); + } + +} + +static int +print_usage() { + printf("Usage: fruid-util [ %s ]\n", pal_fru_list); +} + +/* Utility to just print the FRUID */ +int main(int argc, char * argv[]) { + + int ret; + uint8_t fru; + char path[64] = {0}; + char name[64] = {0}; + + if (argc != 2) { + print_usage(); + exit(-1); + } + + ret = pal_get_fru_id(argv[1], &fru); + if (ret < 0) { + print_usage(); + return ret; + } + + if (fru == 0) { + fru = 1; + while (fru <= MAX_NUM_FRUS) { + ret = pal_get_fruid_path(fru, path); + if (ret < 0) { + return ret; + } + + ret = pal_get_fruid_name(fru, name); + if (ret < 0) { + return ret; + } + + if (fru == FRU_NIC) { + printf("fruid-util does not support nic\n"); + exit(-1); + } + + get_fruid_info(fru, path, name); + + fru++; + } + } else { + ret = pal_get_fruid_path(fru, path); + if (ret < 0) { + return ret; + } + + ret = pal_get_fruid_name(fru, name); + if (ret < 0) { + return ret; + } + + if (fru == FRU_NIC) { + printf("fruid-util does not support nic\n"); + exit(-1); + } + + get_fruid_info(fru, path, name); + } + + return 0; +} diff --git a/common/recipes-core/fruid/fruid_0.1.bb b/common/recipes-core/fruid/fruid_0.1.bb new file mode 100644 index 0000000..604845f --- /dev/null +++ b/common/recipes-core/fruid/fruid_0.1.bb @@ -0,0 +1,55 @@ +# Copyright 2015-present Facebook. All Rights Reserved. +# +# This program file is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program in a file named COPYING; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, +# Boston, MA 02110-1301 USA + +SUMMARY = "IPMI FRUID Utilities" +DESCRIPTION = "Util for ipmi fruid" +SECTION = "base" +PR = "r1" +LICENSE = "GPLv2" +LIC_FILES_CHKSUM = "file://fruid-util.c;beginline=4;endline=16;md5=da35978751a9d71b73679307c4d296ec" + +LDFLAGS = " -lfruid -lpal " +DEPENDS = "libfruid libpal " + +SRC_URI = "file://Makefile \ + file://fruid-util.c \ + " + +S = "${WORKDIR}" + +binfiles = "fruid-util \ + " + +pkgdir = "fruid" + +do_install() { + dst="${D}/usr/local/fbpackages/${pkgdir}" + bin="${D}/usr/local/bin" + install -d $dst + install -d $bin + for f in ${binfiles}; do + install -m 755 $f ${dst}/$f + ln -snf ../fbpackages/${pkgdir}/$f ${bin}/$f + done +} + +FBPACKAGEDIR = "${prefix}/local/fbpackages" + +FILES_${PN} = "${FBPACKAGEDIR}/fruid ${prefix}/local/bin" + +INHIBIT_PACKAGE_DEBUG_SPLIT = "1" +INHIBIT_PACKAGE_STRIP = "1" diff --git a/common/recipes-core/ipmbd/files/Makefile b/common/recipes-core/ipmbd/files/Makefile new file mode 100644 index 0000000..719ccc3 --- /dev/null +++ b/common/recipes-core/ipmbd/files/Makefile @@ -0,0 +1,10 @@ +# Copyright 2015-present Facebook. All Rights Reserved. +all: ipmbd + +ipmbd: ipmbd.o + $(CC) $(CFLAGS) -pthread -lrt -lipmi -std=gnu99 -o $@ $^ $(LDFLAGS) + +.PHONY: clean + +clean: + rm -rf *.o ipmbd diff --git a/common/recipes-core/ipmbd/files/ipmbd.c b/common/recipes-core/ipmbd/files/ipmbd.c new file mode 100644 index 0000000..5ea5af3 --- /dev/null +++ b/common/recipes-core/ipmbd/files/ipmbd.c @@ -0,0 +1,843 @@ +/* + * Copyright 2015-present Facebook. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * This module handles all the IPMB communication protocol + * Refer http://www.intel.com/content/www/us/en/servers/ipmi/ipmp-spec-v1-0.html + * for more information. + * + * IPMB packet format is described here for quick reference + * Request: + * <Responder Slave Address(rsSA)> + * <NetFn/ResponderLUN(Netfn/rsLUN)> + * <Header Checksum(hdrCksum)> + * <Requester Slave Address(rqSA)> + * <Requester Sequence Number/RequesterLUN(rqSeq/rqLUN> + * <Command> + * <Data[0..n]> + * <Data Checksum(dataCksum)> + * Response: + * <Requester Slave Address(rqSA)> + * <NetFn/RequesterLUN(Netfn/rqLUN)> + * <Header Checksum(hdrCksum)> + * <Responder Slave Address(rsSA)> + * <Requester Sequence Number/ResponderLUN(rqSeq/rsLUN> + * <Command> + * <Completion Code(CC)> + * <Data[0..n]> + * <Data Checksum(dataCksum)> + */ +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <syslog.h> +#include <pthread.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> +#include <stdint.h> +#include <mqueue.h> +#include <semaphore.h> + +#include "facebook/i2c-dev.h" +#include "openbmc/ipmi.h" +#include "openbmc/ipmb.h" + +//#define DEBUG 0 + +#define MAX_BYTES 255 + +#define MQ_IPMB_REQ "/mq_ipmb_req" +#define MQ_IPMB_RES "/mq_ipmb_res" +#define MQ_MAX_MSG_SIZE MAX_BYTES +#define MQ_MAX_NUM_MSGS 10 + +#define SEQ_NUM_MAX 64 + +#define I2C_RETRIES_MAX 3 + +// Structure for i2c file descriptor and socket +typedef struct _ipmb_sfd_t { + int fd; + int sock; +} ipmb_sfd_t; + +// Structure for sequence number and buffer +typedef struct _seq_buf_t { + bool in_use; // seq# is being used + uint8_t len; // buffer size + uint8_t *p_buf; // pointer to buffer + sem_t s_seq; // semaphore for thread sync. +} seq_buf_t; + +// Structure for holding currently used sequence number and +// array of all possible sequence number +typedef struct _ipmb_sbuf_t { + uint8_t curr_seq; // currently used seq# + seq_buf_t seq[SEQ_NUM_MAX]; //array of all possible seq# struct. +} ipmb_sbuf_t; + +// Global storage for holding IPMB sequence number and buffer +ipmb_sbuf_t g_seq; + +// mutex to protect global data access +pthread_mutex_t m_seq; + +pthread_mutex_t m_i2c; + +#ifdef CONFIG_YOSEMITE +// Returns the payload ID from IPMB bus routing +// Slot#1: bus#3, Slot#2: bus#1, Slot#3: bus#7, Slot#4: bus#5 +static uint8_t +get_payload_id(uint8_t bus_id) { + uint8_t payload_id = 0xFF; // Invalid payload ID + + switch(bus_id) { + case 1: + payload_id = 2; + break; + case 3: + payload_id = 1; + break; + case 5: + payload_id = 4; + break; + case 7: + payload_id = 3; + break; + default: + syslog(LOG_ALERT, "get_payload_id: Wrong bus ID\n"); + break; + } + + return payload_id; +} +#endif + +// Returns an unused seq# from all possible seq# +static uint8_t +seq_get_new(void) { + uint8_t ret = -1; + uint8_t index; + + pthread_mutex_lock(&m_seq); + + // Search for unused sequence number + index = g_seq.curr_seq; + do { + if (g_seq.seq[index].in_use == false) { + // Found it! + ret = index; + g_seq.seq[index].in_use = true; + g_seq.seq[index].len = 0; + break; + } + + if (++index == SEQ_NUM_MAX) { + index = 0; + } + } while (index != g_seq.curr_seq); + + // Update the current seq num + if (ret >= 0) { + if (++index == SEQ_NUM_MAX) { + index = 0; + } + g_seq.curr_seq = index; + } + + pthread_mutex_unlock(&m_seq); + + return ret; +} + +static int +i2c_open(uint8_t bus_num) { + int fd; + char fn[32]; + int rc; + + snprintf(fn, sizeof(fn), "/dev/i2c-%d", bus_num); + fd = open(fn, O_RDWR); + if (fd == -1) { + syslog(LOG_ALERT, "Failed to open i2c device %s", fn); + return -1; + } + + rc = ioctl(fd, I2C_SLAVE, BRIDGE_SLAVE_ADDR); + if (rc < 0) { + syslog(LOG_ALERT, "Failed to open slave @ address 0x%x", BRIDGE_SLAVE_ADDR); + close(fd); + return -1; + } + + return fd; +} + +static int +i2c_write(int fd, uint8_t *buf, uint8_t len) { + struct i2c_rdwr_ioctl_data data; + struct i2c_msg msg; + int rc; + int i; + + memset(&msg, 0, sizeof(msg)); + + msg.addr = BRIDGE_SLAVE_ADDR; + msg.flags = 0; + msg.len = len; + msg.buf = buf; + + data.msgs = &msg; + data.nmsgs = 1; + + pthread_mutex_lock(&m_i2c); + + for (i = 0; i < I2C_RETRIES_MAX; i++) { + rc = ioctl(fd, I2C_RDWR, &data); + if (rc < 0) { + sleep(1); + continue; + } else { + break; + } + } + + if (rc < 0) { + syslog(LOG_ALERT, "Failed to do raw io"); + pthread_mutex_unlock(&m_i2c); + return -1; + } + + pthread_mutex_unlock(&m_i2c); + + return 0; +} + +static int +i2c_slave_open(uint8_t bus_num) { + int fd; + char fn[32]; + int rc; + struct i2c_rdwr_ioctl_data data; + struct i2c_msg msg; + uint8_t read_bytes[MAX_BYTES] = { 0 }; + + snprintf(fn, sizeof(fn), "/dev/i2c-%d", bus_num); + fd = open(fn, O_RDWR); + if (fd == -1) { + syslog(LOG_ALERT, "Failed to open i2c device %s", fn); + return -1; + } + + memset(&msg, 0, sizeof(msg)); + + msg.addr = BMC_SLAVE_ADDR; + msg.flags = I2C_S_EN; + msg.len = 1; + msg.buf = read_bytes; + msg.buf[0] = 1; + + data.msgs = &msg; + data.nmsgs = 1; + + rc = ioctl(fd, I2C_SLAVE_RDWR, &data); + if (rc < 0) { + syslog(LOG_ALERT, "Failed to open slave @ address 0x%x", BMC_SLAVE_ADDR); + close(fd); + } + + return fd; +} + +static int +i2c_slave_read(int fd, uint8_t *buf, uint8_t *len) { + struct i2c_rdwr_ioctl_data data; + struct i2c_msg msg; + int rc; + + memset(&msg, 0, sizeof(msg)); + + msg.addr = BMC_SLAVE_ADDR; + msg.flags = 0; + msg.len = MAX_BYTES; + msg.buf = buf; + + data.msgs = &msg; + data.nmsgs = 1; + + rc = ioctl(fd, I2C_SLAVE_RDWR, &data); + if (rc < 0) { + return -1; + } + + *len = msg.len; + + return 0; +} + +// Thread to handle new requests +static void* +ipmb_req_handler(void *bus_num) { + uint8_t *bnum = (uint8_t*) bus_num; + mqd_t mq; + int fd; + int i; + + //Buffers for IPMB transport + uint8_t rxbuf[MQ_MAX_MSG_SIZE] = {0}; + uint8_t txbuf[MQ_MAX_MSG_SIZE] = {0}; + ipmb_req_t *p_ipmb_req; + ipmb_res_t *p_ipmb_res; + + p_ipmb_req = (ipmb_req_t*) rxbuf; + p_ipmb_res = (ipmb_res_t*) txbuf; + + //Buffers for IPMI Stack + uint8_t rbuf[MQ_MAX_MSG_SIZE] = {0}; + uint8_t tbuf[MQ_MAX_MSG_SIZE] = {0}; + ipmi_mn_req_t *p_ipmi_mn_req; + ipmi_res_t *p_ipmi_res; + + p_ipmi_mn_req = (ipmi_mn_req_t*) rbuf; + p_ipmi_res = (ipmi_res_t*) tbuf; + + uint8_t rlen = 0; + uint8_t tlen = 0; + + char mq_ipmb_req[64] = {0}; + + sprintf(mq_ipmb_req, "%s_%d", MQ_IPMB_REQ, *bnum); + + // Open Queue to receive requests + mq = mq_open(mq_ipmb_req, O_RDONLY); + if (mq == (mqd_t) -1) { + return NULL; + } + + // Open the i2c bus for sending response + fd = i2c_open(*bnum); + if (fd < 0) { + syslog(LOG_ALERT, "i2c_open failure\n"); + close(mq); + return NULL; + } + + // Loop to process incoming requests + while (1) { + if ((rlen = mq_receive(mq, rxbuf, MQ_MAX_MSG_SIZE, NULL)) < 0) { + sleep(1); + continue; + } + +#ifdef DEBUG + syslog(LOG_ALERT, "Received Request of %d bytes\n", rlen); + for (i = 0; i < rlen; i++) { + syslog(LOG_ALERT, "0x%X", rxbuf[i]); + } +#endif + + // Create IPMI request from IPMB data +#ifdef CONFIG_YOSEMITE + p_ipmi_mn_req->payload_id = get_payload_id(*bnum); +#else + // For single node systems use payload ID as 1 + p_ipmi_mn_req->payload_id = 0x1; +#endif + p_ipmi_mn_req->netfn_lun = p_ipmb_req->netfn_lun; + p_ipmi_mn_req->cmd = p_ipmb_req->cmd; + + memcpy(p_ipmi_mn_req->data, p_ipmb_req->data, rlen - IPMB_HDR_SIZE - IPMI_REQ_HDR_SIZE); + + // Send to IPMI stack and get response + // Additional byte as we are adding and passing payload ID for MN support + lib_ipmi_handle(rbuf, rlen - IPMB_HDR_SIZE + 1, tbuf, &tlen); + + // Populate IPMB response data from IPMB request + p_ipmb_res->req_slave_addr = p_ipmb_req->req_slave_addr; + p_ipmb_res->res_slave_addr = p_ipmb_req->res_slave_addr; + p_ipmb_res->cmd = p_ipmb_req->cmd; + p_ipmb_res->seq_lun = p_ipmb_req->seq_lun; + + // Add IPMI response data + p_ipmb_res->netfn_lun = p_ipmi_res->netfn_lun; + p_ipmb_res->cc = p_ipmi_res->cc; + + memcpy(p_ipmb_res->data, p_ipmi_res->data, tlen - IPMI_RESP_HDR_SIZE); + + // Calculate Header Checksum + p_ipmb_res->hdr_cksum = p_ipmb_res->req_slave_addr + + p_ipmb_res->netfn_lun; + p_ipmb_res->hdr_cksum = ZERO_CKSUM_CONST - p_ipmb_res->hdr_cksum; + + // Calculate Data Checksum + p_ipmb_res->data[tlen-IPMI_RESP_HDR_SIZE] = p_ipmb_res->res_slave_addr + + p_ipmb_res->seq_lun + + p_ipmb_res->cmd + + p_ipmb_res->cc; + + for (i = 0; i < tlen-IPMI_RESP_HDR_SIZE; i++) { + p_ipmb_res->data[tlen-IPMI_RESP_HDR_SIZE] += p_ipmb_res->data[i]; + } + + p_ipmb_res->data[tlen-IPMI_RESP_HDR_SIZE] = ZERO_CKSUM_CONST - + p_ipmb_res->data[tlen-IPMI_RESP_HDR_SIZE]; + +#ifdef DEBUG + syslog(LOG_ALERT, "Sending Response of %d bytes\n", tlen+IPMB_HDR_SIZE-1); + for (i = 1; i < tlen+IPMB_HDR_SIZE; i++) { + syslog(LOG_ALERT, "0x%X:", txbuf[i]); + } +#endif + + // Send response back + i2c_write(fd, &txbuf[1], tlen+IPMB_HDR_SIZE-1); + } +} + +// Thread to handle the incoming responses +static void* +ipmb_res_handler(void *bus_num) { + uint8_t *bnum = (uint8_t*) bus_num; + uint8_t buf[MQ_MAX_MSG_SIZE] = { 0 }; + uint8_t len = 0; + mqd_t mq; + ipmb_res_t *p_res; + uint8_t index; + char mq_ipmb_res[64] = {0}; + + sprintf(mq_ipmb_res, "%s_%d", MQ_IPMB_RES, *bnum); + + // Open the message queue + mq = mq_open(mq_ipmb_res, O_RDONLY); + if (mq == (mqd_t) -1) { + syslog(LOG_ALERT, "mq_open fails\n"); + return NULL; + } + + // Loop to wait for incomng response messages + while (1) { + if ((len = mq_receive(mq, buf, MQ_MAX_MSG_SIZE, NULL)) < 0) { + sleep(1); + continue; + } + + p_res = (ipmb_res_t *) buf; + + // Check the seq# of response + index = p_res->seq_lun >> LUN_OFFSET; + + // Check if the response is being waited for + pthread_mutex_lock(&m_seq); + if (g_seq.seq[index].in_use) { + // Copy the response to the requester's buffer + memcpy(g_seq.seq[index].p_buf, buf, len); + g_seq.seq[index].len = len; + + // Wake up the worker thread to receive the response + sem_post(&g_seq.seq[index].s_seq); + } + pthread_mutex_unlock(&m_seq); + +#ifdef DEBUG + syslog(LOG_ALERT, "Received Response of %d bytes\n", len); + int i; + for (i = 0; i < len; i++) { + syslog(LOG_ALERT, "0x%X:", buf[i]); + } +#endif + } +} + +// Thread to receive the IPMB messages over i2c bus as a slave +static void* +ipmb_rx_handler(void *bus_num) { + uint8_t *bnum = (uint8_t*) bus_num; + int fd; + uint8_t len; + uint8_t tlun; + uint8_t buf[MAX_BYTES] = { 0 }; + mqd_t mq_req, mq_res, tmq; + ipmb_req_t *p_req; + struct timespec req; + struct timespec rem; + char mq_ipmb_req[64] = {0}; + char mq_ipmb_res[64] = {0}; + + // Setup wait time + req.tv_sec = 0; + req.tv_nsec = 10000000;//10mSec + + // Open the i2c bus as a slave + fd = i2c_slave_open(*bnum); + if (fd < 0) { + syslog(LOG_ALERT, "i2c_slave_open fails\n"); + goto cleanup; + } + + sprintf(mq_ipmb_req, "%s_%d", MQ_IPMB_REQ, *bnum); + sprintf(mq_ipmb_res, "%s_%d", MQ_IPMB_RES, *bnum); + + // Open the message queues for post processing + mq_req = mq_open(mq_ipmb_req, O_WRONLY); + if (mq_req == (mqd_t) -1) { + syslog(LOG_ALERT, "mq_open req fails\n"); + goto cleanup; + } + + mq_res = mq_open(mq_ipmb_res, O_WRONLY); + if (mq_res == (mqd_t) -1) { + syslog(LOG_ALERT, "mq_open res fails\n"); + goto cleanup; + } + + // Loop that retrieves messages + while (1) { + // Read messages from i2c driver + if (i2c_slave_read(fd, buf, &len) < 0) { + nanosleep(&req, &rem); + continue; + } + + // Check if the messages is request or response + // Even NetFn: Request, Odd NetFn: Response + p_req = (ipmb_req_t*) buf; + tlun = p_req->netfn_lun >> LUN_OFFSET; + if (tlun%2) { + tmq = mq_res; + } else { + tmq = mq_req; + } + // Post message to approriate Queue for further processing + if (mq_send(tmq, buf, len, 0)) { + syslog(LOG_ALERT, "mq_send failed\n"); + sleep(1); + continue; + } + } + +cleanup: + if (fd > 0) { + close (fd); + } + + if (mq_req > 0) { + close(mq_req); + } + + if (mq_res > 0) { + close(mq_req); + } +} + +/* + * Function to handle all IPMB requests + */ +static void +ipmb_handle (int fd, unsigned char *request, unsigned char req_len, + unsigned char *response, unsigned char *res_len) +{ + ipmb_req_t *req = (ipmb_req_t *) request; + ipmb_res_t *res = (ipmb_res_t *) response; + + uint8_t index; + struct timespec ts; + + // Allocate right sequence Number + index = seq_get_new(); + if (index < 0) { + *res_len = 0; + return ; + } + + req->seq_lun = index << LUN_OFFSET; + + // Calculate/update dataCksum + // Note: dataCkSum byte is last byte + int i; + for (i = IPMB_DATA_OFFSET; i < req_len-1; i++) { + request[req_len-1] += request[i]; + } + + request[req_len-1] = ZERO_CKSUM_CONST - request[req_len-1]; + + // Setup response buffer + pthread_mutex_lock(&m_seq); + g_seq.seq[index].p_buf = response; + pthread_mutex_unlock(&m_seq); + + // Send request over i2c bus + // Note: Need not send first byte SlaveAddress automatically added by driver + if (i2c_write(fd, &request[1], req_len-1)) { + goto ipmb_handle_out; + } + + // Wait on semaphore for that sequence Number + clock_gettime(CLOCK_REALTIME, &ts); + + ts.tv_sec += TIMEOUT_IPMI; + + int ret; + ret = sem_timedwait(&g_seq.seq[index].s_seq, &ts); + if (ret == -1) { + syslog(LOG_ALERT, "No response for sequence number: %d\n", index); + *res_len = 0; + } + +ipmb_handle_out: + // Reply to user with data + pthread_mutex_lock(&m_seq); + *res_len = g_seq.seq[index].len; + + g_seq.seq[index].in_use = false; + pthread_mutex_unlock(&m_seq); + + return; +} + +void +*conn_handler(void *sfd) { + ipmb_sfd_t *p_sfd = (ipmb_sfd_t *) sfd; + + int sock = p_sfd->sock; + int fd = p_sfd->fd; + int n; + unsigned char req_buf[MAX_IPMI_MSG_SIZE]; + unsigned char res_buf[MAX_IPMI_MSG_SIZE]; + unsigned char res_len = 0; + + n = recv(sock, req_buf, sizeof(req_buf), 0); + if (n <= 0) { + syslog(LOG_ALERT, "ipmbd: recv() failed with %d\n", n); + goto conn_cleanup; + } + + ipmb_handle(fd, req_buf, n, res_buf, &res_len); + + if (send(sock, res_buf, res_len, MSG_NOSIGNAL) < 0) { + syslog(LOG_ALERT, "ipmbd: send() failed\n"); + } + +conn_cleanup: + close(sock); + free(p_sfd); + + pthread_exit(NULL); + return 0; +} + +// Thread to receive the IPMB lib messages from various apps +static void* +ipmb_lib_handler(void *bus_num) { + int s, s2, t, len; + struct sockaddr_un local, remote; + pthread_t tid; + ipmb_sfd_t *sfd; + int fd; + uint8_t *bnum = (uint8_t*) bus_num; + char sock_path[20] = {0}; + + // Open the i2c bus for sending request + fd = i2c_open(*bnum); + if (fd < 0) { + syslog(LOG_ALERT, "i2c_open failure\n"); + return NULL; + } + + // Initialize g_seq structure + int i; + for (i = 0; i < SEQ_NUM_MAX; i++) { + g_seq.seq[i].in_use = false; + sem_init(&g_seq.seq[i].s_seq, 0, 0); + g_seq.seq[i].len = 0; + } + + // Initialize mutex to access global structure + pthread_mutex_init(&m_seq, NULL); + + if ((s = socket (AF_UNIX, SOCK_STREAM, 0)) == -1) + { + syslog(LOG_ALERT, "ipmbd: socket() failed\n"); + exit (1); + } + + sprintf(sock_path, "%s_%d", SOCK_PATH_IPMB, *bnum); + + local.sun_family = AF_UNIX; + strcpy (local.sun_path, sock_path); + unlink (local.sun_path); + len = strlen (local.sun_path) + sizeof (local.sun_family); + if (bind (s, (struct sockaddr *) &local, len) == -1) + { + syslog(LOG_ALERT, "ipmbd: bind() failed\n"); + exit (1); + } + + if (listen (s, 5) == -1) + { + syslog(LOG_ALERT, "ipmbd: listen() failed\n"); + exit (1); + } + + while(1) { + int n; + t = sizeof (remote); + if ((s2 = accept (s, (struct sockaddr *) &remote, &t)) < 0) { + syslog(LOG_ALERT, "ipmbd: accept() failed\n"); + break; + } + + // Creating a worker thread to handle the request + // TODO: Need to monitor the server performance with higher load and + // see if we need to create pre-defined number of workers and schedule + // the requests among them. + sfd = (ipmb_sfd_t *) malloc(sizeof(ipmb_sfd_t)); + sfd->fd = fd; + sfd->sock = s2; + if (pthread_create(&tid, NULL, conn_handler, (void*) sfd) < 0) { + syslog(LOG_ALERT, "ipmbd: pthread_create failed\n"); + close(s2); + continue; + } + + pthread_detach(tid); + } + + close(s); + pthread_mutex_destroy(&m_seq); + + return 0; +} + +int +main(int argc, char * const argv[]) { + pthread_t tid_ipmb_rx; + pthread_t tid_req_handler; + pthread_t tid_res_handler; + pthread_t tid_lib_handler; + uint8_t ipmb_bus_num; + mqd_t mqd_req, mqd_res; + struct mq_attr attr; + char mq_ipmb_req[64] = {0}; + char mq_ipmb_res[64] = {0}; + + daemon(1, 0); + openlog("ipmbd", LOG_CONS, LOG_DAEMON); + + if (argc != 2) { + syslog(LOG_ALERT, "ipmbd: Usage: ipmbd <bus#>"); + exit(1); + } + + ipmb_bus_num = atoi(argv[1]); +syslog(LOG_ALERT, "ipmbd: bus#:%d\n", ipmb_bus_num); + + pthread_mutex_init(&m_i2c, NULL); + + // Create Message Queues for Request Messages and Response Messages + attr.mq_flags = 0; + attr.mq_maxmsg = MQ_MAX_NUM_MSGS; + attr.mq_msgsize = MQ_MAX_MSG_SIZE; + attr.mq_curmsgs = 0; + + sprintf(mq_ipmb_req, "%s_%d", MQ_IPMB_REQ, ipmb_bus_num); + sprintf(mq_ipmb_res, "%s_%d", MQ_IPMB_RES, ipmb_bus_num); + + // Remove the MQ if exists + mq_unlink(mq_ipmb_req); + + errno = 0; + mqd_req = mq_open(mq_ipmb_req, O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH, &attr); + if (mqd_req == (mqd_t) -1) { + syslog(LOG_ALERT, "ipmbd: mq_open request failed errno:%d\n", errno); + goto cleanup; + } + + // Remove the MQ if exists + mq_unlink(mq_ipmb_res); + + errno = 0; + mqd_res = mq_open(mq_ipmb_res, O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH, &attr); + if (mqd_res == (mqd_t) -1) { + syslog(LOG_ALERT, "ipmbd: mq_open response failed errno: %d\n", errno); + goto cleanup; + } + + // Create thread to handle IPMB Requests + if (pthread_create(&tid_req_handler, NULL, ipmb_req_handler, (void*) &ipmb_bus_num) < 0) { + syslog(LOG_ALERT, "ipmbd: pthread_create failed\n"); + goto cleanup; + } + + // Create thread to handle IPMB Responses + if (pthread_create(&tid_res_handler, NULL, ipmb_res_handler, (void*) &ipmb_bus_num) < 0) { + syslog(LOG_ALERT, "ipmbd: pthread_create failed\n"); + goto cleanup; + } + + // Create thread to retrieve ipmb traffic from i2c bus as slave + if (pthread_create(&tid_ipmb_rx, NULL, ipmb_rx_handler, (void*) &ipmb_bus_num) < 0) { + syslog(LOG_ALERT, "ipmbd: pthread_create failed\n"); + goto cleanup; + } + + // Create thread to receive ipmb library requests from apps + if (pthread_create(&tid_lib_handler, NULL, ipmb_lib_handler, (void*) &ipmb_bus_num) < 0) { + syslog(LOG_ALERT, "ipmbd: pthread_create failed\n"); + goto cleanup; + } + +cleanup: + if (tid_ipmb_rx > 0) { + pthread_join(tid_ipmb_rx, NULL); + } + + if (tid_req_handler > 0) { + pthread_join(tid_req_handler, NULL); + } + + if (tid_res_handler > 0) { + pthread_join(tid_res_handler, NULL); + } + + if (tid_lib_handler > 0) { + pthread_join(tid_lib_handler, NULL); + } + + if (mqd_res > 0) { + mq_close(mqd_res); + mq_unlink(mq_ipmb_res); + } + + if (mqd_req > 0) { + mq_close(mqd_req); + mq_unlink(mq_ipmb_req); + } + + pthread_mutex_destroy(&m_i2c); + + return 0; +} diff --git a/common/recipes-core/ipmbd/ipmbd_0.1.bb b/common/recipes-core/ipmbd/ipmbd_0.1.bb new file mode 100644 index 0000000..f5724b0 --- /dev/null +++ b/common/recipes-core/ipmbd/ipmbd_0.1.bb @@ -0,0 +1,19 @@ +# Copyright 2015-present Facebook. All Rights Reserved. + +SUMMARY = "ipmbd tx/rx daemon" +DESCRIPTION = "The ipmb daemon to receive/transmit messages" +SECTION = "base" +PR = "r2" +LICENSE = "GPLv2" +LIC_FILES_CHKSUM = "file://ipmbd.c;beginline=4;endline=16;md5=da35978751a9d71b73679307c4d296ec" + +SRC_URI = "file://Makefile \ + file://ipmbd.c \ + " + +S = "${WORKDIR}" +DEPENDS += "libipmi libipmb" + +binfiles = "ipmbd" + +pkgdir = "ipmbd" diff --git a/common/recipes-core/ipmid/files/Makefile b/common/recipes-core/ipmid/files/Makefile new file mode 100644 index 0000000..4f0db3d --- /dev/null +++ b/common/recipes-core/ipmid/files/Makefile @@ -0,0 +1,29 @@ +# Copyright 2014-present Facebook. All Rights Reserved. +# +# This program file is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program in a file named COPYING; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, +# Boston, MA 02110-1301 USA + +C_SRCS := $(wildcard *.c) +C_OBJS := ${C_SRCS:.c=.o} + +all: ipmid + +ipmid: $(C_OBJS) + $(CC) -pthread -lpal -std=c99 -o $@ $^ $(LDFLAGS) + +.PHONY: clean + +clean: + rm -rf *.o ipmid diff --git a/common/recipes-core/ipmid/files/fruid.h b/common/recipes-core/ipmid/files/fruid.h new file mode 100644 index 0000000..3580b08 --- /dev/null +++ b/common/recipes-core/ipmid/files/fruid.h @@ -0,0 +1,28 @@ +/* + * + * Copyright 2015-present Facebook. All Rights Reserved. + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __FRUID_H__ +#define __FRUID_H__ + +int plat_fruid_size(void); +int plat_fruid_data(int offset, int count, unsigned char *data); +int plat_fruid_init(void); + +#endif /* __FRUID_H__ */ diff --git a/common/recipes-core/ipmid/files/ipmid.c b/common/recipes-core/ipmid/files/ipmid.c new file mode 100644 index 0000000..c79d3e2 --- /dev/null +++ b/common/recipes-core/ipmid/files/ipmid.c @@ -0,0 +1,1412 @@ +/* + * + * Copyright 2014-present Facebook. All Rights Reserved. + * + * This file contains code to support IPMI2.0 Specificaton available @ + * http://www.intel.com/content/www/us/en/servers/ipmi/ipmi-specifications.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "sdr.h" +#include "sel.h" +#include "fruid.h" +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <syslog.h> +#include <string.h> +#include <pthread.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <openbmc/ipmi.h> + +// TODO: Once data storage is finalized, the following structure needs +// to be retrieved/updated from persistant backend storage +static lan_config_t g_lan_config = { 0 }; +static proc_info_t g_proc_info = { 0 }; +static dimm_info_t g_dimm_info[MAX_NUM_DIMMS] = { 0 }; + +// TODO: Need to store this info after identifying proper storage +static sys_info_param_t g_sys_info_params; + +// TODO: Based on performance testing results, might need fine grained locks +// Since the global data is specific to a NetFunction, adding locs at NetFn level +static pthread_mutex_t m_chassis; +static pthread_mutex_t m_app; +static pthread_mutex_t m_storage; +static pthread_mutex_t m_transport; +static pthread_mutex_t m_oem; +static pthread_mutex_t m_oem_1s; + +static void ipmi_handle(unsigned char *request, unsigned char req_len, + unsigned char *response, unsigned char *res_len); + +/* + * Function(s) to handle IPMI messages with NetFn: Chassis + */ +// Get Chassis Status (IPMI/Section 28.2) +static void +chassis_get_status (unsigned char *response, unsigned char *res_len) +{ + ipmi_res_t *res = (ipmi_res_t *) response; + unsigned char *data = &res->data[0]; + + res->cc = CC_SUCCESS; + + // TODO: Need to obtain current power state and last power event + // from platform and return + *data++ = 0x01; // Current Power State + *data++ = 0x00; // Last Power Event + *data++ = 0x40; // Misc. Chassis Status + *data++ = 0x00; // Front Panel Button Disable + + res_len = data - &res->data[0]; +} + +// Get System Boot Options (IPMI/Section 28.12) +static void +chassis_get_boot_options (unsigned char *request, unsigned char *response, + unsigned char *res_len) +{ + ipmi_mn_req_t *req = (ipmi_mn_req_t *) request; + ipmi_res_t *res= (ipmi_res_t *) response; + unsigned char *data = &res->data[0]; + unsigned char param = req->data[0]; + + // Fill response with default values + res->cc = CC_SUCCESS; + *data++ = 0x01; // Parameter Version + *data++ = req->data[0]; // Parameter + + // TODO: Need to store user settings and return + switch (param) + { + case PARAM_SET_IN_PROG: + *data++ = 0x00; // Set In Progress + break; + case PARAM_SVC_PART_SELECT: + *data++ = 0x00; // Service Partition Selector + break; + case PARAM_SVC_PART_SCAN: + *data++ = 0x00; // Service Partition Scan + break; + case PARAM_BOOT_FLAG_CLR: + *data++ = 0x00; // BMC Boot Flag Valid Bit Clear + break; + case PARAM_BOOT_INFO_ACK: + *data++ = 0x00; // Write Mask + *data++ = 0x00; // Boot Initiator Ack Data + break; + case PARAM_BOOT_FLAGS: + *data++ = 0x00; // Boot Flags + *data++ = 0x00; // Boot Device Selector + *data++ = 0x00; // Firmwaer Verbosity + *data++ = 0x00; // BIOS Override + *data++ = 0x00; // Device Instance Selector + break; + case PARAM_BOOT_INIT_INFO: + *data++ = 0x00; // Chanel Number + *data++ = 0x00; // Session ID (4 bytes) + *data++ = 0x00; + *data++ = 0x00; + *data++ = 0x00; + *data++ = 0x00; // Boot Info Timestamp (4 bytes) + *data++ = 0x00; + *data++ = 0x00; + *data++ = 0x00; + break; + deault: + res->cc = CC_PARAM_OUT_OF_RANGE; + break; + } + + if (res->cc == CC_SUCCESS) { + *res_len = data - &res->data[0]; + } +} + +// Handle Chassis Commands (IPMI/Section 28) +static void +ipmi_handle_chassis (unsigned char *request, unsigned char req_len, + unsigned char *response, unsigned char *res_len) +{ + ipmi_mn_req_t *req = (ipmi_mn_req_t *) request; + ipmi_res_t *res = (ipmi_res_t *) response; + unsigned char cmd = req->cmd; + + pthread_mutex_lock(&m_chassis); + switch (cmd) + { + case CMD_CHASSIS_GET_STATUS: + chassis_get_status (response, res_len); + break; + case CMD_CHASSIS_GET_BOOT_OPTIONS: + chassis_get_boot_options (request, response, res_len); + break; + default: + res->cc = CC_INVALID_CMD; + break; + } + pthread_mutex_unlock(&m_chassis); +} + +/* + * Function(s) to handle IPMI messages with NetFn: Application + */ +// Get Device ID (IPMI/Section 20.1) +static void +app_get_device_id (unsigned char *response, unsigned char *res_len) +{ + + ipmi_res_t *res = (ipmi_res_t *) response; + unsigned char *data = &res->data[0]; + + res->cc = CC_SUCCESS; + + //TODO: Following data needs to be updated based on platform + *data++ = 0x20; // Device ID + *data++ = 0x81; // Device Revision + *data++ = 0x00; // Firmware Revision Major + *data++ = 0x09; // Firmware Revision Minor + *data++ = 0x02; // IPMI Version + *data++ = 0xBF; // Additional Device Support + *data++ = 0x15; // Manufacturer ID1 + *data++ = 0xA0; // Manufacturer ID2 + *data++ = 0x00; // Manufacturer ID3 + *data++ = 0x46; // Product ID1 + *data++ = 0x31; // Product ID2 + *data++ = 0x00; // Aux. Firmware Version1 + *data++ = 0x00; // Aux. Firmware Version2 + *data++ = 0x00; // Aux. Firmware Version3 + *data++ = 0x00; // Aux. Firmware Version4 + + *res_len = data - &res->data[0]; +} + +// Get Self Test Results (IPMI/Section 20.4) +static void +app_get_selftest_results (unsigned char *response, unsigned char *res_len) +{ + + ipmi_res_t *res = (ipmi_res_t *) response; + unsigned char *data = &res->data[0]; + + res->cc = CC_SUCCESS; + + //TODO: Following data needs to be updated based on self-test results + *data++ = 0x55; // Self-Test result + *data++ = 0x00; // Extra error info in case of failure + + *res_len = data - &res->data[0]; +} + +// Get Device GUID (IPMI/Section 20.8) +static void +app_get_device_guid (unsigned char *response, unsigned char *res_len) +{ + ipmi_res_t *res = (ipmi_res_t *) response; + unsigned char *data = &res->data[0]; + + res->cc = 0x00; + + // TODO: Following data is Globaly Unique ID i.e. MAC Address.. + *data++ = 0x0; + *data++ = 0x1; + *data++ = 0x2; + *data++ = 0x3; + *data++ = 0x4; + *data++ = 0x5; + *data++ = 0x6; + *data++ = 0x7; + *data++ = 0x8; + *data++ = 0x9; + *data++ = 0xa; + *data++ = 0xb; + *data++ = 0xc; + *data++ = 0xd; + *data++ = 0xe; + *data++ = 0xf; + + *res_len = data - &res->data[0]; +} + +// Get BMC Global Enables (IPMI/Section 22.2) +static void +app_get_global_enables (unsigned char *response, unsigned char *res_len) +{ + + ipmi_res_t *res = (ipmi_res_t *) response; + unsigned char *data = &res->data[0]; + + res->cc = CC_SUCCESS; + + *data++ = 0x09; // Global Enable + + *res_len = data - &res->data[0]; +} + +// Set System Info Params (IPMI/Section 22.14a) +static void +app_set_sys_info_params (unsigned char *request, unsigned char *response, + unsigned char *res_len) +{ + + ipmi_mn_req_t *req = (ipmi_mn_req_t *) request; + ipmi_res_t *res = (ipmi_res_t *) response; + unsigned char param = req->data[0]; + + res->cc = CC_SUCCESS; + + switch (param) + { + case SYS_INFO_PARAM_SET_IN_PROG: + g_sys_info_params.set_in_prog = req->data[1]; + break; + case SYS_INFO_PARAM_SYSFW_VER: + memcpy(g_sys_info_params.sysfw_ver, &req->data[1], SIZE_SYSFW_VER); + break; + case SYS_INFO_PARAM_SYS_NAME: + memcpy(g_sys_info_params.sys_name, &req->data[1], SIZE_SYS_NAME); + break; + case SYS_INFO_PARAM_PRI_OS_NAME: + memcpy(g_sys_info_params.pri_os_name, &req->data[1], SIZE_OS_NAME); + break; + case SYS_INFO_PARAM_PRESENT_OS_NAME: + memcpy(g_sys_info_params.present_os_name, &req->data[1], SIZE_OS_NAME); + break; + case SYS_INFO_PARAM_PRESENT_OS_VER: + memcpy(g_sys_info_params.present_os_ver, &req->data[1], SIZE_OS_VER); + break; + case SYS_INFO_PARAM_BMC_URL: + memcpy(g_sys_info_params.bmc_url, &req->data[1], SIZE_BMC_URL); + break; + case SYS_INFO_PARAM_OS_HV_URL: + memcpy(g_sys_info_params.os_hv_url, &req->data[1], SIZE_OS_HV_URL); + break; + default: + res->cc = CC_INVALID_PARAM; + break; + } + + return; +} + +// Get System Info Params (IPMI/Section 22.14b) +static void +app_get_sys_info_params (unsigned char *request, unsigned char *response, + unsigned char *res_len) +{ + + ipmi_mn_req_t *req = (ipmi_mn_req_t *) request; + ipmi_res_t *res = (ipmi_res_t *) response; + unsigned char *data = &res->data[0]; + unsigned char param = req->data[1]; + + // Fill default return values + res->cc = CC_SUCCESS; + *data++ = 1; // Parameter revision + + switch (param) + { + case SYS_INFO_PARAM_SET_IN_PROG: + *data++ = g_sys_info_params.set_in_prog; + break; + case SYS_INFO_PARAM_SYSFW_VER: + memcpy(data, g_sys_info_params.sysfw_ver, SIZE_SYSFW_VER); + data += SIZE_SYSFW_VER; + break; + case SYS_INFO_PARAM_SYS_NAME: + memcpy(data, g_sys_info_params.sys_name, SIZE_SYS_NAME); + data += SIZE_SYS_NAME; + break; + case SYS_INFO_PARAM_PRI_OS_NAME: + memcpy(data, g_sys_info_params.pri_os_name, SIZE_OS_NAME); + data += SIZE_OS_NAME; + break; + case SYS_INFO_PARAM_PRESENT_OS_NAME: + memcpy(data, g_sys_info_params.present_os_name, SIZE_OS_NAME); + data += SIZE_OS_NAME; + break; + case SYS_INFO_PARAM_PRESENT_OS_VER: + memcpy(data, g_sys_info_params.present_os_ver, SIZE_OS_VER); + data += SIZE_OS_VER; + break; + case SYS_INFO_PARAM_BMC_URL: + memcpy(data, g_sys_info_params.bmc_url, SIZE_BMC_URL); + data += SIZE_BMC_URL; + break; + case SYS_INFO_PARAM_OS_HV_URL: + memcpy(data, g_sys_info_params.os_hv_url, SIZE_OS_HV_URL); + data += SIZE_OS_HV_URL; + break; + default: + res->cc = CC_INVALID_PARAM; + break; + } + + if (res->cc == CC_SUCCESS) { + *res_len = data - &res->data[0]; + } + + return; +} + +// Handle Appliction Commands (IPMI/Section 20) +static void +ipmi_handle_app (unsigned char *request, unsigned char req_len, + unsigned char *response, unsigned char *res_len) +{ + ipmi_mn_req_t *req = (ipmi_mn_req_t *) request; + ipmi_res_t *res = (ipmi_res_t *) response; + unsigned char cmd = req->cmd; + + pthread_mutex_lock(&m_app); + switch (cmd) + { + case CMD_APP_GET_DEVICE_ID: + app_get_device_id (response, res_len); + break; + case CMD_APP_GET_SELFTEST_RESULTS: + app_get_selftest_results (response, res_len); + break; + case CMD_APP_GET_DEVICE_GUID: + case CMD_APP_GET_SYSTEM_GUID: + // Get Device GUID and Get System GUID returns same data + // from IPMI stack. FYI, Get System GUID will have to be + // sent with in an IPMI session that includes session info + app_get_device_guid (response, res_len); + break; + case CMD_APP_GET_GLOBAL_ENABLES: + app_get_global_enables (response, res_len); + break; + case CMD_APP_SET_SYS_INFO_PARAMS: + app_set_sys_info_params (request, response, res_len); + break; + case CMD_APP_GET_SYS_INFO_PARAMS: + app_get_sys_info_params (request, response, res_len); + break; + default: + res->cc = CC_INVALID_CMD; + break; + } + pthread_mutex_unlock(&m_app); +} + +/* + * Function(s) to handle IPMI messages with NetFn: Storage + */ + +static void +storage_get_fruid_info(unsigned char *response, unsigned char *res_len) +{ + ipmi_res_t *res = (ipmi_res_t *) response; + unsigned char *data = &res->data[0]; + int size = plat_fruid_size(); + + res->cc = CC_SUCCESS; + + *data++ = size & 0xFF; // FRUID size LSB + *data++ = (size >> 8) & 0xFF; // FRUID size MSB + *data++ = 0x00; // Device accessed by bytes + + *res_len = data - &res->data[0]; + + return; +} + +static void +storage_get_fruid_data(unsigned char *request, unsigned char *response, + unsigned char *res_len) +{ + ipmi_mn_req_t *req = (ipmi_mn_req_t *) request; + ipmi_res_t *res = (ipmi_res_t *) response; + unsigned char *data = &res->data[0]; + + int offset = req->data[1] + (req->data[2] << 8); + int count = req->data[3]; + + int ret = plat_fruid_data(offset, count, &(res->data[1])); + if (ret) { + res->cc = CC_UNSPECIFIED_ERROR; + } else { + res->cc = CC_SUCCESS; + *data++ = count; + data += count; + } + + if (res->cc == CC_SUCCESS) { + *res_len = data - &res->data[0]; + } + return; +} + +static void +storage_get_sdr_info (unsigned char *response, unsigned char *res_len) +{ + ipmi_res_t *res = (ipmi_res_t *) response; + unsigned char *data = &res->data[0]; + int num_entries; // number of sdr records + int free_space; // free space in SDR device in bytes + time_stamp_t ts_recent_add; // Recent Addition Timestamp + time_stamp_t ts_recent_erase; // Recent Erasure Timestamp + + // Use platform APIs to get SDR information + num_entries = sdr_num_entries (); + free_space = sdr_free_space (); + sdr_ts_recent_add (&ts_recent_add); + sdr_ts_recent_erase (&ts_recent_erase); + + res->cc = CC_SUCCESS; + + *data++ = IPMI_SDR_VERSION; // SDR version + *data++ = num_entries & 0xFF; // number of sdr entries + *data++ = (num_entries >> 8) & 0xFF; + *data++ = free_space & 0xFF; // Free SDR Space + *data++ = (free_space >> 8) & 0xFF; + + memcpy(data, ts_recent_add.ts, SIZE_TIME_STAMP); + data += SIZE_TIME_STAMP; + + memcpy(data, ts_recent_erase.ts, SIZE_TIME_STAMP); + data += SIZE_TIME_STAMP; + + *data++ = 0x02; // Operations supported + + *res_len = data - &res->data[0]; + + return; +} + +static void +storage_rsv_sdr (unsigned char *response, unsigned char *res_len) +{ + ipmi_res_t *res = (ipmi_res_t *) response; + unsigned char *data = &res->data[0]; + int rsv_id; // SDR reservation ID + + // Use platform APIs to get a SDR reservation ID + rsv_id = sdr_rsv_id (); + if (rsv_id < 0) + { + res->cc = CC_UNSPECIFIED_ERROR; + return; + } + + res->cc = CC_SUCCESS; + *data++ = rsv_id & 0xFF; // Reservation ID + *data++ = (rsv_id >> 8) & 0XFF; + + *res_len = data - &res->data[0]; + + return; +} + +static void +storage_get_sdr (unsigned char *request, unsigned char *response, + unsigned char *res_len) +{ + ipmi_mn_req_t *req = (ipmi_mn_req_t *) request; + ipmi_res_t *res = (ipmi_res_t *) response; + unsigned char *data = &res->data[0]; + + int read_rec_id; //record ID to be read + int next_rec_id; //record ID for the next entry + int rsv_id; // Reservation ID for the request + int rec_offset; // Read offset into the record + int rec_bytes; // Number of bytes to be read + sdr_rec_t entry; // SDR record entry + int ret; + + rsv_id = (req->data[1] >> 8) | req->data[0]; + read_rec_id = (req->data[3] >> 8) | req->data[2]; + rec_offset = req->data[4]; + rec_bytes = req->data[5]; + + // Use platform API to read the record Id and get next ID + ret = sdr_get_entry (rsv_id, read_rec_id, &entry, &next_rec_id); + if (ret) + { + res->cc = CC_UNSPECIFIED_ERROR; + return; + } + + res->cc = CC_SUCCESS; + *data++ = next_rec_id & 0xFF; // next record ID + *data++ = (next_rec_id >> 8) & 0xFF; + + memcpy (data, &entry.rec[rec_offset], rec_bytes); + data += rec_bytes; + + *res_len = data - &res->data[0]; + + return; +} + +static void +storage_get_sel_info (unsigned char *response, unsigned char *res_len) +{ + ipmi_res_t *res = (ipmi_res_t *) response; + unsigned char *data = &res->data[0]; + int num_entries; // number of log entries + int free_space; // free space in SEL device in bytes + time_stamp_t ts_recent_add; // Recent Addition Timestamp + time_stamp_t ts_recent_erase; // Recent Erasure Timestamp + + // Use platform APIs to get SEL information + num_entries = sel_num_entries (); + free_space = sel_free_space (); + sel_ts_recent_add (&ts_recent_add); + sel_ts_recent_erase (&ts_recent_erase); + + res->cc = CC_SUCCESS; + + *data++ = IPMI_SEL_VERSION; // SEL version + *data++ = num_entries & 0xFF; // number of log entries + *data++ = (num_entries >> 8) & 0xFF; + *data++ = free_space & 0xFF; // Free SEL Space + *data++ = (free_space >> 8) & 0xFF; + + memcpy(data, ts_recent_add.ts, SIZE_TIME_STAMP); + data += SIZE_TIME_STAMP; + + memcpy(data, ts_recent_erase.ts, SIZE_TIME_STAMP); + data += SIZE_TIME_STAMP; + + *data++ = 0x02; // Operations supported + + *res_len = data - &res->data[0]; + + return; +} + +static void +storage_rsv_sel (unsigned char *response, unsigned char *res_len) +{ + ipmi_res_t *res = (ipmi_res_t *) response; + unsigned char *data = &res->data[0]; + int rsv_id; // SEL reservation ID + + // Use platform APIs to get a SEL reservation ID + rsv_id = sel_rsv_id (); + if (rsv_id < 0) + { + res->cc = CC_SEL_ERASE_PROG; + return; + } + + res->cc = CC_SUCCESS; + *data++ = rsv_id & 0xFF; // Reservation ID + *data++ = (rsv_id >> 8) & 0XFF; + + *res_len = data - &res->data[0]; + + return; +} + +static void +storage_get_sel (unsigned char *request, unsigned char *response, + unsigned char *res_len) +{ + ipmi_mn_req_t *req = (ipmi_mn_req_t *) request; + ipmi_res_t *res = (ipmi_res_t *) response; + unsigned char *data = &res->data[0]; + + int read_rec_id; //record ID to be read + int next_rec_id; //record ID for the next msg + sel_msg_t entry; // SEL log entry + int ret; + + read_rec_id = (req->data[3] >> 8) | req->data[2]; + + // Use platform API to read the record Id and get next ID + ret = sel_get_entry (read_rec_id, &entry, &next_rec_id); + if (ret) + { + res->cc = CC_UNSPECIFIED_ERROR; + return; + } + + res->cc = CC_SUCCESS; + *data++ = next_rec_id & 0xFF; // next record ID + *data++ = (next_rec_id >> 8) & 0xFF; + + memcpy(data, entry.msg, SIZE_SEL_REC); + data += SIZE_SEL_REC; + + *res_len = data - &res->data[0]; + + return; +} + +static void +storage_add_sel (unsigned char *request, unsigned char *response, + unsigned char *res_len) +{ + ipmi_mn_req_t *req = (ipmi_mn_req_t *) request; + ipmi_res_t *res = (ipmi_res_t *) response; + unsigned char *data = &res->data[0]; + + + int record_id; // Record ID for added entry + int ret; + + sel_msg_t entry; + + memcpy(entry.msg, req->data, SIZE_SEL_REC); + + // Use platform APIs to add the new SEL entry + ret = sel_add_entry (&entry, &record_id); + if (ret) + { + res->cc = CC_UNSPECIFIED_ERROR; + return; + } + + res->cc = CC_SUCCESS; + *data++ = record_id & 0xFF; + *data++ = (record_id >> 8) & 0xFF; + + *res_len = data - &res->data[0]; + + return; +} + +static void +storage_clr_sel (unsigned char *request, unsigned char *response, + unsigned char *res_len) +{ + ipmi_mn_req_t *req = (ipmi_mn_req_t *) request; + ipmi_res_t *res = (ipmi_res_t *) response; + unsigned char *data = &res->data[0]; + + sel_erase_stat_t status; + int ret; + int rsv_id; + + // Verify the request to contain 'CLR' characters + if ((req->data[2] != 'C') || (req->data[3] != 'L') || (req->data[4] != 'R')) + { + res->cc = CC_INVALID_PARAM; + return; + } + + // Populate reservation ID given in request + rsv_id = (req->data[1] << 8) | req->data[0]; + + // Use platform APIs to clear or get status + if (req->data[5] == IPMI_SEL_INIT_ERASE) + { + ret = sel_erase (rsv_id); + } + else if (req->data[5] == IPMI_SEL_ERASE_STAT) + { + ret = sel_erase_status (rsv_id, &status); + } + else + { + res->cc = CC_INVALID_PARAM; + return; + } + + // Handle platform error and return + if (ret) + { + res->cc = CC_UNSPECIFIED_ERROR; + return; + } + + res->cc = CC_SUCCESS; + *data++ = status; + + *res_len = data - &res->data[0]; + + return; +} + +static void +ipmi_handle_storage (unsigned char *request, unsigned char req_len, + unsigned char *response, unsigned char *res_len) +{ + ipmi_mn_req_t *req = (ipmi_mn_req_t *) request; + ipmi_res_t *res = (ipmi_res_t *) response; + unsigned char cmd = req->cmd; + + res->cc = CC_SUCCESS; + *res_len = 0; + + pthread_mutex_lock(&m_storage); + switch (cmd) + { + case CMD_STORAGE_GET_FRUID_INFO: + storage_get_fruid_info (response, res_len); + break; + case CMD_STORAGE_READ_FRUID_DATA: + storage_get_fruid_data (request, response, res_len); + break; + case CMD_STORAGE_GET_SEL_INFO: + storage_get_sel_info (response, res_len); + break; + case CMD_STORAGE_RSV_SEL: + storage_rsv_sel (response, res_len); + break; + case CMD_STORAGE_ADD_SEL: + storage_add_sel (request, response, res_len); + break; + case CMD_STORAGE_GET_SEL: + storage_get_sel (request, response, res_len); + break; + case CMD_STORAGE_CLR_SEL: + storage_clr_sel (request, response, res_len); + break; + case CMD_STORAGE_GET_SDR_INFO: + storage_get_sdr_info (response, res_len); + break; + case CMD_STORAGE_RSV_SDR: + storage_rsv_sdr (response, res_len); + break; + case CMD_STORAGE_GET_SDR: + storage_get_sdr (request, response, res_len); + break; + default: + res->cc = CC_INVALID_CMD; + break; + } + + pthread_mutex_unlock(&m_storage); + return; +} + +/* + * Function(s) to handle IPMI messages with NetFn: Transport + */ + +// Set LAN Configuration (IPMI/Section 23.1) +static void +transport_set_lan_config (unsigned char *request, unsigned char *response, + unsigned char *res_len) +{ + ipmi_mn_req_t *req = (ipmi_mn_req_t *) request; + ipmi_res_t *res = (ipmi_res_t *) response; + unsigned char param = req->data[1]; + + // Fill the response with default values + res->cc = CC_SUCCESS; + + switch (param) + { + case LAN_PARAM_SET_IN_PROG: + g_lan_config.set_in_prog = req->data[2]; + break; + case LAN_PARAM_AUTH_SUPPORT: + g_lan_config.auth_support = req->data[2]; + break; + case LAN_PARAM_AUTH_ENABLES: + memcpy(g_lan_config.auth_enables, &req->data[2], SIZE_AUTH_ENABLES); + break; + case LAN_PARAM_IP_ADDR: + memcpy(g_lan_config.ip_addr, &req->data[2], SIZE_IP_ADDR); + break; + case LAN_PARAM_IP_SRC: + g_lan_config.ip_src = req->data[2]; + break; + case LAN_PARAM_MAC_ADDR: + memcpy(g_lan_config.mac_addr, &req->data[2], SIZE_MAC_ADDR); + break; + case LAN_PARAM_NET_MASK: + memcpy(g_lan_config.net_mask, &req->data[2], SIZE_NET_MASK); + break; + case LAN_PARAM_IP_HDR: + memcpy(g_lan_config.ip_hdr, &req->data[2], SIZE_IP_HDR); + break; + case LAN_PARAM_PRI_RMCP_PORT: + g_lan_config.pri_rmcp_port[0] = req->data[2]; + g_lan_config.pri_rmcp_port[1] = req->data[3]; + break; + case LAN_PARAM_SEC_RMCP_PORT: + g_lan_config.sec_rmcp_port[0] = req->data[2]; + g_lan_config.sec_rmcp_port[1] = req->data[3]; + break; + case LAN_PARAM_ARP_CTRL: + g_lan_config.arp_ctrl = req->data[2]; + break; + case LAN_PARAM_GARP_INTERVAL: + g_lan_config.garp_interval = req->data[2]; + break; + case LAN_PARAM_DF_GW_IP_ADDR: + memcpy(g_lan_config.df_gw_ip_addr, &req->data[2], SIZE_IP_ADDR); + break; + case LAN_PARAM_DF_GW_MAC_ADDR: + memcpy(g_lan_config.df_gw_mac_addr, &req->data[2], SIZE_MAC_ADDR); + break; + case LAN_PARAM_BACK_GW_IP_ADDR: + memcpy(g_lan_config.back_gw_ip_addr, &req->data[2], SIZE_IP_ADDR); + break; + case LAN_PARAM_BACK_GW_MAC_ADDR: + memcpy(g_lan_config.back_gw_mac_addr, &req->data[2], SIZE_MAC_ADDR); + break; + case LAN_PARAM_COMMUNITY_STR: + memcpy(g_lan_config.community_str, &req->data[2], SIZE_COMMUNITY_STR); + break; + case LAN_PARAM_NO_OF_DEST: + g_lan_config.no_of_dest = req->data[2]; + break; + case LAN_PARAM_DEST_TYPE: + memcpy(g_lan_config.dest_type, &req->data[2], SIZE_DEST_TYPE); + break; + case LAN_PARAM_DEST_ADDR: + memcpy(g_lan_config.dest_addr, &req->data[2], SIZE_DEST_ADDR); + break; + default: + res->cc = CC_INVALID_PARAM; + break; + } +} + +// Get LAN Configuration (IPMI/Section 23.2) +static void +transport_get_lan_config (unsigned char *request, unsigned char *response, + unsigned char *res_len) +{ + + ipmi_mn_req_t *req = (ipmi_mn_req_t *) request; + ipmi_res_t *res = (ipmi_res_t *) response; + unsigned char *data = &res->data[0]; + unsigned char param = req->data[1]; + + // Fill the response with default values + res->cc = CC_SUCCESS; + *data++ = 0x01; // Parameter revision + + switch (param) + { + case LAN_PARAM_SET_IN_PROG: + *data++ = g_lan_config.set_in_prog; + break; + case LAN_PARAM_AUTH_SUPPORT: + *data++ = g_lan_config.auth_support; + break; + case LAN_PARAM_AUTH_ENABLES: + memcpy(data, g_lan_config.auth_enables, SIZE_AUTH_ENABLES); + data += SIZE_AUTH_ENABLES; + break; + case LAN_PARAM_IP_ADDR: + memcpy(data, g_lan_config.ip_addr, SIZE_IP_ADDR); + data += SIZE_IP_ADDR; + break; + case LAN_PARAM_IP_SRC: + *data++ = g_lan_config.ip_src; + break; + case LAN_PARAM_MAC_ADDR: + memcpy(data, g_lan_config.mac_addr, SIZE_MAC_ADDR); + data += SIZE_MAC_ADDR; + break; + case LAN_PARAM_NET_MASK: + memcpy(data, g_lan_config.net_mask, SIZE_NET_MASK); + data += SIZE_NET_MASK; + break; + case LAN_PARAM_IP_HDR: + memcpy(data, g_lan_config.ip_hdr, SIZE_IP_HDR); + data += SIZE_IP_HDR; + break; + case LAN_PARAM_PRI_RMCP_PORT: + *data++ = g_lan_config.pri_rmcp_port[0]; + *data++ = g_lan_config.pri_rmcp_port[1]; + break; + case LAN_PARAM_SEC_RMCP_PORT: + *data++ = g_lan_config.sec_rmcp_port[0]; + *data++ = g_lan_config.sec_rmcp_port[1]; + break; + case LAN_PARAM_ARP_CTRL: + *data++ = g_lan_config.arp_ctrl; + break; + case LAN_PARAM_GARP_INTERVAL: + *data++ = g_lan_config.garp_interval; + break; + case LAN_PARAM_DF_GW_IP_ADDR: + memcpy(data, g_lan_config.df_gw_ip_addr, SIZE_IP_ADDR); + data += SIZE_IP_ADDR; + break; + case LAN_PARAM_DF_GW_MAC_ADDR: + memcpy(data, g_lan_config.df_gw_mac_addr, SIZE_MAC_ADDR); + data += SIZE_MAC_ADDR; + break; + case LAN_PARAM_BACK_GW_IP_ADDR: + memcpy(data, g_lan_config.back_gw_ip_addr, SIZE_IP_ADDR); + data += SIZE_IP_ADDR; + break; + case LAN_PARAM_BACK_GW_MAC_ADDR: + memcpy(data, g_lan_config.back_gw_mac_addr, SIZE_MAC_ADDR); + data += SIZE_MAC_ADDR; + break; + case LAN_PARAM_COMMUNITY_STR: + memcpy(data, g_lan_config.community_str, SIZE_COMMUNITY_STR); + data += SIZE_COMMUNITY_STR; + break; + case LAN_PARAM_NO_OF_DEST: + *data++ = g_lan_config.no_of_dest; + break; + case LAN_PARAM_DEST_TYPE: + memcpy(data, g_lan_config.dest_type, SIZE_DEST_TYPE); + data += SIZE_DEST_TYPE; + break; + case LAN_PARAM_DEST_ADDR: + memcpy(data, g_lan_config.dest_addr, SIZE_DEST_ADDR); + data += SIZE_DEST_ADDR; + break; + default: + res->cc = CC_INVALID_PARAM; + break; + } + + if (res->cc == CC_SUCCESS) { + *res_len = data - &res->data[0]; + } +} + +// Handle Transport Commands (IPMI/Section 23) +static void +ipmi_handle_transport (unsigned char *request, unsigned char req_len, + unsigned char *response, unsigned char *res_len) +{ + ipmi_mn_req_t *req = (ipmi_mn_req_t *) request; + ipmi_res_t *res = (ipmi_res_t *) response; + unsigned char cmd = req->cmd; + + pthread_mutex_lock(&m_transport); + switch (cmd) + { + case CMD_TRANSPORT_SET_LAN_CONFIG: + transport_set_lan_config (request, response, res_len); + break; + case CMD_TRANSPORT_GET_LAN_CONFIG: + transport_get_lan_config (request, response, res_len); + break; + default: + res->cc = CC_INVALID_CMD; + break; + } + pthread_mutex_unlock(&m_transport); +} + +/* + * Function(s) to handle IPMI messages with NetFn: OEM + */ + +static void +oem_set_proc_info (unsigned char *request, unsigned char *response, + unsigned char *res_len) +{ + ipmi_mn_req_t *req = (ipmi_mn_req_t *) request; + ipmi_res_t *res = (ipmi_res_t *) response; + + g_proc_info.type = req->data[1]; + g_proc_info.freq[0] = req->data[2]; + g_proc_info.freq[1] = req->data[3]; + + res->cc = CC_SUCCESS; + *res_len = 0; +} + +static void +oem_set_dimm_info (unsigned char *request, unsigned char *response, + unsigned char *res_len) +{ + ipmi_mn_req_t *req = (ipmi_mn_req_t *) request; + ipmi_res_t *res = (ipmi_res_t *) response; + + unsigned char index = req->data[0]; + + g_dimm_info[index].type = req->data[1]; + g_dimm_info[index].speed[0] = req->data[2]; + g_dimm_info[index].speed[1] = req->data[3]; + g_dimm_info[index].size[0] = req->data[4]; + g_dimm_info[index].size[1] = req->data[5]; + + res->cc = CC_SUCCESS; + *res_len = 0; +} + +static void +oem_set_post_start (unsigned char *request, unsigned char *response, + unsigned char *res_len) +{ + ipmi_mn_req_t *req = (ipmi_mn_req_t *) request; + ipmi_res_t *res = (ipmi_res_t *) response; + + // TODO: For now logging the event, need to find usage for this info + syslog (LOG_INFO, "POST Start Event for Payload#%d\n", req->payload_id); + + res->cc = CC_SUCCESS; + *res_len = 0; +} + +static void +oem_set_post_end (unsigned char *request, unsigned char *response, + unsigned char *res_len) +{ + ipmi_mn_req_t *req = (ipmi_mn_req_t *) request; + ipmi_res_t *res = (ipmi_res_t *) response; + + // TODO: For now logging the event, need to find usage for this info + syslog (LOG_INFO, "POST End Event for Payload#%d\n", req->payload_id); + + res->cc = CC_SUCCESS; + *res_len = 0; +} + +static void +ipmi_handle_oem (unsigned char *request, unsigned char req_len, + unsigned char *response, unsigned char *res_len) +{ + ipmi_mn_req_t *req = (ipmi_mn_req_t *) request; + ipmi_res_t *res = (ipmi_res_t *) response; + + unsigned char cmd = req->cmd; + + pthread_mutex_lock(&m_oem); + switch (cmd) + { + case CMD_OEM_SET_PROC_INFO: + oem_set_proc_info (request, response, res_len); + break; + case CMD_OEM_SET_DIMM_INFO: + oem_set_dimm_info (request, response, res_len); + break; + case CMD_OEM_SET_POST_START: + oem_set_post_start (request, response, res_len); + break; + case CMD_OEM_SET_POST_END: + oem_set_post_end (request, response, res_len); + break; + default: + res->cc = CC_INVALID_CMD; + break; + } + pthread_mutex_unlock(&m_oem); +} + +static void +oem_1s_handle_ipmb_kcs(unsigned char *request, unsigned char req_len, + unsigned char *response, unsigned char *res_len) +{ + ipmi_mn_req_t *req = (ipmi_mn_req_t *) request; + ipmi_res_t *res = (ipmi_res_t *) response; + + // Need to extract bridged IPMI command and handle + unsigned char req_buf[MAX_IPMI_MSG_SIZE] = {0}; + unsigned char res_buf[MAX_IPMI_MSG_SIZE] = {0}; + + // Add the payload id from the bridged command + req_buf[0] = req->payload_id; + + // Remove OEM IPMI Header + 1 byte for BIC interface + // The offset moves by one due to the payload ID + memcpy(&req_buf[1], &request[BIC_INTF_HDR_SIZE + 1], req_len - BIC_INTF_HDR_SIZE); + + // Send the bridged KCS command along with the payload ID + // The offset moves by one due to the payload ID + ipmi_handle(req_buf, req_len - BIC_INTF_HDR_SIZE + 1, res_buf, res_len); + + // Copy the response back + memcpy(&res->data[1], res_buf, *res_len); + + // Add the OEM command's response + res->cc = CC_SUCCESS; + res->data[0] = req->data[0]; // Bridge-IC interface + *res_len += 1; +} + +static void +oem_1s_handle_ipmb_req(unsigned char *request, unsigned char req_len, + unsigned char *response, unsigned char *res_len) +{ + ipmi_mn_req_t *req = (ipmi_mn_req_t *) request; + ipmi_res_t *res = (ipmi_res_t *) response; + + // handle based on Bridge-IC interface + switch(req->data[0]) { + case BIC_INTF_ME: + // TODO: Need to call ME command handler + syslog(LOG_INFO, "oem_1s_handle_ipmb_req: Command received from ME for " + "payload#%d\n", req->payload_id); + res->data[0] = BIC_INTF_ME; + res->cc = CC_SUCCESS; + *res_len = 1; + break; + case BIC_INTF_SOL: + // TODO: Need to call Optional SoL message handler + syslog(LOG_INFO, "oem_1s_handle_ipmb_req: Command received from SOL for " + "payload#%d\n", req->payload_id); + res->data[0] = BIC_INTF_SOL; + res->cc = CC_SUCCESS; + *res_len = 1; + break; + case BIC_INTF_KCS: + oem_1s_handle_ipmb_kcs(request, req_len, response, res_len); + break; + default: + // TODO: Need to add additonal interface handler, if supported + syslog(LOG_ALERT, "oem_1s_handle_ipmb_req: Command received on intf#%d " + "for payload#%d", req->data[0], req->payload_id); + res->cc = CC_INVALID_PARAM; + *res_len = 0; + break; + } +} + +static void +ipmi_handle_oem_1s(unsigned char *request, unsigned char req_len, + unsigned char *response, unsigned char *res_len) +{ + ipmi_mn_req_t *req = (ipmi_mn_req_t *) request; + ipmi_res_t *res = (ipmi_res_t *) response; + int i; + + unsigned char cmd = req->cmd; + + pthread_mutex_lock(&m_oem_1s); + switch (cmd) + { + case CMD_OEM_1S_MSG_IN: + oem_1s_handle_ipmb_req(request, req_len, response, res_len); + break; + case CMD_OEM_1S_INTR: + syslog(LOG_INFO, "ipmi_handle_oem_1s: 1S server interrupt#%d received " + "for payload#%d\n", req->data[0], req->payload_id); + + res->cc = CC_SUCCESS; + *res_len = 0; + break; + case CMD_OEM_1S_POST_BUF: + for (i = 1; i <= req->data[0]; i++) { + pal_post_handle(req->payload_id, req->data[i]); + } + + res->cc = CC_SUCCESS; + *res_len = 0; + break; + case CMD_OEM_1S_PLAT_DISC: + syslog(LOG_INFO, "ipmi_handle_oem_1s: Platform Discovery received for " + "payload#%d\n", req->payload_id); + res->cc = CC_SUCCESS; + *res_len = 0; + break; + case CMD_OEM_1S_BIC_RESET: + syslog(LOG_INFO, "ipmi_handle_oem_1s: BIC Reset received " + "for payload#%d\n", req->payload_id); + + if (req->data[0] == 0x0) { + syslog(LOG_ALERT, "Cold Reset by Firmware Update\n"); + res->cc = CC_SUCCESS; + } else if (req->data[1] == 0x01) { + syslog(LOG_ALERT, "WDT Reset\n"); + res->cc = CC_SUCCESS; + } else { + syslog(LOG_ALERT, "Error\n"); + res->cc = CC_INVALID_PARAM; + } + + *res_len = 0; + break; + case CMD_OEM_1S_BIC_UPDATE_MODE: + syslog(LOG_INFO, "ipmi_handle_oem_1s: BIC Update Mode received " + "for payload#%d\n", req->payload_id); + + if (req->data[0] == 0x0) { + syslog(LOG_INFO, "Normal Mode\n"); + res->cc = CC_SUCCESS; + } else if (req->data[1] == 0x0F) { + syslog(LOG_INFO, "Update Mode\n"); + res->cc = CC_SUCCESS; + } else { + syslog(LOG_ALERT, "Error\n"); + res->cc = CC_INVALID_PARAM; + } + + *res_len = 0; + break; + default: + res->cc = CC_INVALID_CMD; + *res_len = 0; + break; + } + pthread_mutex_unlock(&m_oem_1s); +} + +/* + * Function to handle all IPMI messages + */ +static void +ipmi_handle (unsigned char *request, unsigned char req_len, + unsigned char *response, unsigned char *res_len) +{ + + ipmi_mn_req_t *req = (ipmi_mn_req_t *) request; + ipmi_res_t *res = (ipmi_res_t *) response; + unsigned char netfn; + + netfn = req->netfn_lun >> 2; + + // Provide default values in the response message + res->cmd = req->cmd; + res->cc = 0xFF; // Unspecified completion code + *res_len = 0; + + switch (netfn) + { + case NETFN_CHASSIS_REQ: + res->netfn_lun = NETFN_CHASSIS_RES << 2; + ipmi_handle_chassis (request, req_len, response, res_len); + break; + case NETFN_APP_REQ: + res->netfn_lun = NETFN_APP_RES << 2; + ipmi_handle_app (request, req_len, response, res_len); + break; + case NETFN_STORAGE_REQ: + res->netfn_lun = NETFN_STORAGE_RES << 2; + ipmi_handle_storage (request, req_len, response, res_len); + break; + case NETFN_TRANSPORT_REQ: + res->netfn_lun = NETFN_TRANSPORT_RES << 2; + ipmi_handle_transport (request, req_len, response, res_len); + break; + case NETFN_OEM_REQ: + res->netfn_lun = NETFN_OEM_RES << 2; + ipmi_handle_oem (request, req_len, response, res_len); + break; + case NETFN_OEM_1S_REQ: + res->netfn_lun = NETFN_OEM_1S_RES << 2; + ipmi_handle_oem_1s(request, req_len, response, res_len); + break; + default: + res->netfn_lun = (netfn + 1) << 2; + break; + } + + // This header includes NetFunction, Command, and Completion Code + *res_len += IPMI_RESP_HDR_SIZE; + + return; +} + +void +*conn_handler(void *socket_desc) { + int sock = *(int*)socket_desc; + int n; + unsigned char req_buf[MAX_IPMI_MSG_SIZE]; + unsigned char res_buf[MAX_IPMI_MSG_SIZE]; + unsigned char res_len = 0; + + n = recv (sock, req_buf, sizeof(req_buf), 0); + if (n <= 0) { + syslog(LOG_ALERT, "ipmid: recv() failed with %d\n", n); + goto conn_cleanup; + } + + ipmi_handle(req_buf, n, res_buf, &res_len); + + if (send (sock, res_buf, res_len, 0) < 0) { + syslog(LOG_ALERT, "ipmid: send() failed\n"); + } + +conn_cleanup: + close(sock); + + pthread_exit(NULL); + return 0; +} + + +int +main (void) +{ + int s, s2, t, len; + struct sockaddr_un local, remote; + pthread_t tid; + + daemon(1, 0); + openlog("ipmid", LOG_CONS, LOG_DAEMON); + + + plat_fruid_init(); + plat_sensor_init(); + + sdr_init(); + sel_init(); + + pthread_mutex_init(&m_chassis, NULL); + pthread_mutex_init(&m_app, NULL); + pthread_mutex_init(&m_storage, NULL); + pthread_mutex_init(&m_transport, NULL); + pthread_mutex_init(&m_oem, NULL); + pthread_mutex_init(&m_oem_1s, NULL); + + if ((s = socket (AF_UNIX, SOCK_STREAM, 0)) == -1) + { + syslog(LOG_ALERT, "ipmid: socket() failed\n"); + exit (1); + } + + local.sun_family = AF_UNIX; + strcpy (local.sun_path, SOCK_PATH_IPMI); + unlink (local.sun_path); + len = strlen (local.sun_path) + sizeof (local.sun_family); + if (bind (s, (struct sockaddr *) &local, len) == -1) + { + syslog(LOG_ALERT, "ipmid: bind() failed\n"); + exit (1); + } + + if (listen (s, 5) == -1) + { + syslog(LOG_ALERT, "ipmid: listen() failed\n"); + exit (1); + } + + while(1) { + int n; + t = sizeof (remote); + if ((s2 = accept (s, (struct sockaddr *) &remote, &t)) < 0) { + syslog(LOG_ALERT, "ipmid: accept() failed\n"); + break; + } + + // Creating a worker thread to handle the request + // TODO: Need to monitor the server performance with higher load and + // see if we need to create pre-defined number of workers and schedule + // the requests among them. + if (pthread_create(&tid, NULL, conn_handler, (void*) &s2) < 0) { + syslog(LOG_ALERT, "ipmid: pthread_create failed\n"); + close(s2); + continue; + } + + pthread_detach(tid); + } + + close(s); + + pthread_mutex_destroy(&m_chassis); + pthread_mutex_destroy(&m_app); + pthread_mutex_destroy(&m_storage); + pthread_mutex_destroy(&m_transport); + pthread_mutex_destroy(&m_oem); + pthread_mutex_destroy(&m_oem_1s); + + return 0; +} diff --git a/common/recipes-core/ipmid/files/sdr.c b/common/recipes-core/ipmid/files/sdr.c new file mode 100644 index 0000000..91a4df5 --- /dev/null +++ b/common/recipes-core/ipmid/files/sdr.c @@ -0,0 +1,414 @@ +/* + * + * Copyright 2014-present Facebook. All Rights Reserved. + * + * This file represents platform specific implementation for storing + * SDR record entries and acts as back-end for IPMI stack + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include "sdr.h" +#include "sensor.h" +#include "timestamp.h" +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <syslog.h> +#include <string.h> +#include <openbmc/ipmi.h> + +// SDR Header magic number +#define SDR_HDR_MAGIC 0xFBFBFBFB + +// SDR Header version number +#define SDR_HDR_VERSION 0x01 + +// SDR reservation IDs can not be 0x00 or 0xFFFF +#define SDR_RSVID_MIN 0x01 +#define SDR_RSVID_MAX 0xFFFE + +#define SDR_RECORDS_MAX 64 // to support around 64 sensors + +// SDR index to keep track +#define SDR_INDEX_MIN 0 +#define SDR_INDEX_MAX (SDR_RECORDS_MAX - 1) + +// Record ID can not be 0x0 (IPMI/Section 31) +#define SDR_RECID_MIN 1 +#define SDR_RECID_MAX SDR_RECORDS_MAX + +// Special RecID value for first and last (IPMI/Section 31) +#define SDR_RECID_FIRST 0x0000 +#define SDR_RECID_LAST 0xFFFF + +#define SDR_VERSION 0x51 +#define SDR_LEN_MAX 64 + +#define SDR_FULL_TYPE 0x01 +#define SDR_MGMT_TYPE 0x12 +#define SDR_OEM_TYPE 0xC0 + +#define SDR_FULL_LEN 64 +#define SDR_MGMT_LEN 32 +#define SDR_OEM_LEN 64 + +// SDR header struct to keep track of SEL Log entries +typedef struct { + int magic; // Magic number to check validity + int version; // version number of this header + int begin; // index to the first SDR entry + int end; // index to the last SDR entry + time_stamp_t ts_add; // last addition time stamp + time_stamp_t ts_erase; // last erase time stamp +} sdr_hdr_t; + +// Keep track of last Reservation ID +static int g_rsv_id = 0x01; + +// SDR Header and data global structures +static sdr_hdr_t g_sdr_hdr; +static sdr_rec_t g_sdr_data[SDR_RECORDS_MAX]; + +// Add a new SDR entry +static int +sdr_add_entry(sdr_rec_t *rec, int *rec_id) { + // If SDR is full, return error + if (sdr_num_entries() == SDR_RECORDS_MAX) { + syslog(LOG_ALERT, "sdr_add_entry: SDR full\n"); + return -1; + } + + // Add Record ID which is array index + 1 + rec->rec[0] = g_sdr_hdr.end+1; + + // Add the enry at end + memcpy(g_sdr_data[g_sdr_hdr.end].rec, rec->rec, sizeof(sdr_rec_t)); + + // Return the newly added record ID + *rec_id = g_sdr_hdr.end+1; + + // Increment the end pointer + ++g_sdr_hdr.end; + + // Update timestamp for add in header + time_stamp_fill(g_sdr_hdr.ts_add.ts); + + return 0; +} + +static int +sdr_add_mgmt_rec(sensor_mgmt_t *p_rec) { + int rec_id = 0; + sdr_rec_t sdr = { 0 }; + sdr_mgmt_t rec = { 0 }; + + // Populate SDR MGMT record + rec.ver = SDR_VERSION; + rec.type = SDR_MGMT_TYPE; + rec.len = SDR_MGMT_LEN; + + rec.slave_addr = p_rec->slave_addr; + rec.chan_no = p_rec->chan_no; + + rec.pwr_state_init = p_rec->pwr_state_init; + rec.dev_caps = p_rec->dev_caps; + rec.ent_id = p_rec->ent_id; + rec.ent_inst = p_rec->ent_inst; + rec.oem = p_rec->oem; + rec.str_type_len = p_rec->str_type_len; + memcpy(rec.str, p_rec->str, SENSOR_STR_SIZE); + + // Copy this record to generic SDR record + memcpy(sdr.rec, &rec, SDR_LEN_MAX); + + // Add this record to SDR repo + if (sdr_add_entry(&sdr, &rec_id)) { + syslog(LOG_ALERT, "sdr_add_mgmt_rec: sdr_add_entry failed\n"); + return -1; + } + + return 0; +} + +static int +sdr_add_disc_rec(sensor_disc_t *p_rec) { + int rec_id = 0; + sdr_rec_t sdr = { 0 }; + sdr_full_t rec = { 0 }; + + // Populate SDR FULL record + rec.ver = SDR_VERSION; + rec.type = SDR_FULL_TYPE; + rec.len = SDR_FULL_LEN; + + rec.owner = p_rec->owner; + rec.lun = p_rec->lun; + + rec.ent_id = p_rec->ent_id; + rec.ent_inst = p_rec->ent_inst; + rec.sensor_init = p_rec->sensor_init; + rec.sensor_caps = p_rec->sensor_caps; + rec.sensor_type = p_rec->sensor_type; + rec.evt_read_type = p_rec->evt_read_type; + memcpy(rec.assert_evt_mask, p_rec->assert_evt_mask, 2); + memcpy(rec.deassert_evt_mask, p_rec->deassert_evt_mask, 2); + memcpy(rec.read_evt_mask, p_rec->read_evt_mask, 2); + rec.oem = p_rec->oem; + rec.str_type_len = p_rec->str_type_len; + memcpy(rec.str, p_rec->str, SENSOR_STR_SIZE); + + // Copy this record to generic SDR record + memcpy(sdr.rec, &rec, SDR_LEN_MAX); + + // Add this record to SDR repo + if (sdr_add_entry(&sdr, &rec_id)) { + syslog(LOG_ALERT, "sdr_add_disc_rec: sdr_add_entry failed\n"); + return -1; + } + + return 0; +} + +static int +sdr_add_thresh_rec(sensor_thresh_t *p_rec) { + int rec_id = 0; + sdr_rec_t sdr = { 0 }; + sdr_full_t rec = { 0 }; + + // Populate SDR FULL record + rec.ver = SDR_VERSION; + rec.type = SDR_FULL_TYPE; + rec.len = SDR_FULL_LEN; + + rec.owner = p_rec->owner; + rec.lun = p_rec->lun; + + rec.ent_id = p_rec->ent_id; + rec.ent_inst = p_rec->ent_inst; + rec.sensor_init = p_rec->sensor_init; + rec.sensor_caps = p_rec->sensor_caps; + rec.sensor_type = p_rec->sensor_type; + rec.evt_read_type = p_rec->evt_read_type; + memcpy(rec.lt_read_mask, p_rec->lt_read_mask, 2); + memcpy(rec.ut_read_mask, p_rec->ut_read_mask, 2); + memcpy(rec.set_thresh_mask, p_rec->set_thresh_mask, 2); + rec.sensor_units1 = p_rec->sensor_units1; + rec.sensor_units2 = p_rec->sensor_units2; + rec.sensor_units3 = p_rec->sensor_units3; + rec.linear = p_rec->linear; + rec.m_val = p_rec->m_val; + rec.m_tolerance = p_rec->m_tolerance; + rec.b_val = p_rec->b_val; + rec.b_accuracy = p_rec->b_accuracy; + rec.analog_flags = p_rec->analog_flags; + rec.nominal = p_rec->nominal; + rec.normal_max = p_rec->normal_max; + rec.normal_min = p_rec->normal_min; + rec.max_reading = p_rec->max_reading; + rec.min_reading = p_rec->min_reading; + rec.unr_thresh = p_rec->unr_thresh; + rec.uc_thresh = p_rec->uc_thresh; + rec.unc_thresh = p_rec->unc_thresh; + rec.lnr_thresh = p_rec->lnr_thresh; + rec.lc_thresh = p_rec->lc_thresh; + rec.lnc_thresh = p_rec->lnc_thresh; + rec.pos_hyst = p_rec->pos_hyst; + rec.neg_hyst = p_rec->neg_hyst; + rec.oem = p_rec->oem; + rec.str_type_len = p_rec->str_type_len; + memcpy(rec.str, p_rec->str, SENSOR_STR_SIZE); + + // Copy this record to generic SDR record + memcpy(sdr.rec, &rec, SDR_LEN_MAX); + + // Add this record to SDR repo + if (sdr_add_entry(&sdr, &rec_id)) { + syslog(LOG_ALERT, "sdr_add_thresh_rec: sdr_add_entry failed\n"); + return -1; + } + + return 0; +} + +static int +sdr_add_oem_rec(sensor_oem_t *p_rec) { + int rec_id = 0; + sdr_rec_t sdr = { 0 }; + sdr_oem_t rec = { 0 }; + + // Populate SDR OEM record + rec.ver = SDR_VERSION; + rec.type = SDR_OEM_TYPE; + rec.len = SDR_OEM_LEN; + + memcpy(rec.mfr_id, p_rec->mfr_id, 3); + memcpy(rec.oem_data, p_rec->oem_data, SENSOR_OEM_DATA_SIZE); + + // Copy this record to generic SDR record + memcpy(sdr.rec, &rec, SDR_LEN_MAX); + + // Add this record to SDR repo + if (sdr_add_entry(&sdr, &rec_id)) { + syslog(LOG_ALERT, "sdr_add_oem_rec: sdr_add_entry failed\n"); + return -1; + } + + return 0; +} + +// Platform specific SEL API entry points +// Retrieve time stamp for recent add operation +void +sdr_ts_recent_add(time_stamp_t *ts) { + memcpy(ts->ts, g_sdr_hdr.ts_add.ts, 0x04); +} + +// Retrieve time stamp for recent erase operation +void +sdr_ts_recent_erase(time_stamp_t *ts) { + memcpy(ts->ts, g_sdr_hdr.ts_erase.ts, 0x04); +} + +// Retrieve total number of entries in SDR repo +int +sdr_num_entries(void) { + return (g_sdr_hdr.end - g_sdr_hdr.begin); +} + +// Retrieve total free space available in SDR repo +int +sdr_free_space(void) { + int total_space; + int used_space; + + total_space = SDR_RECORDS_MAX * sizeof(sdr_rec_t); + used_space = sdr_num_entries() * sizeof(sdr_rec_t); + + return (total_space - used_space); +} + +// Reserve an ID that will be used in later operations +// IPMI/Section 33.11 +int +sdr_rsv_id() { + // Increment the current reservation ID and return + if (g_rsv_id++ == SDR_RSVID_MAX) { + g_rsv_id = SDR_RSVID_MIN; + } + + return g_rsv_id; +} + +// Get the SDR entry for a given record ID +// IPMI/Section 33.12 +int +sdr_get_entry(int rsv_id, int read_rec_id, sdr_rec_t *rec, + int *next_rec_id) { + + int index; + + // Make sure the rsv_id matches + if (rsv_id != g_rsv_id) { + syslog(LOG_ALERT, "sdr_get_entry: Reservation ID mismatch\n"); + return -1; + } + + // Find the index in to array based on given index + if (read_rec_id == SDR_RECID_FIRST) { + index = g_sdr_hdr.begin; + } else if (read_rec_id == SDR_RECID_LAST) { + index = g_sdr_hdr.end - 1; + } else { + index = read_rec_id - 1; + } + + // If the SDR repo is empty return error + if (sdr_num_entries() == 0) { + syslog(LOG_ALERT, "sdr_get_entry: No entries\n"); + return -1; + } + + // Check for boundary conditions + if ((index < SDR_INDEX_MIN) || (index > SDR_INDEX_MAX)) { + syslog(LOG_ALERT, "sdr_get_entry: Invalid Record ID %d\n", read_rec_id); + return -1; + } + + // Check to make sure the given id is valid + if (index < g_sdr_hdr.begin || index >= g_sdr_hdr.end) { + syslog(LOG_ALERT, "sdr_get_entry: Wrong Record ID %d\n", read_rec_id); + return -1; + } + + memcpy(rec->rec, g_sdr_data[index].rec, sizeof(sdr_rec_t)); + + // Return the next record ID in the log + *next_rec_id = ++read_rec_id; + + // If this is the last entry in the log, return 0xFFFF + if (*next_rec_id == g_sdr_hdr.end) { + *next_rec_id = SDR_RECID_LAST; + } + + return 0; +} + + +// Initialize SDR Repo structure +int +sdr_init(void) { + int num; + int i; + sensor_mgmt_t *p_mgmt; + sensor_thresh_t *p_thresh; + sensor_disc_t *p_disc; + sensor_oem_t *p_oem; + + // Populate SDR Header + g_sdr_hdr.magic = SDR_HDR_MAGIC; + g_sdr_hdr.version = SDR_HDR_VERSION; + g_sdr_hdr.begin = SDR_INDEX_MIN; + g_sdr_hdr.end = SDR_INDEX_MIN; + memset(g_sdr_hdr.ts_add.ts, 0x0, 4); + memset(g_sdr_hdr.ts_erase.ts, 0x0, 4); + + // Populate all mgmt control sensors + plat_sensor_mgmt_info(&num, &p_mgmt); + for (i = 0; i < num; i++) { + sdr_add_mgmt_rec(&p_mgmt[i]); + } + + // Populate all discrete sensors + plat_sensor_disc_info(&num, &p_disc); + for (i = 0; i < num; i++) { + sdr_add_disc_rec(&p_disc[i]); + } + + // Populate all threshold sensors + plat_sensor_thresh_info(&num, &p_thresh); + for (i = 0; i < num; i++) { + sdr_add_thresh_rec(&p_thresh[i]); + } + + // Populate all OEM sensors + plat_sensor_oem_info(&num, &p_oem); + for (i = 0; i < num; i++) { + sdr_add_oem_rec(&p_oem[i]); + } + + return 0; +} diff --git a/common/recipes-core/ipmid/files/sdr.h b/common/recipes-core/ipmid/files/sdr.h new file mode 100644 index 0000000..5c11c31 --- /dev/null +++ b/common/recipes-core/ipmid/files/sdr.h @@ -0,0 +1,72 @@ +/* + * + * Copyright 2014-present Facebook. All Rights Reserved. + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __SDR_H__ +#define __SDR_H__ + +#include "timestamp.h" + +typedef struct { + unsigned char rec[64]; +} sdr_rec_t; + +// Mgmt. Controller SDR record; IPMI/ Section 43.9 +typedef struct { + // Sensor Record Header + unsigned char rec_id[2]; + unsigned char ver; + unsigned char type; + unsigned char len; + // Record Key Bytes + unsigned char slave_addr; + unsigned char chan_no; + // Record Body Bytes + unsigned char pwr_state_init; + unsigned char dev_caps; + unsigned char rsvd[3]; + unsigned char ent_id; + unsigned char ent_inst; + unsigned char oem; + unsigned char str_type_len; + char str[16]; +} sdr_mgmt_t; + +// OEM type SDR record; IPMI/Section 43.12 +typedef struct { + // Sensor Record Header + unsigned char rec_id[2]; + unsigned char ver; + unsigned char type; + unsigned char len; + // Record Body Bytes + unsigned char mfr_id[3]; + unsigned char oem_data[56]; +} sdr_oem_t; + +void sdr_ts_recent_add(time_stamp_t *ts); +void sdr_ts_recent_erase(time_stamp_t *ts); +int sdr_num_entries(void); +int sdr_free_space(void); +int sdr_rsv_id(); +int sdr_get_entry(int rsv_id, int read_rec_id, sdr_rec_t *rec, + int *next_rec_id); +int sdr_init(void); + +#endif /* __SDR_H__ */ diff --git a/common/recipes-core/ipmid/files/sel.c b/common/recipes-core/ipmid/files/sel.c new file mode 100644 index 0000000..d598bb1 --- /dev/null +++ b/common/recipes-core/ipmid/files/sel.c @@ -0,0 +1,440 @@ +/* + * + * Copyright 2014-present Facebook. All Rights Reserved. + * + * This file represents platform specific implementation for storing + * SEL logs and acts as back-end for IPMI stack + * + * TODO: Optimize the file handling to keep file open always instead of + * current open/seek/close + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include "sel.h" +#include "timestamp.h" +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <syslog.h> +#include <string.h> + +// SEL File. +#define SEL_LOG_FILE "/mnt/data/sel.bin" + +// SEL Header magic number +#define SEL_HDR_MAGIC 0xFBFBFBFB + +// SEL Header version number +#define SEL_HDR_VERSION 0x01 + +// SEL Data offset from file beginning +#define SEL_DATA_OFFSET 0x100 + +// SEL reservation IDs can not be 0x00 or 0xFFFF +#define SEL_RSVID_MIN 0x01 +#define SEL_RSVID_MAX 0xFFFE + +// Number of SEL records before wrap +#define SEL_RECORDS_MAX 128 // TODO: Based on need we can make it bigger +#define SEL_ELEMS_MAX (SEL_RECORDS_MAX+1) + +// Index for circular array +#define SEL_INDEX_MIN 0x00 +#define SEL_INDEX_MAX SEL_RECORDS_MAX + +// Record ID can not be 0x0 (IPMI/Section 31) +#define SEL_RECID_MIN (SEL_INDEX_MIN+1) +#define SEL_RECID_MAX (SEL_INDEX_MAX+1) + +// Special RecID value for first and last (IPMI/Section 31) +#define SEL_RECID_FIRST 0x0000 +#define SEL_RECID_LAST 0xFFFF + +// SEL header struct to keep track of SEL Log entries +typedef struct { + int magic; // Magic number to check validity + int version; // version number of this header + int begin; // index to the begining of the log + int end; // index to end of the log + time_stamp_t ts_add; // last addition time stamp + time_stamp_t ts_erase; // last erase time stamp +} sel_hdr_t; + +// Keep track of last Reservation ID +static int g_rsv_id = 0x01; + +// Cached version of SEL Header and data +static sel_hdr_t g_sel_hdr; +static sel_msg_t g_sel_data[SEL_ELEMS_MAX]; + +// Local helper functions to interact with file system +static int +file_get_sel_hdr(void) { + FILE *fp; + + fp = fopen(SEL_LOG_FILE, "r"); + if (fp == NULL) { + return -1; + } + + if (fread(&g_sel_hdr, sizeof(sel_hdr_t), 1, fp) <= 0) { + syslog(LOG_ALERT, "file_get_sel_hdr: fread\n"); + fclose (fp); + return -1; + } + + fclose(fp); + return 0; +} + +static int +file_get_sel_data(void) { + FILE *fp; + int i, j; + + fp = fopen(SEL_LOG_FILE, "r"); + if (fp == NULL) { + syslog(LOG_ALERT, "file_get_sel_data: fopen\n"); + return -1; + } + + if (fseek(fp, SEL_DATA_OFFSET, SEEK_SET)) { + syslog(LOG_ALERT, "file_get_sel_data: fseek\n"); + fclose(fp); + return -1; + } + + unsigned char buf[SEL_ELEMS_MAX * 16]; + if (fread(buf, 1, SEL_ELEMS_MAX * sizeof(sel_msg_t), fp) <= 0) { + syslog(LOG_ALERT, "file_get_sel_data: fread\n"); + fclose(fp); + return -1; + } + + fclose(fp); + + for (i = 0; i < SEL_ELEMS_MAX; i++) { + for (j = 0; j < sizeof(sel_msg_t);j++) { + g_sel_data[i].msg[j] = buf[i*16 + j]; + } + } + + return 0; +} + +static int +file_store_sel_hdr(void) { + FILE *fp; + + fp = fopen(SEL_LOG_FILE, "r+"); + if (fp == NULL) { + syslog(LOG_ALERT, "file_store_sel_hdr: fopen\n"); + return -1; + } + + if (fwrite(&g_sel_hdr, sizeof(sel_hdr_t), 1, fp) <= 0) { + syslog(LOG_ALERT, "file_store_sel_hdr: fwrite\n"); + fclose(fp); + return -1; + } + + fclose(fp); + + return 0; +} + +static int +file_store_sel_data(int recId, sel_msg_t *data) { + FILE *fp; + int index; + + fp = fopen(SEL_LOG_FILE, "r+"); + if (fp == NULL) { + syslog(LOG_ALERT, "file_store_sel_data: fopen\n"); + return -1; + } + + // Records are stored using zero-based index + index = (recId-1) * sizeof(sel_msg_t); + + if (fseek(fp, SEL_DATA_OFFSET+index, SEEK_SET)) { + syslog(LOG_ALERT, "file_store_sel_data: fseek\n"); + fclose(fp); + return -1; + } + + if (fwrite(data->msg, sizeof(sel_msg_t), 1, fp) <= 0) { + syslog(LOG_ALERT, "file_store_sel_data: fwrite\n"); + fclose(fp); + return -1; + } + + fclose(fp); + + return 0; +} + +// Platform specific SEL API entry points +// Retrieve time stamp for recent add operation +void +sel_ts_recent_add(time_stamp_t *ts) { + memcpy(ts->ts, g_sel_hdr.ts_add.ts, 0x04); +} + +// Retrieve time stamp for recent erase operation +void +sel_ts_recent_erase(time_stamp_t *ts) { + memcpy(ts->ts, g_sel_hdr.ts_erase.ts, 0x04); +} + +// Retrieve total number of entries in SEL log +int +sel_num_entries(void) { + if (g_sel_hdr.begin <= g_sel_hdr.end) { + return (g_sel_hdr.end - g_sel_hdr.begin); + } else { + return (g_sel_hdr.end + (SEL_INDEX_MAX - g_sel_hdr.begin + 1)); + } +} + +// Retrieve total free space available in SEL log +int +sel_free_space(void) { + int total_space; + int used_space; + + total_space = SEL_RECORDS_MAX * sizeof(sel_msg_t); + used_space = sel_num_entries() * sizeof(sel_msg_t); + + return (total_space - used_space); +} + +// Reserve an ID that will be used in later operations +// IPMI/Section 31.4 +int +sel_rsv_id() { + // Increment the current reservation ID and return + if (g_rsv_id++ == SEL_RSVID_MAX) { + g_rsv_id = SEL_RSVID_MIN; + } + + return g_rsv_id; +} + +// Get the SEL entry for a given record ID +// IPMI/Section 31.5 +int +sel_get_entry(int read_rec_id, sel_msg_t *msg, int *next_rec_id) { + + int index; + + // Find the index in to array based on given index + if (read_rec_id == SEL_RECID_FIRST) { + index = g_sel_hdr.begin; + } else if (read_rec_id == SEL_RECID_LAST) { + if (g_sel_hdr.end) { + index = g_sel_hdr.end - 1; + } else { + index = SEL_INDEX_MAX; + } + } else { + index = read_rec_id - 1; + } + + // If the log is empty return error + if (sel_num_entries() == 0) { + syslog(LOG_ALERT, "sel_get_entry: No entries\n"); + return -1; + } + + // Check for boundary conditions + if ((index < SEL_INDEX_MIN) || (index > SEL_INDEX_MAX)) { + syslog(LOG_ALERT, "sel_get_entry: Invalid Record ID %d\n", read_rec_id); + return -1; + } + + // If begin < end, check to make sure the given id falls between + if (g_sel_hdr.begin < g_sel_hdr.end) { + if (index < g_sel_hdr.begin || index >= g_sel_hdr.end) { + syslog(LOG_ALERT, "sel_get_entry: Wrong Record ID %d\n", read_rec_id); + return -1; + } + } + + // If end < begin, check to make sure the given id is valid + if (g_sel_hdr.begin > g_sel_hdr.end) { + if (index >= g_sel_hdr.end && index < g_sel_hdr.begin) { + syslog(LOG_ALERT, "sel_get_entry: Wrong Record ID2 %d\n", read_rec_id); + return -1; + } + } + + memcpy(msg->msg, g_sel_data[index].msg, sizeof(sel_msg_t)); + + // Return the next record ID in the log + *next_rec_id = read_rec_id++; + if (*next_rec_id > SEL_INDEX_MAX) { + *next_rec_id = SEL_INDEX_MIN; + } + + // If this is the last entry in the log, return 0xFFFF + if (*next_rec_id == g_sel_hdr.end) { + *next_rec_id = SEL_RECID_LAST; + } + + return 0; +} + +// Add a new entry in to SEL log +// IPMI/Section 31.6 +int +sel_add_entry(sel_msg_t *msg, int *rec_id) { + // If the SEL if full, roll over. To keep track of empty condition, use + // one empty location less than the max records. + if (sel_num_entries() == SEL_RECORDS_MAX) { + syslog(LOG_ALERT, "sel_add_entry: SEL rollover\n"); + if (++g_sel_hdr.begin > SEL_INDEX_MAX) { + g_sel_hdr.begin = SEL_INDEX_MIN; + } + } + + // Update message's time stamp starting at byte 4 + time_stamp_fill(&msg->msg[3]); + + // Add the enry at end + memcpy(g_sel_data[g_sel_hdr.end].msg, msg->msg, sizeof(sel_msg_t)); + + // Return the newly added record ID + *rec_id = g_sel_hdr.end+1; + + if (file_store_sel_data(*rec_id, msg)) { + syslog(LOG_ALERT, "sel_add_entry: file_store_sel_data\n"); + return -1; + } + + // Increment the end pointer + if (++g_sel_hdr.end > SEL_INDEX_MAX) { + g_sel_hdr.end = SEL_INDEX_MIN; + } + + // Update timestamp for add in header + time_stamp_fill(g_sel_hdr.ts_add.ts); + + // Store the structure persistently + if (file_store_sel_hdr()) { + syslog(LOG_ALERT, "sel_add_entry: file_store_sel_hdr\n"); + return -1; + } + + return 0; +} + +// Erase the SEL completely +// IPMI/Section 31.9 +// Note: To reduce wear/tear, instead of erasing, manipulating the metadata +int +sel_erase(int rsv_id) { + if (rsv_id != g_rsv_id) { + return -1; + } + + // Erase SEL Logs + g_sel_hdr.begin = SEL_INDEX_MIN; + g_sel_hdr.end = SEL_INDEX_MIN; + + // Update timestamp for erase in header + time_stamp_fill(g_sel_hdr.ts_erase.ts); + + // Store the structure persistently + if (file_store_sel_hdr()) { + syslog(LOG_ALERT, "sel_erase: file_store_sel_hdr\n"); + return -1; + } + + return 0; +} + +// To get the erase status while erase happens +// IPMI/Section 31.2 +// Note: Since we are not doing offline erasing, need not return in-progress state +int +sel_erase_status(int rsv_id, sel_erase_stat_t *status) { + if (rsv_id != g_rsv_id) { + return -1; + } + + // Since we do not do any offline erasing, always return erase done + *status = SEL_ERASE_DONE; + + return 0; +} + +// Initialize SEL log file +int +sel_init(void) { + FILE *fp; + int i; + + // Check if the file exists or not + if (access(SEL_LOG_FILE, F_OK) == 0) { + // Since file is present, fetch all the contents to cache + if (file_get_sel_hdr()) { + syslog(LOG_ALERT, "init_sel: file_get_sel_hdr\n"); + return -1; + } + + if (file_get_sel_data()) { + syslog(LOG_ALERT, "init_sel: file_get_sel_data\n"); + return -1; + } + + return 0; + } + + // File not present, so create the file + fp = fopen(SEL_LOG_FILE, "w+"); + if (fp == NULL) { + syslog(LOG_ALERT, "init_sel: fopen\n"); + return -1; + } + + fclose (fp); + + // Populate SEL Header in to the file + g_sel_hdr.magic = SEL_HDR_MAGIC; + g_sel_hdr.version = SEL_HDR_VERSION; + g_sel_hdr.begin = SEL_INDEX_MIN; + g_sel_hdr.end = SEL_INDEX_MIN; + memset(g_sel_hdr.ts_add.ts, 0x0, 4); + memset(g_sel_hdr.ts_erase.ts, 0x0, 4); + + if (file_store_sel_hdr()) { + syslog(LOG_ALERT, "init_sel: file_store_sel_hdr\n"); + return -1; + } + + // Populate SEL Data in to the file + for (i = 1; i <= SEL_RECORDS_MAX; i++) { + sel_msg_t msg = {0}; + if (file_store_sel_data(i, &msg)) { + syslog(LOG_ALERT, "init_sel: file_store_sel_data\n"); + return -1; + } + } + + return 0; +} diff --git a/common/recipes-core/ipmid/files/sel.h b/common/recipes-core/ipmid/files/sel.h new file mode 100644 index 0000000..3bb9a2f --- /dev/null +++ b/common/recipes-core/ipmid/files/sel.h @@ -0,0 +1,51 @@ +/* + * + * Copyright 2014-present Facebook. All Rights Reserved. + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __SEL_H__ +#define __SEL_H__ + +#include "timestamp.h" + +enum { + IPMI_SEL_INIT_ERASE = 0xAA, + IPMI_SEL_ERASE_STAT = 0x00, +}; + +typedef enum { + SEL_ERASE_IN_PROG = 0x00, + SEL_ERASE_DONE = 0x01, +} sel_erase_stat_t; + +typedef struct { + unsigned char msg[16]; +} sel_msg_t; + +void sel_ts_recent_add(time_stamp_t *ts); +void sel_ts_recent_erase(time_stamp_t *ts); +int sel_num_entries(void); +int sel_free_space(void); +int sel_rsv_id(); +int sel_get_entry(int read_rec_id, sel_msg_t *msg, int *next_rec_id); +int sel_add_entry(sel_msg_t *msg, int *rec_id); +int sel_erase(int rsv_id); +int sel_erase_status(int rsv_id, sel_erase_stat_t *status); +int sel_init(void); + +#endif /* __SEL_H__ */ diff --git a/common/recipes-core/ipmid/files/sensor.h b/common/recipes-core/ipmid/files/sensor.h new file mode 100644 index 0000000..5d8c11a --- /dev/null +++ b/common/recipes-core/ipmid/files/sensor.h @@ -0,0 +1,117 @@ +/* + * + * Copyright 2014-present Facebook. All Rights Reserved. + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __SENSOR_H__ +#define __SENSOR_H__ + +#include "timestamp.h" + +#define IANA_ID_SIZE 3 +#define SENSOR_STR_SIZE 16 +#define SENSOR_OEM_DATA_SIZE 56 + +// Threshold Sensor Descriptor +typedef struct { + unsigned char owner; + unsigned char lun; + unsigned char sensor_num; + unsigned char ent_id; + unsigned char ent_inst; + unsigned char sensor_init; + unsigned char sensor_caps; + unsigned char sensor_type; + unsigned char evt_read_type; + unsigned char lt_read_mask[2]; + unsigned char ut_read_mask[2]; + unsigned char set_thresh_mask[2]; + unsigned char sensor_units1; + unsigned char sensor_units2; + unsigned char sensor_units3; + unsigned char linear; + unsigned char m_val; + unsigned char m_tolerance; + unsigned char b_val; + unsigned char b_accuracy; + unsigned char accuracy_dir; + unsigned char rb_exp; + unsigned char analog_flags; + unsigned char nominal; + unsigned char normal_max; + unsigned char normal_min; + unsigned char max_reading; + unsigned char min_reading; + unsigned char unr_thresh; + unsigned char uc_thresh; + unsigned char unc_thresh; + unsigned char lnr_thresh; + unsigned char lc_thresh; + unsigned char lnc_thresh; + unsigned char pos_hyst; + unsigned char neg_hyst; + unsigned char oem; + unsigned char str_type_len; + char str[SENSOR_STR_SIZE]; +} sensor_thresh_t; + +// Discrete Sensor Descriptor +typedef struct { + unsigned char owner; + unsigned char lun; + unsigned char sensor_num; + unsigned char ent_id; + unsigned char ent_inst; + unsigned char sensor_init; + unsigned char sensor_caps; + unsigned char sensor_type; + unsigned char evt_read_type; + unsigned char assert_evt_mask[2]; + unsigned char deassert_evt_mask[2]; + unsigned char read_evt_mask[2]; + unsigned char oem; + unsigned char str_type_len; + char str[SENSOR_STR_SIZE]; +} sensor_disc_t; + +// Mgmt. Controller Sensor Descriptor +typedef struct { + unsigned char slave_addr; + unsigned char chan_no; + unsigned char pwr_state_init; + unsigned char dev_caps; + unsigned char ent_id; + unsigned char ent_inst; + unsigned char oem; + unsigned char str_type_len; + char str[SENSOR_STR_SIZE]; +} sensor_mgmt_t; + +// OEM type Sensor Descriptor +typedef struct { + unsigned char mfr_id[IANA_ID_SIZE]; + unsigned char oem_data[SENSOR_OEM_DATA_SIZE]; +} sensor_oem_t; + +void plat_sensor_mgmt_info(int *num, sensor_mgmt_t **p_sensor); +void plat_sensor_disc_info(int *num, sensor_disc_t **p_sensor); +void plat_sensor_thresh_info(int *num, sensor_thresh_t **p_sensor); +void plat_sensor_oem_info(int *num, sensor_oem_t **p_sensor); +int plat_sensor_init(void); + +#endif /* __SENSOR_H__ */ diff --git a/common/recipes-core/ipmid/files/timestamp.c b/common/recipes-core/ipmid/files/timestamp.c new file mode 100644 index 0000000..11ac03e --- /dev/null +++ b/common/recipes-core/ipmid/files/timestamp.c @@ -0,0 +1,46 @@ +/* + * + * Copyright 2014-present Facebook. All Rights Reserved. + * + * This file is a helper file to fill timestamps from platform + * used by SEL Logs, SDR records etc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <syslog.h> +#include <string.h> +#include <sys/time.h> +#include <time.h> + +// Local helper function to fill time stamp +void +time_stamp_fill(unsigned char *ts) { + unsigned int time; + struct timeval tv; + + gettimeofday(&tv, NULL); + + time = tv.tv_sec; + ts[0] = time & 0xFF; + ts[1] = (time >> 8) & 0xFF; + ts[2] = (time >> 16) & 0xFF; + ts[3] = (time >> 24) & 0xFF; + + return; +} diff --git a/common/recipes-core/ipmid/files/timestamp.h b/common/recipes-core/ipmid/files/timestamp.h new file mode 100644 index 0000000..430dd23 --- /dev/null +++ b/common/recipes-core/ipmid/files/timestamp.h @@ -0,0 +1,30 @@ +/* + * + * Copyright 2014-present Facebook. All Rights Reserved. + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __TIMESTAMP_H__ +#define __TIMESTAMP_H__ + +typedef struct { + unsigned char ts[4]; +} time_stamp_t; + +void time_stamp_fill(unsigned char *ts); + +#endif /* __TIMESTAMP_H__ */ diff --git a/common/recipes-core/ipmid/ipmid_0.2.bb b/common/recipes-core/ipmid/ipmid_0.2.bb new file mode 100644 index 0000000..14c8c7f --- /dev/null +++ b/common/recipes-core/ipmid/ipmid_0.2.bb @@ -0,0 +1,42 @@ +# Copyright 2014-present Facebook. All Rights Reserved. +# +# This program file is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program in a file named COPYING; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, +# Boston, MA 02110-1301 USA + +SUMMARY = "IPMI Daemon" +DESCRIPTION = "Daemon to handle IPMI Messages." +SECTION = "base" +PR = "r1" +LICENSE = "GPLv2" +LIC_FILES_CHKSUM = "file://ipmid.c;beginline=8;endline=20;md5=da35978751a9d71b73679307c4d296ec" + + +SRC_URI = "file://Makefile \ + file://ipmid.c \ + file://timestamp.c \ + file://timestamp.h \ + file://sel.c \ + file://sel.h \ + file://sdr.c \ + file://sdr.h \ + file://sensor.h \ + file://fruid.h \ + " + +DEPENDS += " libpal " + +binfiles = "ipmid" + +pkgdir = "ipmid" diff --git a/common/recipes-core/power-util/files/Makefile b/common/recipes-core/power-util/files/Makefile new file mode 100644 index 0000000..97ee9d5 --- /dev/null +++ b/common/recipes-core/power-util/files/Makefile @@ -0,0 +1,11 @@ +# Copyright 2015-present Facebook. All Rights Reserved. +all: power-util + + +power-util: power-util.o + $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) + +.PHONY: clean + +clean: + rm -rf *.o power-util diff --git a/common/recipes-core/power-util/files/power-util.c b/common/recipes-core/power-util/files/power-util.c new file mode 100644 index 0000000..0fda9d6 --- /dev/null +++ b/common/recipes-core/power-util/files/power-util.c @@ -0,0 +1,295 @@ +/* + * power-util + * + * Copyright 2015-present Facebook. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <syslog.h> +#include <stdint.h> +#include <string.h> +#include <getopt.h> +#include <stdbool.h> +#include <fcntl.h> +#include <openbmc/pal.h> + +#define POWER_ON_STR "on" +#define POWER_OFF_STR "off" + +const char *pwr_option_list = "status, graceful-shutdown, off, on, cycle, 12V-off," + "12V-on, 12V-cycle"; + +enum { + PWR_STATUS = 1, + PWR_GRACEFUL_SHUTDOWN, + PWR_OFF, + PWR_ON, + PWR_CYCLE, + PWR_12V_OFF, + PWR_12V_ON, + PWR_12V_CYCLE, + PWR_SLED_CYCLE +}; + +static int +set_last_pwr_state(uint8_t fru, char * state) { + + int ret; + char key[MAX_KEY_LEN] = {0}; + + sprintf(key, "pwr_server%d_last_state", (int) fru); + + ret = pal_set_key_value(key, state); + if (ret < 0) { + syslog(LOG_ALERT, "set_last_pwr_state: pal_set_key_value failed for " + "fru %u", fru); + } + return ret; +} + +static void +print_usage() { + printf("Usage: power-util [ %s ] [ %s ]\nUsage: power-util sled-cycle\n", + pal_server_list, pwr_option_list); +} + +static int +get_power_opt(char *option, uint8_t *opt) { + + if (!strcmp(option, "status")) { + *opt = PWR_STATUS; + } else if (!strcmp(option, "graceful-shutdown")) { + *opt = PWR_GRACEFUL_SHUTDOWN; + } else if (!strcmp(option, "off")) { + *opt = PWR_OFF; + } else if (!strcmp(option, "on")) { + *opt = PWR_ON; + } else if (!strcmp(option, "cycle")) { + *opt = PWR_CYCLE; + } else if (!strcmp(option, "12V-off")) { + *opt = PWR_12V_OFF; + } else if (!strcmp(option, "12V-on")) { + *opt = PWR_12V_ON; + } else if (!strcmp(option, "12V-cycle")) { + *opt = PWR_12V_CYCLE; + } else if (!strcmp(option, "sled-cycle")) { + *opt = PWR_SLED_CYCLE; + } else { + return -1; + } + + return 0; +} + +static int +power_util(uint8_t fru, uint8_t opt) { + + int ret; + uint8_t status; + + switch(opt) { + case PWR_STATUS: + ret = pal_get_server_power(fru, &status); + if (ret < 0) { + syslog(LOG_ALERT, "power_util: pal_get_server_power failed for fru %u\n", fru); + return ret; + } + printf("Power status for fru %u : %s\n", fru, status?"ON":"OFF"); + break; + + case PWR_GRACEFUL_SHUTDOWN: + + printf("Shutting down fru %u gracefully...\n", fru); + + ret = pal_set_server_power(fru, SERVER_GRACEFUL_SHUTDOWN); + if (ret < 0) { + syslog(LOG_ALERT, "power_util: pal_set_server_power failed for" + " fru %u", fru); + return ret; + } else if (ret == 1) { + printf("fru %u is already powered OFF...\n", fru); + return 0; + } + + ret = set_last_pwr_state(fru, POWER_OFF_STR); + if (ret < 0) { + return ret; + } + + ret = pal_set_led(fru, LED_STATE_OFF); + if (ret < 0) { + syslog(LOG_ALERT, "power_util: pal_set_led failed for fru %u", fru); + return ret; + } + break; + + case PWR_OFF: + + printf("Powering fru %u to OFF state...\n", fru); + + ret = pal_set_server_power(fru, SERVER_POWER_OFF); + if (ret < 0) { + syslog(LOG_ALERT, "power_util: pal_set_server_power failed for" + " fru %u", fru); + return ret; + } else if (ret == 1) { + printf("fru %u is already powered OFF...\n", fru); + return 0; + } + + ret = set_last_pwr_state(fru, POWER_OFF_STR); + if (ret < 0) { + return ret; + } + + ret = pal_set_led(fru, LED_STATE_OFF); + if (ret < 0) { + syslog(LOG_ALERT, "power_util: pal_set_led failed for fru %u", fru); + return ret; + } + break; + + case PWR_ON: + + printf("Powering fru %u to ON state...\n", fru); + + ret = pal_set_server_power(fru, SERVER_POWER_ON); + if (ret < 0) { + syslog(LOG_ALERT, "power_util: pal_set_server_power failed for" + " fru %u", fru); + return ret; + } else if (ret == 1) { + printf("fru %u is already powered ON...\n", fru); + return 0; + } + + ret = set_last_pwr_state(fru, POWER_ON_STR); + if (ret < 0) { + return ret; + } + + ret = pal_set_led(fru, LED_STATE_ON); + if (ret < 0) { + syslog(LOG_ALERT, "power_util: pal_set_led failed for fru %u", fru); + return ret; + } + break; + + case PWR_CYCLE: + + printf("Power cycling fru %u...\n", fru); + + ret = pal_set_server_power(fru, SERVER_POWER_CYCLE); + if (ret < 0) { + syslog(LOG_ALERT, "power_util: pal_set_server_power failed for" + " fru %u", fru); + return ret; + } + + ret = set_last_pwr_state(fru, POWER_ON_STR); + if (ret < 0) { + return ret; + } + + ret = pal_set_led(fru, LED_STATE_ON); + if (ret < 0) { + syslog(LOG_ALERT, "power_util: pal_set_led failed for fru %u", fru); + return ret; + } + break; + + case PWR_12V_OFF: + ret = 0; // TODO: Need to add the API to support this power state setting + break; + + case PWR_12V_ON: + ret = 0; // TODO: Need to add the API to support this power state setting + break; + + case PWR_12V_CYCLE: + ret = 0; // TODO: Need to add the API to support this power state setting + break; + + case PWR_SLED_CYCLE: + pal_sled_cycle(); + break; + + default: + syslog(LOG_ALERT, "power_util: wrong option"); + + } + + return ret; +} + +int +main(int argc, char **argv) { + + int ret; + + uint8_t fru, status, opt; + char *option; + + /* Check for sled-cycle */ + if (argc < 2 || argc > 3) { + print_usage(); + exit (-1); + } + + option = argc == 2 ? argv[1] : argv [2]; + + ret = get_power_opt(option, &opt); + /* If argc is 2, the option is sled-cycle */ + if ((ret < 0) || (argc == 2 && opt != PWR_SLED_CYCLE)) { + printf("Wrong option: %s\n", option); + print_usage(); + exit(-1); + } + + if (argc > 2) { + ret = pal_get_fru_id(argv[1], &fru); + if (ret < 0) { + printf("Wrong fru: %s\n", argv[1]); + print_usage(); + exit(-1); + } + } else { + fru = -1; + } + + if (argc > 2) { + ret = pal_is_server_prsnt(fru, &status); + if (ret < 0) { + printf("pal_is_server_prsnt failed for fru: %d\n", fru); + print_usage(); + exit(-1); + } + if (status == 0) { + printf("%s is empty!\n", argv[1]); + print_usage(); + exit(-1); + } + } + + ret = power_util(fru, opt); + if (ret < 0) { + print_usage(); + return ret; + } +} diff --git a/common/recipes-core/power-util/power-util_0.1.bb b/common/recipes-core/power-util/power-util_0.1.bb new file mode 100644 index 0000000..69ef2ad --- /dev/null +++ b/common/recipes-core/power-util/power-util_0.1.bb @@ -0,0 +1,36 @@ +# Copyright 2015-present Facebook. All Rights Reserved. +SUMMARY = "Power Utility" +DESCRIPTION = "Utility for Power Policy and Management" +SECTION = "base" +PR = "r1" +LICENSE = "GPLv2" +LIC_FILES_CHKSUM = "file://power-util.c;beginline=4;endline=16;md5=b395943ba8a0717a83e62ca123a8d238" + +SRC_URI = "file://Makefile \ + file://power-util.c \ + " +S = "${WORKDIR}" + +LDFLAGS =+ " -lpal " + +DEPENDS =+ " libpal " + +binfiles = "power-util" + +pkgdir = "power-util" + +do_install() { + dst="${D}/usr/local/fbpackages/${pkgdir}" + bin="${D}/usr/local/bin" + install -d $dst + install -d $bin + install -m 755 power-util ${dst}/power-util + ln -snf ../fbpackages/${pkgdir}/power-util ${bin}/power-util +} + +FBPACKAGEDIR = "${prefix}/local/fbpackages" + +FILES_${PN} = "${FBPACKAGEDIR}/power-util ${prefix}/local/bin" + +INHIBIT_PACKAGE_DEBUG_SPLIT = "1" +INHIBIT_PACKAGE_STRIP = "1" diff --git a/common/recipes-core/sensor-mon/files/Makefile b/common/recipes-core/sensor-mon/files/Makefile new file mode 100644 index 0000000..68a9fe7 --- /dev/null +++ b/common/recipes-core/sensor-mon/files/Makefile @@ -0,0 +1,26 @@ +# Copyright 2015-present Facebook. All Rights Reserved. +# +# This program file is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program in a file named COPYING; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, +# Boston, MA 02110-1301 USA + +all: sensord + +sensord: sensord.c + $(CC) $(CFLAGS) -D _XOPEN_SOURCE -pthread -lm -std=c99 -o $@ $^ $(LDFLAGS) + +.PHONY: clean + +clean: + rm -rf *.o sensord diff --git a/common/recipes-core/sensor-mon/files/sensord.c b/common/recipes-core/sensor-mon/files/sensord.c new file mode 100644 index 0000000..1852af3 --- /dev/null +++ b/common/recipes-core/sensor-mon/files/sensord.c @@ -0,0 +1,1031 @@ +/* + * sensord + * + * Copyright 2015-present Facebook. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <unistd.h> +#include <errno.h> +#include <syslog.h> +#include <stdint.h> +#include <math.h> +#include <string.h> +#include <pthread.h> +#include <sys/file.h> +#include <facebook/bic.h> +#include <openbmc/ipmi.h> +#include <openbmc/sdr.h> +#ifdef CONFIG_YOSEMITE +#include <facebook/yosemite_sensor.h> +#endif /* CONFIG_YOSEMITE */ + +#define MAX_SENSOR_NUM 0xFF +#define NORMAL_STATE 0x00 + +#define SETBIT(x, y) (x | (1 << y)) +#define GETBIT(x, y) ((x & (1 << y)) > y) +#define CLEARBIT(x, y) (x & (~(1 << y))) +#define GETMASK(y) (1 << y) + + + +/* Enum for type of Upper and Lower threshold values */ +enum { + UCR_THRESH = 0x01, + UNC_THRESH, + UNR_THRESH, + LCR_THRESH, + LNC_THRESH, + LNR_THRESH, + POS_HYST, + NEG_HYST, +}; + +/* To hold the sensor info and calculated threshold values from the SDR */ +typedef struct { + uint8_t flag; + float ucr_thresh; + float unc_thresh; + float unr_thresh; + float lcr_thresh; + float lnc_thresh; + float lnr_thresh; + float pos_hyst; + float neg_hyst; + int curr_state; + char name[23]; + char units[64]; + +} thresh_sensor_t; + +/* Function pointer to read sensor current value */ +static int (*read_snr_val)(uint8_t, uint8_t, void *); + +#ifdef CONFIG_YOSEMITE + +// TODO: Change to 6 after adding SPB and NIC +#define MAX_NUM_FRUS 4 +#define YOSEMITE_SDR_PATH "/tmp/sdr_%s.bin" + +static thresh_sensor_t snr_slot1[MAX_SENSOR_NUM] = {0}; +static thresh_sensor_t snr_slot2[MAX_SENSOR_NUM] = {0}; +static thresh_sensor_t snr_slot3[MAX_SENSOR_NUM] = {0}; +static thresh_sensor_t snr_slot4[MAX_SENSOR_NUM] = {0}; +static thresh_sensor_t snr_spb[MAX_SENSOR_NUM] = {0}; +static thresh_sensor_t snr_nic[MAX_SENSOR_NUM] = {0}; + +static sensor_info_t sinfo_slot1[MAX_SENSOR_NUM] = {0}; +static sensor_info_t sinfo_slot2[MAX_SENSOR_NUM] = {0}; +static sensor_info_t sinfo_slot3[MAX_SENSOR_NUM] = {0}; +static sensor_info_t sinfo_slot4[MAX_SENSOR_NUM] = {0}; +static sensor_info_t sinfo_spb[MAX_SENSOR_NUM] = {0}; +static sensor_info_t sinfo_nic[MAX_SENSOR_NUM] = {0}; +#endif /* CONFIG_YOSEMITE */ + + +/* + * Returns the pointer to the struct holding all sensor info and + * calculated threshold values for the fru# + */ +static thresh_sensor_t * +get_struct_thresh_sensor(uint8_t fru) { + + thresh_sensor_t *snr; + +#ifdef CONFIG_YOSEMITE + switch (fru) { + case FRU_SLOT1: + snr = snr_slot1; + break; + case FRU_SLOT2: + snr = snr_slot2; + break; + case FRU_SLOT3: + snr = snr_slot3; + break; + case FRU_SLOT4: + snr = snr_slot4; + break; + case FRU_SPB: + snr = snr_spb; + break; + case FRU_NIC: + snr = snr_nic; + break; + default: + syslog(LOG_ALERT, "get_struct_thresh_sensor: Wrong FRU ID %d\n", fru); + return NULL; + } +#endif /* CONFIG_YOSEMITE */ + + return snr; +} + + +/* Returns the all the SDRs for the particular fru# */ +static sensor_info_t * +get_struct_sensor_info(uint8_t fru) { + + sensor_info_t *sinfo; + +#ifdef CONFIG_YOSEMITE + switch (fru) { + case FRU_SLOT1: + sinfo = sinfo_slot1; + break; + case FRU_SLOT2: + sinfo = sinfo_slot2; + break; + case FRU_SLOT3: + sinfo = sinfo_slot3; + break; + case FRU_SLOT4: + sinfo = sinfo_slot4; + break; + case FRU_SPB: + sinfo = sinfo_spb; + break; + case FRU_NIC: + sinfo = sinfo_nic; + break; + default: + syslog(LOG_ALERT, "get_struct_sensor_info: Wrong FRU ID %d\n", fru); + return NULL; + } +#endif /* CONFIG_YOSEMITE */ + + return sinfo; +} + + +/* Returns the SDR for a particular sensor of particular fru# */ +static sdr_full_t * +get_struct_sdr(uint8_t fru, uint8_t snr_num) { + + sdr_full_t *sdr; + sensor_info_t *sinfo; + sinfo = get_struct_sensor_info(fru); + if (sinfo == NULL) { + syslog(LOG_ALERT, "get_struct_sdr: get_struct_sensor_info failed\n"); + return NULL; + } + sdr = &sinfo[snr_num].sdr; + return sdr; +} + +/* Get the threshold values from the SDRs */ +static int +get_sdr_thresh_val(uint8_t fru, uint8_t snr_num, uint8_t thresh, void *value) { + + int ret; + uint8_t x; + uint8_t m_lsb, m_msb, m; + uint8_t b_lsb, b_msb, b; + int8_t b_exp, r_exp; + uint8_t thresh_val; + sdr_full_t *sdr; + + sdr = get_struct_sdr(fru, snr_num); + if (sdr == NULL) { + syslog(LOG_ALERT, "get_sdr_thresh_val: get_struct_sdr failed\n"); + return -1; + } + + switch (thresh) { + case UCR_THRESH: + thresh_val = sdr->uc_thresh; + break; + case UNC_THRESH: + thresh_val = sdr->unc_thresh; + break; + case UNR_THRESH: + thresh_val = sdr->unr_thresh; + break; + case LCR_THRESH: + thresh_val = sdr->lc_thresh; + break; + case LNC_THRESH: + thresh_val = sdr->lnc_thresh; + break; + case LNR_THRESH: + thresh_val = sdr->lnr_thresh; + break; + case POS_HYST: + thresh_val = sdr->pos_hyst; + break; + case NEG_HYST: + thresh_val = sdr->neg_hyst; + break; + default: + syslog(LOG_ERR, "get_sdr_thresh_val: reading unknown threshold val"); + return -1; + } + + // y = (mx + b * 10^b_exp) * 10^r_exp + x = thresh_val; + + m_lsb = sdr->m_val; + m_msb = sdr->m_tolerance >> 6; + m = (m_msb << 8) | m_lsb; + + b_lsb = sdr->b_val; + b_msb = sdr->b_accuracy >> 6; + b = (b_msb << 8) | b_lsb; + + // exponents are 2's complement 4-bit number + b_exp = sdr->rb_exp & 0xF; + if (b_exp > 7) { + b_exp = (~b_exp + 1) & 0xF; + b_exp = -b_exp; + } + r_exp = (sdr->rb_exp >> 4) & 0xF; + if (r_exp > 7) { + r_exp = (~r_exp + 1) & 0xF; + r_exp = -r_exp; + } + + * (float *) value = ((m * x) + (b * pow(10, b_exp))) * (pow(10, r_exp)); + + return 0; +} + +/* + * Populate all fields of thresh_sensor_t struct for a particular sensor. + * Incase the threshold value is 0 mask the check for that threshvold + * value in flag field. + */ +int +init_snr_thresh(uint8_t fru, uint8_t snr_num, uint8_t flag) { + + int value; + float fvalue; + uint8_t op, modifier; + thresh_sensor_t *snr; + + sdr_full_t *sdr; + + sdr = get_struct_sdr(fru, snr_num); + if (sdr == NULL) { + syslog(LOG_ALERT, "init_snr_name: get_struct_sdr failed\n"); + return -1; + } + + snr = get_struct_thresh_sensor(fru); + if (snr == NULL) { + syslog(LOG_ALERT, "init_snr_thresh: get_struct_thresh_sensor failed"); + return -1; + } + + snr[snr_num].flag = flag; + snr[snr_num].curr_state = NORMAL_STATE; + + if (sdr_get_sensor_name(sdr, snr[snr_num].name)) { + syslog(LOG_ALERT, "sdr_get_sensor_name: FRU %d: num: 0x%X: reading name" + " from SDR failed.", fru, snr_num); + return -1; + } + + // TODO: Add support for modifier (Mostly modifier is zero) + if (sdr_get_sensor_units(sdr, &op, &modifier, snr[snr_num].units)) { + syslog(LOG_ALERT, "sdr_get_sensor_units: FRU %d: num 0x%X: reading units" + " from SDR failed.", fru, snr_num); + return -1; + } + + if (get_sdr_thresh_val(fru, snr_num, UCR_THRESH, &fvalue)) { + syslog(LOG_ERR, + "get_sdr_thresh_val: failed for FRU: %d, num: 0x%X, %-16s, UCR_THRESH", + fru, snr_num, snr[snr_num].name); + } else { + snr[snr_num].ucr_thresh = fvalue; + if (!(fvalue)) { + snr[snr_num].flag = CLEARBIT(snr[snr_num].flag, UCR_THRESH); + syslog(LOG_ALERT, + "FRU: %d, num: 0x%X, %-16s, UCR_THRESH check disabled val->%.2f", + fru, snr_num, snr[snr_num].name, fvalue); + } + } + + if (get_sdr_thresh_val(fru, snr_num, UNC_THRESH, &fvalue)) { + syslog(LOG_ERR, + "get_sdr_thresh_val: failed for FRU: %d, num: 0x%X, %-16s, UNC_THRESH", + fru, snr_num, snr[snr_num].name); + } else { + snr[snr_num].unc_thresh = fvalue; + if (!(fvalue)) { + snr[snr_num].flag = CLEARBIT(snr[snr_num].flag, UNC_THRESH); + syslog(LOG_ALERT, + "FRU: %d, num: 0x%X, %-16s, UNC_THRESH check disabled val->%.2f", + fru, snr_num, snr[snr_num].name, fvalue); + } + } + + if (get_sdr_thresh_val(fru, snr_num, UNR_THRESH, &fvalue)) { + syslog(LOG_ERR, + "get_sdr_thresh_val: failed for FRU: %d, num: 0x%X, %-16s, UNR_THRESH", + fru, snr_num, snr[snr_num].name); + } else { + snr[snr_num].unr_thresh = fvalue; + if (!(fvalue)) { + snr[snr_num].flag = CLEARBIT(snr[snr_num].flag, UNR_THRESH); + syslog(LOG_ALERT, + "FRU: %d, num: 0x%X, %-16s, UNR_THRESH check disabled val->%.2f", + fru, snr_num, snr[snr_num].name, fvalue); + } + } + + if (get_sdr_thresh_val(fru, snr_num, LCR_THRESH, &fvalue)) { + syslog(LOG_ERR, + "get_sdr_thresh_val: failed for FRU: %d, num: 0x%X, %-16s, LCR_THRESH", + fru, snr_num, snr[snr_num].name); + } else { + snr[snr_num].lcr_thresh = fvalue; + if (!(fvalue)) { + snr[snr_num].flag = CLEARBIT(snr[snr_num].flag, LCR_THRESH); + syslog(LOG_ALERT, + "FRU: %d, num: 0x%X, %-16s, LCR_THRESH check disabled val->%.2f", + fru, snr_num, snr[snr_num].name, fvalue); + } + } + + if (get_sdr_thresh_val(fru, snr_num, LNC_THRESH, &fvalue)) { + syslog(LOG_ERR, + "get_sdr_thresh_val: failed for FRU: %d, num: 0x%X, %-16s, LNC_THRESH", + fru, snr_num, snr[snr_num].name); + } else { + snr[snr_num].lnc_thresh = fvalue; + if (!(fvalue)) { + snr[snr_num].flag = CLEARBIT(snr[snr_num].flag, LNC_THRESH); + syslog(LOG_ALERT, + "FRU: %d, num: 0x%X, %-16s, LNC_THRESH check disabled val->%.2f", + fru, snr_num, snr[snr_num].name, fvalue); + } + } + + if (get_sdr_thresh_val(fru, snr_num, LNR_THRESH, &fvalue)) { + syslog(LOG_ERR, + "get_sdr_thresh_val: failed for FRU: %d, num: 0x%X, %-16s, LNR_THRESH", + fru, snr_num, snr[snr_num].name); + } else { + snr[snr_num].lnr_thresh = fvalue; + if (!(fvalue)) { + snr[snr_num].flag = CLEARBIT(snr[snr_num].flag, LNR_THRESH); + syslog(LOG_ALERT, + "FRU: %d, num: 0x%X, %-16s, LNR_THRESH check disabled val->%.2f", + fru, snr_num, snr[snr_num].name, fvalue); + } + } + + if (get_sdr_thresh_val(fru, snr_num, POS_HYST, &fvalue)) { + syslog(LOG_ERR, "get_sdr_thresh_val: failed for FRU: %d, num: 0x%X, %-16s, POS_HYST", + fru, snr_num, snr[snr_num].name); + } else + snr[snr_num].pos_hyst = fvalue; + + if (get_sdr_thresh_val(fru, snr_num, NEG_HYST, &fvalue)) { + syslog(LOG_ERR, "get_sdr_thresh_val: failed for FRU: %d, num: 0x%X, %-16s, NEG_HYST", + fru, snr_num, snr[snr_num].name); + } else + snr[snr_num].neg_hyst = fvalue; + + return 0; +} + +#ifdef CONFIG_YOSEMITE +/* Initialize all thresh_sensor_t structs for all the Yosemite sensors */ +static void +init_yosemite_snr_thresh(uint8_t fru) { + + int i; + + switch (fru) { + case FRU_SLOT1: + case FRU_SLOT2: + case FRU_SLOT3: + case FRU_SLOT4: + + for (i < 0; i < bic_sensor_cnt; i++) { + init_snr_thresh(fru, bic_sensor_list[i], GETMASK(UCR_THRESH) | GETMASK(LCR_THRESH)); + } + /* + init_snr_thresh(fru, BIC_SENSOR_MB_OUTLET_TEMP, GETMASK(UCR_THRESH)); + + init_snr_thresh(fru, BIC_SENSOR_VCCIN_VR_TEMP, GETMASK(UCR_THRESH)); + + init_snr_thresh(fru, BIC_SENSOR_VCC_GBE_VR_TEMP, GETMASK(UCR_THRESH)); + + init_snr_thresh(fru, BIC_SENSOR_1V05PCH_VR_TEMP, GETMASK(UCR_THRESH)); + + init_snr_thresh(fru, BIC_SENSOR_SOC_TEMP, GETMASK(UCR_THRESH)); + + init_snr_thresh(fru, BIC_SENSOR_MB_INLET_TEMP, GETMASK(UCR_THRESH)); + + init_snr_thresh(fru, BIC_SENSOR_PCH_TEMP, GETMASK(UCR_THRESH)); + + init_snr_thresh(fru, BIC_SENSOR_SOC_THERM_MARGIN, GETMASK(UCR_THRESH)); + + init_snr_thresh(fru, BIC_SENSOR_VDDR_VR_TEMP, GETMASK(UCR_THRESH)); + + init_snr_thresh(fru, BIC_SENSOR_VCC_GBE_VR_CURR, GETMASK(UCR_THRESH)); + + init_snr_thresh(fru, BIC_SENSOR_1V05_PCH_VR_CURR, GETMASK(UCR_THRESH)); + + init_snr_thresh(fru, BIC_SENSOR_VCCIN_VR_POUT, GETMASK(UCR_THRESH)); + + init_snr_thresh(fru, BIC_SENSOR_VCCIN_VR_CURR, GETMASK(UCR_THRESH)); + + init_snr_thresh(fru, BIC_SENSOR_VCCIN_VR_VOL, + GETMASK(UCR_THRESH) | GETMASK(LCR_THRESH)); + + init_snr_thresh(fru, BIC_SENSOR_INA230_POWER, GETMASK(UCR_THRESH)); + + init_snr_thresh(fru, BIC_SENSOR_SOC_PACKAGE_PWR, GETMASK(UCR_THRESH)); + + init_snr_thresh(fru, BIC_SENSOR_SOC_TJMAX, GETMASK(UCR_THRESH)); + + init_snr_thresh(fru, BIC_SENSOR_VDDR_VR_POUT, GETMASK(UCR_THRESH)); + + init_snr_thresh(fru, BIC_SENSOR_VDDR_VR_CURR, GETMASK(UCR_THRESH)); + + init_snr_thresh(fru, BIC_SENSOR_VDDR_VR_VOL, + GETMASK(UCR_THRESH) | GETMASK(LCR_THRESH)); + + init_snr_thresh(fru, BIC_SENSOR_VCC_SCSUS_VR_CURR, GETMASK(UCR_THRESH)); + + init_snr_thresh(fru, BIC_SENSOR_VCC_SCSUS_VR_VOL, + GETMASK(UCR_THRESH) | GETMASK(LCR_THRESH)); + + init_snr_thresh(fru, BIC_SENSOR_VCC_SCSUS_VR_TEMP, GETMASK(UCR_THRESH)); + + init_snr_thresh(fru, BIC_SENSOR_VCC_SCSUS_VR_POUT, GETMASK(UCR_THRESH)); + + init_snr_thresh(fru, BIC_SENSOR_VCC_GBE_VR_POUT, GETMASK(UCR_THRESH)); + + init_snr_thresh(fru, BIC_SENSOR_VCC_GBE_VR_VOL, + GETMASK(UCR_THRESH) | GETMASK(LCR_THRESH)); + + init_snr_thresh(fru, BIC_SENSOR_1V05_PCH_VR_VOL, + GETMASK(UCR_THRESH) | GETMASK(LCR_THRESH)); + + init_snr_thresh(fru, BIC_SENSOR_SOC_DIMMA0_TEMP, GETMASK(UCR_THRESH)); + + init_snr_thresh(fru, BIC_SENSOR_SOC_DIMMA1_TEMP, GETMASK(UCR_THRESH)); + + init_snr_thresh(fru, BIC_SENSOR_SOC_DIMMB0_TEMP, GETMASK(UCR_THRESH)); + + init_snr_thresh(fru, BIC_SENSOR_SOC_DIMMB1_TEMP, GETMASK(UCR_THRESH)); + + init_snr_thresh(fru, BIC_SENSOR_P3V3_MB, + GETMASK(UCR_THRESH) | GETMASK(LCR_THRESH)); + + init_snr_thresh(fru, BIC_SENSOR_P12V_MB, + GETMASK(UCR_THRESH) | GETMASK(LCR_THRESH)); + + init_snr_thresh(fru, BIC_SENSOR_P1V05_PCH, + GETMASK(UCR_THRESH) | GETMASK(LCR_THRESH)); + + init_snr_thresh(fru, BIC_SENSOR_P3V3_STBY_MB, + GETMASK(UCR_THRESH) | GETMASK(LCR_THRESH)); + + init_snr_thresh(fru, BIC_SENSOR_P5V_STBY_MB, + GETMASK(UCR_THRESH) | GETMASK(LCR_THRESH)); + + init_snr_thresh(fru, BIC_SENSOR_PV_BAT, + GETMASK(UCR_THRESH) | GETMASK(LCR_THRESH)); + + init_snr_thresh(fru, BIC_SENSOR_PVDDR, + GETMASK(UCR_THRESH) | GETMASK(LCR_THRESH)); + + init_snr_thresh(fru, BIC_SENSOR_PVCC_GBE, + GETMASK(UCR_THRESH) | GETMASK(LCR_THRESH)); + // TODO: Add Support for Discrete sensors + // init_snr_thresh(fru, BIC_SENSOR_POST_ERR, //Event-only + // init_snr_thresh(fru, BIC_SENSOR_SYSTEM_STATUS, //Discrete + // init_snr_thresh(fru, BIC_SENSOR_SPS_FW_HLTH); //Event-only + // init_snr_thresh(fru, BIC_SENSOR_POWER_THRESH_EVENT, //Event-only + // init_snr_thresh(fru, BIC_SENSOR_MACHINE_CHK_ERR, //Event-only + // init_snr_thresh(fru, BIC_SENSOR_PCIE_ERR); //Event-only + // init_snr_thresh(fru, BIC_SENSOR_OTHER_IIO_ERR); //Event-only + // init_snr_thresh(fru, BIC_SENSOR_PROC_HOT_EXT); //Event-only + // init_snr_thresh(fru, BIC_SENSOR_POWER_ERR); //Event-only + // init_snr_thresh(fru, , ); //Event-only + // init_snr_thresh(fru, BIC_SENSOR_PROC_FAIL); //Discrete + // init_snr_thresh(fru, BIC_SENSOR_SYS_BOOT_STAT ); //Discrete + // init_snr_thresh(fru, BIC_SENSOR_VR_HOT); //Discrete + // init_snr_thresh(fru, BIC_SENSOR_CPU_DIMM_HOT ); //Discrete + // init_snr_thresh(fru, BIC_SENSOR_CAT_ERR, //Event-only + + */ + + break; + + case FRU_SPB: + // TODO: Add support for threshold calculation for SP sensors + /* + init_snr_thresh(fru, SP_SENSOR_INLET_TEMP, "SP_SENSOR_INLET_TEMP"); + init_snr_thresh(fru, SP_SENSOR_OUTLET_TEMP, "SP_SENSOR_OUTLET_TEMP"); + init_snr_thresh(fru, SP_SENSOR_MEZZ_TEMP, "SP_SENSOR_MEZZ_TEMP"); + init_snr_thresh(fru, SP_SENSOR_FAN0_TACH, "SP_SENSOR_FAN0_TACH"); + init_snr_thresh(fru, SP_SENSOR_FAN1_TACH, "SP_SENSOR_FAN1_TACH"); + init_snr_thresh(fru, SP_SENSOR_AIR_FLOW, "SP_SENSOR_AIR_FLOW"); + init_snr_thresh(fru, SP_SENSOR_P5V, "SP_SENSOR_P5V"); + init_snr_thresh(fru, SP_SENSOR_P12V, "SP_SENSOR_P12V"); + init_snr_thresh(fru, SP_SENSOR_P3V3_STBY, "SP_SENSOR_P3V3_STBY"); + init_snr_thresh(fru, SP_SENSOR_P12V_SLOT0, "SP_SENSOR_P12V_SLOT0"); + init_snr_thresh(fru, SP_SENSOR_P12V_SLOT1, "SP_SENSOR_P12V_SLOT1"); + init_snr_thresh(fru, SP_SENSOR_P12V_SLOT2, "SP_SENSOR_P12V_SLOT2"); + init_snr_thresh(fru, SP_SENSOR_P12V_SLOT3, "SP_SENSOR_P12V_SLOT3"); + init_snr_thresh(fru, SP_SENSOR_P3V3, "SP_SENSOR_P3V3"); + init_snr_thresh(fru, SP_SENSOR_HSC_IN_VOLT, "SP_SENSOR_HSC_IN_VOLT"); + init_snr_thresh(fru, SP_SENSOR_HSC_OUT_CURR, "SP_SENSOR_HSC_OUT_CURR"); + init_snr_thresh(fru, SP_SENSOR_HSC_TEMP, "SP_SENSOR_HSC_TEMP"); + init_snr_thresh(fru, SP_SENSOR_HSC_IN_POWER, "SP_SENSOR_HSC_IN_POWER"); + */ + break; + + case FRU_NIC: + // TODO: Add support for NIC sensor threshold, if any. + break; + + default: + syslog(LOG_ALERT, "init_yosemite_snr_thresh: wrong FRU ID"); + exit(-1); + } +} +#endif /* CONFIG_YOSEMITE */ + +/* Wrapper function to initialize all the platform sensors */ +static void +init_all_snr_thresh() { + int fru; + + char path[64] = {0}; + sensor_info_t *sinfo; + + +#ifdef CONFIG_YOSEMITE + for (fru = FRU_SLOT1; fru < (FRU_SLOT1 + MAX_NUM_FRUS); fru++) { + + if (get_fru_sdr_path(fru, path) < 0) { + syslog(LOG_ALERT, "yosemite_sdr_init: get_fru_sdr_path failed\n"); + continue; + } +#endif /* CONFIG_YOSEMITE */ + + sinfo = get_struct_sensor_info(fru); + + if (sdr_init(path, sinfo) < 0) + syslog(LOG_ERR, "init_all_snr_thresh: sdr_init failed for FRU %d", fru); + } + +#ifdef CONFIG_YOSEMITE + for (fru = FRU_SLOT1; fru < (FRU_SLOT1 + MAX_NUM_FRUS); fru++) { + init_yosemite_snr_thresh(fru); + } +#endif /* CONFIG_YOSEMITE */ +} + + + + +static float +get_snr_thresh_val(uint8_t fru, uint8_t snr_num, uint8_t thresh) { + + float val; + thresh_sensor_t *snr; + + snr = get_struct_thresh_sensor(fru); + + switch (thresh) { + case UCR_THRESH: + val = snr[snr_num].ucr_thresh; + break; + case UNC_THRESH: + val = snr[snr_num].unc_thresh; + break; + case UNR_THRESH: + val = snr[snr_num].unr_thresh; + break; + case LCR_THRESH: + val = snr[snr_num].lcr_thresh; + break; + case LNC_THRESH: + val = snr[snr_num].lnc_thresh; + break; + case LNR_THRESH: + val = snr[snr_num].lnr_thresh; + break; + default: + syslog(LOG_ALERT, "get_snr_thresh_val: wrong thresh enum value"); + exit(-1); + } + + return val; +} + +/* + * Check the curr sensor values against the threshold and + * if the curr val has deasserted, log it. + */ +static void +check_thresh_deassert(uint8_t fru, uint8_t snr_num, uint8_t thresh, + float curr_val) { + uint8_t curr_state = 0; + float thresh_val; + char thresh_name[100]; + thresh_sensor_t *snr; + + snr = get_struct_thresh_sensor(fru); + + if (!GETBIT(snr[snr_num].flag, thresh) || + !GETBIT(snr[snr_num].curr_state, thresh)) + return; + + thresh_val = get_snr_thresh_val(fru, snr_num, thresh); + + switch (thresh) { + case UNC_THRESH: + if (curr_val <= thresh_val) { + curr_state = ~(SETBIT(curr_state, UNR_THRESH) | + SETBIT(curr_state, UCR_THRESH) | + SETBIT(curr_state, UNC_THRESH)); + sprintf(thresh_name, "Upper Non Critical"); + } + break; + + case UCR_THRESH: + if (curr_val <= thresh_val) { + curr_state = ~(SETBIT(curr_state, UCR_THRESH) | + SETBIT(curr_state, UNR_THRESH)); + sprintf(thresh_name, "Upper Critical"); + } + break; + + case UNR_THRESH: + if (curr_val <= thresh_val) { + curr_state = ~(SETBIT(curr_state, UNR_THRESH)); + sprintf(thresh_name, "Upper Non Recoverable"); + } + break; + + case LNC_THRESH: + if (curr_val >= thresh_val) { + curr_state = ~(SETBIT(curr_state, LNR_THRESH) | + SETBIT(curr_state, LCR_THRESH) | + SETBIT(curr_state, LNC_THRESH)); + sprintf(thresh_name, "Lower Non Critical"); + } + break; + + case LCR_THRESH: + if (curr_val >= thresh_val) { + curr_state = ~(SETBIT(curr_state, LCR_THRESH) | + SETBIT(curr_state, LNR_THRESH)); + sprintf(thresh_name, "Lower Critical"); + } + break; + + case LNR_THRESH: + if (curr_val >= thresh_val) { + curr_state = ~(SETBIT(curr_state, LNR_THRESH)); + sprintf(thresh_name, "Lower Non Recoverable"); + } + break; + + default: + syslog(LOG_ALERT, "get_snr_thresh_val: wrong thresh enum value"); + exit(-1); + } + + if (curr_state) { + snr[snr_num].curr_state &= curr_state; + syslog(LOG_CRIT, "DEASSERT: %s threshold raised - FRU: %d, num: 0x%X," + " snr: %-16s,",thresh_name, fru, snr_num, snr[snr_num].name); + syslog(LOG_CRIT, "curr_val: %.2f %s, thresh_val: %.2f %s cf: %u", + curr_val, snr[snr_num].units, thresh_val, snr[snr_num].units, snr[snr_num].curr_state); + } +} + + +/* + * Check the curr sensor values against the threshold and + * if the curr val has asserted, log it. + */ +static void +check_thresh_assert(uint8_t fru, uint8_t snr_num, uint8_t thresh, + float curr_val) { + uint8_t curr_state = 0; + float thresh_val; + char thresh_name[100]; + thresh_sensor_t *snr; + + snr = get_struct_thresh_sensor(fru); + + if (!GETBIT(snr[snr_num].flag, thresh) || + GETBIT(snr[snr_num].curr_state, thresh)) + return; + + thresh_val = get_snr_thresh_val(fru, snr_num, thresh); + + switch (thresh) { + case UNR_THRESH: + if (curr_val >= thresh_val) { + curr_state = (SETBIT(curr_state, UNR_THRESH) | + SETBIT(curr_state, UCR_THRESH) | + SETBIT(curr_state, UNC_THRESH)); + sprintf(thresh_name, "Upper Non Recoverable"); + } + break; + + case UCR_THRESH: + if (curr_val >= thresh_val) { + curr_state = (SETBIT(curr_state, UCR_THRESH) | + SETBIT(curr_state, UNC_THRESH)); + sprintf(thresh_name, "Upper Critical"); + } + break; + + case UNC_THRESH: + if (curr_val >= thresh_val) { + curr_state = (SETBIT(curr_state, UNC_THRESH)); + sprintf(thresh_name, "Upper Non Critical"); + } + break; + + case LNR_THRESH: + if (curr_val <= thresh_val) { + curr_state = (SETBIT(curr_state, LNR_THRESH) | + SETBIT(curr_state, LCR_THRESH) | + SETBIT(curr_state, LNC_THRESH)); + sprintf(thresh_name, "Lower Non Recoverable"); + } + break; + + case LCR_THRESH: + if (curr_val <= thresh_val) { + curr_state = (SETBIT(curr_state, LCR_THRESH) | + SETBIT(curr_state, LNC_THRESH)); + sprintf(thresh_name, "Lower Critical"); + } + break; + + case LNC_THRESH: + if (curr_val <= thresh_val) { + curr_state = (SETBIT(curr_state, LNC_THRESH)); + sprintf(thresh_name, "Lower Non Critical"); + } + break; + + default: + syslog(LOG_ALERT, "get_snr_thresh_val: wrong thresh enum value"); + exit(-1); + } + + if (curr_state) { + curr_state &= snr[snr_num].flag; + snr[snr_num].curr_state |= curr_state; + syslog(LOG_CRIT, "ASSERT: %s threshold raised - FRU: %d, num: 0x%X," + " snr: %-16s,",thresh_name, fru, snr_num, snr[snr_num].name); + syslog(LOG_CRIT, "curr_val: %.2f %s, thresh_val: %.2f %s cf: %u", + curr_val, snr[snr_num].units, thresh_val, snr[snr_num].units, snr[snr_num].curr_state); + } +} + +/* + * Starts monitoring all the sensors on a fru for all the threshold values. + * Each pthread runs this monitoring for a different fru. + */ +static void * +snr_monitor(void *arg) { + + uint8_t fru = *(uint8_t *) arg; + int f, ret, snr_num; + float normal_val, curr_val; + thresh_sensor_t *snr; + +#ifdef TESTING + float temp_thresh; + int cnt = 0; +#endif /* TESTING */ + + snr = get_struct_thresh_sensor(fru); + if (snr == NULL) { + syslog(LOG_ALERT, "snr_monitor: get_struct_thresh_sensor failed"); + exit(-1); + } + + while(1) { + +#ifdef TESTING + cnt++; +#endif /* TESTING */ + + for (snr_num = 0; snr_num < MAX_SENSOR_NUM; snr_num++) { + curr_val = 0; + if (snr[snr_num].flag) { + if (!(ret = read_snr_val(fru, snr_num, &curr_val))) { + + +#ifdef TESTING + /* + * The curr_val crosses UCR and then return to a state + * where UNC < curr_val < UCR and then eventually back to normal. + */ + if (cnt == 5 && snr_num == BIC_SENSOR_MB_INLET_TEMP) { + snr[snr_num].flag |= SETBIT(snr[snr_num].flag, UNC_THRESH); + temp_thresh = snr[snr_num].ucr_thresh; + snr[snr_num].ucr_thresh = 20.0; + snr[snr_num].unc_thresh = 10.0; + } else if (cnt == 8 && snr_num == BIC_SENSOR_MB_INLET_TEMP) { + snr[snr_num].ucr_thresh = temp_thresh; + } else if (cnt == 10 && snr_num == BIC_SENSOR_MB_INLET_TEMP) { + snr[snr_num].unc_thresh = 50.0; + } else if (cnt == 11 && snr_num == BIC_SENSOR_MB_INLET_TEMP) { + snr[snr_num].unc_thresh = 0.0; + snr[snr_num].flag &= CLEARBIT(snr[snr_num].flag, UNC_THRESH); + + } +#endif /* TESTING */ + +#ifdef DEBUG + if (cnt == 2) { + syslog(LOG_INFO, "pthread %d, cnt: %d, num: 0x%X name: %-16s" + " units:%s", fru, cnt, snr_num, snr[snr_num].name, + snr[snr_num].units); + } +#endif /* DEBUG */ + + check_thresh_assert(fru, snr_num, UNR_THRESH, curr_val); + check_thresh_assert(fru, snr_num, UCR_THRESH, curr_val); + check_thresh_assert(fru, snr_num, UNC_THRESH, curr_val); + check_thresh_assert(fru, snr_num, LNR_THRESH, curr_val); + check_thresh_assert(fru, snr_num, LCR_THRESH, curr_val); + check_thresh_assert(fru, snr_num, LNC_THRESH, curr_val); + + check_thresh_deassert(fru, snr_num, UNC_THRESH, curr_val); + check_thresh_deassert(fru, snr_num, UCR_THRESH, curr_val); + check_thresh_deassert(fru, snr_num, UNR_THRESH, curr_val); + check_thresh_deassert(fru, snr_num, LNC_THRESH, curr_val); + check_thresh_deassert(fru, snr_num, LCR_THRESH, curr_val); + check_thresh_deassert(fru, snr_num, LNR_THRESH, curr_val); + + } else { + /* + * Incase the read_snr_val failed for a sensor, + * disable all the threshold checks for that sensor + * after logging an approciate syslog message. + */ + if (ret) { + syslog(LOG_ERR, "FRU: %d, num: 0x%X, snr:%-16s, read failed", + fru, snr_num, snr[snr_num].name); + syslog(LOG_ERR, "FRU: %d, num: 0x%X, snr:%-16s, check disabled", + fru, snr_num, snr[snr_num].name); + snr[snr_num].flag = 0; + //} + } + } /* read_snr_val return check */ + } /* flag check */ + } /* loop for all sensors */ + sleep(5); + } /* while loop*/ +} /* function definition */ + + +/* Spawns a pthread for each fru to monitor all the sensors on it */ +static void +run_sensord(int argc, char **argv) { + int i, arg; + + pthread_t thread_snr[MAX_NUM_FRUS]; + int fru[MAX_NUM_FRUS] = {0}; + + for (arg = 1; arg < argc; arg ++) { +#ifdef CONFIG_YOSEMITE + if (!(strcmp(argv[arg], "slot1"))) + fru[FRU_SLOT1 - 1] = FRU_SLOT1; + else if (!(strcmp(argv[arg], "slot2"))) + fru[FRU_SLOT2 - 1] = FRU_SLOT2; + else if (!(strcmp(argv[arg], "slot3"))) + fru[FRU_SLOT3 - 1] = FRU_SLOT3; + else if (!(strcmp(argv[arg], "slot4"))) + fru[FRU_SLOT4 - 1] = FRU_SLOT4; + else if (!(strcmp(argv[arg], "spb"))) + fru[FRU_SPB - 1] = FRU_SPB; + else if (!(strcmp(argv[arg], "nic"))) + fru[FRU_NIC - 1] = FRU_NIC; + else { + syslog(LOG_ALERT, "Wrong argument: %s", argv[arg]); + exit(1); + } +#endif /* CONFIG_YOSEMITE */ + + } + + for (i = 0; i < MAX_NUM_FRUS; i++) { + if (fru[i]) { + if (pthread_create(&thread_snr[i], NULL, snr_monitor, + (void*) &fru[i]) < 0) { + syslog(LOG_ALERT, "pthread_create for FRU %d failed\n", fru[i]); + } +#ifdef DEBUG + else { + syslog(LOG_ALERT, "pthread_create for FRU %d succeed\n", fru[i]); + } +#endif /* DEBUG */ + } + sleep(1); + } + + for (i = 0; i < MAX_NUM_FRUS; i++) { + if (fru[i]) + pthread_join(thread_snr[i], NULL); + } +} + + +#ifdef DEBUG +void print_snr_thread(uint8_t fru, thresh_sensor_t *snr) +{ + int i; + float curr_val; + + for (i = 1; i <= MAX_SENSOR_NUM; i++) { + if (snr[i].flag) { + curr_val = 0; + if(!(read_snr_val(fru, i, &curr_val))) { + printf("%-30s:\t%.2f %s\t%.2f\t%.2f\t%.2f\t%.2f\t%.2f\t%.2f\t%.2f\t%.2f\n", + snr[i].name, curr_val,snr[i].units, + snr[i].ucr_thresh, + snr[i].unc_thresh, + snr[i].lcr_thresh, + snr[i].lnr_thresh, + snr[i].pos_hyst, + snr[i].neg_hyst); + } else + printf("Reading failed: %-16s\n", snr[i].name); + } + } +} +#endif /* DEBUG */ + +int +main(int argc, void **argv) { + int dev, rc, pid_file; + + if (argc < 2) { + syslog(LOG_ALERT, "Usage: sensord <options>"); + printf("Usage: sensord <options>\n"); +#ifdef CONFIG_YOSEMITE + syslog(LOG_ALERT, "Options: [slot1 | slot2 | slot3 | slot4 | spb | nic]"); + printf("Options: [slot1 | slot2 | slot3 | slot4 | spb | nic]\n"); +#endif /* CONFIG_YOSEMITE */ + exit(1); + } + + pid_file = open("/var/run/sensord.pid", O_CREAT | O_RDWR, 0666); + rc = flock(pid_file, LOCK_EX | LOCK_NB); + if(rc) { + if(EWOULDBLOCK == errno) { + printf("Another sensord instance is running...\n"); + exit(-1); + } + } else { + +#ifdef CONFIG_YOSEMITE + read_snr_val = &yosemite_sensor_read; +#endif /* CONFIG_YOSEMITE */ + + init_all_snr_thresh(); + +#ifdef DEBUG + print_snr_thread(1, snr_slot1); + print_snr_thread(2, snr_slot2); + print_snr_thread(3, snr_slot3); + print_snr_thread(4, snr_slot4); +#endif /* DEBUG */ + + daemon(0,1); + openlog("sensord", LOG_CONS, LOG_DAEMON); + syslog(LOG_INFO, "sensord: daemon started"); + run_sensord(argc, (char **) argv); + } + + return 0; +} diff --git a/common/recipes-core/sensor-mon/sensor-mon_0.1.bb b/common/recipes-core/sensor-mon/sensor-mon_0.1.bb new file mode 100644 index 0000000..adbd4cb --- /dev/null +++ b/common/recipes-core/sensor-mon/sensor-mon_0.1.bb @@ -0,0 +1,58 @@ +# Copyright 2015-present Facebook. All Rights Reserved. +# +# This program file is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program in a file named COPYING; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, +# Boston, MA 02110-1301 USA + +SUMMARY = "Sensor Monitoring Daemon" +DESCRIPTION = "Daemon for monitoring the sensors" +SECTION = "base" +PR = "r1" +LICENSE = "GPLv2" +LIC_FILES_CHKSUM = "file://sensord.c;beginline=4;endline=16;md5=b395943ba8a0717a83e62ca123a8d238" + +SRC_URI = "file://Makefile \ + file://sensord.c \ + " + +S = "${WORKDIR}" + +binfiles = "sensord \ + " + +CFLAGS += " -lsdr " + +DEPENDS += " libsdr " + +pkgdir = "sensor-mon" + +do_install() { + dst="${D}/usr/local/fbpackages/${pkgdir}" + bin="${D}/usr/local/bin" + install -d $dst + install -d $bin + for f in ${binfiles}; do + install -m 755 $f ${dst}/$f + ln -snf ../fbpackages/${pkgdir}/$f ${bin}/$f + done +} + +FBPACKAGEDIR = "${prefix}/local/fbpackages" + +FILES_${PN} = "${FBPACKAGEDIR}/sensor-mon ${prefix}/local/bin" + +# Inhibit complaints about .debug directories for the sensord binary: + +INHIBIT_PACKAGE_DEBUG_SPLIT = "1" +INHIBIT_PACKAGE_STRIP = "1" diff --git a/common/recipes-core/sensor-util/files/Makefile b/common/recipes-core/sensor-util/files/Makefile new file mode 100644 index 0000000..2d39a04 --- /dev/null +++ b/common/recipes-core/sensor-util/files/Makefile @@ -0,0 +1,11 @@ +# Copyright 2015-present Facebook. All Rights Reserved. +all: sensor-util + + +sensor-util: sensor-util.o + $(CC) $(CFLAGS) -lpal -lrt -lm -std=gnu99 -o $@ $^ $(LDFLAGS) + +.PHONY: clean + +clean: + rm -rf *.o sensor-util diff --git a/common/recipes-core/sensor-util/files/sensor-util.c b/common/recipes-core/sensor-util/files/sensor-util.c new file mode 100644 index 0000000..eb38d65 --- /dev/null +++ b/common/recipes-core/sensor-util/files/sensor-util.c @@ -0,0 +1,173 @@ +/* + * yosemite-sensors + * + * Copyright 2015-present Facebook. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <syslog.h> +#include <stdint.h> +#include <string.h> +#include <errno.h> +#include <openbmc/pal.h> + +static int +print_usage() { + printf("Usage: sensor-util [ %s ] <sensor num>\n" + "sensor num is optional.", pal_fru_list); +} + +static void +print_single_sensor_reading(uint8_t fru, uint8_t *sensor_list, int sensor_cnt, uint8_t num) { + + int i; + float fvalue; + char name[24]; + char units[64]; + int ret = 0; + + for (i = 0; i < sensor_cnt; i++) { + + if (sensor_list[i] == num) { + ret = 1; + pal_get_sensor_name(fru, sensor_list[i], name); + pal_get_sensor_units(fru, sensor_list[i], units); + + if (pal_sensor_read(fru, sensor_list[i], &fvalue) < 0) { + + printf("pal_sensor_read failed: fru: %d num: 0x%X name: %-23s\n", + fru, sensor_list[i], name); + } else { + printf("%-23s: %.2f %s\n", name, fvalue, units); + } + + break; + } + } + + if (!ret) { + printf("Wrong sensor number!\n"); + print_usage(); + exit(-1); + } +} + +static void +print_sensor_reading(uint8_t fru, uint8_t *sensor_list, int sensor_cnt) { + + int i; + float fvalue; + char name[24]; + char units[64]; + + for (i = 0; i < sensor_cnt; i++) { + + /* Clear the variable */ + sprintf(name, ""); + sprintf(units, ""); + + pal_get_sensor_name(fru, sensor_list[i], name); + pal_get_sensor_units(fru, sensor_list[i], units); + + if (pal_sensor_read(fru, sensor_list[i], &fvalue) < 0) { + + printf("pal_sensor_read failed: fru: %d num: 0x%X name: %-23s\n", + fru, sensor_list[i], name); + } else { + printf("%-23s: %.2f %s\n", name, fvalue, units); + } + } +} + +int +main(int argc, char **argv) { + + int ret; + int sensor_cnt; + uint8_t *sensor_list; + uint8_t fru; + uint8_t num; + + if (argc < 2 || argc > 3) { + print_usage(); + exit(-1); + } + + if (argc == 3) { + errno = 0; + num = (uint8_t) strtol(argv[2], NULL, 0); + if (errno) { + printf("Sensor number format incorrect.\n"); + print_usage(); + exit(-1); + } + } + + + ret = pal_get_fru_id(argv[1], &fru); + if (ret < 0) { + print_usage(); + return ret; + } + + if (fru == 0) { + fru = 1; + while (fru <= MAX_NUM_FRUS) { + + if (fru == FRU_NIC) { + printf("\nsensor-util does not support nic\n"); + exit(-1); + } + + ret = pal_get_fru_sensor_list(fru, &sensor_list, &sensor_cnt); + if (ret < 0) { + return ret; + } + + if (num) { + print_single_sensor_reading(fru, sensor_list, sensor_cnt, num); + } else { + print_sensor_reading(fru, sensor_list, sensor_cnt); + } + + fru++; + printf("\n"); + } + } else { + + if (fru == FRU_NIC) { + printf("\nsensor-util does not support nic\n"); + //exit(-1); + } + + ret = pal_get_fru_sensor_list(fru, &sensor_list, &sensor_cnt); + if (ret < 0) { + return ret; + } + + if (num) { + print_single_sensor_reading(fru, sensor_list, sensor_cnt, num); + } else { + print_sensor_reading(fru, sensor_list, sensor_cnt); + } + } + + return 0; +} diff --git a/common/recipes-core/sensor-util/sensor-util_0.1.bb b/common/recipes-core/sensor-util/sensor-util_0.1.bb new file mode 100644 index 0000000..227ec53 --- /dev/null +++ b/common/recipes-core/sensor-util/sensor-util_0.1.bb @@ -0,0 +1,34 @@ +# Copyright 2015-present Facebook. All Rights Reserved. +SUMMARY = "Sensor Utility" +DESCRIPTION = "Util for reading various sensors" +SECTION = "base" +PR = "r1" +LICENSE = "GPLv2" +LIC_FILES_CHKSUM = "file://sensor-util.c;beginline=4;endline=16;md5=b395943ba8a0717a83e62ca123a8d238" + +SRC_URI = "file://Makefile \ + file://sensor-util.c \ + " +S = "${WORKDIR}" + +binfiles = "sensor-util" + +DEPENDS =+ " libpal " + +pkgdir = "sensor-util" + +do_install() { + dst="${D}/usr/local/fbpackages/${pkgdir}" + bin="${D}/usr/local/bin" + install -d $dst + install -d $bin + install -m 755 sensor-util ${dst}/sensor-util + ln -snf ../fbpackages/${pkgdir}/sensor-util ${bin}/sensor-util +} + +FBPACKAGEDIR = "${prefix}/local/fbpackages" + +FILES_${PN} = "${FBPACKAGEDIR}/sensor-util ${prefix}/local/bin" + +INHIBIT_PACKAGE_DEBUG_SPLIT = "1" +INHIBIT_PACKAGE_STRIP = "1" diff --git a/common/recipes-core/watchdog-ctrl/watchdog-ctrl_0.1.bb b/common/recipes-core/watchdog-ctrl/watchdog-ctrl_0.1.bb index 00cf0f9..14c0fee 100644 --- a/common/recipes-core/watchdog-ctrl/watchdog-ctrl_0.1.bb +++ b/common/recipes-core/watchdog-ctrl/watchdog-ctrl_0.1.bb @@ -1,4 +1,19 @@ # Copyright 2014-present Facebook. All Rights Reserved. +# +# This program file is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program in a file named COPYING; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, +# Boston, MA 02110-1301 USA SUMMARY = "Watchdog control utilities." DESCRIPTION = "The utilities to control system watchdog." SECTION = "base" |