Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 13952ec1 authored by Sebastian Ott's avatar Sebastian Ott Committed by Martin Schwidefsky
Browse files

[S390] cio: introduce cio_commit_config



To change the configuration of a subchannel we alter the modifiable
bits of the subchannel's schib field and issue a modify subchannel.
There can be the case that not all changes were applied -or worse-
quietly overwritten by the hardware. With the next store subchannel
we obtain the current state of the hardware but lose our target
configuration.

With this patch we introduce a subchannel_config structure which
contains the target subchannel configuration. Additionally the msch
wrapper cio_modify is replaced with cio_commit_config which
copies the desired changes to a temporary schib. msch is then
called with the temporary schib. This schib is only written back
to the subchannel if all changes were applied.

Signed-off-by: default avatarSebastian Ott <sebott@linux.vnet.ibm.com>
Signed-off-by: default avatarMartin Schwidefsky <schwidefsky@de.ibm.com>
parent cdb912a4
Loading
Loading
Loading
Loading
+73 −61
Original line number Diff line number Diff line
@@ -330,23 +330,63 @@ cio_cancel (struct subchannel *sch)
	}
}


static void cio_apply_config(struct subchannel *sch, struct schib *schib)
{
	schib->pmcw.intparm = sch->config.intparm;
	schib->pmcw.mbi = sch->config.mbi;
	schib->pmcw.isc = sch->config.isc;
	schib->pmcw.ena = sch->config.ena;
	schib->pmcw.mme = sch->config.mme;
	schib->pmcw.mp = sch->config.mp;
	schib->pmcw.csense = sch->config.csense;
	schib->pmcw.mbfc = sch->config.mbfc;
	if (sch->config.mbfc)
		schib->mba = sch->config.mba;
}

static int cio_check_config(struct subchannel *sch, struct schib *schib)
{
	return (schib->pmcw.intparm == sch->config.intparm) &&
		(schib->pmcw.mbi == sch->config.mbi) &&
		(schib->pmcw.isc == sch->config.isc) &&
		(schib->pmcw.ena == sch->config.ena) &&
		(schib->pmcw.mme == sch->config.mme) &&
		(schib->pmcw.mp == sch->config.mp) &&
		(schib->pmcw.csense == sch->config.csense) &&
		(schib->pmcw.mbfc == sch->config.mbfc) &&
		(!sch->config.mbfc || (schib->mba == sch->config.mba));
}

/*
 * Function: cio_modify
 * Issues a "Modify Subchannel" on the specified subchannel
 * cio_commit_config - apply configuration to the subchannel
 */
int
cio_modify (struct subchannel *sch)
int cio_commit_config(struct subchannel *sch)
{
	int ccode, retry, ret;
	struct schib schib;
	int ccode, retry, ret = 0;

	if (stsch(sch->schid, &schib) || !css_sch_is_valid(&schib))
		return -ENODEV;

	ret = 0;
	for (retry = 0; retry < 5; retry++) {
		ccode = msch_err (sch->schid, &sch->schib);
		/* copy desired changes to local schib */
		cio_apply_config(sch, &schib);
		ccode = msch_err(sch->schid, &schib);
		if (ccode < 0) /* -EIO if msch gets a program check. */
			return ccode;
		switch (ccode) {
		case 0: /* successfull */
			if (stsch(sch->schid, &schib) ||
			    !css_sch_is_valid(&schib))
				return -ENODEV;
			if (cio_check_config(sch, &schib)) {
				/* commit changes from local schib */
				memcpy(&sch->schib, &schib, sizeof(schib));
				return 0;
			}
			ret = -EAGAIN;
			break;
		case 1: /* status pending */
			return -EBUSY;
		case 2: /* busy */
@@ -396,32 +436,24 @@ int cio_enable_subchannel(struct subchannel *sch, u32 intparm)
	if (cio_update_schib(sch))
		return -ENODEV;

	for (retry = 5, ret = 0; retry > 0; retry--) {
		sch->schib.pmcw.ena = 1;
		sch->schib.pmcw.isc = sch->isc;
		sch->schib.pmcw.intparm = intparm;
		ret = cio_modify(sch);
		if (ret == -ENODEV)
			break;
		if (ret == -EIO)
	sch->config.ena = 1;
	sch->config.isc = sch->isc;
	sch->config.intparm = intparm;

	for (retry = 0; retry < 3; retry++) {
		ret = cio_commit_config(sch);
		if (ret == -EIO) {
			/*
			 * Got a program check in cio_modify. Try without
			 * Got a program check in msch. Try without
			 * the concurrent sense bit the next time.
			 */
			sch->schib.pmcw.csense = 0;
		if (ret == 0) {
			if (cio_update_schib(sch)) {
				ret = -ENODEV;
				break;
			}
			if (sch->schib.pmcw.ena)
				break;
		}
		if (ret == -EBUSY) {
			sch->config.csense = 0;
		} else if (ret == -EBUSY) {
			struct irb irb;
			if (tsch(sch->schid, &irb) != 0)
				break;
		}
		} else
			break;
	}
	sprintf (dbf_txt, "ret:%d", ret);
	CIO_TRACE_EVENT (2, dbf_txt);
@@ -436,7 +468,6 @@ EXPORT_SYMBOL_GPL(cio_enable_subchannel);
int cio_disable_subchannel(struct subchannel *sch)
{
	char dbf_txt[15];
	int retry;
	int ret;

	CIO_TRACE_EVENT (2, "dissch");
@@ -454,27 +485,9 @@ int cio_disable_subchannel(struct subchannel *sch)
		 */
		return -EBUSY;

	for (retry = 5, ret = 0; retry > 0; retry--) {
		sch->schib.pmcw.ena = 0;
		ret = cio_modify(sch);
		if (ret == -ENODEV)
			break;
		if (ret == -EBUSY)
			/*
			 * The subchannel is busy or status pending.
			 * We'll disable when the next interrupt was delivered
			 * via the state machine.
			 */
			break;
		if (ret == 0) {
			if (cio_update_schib(sch)) {
				ret = -ENODEV;
				break;
			}
			if (!sch->schib.pmcw.ena)
				break;
		}
	}
	sch->config.ena = 0;
	ret = cio_commit_config(sch);

	sprintf (dbf_txt, "ret:%d", ret);
	CIO_TRACE_EVENT (2, dbf_txt);
	return ret;
@@ -817,10 +830,9 @@ cio_probe_console(void)
	 * enable console I/O-interrupt subclass
	 */
	isc_register(CONSOLE_ISC);
	console_subchannel.schib.pmcw.isc = CONSOLE_ISC;
	console_subchannel.schib.pmcw.intparm =
		(u32)(addr_t)&console_subchannel;
	ret = cio_modify(&console_subchannel);
	console_subchannel.config.isc = CONSOLE_ISC;
	console_subchannel.config.intparm = (u32)(addr_t)&console_subchannel;
	ret = cio_commit_config(&console_subchannel);
	if (ret) {
		isc_unregister(CONSOLE_ISC);
		console_subchannel_in_use = 0;
@@ -832,8 +844,8 @@ cio_probe_console(void)
void
cio_release_console(void)
{
	console_subchannel.schib.pmcw.intparm = 0;
	cio_modify(&console_subchannel);
	console_subchannel.config.intparm = 0;
	cio_commit_config(&console_subchannel);
	isc_unregister(CONSOLE_ISC);
	console_subchannel_in_use = 0;
}
+15 −1
Original line number Diff line number Diff line
@@ -45,6 +45,19 @@ struct pmcw {
				/*  ... in an operand exception.       */
} __attribute__ ((packed));

/* Target SCHIB configuration. */
struct schib_config {
	u64 mba;
	u32 intparm;
	u16 mbi;
	u32 isc:3;
	u32 ena:1;
	u32 mme:2;
	u32 mp:1;
	u32 csense:1;
	u32 mbfc:1;
} __attribute__ ((packed));

/*
 * subchannel information block
 */
@@ -83,6 +96,7 @@ struct subchannel {
	struct css_driver *driver;
	void *private; /* private per subchannel type data */
	struct work_struct work;
	struct schib_config config;
} __attribute__ ((aligned(8)));

#define IO_INTERRUPT_TYPE	   0 /* I/O interrupt type */
@@ -101,8 +115,8 @@ extern int cio_start_key (struct subchannel *, struct ccw1 *, __u8, __u8);
extern int cio_cancel (struct subchannel *);
extern int cio_set_options (struct subchannel *, int);
extern int cio_get_options (struct subchannel *);
extern int cio_modify (struct subchannel *);
extern int cio_update_schib(struct subchannel *sch);
extern int cio_commit_config(struct subchannel *sch);

int cio_tm_start_key(struct subchannel *sch, struct tcw *tcw, u8 lpm, u8 key);
int cio_tm_intrg(struct subchannel *sch);
+8 −47
Original line number Diff line number Diff line
@@ -185,58 +185,19 @@ static inline void cmf_activate(void *area, unsigned int onoff)
static int set_schib(struct ccw_device *cdev, u32 mme, int mbfc,
		     unsigned long address)
{
	int ret;
	int retry;
	struct subchannel *sch;
	struct schib *schib;

	sch = to_subchannel(cdev->dev.parent);
	schib = &sch->schib;
	/* msch can silently fail, so do it again if necessary */
	for (retry = 0; retry < 3; retry++) {
		/* prepare schib */
		if (cio_update_schib(sch))
			return -ENODEV;
		schib->pmcw.mme  = mme;
		schib->pmcw.mbfc = mbfc;

	sch->config.mme = mme;
	sch->config.mbfc = mbfc;
	/* address can be either a block address or a block index */
	if (mbfc)
			schib->mba = address;
		sch->config.mba = address;
	else
			schib->pmcw.mbi = address;

		/* try to submit it */
		switch(ret = msch_err(sch->schid, schib)) {
			case 0:
				break;
			case 1:
			case 2: /* in I/O or status pending */
				ret = -EBUSY;
				break;
			case 3: /* subchannel is no longer valid */
				ret = -ENODEV;
				break;
			default: /* msch caught an exception */
				ret = -EINVAL;
				break;
		}
		if (cio_update_schib(sch))
			return -ENODEV;

		if (ret)
			break;

		/* check if it worked */
		if (schib->pmcw.mme  == mme &&
		    schib->pmcw.mbfc == mbfc &&
		    (mbfc ? (schib->mba == address)
			  : (schib->pmcw.mbi == address)))
			return 0;
		sch->config.mbi = address;

		ret = -EINVAL;
	}

	return ret;
	return cio_commit_config(sch);
}

struct set_schib_struct {
+2 −2
Original line number Diff line number Diff line
@@ -128,8 +128,8 @@ css_free_subchannel(struct subchannel *sch)
{
	if (sch) {
		/* Reset intparm to zeroes. */
		sch->schib.pmcw.intparm = 0;
		cio_modify(sch);
		sch->config.intparm = 0;
		cio_commit_config(sch);
		kfree(sch->lock);
		kfree(sch);
	}
+18 −18
Original line number Diff line number Diff line
@@ -1020,8 +1020,8 @@ static void ccw_device_call_sch_unregister(struct work_struct *work)
	sch = to_subchannel(cdev->dev.parent);
	css_sch_device_unregister(sch);
	/* Reset intparm to zeroes. */
	sch->schib.pmcw.intparm = 0;
	cio_modify(sch);
	sch->config.intparm = 0;
	cio_commit_config(sch);
	/* Release cdev reference for workqueue processing.*/
	put_device(&cdev->dev);
	/* Release subchannel reference for local processing. */
@@ -1148,8 +1148,8 @@ static void ccw_device_move_to_sch(struct work_struct *work)
		spin_unlock_irq(former_parent->lock);
		css_sch_device_unregister(former_parent);
		/* Reset intparm to zeroes. */
		former_parent->schib.pmcw.intparm = 0;
		cio_modify(former_parent);
		former_parent->config.intparm = 0;
		cio_commit_config(former_parent);
	}
	sch_attach_device(sch, cdev);
out:
@@ -1170,6 +1170,14 @@ static void io_subchannel_irq(struct subchannel *sch)
		dev_fsm_event(cdev, DEV_EVENT_INTERRUPT);
}

void io_subchannel_init_config(struct subchannel *sch)
{
	memset(&sch->config, 0, sizeof(sch->config));
	sch->config.csense = 1;
	if ((sch->lpm & (sch->lpm - 1)) != 0)
		sch->config.mp = 1;
}

static void io_subchannel_init_fields(struct subchannel *sch)
{
	if (cio_is_console(sch->schid))
@@ -1184,16 +1192,8 @@ static void io_subchannel_init_fields(struct subchannel *sch)
		      sch->schib.pmcw.dev, sch->schid.ssid,
		      sch->schid.sch_no, sch->schib.pmcw.pim,
		      sch->schib.pmcw.pam, sch->schib.pmcw.pom);
	/* Initially set up some fields in the pmcw. */
	sch->schib.pmcw.ena = 0;
	sch->schib.pmcw.csense = 1;	/* concurrent sense */
	if ((sch->lpm & (sch->lpm - 1)) != 0)
		sch->schib.pmcw.mp = 1; /* multipath mode */
	/* clean up possible residual cmf stuff */
	sch->schib.pmcw.mme = 0;
	sch->schib.pmcw.mbfc = 0;
	sch->schib.pmcw.mbi = 0;
	sch->schib.mba = 0;

	io_subchannel_init_config(sch);
}

static void io_subchannel_do_unreg(struct work_struct *work)
@@ -1203,8 +1203,8 @@ static void io_subchannel_do_unreg(struct work_struct *work)
	sch = container_of(work, struct subchannel, work);
	css_sch_device_unregister(sch);
	/* Reset intparm to zeroes. */
	sch->schib.pmcw.intparm = 0;
	cio_modify(sch);
	sch->config.intparm = 0;
	cio_commit_config(sch);
	put_device(&sch->dev);
}

@@ -1680,8 +1680,8 @@ static int io_subchannel_sch_event(struct subchannel *sch, int slow)
		spin_lock_irqsave(sch->lock, flags);

		/* Reset intparm to zeroes. */
		sch->schib.pmcw.intparm = 0;
		cio_modify(sch);
		sch->config.intparm = 0;
		cio_commit_config(sch);
		break;
	case REPROBE:
		ccw_device_trigger_reprobe(cdev);
Loading