Commit f3eb5384 authored by Stefan Weinhuber's avatar Stefan Weinhuber Committed by Martin Schwidefsky
Browse files

[S390] dasd: add High Performance FICON support



To support High Performance FICON, the DASD device driver has to
translate I/O requests into the new transport mode control words (TCW)
instead of the traditional (command mode) CCW requests.

Signed-off-by: default avatarStefan Weinhuber <wein@de.ibm.com>
Signed-off-by: default avatarMartin Schwidefsky <schwidefsky@de.ibm.com>
parent b44b0ab3
Loading
Loading
Loading
Loading
+5 −12
Original line number Diff line number Diff line
@@ -44,24 +44,18 @@ idal_is_needed(void *vaddr, unsigned int length)
/*
 * Return the number of idal words needed for an address/length pair.
 */
static inline unsigned int
idal_nr_words(void *vaddr, unsigned int length)
static inline unsigned int idal_nr_words(void *vaddr, unsigned int length)
{
#ifdef __s390x__
	if (idal_is_needed(vaddr, length))
	return ((__pa(vaddr) & (IDA_BLOCK_SIZE-1)) + length +
		(IDA_BLOCK_SIZE-1)) >> IDA_SIZE_LOG;
#endif
	return 0;
}

/*
 * Create the list of idal words for an address/length pair.
 */
static inline unsigned long *
idal_create_words(unsigned long *idaws, void *vaddr, unsigned int length)
static inline unsigned long *idal_create_words(unsigned long *idaws,
					       void *vaddr, unsigned int length)
{
#ifdef __s390x__
	unsigned long paddr;
	unsigned int cidaw;

@@ -74,7 +68,6 @@ idal_create_words(unsigned long *idaws, void *vaddr, unsigned int length)
		paddr += IDA_BLOCK_SIZE;
		*idaws++ = paddr;
	}
#endif
	return idaws;
}

+58 −12
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@
#include <asm/ebcdic.h>
#include <asm/idals.h>
#include <asm/todclk.h>
#include <asm/itcw.h>

/* This is ugly... */
#define PRINTK_HEADER "dasd:"
@@ -852,8 +853,13 @@ int dasd_start_IO(struct dasd_ccw_req *cqr)
	cqr->startclk = get_clock();
	cqr->starttime = jiffies;
	cqr->retries--;
	rc = ccw_device_start(device->cdev, cqr->cpaddr, (long) cqr,
			      cqr->lpm, 0);
	if (cqr->cpmode == 1) {
		rc = ccw_device_tm_start(device->cdev, cqr->cpaddr,
					 (long) cqr, cqr->lpm);
	} else {
		rc = ccw_device_start(device->cdev, cqr->cpaddr,
				      (long) cqr, cqr->lpm, 0);
	}
	switch (rc) {
	case 0:
		cqr->status = DASD_CQR_IN_IO;
@@ -881,9 +887,12 @@ int dasd_start_IO(struct dasd_ccw_req *cqr)
			      " retry on all pathes");
		break;
	case -ENODEV:
		DBF_DEV_EVENT(DBF_DEBUG, device, "%s",
			      "start_IO: -ENODEV device gone, retry");
		break;
	case -EIO:
		DBF_DEV_EVENT(DBF_ERR, device, "%s",
			      "start_IO: device gone, retry");
			      "start_IO: -EIO device gone, retry");
		break;
	default:
		DEV_MESSAGE(KERN_ERR, device,
@@ -1015,9 +1024,9 @@ void dasd_int_handler(struct ccw_device *cdev, unsigned long intparm,

	/* check for unsolicited interrupts */
	cqr = (struct dasd_ccw_req *) intparm;
	if (!cqr || ((irb->scsw.cmd.cc == 1) &&
		     (irb->scsw.cmd.fctl & SCSW_FCTL_START_FUNC) &&
		     (irb->scsw.cmd.stctl & SCSW_STCTL_STATUS_PEND))) {
	if (!cqr || ((scsw_cc(&irb->scsw) == 1) &&
		     (scsw_fctl(&irb->scsw) & SCSW_FCTL_START_FUNC) &&
		     (scsw_stctl(&irb->scsw) & SCSW_STCTL_STATUS_PEND))) {
		if (cqr && cqr->status == DASD_CQR_IN_IO)
			cqr->status = DASD_CQR_QUEUED;
		device = dasd_device_from_cdev_locked(cdev);
@@ -1040,7 +1049,7 @@ void dasd_int_handler(struct ccw_device *cdev, unsigned long intparm,

	/* Check for clear pending */
	if (cqr->status == DASD_CQR_CLEAR_PENDING &&
	    irb->scsw.cmd.fctl & SCSW_FCTL_CLEAR_FUNC) {
	    scsw_fctl(&irb->scsw) & SCSW_FCTL_CLEAR_FUNC) {
		cqr->status = DASD_CQR_CLEARED;
		dasd_device_clear_timer(device);
		wake_up(&dasd_flush_wq);
@@ -1059,8 +1068,8 @@ void dasd_int_handler(struct ccw_device *cdev, unsigned long intparm,
		      ((irb->scsw.cmd.cstat << 8) | irb->scsw.cmd.dstat), cqr);
	next = NULL;
	expires = 0;
	if (irb->scsw.cmd.dstat == (DEV_STAT_CHN_END | DEV_STAT_DEV_END) &&
	    irb->scsw.cmd.cstat == 0 && !irb->esw.esw0.erw.cons) {
	if (scsw_dstat(&irb->scsw) == (DEV_STAT_CHN_END | DEV_STAT_DEV_END) &&
	    scsw_cstat(&irb->scsw) == 0) {
		/* request was completed successfully */
		cqr->status = DASD_CQR_SUCCESS;
		cqr->stopclk = now;
@@ -1991,8 +2000,11 @@ static void dasd_setup_queue(struct dasd_block *block)
	blk_queue_max_sectors(block->request_queue, max);
	blk_queue_max_phys_segments(block->request_queue, -1L);
	blk_queue_max_hw_segments(block->request_queue, -1L);
	blk_queue_max_segment_size(block->request_queue, -1L);
	blk_queue_segment_boundary(block->request_queue, -1L);
	/* with page sized segments we can translate each segement into
	 * one idaw/tidaw
	 */
	blk_queue_max_segment_size(block->request_queue, PAGE_SIZE);
	blk_queue_segment_boundary(block->request_queue, PAGE_SIZE - 1);
	blk_queue_ordered(block->request_queue, QUEUE_ORDERED_DRAIN, NULL);
}

@@ -2432,6 +2444,40 @@ int dasd_generic_read_dev_chars(struct dasd_device *device, char *magic,
}
EXPORT_SYMBOL_GPL(dasd_generic_read_dev_chars);

/*
 *   In command mode and transport mode we need to look for sense
 *   data in different places. The sense data itself is allways
 *   an array of 32 bytes, so we can unify the sense data access
 *   for both modes.
 */
char *dasd_get_sense(struct irb *irb)
{
	struct tsb *tsb = NULL;
	char *sense = NULL;

	if (scsw_is_tm(&irb->scsw) && (irb->scsw.tm.fcxs == 0x01)) {
		if (irb->scsw.tm.tcw)
			tsb = tcw_get_tsb((struct tcw *)(unsigned long)
					  irb->scsw.tm.tcw);
		if (tsb && tsb->length == 64 && tsb->flags)
			switch (tsb->flags & 0x07) {
			case 1:	/* tsa_iostat */
				sense = tsb->tsa.iostat.sense;
				break;
			case 2: /* tsa_ddpc */
				sense = tsb->tsa.ddpc.sense;
				break;
			default:
				/* currently we don't use interrogate data */
				break;
			}
	} else if (irb->esw.esw0.erw.cons) {
		sense = irb->ecw;
	}
	return sense;
}
EXPORT_SYMBOL_GPL(dasd_get_sense);

static int __init dasd_init(void)
{
	int rc;
+94 −47
Original line number Diff line number Diff line
@@ -1561,6 +1561,13 @@ dasd_3990_erp_action_1B_32(struct dasd_ccw_req * default_erp, char *sense)
		cqr = cqr->refers;
	}

	if (scsw_is_tm(&cqr->irb.scsw)) {
		DBF_DEV_EVENT(DBF_WARNING, device, "%s",
			      "32 bit sense, action 1B is not defined"
			      " in transport mode - just retry");
		return default_erp;
	}

	/* for imprecise ending just do default erp */
	if (sense[1] & 0x01) {

@@ -1599,7 +1606,7 @@ dasd_3990_erp_action_1B_32(struct dasd_ccw_req * default_erp, char *sense)
	oldccw = cqr->cpaddr;
	if (oldccw->cmd_code == DASD_ECKD_CCW_PFX) {
		PFX_data = cqr->data;
		memcpy(DE_data, &PFX_data->define_extend,
		memcpy(DE_data, &PFX_data->define_extent,
		       sizeof(struct DE_eckd_data));
	} else
		memcpy(DE_data, cqr->data, sizeof(struct DE_eckd_data));
@@ -1712,6 +1719,13 @@ dasd_3990_update_1B(struct dasd_ccw_req * previous_erp, char *sense)
		cqr = cqr->refers;
	}

	if (scsw_is_tm(&cqr->irb.scsw)) {
		DBF_DEV_EVENT(DBF_WARNING, device, "%s",
			      "32 bit sense, action 1B, update,"
			      " in transport mode - just retry");
		return previous_erp;
	}

	/* for imprecise ending just do default erp */
	if (sense[1] & 0x01) {

@@ -2171,7 +2185,7 @@ dasd_3990_erp_control_check(struct dasd_ccw_req *erp)
{
	struct dasd_device *device = erp->startdev;

	if (erp->refers->irb.scsw.cmd.cstat & (SCHN_STAT_INTF_CTRL_CHK
	if (scsw_cstat(&erp->refers->irb.scsw) & (SCHN_STAT_INTF_CTRL_CHK
					   | SCHN_STAT_CHN_CTRL_CHK)) {
		DEV_MESSAGE(KERN_DEBUG, device, "%s",
			    "channel or interface control check");
@@ -2197,17 +2211,19 @@ dasd_3990_erp_inspect(struct dasd_ccw_req * erp)
{

	struct dasd_ccw_req *erp_new = NULL;
	/* sense data are located in the refers record of the */
	/* already set up new ERP !			      */
	char *sense = erp->refers->irb.ecw;
	char *sense;

	/* if this problem occured on an alias retry on base */
	erp_new = dasd_3990_erp_inspect_alias(erp);
	if (erp_new)
		return erp_new;

	/* check if no concurrent sens is available */
	if (!erp->refers->irb.esw.esw0.erw.cons)
	/* sense data are located in the refers record of the
	 * already set up new ERP !
	 * check if concurrent sens is available
	 */
	sense = dasd_get_sense(&erp->refers->irb);
	if (!sense)
		erp_new = dasd_3990_erp_control_check(erp);
	/* distinguish between 24 and 32 byte sense data */
	else if (sense[27] & DASD_SENSE_BIT_0) {
@@ -2231,7 +2247,11 @@ dasd_3990_erp_inspect(struct dasd_ccw_req * erp)
 * DESCRIPTION
 *   This funtion adds an additional request block (ERP) to the head of
 *   the given cqr (or erp).
 *   This erp is initialized as an default erp (retry TIC)
 *   For a command mode cqr the erp is initialized as an default erp
 *   (retry TIC).
 *   For transport mode we make a copy of the original TCW (points to
 *   the original TCCB, TIDALs, etc.) but give it a fresh
 *   TSB so the original sense data will not be changed.
 *
 * PARAMETER
 *   cqr		head of the current ERP-chain (or single cqr if
@@ -2239,17 +2259,27 @@ dasd_3990_erp_inspect(struct dasd_ccw_req * erp)
 * RETURN VALUES
 *   erp		pointer to new ERP-chain head
 */
static struct dasd_ccw_req *
dasd_3990_erp_add_erp(struct dasd_ccw_req * cqr)
static struct dasd_ccw_req *dasd_3990_erp_add_erp(struct dasd_ccw_req *cqr)
{

	struct dasd_device *device = cqr->startdev;
	struct ccw1 *ccw;

	/* allocate additional request block */
	struct dasd_ccw_req *erp;
	int cplength, datasize;
	struct tcw *tcw;
	struct tsb *tsb;

	erp = dasd_alloc_erp_request((char *) &cqr->magic, 2, 0, device);
	if (cqr->cpmode == 1) {
		cplength = 0;
		datasize = sizeof(struct tcw) + sizeof(struct tsb);
	} else {
		cplength = 2;
		datasize = 0;
	}

	/* allocate additional request block */
	erp = dasd_alloc_erp_request((char *) &cqr->magic,
				     cplength, datasize, device);
	if (IS_ERR(erp)) {
                if (cqr->retries <= 0) {
		        DEV_MESSAGE(KERN_ERR, device, "%s",
@@ -2266,6 +2296,15 @@ dasd_3990_erp_add_erp(struct dasd_ccw_req * cqr)
		return cqr;
	}

	if (cqr->cpmode == 1) {
		/* make a shallow copy of the original tcw but set new tsb */
		erp->cpmode = 1;
		erp->cpaddr = erp->data;
		tcw = erp->data;
		tsb = (struct tsb *) &tcw[1];
		*tcw = *((struct tcw *)cqr->cpaddr);
		tcw->tsb = (long)tsb;
	} else {
		/* initialize request with default TIC to current ERP/CQR */
		ccw = erp->cpaddr;
		ccw->cmd_code = CCW_CMD_NOOP;
@@ -2273,6 +2312,8 @@ dasd_3990_erp_add_erp(struct dasd_ccw_req * cqr)
		ccw++;
		ccw->cmd_code = CCW_CMD_TIC;
		ccw->cda      = (long)(cqr->cpaddr);
	}

	erp->function = dasd_3990_erp_add_erp;
	erp->refers   = cqr;
	erp->startdev = device;
@@ -2282,7 +2323,6 @@ dasd_3990_erp_add_erp(struct dasd_ccw_req * cqr)
	erp->expires  = 0;
	erp->retries  = 256;
	erp->buildclk = get_clock();

	erp->status = DASD_CQR_FILLED;

	return erp;
@@ -2340,28 +2380,33 @@ dasd_3990_erp_additional_erp(struct dasd_ccw_req * cqr)
 *   match		'boolean' for match found
 *			returns 1 if match found, otherwise 0.
 */
static int
dasd_3990_erp_error_match(struct dasd_ccw_req *cqr1, struct dasd_ccw_req *cqr2)
static int dasd_3990_erp_error_match(struct dasd_ccw_req *cqr1,
				     struct dasd_ccw_req *cqr2)
{
	char *sense1, *sense2;

	if (cqr1->startdev != cqr2->startdev)
		return 0;

	if (cqr1->irb.esw.esw0.erw.cons != cqr2->irb.esw.esw0.erw.cons)
		return 0;
	sense1 = dasd_get_sense(&cqr1->irb);
	sense2 = dasd_get_sense(&cqr2->irb);

	if ((cqr1->irb.esw.esw0.erw.cons == 0) &&
	    (cqr2->irb.esw.esw0.erw.cons == 0))	{
		if ((cqr1->irb.scsw.cmd.cstat & (SCHN_STAT_INTF_CTRL_CHK |
	/* one request has sense data, the other not -> no match, return 0 */
	if (!sense1 != !sense2)
		return 0;
	/* no sense data in both cases -> check cstat for IFCC */
	if (!sense1 && !sense2)	{
		if ((scsw_cstat(&cqr1->irb.scsw) & (SCHN_STAT_INTF_CTRL_CHK |
						    SCHN_STAT_CHN_CTRL_CHK)) ==
		    (cqr2->irb.scsw.cmd.cstat & (SCHN_STAT_INTF_CTRL_CHK |
		    (scsw_cstat(&cqr2->irb.scsw) & (SCHN_STAT_INTF_CTRL_CHK |
						    SCHN_STAT_CHN_CTRL_CHK)))
			return 1; /* match with ifcc*/
	}
	/* check sense data; byte 0-2,25,27 */
	if (!((memcmp (cqr1->irb.ecw, cqr2->irb.ecw, 3) == 0) &&
	      (cqr1->irb.ecw[27] == cqr2->irb.ecw[27]) &&
	      (cqr1->irb.ecw[25] == cqr2->irb.ecw[25]))) {
	if (!(sense1 && sense2 &&
	      (memcmp(sense1, sense2, 3) == 0) &&
	      (sense1[27] == sense2[27]) &&
	      (sense1[25] == sense2[25]))) {

		return 0;	/* sense doesn't match */
	}
@@ -2434,7 +2479,7 @@ dasd_3990_erp_further_erp(struct dasd_ccw_req *erp)
{

	struct dasd_device *device = erp->startdev;
	char *sense = erp->irb.ecw;
	char *sense = dasd_get_sense(&erp->irb);

	/* check for 24 byte sense ERP */
	if ((erp->function == dasd_3990_erp_bus_out) ||
@@ -2449,7 +2494,7 @@ dasd_3990_erp_further_erp(struct dasd_ccw_req *erp)
		/* prepare erp for retry on different channel path */
		erp = dasd_3990_erp_action_1(erp);

		if (!(sense[2] & DASD_SENSE_BIT_0)) {
		if (sense && !(sense[2] & DASD_SENSE_BIT_0)) {

			/* issue a Diagnostic Control command with an
			 * Inhibit Write subcommand */
@@ -2479,10 +2524,11 @@ dasd_3990_erp_further_erp(struct dasd_ccw_req *erp)
		}

		/* check for 32 byte sense ERP */
	} else if ((erp->function == dasd_3990_erp_compound_retry) ||
	} else if (sense &&
		   ((erp->function == dasd_3990_erp_compound_retry) ||
		    (erp->function == dasd_3990_erp_compound_path) ||
		    (erp->function == dasd_3990_erp_compound_code) ||
		   (erp->function == dasd_3990_erp_compound_config)) {
		    (erp->function == dasd_3990_erp_compound_config))) {

		erp = dasd_3990_erp_compound(erp, sense);

@@ -2548,18 +2594,19 @@ dasd_3990_erp_handle_match_erp(struct dasd_ccw_req *erp_head,

	if (erp->retries > 0) {

		char *sense = erp->refers->irb.ecw;
		char *sense = dasd_get_sense(&erp->refers->irb);

		/* check for special retries */
		if (erp->function == dasd_3990_erp_action_4) {
		if (sense && erp->function == dasd_3990_erp_action_4) {

			erp = dasd_3990_erp_action_4(erp, sense);

		} else if (erp->function == dasd_3990_erp_action_1B_32) {
		} else if (sense &&
			   erp->function == dasd_3990_erp_action_1B_32) {

			erp = dasd_3990_update_1B(erp, sense);

		} else if (erp->function == dasd_3990_erp_int_req) {
		} else if (sense && erp->function == dasd_3990_erp_int_req) {

			erp = dasd_3990_erp_int_req(erp);

@@ -2622,8 +2669,8 @@ dasd_3990_erp_action(struct dasd_ccw_req * cqr)
	}

	/* double-check if current erp/cqr was successful */
	if ((cqr->irb.scsw.cmd.cstat == 0x00) &&
	    (cqr->irb.scsw.cmd.dstat ==
	if ((scsw_cstat(&cqr->irb.scsw) == 0x00) &&
	    (scsw_dstat(&cqr->irb.scsw) ==
	     (DEV_STAT_CHN_END | DEV_STAT_DEV_END))) {

		DEV_MESSAGE(KERN_DEBUG, device,
+18 −7
Original line number Diff line number Diff line
@@ -646,14 +646,16 @@ static int reset_summary_unit_check(struct alias_lcu *lcu,
{
	struct dasd_ccw_req *cqr;
	int rc = 0;
	struct ccw1 *ccw;

	cqr = lcu->rsu_cqr;
	strncpy((char *) &cqr->magic, "ECKD", 4);
	ASCEBC((char *) &cqr->magic, 4);
	cqr->cpaddr->cmd_code = DASD_ECKD_CCW_RSCK;
	cqr->cpaddr->flags = 0 ;
	cqr->cpaddr->count = 16;
	cqr->cpaddr->cda = (__u32)(addr_t) cqr->data;
	ccw = cqr->cpaddr;
	ccw->cmd_code = DASD_ECKD_CCW_RSCK;
	ccw->flags = 0 ;
	ccw->count = 16;
	ccw->cda = (__u32)(addr_t) cqr->data;
	((char *)cqr->data)[0] = reason;

	clear_bit(DASD_CQR_FLAGS_USE_ERP, &cqr->flags);
@@ -855,12 +857,21 @@ void dasd_alias_handle_summary_unit_check(struct dasd_device *device,
	struct alias_lcu *lcu;
	char reason;
	struct dasd_eckd_private *private;
	char *sense;

	private = (struct dasd_eckd_private *) device->private;

	reason = irb->ecw[8];
	DEV_MESSAGE(KERN_WARNING, device, "%s %x",
	sense = dasd_get_sense(irb);
	if (sense) {
		reason = sense[8];
		DBF_DEV_EVENT(DBF_NOTICE, device, "%s %x",
			    "eckd handle summary unit check: reason", reason);
	} else {
		DBF_DEV_EVENT(DBF_WARNING, device, "%s",
			    "eckd handle summary unit check:"
			    " no reason code available");
		return;
	}

	lcu = private->lcu;
	if (!lcu) {
+7 −0
Original line number Diff line number Diff line
@@ -67,6 +67,8 @@ int dasd_probeonly = 0; /* is true, when probeonly mode is active */
int dasd_autodetect = 0;	/* is true, when autodetection is active */
int dasd_nopav = 0;		/* is true, when PAV is disabled */
EXPORT_SYMBOL_GPL(dasd_nopav);
int dasd_nofcx;			/* disable High Performance Ficon */
EXPORT_SYMBOL_GPL(dasd_nofcx);

/*
 * char *dasd[] is intended to hold the ranges supplied by the dasd= statement
@@ -272,6 +274,11 @@ dasd_parse_keyword( char *parsestring ) {
		}
		return residual_str;
	}
	if (strncmp("nofcx", parsestring, length) == 0) {
		dasd_nofcx = 1;
		MESSAGE(KERN_INFO, "%s", "disable High Performance Ficon");
		return residual_str;
	}
	if (strncmp("fixedbuffers", parsestring, length) == 0) {
		if (dasd_page_cache)
			return residual_str;
Loading