/*
 * zipl - zSeries Initial Program Loader tool
 *
 * Common IO functions for channel based devices
 *
 * Copyright IBM Corp. 2013, 2017
 *
 * s390-tools is free software; you can redistribute it and/or modify
 * it under the terms of the MIT license. See LICENSE for details.
 */

#include "cio.h"
#include "error.h"
#include "libc.h"
#include "s390.h"

static unsigned long initial_lpm = 0x00;
static const char *msg_essch = "Start subchannel failed";
static const char *msg_eenable_dev = "Enable device failed";

static int clear_subchannel(struct subchannel_id subchannel_id)
{
	register struct subchannel_id reg1 asm ("1") = subchannel_id;
	int rc;

	asm volatile(
		"     csch\n"
		"     ipm    %0\n"
		"     srl    %0,28\n"
		: "=d" (rc)
		: "d" (reg1)
		: "cc");
	return rc;
}

static int test_subchannel(struct subchannel_id subchannel_id, struct irb *irb)
{
	register struct subchannel_id reg1 asm ("1") = subchannel_id;
	int rc;

	asm volatile(
		"       tsch    0(%3)\n"
		"       ipm     %0\n"
		"       srl     %0,28\n"
		: "=d" (rc), "=m" (*irb)
		: "d" (reg1), "a" (irb)
		: "cc");
	return rc;
}

static int start_subchannel(struct subchannel_id subchannel_id, struct orb *orb)
{
	register struct subchannel_id reg1 asm ("1") = subchannel_id;
	int rc;

	asm volatile(
		"      ssch    0(%2)\n"
		"      ipm     %0\n"
		"      srl     %0,28\n"
		: "=d" (rc)
		: "d" (reg1), "a" (orb), "m" (*orb)
		: "cc", "memory");
	return rc;
}

int store_subchannel(struct subchannel_id subchannel_id, struct schib *schib)
{
	register struct subchannel_id reg1 asm ("1") = subchannel_id;
	int ccode;

	asm volatile(
		"      stsch    0(%3)\n"
		"      ipm      %0\n"
		"      srl      %0,28\n"
		: "=d" (ccode), "=m" (*schib)
		: "d" (reg1), "a" (schib)
		: "cc");
	return ccode;
}

static int modify_subchannel(struct subchannel_id subchannel_id,
			     struct schib *schib)
{
	register struct subchannel_id reg1 asm ("1") = subchannel_id;
	int rc;

	asm volatile(
		"      msch    0(%2)\n"
		"      ipm     %0\n"
		"      srl     %0,28\n"
		: "=d" (rc)
		: "d" (reg1), "a" (schib), "m" (*schib)
		: "cc");
	return rc;
}

void io_irq_enable(void)
{
	unsigned long ctl = 0xff000000;

	__ctl_load(ctl, 6, 6);
}


void io_irq_disable(void)
{
	unsigned long ctl = 0x00000000;

	__ctl_load(ctl, 6, 6);
}

/*
 * load wait psw and test subchannel after interrupt was received
 * return condition code
 */
static int wait_for_int(struct subchannel_id subchannel_id, struct irb *irb)
{
	int rc;

	do {
		load_wait_psw(0x0202000180000000ULL, &S390_lowcore.io_new_psw);
		rc = test_subchannel(subchannel_id, irb);
	} while (rc == 1);

	return rc;
}

/*
 * get next path for IO
 * call panic if no path is left
 */
static uint32_t next_path(uint32_t mask, struct subchannel_id subchannel_id,
			  struct irb *irb)
{
	if (!initial_lpm) {
		mask >>= 1;
		if (mask == 0)
			panic(ESSCH, "%s", msg_essch);
	}

	/* clear initial path mask to iterate throu other paths, too */
	initial_lpm = 0;

	if (clear_subchannel(subchannel_id))
		panic(ESSCH, "%s", msg_essch);

	wait_for_int(subchannel_id, irb);

	return mask;
}

int start_io(struct subchannel_id subchannel_id, struct irb *irb,
	     struct orb *orb, int panic)
{
	uint32_t mask = FIRST_PATH_MASK;
	struct scsw *scsw;
	int rc, retry = MAX_RETRIES;

	while (1) {
		/*
		 * set path mask in orb to initial path mask or to current mask
		 * initial_lpm gets cleared in next_path()
		 */
		if (!initial_lpm)
			orb->lpm = mask;
		else
			orb->lpm = initial_lpm;

		rc = start_subchannel(subchannel_id, orb);
		if (rc == CC_NOT_OPER) {
			mask = next_path(mask, subchannel_id, irb);
			retry = MAX_RETRIES;
			continue;
		} else if (rc != CC_INITIATED) {
			/*
			 * possible status pending
			 * call test_subchannel to clear and retry
			 */
			goto retry;
		}

		scsw = (struct scsw *)irb;
		/* wait for interrupt and device is ready */
		do {
			rc = wait_for_int(subchannel_id, irb);
			if (rc) {
				if (panic)
					panic(ESSCH, "%s", msg_essch);
				else
					return -1;
			}
			/* device end */
			if (scsw->dstat & 0x04)
				break;
			/* deferred condition code */
			if (scsw->cc != 0)
				break;
			/* status alert set */
			if (scsw->stctl & 0x10)
				break;
			/* channel state */
			if (scsw->cstat & 0xff)
				break;
		} while (1);

		/* path not operational */
		if (scsw->cc == 3) {
			mask = next_path(mask, subchannel_id, irb);
			retry = MAX_RETRIES;
			continue;
		}

		/*
		 * If no deferred condition code is set as well as
		 * no channel status, no alert status control and no device
		 * status except channel_end and device_end, we are done.
		 */
		if (scsw->cc == 0 &&
		    scsw->cstat == 0 &&
		    scsw->dstat & 0x04 &&
		    !(scsw->stctl & 0x10) &&
		    !(scsw->dstat & 0xf3))
			return 0;

retry:
		/* else we clear the subchannel and do a retry */
		test_subchannel(subchannel_id, irb);
		retry--;

		/*
		 * if no retry is left we try another path;
		 * if no path is left it will panic
		 */
		if (retry <= 0) {
			mask = next_path(mask, subchannel_id, irb);
			retry = MAX_RETRIES;
		}
	}
}

void set_device(struct subchannel_id subchannel_id, int enabled)
{
	struct schib schib;
	struct irb *irb;
	int i, rc;

	irb = (struct irb *)&S390_lowcore.irb;
	rc = store_subchannel(subchannel_id, &schib);

	if (rc == CC_NOT_OPER)
		/* Panic if path not operational */
		panic(EENABLE_DEV, "%s", msg_eenable_dev);

	/* enable/disable subchannel */
	schib.pmcw.ena = enabled ? ENABLED : DISABLED;

	for (i = 0; i < 256; i++) {
		rc = modify_subchannel(subchannel_id, &schib);
		if (!rc) {
			/* successful */
			break;
		} else if (rc == CC_STATUS_PENDING || rc == CC_BUSY) {
			/* call test_subchannel to clear pending status */
			rc = test_subchannel(subchannel_id, irb);
			if (rc == CC_NOT_OPER)
				panic(EENABLE_DEV, "%s", msg_eenable_dev);
		} else if (rc == CC_NOT_OPER) {
			/* Panic if path not operational */
			panic(EENABLE_DEV, "%s", msg_eenable_dev);
		}
	}

	if (!enabled)
		return;

	/* set initial lpm to lpum if path is enabled and 0 if not */
	initial_lpm = rc ? 0 : schib.pmcw.lpum;
}
