meta-facebook/meta-grandcanyon/recipes-grandcanyon/gpiod/files/gpiod.c (445 lines of code) (raw):
/*
* gpiod
*
* Copyright 2021-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/un.h>
#include <sys/file.h>
#include <openbmc/libgpio.h>
#include <openbmc/pal.h>
#include <openbmc/pal_sensors.h>
#include <openbmc/kv.h>
#include <facebook/fbgc_gpio.h>
#define MONITOR_FRUS_PRESENT_STATUS_INTERVAL 1 // seconds
#define MONITOR_SERVER_POWER_STATUS_INTERVAL 1 // seconds
#define MONITOR_SCC_STBY_POWER_INTERVAL 1 // seconds
static void
e1s_iocm_remove_event(int e1s_iocm_slot_id, uint8_t *present_status) {
char cmd[MAX_PATH_LEN] = {0};
uint8_t chassis_type = 0;
if (present_status == NULL) {
syslog(LOG_ERR, "%s() Failed to disable E1.S %d/IOCM I2C because the parameter is NULL\n", __func__, e1s_iocm_slot_id);
return;
}
if (fbgc_common_get_chassis_type(&chassis_type) < 0) {
syslog(LOG_WARNING, "%s() Failed to get chassis type.\n", __func__);
return;
}
if (present_status[e1s_iocm_slot_id] == FRU_ABSENT) {
if ((chassis_type == CHASSIS_TYPE7) && (e1s_iocm_slot_id == T5_E1S0_T7_IOC_AVENGER)) {
memset(cmd, 0, sizeof(cmd));
snprintf(cmd, sizeof(cmd), "sv stop iocd_%d > /dev/null 2>&1", I2C_T5E1S0_T7IOC_BUS);
if (system(cmd) != 0) {
syslog(LOG_WARNING, "%s() Fail to stop IOC Daemon:%d\n", __func__, I2C_T5E1S0_T7IOC_BUS);
return;
}
}
}
}
static void
e1s_iocm_insert_event(int e1s_iocm_slot_id, uint8_t *present_status) {
char cmd[MAX_PATH_LEN] = {0};
uint8_t chassis_type = 0;
uint8_t server_power_status = SERVER_POWER_ON;
if (present_status == NULL) {
syslog(LOG_ERR, "%s() Failed to enable E1.S %d/IOCM I2C because the parameter is NULL\n", __func__, e1s_iocm_slot_id);
return;
}
if (pal_get_server_power(FRU_SERVER, &server_power_status) < 0) {
syslog(LOG_ERR, "%s() Failed to enable E1.S %d/IOCM I2C because failed to get server power status\n", __func__, e1s_iocm_slot_id);
return;
}
if (fbgc_common_get_chassis_type(&chassis_type) < 0) {
syslog(LOG_WARNING, "%s() Failed to get chassis type.\n", __func__);
return;
}
if ((present_status[e1s_iocm_slot_id] == FRU_PRESENT) && (server_power_status == SERVER_POWER_ON)) {
if ((chassis_type == CHASSIS_TYPE7) && (e1s_iocm_slot_id == T5_E1S0_T7_IOC_AVENGER)) {
memset(cmd, 0, sizeof(cmd));
snprintf(cmd, sizeof(cmd), "sv start iocd_%d > /dev/null 2>&1", I2C_T5E1S0_T7IOC_BUS);
if (system(cmd) != 0) {
syslog(LOG_WARNING, "%s() Fail to start IOC Daemon:%d\n", __func__, I2C_T5E1S0_T7IOC_BUS);
return;
}
}
}
}
static void
fru_remove_event(int fru_id, uint8_t *e1s_iocm_present_status) {
int ret = 0;
uint8_t chassis_type = 0;
char cmd[MAX_FILE_PATH] = {0};
if (fru_id == FRU_SERVER) {
// AC off server
ret = pal_set_server_power(FRU_SERVER, SERVER_12V_OFF);
if (ret < 0) {
syslog(LOG_ERR, "%s(): Failed to AC off server\n", __func__);
}
pal_set_error_code(ERR_CODE_SERVER_MISSING, ERR_CODE_ENABLE);
} else if (fru_id == FRU_SCC) {
// Stop SCC IOCD
memset(cmd, 0, sizeof(cmd));
snprintf(cmd, sizeof(cmd), "sv stop iocd_%d > /dev/null 2>&1", I2C_T5IOC_BUS);
if (system(cmd) != 0) {
syslog(LOG_WARNING, "%s() Fail to stop IOC Daemon:%d\n", __func__, I2C_T5IOC_BUS);
return;
}
// AC off SCC
ret = gpio_set_value_by_shadow(fbgc_get_gpio_name(GPIO_SCC_STBY_PWR_EN), GPIO_VALUE_LOW);
if (ret < 0) {
syslog(LOG_ERR, "%s(): Failed to AC off SCC\n", __func__);
}
pal_set_error_code(ERR_CODE_SCC_MISSING, ERR_CODE_ENABLE);
} else if (fru_id == FRU_E1S_IOCM) {
if (e1s_iocm_present_status == NULL) {
syslog(LOG_ERR, "%s(): Failed to deal with remove event because the parameter: *e1s_iocm_present_status is NULL\n", __func__);
return;
}
e1s_iocm_remove_event(T5_E1S0_T7_IOC_AVENGER, e1s_iocm_present_status);
e1s_iocm_remove_event(T5_E1S1_T7_IOCM_VOLT, e1s_iocm_present_status);
if (fbgc_common_get_chassis_type(&chassis_type) < 0) {
pal_set_error_code(ERR_CODE_E1S_MISSING, ERR_CODE_ENABLE);
pal_set_error_code(ERR_CODE_IOCM_MISSING, ERR_CODE_ENABLE);
} else {
if (chassis_type == CHASSIS_TYPE7) {
pal_set_error_code(ERR_CODE_IOCM_MISSING, ERR_CODE_ENABLE);
} else if (chassis_type == CHASSIS_TYPE5) {
pal_set_error_code(ERR_CODE_E1S_MISSING, ERR_CODE_ENABLE);
} else {
pal_set_error_code(ERR_CODE_E1S_MISSING, ERR_CODE_ENABLE);
pal_set_error_code(ERR_CODE_IOCM_MISSING, ERR_CODE_ENABLE);
}
}
}
}
static void
fru_insert_event(int fru_id, uint8_t *e1s_iocm_present_status) {
int ret = 0;
uint8_t chassis_type = 0;
char power_policy_cfg[MAX_VALUE_LEN] = {0};
memset(power_policy_cfg, 0, sizeof(power_policy_cfg));
if (fru_id == FRU_SERVER) {
// AC on server
ret = pal_set_server_power(FRU_SERVER, SERVER_12V_ON);
if (ret < 0) {
syslog(LOG_ERR, "%s(): Failed to AC on server\n", __func__);
}
//power policy
ret = pal_get_key_value("server_por_cfg", power_policy_cfg);
if (ret < 0) {
syslog(LOG_WARNING, "%s(): Failed to get power policy config\n", __func__);
return;
}
if (strcmp(power_policy_cfg, "on") == 0) {
sleep(3);
ret = pal_set_server_power(FRU_SERVER, SERVER_POWER_ON);
if (ret < 0) {
syslog(LOG_ERR, "%s(): Failed to DC on server\n", __func__);
return;
}
}
pal_set_error_code(ERR_CODE_SERVER_MISSING, ERR_CODE_DISABLE);
} else if (fru_id == FRU_SCC) {
// AC on SCC
ret = gpio_set_value_by_shadow(fbgc_get_gpio_name(GPIO_SCC_STBY_PWR_EN), GPIO_VALUE_HIGH);
if (ret < 0) {
syslog(LOG_ERR, "%s(): Failed to AC on SCC\n", __func__);
}
pal_set_error_code(ERR_CODE_SCC_MISSING, ERR_CODE_DISABLE);
} else if (fru_id == FRU_E1S_IOCM) {
if (e1s_iocm_present_status == NULL) {
syslog(LOG_ERR, "%s(): Failed to deal with insert event because the parameter: *e1s_iocm_present_status is NULL\n", __func__);
return;
}
e1s_iocm_insert_event(T5_E1S0_T7_IOC_AVENGER, e1s_iocm_present_status);
e1s_iocm_insert_event(T5_E1S1_T7_IOCM_VOLT, e1s_iocm_present_status);
if (fbgc_common_get_chassis_type(&chassis_type) < 0) {
pal_set_error_code(ERR_CODE_E1S_MISSING, ERR_CODE_DISABLE);
pal_set_error_code(ERR_CODE_IOCM_MISSING, ERR_CODE_DISABLE);
} else {
if (chassis_type == CHASSIS_TYPE7) {
pal_set_error_code(ERR_CODE_IOCM_MISSING, ERR_CODE_DISABLE);
} else if (chassis_type == CHASSIS_TYPE5) {
pal_set_error_code(ERR_CODE_E1S_MISSING, ERR_CODE_DISABLE);
}
}
}
}
static void
cache_post_code(uint8_t* post_code_buffer, size_t buf_len){
FILE *fp = NULL;
int ret = 0, count = 1;
if (post_code_buffer == NULL) {
syslog(LOG_WARNING, "%s: Fail to cache post code due to NULL parameter.", __func__);
return;
}
fp = fopen(POST_CODE_FILE, "w");
if (fp == NULL) {
syslog(LOG_WARNING, "%s: fail to open %s file because %s ", __func__, POST_CODE_FILE, strerror(errno));
return;
}
ret = pal_flock_retry(fileno(fp));
if (ret < 0) {
syslog(LOG_WARNING, "%s: fail to flock %s file because %s ", __func__, POST_CODE_FILE, strerror(errno));
fclose(fp);
return;
}
while (buf_len > 0) {
if (count > 16) {
fprintf(fp, "\n");
count=1;
}
fprintf(fp, "%02X ", post_code_buffer[buf_len-1]);
buf_len--;
count++;
}
fprintf(fp, "\n");
pal_unflock_retry(fileno(fp));
fclose(fp);
}
static void
check_cache_post_code(){
if (access(POST_CODE_FILE, F_OK) == 0) {
//file exist
if (rename(POST_CODE_FILE, LAST_POST_CODE_FILE) != 0) {
syslog(LOG_WARNING, "%s: fail to rename %s to %s because %s ", __func__, POST_CODE_FILE, LAST_POST_CODE_FILE, strerror(errno));
}
}
}
static void *
fru_missing_monitor() {
uint8_t fru_present_flag = 0, chassis_type = 0, uic_location_id = 0;
uint8_t fru_present_status[FRU_CNT] = {FRU_PRESENT};
uint8_t e1s_iocm_present_status[E1S_IOCM_SLOT_NUM] = {FRU_PRESENT};
char fru_name[MAX_FRU_NAME_STR] = {0};
char uic_location = '?';
int fru_id = 0, e1s_iocm_slot_id = 0;
memset(&fru_present_status, FRU_PRESENT, sizeof(fru_present_status));
memset(&e1s_iocm_present_status, FRU_PRESENT, sizeof(e1s_iocm_present_status));
memset(&fru_name, 0, sizeof(fru_name));
// set flag to notice BMC gpiod fru_missing_monitor is ready
kv_set("flag_gpiod_fru_miss", STR_VALUE_1, 0, 0);
while(1) {
for (fru_id = FRU_SERVER; fru_id < FRU_CNT; fru_id++) {
if ((fru_id == FRU_SERVER) || (fru_id == FRU_SCC)) {
if (pal_is_fru_prsnt(fru_id, &fru_present_flag) < 0) {
syslog(LOG_WARNING, "%s(): fail to get fru: %d present status\n", __func__, fru_id);
} else {
if (pal_get_fru_name(fru_id, fru_name) < 0) {
syslog(LOG_WARNING, "%s(): fail to get fru: %d name\n", __func__, fru_id);
} else {
// fru insert
if ((fru_present_flag == FRU_PRESENT) && (fru_present_status[fru_id] == FRU_ABSENT)) {
syslog(LOG_CRIT, "DEASSERT: %s missing\n", fru_name);
fru_present_status[fru_id] = FRU_PRESENT;
fru_insert_event(fru_id, NULL);
// fru remove
} else if ((fru_present_flag == FRU_ABSENT) && (fru_present_status[fru_id] == FRU_PRESENT)) {
syslog(LOG_CRIT, "ASSERT: %s missing\n", fru_name);
fru_present_status[fru_id] = FRU_ABSENT;
fru_remove_event(fru_id, NULL);
}
}
}
}
if (fru_id == FRU_E1S_IOCM) {
if (fbgc_common_get_chassis_type(&chassis_type) < 0) {
chassis_type = -1;
}
//Type 7
if (chassis_type == CHASSIS_TYPE7) {
if ((is_e1s_iocm_present(T5_E1S0_T7_IOC_AVENGER) == true) && (is_e1s_iocm_present(T5_E1S1_T7_IOCM_VOLT) == true)) {
if (fru_present_status[fru_id] == FRU_ABSENT) {
syslog(LOG_CRIT, "DEASSERT: iocm missing\n");
fru_present_status[fru_id] = FRU_PRESENT;
e1s_iocm_present_status[T5_E1S0_T7_IOC_AVENGER] = FRU_PRESENT;
e1s_iocm_present_status[T5_E1S1_T7_IOCM_VOLT] = FRU_PRESENT;
fru_insert_event(fru_id, e1s_iocm_present_status);
}
} else {
if (fru_present_status[fru_id] == FRU_PRESENT) {
syslog(LOG_CRIT, "ASSERT: iocm missing\n");
fru_present_status[fru_id] = FRU_ABSENT;
if (is_e1s_iocm_present(T5_E1S0_T7_IOC_AVENGER) == false) {
e1s_iocm_present_status[T5_E1S0_T7_IOC_AVENGER] = FRU_ABSENT;
}
if (is_e1s_iocm_present(T5_E1S1_T7_IOCM_VOLT) == false) {
e1s_iocm_present_status[T5_E1S1_T7_IOCM_VOLT] = FRU_ABSENT;
}
fru_remove_event(fru_id, e1s_iocm_present_status);
}
}
// Type 5 and Type unknown
} else {
if (pal_get_uic_location(&uic_location_id) < 0) {
syslog(LOG_WARNING, "%s(): fail to get uic location\n", __func__);
uic_location = '?';
} else {
if(uic_location_id == UIC_SIDEA) {
uic_location = 'a';
} else if(uic_location_id == UIC_SIDEB) {
uic_location = 'b';
} else {
uic_location = '?';
}
}
for (e1s_iocm_slot_id = T5_E1S0_T7_IOC_AVENGER; e1s_iocm_slot_id < E1S_IOCM_SLOT_NUM; e1s_iocm_slot_id++) {
if ((is_e1s_iocm_present(e1s_iocm_slot_id) == true) && (e1s_iocm_present_status[e1s_iocm_slot_id] == FRU_ABSENT)) {
if (chassis_type == CHASSIS_TYPE5) {
syslog(LOG_CRIT, "DEASSERT: e1.s %c%d missing\n", uic_location, e1s_iocm_slot_id);
} else {
syslog(LOG_CRIT, "DEASSERT: chassis type unknown, e1.s %d or iocm missing\n", e1s_iocm_slot_id);
}
e1s_iocm_present_status[e1s_iocm_slot_id] = FRU_PRESENT;
fru_insert_event(fru_id, e1s_iocm_present_status);
}
if ((is_e1s_iocm_present(e1s_iocm_slot_id) == false) && (e1s_iocm_present_status[e1s_iocm_slot_id] == FRU_PRESENT)) {
if (chassis_type == CHASSIS_TYPE5) {
syslog(LOG_CRIT, "ASSERT: e1.s %c%d missing\n", uic_location, e1s_iocm_slot_id);
} else {
syslog(LOG_CRIT, "ASSERT: chassis type unknown, e1.s %d or iocm missing\n", e1s_iocm_slot_id);
}
e1s_iocm_present_status[e1s_iocm_slot_id] = FRU_ABSENT;
fru_present_status[fru_id] = FRU_ABSENT;
fru_remove_event(fru_id, e1s_iocm_present_status);
}
}
if ((is_e1s_iocm_present(T5_E1S0_T7_IOC_AVENGER) == true) && (is_e1s_iocm_present(T5_E1S1_T7_IOCM_VOLT) == true)) {
fru_present_status[fru_id] = FRU_PRESENT;
}
}
}
} // for loop end
sleep(MONITOR_FRUS_PRESENT_STATUS_INTERVAL);
} // while loop end
pthread_exit(NULL);
}
static void *
server_power_monitor() {
uint8_t server_present = FRU_PRESENT;
uint8_t server_pre_pwr_status = -1, server_cur_pwr_status = -1;
uint8_t post_code[MAX_POSTCODE_LEN] = {0};
size_t post_code_len = 0;
bool is_need_cache = true;
int ret = 0;
// set flag to notice BMC gpiod server_power_monitor is ready
kv_set("flag_gpiod_server_pwr", STR_VALUE_1, 0, 0);
while(1) {
if (pal_is_fru_prsnt(FRU_SERVER, &server_present) < 0) {
syslog(LOG_WARNING, "%s(): fail to get fru: %d present status\n", __func__, FRU_SERVER);
} else {
// if server is present, monitor server power status
if (server_present == FRU_PRESENT) {
ret = pal_get_server_power(FRU_SERVER, &server_cur_pwr_status);
if (ret == 0) {
//*****Store server post code
if (server_cur_pwr_status == SERVER_POWER_ON) {
if (is_need_cache == true) {
memset(&post_code, 0, sizeof(post_code));
if (pal_get_80port_record(FRU_SERVER, post_code, MAX_POSTCODE_LEN, &post_code_len) == 0) {
cache_post_code(post_code, post_code_len);
// Stop storing post code when server boot into OS
if (post_code[0] == 0x00) {
is_need_cache = false;
}
} else {
syslog(LOG_WARNING, "%s(): Fail to get post code from BIC", __func__);
}
}
} else {
check_cache_post_code();
}
//*****Server power from on change to off
if ((server_pre_pwr_status == SERVER_POWER_ON)
&& ((server_cur_pwr_status == SERVER_POWER_OFF) || (server_cur_pwr_status == SERVER_12V_OFF))) {
syslog(LOG_CRIT, "FRU: %d, Server is powered off", FRU_SERVER);
//*****Server power from off change to on
} else if (((server_pre_pwr_status == SERVER_POWER_OFF) || (server_pre_pwr_status == SERVER_12V_OFF))
&& (server_cur_pwr_status == SERVER_POWER_ON)) {
// Store post code when server power on
is_need_cache = true;
syslog(LOG_CRIT, "FRU: %d, Server is powered on", FRU_SERVER);
}
server_pre_pwr_status = server_cur_pwr_status;
}
} // server present end
}
sleep(MONITOR_SERVER_POWER_STATUS_INTERVAL);
} // while loop end
pthread_exit(NULL);
}
static void*
scc_stby_power_monitor() {
gpio_value_t scc_stby_pg_value = GPIO_VALUE_INVALID;
gpio_value_t scc_i2c_en_value = GPIO_VALUE_INVALID;
const char * STR_SCC_STBY_PGOOD = fbgc_get_gpio_name(GPIO_SCC_STBY_PGOOD);
const char * STR_SCC_I2C_EN_R = fbgc_get_gpio_name(GPIO_SCC_I2C_EN_R);
if (STR_SCC_STBY_PGOOD == NULL || STR_SCC_I2C_EN_R == NULL) {
syslog(LOG_ERR, "Failed to start SCC stby power monitor, GPIO name mapping error");
pthread_exit(NULL);
}
// set flag to notice BMC gpiod scc_stby_power_monitor is ready
kv_set("flag_gpiod_scc_pwr", STR_VALUE_1, 0, 0);
while (1) {
scc_stby_pg_value = gpio_get_value_by_shadow(STR_SCC_STBY_PGOOD);
scc_i2c_en_value = gpio_get_value_by_shadow(STR_SCC_I2C_EN_R);
// sync GPIO_SCC_I2C_EN_R with GPIO_SCC_STBY_PGOOD for leakage prevention
if ((scc_stby_pg_value != GPIO_VALUE_INVALID) && (scc_stby_pg_value != scc_i2c_en_value)) {
if (gpio_set_value_by_shadow(fbgc_get_gpio_name(GPIO_SCC_I2C_EN_R), scc_stby_pg_value) < 0) {
syslog(LOG_WARNING, "%s(): Failed to set GPIO_SCC_I2C_EN_R.\n", __func__);
}
}
sleep(MONITOR_SCC_STBY_POWER_INTERVAL);
}
pthread_exit(NULL);
}
static void
print_usage() {
printf("Usage: gpiod [ %s ]\n", pal_server_list);
}
static void
run_gpiod(int argc, char **argv) {
pthread_t tid_fru_missing_monitor;
pthread_t tid_server_power_monitor;
pthread_t tid_scc_stby_power_monitor;
int ret_fru_missing = 0, ret_server_power = 0, ret_scc_stby_power = 0;
if (argv == NULL) {
syslog(LOG_ERR, "fail to execute gpiod because NULL parameter: **argv\n");
exit(EXIT_FAILURE);
}
// Monitor fru missing by polling (server, SCC, E1.S, IOCM)
if (pthread_create(&tid_fru_missing_monitor, NULL, fru_missing_monitor, NULL) < 0) {
syslog(LOG_ERR, "fail to creat thread for monitor fru missing\n");
ret_fru_missing = -1;
}
// Monitor server power by polling
if (pthread_create(&tid_server_power_monitor, NULL, server_power_monitor, NULL) < 0) {
syslog(LOG_ERR, "fail to creat thread for monitor server host\n");
ret_server_power = -1;
}
// Monitor scc standby power
if (pthread_create(&tid_scc_stby_power_monitor, NULL, scc_stby_power_monitor, NULL) < 0) {
syslog(LOG_ERR, "fail to creat thread for scc standby power monitor\n");
ret_scc_stby_power = -1;
}
if (ret_fru_missing == 0) {
pthread_join(tid_fru_missing_monitor, NULL);
}
if (ret_server_power == 0) {
pthread_join(tid_server_power_monitor, NULL);
}
if (ret_scc_stby_power == 0) {
pthread_join(tid_scc_stby_power_monitor, NULL);
}
}
int
main(int argc, char **argv) {
int rc = 0, pid_file = 0;
if (argv == NULL) {
syslog(LOG_ERR, "fail to execute gpiod because NULL parameter: **argv\n");
exit(EXIT_FAILURE);
}
if (argc < 2) {
print_usage();
exit(EXIT_FAILURE);
}
pid_file = open("/var/run/gpiod.pid", O_CREAT | O_RDWR, 0666);
rc = pal_flock_retry(pid_file);
if (rc < 0) {
if (EWOULDBLOCK == errno) {
printf("another gpiod instance is running...\n");
} else {
syslog(LOG_ERR, "fail to execute gpiod because %s\n", strerror(errno));
}
pal_unflock_retry(pid_file);
close(pid_file);
exit(EXIT_FAILURE);
} else {
syslog(LOG_INFO, "daemon started");
run_gpiod(argc, argv);
}
pal_unflock_retry(pid_file);
close(pid_file);
return 0;
}