/*
 * Intel(R) Enclosure LED Utilities
 * Copyright (C) 2009-2022 Intel Corporation.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope 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.,
 * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
 *
 */

#include <dirent.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#if _HAVE_DMALLOC_H
#include <dmalloc.h>
#endif

#include <scsi/sg_lib.h>
#include <scsi/sg_cmds_extra.h>

#include "ses.h"
#include "utils.h"

static int debug = 0;

#define ENCL_CFG_DIAG_STATUS		0x01
#define ENCL_CTRL_DIAG_STATUS		0x02
#define ENCL_CTRL_DIAG_CFG		0x02
#define ENCL_EL_DESCR_STATUS		0x07
#define ENCL_ADDITIONAL_EL_STATUS	0x0a
#define SCSI_PROTOCOL_SAS		6

static int get_ses_page(int fd, struct ses_page *p, int pg_code)
{
	int ret;
	int retry_count = 3;

	do {
		ret = sg_ll_receive_diag(fd, 1, pg_code, p->buf, sizeof(p->buf),
					 0, debug);
	} while (ret && retry_count--);

	if (!ret)
		p->len = (p->buf[2] << 8) + p->buf[3] + 4;
	return ret;
}

static int process_page1(struct ses_pages *sp)
{
	int num_encl;		/* number of subenclosures */
	unsigned char *ed;	/* Enclosure Descriptor */
	int len = 0;
	int sum_headers = 0;	/* Number of Type descriptor headers */
	int i = 0;

	/* How many enclosures is in the main enclosure? */
	num_encl = sp->page1.buf[1] + 1;
	/* Go to Enclosure Descriptor */
	ed = sp->page1.buf + 8;
	for (i = 0; i < num_encl; i++, ed += len) {
		if (ed + 3 > sp->page1.buf + sp->page1.len) {
			log_debug
			    ("SES: Error, response pare 1 truncated at %d\n",
			     i);
			return 1;
		}
		sum_headers += ed[2];
		len = ed[3] + 4;
		if (len < 40) {
			log_debug("SES: Response too short for page 1\n");
			continue;
		}
	}

	sp->page1_types = (struct type_descriptor_header *)ed;
	sp->page1_types_len = sum_headers;

	/* ed is on type descr header */
	for (i = 0; i < sum_headers; i++, ed += 4) {
		if (ed > sp->page1.buf + sp->page1.len) {
			log_debug("SES: Response page 1 truncated at %d\n", i);
			return 1;
		}
	}
	return 0;
}

static void print_page10(struct ses_pages *sp)
{
	unsigned char *ai = sp->page10.buf + 8;
	int i = 0, len = 0, eip = 0;
	unsigned char *sas = NULL;

	while (ai < sp->page10.buf + sp->page10.len) {
		printf("%s()[%d]: Inv: %d, EIP: %d, Proto: 0x%04x\n", __func__,
		       i++, ((ai[0] & 0x80) >> 7), ((ai[0] & 0x10) >> 4),
		       (unsigned int) (ai[0] & 0xf));
		printf("\tDescriptor len (x-1): %d\n", ai[1] + 1);
		eip = ai[0] & 0x10;
		if (eip)
			printf("\tElement Index: %d\n", ai[3]);
		len = ai[1] + 2;
		if ((ai[0] & 0xf) == SCSI_PROTOCOL_SAS) {
			if (eip)
				sas = ai + 4;
			else
				sas = ai + 2;
			printf("\tProtocol SAS:\n");
			printf("\tNumber of phy descriptors: %d\n", sas[0]);
			printf("\tNot all phys: %d, descriptor type: 0x%1x\n",
			       (sas[1] & 1), ((sas[1] & 0xc0) >> 6));
			if (eip) {
				printf("\tDevice slot number: %d\n", sas[3]);
				sas += 2;
			}
			sas += 2;
			printf("\tDevice type: 0x%01x\n",
				(unsigned int)((sas[0] & 0x70) >> 4));
			printf("\tSMP Initiator Port: 0x%01x\n",
				(unsigned int)((sas[2] & 2) >> 1));
			printf("\tSTP Initiator Port: 0x%01x\n",
				(unsigned int)((sas[2] & 4) >> 2));
			printf("\tSSP Initiator Port: 0x%01x\n",
				(unsigned int)((sas[2] & 8) >> 3));
			printf("\tSATA DEVICE: 0x%01x\n",
				(unsigned int)(sas[3] & 1));
			printf("\tSMP Target Port: 0x%01x\n",
				(unsigned int)((sas[3] & 2) >> 1));
			printf("\tSTP Target Port: 0x%01x\n",
				(unsigned int)((sas[3] & 4) >> 2));
			printf("\tSSP Target Port: 0x%01x\n",
				(unsigned int)((sas[3] & 8) >> 3));
			printf("\tSATA Port Selector: 0x%01x\n",
				(unsigned int)((sas[3] & 0X80) >> 7));
			printf
			    ("\tAttached SAS Address: 0x%02x%02x%02x%02x%02x%02x%02x%02x\n",
			     sas[4], sas[5], sas[6], sas[7], sas[8], sas[9],
			     sas[10], sas[11]);
			printf
			    ("\tSAS Address: 0x%02x%02x%02x%02x%02x%02x%02x%02x\n",
			     sas[12], sas[13], sas[14], sas[15], sas[16],
			     sas[17], sas[18], sas[19]);
			printf("\tPHY Identified: 0x%01x\n", sas[20]);
		} else
			printf("\tProtocol not SAS: 0x%02x, skipping\n",
				(unsigned int)(ai[0] & 0xf));
		/* */
		ai += len;
	}
	return;
}

int ses_load_pages(int fd, struct ses_pages *sp)
{
	int ret;

	/* Read configuration. */
	ret = get_ses_page(fd, &sp->page1, ENCL_CFG_DIAG_STATUS);
	if (ret)
		return ret;

	ret = process_page1(sp);
	if (ret)
		return ret;

	/* Get Enclosure Status */
	ret = get_ses_page(fd, &sp->page2, ENCL_CTRL_DIAG_STATUS);
	if (ret)
		return ret;

	/* Additional Element Status */
	ret = get_ses_page(fd, &sp->page10, ENCL_ADDITIONAL_EL_STATUS);
	if (ret)
		return ret;

	if (debug)
		print_page10(sp);

	return ret;
}

static enum ibpi_pattern ibpi_to_ses(enum ibpi_pattern ibpi)
{
	switch (ibpi) {
	case IBPI_PATTERN_UNKNOWN:
	case IBPI_PATTERN_ONESHOT_NORMAL:
	case IBPI_PATTERN_NORMAL:
		return SES_REQ_OK;
	case IBPI_PATTERN_FAILED_ARRAY:
		return SES_REQ_IFA;
	case IBPI_PATTERN_DEGRADED:
		return SES_REQ_ICA;
	case IBPI_PATTERN_REBUILD:
		return SES_REQ_REBUILD;
	case IBPI_PATTERN_FAILED_DRIVE:
		return SES_REQ_FAULT;
	case IBPI_PATTERN_LOCATE:
		return SES_REQ_IDENT;
	case IBPI_PATTERN_HOTSPARE:
		return SES_REQ_HOSTSPARE;
	case IBPI_PATTERN_PFA:
		return SES_REQ_PRDFAIL;
	default:
		return ibpi;
	}
}

static inline void _set_prdfail(unsigned char *u)
{
	u[0] |= (1 << 6);
}

static inline void _set_abrt(unsigned char *u)
{
	u[1] |= (1 << 0);
}

static inline void _set_rebuild(unsigned char *u)
{
	u[1] |= (1 << 1);
}

static inline void _set_ifa(unsigned char *u)
{
	u[1] |= (1 << 2);
}

static inline void _set_ica(unsigned char *u)
{
	u[1] |= (1 << 3);
}

static inline void _set_cons_check(unsigned char *u)
{
	u[1] |= (1 << 4);
}

static inline void _set_hspare(unsigned char *u)
{
	u[1] |= (1 << 5);
}

static inline void _set_rsvd_dev(unsigned char *u)
{
	u[1] |= (1 << 6);
}

static inline void _set_ok(unsigned char *u)
{
	u[1] |= (1 << 7);
}

static inline void _set_ident(unsigned char *u)
{
	u[2] |= (1 << 1);
}

static inline void _clr_ident(unsigned char *u)
{
	u[2] &= ~(1 << 1);
}

static inline void _set_rm(unsigned char *u)
{
	u[2] |= (1 << 2);
}

static inline void _set_ins(unsigned char *u)
{
	u[2] |= (1 << 3);
}

static inline void _set_miss(unsigned char *u)
{
	u[2] |= (1 << 4);
}

static inline void _set_dnr(unsigned char *u)
{
	u[2] |= (1 << 6);
}

static inline void _set_actv(unsigned char *u)
{
	u[2] |= (1 << 7);
}

static inline void _set_enbb(unsigned char *u)
{
	u[3] |= (1 << 2);
}

static inline void _set_enba(unsigned char *u)
{
	u[3] |= (1 << 3);
}

static inline void _set_off(unsigned char *u)
{
	u[3] |= (1 << 4);
}

static inline void _set_fault(unsigned char *u)
{
	u[3] |= (1 << 5);
}

static int ses_set_message(enum ibpi_pattern ibpi, struct ses_slot_ctrl_elem *el)
{
	struct ses_slot_ctrl_elem msg;

	memset(&msg, 0, sizeof(msg));
	if (ibpi == IBPI_PATTERN_LOCATE_OFF) {
		/*
		 * For locate_off we don't set a new state, just clear the
		 * IDENT bit and the bits that are reserved or have different
		 * meanings in Status and Control pages (RQST ACTIVE and
		 * RQST MISSING).
		 */
		_clr_ident(el->b);
		el->b2 &= 0x4e;
		el->b3 &= 0x3c;
		return 0;
	}

	switch (ibpi_to_ses(ibpi)) {
	case SES_REQ_ABORT:
		_set_abrt(msg.b);
		break;
	case SES_REQ_REBUILD:
		_set_rebuild(msg.b);
		break;
	case SES_REQ_IFA:
		_set_ifa(msg.b);
		break;
	case SES_REQ_ICA:
		_set_ica(msg.b);
		break;
	case SES_REQ_CONS_CHECK:
		_set_cons_check(msg.b);
		break;
	case SES_REQ_HOSTSPARE:
		_set_hspare(msg.b);
		break;
	case SES_REQ_RSVD_DEV:
		_set_rsvd_dev(msg.b);
		break;
	case SES_REQ_OK:
		_set_ok(msg.b);
		break;
	case SES_REQ_IDENT:
		_set_ident(msg.b);
		break;
	case SES_REQ_RM:
		_set_rm(msg.b);
		break;
	case SES_REQ_INS:
		_set_ins(msg.b);
		break;
	case SES_REQ_MISSING:
		_set_miss(msg.b);
		break;
	case SES_REQ_DNR:
		_set_dnr(msg.b);
		break;
	case SES_REQ_ACTIVE:
		_set_actv(msg.b);
		break;
	case SES_REQ_EN_BB:
		_set_enbb(msg.b);
		break;
	case SES_REQ_EN_BA:
		_set_enba(msg.b);
		break;
	case SES_REQ_DEV_OFF:
		_set_off(msg.b);
		break;
	case SES_REQ_FAULT:
		_set_fault(msg.b);
		break;
	case SES_REQ_PRDFAIL:
		_set_prdfail(msg.b);
		break;
	default:
		return 1;
	}

	*el = msg;

	return 0;
}

int ses_write_msg(enum ibpi_pattern ibpi, struct ses_pages *sp, int idx)
{
	/* Move do descriptors */
	struct ses_slot_ctrl_elem *descriptors = (void *)(sp->page2.buf + 8);
	int i;
	struct ses_slot_ctrl_elem *desc_element = NULL;
	element_type local_element_type = SES_UNSPECIFIED;

	for (i = 0; i < sp->page1_types_len; i++) {
		const struct type_descriptor_header *t = &sp->page1_types[i];

		descriptors++; /* At first, skip overall header. */

		if (t->element_type == SES_DEVICE_SLOT ||
		    t->element_type == SES_ARRAY_DEVICE_SLOT) {
			if (local_element_type < t->element_type &&
			    t->num_of_elements > idx) {
				local_element_type = t->element_type;
				desc_element = &descriptors[idx];
			}
		} else {
			/*
			 * Device Slot and Array Device Slot elements are
			 * always first on the type descriptor header list
			 */
			break;
		}

		descriptors += t->num_of_elements;
	}

	if (desc_element) {
		int ret = ses_set_message(ibpi, desc_element);
		if (ret)
			return ret;

		sp->changes++;

		/* keep PRDFAIL, clear rest */
		desc_element->common_control &= 0x40;
		/* set select */
		desc_element->common_control |= 0x80;

		/* second byte is valid only for Array Device Slot */
		if (local_element_type != SES_ARRAY_DEVICE_SLOT)
			desc_element->array_slot_control = 0;

		return 0;
	}

	return 1;
}

int ses_send_diag(int fd, struct ses_pages *sp)
{
	return sg_ll_send_diag(fd, 0, 1, 0, 0, 0, 0, sp->page2.buf,
			       sp->page2.len, 0, debug);
}

int ses_get_slots(struct ses_pages *sp, struct ses_slot **out_slots, int *out_slots_count)
{
	unsigned char *add_desc = NULL;
	unsigned char *ap = NULL, *addr_p = NULL;
	int i, j, len = 0;

	/* Check Page10 for address. Extract index. */
	ap = add_desc = sp->page10.buf + 8;
	for (i = 0; i < sp->page1_types_len; i++) {
		const struct type_descriptor_header *t = &sp->page1_types[i];

		if (t->element_type == SES_DEVICE_SLOT ||
		    t->element_type == SES_ARRAY_DEVICE_SLOT) {
			struct ses_slot *slots;

			slots = calloc(t->num_of_elements, sizeof(*slots));
			if (!slots)
				return -1;

			for (j = 0; j < t->num_of_elements; j++, ap += len) {
				/* Get Additional Element Status Descriptor */
				/* length (x-1) */
				len = ap[1] + 2;
				if ((ap[0] & 0xf) != SCSI_PROTOCOL_SAS) {
					slots[j].index = -1;
					continue;	/* need SAS PROTO */
				}
				/* It is a SAS protocol, go on */
				if ((ap[0] & 0x10))	/* Check EIP */
					addr_p = ap + 8;
				else
					addr_p = ap + 4;
				/* Process only PHY 0 descriptor. */

				/* Convert be64 to le64 */
				slots[j].sas_addr =
					((uint64_t)addr_p[12] << 8*7) |
					((uint64_t)addr_p[13] << 8*6) |
					((uint64_t)addr_p[14] << 8*5) |
					((uint64_t)addr_p[15] << 8*4) |
					((uint64_t)addr_p[16] << 8*3) |
					((uint64_t)addr_p[17] << 8*2) |
					((uint64_t)addr_p[18] << 8*1) |
					((uint64_t)addr_p[19]);

				slots[j].index = ap[0] & 0x10 ? ap[3] : j;
			}

			*out_slots = slots;
			*out_slots_count = t->num_of_elements;

			return 0;
		}
	}

	return 1;
}
