Blame src/amd_ipmi.c

Packit Service db8df9
/*
Packit Service db8df9
 * AMD IPMI LED control
Packit Service db8df9
 * Copyright (C) 2019, Advanced Micro Devices, Inc.
Packit Service db8df9
 *
Packit Service db8df9
 * This program is free software; you can redistribute it and/or modify it
Packit Service db8df9
 * under the terms and conditions of the GNU General Public License,
Packit Service db8df9
 * version 2, as published by the Free Software Foundation.
Packit Service db8df9
 *
Packit Service db8df9
 * This program is distributed in the hope it will be useful, but WITHOUT
Packit Service db8df9
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
Packit Service db8df9
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
Packit Service db8df9
 * more details.
Packit Service db8df9
 *
Packit Service db8df9
 * You should have received a copy of the GNU General Public License along with
Packit Service db8df9
 * this program; if not, write to the Free Software Foundation, Inc.,
Packit Service db8df9
 * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
Packit Service db8df9
 *
Packit Service db8df9
 */
Packit Service db8df9
Packit Service db8df9
#include <errno.h>
Packit Service db8df9
#include <fcntl.h>
Packit Service db8df9
#include <limits.h>
Packit Service db8df9
#include <stdint.h>
Packit Service db8df9
#include <stdio.h>
Packit Service db8df9
#include <stdlib.h>
Packit Service db8df9
#include <string.h>
Packit Service db8df9
#include <unistd.h>
Packit Service db8df9
#include <libgen.h>
Packit Service db8df9
#include <inttypes.h>
Packit Service db8df9
#include <sys/stat.h>
Packit Service db8df9
#include <sys/mman.h>
Packit Service db8df9
#include <sys/sysinfo.h>
Packit Service db8df9
#include <sys/file.h>
Packit Service db8df9
Packit Service db8df9
#if _HAVE_DMALLOC_H
Packit Service db8df9
#include <dmalloc.h>
Packit Service db8df9
#endif
Packit Service db8df9
Packit Service db8df9
#include "config.h"
Packit Service db8df9
#include "ibpi.h"
Packit Service db8df9
#include "list.h"
Packit Service db8df9
#include "utils.h"
Packit Service db8df9
#include "amd.h"
Packit Service db8df9
#include "ipmi.h"
Packit Service db8df9
Packit Service db8df9
static uint8_t amd_ibpi_ipmi_register[] = {
Packit Service db8df9
	[IBPI_PATTERN_PFA] = 0x41,
Packit Service db8df9
	[IBPI_PATTERN_LOCATE] = 0x42,
Packit Service db8df9
	[IBPI_PATTERN_FAILED_DRIVE] = 0x44,
Packit Service db8df9
	[IBPI_PATTERN_FAILED_ARRAY] = 0x45,
Packit Service db8df9
	[IBPI_PATTERN_REBUILD] = 0x46,
Packit Service db8df9
	[IBPI_PATTERN_HOTSPARE] = 0x47,
Packit Service db8df9
};
Packit Service db8df9
Packit Service db8df9
/* The path we are given should be similar to
Packit Service db8df9
 * /sys/devices/pci0000:e0/0000:e0:03.3/0000:e3:00.0
Packit Service db8df9
 *                                      ^^^^^^^^^^
Packit Service db8df9
 * We need to retrieve the address from the path (indicated above)
Packit Service db8df9
 * then use it to find the corresponding address for a slot in
Packit Service db8df9
 * /sys/bus/pci_slots to determine the icorrect port for the NVMe device.
Packit Service db8df9
 */
Packit Service db8df9
static int _get_ipmi_nvme_port(char *path)
Packit Service db8df9
{
Packit Service db8df9
	int rc;
Packit Service db8df9
	char *p, *f;
Packit Service db8df9
	struct list dir;
Packit Service db8df9
	const char *dir_path;
Packit Service db8df9
	char *port_name;
Packit Service db8df9
	int port = -1;
Packit Service db8df9
Packit Service db8df9
	p = strrchr(path, '/');
Packit Service db8df9
	if (!p) {
Packit Service db8df9
		log_error("Couldn't parse NVMe path to determine port\n");
Packit Service db8df9
		return -1;
Packit Service db8df9
	}
Packit Service db8df9
Packit Service db8df9
	p++;
Packit Service db8df9
Packit Service db8df9
	/* p now points to the address, remove the bits after the '.' */
Packit Service db8df9
	f = strchr(p, '.');
Packit Service db8df9
	if (!f) {
Packit Service db8df9
		log_error("Couldn't parse NVMe port address\n");
Packit Service db8df9
		return -1;
Packit Service db8df9
	}
Packit Service db8df9
Packit Service db8df9
	*f = '\0';
Packit Service db8df9
Packit Service db8df9
	rc = scan_dir("/sys/bus/pci/slots", &dir;;
Packit Service db8df9
	if (rc)
Packit Service db8df9
		return -1;
Packit Service db8df9
Packit Service db8df9
	list_for_each(&dir, dir_path) {
Packit Service db8df9
		port_name = get_text(dir_path, "address");
Packit Service db8df9
		if (port_name && !strcmp(port_name, p)) {
Packit Service db8df9
			char *dname = strrchr(dir_path, '/');
Packit Service db8df9
Packit Service db8df9
			dname++;
Packit Service db8df9
			port = strtol(dname, NULL, 0);
Packit Service db8df9
			break;
Packit Service db8df9
		}
Packit Service db8df9
	}
Packit Service db8df9
Packit Service db8df9
	list_erase(&dir;;
Packit Service db8df9
Packit Service db8df9
	/* Some platfroms require an adjustment to the port value based
Packit Service db8df9
	 * on how they are numbered by the BIOS.
Packit Service db8df9
	 */
Packit Service db8df9
	switch (amd_platform) {
Packit Service db8df9
	case AMD_PLATFORM_DAYTONA_X:
Packit Service db8df9
		port -= 2;
Packit Service db8df9
		break;
Packit Service db8df9
	case AMD_PLATFORM_ETHANOL_X:
Packit Service db8df9
		port -= 7;
Packit Service db8df9
		break;
Packit Service db8df9
	default:
Packit Service db8df9
		break;
Packit Service db8df9
	}
Packit Service db8df9
Packit Service db8df9
	/* Validate port. Some BIOSes provide port values that are
Packit Service db8df9
	 * not valid.
Packit Service db8df9
	 */
Packit Service db8df9
	if ((port < 0) || (port > 24)) {
Packit Service db8df9
		log_error("Invalid NVMe physical port %d\n", port);
Packit Service db8df9
		port = -1;
Packit Service db8df9
	}
Packit Service db8df9
Packit Service db8df9
	return port;
Packit Service db8df9
}
Packit Service db8df9
Packit Service db8df9
static int _get_ipmi_sata_port(const char *start_path)
Packit Service db8df9
{
Packit Service db8df9
	int port;
Packit Service db8df9
	char *p, *t;
Packit Service db8df9
	char path[PATH_MAX];
Packit Service db8df9
Packit Service db8df9
	strncpy(path, start_path, PATH_MAX);
Packit Service db8df9
	path[PATH_MAX - 1] = 0;
Packit Service db8df9
	t = p = strstr(path, "ata");
Packit Service db8df9
Packit Service db8df9
	if (!p)
Packit Service db8df9
		return -1;
Packit Service db8df9
Packit Service db8df9
	/* terminate the path after the ataXX/ part */
Packit Service db8df9
	p = strchr(p, '/');
Packit Service db8df9
	if (!p)
Packit Service db8df9
		return -1;
Packit Service db8df9
	*p = '\0';
Packit Service db8df9
Packit Service db8df9
	/* skip past 'ata' to get the ata port number */
Packit Service db8df9
	t += 3;
Packit Service db8df9
	port = strtoul(t, NULL, 10);
Packit Service db8df9
Packit Service db8df9
	return port;
Packit Service db8df9
}
Packit Service db8df9
Packit Service db8df9
static int _get_amd_ipmi_drive(const char *start_path,
Packit Service db8df9
			       struct amd_drive *drive)
Packit Service db8df9
{
Packit Service db8df9
	int found;
Packit Service db8df9
	char path[PATH_MAX];
Packit Service db8df9
Packit Service db8df9
	found = _find_file_path(start_path, "nvme", path, PATH_MAX);
Packit Service db8df9
	if (found) {
Packit Service db8df9
		drive->port = _get_ipmi_nvme_port(path);
Packit Service db8df9
		if (drive->port < 0) {
Packit Service db8df9
			log_error("Could not retrieve port number\n");
Packit Service db8df9
			return -1;
Packit Service db8df9
		}
Packit Service db8df9
Packit Service db8df9
		drive->drive_bay = 1 << (drive->port - 1);
Packit Service db8df9
		drive->dev = AMD_NVME_DEVICE;
Packit Service db8df9
	} else {
Packit Service db8df9
		int shift;
Packit Service db8df9
Packit Service db8df9
		drive->port = _get_ipmi_sata_port(start_path);
Packit Service db8df9
		if (drive->port < 0) {
Packit Service db8df9
			log_error("Could not retrieve port number\n");
Packit Service db8df9
			return -1;
Packit Service db8df9
		}
Packit Service db8df9
Packit Service db8df9
		/* IPMI control is handled through the MG9098 chips on
Packit Service db8df9
		 * the platform, where each MG9098 chip can control up
Packit Service db8df9
		 * to 8 drives. Since we can have multiple MG9098 chips,
Packit Service db8df9
		 * we need the drive bay relative to the set of 8 controlled
Packit Service db8df9
		 * by the MG9098 chip.
Packit Service db8df9
		 */
Packit Service db8df9
		shift = drive->port - 1;
Packit Service db8df9
		if (shift >= 8)
Packit Service db8df9
			shift %= 8;
Packit Service db8df9
Packit Service db8df9
		drive->drive_bay = 1 << shift;
Packit Service db8df9
		drive->dev = AMD_SATA_DEVICE;
Packit Service db8df9
	}
Packit Service db8df9
Packit Service db8df9
	log_debug("AMD Drive: port: %d, bay %x\n", drive->port,
Packit Service db8df9
		  drive->drive_bay);
Packit Service db8df9
Packit Service db8df9
	return 0;
Packit Service db8df9
}
Packit Service db8df9
Packit Service db8df9
static int _ipmi_platform_channel(struct amd_drive *drive)
Packit Service db8df9
{
Packit Service db8df9
	int rc = 0;
Packit Service db8df9
Packit Service db8df9
	switch (amd_platform) {
Packit Service db8df9
	case AMD_PLATFORM_ETHANOL_X:
Packit Service db8df9
		drive->channel =  0xd;
Packit Service db8df9
		break;
Packit Service db8df9
	case AMD_PLATFORM_DAYTONA_X:
Packit Service db8df9
		drive->channel = 0x17;
Packit Service db8df9
		break;
Packit Service db8df9
	default:
Packit Service db8df9
		rc = -1;
Packit Service db8df9
		log_error("AMD Platform does not have a defined IPMI channel\n");
Packit Service db8df9
		break;
Packit Service db8df9
	}
Packit Service db8df9
Packit Service db8df9
	return rc;
Packit Service db8df9
}
Packit Service db8df9
Packit Service db8df9
static int _ipmi_platform_slave_address(struct amd_drive *drive)
Packit Service db8df9
{
Packit Service db8df9
	int rc = 0;
Packit Service db8df9
Packit Service db8df9
	switch (amd_platform) {
Packit Service db8df9
	case AMD_PLATFORM_ETHANOL_X:
Packit Service db8df9
		drive->slave_addr = 0xc0;
Packit Service db8df9
		break;
Packit Service db8df9
	case AMD_PLATFORM_DAYTONA_X:
Packit Service db8df9
		if (drive->dev == AMD_NO_DEVICE) {
Packit Service db8df9
			/* Assume base slave address, we may not be able
Packit Service db8df9
			 * to retrieve a valid amd_drive yet.
Packit Service db8df9
			 */
Packit Service db8df9
			drive->slave_addr = 0xc0;
Packit Service db8df9
		} else if (drive->dev == AMD_NVME_DEVICE) {
Packit Service db8df9
			/* On DaytonaX systems only drive bays 19 - 24
Packit Service db8df9
			 * support NVMe devices so use the slave address
Packit Service db8df9
			 * for the corresponding MG9098 chip.
Packit Service db8df9
			 */
Packit Service db8df9
			drive->slave_addr = 0xc4;
Packit Service db8df9
		} else {
Packit Service db8df9
			if (drive->port <= 8)
Packit Service db8df9
				drive->slave_addr = 0xc0;
Packit Service db8df9
			else if (drive->port > 8 && drive->port < 17)
Packit Service db8df9
				drive->slave_addr = 0xc2;
Packit Service db8df9
			else
Packit Service db8df9
				drive->slave_addr = 0xc4;
Packit Service db8df9
		}
Packit Service db8df9
Packit Service db8df9
		break;
Packit Service db8df9
	default:
Packit Service db8df9
		rc = -1;
Packit Service db8df9
		log_error("AMD Platform does not have a defined IPMI slave address\n");
Packit Service db8df9
		break;
Packit Service db8df9
	}
Packit Service db8df9
Packit Service db8df9
	return rc;
Packit Service db8df9
}
Packit Service db8df9
Packit Service db8df9
static int _set_ipmi_register(int enable, uint8_t reg,
Packit Service db8df9
			      struct amd_drive *drive)
Packit Service db8df9
{
Packit Service db8df9
	int rc;
Packit Service db8df9
	int status, data_sz;
Packit Service db8df9
	uint8_t drives_status;
Packit Service db8df9
	uint8_t new_drives_status;
Packit Service db8df9
	uint8_t cmd_data[5];
Packit Service db8df9
Packit Service db8df9
	memset(cmd_data, 0, sizeof(cmd_data));
Packit Service db8df9
Packit Service db8df9
	rc = _ipmi_platform_channel(drive);
Packit Service db8df9
	rc |= _ipmi_platform_slave_address(drive);
Packit Service db8df9
	if (rc)
Packit Service db8df9
		return -1;
Packit Service db8df9
Packit Service db8df9
	cmd_data[0] = drive->channel;
Packit Service db8df9
	cmd_data[1] = drive->slave_addr;
Packit Service db8df9
	cmd_data[2] = 0x1;
Packit Service db8df9
	cmd_data[3] = reg;
Packit Service db8df9
Packit Service db8df9
	/* Find current register setting */
Packit Service db8df9
	status = 0;
Packit Service db8df9
Packit Service db8df9
	log_debug("Retrieving current register status\n");
Packit Service db8df9
	log_debug(REG_FMT_2, "channel", cmd_data[0], "slave addr", cmd_data[1]);
Packit Service db8df9
	log_debug(REG_FMT_2, "len", cmd_data[2], "register", cmd_data[3]);
Packit Service db8df9
Packit Service db8df9
	rc = ipmicmd(BMC_SA, 0x0, 0x6, 0x52, 4, &cmd_data, 1, &data_sz,
Packit Service db8df9
		     &status);
Packit Service db8df9
	if (rc) {
Packit Service db8df9
		log_error("Could not determine current register %x setting\n",
Packit Service db8df9
			  reg);
Packit Service db8df9
		return rc;
Packit Service db8df9
	}
Packit Service db8df9
Packit Service db8df9
	drives_status = status;
Packit Service db8df9
Packit Service db8df9
	if (enable)
Packit Service db8df9
		new_drives_status = drives_status | drive->drive_bay;
Packit Service db8df9
	else
Packit Service db8df9
		new_drives_status = drives_status & ~drive->drive_bay;
Packit Service db8df9
Packit Service db8df9
	/* Set the appropriate status */
Packit Service db8df9
	status = 0;
Packit Service db8df9
	cmd_data[4] = new_drives_status;
Packit Service db8df9
Packit Service db8df9
	log_debug("Updating register status: %x -> %x\n", drives_status,
Packit Service db8df9
		  new_drives_status);
Packit Service db8df9
	log_debug(REG_FMT_2, "channel", cmd_data[0], "slave addr", cmd_data[1]);
Packit Service db8df9
	log_debug(REG_FMT_2, "len", cmd_data[2], "register", cmd_data[3]);
Packit Service db8df9
	log_debug(REG_FMT_1, "status", cmd_data[4]);
Packit Service db8df9
Packit Service db8df9
	rc = ipmicmd(BMC_SA, 0x0, 0x6, 0x52, 5, &cmd_data, 1, &data_sz,
Packit Service db8df9
		     &status);
Packit Service db8df9
	if (rc) {
Packit Service db8df9
		log_error("Could not enable register %x\n", reg);
Packit Service db8df9
		return rc;
Packit Service db8df9
	}
Packit Service db8df9
Packit Service db8df9
	return 0;
Packit Service db8df9
}
Packit Service db8df9
Packit Service db8df9
static int _enable_smbus_control(struct amd_drive *drive)
Packit Service db8df9
{
Packit Service db8df9
	log_debug("Enabling SMBUS Control\n");
Packit Service db8df9
	return _set_ipmi_register(1, 0x3c, drive);
Packit Service db8df9
}
Packit Service db8df9
Packit Service db8df9
static int _enable_ibpi_state(struct amd_drive *drive, enum ibpi_pattern ibpi)
Packit Service db8df9
{
Packit Service db8df9
	log_debug("Enabling %s LED\n", ibpi2str(ibpi));
Packit Service db8df9
	return _set_ipmi_register(1, amd_ibpi_ipmi_register[ibpi], drive);
Packit Service db8df9
}
Packit Service db8df9
Packit Service db8df9
static int _disable_ibpi_state(struct amd_drive *drive, enum ibpi_pattern ibpi)
Packit Service db8df9
{
Packit Service db8df9
	log_debug("Disabling %s LED\n", ibpi2str(ibpi));
Packit Service db8df9
	return _set_ipmi_register(0, amd_ibpi_ipmi_register[ibpi], drive);
Packit Service db8df9
}
Packit Service db8df9
Packit Service db8df9
static int _disable_all_ibpi_states(struct amd_drive *drive)
Packit Service db8df9
{
Packit Service db8df9
	int rc;
Packit Service db8df9
Packit Service db8df9
	rc = _disable_ibpi_state(drive, IBPI_PATTERN_PFA);
Packit Service db8df9
	rc |= _disable_ibpi_state(drive, IBPI_PATTERN_LOCATE);
Packit Service db8df9
	rc |= _disable_ibpi_state(drive, IBPI_PATTERN_FAILED_DRIVE);
Packit Service db8df9
	rc |= _disable_ibpi_state(drive, IBPI_PATTERN_FAILED_ARRAY);
Packit Service db8df9
	rc |= _disable_ibpi_state(drive, IBPI_PATTERN_REBUILD);
Packit Service db8df9
Packit Service db8df9
	return rc;
Packit Service db8df9
}
Packit Service db8df9
Packit Service db8df9
int _amd_ipmi_em_enabled(const char *path)
Packit Service db8df9
{
Packit Service db8df9
	int rc;
Packit Service db8df9
	int status, data_sz;
Packit Service db8df9
	uint8_t cmd_data[4];
Packit Service db8df9
	struct amd_drive drive;
Packit Service db8df9
Packit Service db8df9
	memset(&drive, 0, sizeof(struct amd_drive));
Packit Service db8df9
Packit Service db8df9
	rc = _ipmi_platform_channel(&drive);
Packit Service db8df9
	rc |= _ipmi_platform_slave_address(&drive);
Packit Service db8df9
	if (rc)
Packit Service db8df9
		return -1;
Packit Service db8df9
Packit Service db8df9
	cmd_data[0] = drive.channel;
Packit Service db8df9
	cmd_data[1] = drive.slave_addr;
Packit Service db8df9
	cmd_data[2] = 0x1;
Packit Service db8df9
	cmd_data[3] = 0x63;
Packit Service db8df9
Packit Service db8df9
	status = 0;
Packit Service db8df9
	rc = ipmicmd(BMC_SA, 0x0, 0x6, 0x52, 4, &cmd_data, 1,
Packit Service db8df9
		     &data_sz, &status);
Packit Service db8df9
Packit Service db8df9
	if (rc) {
Packit Service db8df9
		log_error("Can't determine MG9098 Status\n");
Packit Service db8df9
		return 0;
Packit Service db8df9
	}
Packit Service db8df9
Packit Service db8df9
	if (status != 98) {
Packit Service db8df9
		log_error("Not a MG9098\n");
Packit Service db8df9
		return 0;
Packit Service db8df9
	}
Packit Service db8df9
Packit Service db8df9
	return 1;
Packit Service db8df9
}
Packit Service db8df9
Packit Service db8df9
int _amd_ipmi_write(struct block_device *device, enum ibpi_pattern ibpi)
Packit Service db8df9
{
Packit Service db8df9
	int rc;
Packit Service db8df9
	struct amd_drive drive;
Packit Service db8df9
Packit Service db8df9
	log_info("\n");
Packit Service db8df9
	log_info("Setting %s...", ibpi2str(ibpi));
Packit Service db8df9
Packit Service db8df9
	rc = _get_amd_ipmi_drive(device->cntrl_path, &drive);
Packit Service db8df9
	if (rc)
Packit Service db8df9
		return rc;
Packit Service db8df9
Packit Service db8df9
	if ((ibpi == IBPI_PATTERN_NORMAL) ||
Packit Service db8df9
	    (ibpi == IBPI_PATTERN_ONESHOT_NORMAL)) {
Packit Service db8df9
		rc = _disable_all_ibpi_states(&drive);
Packit Service db8df9
		return rc;
Packit Service db8df9
	}
Packit Service db8df9
Packit Service db8df9
	if (ibpi == IBPI_PATTERN_LOCATE_OFF) {
Packit Service db8df9
		rc = _disable_ibpi_state(&drive, IBPI_PATTERN_LOCATE);
Packit Service db8df9
		return rc;
Packit Service db8df9
	}
Packit Service db8df9
Packit Service db8df9
	rc = _enable_smbus_control(&drive);
Packit Service db8df9
	if (rc)
Packit Service db8df9
		return rc;
Packit Service db8df9
Packit Service db8df9
	rc = _enable_ibpi_state(&drive, ibpi);
Packit Service db8df9
	if (rc)
Packit Service db8df9
		return rc;
Packit Service db8df9
Packit Service db8df9
	return 0;
Packit Service db8df9
}
Packit Service db8df9
Packit Service db8df9
char *_amd_ipmi_get_path(const char *cntrl_path, const char *sysfs_path)
Packit Service db8df9
{
Packit Service db8df9
	char *p, *t;
Packit Service db8df9
Packit Service db8df9
	/* For NVMe devices we can just dup the path sysfs path */
Packit Service db8df9
	p = strstr(cntrl_path, "nvme");
Packit Service db8df9
	if (p)
Packit Service db8df9
		return strdup(sysfs_path);
Packit Service db8df9
Packit Service db8df9
	/* For SATA devices we need everything up to 'ataXX/' in the path */
Packit Service db8df9
	p = strdup(cntrl_path);
Packit Service db8df9
	if (!p)
Packit Service db8df9
		return NULL;
Packit Service db8df9
Packit Service db8df9
	/* Find the beginning of the ataXX piece of the path */
Packit Service db8df9
	t = strstr(p, "ata");
Packit Service db8df9
	if (!t)
Packit Service db8df9
		return NULL;
Packit Service db8df9
Packit Service db8df9
	/* Move to the '/' after the ataXX piece of the path and terminate the
Packit Service db8df9
	 * string there.
Packit Service db8df9
	 */
Packit Service db8df9
	t = strchr(t, '/');
Packit Service db8df9
	if (!t)
Packit Service db8df9
		return NULL;
Packit Service db8df9
Packit Service db8df9
	*++t = '\0';
Packit Service db8df9
Packit Service db8df9
	return p;
Packit Service db8df9
}
Packit Service db8df9