device_pgid.c 15.7 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
1 2 3 4 5
/*
 * drivers/s390/cio/device_pgid.c
 *
 *    Copyright (C) 2002 IBM Deutschland Entwicklung GmbH,
 *			 IBM Corporation
6
 *    Author(s): Cornelia Huck (cornelia.huck@de.ibm.com)
Linus Torvalds's avatar
Linus Torvalds committed
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 *		 Martin Schwidefsky (schwidefsky@de.ibm.com)
 *
 * Path Group ID functions.
 */

#include <linux/module.h>
#include <linux/init.h>

#include <asm/ccwdev.h>
#include <asm/cio.h>
#include <asm/delay.h>
#include <asm/lowcore.h>

#include "cio.h"
#include "cio_debug.h"
#include "css.h"
#include "device.h"
24
#include "ioasm.h"
Linus Torvalds's avatar
Linus Torvalds committed
25

26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
/*
 * Helper function called from interrupt context to decide whether an
 * operation should be tried again.
 */
static int __ccw_device_should_retry(struct scsw *scsw)
{
	/* CC is only valid if start function bit is set. */
	if ((scsw->fctl & SCSW_FCTL_START_FUNC) && scsw->cc == 1)
		return 1;
	/* No more activity. For sense and set PGID we stubbornly try again. */
	if (!scsw->actl)
		return 1;
	return 0;
}

Linus Torvalds's avatar
Linus Torvalds committed
41 42 43 44 45 46 47 48 49 50
/*
 * Start Sense Path Group ID helper function. Used in ccw_device_recog
 * and ccw_device_sense_pgid.
 */
static int
__ccw_device_sense_pgid_start(struct ccw_device *cdev)
{
	struct subchannel *sch;
	struct ccw1 *ccw;
	int ret;
51
	int i;
Linus Torvalds's avatar
Linus Torvalds committed
52 53

	sch = to_subchannel(cdev->dev.parent);
54 55 56 57 58
	/* Return if we already checked on all paths. */
	if (cdev->private->imask == 0)
		return (sch->lpm == 0) ? -ENODEV : -EACCES;
	i = 8 - ffs(cdev->private->imask);

Linus Torvalds's avatar
Linus Torvalds committed
59 60 61 62 63 64 65 66 67 68 69 70
	/* Setup sense path group id channel program. */
	ccw = cdev->private->iccws;
	ccw->cmd_code = CCW_CMD_SENSE_PGID;
	ccw->count = sizeof (struct pgid);
	ccw->flags = CCW_FLAG_SLI;

	/* Reset device status. */
	memset(&cdev->private->irb, 0, sizeof(struct irb));
	/* Try on every path. */
	ret = -ENODEV;
	while (cdev->private->imask != 0) {
		/* Try every path multiple times. */
71
		ccw->cda = (__u32) __pa (&cdev->private->pgid[i]);
Linus Torvalds's avatar
Linus Torvalds committed
72 73
		if (cdev->private->iretry > 0) {
			cdev->private->iretry--;
74 75
			/* Reset internal retry indication. */
			cdev->private->flags.intretry = 0;
Linus Torvalds's avatar
Linus Torvalds committed
76 77 78 79 80 81
			ret = cio_start (sch, cdev->private->iccws, 
					 cdev->private->imask);
			/* ret is 0, -EBUSY, -EACCES or -ENODEV */
			if (ret != -EACCES)
				return ret;
			CIO_MSG_EVENT(2, "SNID - Device %04x on Subchannel "
82
				      "0.%x.%04x, lpm %02X, became 'not "
Linus Torvalds's avatar
Linus Torvalds committed
83
				      "operational'\n",
84 85
				      cdev->private->dev_id.devno,
				      sch->schid.ssid,
86
				      sch->schid.sch_no, cdev->private->imask);
Linus Torvalds's avatar
Linus Torvalds committed
87 88 89 90

		}
		cdev->private->imask >>= 1;
		cdev->private->iretry = 5;
91
		i++;
Linus Torvalds's avatar
Linus Torvalds committed
92
	}
93

Linus Torvalds's avatar
Linus Torvalds committed
94 95 96 97 98 99 100 101
	return ret;
}

void
ccw_device_sense_pgid_start(struct ccw_device *cdev)
{
	int ret;

102 103 104
	/* Set a timeout of 60s */
	ccw_device_set_timeout(cdev, 60*HZ);

Linus Torvalds's avatar
Linus Torvalds committed
105 106 107
	cdev->private->state = DEV_STATE_SENSE_PGID;
	cdev->private->imask = 0x80;
	cdev->private->iretry = 5;
108
	memset (&cdev->private->pgid, 0, sizeof (cdev->private->pgid));
Linus Torvalds's avatar
Linus Torvalds committed
109 110 111 112 113 114 115 116 117 118 119 120 121 122
	ret = __ccw_device_sense_pgid_start(cdev);
	if (ret && ret != -EBUSY)
		ccw_device_sense_pgid_done(cdev, ret);
}

/*
 * Called from interrupt context to check if a valid answer
 * to Sense Path Group ID was received.
 */
static int
__ccw_device_check_sense_pgid(struct ccw_device *cdev)
{
	struct subchannel *sch;
	struct irb *irb;
123
	int i;
Linus Torvalds's avatar
Linus Torvalds committed
124 125 126

	sch = to_subchannel(cdev->dev.parent);
	irb = &cdev->private->irb;
127 128 129 130 131 132
	if (irb->scsw.fctl & (SCSW_FCTL_HALT_FUNC | SCSW_FCTL_CLEAR_FUNC)) {
		/* Retry Sense PGID if requested. */
		if (cdev->private->flags.intretry) {
			cdev->private->flags.intretry = 0;
			return -EAGAIN;
		}
Linus Torvalds's avatar
Linus Torvalds committed
133
		return -ETIME;
134
	}
Linus Torvalds's avatar
Linus Torvalds committed
135 136 137 138 139 140 141 142 143
	if (irb->esw.esw0.erw.cons &&
	    (irb->ecw[0]&(SNS0_CMD_REJECT|SNS0_INTERVENTION_REQ))) {
		/*
		 * If the device doesn't support the Sense Path Group ID
		 *  command further retries wouldn't help ...
		 */
		return -EOPNOTSUPP;
	}
	if (irb->esw.esw0.erw.cons) {
144
		CIO_MSG_EVENT(2, "SNID - device 0.%x.%04x, unit check, "
Linus Torvalds's avatar
Linus Torvalds committed
145 146
			      "lpum %02X, cnt %02d, sns : "
			      "%02X%02X%02X%02X %02X%02X%02X%02X ...\n",
147 148
			      cdev->private->dev_id.ssid,
			      cdev->private->dev_id.devno,
Linus Torvalds's avatar
Linus Torvalds committed
149 150 151 152 153 154 155 156 157
			      irb->esw.esw0.sublog.lpum,
			      irb->esw.esw0.erw.scnt,
			      irb->ecw[0], irb->ecw[1],
			      irb->ecw[2], irb->ecw[3],
			      irb->ecw[4], irb->ecw[5],
			      irb->ecw[6], irb->ecw[7]);
		return -EAGAIN;
	}
	if (irb->scsw.cc == 3) {
158 159
		CIO_MSG_EVENT(2, "SNID - Device %04x on Subchannel 0.%x.%04x,"
			      " lpm %02X, became 'not operational'\n",
160
			      cdev->private->dev_id.devno, sch->schid.ssid,
161
			      sch->schid.sch_no, sch->orb.lpm);
Linus Torvalds's avatar
Linus Torvalds committed
162 163
		return -EACCES;
	}
164 165
	i = 8 - ffs(cdev->private->imask);
	if (cdev->private->pgid[i].inf.ps.state2 == SNID_STATE2_RESVD_ELSE) {
166
		CIO_MSG_EVENT(2, "SNID - Device %04x on Subchannel 0.%x.%04x "
Linus Torvalds's avatar
Linus Torvalds committed
167
			      "is reserved by someone else\n",
168
			      cdev->private->dev_id.devno, sch->schid.ssid,
169
			      sch->schid.sch_no);
Linus Torvalds's avatar
Linus Torvalds committed
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
		return -EUSERS;
	}
	return 0;
}

/*
 * Got interrupt for Sense Path Group ID.
 */
void
ccw_device_sense_pgid_irq(struct ccw_device *cdev, enum dev_event dev_event)
{
	struct subchannel *sch;
	struct irb *irb;
	int ret;

	irb = (struct irb *) __LC_IRB;
186

Linus Torvalds's avatar
Linus Torvalds committed
187 188
	if (irb->scsw.stctl ==
	    (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) {
189
		if (__ccw_device_should_retry(&irb->scsw)) {
Linus Torvalds's avatar
Linus Torvalds committed
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
			ret = __ccw_device_sense_pgid_start(cdev);
			if (ret && ret != -EBUSY)
				ccw_device_sense_pgid_done(cdev, ret);
		}
		return;
	}
	if (ccw_device_accumulate_and_sense(cdev, irb) != 0)
		return;
	sch = to_subchannel(cdev->dev.parent);
	ret = __ccw_device_check_sense_pgid(cdev);
	memset(&cdev->private->irb, 0, sizeof(struct irb));
	switch (ret) {
	/* 0, -ETIME, -EOPNOTSUPP, -EAGAIN, -EACCES or -EUSERS */
	case -EOPNOTSUPP:	/* Sense Path Group ID not supported */
		ccw_device_sense_pgid_done(cdev, -EOPNOTSUPP);
		break;
	case -ETIME:		/* Sense path group id stopped by timeout. */
		ccw_device_sense_pgid_done(cdev, -ETIME);
		break;
	case -EACCES:		/* channel is not operational. */
		sch->lpm &= ~cdev->private->imask;
211 212
		/* Fall through. */
	case 0:			/* Sense Path Group ID successful. */
Linus Torvalds's avatar
Linus Torvalds committed
213 214 215 216 217 218
		cdev->private->imask >>= 1;
		cdev->private->iretry = 5;
		/* Fall through. */
	case -EAGAIN:		/* Try again. */
		ret = __ccw_device_sense_pgid_start(cdev);
		if (ret != 0 && ret != -EBUSY)
219
			ccw_device_sense_pgid_done(cdev, ret);
Linus Torvalds's avatar
Linus Torvalds committed
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
		break;
	case -EUSERS:		/* device is reserved for someone else. */
		ccw_device_sense_pgid_done(cdev, -EUSERS);
		break;
	}
}

/*
 * Path Group ID helper function.
 */
static int
__ccw_device_do_pgid(struct ccw_device *cdev, __u8 func)
{
	struct subchannel *sch;
	struct ccw1 *ccw;
	int ret;

	sch = to_subchannel(cdev->dev.parent);

	/* Setup sense path group id channel program. */
240
	cdev->private->pgid[0].inf.fc = func;
Linus Torvalds's avatar
Linus Torvalds committed
241 242
	ccw = cdev->private->iccws;
	if (!cdev->private->flags.pgid_single) {
243
		cdev->private->pgid[0].inf.fc |= SPID_FUNC_MULTI_PATH;
Linus Torvalds's avatar
Linus Torvalds committed
244 245 246 247 248 249
		ccw->cmd_code = CCW_CMD_SUSPEND_RECONN;
		ccw->cda = 0;
		ccw->count = 0;
		ccw->flags = CCW_FLAG_SLI | CCW_FLAG_CC;
		ccw++;
	} else
250
		cdev->private->pgid[0].inf.fc |= SPID_FUNC_SINGLE_PATH;
Linus Torvalds's avatar
Linus Torvalds committed
251 252

	ccw->cmd_code = CCW_CMD_SET_PGID;
253
	ccw->cda = (__u32) __pa (&cdev->private->pgid[0]);
Linus Torvalds's avatar
Linus Torvalds committed
254 255 256 257 258 259 260
	ccw->count = sizeof (struct pgid);
	ccw->flags = CCW_FLAG_SLI;

	/* Reset device status. */
	memset(&cdev->private->irb, 0, sizeof(struct irb));

	/* Try multiple times. */
261
	ret = -EACCES;
Linus Torvalds's avatar
Linus Torvalds committed
262 263
	if (cdev->private->iretry > 0) {
		cdev->private->iretry--;
264 265
		/* Reset internal retry indication. */
		cdev->private->flags.intretry = 0;
Linus Torvalds's avatar
Linus Torvalds committed
266 267
		ret = cio_start (sch, cdev->private->iccws,
				 cdev->private->imask);
268 269 270
		/* We expect an interrupt in case of success or busy
		 * indication. */
		if ((ret == 0) || (ret == -EBUSY))
Linus Torvalds's avatar
Linus Torvalds committed
271 272
			return ret;
	}
273
	/* PGID command failed on this path. */
Linus Torvalds's avatar
Linus Torvalds committed
274
	CIO_MSG_EVENT(2, "SPID - Device %04x on Subchannel "
275
		      "0.%x.%04x, lpm %02X, became 'not operational'\n",
276
		      cdev->private->dev_id.devno, sch->schid.ssid,
277
		      sch->schid.sch_no, cdev->private->imask);
Linus Torvalds's avatar
Linus Torvalds committed
278 279 280
	return ret;
}

281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302
/*
 * Helper function to send a nop ccw down a path.
 */
static int __ccw_device_do_nop(struct ccw_device *cdev)
{
	struct subchannel *sch;
	struct ccw1 *ccw;
	int ret;

	sch = to_subchannel(cdev->dev.parent);

	/* Setup nop channel program. */
	ccw = cdev->private->iccws;
	ccw->cmd_code = CCW_CMD_NOOP;
	ccw->cda = 0;
	ccw->count = 0;
	ccw->flags = CCW_FLAG_SLI;

	/* Reset device status. */
	memset(&cdev->private->irb, 0, sizeof(struct irb));

	/* Try multiple times. */
303
	ret = -EACCES;
304 305
	if (cdev->private->iretry > 0) {
		cdev->private->iretry--;
306 307
		/* Reset internal retry indication. */
		cdev->private->flags.intretry = 0;
308 309
		ret = cio_start (sch, cdev->private->iccws,
				 cdev->private->imask);
310 311 312
		/* We expect an interrupt in case of success or busy
		 * indication. */
		if ((ret == 0) || (ret == -EBUSY))
313 314
			return ret;
	}
315
	/* nop command failed on this path. */
316 317
	CIO_MSG_EVENT(2, "NOP - Device %04x on Subchannel "
		      "0.%x.%04x, lpm %02X, became 'not operational'\n",
318
		      cdev->private->dev_id.devno, sch->schid.ssid,
319 320 321 322 323
		      sch->schid.sch_no, cdev->private->imask);
	return ret;
}


Linus Torvalds's avatar
Linus Torvalds committed
324 325 326 327 328 329 330 331 332 333 334 335
/*
 * Called from interrupt context to check if a valid answer
 * to Set Path Group ID was received.
 */
static int
__ccw_device_check_pgid(struct ccw_device *cdev)
{
	struct subchannel *sch;
	struct irb *irb;

	sch = to_subchannel(cdev->dev.parent);
	irb = &cdev->private->irb;
336 337 338 339 340 341
	if (irb->scsw.fctl & (SCSW_FCTL_HALT_FUNC | SCSW_FCTL_CLEAR_FUNC)) {
		/* Retry Set PGID if requested. */
		if (cdev->private->flags.intretry) {
			cdev->private->flags.intretry = 0;
			return -EAGAIN;
		}
Linus Torvalds's avatar
Linus Torvalds committed
342
		return -ETIME;
343
	}
Linus Torvalds's avatar
Linus Torvalds committed
344 345 346 347
	if (irb->esw.esw0.erw.cons) {
		if (irb->ecw[0] & SNS0_CMD_REJECT)
			return -EOPNOTSUPP;
		/* Hmm, whatever happened, try again. */
348 349
		CIO_MSG_EVENT(2, "SPID - device 0.%x.%04x, unit check, "
			      "cnt %02d, "
Linus Torvalds's avatar
Linus Torvalds committed
350
			      "sns : %02X%02X%02X%02X %02X%02X%02X%02X ...\n",
351 352 353
			      cdev->private->dev_id.ssid,
			      cdev->private->dev_id.devno,
			      irb->esw.esw0.erw.scnt,
Linus Torvalds's avatar
Linus Torvalds committed
354 355 356 357 358 359 360
			      irb->ecw[0], irb->ecw[1],
			      irb->ecw[2], irb->ecw[3],
			      irb->ecw[4], irb->ecw[5],
			      irb->ecw[6], irb->ecw[7]);
		return -EAGAIN;
	}
	if (irb->scsw.cc == 3) {
361 362
		CIO_MSG_EVENT(2, "SPID - Device %04x on Subchannel 0.%x.%04x,"
			      " lpm %02X, became 'not operational'\n",
363
			      cdev->private->dev_id.devno, sch->schid.ssid,
364
			      sch->schid.sch_no, cdev->private->imask);
Linus Torvalds's avatar
Linus Torvalds committed
365 366 367 368 369
		return -EACCES;
	}
	return 0;
}

370 371 372 373 374 375 376 377 378 379 380
/*
 * Called from interrupt context to check the path status after a nop has
 * been send.
 */
static int __ccw_device_check_nop(struct ccw_device *cdev)
{
	struct subchannel *sch;
	struct irb *irb;

	sch = to_subchannel(cdev->dev.parent);
	irb = &cdev->private->irb;
381 382 383 384 385 386
	if (irb->scsw.fctl & (SCSW_FCTL_HALT_FUNC | SCSW_FCTL_CLEAR_FUNC)) {
		/* Retry NOP if requested. */
		if (cdev->private->flags.intretry) {
			cdev->private->flags.intretry = 0;
			return -EAGAIN;
		}
387
		return -ETIME;
388
	}
389 390 391
	if (irb->scsw.cc == 3) {
		CIO_MSG_EVENT(2, "NOP - Device %04x on Subchannel 0.%x.%04x,"
			      " lpm %02X, became 'not operational'\n",
392
			      cdev->private->dev_id.devno, sch->schid.ssid,
393 394 395 396 397 398
			      sch->schid.sch_no, cdev->private->imask);
		return -EACCES;
	}
	return 0;
}

Linus Torvalds's avatar
Linus Torvalds committed
399 400 401 402
static void
__ccw_device_verify_start(struct ccw_device *cdev)
{
	struct subchannel *sch;
403
	__u8 func;
Linus Torvalds's avatar
Linus Torvalds committed
404 405 406
	int ret;

	sch = to_subchannel(cdev->dev.parent);
407 408 409 410 411 412
	/* Repeat for all paths. */
	for (; cdev->private->imask; cdev->private->imask >>= 1,
				     cdev->private->iretry = 5) {
		if ((cdev->private->imask & sch->schib.pmcw.pam) == 0)
			/* Path not available, try next. */
			continue;
413
		if (cdev->private->options.pgroup) {
414 415 416 417
			if (sch->opm & cdev->private->imask)
				func = SPID_FUNC_ESTABLISH;
			else
				func = SPID_FUNC_RESIGN;
418 419 420
			ret = __ccw_device_do_pgid(cdev, func);
		} else
			ret = __ccw_device_do_nop(cdev);
421 422
		/* We expect an interrupt in case of success or busy
		 * indication. */
Linus Torvalds's avatar
Linus Torvalds committed
423 424
		if (ret == 0 || ret == -EBUSY)
			return;
425
		/* Permanent path failure, try next. */
Linus Torvalds's avatar
Linus Torvalds committed
426
	}
427 428
	/* Done with all paths. */
	ccw_device_verify_done(cdev, (sch->vpm != 0) ? 0 : -ENODEV);
Linus Torvalds's avatar
Linus Torvalds committed
429 430 431 432 433 434 435 436 437 438 439 440 441
}
		
/*
 * Got interrupt for Set Path Group ID.
 */
void
ccw_device_verify_irq(struct ccw_device *cdev, enum dev_event dev_event)
{
	struct subchannel *sch;
	struct irb *irb;
	int ret;

	irb = (struct irb *) __LC_IRB;
442

Linus Torvalds's avatar
Linus Torvalds committed
443 444
	if (irb->scsw.stctl ==
	    (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) {
445
		if (__ccw_device_should_retry(&irb->scsw))
Linus Torvalds's avatar
Linus Torvalds committed
446 447 448 449 450 451
			__ccw_device_verify_start(cdev);
		return;
	}
	if (ccw_device_accumulate_and_sense(cdev, irb) != 0)
		return;
	sch = to_subchannel(cdev->dev.parent);
452 453 454 455
	if (cdev->private->options.pgroup)
		ret = __ccw_device_check_pgid(cdev);
	else
		ret = __ccw_device_check_nop(cdev);
Linus Torvalds's avatar
Linus Torvalds committed
456
	memset(&cdev->private->irb, 0, sizeof(struct irb));
457

Linus Torvalds's avatar
Linus Torvalds committed
458 459 460
	switch (ret) {
	/* 0, -ETIME, -EAGAIN, -EOPNOTSUPP or -EACCES */
	case 0:
461 462 463 464
		/* Path verification ccw finished successfully, update lpm. */
		sch->vpm |= sch->opm & cdev->private->imask;
		/* Go on with next path. */
		cdev->private->imask >>= 1;
Linus Torvalds's avatar
Linus Torvalds committed
465 466 467 468 469 470 471 472
		cdev->private->iretry = 5;
		__ccw_device_verify_start(cdev);
		break;
	case -EOPNOTSUPP:
		/*
		 * One of those strange devices which claim to be able
		 * to do multipathing but not for Set Path Group ID.
		 */
473 474 475 476
		if (cdev->private->flags.pgid_single)
			cdev->private->options.pgroup = 0;
		else
			cdev->private->flags.pgid_single = 1;
477 478 479 480
		/* Retry */
		sch->vpm = 0;
		cdev->private->imask = 0x80;
		cdev->private->iretry = 5;
Linus Torvalds's avatar
Linus Torvalds committed
481 482 483 484 485 486 487 488
		/* fall through. */
	case -EAGAIN:		/* Try again. */
		__ccw_device_verify_start(cdev);
		break;
	case -ETIME:		/* Set path group id stopped by timeout. */
		ccw_device_verify_done(cdev, -ETIME);
		break;
	case -EACCES:		/* channel is not operational. */
489
		cdev->private->imask >>= 1;
Linus Torvalds's avatar
Linus Torvalds committed
490 491 492 493 494 495 496 497 498
		cdev->private->iretry = 5;
		__ccw_device_verify_start(cdev);
		break;
	}
}

void
ccw_device_verify_start(struct ccw_device *cdev)
{
499 500
	struct subchannel *sch = to_subchannel(cdev->dev.parent);

Linus Torvalds's avatar
Linus Torvalds committed
501
	cdev->private->flags.pgid_single = 0;
502
	cdev->private->imask = 0x80;
Linus Torvalds's avatar
Linus Torvalds committed
503
	cdev->private->iretry = 5;
504 505 506 507 508

	/* Start with empty vpm. */
	sch->vpm = 0;

	/* Get current pam. */
509
	if (stsch(sch->schid, &sch->schib)) {
510 511 512
		ccw_device_verify_done(cdev, -ENODEV);
		return;
	}
513 514
	/* After 60s path verification is considered to have failed. */
	ccw_device_set_timeout(cdev, 60*HZ);
Linus Torvalds's avatar
Linus Torvalds committed
515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533
	__ccw_device_verify_start(cdev);
}

static void
__ccw_device_disband_start(struct ccw_device *cdev)
{
	struct subchannel *sch;
	int ret;

	sch = to_subchannel(cdev->dev.parent);
	while (cdev->private->imask != 0) {
		if (sch->lpm & cdev->private->imask) {
			ret = __ccw_device_do_pgid(cdev, SPID_FUNC_DISBAND);
			if (ret == 0)
				return;
		}
		cdev->private->iretry = 5;
		cdev->private->imask >>= 1;
	}
534
	ccw_device_disband_done(cdev, (sch->lpm != 0) ? 0 : -ENODEV);
Linus Torvalds's avatar
Linus Torvalds committed
535 536 537 538 539 540 541 542 543 544 545 546 547
}

/*
 * Got interrupt for Unset Path Group ID.
 */
void
ccw_device_disband_irq(struct ccw_device *cdev, enum dev_event dev_event)
{
	struct subchannel *sch;
	struct irb *irb;
	int ret;

	irb = (struct irb *) __LC_IRB;
548

Linus Torvalds's avatar
Linus Torvalds committed
549 550
	if (irb->scsw.stctl ==
	    (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) {
551
		if (__ccw_device_should_retry(&irb->scsw))
Linus Torvalds's avatar
Linus Torvalds committed
552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588
			__ccw_device_disband_start(cdev);
		return;
	}
	if (ccw_device_accumulate_and_sense(cdev, irb) != 0)
		return;
	sch = to_subchannel(cdev->dev.parent);
	ret = __ccw_device_check_pgid(cdev);
	memset(&cdev->private->irb, 0, sizeof(struct irb));
	switch (ret) {
	/* 0, -ETIME, -EAGAIN, -EOPNOTSUPP or -EACCES */
	case 0:			/* disband successful. */
		ccw_device_disband_done(cdev, ret);
		break;
	case -EOPNOTSUPP:
		/*
		 * One of those strange devices which claim to be able
		 * to do multipathing but not for Unset Path Group ID.
		 */
		cdev->private->flags.pgid_single = 1;
		/* fall through. */
	case -EAGAIN:		/* Try again. */
		__ccw_device_disband_start(cdev);
		break;
	case -ETIME:		/* Set path group id stopped by timeout. */
		ccw_device_disband_done(cdev, -ETIME);
		break;
	case -EACCES:		/* channel is not operational. */
		cdev->private->imask >>= 1;
		cdev->private->iretry = 5;
		__ccw_device_disband_start(cdev);
		break;
	}
}

void
ccw_device_disband_start(struct ccw_device *cdev)
{
589 590 591
	/* After 60s disbanding is considered to have failed. */
	ccw_device_set_timeout(cdev, 60*HZ);

Linus Torvalds's avatar
Linus Torvalds committed
592 593 594 595 596
	cdev->private->flags.pgid_single = 0;
	cdev->private->iretry = 5;
	cdev->private->imask = 0x80;
	__ccw_device_disband_start(cdev);
}