meta-facebook/meta-fby35/recipes-fby35/gpiointrd/files/gpiointrd.c (340 lines of code) (raw):
/*
* sensord
*
* Copyright 2015-present Facebook. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <errno.h>
#include <syslog.h>
#include <stdint.h>
#include <pthread.h>
#include <assert.h>
#include <openbmc/kv.h>
#include <openbmc/libgpio.h>
#include <openbmc/ipmi.h>
#include <openbmc/obmc-i2c.h>
#include <openbmc/pal.h>
#include <openbmc/ras.h>
#define POLL_TIMEOUT -1 /* Forever */
#define POC1_BOARD_ID 0x0
static void
log_slot_present(uint8_t slot_id, gpio_value_t value)
{
// no need to consider Config C, because the HW "PRSNT_MB_BMC_SLOTX_BB_N" is not connected to Config C BMC.
if ( value == GPIO_VALUE_LOW ) {
syslog(LOG_CRIT, "slot%d present", slot_id);
} else if ( value == GPIO_VALUE_HIGH ) {
syslog(LOG_CRIT, "Abnormal - slot%d not detected", slot_id);
return;
}
}
static void
slot_present(gpiopoll_pin_t *gpdesc, gpio_value_t value) {
uint32_t slot_id;
const struct gpiopoll_config *cfg = gpio_poll_get_config(gpdesc);
assert(cfg);
sscanf(cfg->description, "GPIOH%u", &slot_id);
slot_id -= 3;
log_gpio_change(gpdesc, value, 0, true);
log_slot_present(slot_id, value);
if ( value == GPIO_VALUE_LOW ) {
// TODO: Need to check management cable for Yv3.5 system
//pal_check_sled_mgmt_cbl_id(slot_id, NULL, true, BB_BMC);
pal_check_slot_cpu_present(slot_id);
pal_check_slot_fru(slot_id);
}
}
static void
slot_hotplug_setup(uint8_t slot_id) {
#define SCRIPT_PATH "sh /etc/init.d/server-init.sh %d &"
char cmd[128] = {0};
int cmd_len = sizeof(cmd);
snprintf(cmd, cmd_len, SCRIPT_PATH, slot_id);
if ( system(cmd) != 0 ) {
syslog(LOG_CRIT, "Failed to run: %s", cmd);
}
}
static void
slot_hotplug_hndlr(gpiopoll_pin_t *gp, gpio_value_t last, gpio_value_t curr) {
uint32_t slot_id;
const struct gpiopoll_config *cfg = gpio_poll_get_config(gp);
assert(cfg);
sscanf(cfg->description, "GPIOH%u", &slot_id);
slot_id -= 3;
slot_hotplug_setup(slot_id);
log_gpio_change(gp, curr, 0, true);
log_slot_present(slot_id, curr);
if ( curr == GPIO_VALUE_LOW ) {
// TODO: Need to check management cable for Yv3.5 system
//pal_check_sled_mgmt_cbl_id(slot_id, NULL, true, BB_BMC);
// Wait for IPMB ready
sleep(6);
pal_check_slot_cpu_present(slot_id);
pal_check_slot_fru(slot_id);
}
}
static void
issue_slot_ocp_fault_sel(uint8_t slot_id) {
uint8_t tbuf[32] = {0x00};
uint8_t rbuf[32] = {0x00};
uint8_t tlen = 0;
uint16_t rlen = 0;
tbuf[0] = slot_id;
tbuf[1] = NETFN_STORAGE_REQ << 2;
tbuf[2] = CMD_STORAGE_ADD_SEL;
tbuf[5] = 0x02; //record ID
tbuf[10] = 0x20; //addr
tbuf[11] = 0x00;
tbuf[12] = 0x04;
tbuf[13] = 0xC9;
tbuf[14] = 0x46;
//When the OCP fault is asserted, the slot is shutdown.
//So, only the assertion event is issued.
tbuf[15] = 0x6F;
tbuf[16] = 0x08; //fault event
tbuf[17] = 0xff;
tbuf[18] = 0xff;
tlen = 19;
lib_ipmi_handle(tbuf, tlen, rbuf, &rlen);
}
static void
slot_ocp_fault_hndlr(gpiopoll_pin_t *gp, gpio_value_t last, gpio_value_t curr) {
uint32_t slot_id;
const struct gpiopoll_config *cfg = gpio_poll_get_config(gp);
assert(cfg);
// because we don't have consistent shadow name here, we use description instead
sscanf(cfg->description, "GPION%u", &slot_id);
slot_id += 1;
log_gpio_change(gp, curr, 0, true);
issue_slot_ocp_fault_sel(slot_id);
}
static void
slot_power_control(char *opt) {
char cmd[64] = "/usr/local/bin/power-util ";
strcat(cmd, opt);
if ( system(cmd) != 0 ) {
syslog(LOG_WARNING, "Failed to do %s", cmd);
}
}
static void
slot_rst_hndler(gpiopoll_pin_t *gp, gpio_value_t last, gpio_value_t curr) {
uint32_t slot_id;
char pwr_cmd[32];
const struct gpiopoll_config *cfg = gpio_poll_get_config(gp);
assert(cfg);
sscanf(cfg->description, "GPIOF%u", &slot_id);
slot_id += 1;
sprintf(pwr_cmd, "slot%u reset", slot_id);
if ( curr == GPIO_VALUE_LOW ) slot_power_control(pwr_cmd);
log_gpio_change(gp, curr, 0, true);
}
static void
ocp_nic_hotplug_hndlr(gpiopoll_pin_t *gp, gpio_value_t last, gpio_value_t curr) {
if (curr == GPIO_VALUE_LOW)
syslog(LOG_CRIT, "OCP NIC Inserted");
else
syslog(LOG_CRIT, "OCP NIC Removed");
}
static void
ocp_nic_init(gpiopoll_pin_t *gp, gpio_value_t value) {
if (value == GPIO_VALUE_HIGH)
syslog(LOG_CRIT, "OCP NIC Removed");
}
static void
usb_hotplug_hndlr(gpiopoll_pin_t *gp, gpio_value_t last, gpio_value_t curr) {
int ret = 0, i2cfd = 0, retry=0;
uint8_t tbuf[1] = {0x08}, rbuf[1] = {0};
uint8_t tlen = 1, rlen = 1;
const struct gpiopoll_config *cfg = gpio_poll_get_config(gp);
assert(cfg);
sscanf(cfg->description, "GPIOS2");
log_gpio_change(gp, curr, 0, true);
if (curr == GPIO_VALUE_LOW) {
i2cfd = i2c_cdev_slave_open(BB_CPLD_BUS, CPLD_ADDRESS >> 1, I2C_SLAVE_FORCE_CLAIM);
if ( i2cfd < 0 ) {
syslog(LOG_WARNING, "Failed to open bus 12\n");
return;
}
retry = 0;
while (retry < MAX_READ_RETRY) {
ret = i2c_rdwr_msg_transfer(i2cfd, CPLD_ADDRESS, tbuf, tlen, rbuf, rlen);
if ( ret < 0 ) {
retry++;
msleep(100);
} else {
break;
}
}
if (retry == MAX_READ_RETRY) {
syslog(LOG_WARNING, "%s() Failed to do i2c_rdwr_msg_transfer, tlen=%d", __func__, tlen);
}
if ( i2cfd > 0 ) close(i2cfd);
// Do not handle in POC1
if (rbuf[0] == POC1_BOARD_ID) {
return;
}
gpio_set_value_by_shadow("USB_BMC_EN_R", GPIO_VALUE_LOW);
msleep(250);
gpio_set_value_by_shadow("USB_BMC_EN_R", GPIO_VALUE_HIGH);
}
}
// GPIO table of the class 1
static struct gpiopoll_config g_class1_gpios[] = {
// shadow, description, edge, handler, oneshot
{"PRSNT_MB_BMC_SLOT1_BB_N", "GPIOH4", GPIO_EDGE_BOTH, slot_hotplug_hndlr, slot_present},
{"PRSNT_MB_SLOT2_BB_N", "GPIOH5", GPIO_EDGE_BOTH, slot_hotplug_hndlr, slot_present},
{"PRSNT_MB_BMC_SLOT3_BB_N", "GPIOH6", GPIO_EDGE_BOTH, slot_hotplug_hndlr, slot_present},
{"PRSNT_MB_SLOT4_BB_N", "GPIOH7", GPIO_EDGE_BOTH, slot_hotplug_hndlr, slot_present},
{"FM_RESBTN_SLOT1_BMC_N", "GPIOF0", GPIO_EDGE_BOTH, slot_rst_hndler, NULL},
{"FM_RESBTN_SLOT2_N", "GPIOF1", GPIO_EDGE_BOTH, slot_rst_hndler, NULL},
{"FM_RESBTN_SLOT3_BMC_N", "GPIOF2", GPIO_EDGE_BOTH, slot_rst_hndler, NULL},
{"FM_RESBTN_SLOT4_N", "GPIOF3", GPIO_EDGE_BOTH, slot_rst_hndler, NULL},
{"HSC_FAULT_BMC_SLOT1_N_R", "GPION0", GPIO_EDGE_BOTH, slot_ocp_fault_hndlr, NULL},
{"HSC_FAULT_SLOT2_N", "GPION1", GPIO_EDGE_BOTH, slot_ocp_fault_hndlr, NULL},
{"HSC_FAULT_BMC_SLOT3_N_R", "GPION2", GPIO_EDGE_BOTH, slot_ocp_fault_hndlr, NULL},
{"HSC_FAULT_SLOT4_N", "GPION3", GPIO_EDGE_BOTH, slot_ocp_fault_hndlr, NULL},
{"OCP_NIC_PRSNT_BMC_N", "GPIOC5", GPIO_EDGE_BOTH, ocp_nic_hotplug_hndlr, ocp_nic_init},
{"P5V_USB_PG_BMC", "GPIOS2", GPIO_EDGE_BOTH, usb_hotplug_hndlr, NULL},
{"FAST_PROCHOT_BMC_N_R", "GPIOM4", GPIO_EDGE_BOTH, fast_prochot_hndlr, fast_prochot_init},
};
// GPIO table of the class 2
static struct gpiopoll_config g_class2_gpios[] = {
// shadow, description, edge, handler, oneshot
};
static void
*ac_button_event() {
unsigned int gpio_vals = 0x0;
/*
Baseboard AC button:
- Press it more than 4s or will do nothing.
Server board AC on/ off/ cycle button:
- Pwr cycle: press it more than 4s
- Pwr off: press it more than 8s
- Pwr on: press it more than 1s and <= 4s
*/
enum {
BTN_BMC = 0x0,
BTN_SLOT1,
BTN_SLOT2,
BTN_SLOT3,
BTN_SLOT4,
BTN_ARRAY_SIZE,
/*Get cmd_str*/
PWR_12V_OFF = 0x0,
PWR_12V_ON,
PWR_12V_CYCLE,
SLED_CYCLE,
UNKNOWN_PWR_ACTION,
};
const char *cmd_list[] = {
"slot%c 12V-off",
"slot%c 12V-on",
"slot%c 12V-cycle",
"sled%ccycle"
};
const char *shadows[] = {
"BB_BUTTON_BMC_CO_N_R",
"AC_ON_OFF_BTN_BMC_SLOT1_N_R",
"AC_ON_OFF_BTN_SLOT2_N",
"AC_ON_OFF_BTN_BMC_SLOT3_N_R",
"AC_ON_OFF_BTN_SLOT4_N"
};
uint8_t action[BTN_ARRAY_SIZE] = {0};
bool is_asserted[BTN_ARRAY_SIZE] = {0};
int time_elapsed[BTN_ARRAY_SIZE] = {0};
int i = 0;
uint8_t pwr_sts = 0;
pthread_detach(pthread_self());
memset(action, UNKNOWN_PWR_ACTION, BTN_ARRAY_SIZE);
memset(is_asserted, false, BTN_ARRAY_SIZE);
//setup the default status
if ( gpio_get_value_by_shadow_list(shadows, ARRAY_SIZE(shadows), &gpio_vals) < 0 ) {
syslog(LOG_CRIT, "AC_ON_OFF buttons are not functional");
pthread_exit(0);
}
//start to poll each pin
while (1) {
if ( gpio_get_value_by_shadow_list(shadows, ARRAY_SIZE(shadows), &gpio_vals) < 0 ) {
syslog(LOG_WARNING, "Could not get the current values of all AC_ON_OFF buttons");
sleep(1);
continue;
}
for ( i = BTN_BMC; i < BTN_ARRAY_SIZE; i++ ) {
if ( GETBIT(gpio_vals, i) == GPIO_VALUE_LOW ) {
if ( is_asserted[i] == false ) {
syslog(LOG_CRIT, "ASSERT: %s", shadows[i]);
is_asserted[i] = true;
}
time_elapsed[i]++;
} else if ( GETBIT(gpio_vals, i) == GPIO_VALUE_HIGH && time_elapsed[i] > 0 ) {
if ( is_asserted[i] == true ) {
syslog(LOG_CRIT, "DEASSERT: %s", shadows[i]);
is_asserted[i] = false;
}
if ( i == BTN_BMC ) {
if ( time_elapsed[i] > 4 ) {
action[i] = SLED_CYCLE;
}
} else {
pal_get_server_12v_power(i, &pwr_sts);
if (pwr_sts == SERVER_12V_OFF && time_elapsed[i] >= 1) {
//pwr 12V on since time_elpased >=1
action[i] = PWR_12V_ON;
} else if ( time_elapsed[i] > 4 ) {
if ( time_elapsed[i] <= 8 ) {
//pwr 12V cycle since 4 < time_elapsed <= 8
action[i] = PWR_12V_CYCLE;
} else {
//pwr 12V off since time_elapsed > 8
action[i] = PWR_12V_OFF;
}
}
}
if ( action[i] != UNKNOWN_PWR_ACTION ) {
char opt[16] = {0};
char c = 0;
if ( action[i] == SLED_CYCLE ) {
c = 0x2D; //2Dh is `-` in ASCII
} else {
c = 0x30; //30h is `1` in ASCII
}
snprintf(opt, sizeof(opt), cmd_list[action[i]], (c == 0x2D)?c:(c+i));
slot_power_control(opt);
action[i] = UNKNOWN_PWR_ACTION;
}
time_elapsed[i] = 0;
}
}
//sleep periodically.
sleep(1);
}
return NULL;
}
static void
init_class1_threads() {
pthread_t tid_ac_button_event;
//Create a thread to monitor the button
if ( pthread_create(&tid_ac_button_event, NULL, ac_button_event, NULL) < 0 ) {
syslog(LOG_WARNING, "Failed to create pthread_create for ac_button_event");
exit(-1);
}
}
static void
init_class2_threads() {
//do nothing
}
int
main(int argc, char **argv) {
int rc = 0;
int pid_file = 0;
uint8_t bmc_location = 0;
struct gpiopoll_config *gpiop = NULL;
size_t gpio_cnt = 0;
gpiopoll_desc_t *polldesc = NULL;
pid_file = open("/var/run/gpiointrd.pid", O_CREAT | O_RDWR, 0666);
rc = flock(pid_file, LOCK_EX | LOCK_NB);
if(rc) {
if (EWOULDBLOCK == errno) {
syslog(LOG_ERR, "Another gpiointrd instance is running...\n");
exit(-1);
}
} else {
if ( fby35_common_get_bmc_location(&bmc_location) < 0 ) {
syslog(LOG_WARNING, "Failed to get the location of BMC!");
exit(-1);
}
if ( bmc_location == BB_BMC ) {
gpiop = g_class1_gpios;
gpio_cnt = sizeof(g_class1_gpios)/sizeof(g_class1_gpios[0]);
init_class1_threads();
} else {
gpiop = g_class2_gpios;
gpio_cnt = sizeof(g_class2_gpios)/sizeof(g_class2_gpios[0]);
init_class2_threads();
}
if ( gpio_cnt > 0 ) {
openlog("gpiointrd", LOG_CONS, LOG_DAEMON);
syslog(LOG_INFO, "gpiointrd: daemon started");
// set flag to notice BMC gpiointrd is ready
kv_set("flag_gpiointrd", STR_VALUE_1, 0, 0);
// set flag to notice BMC gpiod fru_missing_monitor is ready
kv_set("flag_gpiod_fru_miss", STR_VALUE_1, 0, 0);
polldesc = gpio_poll_open(gpiop, gpio_cnt);
if ( polldesc == NULL ) {
syslog(LOG_CRIT, "Cannot start poll operation on GPIOs");
} else {
if (gpio_poll(polldesc, POLL_TIMEOUT)) {
syslog(LOG_CRIT, "Poll returned error");
}
// clear the flag
kv_set("flag_gpiointrd", STR_VALUE_0, 0, 0);
gpio_poll_close(polldesc);
}
}
}
return 0;
}