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

Commit c820de39 authored by Cornelia Huck's avatar Cornelia Huck Committed by Heiko Carstens
Browse files

[S390] cio: Rework css driver.



Rework the css driver methods to provide sane callbacks for
subchannels of all types.

As a bonus, this cleans up and simplyfies the machine check
handling for I/O subchannels a lot.

Signed-off-by: default avatarCornelia Huck <cornelia.huck@de.ibm.com>
Signed-off-by: default avatarMartin Schwidefsky <schwidefsky@de.ibm.com>
Signed-off-by: default avatarHeiko Carstens <heiko.carstens@de.ibm.com>
parent 7e9db9ea
Loading
Loading
Loading
Loading
+20 −0
Original line number Diff line number Diff line
@@ -496,6 +496,26 @@ void chp_process_crw(int id, int status)
		chsc_chp_offline(chpid);
}

int chp_ssd_get_mask(struct chsc_ssd_info *ssd, struct res_acc_data *data)
{
	int i;
	int mask;

	for (i = 0; i < 8; i++) {
		mask = 0x80 >> i;
		if (!(ssd->path_mask & mask))
			continue;
		if (!chp_id_is_equal(&ssd->chpid[i], &data->chpid))
			continue;
		if ((ssd->fla_valid_mask & mask) &&
		    ((ssd->fla[i] & data->fla_mask) != data->fla))
			continue;
		return mask;
	}
	return 0;
}
EXPORT_SYMBOL_GPL(chp_ssd_get_mask);

static inline int info_bit_num(struct chp_id id)
{
	return id.id + id.cssid * (__MAX_CHPID + 1);
+12 −1
Original line number Diff line number Diff line
@@ -19,6 +19,17 @@
#define CHP_STATUS_RESERVED		2
#define CHP_STATUS_NOT_RECOGNIZED	3

#define CHP_ONLINE 0
#define CHP_OFFLINE 1
#define CHP_VARY_ON 2
#define CHP_VARY_OFF 3

struct res_acc_data {
	struct chp_id chpid;
	u32 fla_mask;
	u16 fla;
};

static inline int chp_test_bit(u8 *bitmap, int num)
{
	int byte = num >> 3;
@@ -50,5 +61,5 @@ int chp_new(struct chp_id chpid);
void chp_cfg_schedule(struct chp_id chpid, int configure);
void chp_cfg_cancel_deconfigure(struct chp_id chpid);
int chp_info_get_status(struct chp_id chpid);

int chp_ssd_get_mask(struct chsc_ssd_info *, struct res_acc_data *);
#endif /* S390_CHP_H */
+16 −202
Original line number Diff line number Diff line
@@ -2,8 +2,7 @@
 *  drivers/s390/cio/chsc.c
 *   S/390 common I/O routines -- channel subsystem call
 *
 *    Copyright (C) 1999-2002 IBM Deutschland Entwicklung GmbH,
 *			      IBM Corporation
 *    Copyright IBM Corp. 1999,2008
 *    Author(s): Ingo Adlung (adlung@de.ibm.com)
 *		 Cornelia Huck (cornelia.huck@de.ibm.com)
 *		 Arnd Bergmann (arndb@de.ibm.com)
@@ -127,77 +126,12 @@ int chsc_get_ssd_info(struct subchannel_id schid, struct chsc_ssd_info *ssd)
	return ret;
}

static int check_for_io_on_path(struct subchannel *sch, int mask)
{
	int cc;

	cc = stsch(sch->schid, &sch->schib);
	if (cc)
		return 0;
	if (sch->schib.scsw.actl && sch->schib.pmcw.lpum == mask)
		return 1;
	return 0;
}

static void terminate_internal_io(struct subchannel *sch)
{
	if (cio_clear(sch)) {
		/* Recheck device in case clear failed. */
		sch->lpm = 0;
		if (device_trigger_verify(sch) != 0)
			css_schedule_eval(sch->schid);
		return;
	}
	/* Request retry of internal operation. */
	device_set_intretry(sch);
	/* Call handler. */
	if (sch->driver && sch->driver->termination)
		sch->driver->termination(sch);
}

static int s390_subchannel_remove_chpid(struct subchannel *sch, void *data)
{
	int j;
	int mask;
	struct chp_id *chpid = data;
	struct schib schib;

	for (j = 0; j < 8; j++) {
		mask = 0x80 >> j;
		if ((sch->schib.pmcw.pim & mask) &&
		    (sch->schib.pmcw.chpid[j] == chpid->id))
			break;
	}
	if (j >= 8)
		return 0;

	spin_lock_irq(sch->lock);

	stsch(sch->schid, &schib);
	if (!css_sch_is_valid(&schib))
		goto out_unreg;
	memcpy(&sch->schib, &schib, sizeof(struct schib));
	/* Check for single path devices. */
	if (sch->schib.pmcw.pim == 0x80)
	if (sch->driver && sch->driver->chp_event)
		if (sch->driver->chp_event(sch, data, CHP_OFFLINE) != 0)
			goto out_unreg;

	if (check_for_io_on_path(sch, mask)) {
		if (device_is_online(sch))
			device_kill_io(sch);
		else {
			terminate_internal_io(sch);
			/* Re-start path verification. */
			if (sch->driver && sch->driver->verify)
				sch->driver->verify(sch);
		}
	} else {
		/* trigger path verification. */
		if (sch->driver && sch->driver->verify)
			sch->driver->verify(sch);
		else if (sch->lpm == mask)
			goto out_unreg;
	}

	spin_unlock_irq(sch->lock);
	return 0;

@@ -242,53 +176,11 @@ static int s390_process_res_acc_new_sch(struct subchannel_id schid, void *data)
	return 0;
}

struct res_acc_data {
	struct chp_id chpid;
	u32 fla_mask;
	u16 fla;
};

static int get_res_chpid_mask(struct chsc_ssd_info *ssd,
			      struct res_acc_data *data)
{
	int i;
	int mask;

	for (i = 0; i < 8; i++) {
		mask = 0x80 >> i;
		if (!(ssd->path_mask & mask))
			continue;
		if (!chp_id_is_equal(&ssd->chpid[i], &data->chpid))
			continue;
		if ((ssd->fla_valid_mask & mask) &&
		    ((ssd->fla[i] & data->fla_mask) != data->fla))
			continue;
		return mask;
	}
	return 0;
}

static int __s390_process_res_acc(struct subchannel *sch, void *data)
{
	int chp_mask, old_lpm;
	struct res_acc_data *res_data = data;

	spin_lock_irq(sch->lock);
	chp_mask = get_res_chpid_mask(&sch->ssd_info, res_data);
	if (chp_mask == 0)
		goto out;
	if (stsch(sch->schid, &sch->schib))
		goto out;
	old_lpm = sch->lpm;
	sch->lpm = ((sch->schib.pmcw.pim &
		     sch->schib.pmcw.pam &
		     sch->schib.pmcw.pom)
		    | chp_mask) & sch->opm;
	if (!old_lpm && sch->lpm)
		device_trigger_reprobe(sch);
	else if (sch->driver && sch->driver->verify)
		sch->driver->verify(sch);
out:
	if (sch->driver && sch->driver->chp_event)
		sch->driver->chp_event(sch, data, CHP_ONLINE);
	spin_unlock_irq(sch->lock);

	return 0;
@@ -509,114 +401,36 @@ void chsc_process_crw(void)
	} while (sei_area->flags & 0x80);
}

static int __chp_add_new_sch(struct subchannel_id schid, void *data)
{
	struct schib schib;

	if (stsch_err(schid, &schib))
		/* We're through */
		return -ENXIO;

	/* Put it on the slow path. */
	css_schedule_eval(schid);
	return 0;
}


static int __chp_add(struct subchannel *sch, void *data)
{
	int i, mask;
	struct chp_id *chpid = data;

	spin_lock_irq(sch->lock);
	for (i=0; i<8; i++) {
		mask = 0x80 >> i;
		if ((sch->schib.pmcw.pim & mask) &&
		    (sch->schib.pmcw.chpid[i] == chpid->id))
			break;
	}
	if (i==8) {
		spin_unlock_irq(sch->lock);
		return 0;
	}
	if (stsch(sch->schid, &sch->schib)) {
		spin_unlock_irq(sch->lock);
		css_schedule_eval(sch->schid);
		return 0;
	}
	sch->lpm = ((sch->schib.pmcw.pim &
		     sch->schib.pmcw.pam &
		     sch->schib.pmcw.pom)
		    | mask) & sch->opm;

	if (sch->driver && sch->driver->verify)
		sch->driver->verify(sch);

	spin_unlock_irq(sch->lock);

	return 0;
}

void chsc_chp_online(struct chp_id chpid)
{
	char dbf_txt[15];
	struct res_acc_data res_data;

	sprintf(dbf_txt, "cadd%x.%02x", chpid.cssid, chpid.id);
	CIO_TRACE_EVENT(2, dbf_txt);

	if (chp_get_status(chpid) != 0) {
		memset(&res_data, 0, sizeof(struct res_acc_data));
		res_data.chpid = chpid;
		/* Wait until previous actions have settled. */
		css_wait_for_slow_path();
		for_each_subchannel_staged(__chp_add, __chp_add_new_sch,
					   &chpid);
		for_each_subchannel_staged(__s390_process_res_acc, NULL,
					   &res_data);
	}
}

static void __s390_subchannel_vary_chpid(struct subchannel *sch,
					 struct chp_id chpid, int on)
{
	int chp, old_lpm;
	int mask;
	unsigned long flags;
	struct res_acc_data res_data;

	memset(&res_data, 0, sizeof(struct res_acc_data));
	res_data.chpid = chpid;
	spin_lock_irqsave(sch->lock, flags);
	old_lpm = sch->lpm;
	for (chp = 0; chp < 8; chp++) {
		mask = 0x80 >> chp;
		if (!(sch->ssd_info.path_mask & mask))
			continue;
		if (!chp_id_is_equal(&sch->ssd_info.chpid[chp], &chpid))
			continue;

		if (on) {
			sch->opm |= mask;
			sch->lpm |= mask;
			if (!old_lpm)
				device_trigger_reprobe(sch);
			else if (sch->driver && sch->driver->verify)
				sch->driver->verify(sch);
			break;
		}
		sch->opm &= ~mask;
		sch->lpm &= ~mask;
		if (check_for_io_on_path(sch, mask)) {
			if (device_is_online(sch))
				/* Path verification is done after killing. */
				device_kill_io(sch);
			else {
				/* Kill and retry internal I/O. */
				terminate_internal_io(sch);
				/* Re-start path verification. */
				if (sch->driver && sch->driver->verify)
					sch->driver->verify(sch);
			}
		} else if (!sch->lpm) {
			if (device_trigger_verify(sch) != 0)
				css_schedule_eval(sch->schid);
		} else if (sch->driver && sch->driver->verify)
			sch->driver->verify(sch);
		break;
	}
	if (sch->driver && sch->driver->chp_event)
		sch->driver->chp_event(sch, &res_data,
				       on ? CHP_VARY_ON : CHP_VARY_OFF);
	spin_unlock_irqrestore(sch->lock, flags);
}

+1 −0
Original line number Diff line number Diff line
@@ -564,6 +564,7 @@ int cio_validate_subchannel(struct subchannel *sch, struct subchannel_id schid)
	}
	/* Copy subchannel type from path management control word. */
	sch->st = sch->schib.pmcw.st;

	switch (sch->st) {
	case SUBCHANNEL_TYPE_IO:
		err = cio_validate_io_subchannel(sch);
+16 −107
Original line number Diff line number Diff line
@@ -283,7 +283,7 @@ static int css_register_subchannel(struct subchannel *sch)
	return ret;
}

static int css_probe_device(struct subchannel_id schid)
int css_probe_device(struct subchannel_id schid)
{
	int ret;
	struct subchannel *sch;
@@ -330,112 +330,6 @@ int css_sch_is_valid(struct schib *schib)
}
EXPORT_SYMBOL_GPL(css_sch_is_valid);

static int css_get_subchannel_status(struct subchannel *sch)
{
	struct schib schib;

	if (stsch(sch->schid, &schib))
		return CIO_GONE;
	if (!css_sch_is_valid(&schib))
		return CIO_GONE;
	if (sch->schib.pmcw.dnv && (schib.pmcw.dev != sch->schib.pmcw.dev))
		return CIO_REVALIDATE;
	if (!sch->lpm)
		return CIO_NO_PATH;
	return CIO_OPER;
}

static int css_evaluate_known_subchannel(struct subchannel *sch, int slow)
{
	int event, ret, disc;
	unsigned long flags;
	enum { NONE, UNREGISTER, UNREGISTER_PROBE, REPROBE } action;

	spin_lock_irqsave(sch->lock, flags);
	disc = device_is_disconnected(sch);
	if (disc && slow) {
		/* Disconnected devices are evaluated directly only.*/
		spin_unlock_irqrestore(sch->lock, flags);
		return 0;
	}
	/* No interrupt after machine check - kill pending timers. */
	device_kill_pending_timer(sch);
	if (!disc && !slow) {
		/* Non-disconnected devices are evaluated on the slow path. */
		spin_unlock_irqrestore(sch->lock, flags);
		return -EAGAIN;
	}
	event = css_get_subchannel_status(sch);
	CIO_MSG_EVENT(4, "Evaluating schid 0.%x.%04x, event %d, %s, %s path.\n",
		      sch->schid.ssid, sch->schid.sch_no, event,
		      disc ? "disconnected" : "normal",
		      slow ? "slow" : "fast");
	/* Analyze subchannel status. */
	action = NONE;
	switch (event) {
	case CIO_NO_PATH:
		if (disc) {
			/* Check if paths have become available. */
			action = REPROBE;
			break;
		}
		/* fall through */
	case CIO_GONE:
		/* Prevent unwanted effects when opening lock. */
		cio_disable_subchannel(sch);
		device_set_disconnected(sch);
		/* Ask driver what to do with device. */
		action = UNREGISTER;
		if (sch->driver && sch->driver->notify) {
			spin_unlock_irqrestore(sch->lock, flags);
			ret = sch->driver->notify(sch, event);
			spin_lock_irqsave(sch->lock, flags);
			if (ret)
				action = NONE;
		}
		break;
	case CIO_REVALIDATE:
		/* Device will be removed, so no notify necessary. */
		if (disc)
			/* Reprobe because immediate unregister might block. */
			action = REPROBE;
		else
			action = UNREGISTER_PROBE;
		break;
	case CIO_OPER:
		if (disc)
			/* Get device operational again. */
			action = REPROBE;
		break;
	}
	/* Perform action. */
	ret = 0;
	switch (action) {
	case UNREGISTER:
	case UNREGISTER_PROBE:
		/* Unregister device (will use subchannel lock). */
		spin_unlock_irqrestore(sch->lock, flags);
		css_sch_device_unregister(sch);
		spin_lock_irqsave(sch->lock, flags);

		/* Reset intparm to zeroes. */
		sch->schib.pmcw.intparm = 0;
		cio_modify(sch);
		break;
	case REPROBE:
		device_trigger_reprobe(sch);
		break;
	default:
		break;
	}
	spin_unlock_irqrestore(sch->lock, flags);
	/* Probe if necessary. */
	if (action == UNREGISTER_PROBE)
		ret = css_probe_device(sch->schid);

	return ret;
}

static int css_evaluate_new_subchannel(struct subchannel_id schid, int slow)
{
	struct schib schib;
@@ -454,6 +348,21 @@ static int css_evaluate_new_subchannel(struct subchannel_id schid, int slow)
	return css_probe_device(schid);
}

static int css_evaluate_known_subchannel(struct subchannel *sch, int slow)
{
	int ret = 0;

	if (sch->driver) {
		if (sch->driver->sch_event)
			ret = sch->driver->sch_event(sch, slow);
		else
			dev_dbg(&sch->dev,
				"Got subchannel machine check but "
				"no sch_event handler provided.\n");
	}
	return ret;
}

static void css_evaluate_subchannel(struct subchannel_id schid, int slow)
{
	struct subchannel *sch;
Loading