common/recipes-core/healthd/files/healthd.c (1,668 lines of code) (raw):
/*
* healthd
*
* 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.
*/
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <syslog.h>
#include <errno.h>
#include <unistd.h>
#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <sys/file.h>
#include <sys/mman.h>
#include <pthread.h>
#include <jansson.h>
#include <stdbool.h>
#include <openbmc/pal.h>
#include <sys/sysinfo.h>
#include <sys/reboot.h>
#include <openbmc/watchdog.h>
#include <openbmc/pal.h>
#include <openbmc/kv.h>
#include <openbmc/obmc-i2c.h>
#include <openbmc/vbs.h>
#include <openbmc/misc-utils.h>
#include <signal.h>
#define I2C_BUS_NUM 14
#define AST_I2C_BASE 0x1E78A000 /* I2C */
#define I2C_CMD_REG 0x14
#define AST_I2CD_SCL_LINE_STS (0x1 << 18)
#define AST_I2CD_SDA_LINE_STS (0x1 << 17)
#define AST_I2CD_BUS_BUSY_STS (0x1 << 16)
#define PAGE_SIZE 0x1000
#define MIN_THRESHOLD 60.0
#define MAX_THRESHOLD 95.0
#define VERSION_HISTORY_COUNT (4)
#define CPU_INFO_PATH "/proc/stat"
#define CPU_NAME_LENGTH 10
#define DEFAULT_WINDOW_SIZE 120
#define DEFAULT_MONITOR_INTERVAL 1
#define HEALTHD_MAX_RETRY 10
#define CONFIG_PATH "/etc/healthd-config.json"
#define AST_MCR_BASE 0x1e6e0000 // Base Address of SDRAM Memory Controller
#define INTR_CTRL_STS_OFFSET 0x50 // Interrupt Control/Status Register
#define ADDR_FIRST_UNRECOVER_ECC_OFFSET 0x58 // Address of First Un-Recoverable ECC Error Addr
#define ADDR_LAST_RECOVER_ECC_OFFSET 0x5c // Address of Last Recoverable ECC Error Addr
#define MAX_ECC_RECOVERABLE_ERROR_COUNTER 255
#define MAX_ECC_UNRECOVERABLE_ERROR_COUNTER 15
#define BMC_HEALTH_FILE "bmc_health"
#define HEALTH "1"
#define NOT_HEALTH "0"
#define VM_PANIC_ON_OOM_FILE "/proc/sys/vm/panic_on_oom"
/* Identify BMC reboot cause */
#define AST_SRAM_BMC_REBOOT_BASE 0x1E721000
#define AST_SRAM_BMC_REBOOT_OFFSET 0x200
#define AST_G6_SRAM_BMC_REBOOT_BASE 0x10015000
#define AST_G6_SRAM_BMC_REBOOT_OFFSET 0xC00
#define BMC_REBOOT_BY_KERN_PANIC(base, offset) *((uint32_t *)(base + (offset)))
#define BMC_REBOOT_BY_CMD(base, offset) *((uint32_t *)(base + (offset + 0x4)))
#define BMC_REBOOT_SIG(base, offset) *((uint32_t *)(base + (offset + 0x8)))
#define BOOT_MAGIC 0xFB420054
#define BIT_RECORD_LOG (1 << 8)
// kernel panic
#define FLAG_KERN_PANIC (1 << 0)
// reboot command
#define FLAG_REBOOT_CMD (1 << 0)
#define FLAG_CFG_UTIL (1 << 1)
#define FLAG_UBIFS_ERROR (1 << 2)
#define HB_SLEEP_TIME (5 * 60)
#define HB_TIMESTAMP_COUNT (60 * 60 / HB_SLEEP_TIME)
#define SLED_TS_TIMEOUT 100 //SLED Time Sync Timeout
#define KV_KEY_IMAGE_VERSIONS "image_versions"
#define KV_KEY_HEALTHD_REARM "healthd_rearm"
#define BIC_HEALTH_INTERVAL 60 //seconds
#define BIC_RESET_ERR_CNT 3
#define LOG_REARM_CHECK_INTERVAL 3 // seconds
#define DEFAULT_UBIFS_HEALTH_INTERVAL 30 // seconds
#define MAX_LOG_SIZE 128
struct i2c_bus_s {
uint32_t offset;
char *name;
bool enabled;
};
struct i2c_bus_s ast_i2c_dev_offset[I2C_BUS_NUM] = {
{0x040, "I2C DEV1 OFFSET", false},
{0x080, "I2C DEV2 OFFSET", false},
{0x0C0, "I2C DEV3 OFFSET", false},
{0x100, "I2C DEV4 OFFSET", false},
{0x140, "I2C DEV5 OFFSET", false},
{0x180, "I2C DEV6 OFFSET", false},
{0x1C0, "I2C DEV7 OFFSET", false},
{0x300, "I2C DEV8 OFFSET", false},
{0x340, "I2C DEV9 OFFSET", false},
{0x380, "I2C DEV10 OFFSET", false},
{0x3C0, "I2C DEV11 OFFSET", false},
{0x400, "I2C DEV12 OFFSET", false},
{0x440, "I2C DEV13 OFFSET", false},
{0x480, "I2C DEV14 OFFSET", false},
};
struct threshold_s {
float value;
float hysteresis;
bool asserted;
bool log;
int log_level;
bool reboot;
bool bmc_error_trigger;
bool bmc_mem_clear;
};
enum {
BUS_LOCK_RECOVER_ERROR = 0,
BUS_LOCK_RECOVER_TIMEOUT,
BUS_LOCK_RECOVER_SUCCESS,
BUS_LOCK_PRESERVE,
SLAVE_DEAD_RECOVER_ERROR,
SLAVE_DEAD_RECOVER_TIMEOUT,
SLAVE_DEAD_RECOVER_SUCCESS,
SLAVE_DEAD_PRESERVE,
UNDEFINED_CASE,
};
enum ASSERT_BIT {
BIT_CPU_OVER_THRESHOLD = 0,
BIT_MEM_OVER_THRESHOLD = 1,
BIT_RECOVERABLE_ECC = 2,
BIT_UNRECOVERABLE_ECC = 3,
};
enum BIC_ERROR {
BIC_HB_ERR = 0,
BIC_IPMB_ERR,
BIC_READY_ERR,
BIC_ERR_TYPE_CNT,
};
/* Heartbeat configuration */
static unsigned int hb_interval = 500;
/* CPU configuration */
static char *cpu_monitor_name = "BMC CPU utilization";
static bool cpu_monitor_enabled = false;
static unsigned int cpu_window_size = DEFAULT_WINDOW_SIZE;
static unsigned int cpu_monitor_interval = DEFAULT_MONITOR_INTERVAL;
static struct threshold_s *cpu_threshold;
static size_t cpu_threshold_num = 0;
/* Memory monitor enabled */
static char *mem_monitor_name = "BMC Memory utilization";
static bool mem_monitor_enabled = false;
static bool mem_enable_panic = false;
static int mem_min_free_kbytes = 0;
static unsigned int mem_window_size = DEFAULT_WINDOW_SIZE;
static unsigned int mem_monitor_interval = DEFAULT_MONITOR_INTERVAL;
static struct threshold_s *mem_threshold;
static size_t mem_threshold_num = 0;
static pthread_mutex_t global_error_mutex = PTHREAD_MUTEX_INITIALIZER;
static int bmc_health = 0; // CPU/MEM/ECC error flag
/* I2C Monitor enabled */
static bool i2c_monitor_enabled = false;
/* ECC configuration */
static char *recoverable_ecc_name = "ECC Recoverable Error";
static char *unrecoverable_ecc_name = "ECC Unrecoverable Error";
static bool ecc_monitor_enabled = false;
static unsigned int ecc_monitor_interval = DEFAULT_MONITOR_INTERVAL;
// to show the address of ecc error or not. supported chip: AST2500 serials
static bool ecc_addr_log = false;
static struct threshold_s *recov_ecc_threshold;
static size_t recov_ecc_threshold_num = 0;
static struct threshold_s *unrec_ecc_threshold;
static size_t unrec_ecc_threshold_num = 0;
static unsigned int ecc_recov_max_counter = MAX_ECC_RECOVERABLE_ERROR_COUNTER;
static unsigned int ecc_unrec_max_counter = MAX_ECC_UNRECOVERABLE_ERROR_COUNTER;
/* BMC Health Monitor */
static bool regen_log_enabled = false;
static unsigned int bmc_health_monitor_interval = DEFAULT_MONITOR_INTERVAL;
static int regen_interval = 1200;
/* Node Manager Monitor enabled */
static bool nm_monitor_enabled = false;
static int nm_monitor_interval = DEFAULT_MONITOR_INTERVAL;
static unsigned char nm_retry_threshold = 0;
static bool nm_transmission_via_bic = false;
static uint8_t is_duplicated_unaccess_event[MAX_NUM_FRUS] = {false};
static uint8_t is_duplicated_abnormal_event[MAX_NUM_FRUS] = {false};
/* Verified-boot state check */
static bool vboot_state_check = false;
/* BMC time stamp enabled */
static bool bmc_timestamp_enabled = false;
/* PFR status Monitor */
extern bool pfr_monitor_enabled;
extern void initialize_pfr_monitor_config(json_t *);
extern void * pfr_monitor();
/* BIC health monitor */
static bool bic_health_enabled = false;
static uint8_t bic_fru = 0;
/* healthd log rearm monitor */
static bool log_rearm_enabled = false;
/* UBIFS Health Monitor */
struct ubifs_health_monitor_config
{
bool enabled;
int monitor_interval;
};
static struct ubifs_health_monitor_config uhm_config = {
.enabled = false,
.monitor_interval = DEFAULT_UBIFS_HEALTH_INTERVAL,
};
static void
initialize_threshold(const char *target, json_t *thres, struct threshold_s *t) {
json_t *tmp;
size_t i;
size_t act_size;
tmp = json_object_get(thres, "value");
if (!tmp || !json_is_real(tmp)) {
return;
}
t->value = json_real_value(tmp);
/* Do not let the value (CPU/MEM thresholds) exceed these safe ranges */
if ((strcasestr(target, "CPU") != 0ULL) ||
(strcasestr(target, "MEM") != 0ULL)) {
if (t->value > MAX_THRESHOLD) {
syslog(LOG_WARNING,
"%s: user setting %s threshold %f is too high and set threshold as %f",
__func__, target, t->value, MAX_THRESHOLD);
t->value = MAX_THRESHOLD;
}
if (t->value < MIN_THRESHOLD) {
syslog(LOG_WARNING,
"%s: user setting %s threshold %f is too low and set threshold as %f",
__func__, target, t->value, MIN_THRESHOLD);
t->value = MIN_THRESHOLD;
}
}
tmp = json_object_get(thres, "hysteresis");
if (tmp && json_is_real(tmp)) {
t->hysteresis = json_real_value(tmp);
}
tmp = json_object_get(thres, "action");
if (!tmp || !json_is_array(tmp)) {
return;
}
act_size = json_array_size(tmp);
for(i = 0; i < act_size; i++) {
const char *act;
json_t *act_o = json_array_get(tmp, i);
if (!act_o || !json_is_string(act_o)) {
continue;
}
act = json_string_value(act_o);
if (!strcmp(act, "log-warning")) {
t->log_level = LOG_WARNING;
t->log = true;
} else if(!strcmp(act, "log-critical")) {
t->log_level = LOG_CRIT;
t->log = true;
} else if (!strcmp(act, "reboot")) {
t->reboot = true;
} else if(!strcmp(act, "bmc-error-trigger")) {
t->bmc_error_trigger = true;
} else if(!strcmp(act, "bmc-mem-clear")) {
t->bmc_mem_clear = true;
}
}
}
static void initialize_thresholds(const char *target, json_t *array, struct threshold_s **out_arr, size_t *out_len) {
size_t size = json_array_size(array);
size_t i;
struct threshold_s *thres;
if (size == 0) {
return;
}
thres = *out_arr = calloc(size, sizeof(struct threshold_s));
if (!thres) {
return;
}
*out_len = size;
for(i = 0; i < size; i++) {
json_t *e = json_array_get(array, i);
if (!e) {
continue;
}
initialize_threshold(target, e, &thres[i]);
}
}
static void
initialize_hb_config(json_t *conf) {
json_t *tmp;
tmp = json_object_get(conf, "interval");
if (!tmp || !json_is_number(tmp)) {
return;
}
hb_interval = json_integer_value(tmp);
}
static void
initialize_cpu_config(json_t *conf) {
json_t *tmp;
tmp = json_object_get(conf, "enabled");
if (!tmp || !json_is_boolean(tmp)) {
return;
}
cpu_monitor_enabled = json_is_true(tmp);
if (!cpu_monitor_enabled) {
return;
}
tmp = json_object_get(conf, "window_size");
if (tmp && json_is_number(tmp)) {
cpu_window_size = json_integer_value(tmp);
}
tmp = json_object_get(conf, "monitor_interval");
if (tmp && json_is_number(tmp)) {
cpu_monitor_interval = json_integer_value(tmp);
if (cpu_monitor_interval <= 0)
cpu_monitor_interval = DEFAULT_MONITOR_INTERVAL;
}
tmp = json_object_get(conf, "threshold");
if (!tmp || !json_is_array(tmp)) {
cpu_monitor_enabled = false;
return;
}
initialize_thresholds(cpu_monitor_name, tmp, &cpu_threshold, &cpu_threshold_num);
}
static void
initialize_mem_config(json_t *conf) {
json_t *tmp;
tmp = json_object_get(conf, "enabled");
if (!tmp || !json_is_boolean(tmp)) {
return;
}
mem_monitor_enabled = json_is_true(tmp);
if (!mem_monitor_enabled) {
return;
}
tmp = json_object_get(conf, "enable_panic_on_oom");
if (tmp && json_is_true(tmp)) {
mem_enable_panic = true;
}
tmp = json_object_get(conf, "min_free_kbytes");
if (tmp && json_is_number(tmp)) {
mem_min_free_kbytes = json_integer_value(tmp);
}
tmp = json_object_get(conf, "window_size");
if (tmp && json_is_number(tmp)) {
mem_window_size = json_integer_value(tmp);
}
tmp = json_object_get(conf, "monitor_interval");
if (tmp && json_is_number(tmp)) {
mem_monitor_interval = json_integer_value(tmp);
if (mem_monitor_interval <= 0)
mem_monitor_interval = DEFAULT_MONITOR_INTERVAL;
}
tmp = json_object_get(conf, "threshold");
if (!tmp || !json_is_array(tmp)) {
mem_monitor_enabled = false;
return;
}
initialize_thresholds(mem_monitor_name, tmp, &mem_threshold, &mem_threshold_num);
}
static void
initialize_i2c_config(json_t *conf) {
json_t *tmp;
size_t i;
size_t i2c_num_busses;
tmp = json_object_get(conf, "enabled");
if (!tmp || !json_is_boolean(tmp)) {
return;
}
i2c_monitor_enabled = json_is_true(tmp);
if (!i2c_monitor_enabled) {
return;
}
tmp = json_object_get(conf, "busses");
if (!tmp || !json_is_array(tmp)) {
goto error_bail;
}
i2c_num_busses = json_array_size(tmp);
if (!i2c_num_busses) {
/* Nothing to monitor */
goto error_bail;
}
for(i = 0; i < i2c_num_busses; i++) {
size_t bus;
json_t *ind = json_array_get(tmp, i);
if (!ind || !json_is_number(ind)) {
goto error_bail;
}
bus = json_integer_value(ind);
if (bus >= I2C_BUS_NUM) {
syslog(LOG_CRIT, "HEALTHD: Warning: Ignoring unsupported I2C Bus:%u\n", (unsigned int)bus);
continue;
}
ast_i2c_dev_offset[bus].enabled = true;
}
return;
error_bail:
i2c_monitor_enabled = false;
}
static void
initialize_ecc_config(json_t *conf) {
json_t *tmp;
tmp = json_object_get(conf, "enabled");
if (!tmp || !json_is_boolean(tmp)) {
return;
}
ecc_monitor_enabled = json_is_true(tmp);
if (!ecc_monitor_enabled) {
return;
}
tmp = json_object_get(conf, "ecc_address_log");
if (tmp || json_is_boolean(tmp)) {
ecc_addr_log = json_is_true(tmp);
}
tmp = json_object_get(conf, "monitor_interval");
if (tmp && json_is_number(tmp)) {
ecc_monitor_interval = json_integer_value(tmp);
}
tmp = json_object_get(conf, "recov_max_counter");
if (tmp && json_is_number(tmp)) {
ecc_recov_max_counter = json_integer_value(tmp);
}
tmp = json_object_get(conf, "unrec_max_counter");
if (tmp && json_is_number(tmp)) {
ecc_unrec_max_counter = json_integer_value(tmp);
}
tmp = json_object_get(conf, "recov_threshold");
if (tmp || json_is_array(tmp)) {
initialize_thresholds(recoverable_ecc_name, tmp, &recov_ecc_threshold, &recov_ecc_threshold_num);
}
tmp = json_object_get(conf, "unrec_threshold");
if (tmp || json_is_array(tmp)) {
initialize_thresholds(unrecoverable_ecc_name, tmp, &unrec_ecc_threshold, &unrec_ecc_threshold_num);
}
}
static void
initialize_bmc_health_config(json_t *conf) {
json_t *tmp;
tmp = json_object_get(conf, "enabled");
if (tmp || json_is_boolean(tmp)) {
regen_log_enabled = json_is_true(tmp);
}
tmp = json_object_get(conf, "monitor_interval");
if (tmp && json_is_number(tmp)) {
bmc_health_monitor_interval = json_integer_value(tmp);
if (bmc_health_monitor_interval <= 0)
bmc_health_monitor_interval = DEFAULT_MONITOR_INTERVAL;
}
tmp = json_object_get(conf, "regenerating_interval");
if (tmp && json_is_number(tmp)) {
regen_interval = json_integer_value(tmp);
}
}
static void
initialize_nm_monitor_config(json_t *conf)
{
json_t *tmp;
tmp = json_object_get(conf, "enabled");
if (!tmp || !json_is_boolean(tmp))
{
goto error_bail;
}
nm_monitor_enabled = json_is_true(tmp);
if ( !nm_monitor_enabled )
{
goto error_bail;
}
tmp = json_object_get(conf, "monitor_interval");
if (tmp && json_is_number(tmp))
{
nm_monitor_interval = json_integer_value(tmp);
if (nm_monitor_interval <= 0)
{
nm_monitor_interval = DEFAULT_MONITOR_INTERVAL;
}
}
tmp = json_object_get(conf, "retry_threshold");
if (tmp && json_is_number(tmp))
{
nm_retry_threshold = json_integer_value(tmp);
}
tmp = json_object_get(conf, "nm_transmission_via_bic");
if (tmp && json_is_boolean(tmp))
{
nm_transmission_via_bic = json_is_true(tmp);
}
#ifdef DEBUG
syslog(LOG_WARNING, "enabled:%d, monitor_interval:%d, retry_threshold:%d", nm_monitor_enabled, nm_monitor_interval, nm_retry_threshold);
#endif
return;
error_bail:
nm_monitor_enabled = false;
}
static void initialize_vboot_config(json_t *obj)
{
json_t *tmp;
if (!obj) {
return;
}
tmp = json_object_get(obj, "enabled");
if (!tmp || !json_is_boolean(tmp)) {
return;
}
vboot_state_check = json_is_true(tmp);
}
static void initialize_bmc_timestamp_config(json_t *obj) {
json_t *tmp;
if (!obj) {
return;
}
tmp = json_object_get(obj, "enabled");
if (!tmp || !json_is_boolean(tmp)) {
return;
}
bmc_timestamp_enabled = json_is_true(tmp);
}
static void initialize_bic_health_config(json_t *obj) {
json_t *tmp = NULL;
if (obj == NULL) {
return;
}
tmp = json_object_get(obj, "enabled");
if (!tmp || !json_is_boolean(tmp)) {
return;
}
bic_health_enabled = json_is_true(tmp);
tmp = json_object_get(obj, "fru");
if (!tmp || !json_is_number(tmp)) {
return;
}
bic_fru = json_integer_value(tmp);
}
static void initialize_ubifs_health_config(json_t *obj) {
json_t *tmp = NULL;
if (obj == NULL) {
return;
}
tmp = json_object_get(obj, "enabled");
if (!tmp || !json_is_boolean(tmp)) {
return;
}
uhm_config.enabled = json_is_true(tmp);
tmp = json_object_get(obj, "monitor_interval");
if (tmp && json_is_number(tmp)) {
uhm_config.monitor_interval = json_integer_value(tmp);
if (uhm_config.monitor_interval <= 0)
uhm_config.monitor_interval = DEFAULT_UBIFS_HEALTH_INTERVAL;
}
}
static int
initialize_configuration(void) {
json_error_t error;
json_t *conf;
json_t *v;
conf = json_load_file(CONFIG_PATH, 0, &error);
if (!conf) {
syslog(LOG_CRIT, "HEALTHD configuration load failed");
return -1;
}
v = json_object_get(conf, "version");
if (v && json_is_string(v)) {
syslog(LOG_INFO, "Loaded configuration version: %s\n", json_string_value(v));
}
v = json_object_get(conf, "log_rearm");
if (v && json_is_boolean(v)) {
log_rearm_enabled = json_is_true(v);
}
initialize_hb_config(json_object_get(conf, "heartbeat"));
initialize_cpu_config(json_object_get(conf, "bmc_cpu_utilization"));
initialize_mem_config(json_object_get(conf, "bmc_mem_utilization"));
initialize_i2c_config(json_object_get(conf, "i2c"));
initialize_ecc_config(json_object_get(conf, "ecc_monitoring"));
initialize_bmc_health_config(json_object_get(conf, "bmc_health"));
initialize_nm_monitor_config(json_object_get(conf, "nm_monitor"));
initialize_pfr_monitor_config(json_object_get(conf, "pfr_monitor"));
initialize_vboot_config(json_object_get(conf, "verified_boot"));
initialize_bmc_timestamp_config(json_object_get(conf, "bmc_timestamp"));
initialize_bic_health_config(json_object_get(conf, "bic_health"));
initialize_ubifs_health_config(json_object_get(conf, "ubifs_health"));
json_decref(conf);
return 0;
}
static void threshold_assert_check(const char *target, float value, struct threshold_s *thres) {
struct sysinfo info;
if (!thres->asserted && value >= thres->value) {
thres->asserted = true;
if (thres->log) {
syslog(thres->log_level, "ASSERT: %s (%.2f%%) exceeds the threshold (%.2f%%).\n", target, value, thres->value);
}
if (thres->reboot) {
sysinfo(&info);
syslog(thres->log_level, "Rebooting BMC; latest uptime: %ld sec", info.uptime);
sleep(1);
pal_bmc_reboot(RB_AUTOBOOT);
}
if (thres->bmc_error_trigger) {
pthread_mutex_lock(&global_error_mutex);
if (!bmc_health) { // assert bmc_health key only when not yet set
pal_set_key_value(BMC_HEALTH_FILE, NOT_HEALTH);
}
if (strcasestr(target, "CPU") != 0ULL) {
bmc_health = SETBIT(bmc_health, BIT_CPU_OVER_THRESHOLD);
} else if (strcasestr(target, "Mem") != 0ULL) {
bmc_health = SETBIT(bmc_health, BIT_MEM_OVER_THRESHOLD);
} else {
pthread_mutex_unlock(&global_error_mutex);
return;
}
pthread_mutex_unlock(&global_error_mutex);
pal_bmc_err_enable(target);
}
if (thres->bmc_mem_clear) {
if (system("sync;/sbin/sysctl vm.drop_caches=3 > /dev/null")){
syslog(LOG_ERR, "Clear BMC Memory failed\n");
}
}
}
}
static void threshold_deassert_check(const char *target, float value, struct threshold_s *thres) {
if (thres->asserted && value < (thres->value - thres->hysteresis)) {
thres->asserted = false;
if (thres->log) {
syslog(thres->log_level, "DEASSERT: %s (%.2f%%) is under the threshold (%.2f%%).\n", target, value, thres->value);
}
if (thres->bmc_error_trigger) {
pthread_mutex_lock(&global_error_mutex);
if (strcasestr(target, "CPU") != 0ULL) {
bmc_health = CLEARBIT(bmc_health, BIT_CPU_OVER_THRESHOLD);
} else if (strcasestr(target, "Mem") != 0ULL) {
bmc_health = CLEARBIT(bmc_health, BIT_MEM_OVER_THRESHOLD);
} else {
pthread_mutex_unlock(&global_error_mutex);
return;
}
if (!bmc_health) { // deassert bmc_health key if no any error bit assertion
pal_set_key_value(BMC_HEALTH_FILE, HEALTH);
}
pthread_mutex_unlock(&global_error_mutex);
pal_bmc_err_disable(target);
}
}
}
static void
threshold_check(const char *target, float value, struct threshold_s *thresholds, size_t num) {
size_t i;
for(i = 0; i < num; i++) {
threshold_assert_check(target, value, &thresholds[i]);
threshold_deassert_check(target, value, &thresholds[i]);
}
}
static void ecc_threshold_assert_check(const char *target, int value,
struct threshold_s *thres, uint32_t ecc_err_addr) {
int thres_counter = 0;
if (strcasestr(target, "Unrecover") != 0ULL) {
thres_counter = (ecc_unrec_max_counter * thres->value / 100);
} else if (strcasestr(target, "Recover") != 0ULL) {
thres_counter = (ecc_recov_max_counter * thres->value / 100);
} else {
return;
}
if (!thres->asserted && value > thres_counter) {
thres->asserted = true;
if (thres->log) {
if (ecc_addr_log) {
syslog(thres->log_level, "%s occurred (over %d%%) "
"Counter = %d Address of last recoverable ECC error = 0x%x",
target, (int)thres->value, value, (ecc_err_addr >> 4) & 0xFFFFFFFF);
} else {
syslog(thres->log_level, "ECC occurred (over %d%%): %s Counter = %d",
(int)thres->value, target, value);
}
}
if (thres->reboot) {
pal_bmc_reboot(RB_AUTOBOOT);
}
if (thres->bmc_error_trigger) {
pthread_mutex_lock(&global_error_mutex);
if (!bmc_health) { // assert in bmc_health key only when not yet set
pal_set_key_value(BMC_HEALTH_FILE, NOT_HEALTH);
}
if (strcasestr(target, "Unrecover") != 0ULL) {
bmc_health = SETBIT(bmc_health, BIT_UNRECOVERABLE_ECC);
} else if (strcasestr(target, "Recover") != 0ULL) {
bmc_health = SETBIT(bmc_health, BIT_RECOVERABLE_ECC);
} else {
pthread_mutex_unlock(&global_error_mutex);
return;
}
pthread_mutex_unlock(&global_error_mutex);
pal_bmc_err_enable(target);
}
}
}
static void
ecc_threshold_check(const char *target, int value, struct threshold_s *thresholds,
size_t num, uint32_t ecc_err_addr) {
size_t i;
for(i = 0; i < num; i++) {
ecc_threshold_assert_check(target, value, &thresholds[i], ecc_err_addr);
}
}
static void
initilize_all_kv() {
pal_set_def_key_value();
}
static void *
hb_handler() {
// set flag to notice BMC healthd hb_handler is ready
kv_set("flag_healthd_hb_led", "1", 0, 0);
while(1) {
/* Turn ON the HB Led*/
pal_set_hb_led(1);
msleep(hb_interval);
/* Turn OFF the HB led */
pal_set_hb_led(0);
msleep(hb_interval);
}
return NULL;
}
static void *
watchdog_handler() {
/* Start watchdog in manual mode */
open_watchdog(0, 0);
/* Set watchdog to persistent mode so timer expiry will happen independent
* of this process's liveliness.
*/
watchdog_disable_magic_close();
// set flag to notice BMC healthd watchdog_handler is ready
kv_set("flag_healthd_wtd", "1", 0, 0);
while(1) {
sleep(5);
/*
* 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();
}
return NULL;
}
static void *
i2c_mon_handler() {
char i2c_bus_device[16];
int dev;
int bus_status = 0;
int asserted_flag[I2C_BUS_NUM] = {};
bool assert_handle = 0;
int i;
while (1) {
for (i = 0; i < I2C_BUS_NUM; i++) {
if (!ast_i2c_dev_offset[i].enabled) {
continue;
}
sprintf(i2c_bus_device, "/dev/i2c-%d", i);
dev = open(i2c_bus_device, O_RDWR);
if (dev < 0) {
syslog(LOG_DEBUG, "%s(): open() failed", __func__);
continue;
}
bus_status = i2c_smbus_status(dev);
close(dev);
assert_handle = 0;
if (bus_status == 0) {
/* Bus status is normal */
if (asserted_flag[i] != 0) {
asserted_flag[i] = 0;
syslog(LOG_CRIT, "DEASSERT: I2C(%d) Bus recoveried. (I2C bus index base 0)", i);
pal_i2c_crash_deassert_handle(i);
}
} else {
/* Check each case */
if (GETBIT(bus_status, BUS_LOCK_RECOVER_ERROR)
&& !GETBIT(asserted_flag[i], BUS_LOCK_RECOVER_ERROR)) {
asserted_flag[i] = SETBIT(asserted_flag[i], BUS_LOCK_RECOVER_ERROR);
syslog(LOG_CRIT, "ASSERT: I2C(%d) bus is locked (Master Lock or Slave Clock Stretch). "
"Recovery error. (I2C bus index base 0)", i);
assert_handle = 1;
}
bus_status = CLEARBIT(bus_status, BUS_LOCK_RECOVER_ERROR);
if (GETBIT(bus_status, BUS_LOCK_RECOVER_TIMEOUT)
&& !GETBIT(asserted_flag[i], BUS_LOCK_RECOVER_TIMEOUT)) {
asserted_flag[i] = SETBIT(asserted_flag[i], BUS_LOCK_RECOVER_TIMEOUT);
syslog(LOG_CRIT, "ASSERT: I2C(%d) bus is locked (Master Lock or Slave Clock Stretch). "
"Recovery timed out. (I2C bus index base 0)", i);
assert_handle = 1;
}
bus_status = CLEARBIT(bus_status, BUS_LOCK_RECOVER_TIMEOUT);
if (GETBIT(bus_status, BUS_LOCK_RECOVER_SUCCESS)) {
syslog(LOG_CRIT, "I2C(%d) bus had been locked (Master Lock or Slave Clock Stretch) "
"and has been recoveried successfully. (I2C bus index base 0)", i);
}
bus_status = CLEARBIT(bus_status, BUS_LOCK_RECOVER_SUCCESS);
if (GETBIT(bus_status, SLAVE_DEAD_RECOVER_ERROR)
&& !GETBIT(asserted_flag[i], SLAVE_DEAD_RECOVER_ERROR)) {
asserted_flag[i] = SETBIT(asserted_flag[i], SLAVE_DEAD_RECOVER_ERROR);
syslog(LOG_CRIT, "ASSERT: I2C(%d) Slave is dead (SDA keeps low). "
"Bus recovery error. (I2C bus index base 0)", i);
assert_handle = 1;
}
bus_status = CLEARBIT(bus_status, SLAVE_DEAD_RECOVER_ERROR);
if (GETBIT(bus_status, SLAVE_DEAD_RECOVER_TIMEOUT)
&& !GETBIT(asserted_flag[i], SLAVE_DEAD_RECOVER_TIMEOUT)) {
asserted_flag[i] = SETBIT(asserted_flag[i], SLAVE_DEAD_RECOVER_TIMEOUT);
syslog(LOG_CRIT, "ASSERT: I2C(%d) Slave is dead (SDAs keep low). "
"Bus recovery timed out. (I2C bus index base 0)", i);
assert_handle = 1;
}
bus_status = CLEARBIT(bus_status, SLAVE_DEAD_RECOVER_TIMEOUT);
if (GETBIT(bus_status, SLAVE_DEAD_RECOVER_SUCCESS)) {
syslog(LOG_CRIT, "I2C(%d) Slave was dead. and bus has been recoveried successfully. "
"(I2C bus index base 0)", i);
}
bus_status = CLEARBIT(bus_status, SLAVE_DEAD_RECOVER_SUCCESS);
/* Check if any undefined bit remain in bus_status */
if ((bus_status != 0) && !GETBIT(asserted_flag[i], UNDEFINED_CASE)) {
asserted_flag[i] = SETBIT(asserted_flag[i], 8);
syslog(LOG_CRIT, "ASSERT: I2C(%d) Undefined case. (I2C bus index base 0)", i);
assert_handle = 1;
}
if (assert_handle) {
pal_i2c_crash_assert_handle(i);
}
}
}
sleep(30);
}
return NULL;
}
static void *
CPU_usage_monitor() {
unsigned long long user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice;
unsigned long long total_diff, idle_diff, non_idle, idle_time = 0, total = 0, pre_total = 0, pre_idle = 0;
char cpu[CPU_NAME_LENGTH] = {0};
int i, ready_flag = 0, timer = 0, retry = 0;
float cpu_util_avg, cpu_util_total;
float cpu_utilization[cpu_window_size];
FILE *fp;
int ret;
sleep(180); //Wait 180s for BMC to idle stage.
memset(cpu_utilization, 0, sizeof(float) * cpu_window_size);
// set flag to notice BMC healthd CPU_usage_monitor is ready
kv_set("flag_healthd_cpu", "1", 0, 0);
while (1) {
if (retry > HEALTHD_MAX_RETRY) {
syslog(LOG_CRIT, "Cannot get CPU statistics. Stop %s\n", __func__);
return NULL;
}
// Get CPU statistics. Time unit: jiffies
fp = fopen(CPU_INFO_PATH, "r");
if(!fp) {
syslog(LOG_WARNING, "Failed to get CPU statistics.\n");
retry++;
continue;
}
ret = fscanf(fp, "%s %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu",
cpu, &user, &nice, &system, &idle, &iowait, &irq, &softirq, &steal, &guest, &guest_nice);
if (ret != 11) {
syslog(LOG_WARNING, "Cannot parse CPU statistic. Stop %s\n", __func__);
retry++;
continue;
}
retry = 0;
fclose(fp);
timer %= cpu_window_size;
// Need more data to cacluate the avg. utilization. We average 60 records here.
if (timer == (cpu_window_size-1) && !ready_flag)
ready_flag = 1;
// guset and guest_nice are already accounted in user and nice so they are not included in total caculation
idle_time = idle + iowait;
non_idle = user + nice + system + irq + softirq + steal;
total = idle_time + non_idle;
// For runtime caculation, we need to take into account previous value.
total_diff = total - pre_total;
idle_diff = idle_time - pre_idle;
// These records are used to caculate the avg. utilization.
cpu_utilization[timer] = (float) (total_diff - idle_diff)/total_diff;
// Start to average the cpu utilization
if (ready_flag) {
cpu_util_total = 0;
for (i=0; i<cpu_window_size; i++) {
cpu_util_total += cpu_utilization[i];
}
cpu_util_avg = (cpu_util_total/cpu_window_size) * 100.0;
threshold_check(cpu_monitor_name, cpu_util_avg, cpu_threshold, cpu_threshold_num);
}
// Record current value for next caculation
pre_total = total;
pre_idle = idle_time;
timer++;
sleep(cpu_monitor_interval);
}
return NULL;
}
static int set_panic_on_oom(void) {
FILE *fp;
int ret;
int tmp_value;
fp = fopen(VM_PANIC_ON_OOM_FILE, "r+");
if (fp == NULL) {
syslog(LOG_CRIT, "%s: failed to open file: %s", __func__, VM_PANIC_ON_OOM_FILE);
return -1;
}
ret = fscanf(fp, "%d", &tmp_value);
if (ret != 1) {
syslog(LOG_CRIT, "%s: failed to read file: %s", __func__, VM_PANIC_ON_OOM_FILE);
fclose(fp);
return -1;
}
// if /proc/sys/vm/panic_on_oom is 0; set it to 1
if (tmp_value == 0) {
fseek(fp, 0, SEEK_SET);
ret = fputs("1", fp);
if (ret < 0) {
syslog(LOG_CRIT, "%s: failed to write to file: %s", __func__, VM_PANIC_ON_OOM_FILE);
fclose(fp);
return -1;
}
}
fclose(fp);
return 0;
}
static void *
memory_usage_monitor() {
struct sysinfo s_info;
int i, error, timer = 0, ready_flag = 0, retry = 0;
float mem_util_avg, mem_util_total;
float mem_utilization[mem_window_size];
char cmd[128];
memset(mem_utilization, 0, sizeof(float) * mem_window_size);
if (mem_enable_panic) {
set_panic_on_oom();
}
if (mem_min_free_kbytes > 0) {
snprintf(cmd, sizeof(cmd), "/sbin/sysctl -w vm.min_free_kbytes=%d >/dev/null", mem_min_free_kbytes);
if (system(cmd)) {
syslog(LOG_ERR, "set min_free_kbytes failed");
}
}
// set flag to notice BMC healthd memory_usage_monitor is ready
kv_set("flag_healthd_mem", "1", 0, 0);
while (1) {
if (retry > HEALTHD_MAX_RETRY) {
syslog(LOG_CRIT, "Cannot get sysinfo. Stop the %s\n", __func__);
return NULL;
}
timer %= mem_window_size;
// Need more data to cacluate the avg. utilization. We average 60 records here.
if (timer == (mem_window_size-1) && !ready_flag)
ready_flag = 1;
// Get sys info
error = sysinfo(&s_info);
if (error) {
syslog(LOG_WARNING, "%s Failed to get sys info. Error: %d\n", __func__, error);
retry++;
continue;
}
retry = 0;
// These records are used to caculate the avg. utilization.
mem_utilization[timer] = (float) (s_info.totalram - s_info.freeram)/s_info.totalram;
// Start to average the memory utilization
if (ready_flag) {
mem_util_total = 0;
for (i=0; i<mem_window_size; i++)
mem_util_total += mem_utilization[i];
mem_util_avg = (mem_util_total/mem_window_size) * 100.0;
threshold_check(mem_monitor_name, mem_util_avg, mem_threshold, mem_threshold_num);
}
timer++;
sleep(mem_monitor_interval);
}
return NULL;
}
// Thread to monitor the ECC counter
static void *
ecc_mon_handler() {
uint32_t mcr_fd = 0;
uint32_t ecc_status = 0;
uint32_t unrecover_ecc_err_addr = 0;
uint32_t recover_ecc_err_addr = 0;
uint16_t ecc_recoverable_error_counter = 0;
uint8_t ecc_unrecoverable_error_counter = 0;
void *mcr_base_addr;
void *mcr50_addr;
void *mcr58_addr;
void *mcr5c_addr;
int retry_err = 0;
// set flag to notice BMC healthd ecc_mon_handler is ready
kv_set("flag_healthd_ecc", "1", 0, 0);
while (1) {
mcr_fd = open("/dev/mem", O_RDWR | O_SYNC );
if (mcr_fd < 0) {
// In case of error opening the file, sleep for 2 sec and retry.
// During continuous failures, log the error every 20 minutes.
sleep(2);
if (++retry_err >= 600) {
syslog(LOG_ERR, "%s - cannot open /dev/mem", __func__);
retry_err = 0;
}
continue;
}
retry_err = 0;
mcr_base_addr = mmap(NULL, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, mcr_fd,
AST_MCR_BASE);
mcr50_addr = (char*)mcr_base_addr + INTR_CTRL_STS_OFFSET;
ecc_status = *(volatile uint32_t*) mcr50_addr;
if (ecc_addr_log) {
mcr58_addr = (char*)mcr_base_addr + ADDR_FIRST_UNRECOVER_ECC_OFFSET;
unrecover_ecc_err_addr = *(volatile uint32_t*) mcr58_addr;
mcr5c_addr = (char*)mcr_base_addr + ADDR_LAST_RECOVER_ECC_OFFSET;
recover_ecc_err_addr = *(volatile uint32_t*) mcr5c_addr;
}
munmap(mcr_base_addr, PAGE_SIZE);
close(mcr_fd);
ecc_recoverable_error_counter = (ecc_status >> 16) & 0xFF;
ecc_unrecoverable_error_counter = (ecc_status >> 12) & 0xF;
// Check ECC recoverable error counter
ecc_threshold_check(recoverable_ecc_name, ecc_recoverable_error_counter,
recov_ecc_threshold, recov_ecc_threshold_num, recover_ecc_err_addr);
// Check ECC un-recoverable error counter
ecc_threshold_check(unrecoverable_ecc_name, ecc_unrecoverable_error_counter,
unrec_ecc_threshold, unrec_ecc_threshold_num, unrecover_ecc_err_addr);
sleep(ecc_monitor_interval);
}
return NULL;
}
static void *
bmc_health_monitor()
{
int bmc_health_last_state = 1;
int bmc_health_kv_state = 1;
char tmp_health[MAX_VALUE_LEN];
int relog_counter = 0;
int relog_counter_criteria = regen_interval / bmc_health_monitor_interval;
size_t i;
int ret = 0;
while(1) {
// get current health status from kv_store
memset(tmp_health, 0, MAX_VALUE_LEN);
ret = pal_get_key_value(BMC_HEALTH_FILE, tmp_health);
if (ret){
syslog(LOG_ERR, " %s - kv get bmc_health status failed", __func__);
}
bmc_health_kv_state = atoi(tmp_health);
// If log-util clear all fru, cleaning CPU/MEM/ECC error status
// After doing it, daemon will regenerate asserted log
// Generage a syslog every regen_interval loop counter
if ((relog_counter >= relog_counter_criteria) ||
((bmc_health_last_state == 0) && (bmc_health_kv_state == 1))) {
for(i = 0; i < cpu_threshold_num; i++)
cpu_threshold[i].asserted = false;
for(i = 0; i < mem_threshold_num; i++)
mem_threshold[i].asserted = false;
for(i = 0; i < recov_ecc_threshold_num; i++)
recov_ecc_threshold[i].asserted = false;
for(i = 0; i < unrec_ecc_threshold_num; i++)
unrec_ecc_threshold[i].asserted = false;
pthread_mutex_lock(&global_error_mutex);
bmc_health = 0;
pthread_mutex_unlock(&global_error_mutex);
relog_counter = 0;
}
bmc_health_last_state = bmc_health_kv_state;
relog_counter++;
sleep(bmc_health_monitor_interval);
}
return NULL;
}
void check_nm_selftest_result(uint8_t fru, int result, uint8_t *selftest_result)
{
static uint8_t no_response_retry[MAX_NUM_FRUS] = {0};
static uint8_t abnormal_status_retry[MAX_NUM_FRUS] = {0};
char fru_name[10]={0};
int fru_index = fru - 1;//fru id is start from 1.
enum
{
NM_NORMAL_STATUS = 0,
};
//the fru data is validated, no need to check the data again.
pal_get_fru_name(fru, fru_name);
#ifdef DEBUG
syslog(LOG_WARNING, "fru_name:%s, fruid:%d, result:%d, nm_retry_threshold:%d", fru_name, fru, result, nm_retry_threshold);
#endif
if ( PAL_ENOTSUP == result )
{
if ( no_response_retry[fru_index] >= nm_retry_threshold )
{
if ( !is_duplicated_unaccess_event[fru_index] )
{
is_duplicated_unaccess_event[fru_index] = true;
syslog(LOG_CRIT, "ASSERT: ME Status - Controller Unavailable on the %s", fru_name);
}
}
else
{
no_response_retry[fru_index]++;
}
}
else
{
if ( NM_NORMAL_STATUS != result )
{
if ( abnormal_status_retry[fru_index] >= nm_retry_threshold )
{
if ( !is_duplicated_abnormal_event[fru_index] )
{
is_duplicated_abnormal_event[fru_index] = true;
syslog(LOG_CRIT, "ASSERT: ME Status - Controller Access Degraded or Unavailable on the %s, result: %02Xh, %02Xh", fru_name, selftest_result[0], selftest_result[1]);
}
}
else
{
abnormal_status_retry[fru_index]++;
}
}
else
{
if ( is_duplicated_abnormal_event[fru_index] )
{
is_duplicated_abnormal_event[fru_index] = false;
syslog(LOG_CRIT, "DEASSERT: ME Status - Controller Access Degraded or Unavailable on the %s", fru_name);
}
if ( is_duplicated_unaccess_event[fru_index] )
{
is_duplicated_unaccess_event[fru_index] = false;
syslog(LOG_CRIT, "DEASSERT: ME Status - Controller Unavailable on the %s", fru_name);
}
no_response_retry[fru_index] = 0;
abnormal_status_retry[fru_index] = 0;
}
}
}
void
nm_selftest(uint8_t fru) {
int ret = 0;
int result = 0;
const uint8_t normal_status[SIZE_SELF_TEST_RESULT] = {0x55, 0x00}; // If the selftest result is 55 00, the status of the controller is okay
uint8_t data[SIZE_SELF_TEST_RESULT] = {0x0};
if (pal_is_slot_server(fru) == 1) {
if (pal_is_fw_update_ongoing(fru) == true) {
return;
}
ret = pal_get_nm_selftest_result(fru, data);
if (PAL_EOK == ret) {
//if nm has the response, check the status
result = memcmp(data, normal_status, sizeof(normal_status));
} else {
//if nm has no response, suppose it is in the not support state
result = PAL_ENOTSUP;
}
check_nm_selftest_result(fru, result, data);
}
}
static void *
nm_monitor()
{
int fru;
while (1)
{
for ( fru = 1; fru <= MAX_NUM_FRUS; fru++)
{
nm_selftest(fru);
}
sleep(nm_monitor_interval);
}
return NULL;
}
void
crit_proc_ongoing_handle(bool is_crit_proc_updating)
{
static bool last_is_crit_proc_updating = false;
if (last_is_crit_proc_updating == is_crit_proc_updating) {
/* Nothing changed from last time. Just return */
return;
}
last_is_crit_proc_updating = is_crit_proc_updating;
if ( true == is_crit_proc_updating ) { // forbid the execution permission
if (system("chmod 666 /sbin/shutdown.sysvinit") != 0) {
syslog(LOG_ERR, "Disabling shutdown failed\n");
}
if (system("chmod 666 /sbin/halt.sysvinit") != 0) {
syslog(LOG_ERR, "Disabling halt failed\n");
}
if (system("chmod 666 /sbin/init") != 0) {
syslog(LOG_ERR, "Disabling init failed\n");
}
}
else {
if (system("chmod 4755 /sbin/shutdown.sysvinit") != 0) {
syslog(LOG_ERR, "Enabling shutdown failed\n");
}
if (system("chmod 4755 /sbin/halt.sysvinit") != 0) {
syslog(LOG_ERR, "Enabling halt failed\n");
}
if (system("chmod 4755 /sbin/init") != 0) {
syslog(LOG_ERR, "Enabling init failed\n");
}
}
}
//Block reboot and shutdown commands in BMC during any FW updating
static void *
crit_proc_monitor() {
bool is_fw_updating = false;
bool is_crashdump_ongoing = false;
bool is_cplddump_ongoing = false;
// set flag to notice BMC healthd crit_proc_monitor is ready
kv_set("flag_healthd_crit_proc", "1", 0, 0);
while(1)
{
//if is_fw_updating == true, means BMC is Updating a Device FW
is_fw_updating = pal_is_fw_update_ongoing_system();
//if is_autodump_ongoing == true, modify the permission
is_crashdump_ongoing = pal_is_crashdump_ongoing_system();
//if is_cplddump_ongoing == true, modify the permission
is_cplddump_ongoing = pal_is_cplddump_ongoing_system();
if ( (true == is_fw_updating) || (true == is_crashdump_ongoing) || (true == is_cplddump_ongoing) )
{
crit_proc_ongoing_handle(true);
}
if ( (false == is_fw_updating) && (false == is_crashdump_ongoing) && (false == is_cplddump_ongoing) )
{
crit_proc_ongoing_handle(false);
}
sleep(1);
}
return NULL;
}
static int log_count(const char *str)
{
char cmd[512];
FILE *fp;
int ret;
snprintf(cmd, sizeof(cmd), "grep \"%s\" /mnt/data/logfile /mnt/data/logfile.0 2> /dev/null | wc -l", str);
fp = popen(cmd, "r");
if (!fp) {
return 0;
}
if (fscanf(fp, "%d", &ret) != 1) {
ret = 0;
}
pclose(fp);
return ret;
}
/* parser curr_version from /etc/issue
and return version string length on success
otherwise return 0 as failure */
static size_t get_curr_version(char *buf, size_t buflen)
{
FILE *fp = fopen("/etc/issue", "r");
size_t ret = 0;
char *vers = 0;
if (fp) {
if(fscanf(fp, "OpenBMC Release %ms\n", &vers) == 1) {
ret = strnlen(vers, buflen);
if ( ret < buflen) {
snprintf(buf, buflen, "%s", vers);
} else {
ret = 0;
}
}
fclose(fp);
}
if (vers) free(vers);
return ret;
}
static char*
get_latest_n_versions(char* orig, size_t orig_len, size_t cnt)
{
char* p = orig;
if (p == 0)
return p;
p += orig_len;
while (cnt && p > orig) {
p--;
if (*p == ',') cnt--;
}
if (*p == ',') {
p++;
}
return p;
}
static void store_curr_version(void)
{
static char old_versions[MAX_VALUE_LEN*2];
static char curr_version[MAX_VALUE_LEN];
char *new_versions;
size_t old_versions_len;
size_t new_versions_len;
if (0 == get_curr_version(curr_version, sizeof(curr_version))) {
syslog(LOG_ERR, "Getting version failed!\n");
return;
}
if (kv_get(KV_KEY_IMAGE_VERSIONS, old_versions, &old_versions_len, KV_FPERSIST)) {
if (kv_set(KV_KEY_IMAGE_VERSIONS, curr_version, 0, KV_FPERSIST)) {
syslog(LOG_ERR, "Setting image version failed");
}
return;
}
/* kv-store won't gurantee null terminate when request to return value len
* so terminate it here explicitly */
old_versions[old_versions_len] = '\0';
/* check whether current version is the same as last version. */
if (strcmp( get_latest_n_versions(old_versions, old_versions_len, 1),
curr_version) == 0) {
/* version not changed */
return;
}
/* otherwise append curr_version */
new_versions = old_versions;
strcat(new_versions, ",");
strncat(new_versions, curr_version, MAX_VALUE_LEN);
new_versions_len = strnlen(new_versions, sizeof(old_versions));
/* chop off oldest versions until it fits MAX_VALUE_LEN */
while (new_versions_len >= MAX_VALUE_LEN) {
while (*new_versions && *new_versions != ',') {
new_versions++;
new_versions_len--;
}
if (*new_versions == ',') {
new_versions++;
new_versions_len--;
}
}
/* chop off oldest version and only keep at most VERSION_HISTORY_COUNT
* this is to backwards comptable with healthd which will crash
* when parser file contain history contain version more than VERSION_HISTORY_COUNT
*/
new_versions = get_latest_n_versions(new_versions, new_versions_len,
VERSION_HISTORY_COUNT);
/* save the new versions string */
if (kv_set(KV_KEY_IMAGE_VERSIONS, new_versions, 0, KV_FPERSIST)) {
syslog(LOG_ERR, "Setting image version failed");
}
}
/* Called when we have booted into the golden image
* because of verified boot failure */
static void check_vboot_recovery(uint8_t error_type, uint8_t error_code)
{
char log[512];
char curr_err[MAX_VALUE_LEN] = {0};
int assert_count, deassert_count;
/* Technically we can get this from the kv store vboot_error. But
* we cannot trust it since it could be compromised. Hence try to
* infer this by counting ASSERT and DEASSERT logs from the persistent
* log file */
snprintf(log, sizeof(log), " ASSERT: Verified boot failure (%d,%d)", error_type, error_code);
assert_count = log_count(log);
snprintf(log, sizeof(log), " DEASSERT: Verified boot failure (%d,%d)", error_type, error_code);
deassert_count = log_count(log);
if (assert_count <= deassert_count) {
/* This is the first time we are seeing this error. Log it */
syslog(LOG_CRIT, "ASSERT: Verified boot failure (%d,%d)", error_type, error_code);
syslog(LOG_CRIT, "Verified boot failure reason: %s", vboot_error(error_code));
}
/* Set the error so main can deassert with the correct error code */
snprintf(curr_err, sizeof(curr_err), "(%d,%d)", error_type, error_code);
kv_set("vboot_error", curr_err, 0, KV_FPERSIST);
}
/* Called when we have booted into CS1. Note verified boot
* could have still failed if we are not in SW enforcement */
static void check_vboot_main(uint8_t error_type, uint8_t error_code)
{
char last_err[MAX_VALUE_LEN] = {0};
if (error_type != 0 || error_code != 0) {
/* The only way we will boot into BMC (CS1) while there
* is an active vboot error is if we are not enforcing.
* Act as if we are in recovery */
check_vboot_recovery(error_type, error_code);
} else {
/* We have successfully booted into a verified BMC! */
if (0 != kv_get("vboot_error", last_err, NULL, KV_FPERSIST)) {
/* We do not have info of the previous error. Not much we can do
* log an info message and carry on */
syslog(LOG_INFO, "Verified boot successful!");
/* Create the key */
kv_set("vboot_error", "(0,0)", 0, KV_FPERSIST);
} else if (strcmp(last_err, "(0,0)")) {
/* We just recovered from a previous error! */
syslog(LOG_CRIT, "DEASSERT: Verified boot failure %s", last_err);
/* Do not deassert again on reboot */
kv_set("vboot_error", "(0,0)", 0, KV_FPERSIST);
}
store_curr_version();
}
}
static void check_vboot_state(void)
{
struct vbs *v = vboot_status();
if (!v) {
syslog(LOG_CRIT, "%s: Getting verified boot status failed", __func__);
return;
}
if (v->recovery_boot) {
check_vboot_recovery(v->error_type, v->error_code);
} else {
check_vboot_main(v->error_type, v->error_code);
}
}
static void
log_reboot_cause(char *sled_off_time)
{
int mem_fd;
uint8_t *bmc_reboot_base;
uint32_t reboot_detected_flag = 0;
uint32_t kern_panic_flag = 0;
uint32_t sram_bmc_reboot_base = 0x0;
uint32_t sram_offset = 0x0;
if (get_soc_model() == SOC_MODEL_ASPEED_G6) {
sram_bmc_reboot_base = AST_G6_SRAM_BMC_REBOOT_BASE;
sram_offset = AST_G6_SRAM_BMC_REBOOT_OFFSET;
} else {
sram_bmc_reboot_base = AST_SRAM_BMC_REBOOT_BASE;
sram_offset = AST_SRAM_BMC_REBOOT_OFFSET;
}
mem_fd = open("/dev/mem", O_RDWR | O_SYNC);
if (mem_fd < 0) {
syslog(LOG_CRIT, "devmem open failed");
return;
}
bmc_reboot_base = mmap(NULL, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, mem_fd, sram_bmc_reboot_base);
if (bmc_reboot_base == NULL) {
syslog(LOG_CRIT, "Mapping SRAM_BMC_REBOOT_BASE failed");
close(mem_fd);
return;
}
// If this reset is due to Power-On-Reset, we detected SLED power OFF event
if (pal_is_bmc_por()) {
syslog(LOG_CRIT, "SLED Powered OFF at %s", sled_off_time);
pal_add_cri_sel("BMC AC lost");
// Initial BMC reboot flag
BMC_REBOOT_BY_KERN_PANIC(bmc_reboot_base, sram_offset) = 0x0;
BMC_REBOOT_BY_CMD(bmc_reboot_base, sram_offset) = 0x0;
BMC_REBOOT_SIG(bmc_reboot_base, sram_offset) = BOOT_MAGIC;
} else {
kern_panic_flag = BMC_REBOOT_BY_KERN_PANIC(bmc_reboot_base, sram_offset);
reboot_detected_flag = BMC_REBOOT_BY_CMD(bmc_reboot_base, sram_offset);
// Check the SRAM to make sure the reboot cause
// ===== kernel panic =====
if ((kern_panic_flag & BIT_RECORD_LOG)) {
// Clear the flag of record log and reserve the kernel panic flag
kern_panic_flag &= ~(BIT_RECORD_LOG);
if ((kern_panic_flag & FLAG_KERN_PANIC)) {
syslog(LOG_CRIT, "BMC Reboot detected - caused by kernel panic");
} else {
syslog(LOG_CRIT, "BMC Reboot detected - unknown kernel flag (0x%08x)", kern_panic_flag);
}
BMC_REBOOT_BY_KERN_PANIC(bmc_reboot_base, sram_offset) = 0;
// ===== reboot command =====
} else if ((reboot_detected_flag & BIT_RECORD_LOG)) {
// Clear the flag of record log and reserve the reboot command flag
reboot_detected_flag &= ~(BIT_RECORD_LOG);
if ((reboot_detected_flag & FLAG_CFG_UTIL)) {
reboot_detected_flag &= ~(FLAG_CFG_UTIL);
syslog(LOG_CRIT, "Reset BMC data to default factory settings");
}
if ((reboot_detected_flag & FLAG_UBIFS_ERROR)) {
reboot_detected_flag &= ~(FLAG_UBIFS_ERROR);
syslog(LOG_CRIT, "UBIFS Error detected during boot");
}
if ((reboot_detected_flag & FLAG_REBOOT_CMD)) {
syslog(LOG_CRIT, "BMC Reboot detected - caused by reboot command");
} else {
syslog(LOG_CRIT, "BMC Reboot detected - unknown reboot command flag (0x%08x)", reboot_detected_flag);
}
BMC_REBOOT_BY_CMD(bmc_reboot_base, sram_offset) = 0;
// ===== others =====
} else {
syslog(LOG_CRIT, "BMC Reboot detected");
}
}
munmap(bmc_reboot_base, PAGE_SIZE);
close(mem_fd);
}
// Thread to monitor SLED Cycles by using time stamp
static void *
timestamp_handler()
{
int count = 0;
struct timespec ts;
struct timespec mts;
char tstr[MAX_VALUE_LEN] = {0};
char buf[128] = {0};
uint8_t time_init = 0;
long time_sled_on;
long time_sled_off;
// Read the last timestamp from KV storage
pal_get_key_value("timestamp_sled", tstr);
time_sled_off = (long) strtol(tstr, NULL, 10);
ctime_r(&time_sled_off, buf);
log_reboot_cause(buf);
// set flag to notice BMC healthd timestamp_handler is ready
kv_set("flag_healthd_bmc_timestamp", "1", 0, 0);
while (1) {
// Make sure the time is initialized properly
// Since there is no battery backup, the time could be reset to build time
// wait 100s at most, to prevent infinite waiting
if ( time_init < SLED_TS_TIMEOUT ) {
// Read current time
clock_gettime(CLOCK_REALTIME, &ts);
if ( (ts.tv_sec < time_sled_off) && (++time_init < SLED_TS_TIMEOUT) ) {
sleep(1);
continue;
}
// If get the correct time or time sync timeout
time_init = SLED_TS_TIMEOUT;
// Need to log SLED ON event, if this is Power-On-Reset
if (pal_is_bmc_por()) {
// Get uptime
clock_gettime(CLOCK_MONOTONIC, &mts);
// To find out when SLED was on, subtract the uptime from current time
time_sled_on = ts.tv_sec - mts.tv_sec;
ctime_r(&time_sled_on, buf);
// Log an event if this is Power-On-Reset
syslog(LOG_CRIT, "SLED Powered ON at %s", buf);
}
pal_update_ts_sled();
}
// Store timestamp every one hour to keep track of SLED power
if (count++ == HB_TIMESTAMP_COUNT) {
pal_update_ts_sled();
count = 0;
}
sleep(HB_SLEEP_TIME);
}
return NULL;
}
static void *
bic_health_monitor() {
int err_cnt = 0;
int i = 0;
uint8_t status = 0;
uint8_t err_type[BIC_RESET_ERR_CNT] = {0};
uint8_t type = 0;
const char* err_str[BIC_ERR_TYPE_CNT] = {
"heartbeat", "IPMB", "BIC ready"
};
char err_log[MAX_LOG_SIZE] = "\0";
bool is_already_reset = false;
// set flag to notice BMC healthd bic_health_monitor is ready
kv_set("flag_healthd_bic_health", "1", 0, 0);
while (1) {
if ((pal_get_server_12v_power(bic_fru, &status) < 0) || (status == SERVER_12V_OFF)) {
goto next_run;
}
// Check if bic is updating
if (pal_is_fw_update_ongoing(bic_fru) == true) {
err_cnt = 0;
sleep(BIC_HEALTH_INTERVAL);
continue;
}
// Read BIC ready pin to check BIC boots up completely
if ((pal_is_bic_ready(bic_fru, &status) < 0) || (status == false)) {
err_type[err_cnt++] = BIC_READY_ERR;
goto next_run;
}
// Check whether BIC heartbeat works
if (pal_is_bic_heartbeat_ok(bic_fru) == false) {
err_type[err_cnt++] = BIC_HB_ERR;
goto next_run;
}
// Send a IPMB command to check IPMB service works normal
if (pal_bic_self_test() < 0) {
err_type[err_cnt++] = BIC_IPMB_ERR;
goto next_run;
}
// if all check pass, clear error counter and reset flag
err_cnt = 0;
is_already_reset = false;
// The ME commands are transmit via BIC on Grand Canyon, so check ME health when BIC health is good.
if ((nm_monitor_enabled == true) && (nm_transmission_via_bic == true)) {
nm_selftest(bic_fru);
}
next_run:
if ((err_cnt >= BIC_RESET_ERR_CNT) && (is_already_reset == false)) {
// if error counter over 3, reset BIC by hardware
if (pal_bic_hw_reset() == 0) {
memset(err_log, 0, sizeof(err_log));
for (i = 0; i < BIC_RESET_ERR_CNT; i++) {
type = err_type[i];
strcat(err_log, err_str[type]);
if (i != BIC_RESET_ERR_CNT - 1) { // last one
strcat(err_log, ", ");
}
}
syslog(LOG_CRIT, "FRU %d BIC reset by BIC health monitor due to health check failed in following order: %s",
bic_fru, err_log);
err_cnt = 0;
is_already_reset = true;
}
}
sleep(BIC_HEALTH_INTERVAL);
}
return NULL;
}
static void *
log_rearm_check() {
int ret = 0;
char val[MAX_KEY_LEN] = {0};
while (1) {
ret = kv_get(KV_KEY_HEALTHD_REARM, val, NULL, 0);
if (ret < 0) {
sleep(LOG_REARM_CHECK_INTERVAL);
continue;
}
if (strcmp(val, "1") == 0) {
if (nm_monitor_enabled == true) {
memset(is_duplicated_unaccess_event, 0, sizeof(is_duplicated_unaccess_event));
memset(is_duplicated_abnormal_event, 0, sizeof(is_duplicated_abnormal_event));
}
if (vboot_state_check && vboot_supported()) {
check_vboot_state();
}
kv_set(KV_KEY_HEALTHD_REARM, "0", 0, 0);
}
sleep(LOG_REARM_CHECK_INTERVAL);
}
return NULL;
}
static void *
ubifs_health_monitor() {
const char ubifs_ro_error[] = "/sys/kernel/debug/ubifs/ubi0_0/ro_error";
FILE *fp;
int val;
int mem_fd;
uint8_t *bmc_reboot_base;
uint32_t sram_bmc_reboot_base = 0x0;
uint32_t sram_offset = 0x0;
if (get_soc_model() == SOC_MODEL_ASPEED_G6) {
sram_bmc_reboot_base = AST_G6_SRAM_BMC_REBOOT_BASE;
sram_offset = AST_G6_SRAM_BMC_REBOOT_OFFSET;
} else {
sram_bmc_reboot_base = AST_SRAM_BMC_REBOOT_BASE;
sram_offset = AST_SRAM_BMC_REBOOT_OFFSET;
}
while (1) {
fp = fopen(ubifs_ro_error, "r");
if (fp == NULL) {
syslog(LOG_ERR, "%s: open %s failed", __func__, ubifs_ro_error);
} else if (fscanf(fp, "%d", &val) != 1) {
syslog(LOG_ERR, "%s: read %s failed", __func__, ubifs_ro_error);
} else if (val != 0) {
syslog(LOG_CRIT, "%s: ubifs (/dev/ubi0_0) in read-only mode (ro_error=%d)", __func__, val);
mem_fd = open("/dev/mem", O_RDWR | O_SYNC);
if (mem_fd < 0) {
syslog(LOG_ERR, "devmem open failed");
} else {
bmc_reboot_base = mmap(NULL, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, mem_fd, sram_bmc_reboot_base);
if (bmc_reboot_base == NULL) {
syslog(LOG_ERR, "Mapping SRAM_BMC_REBOOT_BASE failed");
} else {
BMC_REBOOT_BY_CMD(bmc_reboot_base, sram_offset) |= BIT_RECORD_LOG | FLAG_UBIFS_ERROR;
}
close(mem_fd);
}
fclose(fp);
break;
}
if (fp != NULL)
fclose(fp);
sleep(uhm_config.monitor_interval);
}
pal_bmc_reboot(RB_AUTOBOOT);
return NULL;
}
void sig_handler(int signo) {
// Catch SIGALRM and SIGTERM. If recived signal record BMC log
syslog(LOG_CRIT, "BMC health daemon stopped.");
exit(0);
}
int
main(int argc, char **argv) {
pthread_t tid_watchdog;
pthread_t tid_hb_led;
pthread_t tid_i2c_mon;
pthread_t tid_cpu_monitor;
pthread_t tid_mem_monitor;
pthread_t tid_crit_proc_monitor;
pthread_t tid_ecc_monitor;
pthread_t tid_bmc_health_monitor;
pthread_t tid_nm_monitor;
pthread_t tid_pfr_monitor;
pthread_t tid_timestamp_handler;
pthread_t tid_bic_health_monitor;
pthread_t tid_log_rearm_check;
pthread_t tid_ubifs_health_monitor;
if (argc > 1) {
exit(1);
}
//Catch signals
signal(SIGALRM, sig_handler);
signal(SIGTERM, sig_handler);
initilize_all_kv();
initialize_configuration();
if (vboot_state_check && vboot_supported()) {
check_vboot_state();
/* curr version is stored only when vboot is
* successful */
} else {
store_curr_version();
}
// For current platforms, we are using WDT from either fand or fscd
// TODO: keeping this code until we make healthd as central daemon that
// monitors all the important daemons for the platforms.
if (pthread_create(&tid_watchdog, NULL, watchdog_handler, NULL)) {
syslog(LOG_WARNING, "pthread_create for watchdog error\n");
exit(1);
}
if (pthread_create(&tid_hb_led, NULL, hb_handler, NULL)) {
syslog(LOG_WARNING, "pthread_create for heartbeat error\n");
exit(1);
}
if (cpu_monitor_enabled) {
if (pthread_create(&tid_cpu_monitor, NULL, CPU_usage_monitor, NULL)) {
syslog(LOG_WARNING, "pthread_create for monitor CPU usage\n");
exit(1);
}
}
if (mem_monitor_enabled) {
if (pthread_create(&tid_mem_monitor, NULL, memory_usage_monitor, NULL)) {
syslog(LOG_WARNING, "pthread_create for monitor memory usage\n");
exit(1);
}
}
if (i2c_monitor_enabled) {
// Add a thread for monitoring all I2C buses crash or not
if (pthread_create(&tid_i2c_mon, NULL, i2c_mon_handler, NULL)) {
syslog(LOG_WARNING, "pthread_create for I2C errorr\n");
exit(1);
}
}
if (ecc_monitor_enabled) {
if (pthread_create(&tid_ecc_monitor, NULL, ecc_mon_handler, NULL)) {
syslog(LOG_WARNING, "pthread_create for ECC monitoring errorr\n");
exit(1);
}
}
if (regen_log_enabled) {
if (pthread_create(&tid_bmc_health_monitor, NULL, bmc_health_monitor, NULL)) {
syslog(LOG_WARNING, "pthread_create for BMC Health monitoring errorr\n");
exit(1);
}
}
if ((nm_monitor_enabled == true) && (nm_transmission_via_bic == false)) {
if (pthread_create(&tid_nm_monitor, NULL, nm_monitor, NULL)) {
syslog(LOG_WARNING, "pthread_create for nm monitor error\n");
exit(1);
}
}
if (pfr_monitor_enabled) {
if (pthread_create(&tid_pfr_monitor, NULL, pfr_monitor, NULL)) {
syslog(LOG_WARNING, "pthread_create for pfr monitor error\n");
exit(1);
}
}
if (pthread_create(&tid_crit_proc_monitor, NULL, crit_proc_monitor, NULL)) {
syslog(LOG_WARNING, "pthread_create for FW Update Monitor error\n");
exit(1);
}
if (bmc_timestamp_enabled) {
if (pthread_create(&tid_timestamp_handler, NULL, timestamp_handler, NULL)) {
syslog(LOG_WARNING, "pthread_create for time stamp handler error\n");
exit(1);
}
}
if (bic_health_enabled) {
if (pthread_create(&tid_bic_health_monitor, NULL, bic_health_monitor, NULL)) {
syslog(LOG_WARNING, "pthread_create for bic health monitor error\n");
exit(1);
}
}
if (pthread_create(&tid_log_rearm_check, NULL, log_rearm_check, NULL)) {
syslog(LOG_WARNING, "pthread_create for log re-arm monitor error\n");
exit(1);
}
if (uhm_config.enabled) {
if (pthread_create(&tid_ubifs_health_monitor, NULL, ubifs_health_monitor, NULL)) {
syslog(LOG_WARNING, "pthread_create for ubifs health monitor error\n");
exit(1);
}
}
pthread_join(tid_watchdog, NULL);
pthread_join(tid_hb_led, NULL);
if (i2c_monitor_enabled) {
pthread_join(tid_i2c_mon, NULL);
}
if (cpu_monitor_enabled) {
pthread_join(tid_cpu_monitor, NULL);
}
if (mem_monitor_enabled) {
pthread_join(tid_mem_monitor, NULL);
}
if (ecc_monitor_enabled) {
pthread_join(tid_ecc_monitor, NULL);
}
if (regen_log_enabled) {
pthread_join(tid_bmc_health_monitor, NULL);
}
if ((nm_monitor_enabled == true) && (nm_transmission_via_bic == false)) {
pthread_join(tid_nm_monitor, NULL);
}
if (pfr_monitor_enabled) {
pthread_join(tid_pfr_monitor, NULL);
}
pthread_join(tid_crit_proc_monitor, NULL);
if (bmc_timestamp_enabled) {
pthread_join(tid_timestamp_handler, NULL);
}
if (bic_health_enabled) {
pthread_join(tid_bic_health_monitor, NULL);
}
if (log_rearm_enabled){
pthread_join(tid_log_rearm_check, NULL);
}
if (uhm_config.enabled) {
pthread_join(tid_ubifs_health_monitor, NULL);
}
return 0;
}