meta-facebook/meta-grandcanyon/recipes-grandcanyon/gpiointrd/files/gpiointrd.c (432 lines of code) (raw):
/*
*
* 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.
*/
#define _GNU_SOURCE
#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 <time.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
#include <assert.h>
#include <sys/un.h>
#include <sys/file.h>
#include <openbmc/libgpio.h>
#include <openbmc/pal.h>
#include <openbmc/kv.h>
#include <facebook/fbgc_gpio.h>
#define POLL_TIMEOUT -1 /* Forever */
#define STUCK_BUTTON_TIME 10 // second
#define LONG_PRESS_BUTTON_TIME 4 // second
#define SHORT_PRESS_BUTTON_TIME 1 // second
#define DISPLAY_UART_SEL_TIMEOUT_INTERVAL 5 // second
static struct timespec press_dbg_rst_btn_time;
static struct timespec press_dbg_pwr_btn_time;
static struct timespec press_dbg_uart_sel_btn_time;
static bool is_dbg_rst_btn_pressed = false;
static bool is_dbg_pwr_btn_pressed = false;
static bool is_dbg_uart_sel_btn_pressed = false;
sem_t semaphore_button_stuck_event; // for monitoring button stuck
pthread_cond_t uart_sel_condition = PTHREAD_COND_INITIALIZER;
pthread_mutex_t post_code_lock = PTHREAD_MUTEX_INITIALIZER;
static int post_code_stored = 0;
static bool is_post_code_need_restore = false;
static void
debug_card_pwr_rst_btn_hndlr(gpiopoll_pin_t *gp, gpio_value_t last, gpio_value_t curr) {
const struct gpiopoll_config *cfg = NULL;
struct timespec now_timespec;
uint8_t power_status = 0, uart_sel_value = DEBUG_UART_SEL_BMC;
int ret = 0, press_time = 0;
if (gp == NULL) {
syslog(LOG_WARNING, "%s(): fail to handle debug card button because parameter: *gp is NULL\n", __func__);
return;
}
cfg = gpio_poll_get_config(gp);
if (cfg == NULL) {
syslog(LOG_WARNING, "%s(): fail to handle debug card button because parameter: *cfg is NULL\n", __func__);
return;
}
ret = pal_get_debug_card_uart_sel(&uart_sel_value);
if (ret < 0) {
syslog(LOG_WARNING, "%s(): fail to get BMC UART selection\n", __func__);
return;
}
clock_gettime(CLOCK_MONOTONIC, &now_timespec);
// UART console is selected to BMC
if (uart_sel_value == DEBUG_UART_SEL_BMC) {
if (strcmp(cfg->shadow, "DEBUG_RST_BTN_N") == 0) {
//press reset button
if (curr == GPIO_VALUE_LOW) {
is_dbg_rst_btn_pressed = true;
sem_post(&semaphore_button_stuck_event);
press_dbg_rst_btn_time.tv_sec = now_timespec.tv_sec;
//release reset button
} else if (curr == GPIO_VALUE_HIGH) {
is_dbg_rst_btn_pressed = false;
press_time = now_timespec.tv_sec - press_dbg_rst_btn_time.tv_sec;
if ((press_time >= SHORT_PRESS_BUTTON_TIME) && (press_time < STUCK_BUTTON_TIME)) {
syslog(LOG_CRIT, "Debug card reset button pressed %d seconds: BMC RESET\n", press_time);
run_command("reboot");
} else if (press_time > STUCK_BUTTON_TIME) {
syslog(LOG_CRIT, "DEASSERT: Debug card reset button seems to stuck for a long time (more than %d seconds)\n", STUCK_BUTTON_TIME);
}
}
}
// UART console is selected to host
} else if (uart_sel_value == DEBUG_UART_SEL_HOST) {
if (pal_get_server_power(FRU_SERVER, &power_status) < 0) {
syslog(LOG_WARNING, "%s(): fail to get server power status\n", __func__);
return;
}
if (strcmp(cfg->shadow, "DEBUG_RST_BTN_N") == 0) {
// press reset button
if (curr == GPIO_VALUE_LOW) {
is_dbg_rst_btn_pressed = true;
sem_post(&semaphore_button_stuck_event);
press_dbg_rst_btn_time.tv_sec = now_timespec.tv_sec;
// release reset button
} else if (curr == GPIO_VALUE_HIGH) {
is_dbg_rst_btn_pressed = false;
press_time = now_timespec.tv_sec - press_dbg_rst_btn_time.tv_sec;
if ((press_time >= SHORT_PRESS_BUTTON_TIME) && (press_time < STUCK_BUTTON_TIME)) {
if (power_status == SERVER_POWER_ON) {
syslog(LOG_CRIT, "Debug card reset button pressed %d seconds: HOST RESET\n", press_time);
ret = pal_set_server_power(FRU_SERVER, SERVER_POWER_RESET);
if (ret < 0) {
syslog(LOG_ERR, "%s(): fail to send host reset command\n", __func__);
return;
}
} else {
syslog(LOG_WARNING, "%s(): fail to send host reset command: HOST is DC OFF\n", __func__);
return;
}
} else if (press_time > STUCK_BUTTON_TIME) {
syslog(LOG_CRIT, "DEASSERT: Debug card reset button seems to stuck for a long time (more than %d seconds)\n", STUCK_BUTTON_TIME);
}
}
} else if (strcmp(cfg->shadow, "DEBUG_PWR_BTN_N") == 0) {
// press power button
if (curr == GPIO_VALUE_LOW) {
is_dbg_pwr_btn_pressed = true;
sem_post(&semaphore_button_stuck_event);
press_dbg_pwr_btn_time.tv_sec = now_timespec.tv_sec;
// release power button
} else if (curr == GPIO_VALUE_HIGH) {
is_dbg_pwr_btn_pressed = false;
press_time = now_timespec.tv_sec - press_dbg_pwr_btn_time.tv_sec;
if ((press_time >= SHORT_PRESS_BUTTON_TIME) && (press_time < STUCK_BUTTON_TIME)) {
if (power_status == SERVER_POWER_OFF) {
syslog(LOG_CRIT, "Debug card power button pressed %d seconds: HOST POWER ON ", press_time);
ret = pal_set_server_power(FRU_SERVER, SERVER_POWER_ON);
if (ret < 0) {
syslog(LOG_ERR, "%s(): fail to send host power on command\n", __func__);
return;
}
} else if (power_status == SERVER_POWER_ON) {
// press more than 4s: power off
if (press_time > LONG_PRESS_BUTTON_TIME) {
syslog(LOG_CRIT, "Debug card power button pressed %d seconds: HOST POWER OFF ", press_time);
ret = pal_set_server_power(FRU_SERVER, SERVER_POWER_OFF);
if (ret < 0) {
syslog(LOG_ERR, "%s(): fail to send host power off command\n", __func__);
return;
}
// press 1-4s: graceful shutdown
} else {
syslog(LOG_CRIT, "Debug card power button pressed %d seconds: HOST GRACEFUL SHUTDOWN ", press_time);
ret = pal_set_server_power(FRU_SERVER, SERVER_GRACEFUL_SHUTDOWN);
if (ret < 0) {
syslog(LOG_ERR, "%s(): fail to send host graceful shutdown command\n", __func__);
return;
}
}
} else {
syslog(LOG_WARNING, "%s(): fail to send host power control command: HOST is AC OFF\n", __func__);
return;
}
} else if (press_time > STUCK_BUTTON_TIME) {
syslog(LOG_CRIT, "DEASSERT: Debug card power button seems to stuck for a long time (more than %d seconds)\n", STUCK_BUTTON_TIME);
}
}
}
}
}
static void
debug_card_uart_sel_btn_hndlr(gpiopoll_pin_t *gp, gpio_value_t last, gpio_value_t curr) {
const struct gpiopoll_config *cfg = NULL;
struct timespec now_timespec;
uint8_t uart_sel_value = DEBUG_UART_SEL_BMC, current_post_code = 0;
int press_time = 0;
if (gp == NULL) {
syslog(LOG_WARNING, "%s(): fail to handle debug card uart selection button because parameter: *gp is NULL\n", __func__);
return;
}
cfg = gpio_poll_get_config(gp);
if (cfg == NULL) {
syslog(LOG_WARNING, "%s(): fail to handle debug card uart selection button because parameter: *cfg is NULL\n", __func__);
return;
}
clock_gettime(CLOCK_MONOTONIC, &now_timespec);
if (strcmp(cfg->shadow, "DEBUG_BMC_UART_SEL_R") == 0) {
//press uart selection button
if (curr == GPIO_VALUE_LOW) {
is_dbg_uart_sel_btn_pressed = true;
sem_post(&semaphore_button_stuck_event);
press_dbg_uart_sel_btn_time.tv_sec = now_timespec.tv_sec;
//release uart selection button
} else if (curr == GPIO_VALUE_HIGH) {
is_dbg_uart_sel_btn_pressed = false;
press_time = now_timespec.tv_sec - press_dbg_uart_sel_btn_time.tv_sec;
if (press_time < STUCK_BUTTON_TIME) {
if (pal_get_debug_card_uart_sel(&uart_sel_value) < 0) {
syslog(LOG_WARNING, "%s(): fail to get BMC UART selection\n", __func__);
return;
}
pthread_mutex_lock(&post_code_lock);
if (pal_get_current_led_post_code(¤t_post_code) < 0) {
syslog(LOG_WARNING, "%s(): fail to get current post code\n", __func__);
}
if (is_post_code_need_restore == false) {
is_post_code_need_restore = true;
post_code_stored = current_post_code;
}
pthread_mutex_unlock(&post_code_lock);
pal_post_display(uart_sel_value);
pthread_cond_signal(&uart_sel_condition);
} else if (press_time > STUCK_BUTTON_TIME) {
syslog(LOG_CRIT, "DEASSERT: Debug card uart selection button seems to stuck for a long time (more than %d seconds)\n", STUCK_BUTTON_TIME);
}
}
}
}
static bool
is_button_stucked(bool *is_btn_pressed) {
bool ret = false;
struct timespec now_timespec, start_timespec;
if (is_btn_pressed == NULL) {
syslog(LOG_WARNING, "%s(): fail to check button stuck, parameter: *is_btn_pressed is NULL\n", __func__);
return ret;
}
clock_gettime(CLOCK_MONOTONIC, &start_timespec);
if ((*is_btn_pressed) == true) {
do {
clock_gettime(CLOCK_MONOTONIC, &now_timespec);
if ((*is_btn_pressed) == false) {
return ret;
}
sleep(1);
} while ((now_timespec.tv_sec - start_timespec.tv_sec) < STUCK_BUTTON_TIME);
ret = true;
}
return ret;
}
static void
*button_stuck_event() {
while (1) {
sem_wait(&semaphore_button_stuck_event);
if (is_button_stucked(&is_dbg_rst_btn_pressed) == true) {
syslog(LOG_CRIT, "ASSERT: Debug card reset button seems to stuck for a long time (more than %d seconds)\n", STUCK_BUTTON_TIME);
}
if (is_button_stucked(&is_dbg_pwr_btn_pressed) == true) {
syslog(LOG_CRIT, "ASSERT: Debug card power button seems to stuck for a long time (more than %d seconds)\n", STUCK_BUTTON_TIME);
}
if (is_button_stucked(&is_dbg_uart_sel_btn_pressed) == true) {
syslog(LOG_CRIT, "ASSERT: Debug card uart selection button seems to stuck for a long time (more than %d seconds)\n", STUCK_BUTTON_TIME);
}
}
return NULL;
}
void *post_code_restorer(){
int ret = 0;
pthread_condattr_t attr;
struct timespec timeout = {0};
pthread_condattr_init(&attr);
pthread_condattr_setclock(&attr, CLOCK_MONOTONIC);
pthread_cond_init(&uart_sel_condition, &attr);
while(1) {
if (clock_gettime(CLOCK_MONOTONIC, &timeout) != 0) {
syslog(LOG_WARNING, "%s(): fail to get current time\n", __func__);
sleep(1);
continue;
}
timeout.tv_sec += DISPLAY_UART_SEL_TIMEOUT_INTERVAL;
pthread_mutex_lock(&post_code_lock);
ret = pthread_cond_timedwait(&uart_sel_condition, &post_code_lock, &timeout);
if (ret == ETIMEDOUT) {
if (is_post_code_need_restore == true) {
pal_post_display(post_code_stored);
is_post_code_need_restore = false;
}
}
pthread_mutex_unlock(&post_code_lock);
}
pthread_exit(0);
}
static int
nic_removed_action() {
int ret = 0;
// Disable NIC power
ret = gpio_set_value_by_shadow(fbgc_get_gpio_name(GPIO_BMC_NIC_FULL_PWR_EN_R), GPIO_VALUE_LOW);
if (ret != 0) {
syslog(LOG_WARNING, "%s(): Failed to disable NIC power.\n", __func__);
return -1;
}
ret = run_command("ifdown eth0");
if (ret != 0) {
syslog(LOG_WARNING, "%s(): Failed to disable network interface eth0.\n", __func__);
return -1;
}
ret = run_command("sv stop ncsid");
if (ret != 0) {
syslog(LOG_WARNING, "%s(): Failed to stop NCSI daemon.\n", __func__);
return -1;
}
return 0;
}
static int
nic_inserted_action() {
int ret = 0;
// Enable NIC power
ret = gpio_set_value_by_shadow(fbgc_get_gpio_name(GPIO_BMC_NIC_FULL_PWR_EN_R), GPIO_VALUE_HIGH);
if (ret != 0) {
syslog(LOG_WARNING, "%s(): Failed to enable NIC power.\n", __func__);
return -1;
}
ret = run_command("ifup eth0");
if (ret != 0) {
syslog(LOG_WARNING, "%s(): Failed to enable network interface eth0.\n", __func__);
return -1;
}
ret = run_command("sv start ncsid");
if (ret != 0) {
syslog(LOG_WARNING, "%s(): Failed to start NCSI daemon.\n", __func__);
return -1;
}
return 0;
}
static void
fru_missing_hndlr(gpiopoll_pin_t *gp, gpio_value_t last, gpio_value_t curr) {
const struct gpiopoll_config *cfg = NULL;
int ret = 0;
uint8_t server_power_status = 0;
if (gp == NULL) {
syslog(LOG_WARNING, "%s(): fail to handle fru missing because parameter: *gp is NULL\n", __func__);
return;
}
cfg = gpio_poll_get_config(gp);
if (cfg == NULL) {
syslog(LOG_WARNING, "%s(): fail to handle fru missing because parameter: *cfg is NULL\n", __func__);
return;
}
if (strcmp(cfg->shadow, "NIC_PRSNTB3_N") == 0) {
if (curr == GPIO_VALUE_HIGH) {
syslog(LOG_CRIT, "ASSERT: nic missing");
pal_set_error_code(ERR_CODE_NIC_MISSING, ERR_CODE_ENABLE);
if (nic_removed_action() < 0) {
syslog(LOG_WARNING, "%s(): Fail to handle actions after NIC is removed.", __func__);
}
} else if (curr == GPIO_VALUE_LOW) {
syslog(LOG_CRIT, "DEASSERT: nic missing");
pal_set_error_code(ERR_CODE_NIC_MISSING, ERR_CODE_DISABLE);
if (nic_inserted_action() < 0) {
syslog(LOG_WARNING, "%s(): Fail to handle actions after NIC is inserted.", __func__);
}
}
ret = pal_get_server_power(FRU_SERVER, &server_power_status);
if (ret < 0) {
syslog(LOG_WARNING, "%s(): fail to get server power status", __func__);
return;
}
if (server_power_status == SERVER_12V_OFF) {
syslog(LOG_CRIT, "Server is AC OFF");
} else if (server_power_status == SERVER_12V_ON) {
syslog(LOG_CRIT, "Server is AC ON");
} else if (server_power_status == SERVER_POWER_OFF) {
syslog(LOG_CRIT, "Server is DC OFF");
} else if (server_power_status == SERVER_POWER_ON) {
syslog(LOG_CRIT, "Server is DC ON");
}
}
}
static void
fru_missing_init(gpiopoll_pin_t *gp, gpio_value_t value) {
const struct gpiopoll_config *cfg = NULL;
uint8_t server_power_status = 0;
int ret = 0;
if (gp == NULL) {
syslog(LOG_WARNING, "%s(): fail to handle fru missing because parameter: *gp is NULL\n", __func__);
return;
}
cfg = gpio_poll_get_config(gp);
if (cfg == NULL) {
syslog(LOG_WARNING, "%s(): fail to handle fru missing because parameter: *cfg is NULL\n", __func__);
return;
}
if (strcmp(cfg->shadow, "NIC_PRSNTB3_N") == 0) {
if (value == GPIO_VALUE_HIGH) {
syslog(LOG_CRIT, "ASSERT: nic missing");
pal_set_error_code(ERR_CODE_NIC_MISSING, ERR_CODE_ENABLE);
if (nic_removed_action() < 0) {
syslog(LOG_WARNING, "%s(): Fail to handle actions after NIC is removed.", __func__);
}
ret = pal_get_server_power(FRU_SERVER, &server_power_status);
if (ret < 0) {
syslog(LOG_WARNING, "%s(): fail to get server power status", __func__);
return;
}
if (server_power_status == SERVER_12V_OFF) {
syslog(LOG_CRIT, "Server is AC OFF");
} else if (server_power_status == SERVER_12V_ON) {
syslog(LOG_CRIT, "Server is AC ON");
} else if (server_power_status == SERVER_POWER_OFF) {
syslog(LOG_CRIT, "Server is DC OFF");
} else if (server_power_status == SERVER_POWER_ON) {
syslog(LOG_CRIT, "Server is DC ON");
}
}
}
}
// GPIO table
static struct gpiopoll_config gpios[] = {
// shadow, description, edge, handler, oneshot
{"DEBUG_RST_BTN_N", "GPIOI7", GPIO_EDGE_BOTH, debug_card_pwr_rst_btn_hndlr, NULL},
{"DEBUG_PWR_BTN_N", "GPION0", GPIO_EDGE_BOTH, debug_card_pwr_rst_btn_hndlr, NULL},
{"DEBUG_BMC_UART_SEL_R", "GPIOM4", GPIO_EDGE_BOTH, debug_card_uart_sel_btn_hndlr, NULL},
{"NIC_PRSNTB3_N", "GPIOS0", GPIO_EDGE_BOTH, fru_missing_hndlr, fru_missing_init},
};
int
main(int argc, char **argv) {
int rc = 0;
int pid_file = 0;
size_t gpio_cnt = 0;
gpiopoll_desc_t *poll_desc = NULL;
pthread_t tid_button_stuck_event;
pthread_t tid_post_code_restorer;
pid_file = open("/var/run/gpiointrd.pid", O_CREAT | O_RDWR, 0666);
rc = pal_flock_retry(pid_file);
if (rc < 0) {
if (EWOULDBLOCK == errno) {
syslog(LOG_ERR, "another gpiointrd instance is running...\n");
} else {
syslog(LOG_ERR, "fail to execute beacuse %s\n", strerror(errno));
}
pal_unflock_retry(pid_file);
close(pid_file);
exit(EXIT_FAILURE);
} else {
sem_init(&semaphore_button_stuck_event, 0, 0);
// Create a thread to monitor debug card power/reset button are stuck or not
if (pthread_create(&tid_button_stuck_event, NULL, button_stuck_event, NULL) < 0) {
syslog(LOG_ERR, "failed to create thread for monitoring debug card button status");
exit(EXIT_FAILURE);
}
if (pthread_create(&tid_post_code_restorer, NULL, post_code_restorer, NULL) < 0) {
syslog(LOG_ERR, "failed to create thread for restore debug card post code");
exit(EXIT_FAILURE);
}
gpio_cnt = sizeof(gpios) / sizeof(gpios[0]);
if (gpio_cnt > 0) {
syslog(LOG_INFO, "daemon start");
poll_desc = gpio_poll_open(gpios, gpio_cnt);
if (poll_desc == NULL) {
syslog(LOG_CRIT, "fail to start poll operation on GPIOs\n");
} else {
// set flag to notice BMC gpiointrd is ready
kv_set("flag_gpiointrd", STR_VALUE_1, 0, 0);
if (gpio_poll(poll_desc, POLL_TIMEOUT) < 0) {
syslog(LOG_CRIT, "fail to poll gpio because gpio_poll() return error\n");
}
// clear the flag
kv_set("flag_gpiointrd", STR_VALUE_0, 0, 0);
gpio_poll_close(poll_desc);
}
}
}
pal_unflock_retry(pid_file);
close(pid_file);
return 0;
}