/*
 * (C) Copyright HDS GmbH 2006. All Rights Reserved.
 *
 * pp_hds_modular.c
 * Version 2.00
 *
 * Prioritizer for Device Mapper Multipath and HDS Storage
 *
 * Hitachis Modular Storage contains two controllers for redundancy. The
 * Storage internal LUN (LDEV) will normally allocated via two paths to the
 * server (one path per controller). For performance reasons should the server
 * access to a LDEV only via one controller. The other path to the other
 * controller is stand-by. It is also possible to allocate more as one path
 * for a LDEV per controller. Here is active/active access allowed. The other
 * paths via the other controller are stand-by.
 *
 * This prioritizer checks with inquiry command the represented LDEV and
 * Controller number and gives back a priority followed by this scheme:
 *
 * CONTROLLER ODD  and LDEV  ODD: PRIORITY 1
 * CONTROLLER ODD  and LDEV EVEN: PRIORITY 0
 * CONTROLLER EVEN and LDEV  ODD: PRIORITY 0
 * CONTROLLER EVEN and LDEV EVEN: PRIORITY 1
 *
 * In the storage you can define for each LDEV a owner controller. If the
 * server makes IOs via the other controller the storage will switch the
 * ownership automatically. In this case you can see in the storage that the
 * current controller is different from the default controller, but this is
 * absolutely no problem.
 *
 * With this prioritizer it is possible to establish a static load balancing.
 * Half of the LUNs are accessed via one HBA/storage controller and the other
 * half via the other HBA/storage controller.
 *
 * In cluster environments (RAC) it also guarantees that all cluster nodes have
 * access to the LDEVs via the same controller.
 *
 * You can run the prioritizer manually in verbose mode:
 * # pp_hds_modular -v 8:224
 * VENDOR:  HITACHI
 * PRODUCT: DF600F-CM
 * SERIAL:  0x0105
 * LDEV:    0x00C6
 * CTRL:    1
 * PORT:    B
 * CTRL ODD, LDEV EVEN, PRIO 0
 *
 * To compile this source please execute # cc pp_hds_modular.c -o /sbin/mpath_prio_hds_modular
 *
 * Changes 2006-07-16:
 *         - Changed to forward declaration of functions
 *         - The switch-statement was changed to a logical expression
 *         - unlinking of the devpath now also occurs at the end of
 *           hds_modular_prio to avoid old /tmp/.pp_balance.%u.%u.devnode
 *           entries in /tmp-Directory
 *         - The for-statements for passing variables where changed to
 *           snprintf-commands in verbose mode
 * Changes 2006-08-10:
 *         - Back to the old switch statements because the regular expression does
 *           not work under RHEL4 U3 i386
 * Changes 2007-06-27:
 *	- switched from major:minor argument to device node argument
 *
 * This file is released under the GPL.
 *
 */

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <stdlib.h>

#include "sg_include.h"
#include "debug.h"
#include "prio.h"
#include "structs.h"

#define INQ_REPLY_LEN 255
#define INQ_CMD_CODE 0x12
#define INQ_CMD_LEN 6

#define pp_hds_log(prio, fmt, args...) \
	condlog(prio, "%s: hds prio: " fmt, dev, ##args)

int hds_modular_prio (const char *dev, int fd, unsigned int timeout)
{
	int k;
	char vendor[9];
	char product[32];
	char serial[32];
	char ldev[32];
	char ctrl[32];
	char port[32];
	unsigned char inqCmdBlk[INQ_CMD_LEN] = { INQ_CMD_CODE, 0, 0, 0, INQ_REPLY_LEN, 0 };
	unsigned char inqBuff[INQ_REPLY_LEN];
	unsigned char *inqBuffp = inqBuff;
	unsigned char sense_buffer[32];
	sg_io_hdr_t io_hdr;

	if ((ioctl (fd, SG_GET_VERSION_NUM, &k) < 0) || (k < 30000)) {
		pp_hds_log(0, "can't use SG ioctl interface");
		return -1;
	}

	memset (&io_hdr, 0, sizeof (sg_io_hdr_t));
	memset (inqBuff, 0, INQ_REPLY_LEN);
	io_hdr.interface_id = 'S';
	io_hdr.cmd_len = sizeof (inqCmdBlk);
	io_hdr.mx_sb_len = sizeof (sense_buffer);
	io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
	io_hdr.dxfer_len = INQ_REPLY_LEN;
	io_hdr.dxferp = inqBuff;
	io_hdr.cmdp = inqCmdBlk;
	io_hdr.sbp = sense_buffer;
	io_hdr.timeout = get_prio_timeout(timeout, 2000); /* TimeOut = 2 seconds */

	if (ioctl (fd, SG_IO, &io_hdr) < 0) {
		pp_hds_log(0, "SG_IO error");
		return -1;
	}
	if ((io_hdr.info & SG_INFO_OK_MASK) != SG_INFO_OK) {
		pp_hds_log(0, "SCSI error");
		return -1;
	}

	snprintf (vendor, 9, "%.8s", inqBuffp + 8);
	snprintf (product, 17, "%.16s", inqBuffp + 16);
	snprintf (serial, 5, "%.4s", inqBuffp + 40);
	snprintf (ldev, 5, "%.4s", inqBuffp + 44);
	snprintf (ctrl, 2, "%.1s", inqBuffp + 49);
	snprintf (port, 2, "%.1s", inqBuffp + 50);

	pp_hds_log(4, "VENDOR:  %s", vendor);
	pp_hds_log(4, "PRODUCT: %s", product);
	pp_hds_log(4, "SERIAL:  0x%s", serial);
	pp_hds_log(4, "LDEV:    0x%s", ldev);
	pp_hds_log(4, "CTRL:    %s", ctrl);
	pp_hds_log(4, "PORT:    %s", port);

	switch (ctrl[0]) {
	case '0': case '2': case '4': case '6': case '8':
		switch (ldev[3]) {
		case '0': case '2': case '4': case '6': case '8': case 'A': case 'C': case 'E':
			pp_hds_log(4, "CTRL EVEN, LDEV EVEN, PRIO 1");
			return 1;
			break;
		case '1': case '3': case '5': case '7': case '9': case 'B': case 'D': case 'F':
			pp_hds_log(4, "CTRL EVEN, LDEV ODD, PRIO 0");
			return 0;
			break;
		}
		break;
	case '1': case '3': case '5': case '7': case '9':
		switch (ldev[3]) {
		case '0': case '2': case '4': case '6': case '8': case 'A': case 'C': case 'E':
			pp_hds_log(4, "CTRL ODD, LDEV EVEN, PRIO 0");
			return 0;
			break;
		case '1': case '3': case '5': case '7': case '9': case 'B': case 'D': case 'F':
			pp_hds_log(4, "CTRL ODD, LDEV ODD, PRIO 1");
			return 1;
			break;
		}
		break;
	}
	return -1;
}

int getprio (struct path * pp, __attribute__((unused)) char *args,
	     unsigned int timeout)
{
	return hds_modular_prio(pp->dev, pp->fd, timeout);
}
