common/recipes-core/power-util/files/power-util.c (583 lines of code) (raw):
/*
 * 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/kv.h>
#include <openbmc/pal.h>
#define POWER_ON_STR        "on"
#define POWER_OFF_STR       "off"
#define MAX_RETRIES          10
#ifndef PWR_OPTION_LIST
#define PWR_OPTION_LIST "status, graceful-shutdown, off, on, reset, cycle, " \
                        "12V-off, 12V-on, 12V-cycle"
#endif
#ifndef PWR_DEV_OPTION_LIST
#define PWR_DEV_OPTION_LIST "status, off, on, reset, cycle"
#endif
#define PWR_UTL_LOCK "/var/run/power-util_%d.lock"
#ifdef FRU_DEVICE_LIST
  static const char * pal_dev_list_power = pal_dev_pwr_list;
  static const char * dev_pwr_option_list = pal_dev_pwr_option_list;
#else
  static const char * pal_dev_list_power = NULL;
  static const char * dev_pwr_option_list = NULL;
#endif
const char *pwr_option_list = PWR_OPTION_LIST;
#ifdef ENABLE_FORCE_POWER_CMD
const char *force_pwr_option_list = "on, 12V-on";
#endif
enum {
  PWR_STATUS = 1,
  PWR_GRACEFUL_SHUTDOWN,
  PWR_OFF,
  PWR_ON,
  PWR_RESET,
  PWR_CYCLE,
  PWR_12V_OFF,
  PWR_12V_ON,
  PWR_12V_CYCLE,
  PWR_SLED_CYCLE
};
static const char *option_list[] = {
  "NA",  // place holder so the rest of the list matches enum above
  "STATUS",
  "GRACEFUL_SHUTDOWN",
  "PWR_OFF",
  "PWR_ON",
  "POWER_RESET",
  "POWER_CYCLE",
  "12V_OFF",
  "12V_ON",
  "12V_CYCLE",
  "SLED_CYCLE",
};
static void
print_usage() {
  char fru_list[256] = {0};
  pal_get_fru_list_by_caps(FRU_CAPABILITY_POWER_STATUS, fru_list, 256);
  printf("Usage: power-util [ %s ] [ %s ]\nUsage: power-util sled-cycle\n",
      fru_list, pwr_option_list);
  // TODO Form list like fruid-util.
  if (pal_dev_list_power != NULL && dev_pwr_option_list != NULL) {
    if (!strncmp(pal_dev_list_power, "all, ", strlen("all, "))) {
      pal_dev_list_power = pal_dev_list_power + strlen("all, ");
    }
    printf("Usage: power-util [ %s ] [ %s ] [ %s ]\n",
      fru_list, pal_dev_list_power, dev_pwr_option_list);
  }
  #ifdef ENABLE_FORCE_POWER_CMD
    printf("Usage: power-util [ %s ] --force [ %s ]\n",
      fru_list, force_pwr_option_list);
  #endif
}
static bool
is_power_cmd_valid(char *option)
{
  char option_list[256];
  char *pch;
  /* All platforms support sled-cycle */
  if (!strcmp(option, "sled-cycle")) {
    return true;
  }
  /* strtok modifies the passed in string. We cannot
   * pass a const string to it. So make a copy */
  strcpy(option_list, pwr_option_list);
  pch = strtok(option_list, ", ");
  while (pch != NULL) {
    if (!strcmp(pch, option)) {
      return true;
    }
    pch = strtok(NULL, ", ");
  }
  return false;
}
static int
get_power_opt(char *option, uint8_t *opt) {
  if (!is_power_cmd_valid(option)) {
    return -1;
  }
  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, "reset")) {
    *opt = PWR_RESET;
  } 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;
}
//check power policy and power state to power on/off server after AC power restore
void
power_policy_control(uint8_t fru, char *last_ps, bool force) {
  uint8_t chassis_status[5] = {0};
  uint8_t chassis_status_length;
  uint8_t power_policy = POWER_CFG_UKNOWN;
  char pwr_state[MAX_VALUE_LEN] = {0};
  if (pal_is_slot_server(fru) == 0) {
    return;
  }
  if (force) { // ignore power policy, force power on
    pal_set_server_power(fru, SERVER_FORCE_POWER_ON);
    return;
  }
  //get power restore policy
  //defined by IPMI Spec/Section 28.2.
  pal_get_chassis_status(fru, NULL, chassis_status, &chassis_status_length);
  //byte[1], bit[6:5]: power restore policy
  power_policy = (*chassis_status >> 5);
  //Check power policy and last power state
  if(power_policy == POWER_CFG_LPS) {
    if (!last_ps) {
      pal_get_last_pwr_state(fru, pwr_state);
      last_ps = pwr_state;
    }
    if (!(strcmp(last_ps, "on"))) {
      sleep(3);
      pal_set_server_power(fru, SERVER_POWER_ON);
    }
  }
  else if(power_policy == POWER_CFG_ON) {
    sleep(3);
    pal_set_server_power(fru, SERVER_POWER_ON);
  }
}
static int
dev_power_util(uint8_t fru, char *dev_name, uint8_t dev_id ,uint8_t opt) {
  int ret = 0;
  uint8_t status, type;
  int retries;
  uint8_t root = 0;
  if (opt != PWR_STATUS) {
    if (!pal_can_change_power(pal_get_root_fru(fru, &root) == PAL_EOK ? root : fru)) {
      return -1;
    }
  }
  switch(opt) {
    case PWR_STATUS:
      ret = pal_get_device_power(fru, dev_id, &status, &type);
      if (ret < 0) {
        syslog(LOG_WARNING, "power_util: pal_get_device_power failed for fru %u dev %s\n", fru, dev_name);
        return ret;
      }
      printf("Power status for fru %u dev %s : %s\n", fru, dev_name, status?"ON":"OFF");
      break;
    case PWR_OFF:
      printf("Powering fru %u dev %s to OFF state...\n", fru, dev_name);
      ret = pal_set_device_power(fru, dev_id, SERVER_POWER_OFF);
      if (ret < 0) {
        syslog(LOG_WARNING, "power_util: pal_set_device_power failed for"
          " fru %u", fru);
        return ret;
      } else if (ret == 1) {
        printf("fru %u dev %s is already powered OFF...\n", fru, dev_name);
        return 0;
      } else {
        syslog(LOG_CRIT, "SERVER_POWER_OFF successful for FRU: %d DEV: %s", fru, dev_name);
      }
      break;
    case PWR_ON:
      printf("Powering fru %u dev %s to ON state...\n", fru, dev_name);
      ret = pal_set_device_power(fru, dev_id, SERVER_POWER_ON);
      if (ret == 1) {
        printf("fru %u dev %s is already powered ON...\n", fru, dev_name);
        return 0;
      } else if (ret == -2) {  //check if fru is not ready
        syslog(LOG_WARNING, "power_util: pal_set_device_power failed for"
          " fru %u", fru);
        return ret;
      }
      for (retries = 0; retries < MAX_RETRIES; retries++) {
         sleep(3);
         ret = pal_get_device_power(fru, dev_id, &status, &type);
         if ((ret >= 0) && (status == SERVER_POWER_ON)) {
           syslog(LOG_CRIT, "SERVER_POWER_ON successful for FRU: %u DEV: %s", fru, dev_name);
           break;
         }
         ret = pal_set_device_power(fru, dev_id, SERVER_POWER_ON);
      }
      if (ret < 0 || status != SERVER_POWER_ON) {
        syslog(LOG_WARNING, "power_util: pal_set_device_power failed for"
          " for fru %u dev %s with status=%u", fru, dev_name, status);
        return ret;
      }
      break;
    case PWR_CYCLE:
      printf("Power cycling fru %u dev %s...\n", fru, dev_name);
      ret = pal_set_device_power(fru, dev_id, SERVER_POWER_CYCLE);
      if (ret < 0) {
        syslog(LOG_WARNING, "power_util: pal_set_server_power failed for"
          " fru %u", fru);
        return ret;
      } else {
        syslog(LOG_CRIT, "SERVER_POWER_CYCLE successful for FRU: %d DEV: %s", fru, dev_name);
      }
      break;
    default:
      syslog(LOG_WARNING, "power_util: wrong option");
  }
  return ret;
}
static int
power_util(uint8_t fru, uint8_t opt, bool force) {
  int ret = 0;
  uint8_t status;
  int retries;
  char pwr_state[MAX_VALUE_LEN] = {0};
  uint8_t root = 0;
  if (opt == PWR_SLED_CYCLE) {
    for(fru = 1; fru <= MAX_NUM_FRUS; fru++) {
      if (!pal_can_change_power(pal_get_root_fru(fru, &root) == PAL_EOK ? root : fru)) {
        return -1;
      }
    }
  } else if (opt != PWR_STATUS) {
    if (!pal_can_change_power(pal_get_root_fru(fru, &root) == PAL_EOK ? root : fru)) {
      return -1;
    }
  }
  switch(opt) {
    case PWR_STATUS:
      ret = pal_get_server_power(fru, &status);
      if (ret < 0) {
        syslog(LOG_WARNING, "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");
      printf("Power status for fru %u : ", fru);
      switch(status) {
        case SERVER_POWER_ON:
          printf("ON\n");
          break;
        case SERVER_POWER_OFF:
          printf("OFF\n");
          break;
        case SERVER_12V_OFF:
          printf("OFF (12V-OFF)\n");
          break;
        case SERVER_12V_ON:
          printf("ON (12V-ON)\n");
          break;
      }
      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_WARNING, "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;
      } else {
        syslog(LOG_CRIT, "SERVER_GRACEFUL_SHUTDOWN successful for FRU: %d", fru);
      }
      ret = pal_set_last_pwr_state(fru, POWER_OFF_STR);
      if (ret < 0) {
        return ret;
      }
      ret = pal_set_led(fru, LED_OFF);
      if (ret < 0) {
        syslog(LOG_WARNING, "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_WARNING, "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;
      } else {
        syslog(LOG_CRIT, "SERVER_POWER_OFF successful for FRU: %d", fru);
      }
      ret = pal_set_last_pwr_state(fru, POWER_OFF_STR);
      if (ret < 0) {
        return ret;
      }
      ret = pal_set_led(fru, LED_OFF);
      if (ret < 0) {
        syslog(LOG_WARNING, "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);
      if (force) {
        ret = pal_set_server_power(fru, SERVER_FORCE_POWER_ON);
      } else {
        ret = pal_set_server_power(fru, SERVER_POWER_ON);
      }
      if (ret == 1) {
        printf("fru %u is already powered ON...\n", fru);
        return 0;
      } else if (ret == -2) {  //check if fru is not ready
        syslog(LOG_WARNING, "power_util: pal_set_server_power failed for"
          " fru %u", fru);
        return ret;
      } else if (ret == -3) {  //block power on due to fan fail, do not retry power on
        return ret;
      }
      for (retries = 0; retries < MAX_RETRIES; retries++) {
         sleep(3);
         ret = pal_get_server_power(fru, &status);
         if ((ret >= 0) && (status == SERVER_POWER_ON)) {
           syslog(LOG_CRIT, "SERVER_POWER_ON successful for FRU: %d", fru);
           break;
         }
         if (force) {
           ret = pal_set_server_power(fru, SERVER_FORCE_POWER_ON);
         } else {
           ret = pal_set_server_power(fru, SERVER_POWER_ON);
         }
      }
      if (ret < 0 || status != SERVER_POWER_ON) {
        syslog(LOG_WARNING, "power_util: pal_set_server_power failed for"
          " for fru %u with status=%u", fru, status);
        return ret;
      }
      ret = pal_set_last_pwr_state(fru, POWER_ON_STR);
      if (ret < 0) {
        return ret;
      }
      ret = pal_set_led(fru, LED_ON);
      if (ret < 0) {
        syslog(LOG_WARNING, "power_util: pal_set_led failed for fru %u", fru);
        return ret;
      }
      pal_set_restart_cause(fru, RESTART_CAUSE_IPMI_CHASSIS_CMD);
      break;
    case PWR_RESET:
      printf("Power reset fru %u...\n", fru);
      ret = pal_set_server_power(fru, SERVER_POWER_RESET);
      if (ret < 0) {
        syslog(LOG_WARNING, "power_util: pal_set_rst_btn failed for"
          " fru %u", fru);
        printf("Power reset fail for fru %u\n", fru);
        return ret;
      }
      syslog(LOG_CRIT, "SERVER_POWER_RESET successful for FRU: %d", fru);
      pal_set_restart_cause(fru, RESTART_CAUSE_IPMI_CHASSIS_CMD);
      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_WARNING, "power_util: pal_set_server_power failed for"
          " fru %u", fru);
        return ret;
      } else {
        syslog(LOG_CRIT, "SERVER_POWER_CYCLE successful for FRU: %d", fru);
      }
      ret = pal_set_last_pwr_state(fru, POWER_ON_STR);
      if (ret < 0) {
        return ret;
      }
      ret = pal_set_led(fru, LED_ON);
      if (ret < 0) {
        syslog(LOG_WARNING, "power_util: pal_set_led failed for fru %u", fru);
        return ret;
      }
      pal_set_restart_cause(fru, RESTART_CAUSE_IPMI_CHASSIS_CMD);
      break;
    case PWR_12V_OFF:
      printf("12V Powering fru %u to OFF state...\n", fru);
      ret = pal_set_server_power(fru, SERVER_12V_OFF);
      if (ret < 0) {
        syslog(LOG_WARNING, "power_util: pal_set_server_power failed for"
          " fru %u", fru);
        return ret;
      } else if (ret == 1) {
        printf("fru %u is already powered 12V-OFF...\n", fru);
        return 0;
      } else {
        syslog(LOG_CRIT, "SERVER_12V_OFF successful for FRU: %d", fru);
      }
      ret = pal_set_led(fru, LED_OFF);
      if (ret < 0) {
        syslog(LOG_WARNING, "power_util: pal_set_led failed for fru %u", fru);
        return ret;
      }
      break;
    case PWR_12V_ON:
      printf("12V Powering fru %u to ON state...\n", fru);
      if (force) {
        ret = pal_set_server_power(fru, SERVER_FORCE_12V_ON);
      } else {
        ret = pal_set_server_power(fru, SERVER_12V_ON);
      }
      if (ret < 0) {
        syslog(LOG_WARNING, "power_util: pal_set_server_power failed for"
          " fru %u", fru);
        return ret;
      } else if (ret == 1) {
        printf("fru %u is already powered 12V-ON...\n", fru);
        return 0;
      } else {
        syslog(LOG_CRIT, "SERVER_12V_ON successful for FRU: %d", fru);
        power_policy_control(fru, NULL,force);
      }
      break;
    case PWR_12V_CYCLE:
      printf("12V Power cycling fru %u...\n", fru);
      pal_get_last_pwr_state(fru, pwr_state);
      ret = pal_set_server_power(fru, SERVER_12V_CYCLE);
      if (ret < 0) {
        syslog(LOG_WARNING, "power_util: pal_set_server_power failed for"
          " fru %u", fru);
        return ret;
      } else {
        syslog(LOG_CRIT, "SERVER_12V_CYCLE successful for FRU: %d", fru);
        power_policy_control(fru, pwr_state,force);
      }
      break;
    case PWR_SLED_CYCLE:
      syslog(LOG_CRIT, "SLED_CYCLE starting...");
      pal_update_ts_sled();
      sync();
      sleep(2);
      ret = pal_sled_cycle();
      break;
    default:
      syslog(LOG_WARNING, "power_util: wrong option");
      ret = 2;
  }
  return ret;
}
static int
add_process_running_flag(uint8_t slot_id, uint8_t opt) {
  int pid_file;
  char path[128];
  if (opt == PWR_STATUS) {
    return opt;
  } else {
    sprintf(path, PWR_UTL_LOCK, slot_id);
    pid_file = open(path, O_CREAT | O_RDWR, 0666);
    if (flock(pid_file, LOCK_EX | LOCK_NB) && (errno == EWOULDBLOCK)) {
      return (-opt);
    }
  }
  return opt;
}
static void
rm_process_running_flag(uint8_t slot_id, uint8_t opt) {
  char path[128];
  if (opt != PWR_STATUS) {
    sprintf(path, PWR_UTL_LOCK, slot_id);
    remove(path);
  }
}
int parse_args(int argc, char *argv[], bool *force) {
  int ret;
  int index;
  static struct option opts[] = {
    {"force", no_argument, 0, 'f'},
    {0,0,0,0},
  };
  /* Set defaults */
  *force = false;
  while(-1 != (ret = getopt_long(argc, argv, "f", opts, &index))) {
    switch(ret) {
#ifdef ENABLE_FORCE_POWER_CMD
      case 'f':
        *force = true;
        break;
#endif
      default:
        return -1;
    }
  }
  return 0;
}
int
main(int argc, char **argv) {
  int ret;
  uint8_t fru, status, opt;
  char *option;
  bool force;
  uint8_t num_devs = 0;
  uint8_t dev_id = DEV_NONE;
  uint8_t root = 0;
  if (parse_args(argc, argv, &force)) {
    print_usage();
    exit(-1);
  }
  if (argc > optind + 1) {
    ret = pal_get_fru_id(argv[optind], &fru);
    if (ret < 0) {
      printf("Wrong fru: %s\n", argv[optind]);
      print_usage();
      exit(-1);
    }
    ret = pal_is_fru_prsnt(fru, &status);
    if (ret < 0) {
      printf("pal_is_fru_prsnt failed for fru: %d\n", fru);
      print_usage();
      exit(-1);
    }
    if (status == 0) {
      printf("%s is empty!\n", argv[optind]);
      print_usage();
      exit(-1);
    }
  } else {
    fru = -1;
  }
  pal_get_num_devs(fru,&num_devs);
  /* Check for sled-cycle */
  if (argc < optind+1 || argc > optind +2) {
    if ( argc != optind + 3 || num_devs == 0) {
      print_usage();
      exit (-1);
    }
  }
  if (argc == optind + 3) {
    ret = pal_get_dev_id(argv[optind+1], &dev_id);
    if (ret < 0 || dev_id == DEV_ALL) {
      printf("pal_get_dev_id failed for %s %s\n", argv[1], argv[2]);
      print_usage();
      exit(-1);
    }
  }
  option =  argc == optind+1 ? argv[optind] : argc == optind+2 ? argv [optind+1] : argv[optind+2];
  ret = get_power_opt(option, &opt);
  /* If argc is 2, the option is sled-cycle;  we should ignore power-util fru sled-cycle*/
  if ((ret < 0) || (argc == optind+1 && opt != PWR_SLED_CYCLE) || (argc != optind+1 && opt == PWR_SLED_CYCLE)) {
    printf("Wrong option: %s\n", option);
    print_usage();
    exit(-1);
  }
  if (argc == optind+3 && opt != PWR_ON && opt != PWR_OFF && opt != PWR_STATUS && opt != PWR_CYCLE) {
    printf("Wrong option for %s: %s\n",argv [2] ,option);
    print_usage();
    exit(-1);
  }
  // Check if another instance is running
  if (add_process_running_flag(pal_get_root_fru(fru, &root) == PAL_EOK ? root : fru, opt) < 0) {
    printf("power_util: another instance is running for FRU:%d...\n",fru);
    //Make power-util exit code to "-2" when another instance is running
    exit(-2);
  }
  if (dev_id == DEV_NONE) {
    ret = power_util(fru, opt, force);
    if (ret < 0) {
      printf("ERROR: power-util fru[%d] [%s] failed\n", fru, option_list[opt]);
    }
  } else if (dev_id != DEV_ALL) {
    ret = dev_power_util(fru, argv[optind+1], dev_id, opt);
    if (ret < 0) {
      printf("ERROR: power-util fru[%d] dev %s [%s] failed\n", fru, argv[optind+1], option_list[opt]);
    }
  }
  rm_process_running_flag(pal_get_root_fru(fru, &root) == PAL_EOK ? root : fru, opt);
  return ret;
}