162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Finite state machine for vfio-ccw device handling
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright IBM Corp. 2017
662306a36Sopenharmony_ci * Copyright Red Hat, Inc. 2019
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * Author(s): Dong Jia Shi <bjsdjshi@linux.vnet.ibm.com>
962306a36Sopenharmony_ci *            Cornelia Huck <cohuck@redhat.com>
1062306a36Sopenharmony_ci */
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include <linux/vfio.h>
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#include <asm/isc.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#include "ioasm.h"
1762306a36Sopenharmony_ci#include "vfio_ccw_private.h"
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_cistatic int fsm_io_helper(struct vfio_ccw_private *private)
2062306a36Sopenharmony_ci{
2162306a36Sopenharmony_ci	struct subchannel *sch = to_subchannel(private->vdev.dev->parent);
2262306a36Sopenharmony_ci	union orb *orb;
2362306a36Sopenharmony_ci	int ccode;
2462306a36Sopenharmony_ci	__u8 lpm;
2562306a36Sopenharmony_ci	unsigned long flags;
2662306a36Sopenharmony_ci	int ret;
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci	spin_lock_irqsave(sch->lock, flags);
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci	orb = cp_get_orb(&private->cp, sch);
3162306a36Sopenharmony_ci	if (!orb) {
3262306a36Sopenharmony_ci		ret = -EIO;
3362306a36Sopenharmony_ci		goto out;
3462306a36Sopenharmony_ci	}
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci	VFIO_CCW_TRACE_EVENT(5, "stIO");
3762306a36Sopenharmony_ci	VFIO_CCW_TRACE_EVENT(5, dev_name(&sch->dev));
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci	/* Issue "Start Subchannel" */
4062306a36Sopenharmony_ci	ccode = ssch(sch->schid, orb);
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci	VFIO_CCW_HEX_EVENT(5, &ccode, sizeof(ccode));
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	switch (ccode) {
4562306a36Sopenharmony_ci	case 0:
4662306a36Sopenharmony_ci		/*
4762306a36Sopenharmony_ci		 * Initialize device status information
4862306a36Sopenharmony_ci		 */
4962306a36Sopenharmony_ci		sch->schib.scsw.cmd.actl |= SCSW_ACTL_START_PEND;
5062306a36Sopenharmony_ci		ret = 0;
5162306a36Sopenharmony_ci		private->state = VFIO_CCW_STATE_CP_PENDING;
5262306a36Sopenharmony_ci		break;
5362306a36Sopenharmony_ci	case 1:		/* Status pending */
5462306a36Sopenharmony_ci	case 2:		/* Busy */
5562306a36Sopenharmony_ci		ret = -EBUSY;
5662306a36Sopenharmony_ci		break;
5762306a36Sopenharmony_ci	case 3:		/* Device/path not operational */
5862306a36Sopenharmony_ci	{
5962306a36Sopenharmony_ci		lpm = orb->cmd.lpm;
6062306a36Sopenharmony_ci		if (lpm != 0)
6162306a36Sopenharmony_ci			sch->lpm &= ~lpm;
6262306a36Sopenharmony_ci		else
6362306a36Sopenharmony_ci			sch->lpm = 0;
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci		if (cio_update_schib(sch))
6662306a36Sopenharmony_ci			ret = -ENODEV;
6762306a36Sopenharmony_ci		else
6862306a36Sopenharmony_ci			ret = sch->lpm ? -EACCES : -ENODEV;
6962306a36Sopenharmony_ci		break;
7062306a36Sopenharmony_ci	}
7162306a36Sopenharmony_ci	default:
7262306a36Sopenharmony_ci		ret = ccode;
7362306a36Sopenharmony_ci	}
7462306a36Sopenharmony_ciout:
7562306a36Sopenharmony_ci	spin_unlock_irqrestore(sch->lock, flags);
7662306a36Sopenharmony_ci	return ret;
7762306a36Sopenharmony_ci}
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_cistatic int fsm_do_halt(struct vfio_ccw_private *private)
8062306a36Sopenharmony_ci{
8162306a36Sopenharmony_ci	struct subchannel *sch = to_subchannel(private->vdev.dev->parent);
8262306a36Sopenharmony_ci	unsigned long flags;
8362306a36Sopenharmony_ci	int ccode;
8462306a36Sopenharmony_ci	int ret;
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	spin_lock_irqsave(sch->lock, flags);
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	VFIO_CCW_TRACE_EVENT(2, "haltIO");
8962306a36Sopenharmony_ci	VFIO_CCW_TRACE_EVENT(2, dev_name(&sch->dev));
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	/* Issue "Halt Subchannel" */
9262306a36Sopenharmony_ci	ccode = hsch(sch->schid);
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	VFIO_CCW_HEX_EVENT(2, &ccode, sizeof(ccode));
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	switch (ccode) {
9762306a36Sopenharmony_ci	case 0:
9862306a36Sopenharmony_ci		/*
9962306a36Sopenharmony_ci		 * Initialize device status information
10062306a36Sopenharmony_ci		 */
10162306a36Sopenharmony_ci		sch->schib.scsw.cmd.actl |= SCSW_ACTL_HALT_PEND;
10262306a36Sopenharmony_ci		ret = 0;
10362306a36Sopenharmony_ci		break;
10462306a36Sopenharmony_ci	case 1:		/* Status pending */
10562306a36Sopenharmony_ci	case 2:		/* Busy */
10662306a36Sopenharmony_ci		ret = -EBUSY;
10762306a36Sopenharmony_ci		break;
10862306a36Sopenharmony_ci	case 3:		/* Device not operational */
10962306a36Sopenharmony_ci		ret = -ENODEV;
11062306a36Sopenharmony_ci		break;
11162306a36Sopenharmony_ci	default:
11262306a36Sopenharmony_ci		ret = ccode;
11362306a36Sopenharmony_ci	}
11462306a36Sopenharmony_ci	spin_unlock_irqrestore(sch->lock, flags);
11562306a36Sopenharmony_ci	return ret;
11662306a36Sopenharmony_ci}
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_cistatic int fsm_do_clear(struct vfio_ccw_private *private)
11962306a36Sopenharmony_ci{
12062306a36Sopenharmony_ci	struct subchannel *sch = to_subchannel(private->vdev.dev->parent);
12162306a36Sopenharmony_ci	unsigned long flags;
12262306a36Sopenharmony_ci	int ccode;
12362306a36Sopenharmony_ci	int ret;
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	spin_lock_irqsave(sch->lock, flags);
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	VFIO_CCW_TRACE_EVENT(2, "clearIO");
12862306a36Sopenharmony_ci	VFIO_CCW_TRACE_EVENT(2, dev_name(&sch->dev));
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	/* Issue "Clear Subchannel" */
13162306a36Sopenharmony_ci	ccode = csch(sch->schid);
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	VFIO_CCW_HEX_EVENT(2, &ccode, sizeof(ccode));
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	switch (ccode) {
13662306a36Sopenharmony_ci	case 0:
13762306a36Sopenharmony_ci		/*
13862306a36Sopenharmony_ci		 * Initialize device status information
13962306a36Sopenharmony_ci		 */
14062306a36Sopenharmony_ci		sch->schib.scsw.cmd.actl = SCSW_ACTL_CLEAR_PEND;
14162306a36Sopenharmony_ci		/* TODO: check what else we might need to clear */
14262306a36Sopenharmony_ci		ret = 0;
14362306a36Sopenharmony_ci		break;
14462306a36Sopenharmony_ci	case 3:		/* Device not operational */
14562306a36Sopenharmony_ci		ret = -ENODEV;
14662306a36Sopenharmony_ci		break;
14762306a36Sopenharmony_ci	default:
14862306a36Sopenharmony_ci		ret = ccode;
14962306a36Sopenharmony_ci	}
15062306a36Sopenharmony_ci	spin_unlock_irqrestore(sch->lock, flags);
15162306a36Sopenharmony_ci	return ret;
15262306a36Sopenharmony_ci}
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_cistatic void fsm_notoper(struct vfio_ccw_private *private,
15562306a36Sopenharmony_ci			enum vfio_ccw_event event)
15662306a36Sopenharmony_ci{
15762306a36Sopenharmony_ci	struct subchannel *sch = to_subchannel(private->vdev.dev->parent);
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	VFIO_CCW_MSG_EVENT(2, "sch %x.%x.%04x: notoper event %x state %x\n",
16062306a36Sopenharmony_ci			   sch->schid.cssid,
16162306a36Sopenharmony_ci			   sch->schid.ssid,
16262306a36Sopenharmony_ci			   sch->schid.sch_no,
16362306a36Sopenharmony_ci			   event,
16462306a36Sopenharmony_ci			   private->state);
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	/*
16762306a36Sopenharmony_ci	 * TODO:
16862306a36Sopenharmony_ci	 * Probably we should send the machine check to the guest.
16962306a36Sopenharmony_ci	 */
17062306a36Sopenharmony_ci	css_sched_sch_todo(sch, SCH_TODO_UNREG);
17162306a36Sopenharmony_ci	private->state = VFIO_CCW_STATE_NOT_OPER;
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	/* This is usually handled during CLOSE event */
17462306a36Sopenharmony_ci	cp_free(&private->cp);
17562306a36Sopenharmony_ci}
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci/*
17862306a36Sopenharmony_ci * No operation action.
17962306a36Sopenharmony_ci */
18062306a36Sopenharmony_cistatic void fsm_nop(struct vfio_ccw_private *private,
18162306a36Sopenharmony_ci		    enum vfio_ccw_event event)
18262306a36Sopenharmony_ci{
18362306a36Sopenharmony_ci}
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_cistatic void fsm_io_error(struct vfio_ccw_private *private,
18662306a36Sopenharmony_ci			 enum vfio_ccw_event event)
18762306a36Sopenharmony_ci{
18862306a36Sopenharmony_ci	pr_err("vfio-ccw: FSM: I/O request from state:%d\n", private->state);
18962306a36Sopenharmony_ci	private->io_region->ret_code = -EIO;
19062306a36Sopenharmony_ci}
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_cistatic void fsm_io_busy(struct vfio_ccw_private *private,
19362306a36Sopenharmony_ci			enum vfio_ccw_event event)
19462306a36Sopenharmony_ci{
19562306a36Sopenharmony_ci	private->io_region->ret_code = -EBUSY;
19662306a36Sopenharmony_ci}
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_cistatic void fsm_io_retry(struct vfio_ccw_private *private,
19962306a36Sopenharmony_ci			 enum vfio_ccw_event event)
20062306a36Sopenharmony_ci{
20162306a36Sopenharmony_ci	private->io_region->ret_code = -EAGAIN;
20262306a36Sopenharmony_ci}
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_cistatic void fsm_async_error(struct vfio_ccw_private *private,
20562306a36Sopenharmony_ci			    enum vfio_ccw_event event)
20662306a36Sopenharmony_ci{
20762306a36Sopenharmony_ci	struct ccw_cmd_region *cmd_region = private->cmd_region;
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci	pr_err("vfio-ccw: FSM: %s request from state:%d\n",
21062306a36Sopenharmony_ci	       cmd_region->command == VFIO_CCW_ASYNC_CMD_HSCH ? "halt" :
21162306a36Sopenharmony_ci	       cmd_region->command == VFIO_CCW_ASYNC_CMD_CSCH ? "clear" :
21262306a36Sopenharmony_ci	       "<unknown>", private->state);
21362306a36Sopenharmony_ci	cmd_region->ret_code = -EIO;
21462306a36Sopenharmony_ci}
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_cistatic void fsm_async_retry(struct vfio_ccw_private *private,
21762306a36Sopenharmony_ci			    enum vfio_ccw_event event)
21862306a36Sopenharmony_ci{
21962306a36Sopenharmony_ci	private->cmd_region->ret_code = -EAGAIN;
22062306a36Sopenharmony_ci}
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_cistatic void fsm_disabled_irq(struct vfio_ccw_private *private,
22362306a36Sopenharmony_ci			     enum vfio_ccw_event event)
22462306a36Sopenharmony_ci{
22562306a36Sopenharmony_ci	struct subchannel *sch = to_subchannel(private->vdev.dev->parent);
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	/*
22862306a36Sopenharmony_ci	 * An interrupt in a disabled state means a previous disable was not
22962306a36Sopenharmony_ci	 * successful - should not happen, but we try to disable again.
23062306a36Sopenharmony_ci	 */
23162306a36Sopenharmony_ci	cio_disable_subchannel(sch);
23262306a36Sopenharmony_ci}
23362306a36Sopenharmony_ciinline struct subchannel_id get_schid(struct vfio_ccw_private *p)
23462306a36Sopenharmony_ci{
23562306a36Sopenharmony_ci	struct subchannel *sch = to_subchannel(p->vdev.dev->parent);
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci	return sch->schid;
23862306a36Sopenharmony_ci}
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci/*
24162306a36Sopenharmony_ci * Deal with the ccw command request from the userspace.
24262306a36Sopenharmony_ci */
24362306a36Sopenharmony_cistatic void fsm_io_request(struct vfio_ccw_private *private,
24462306a36Sopenharmony_ci			   enum vfio_ccw_event event)
24562306a36Sopenharmony_ci{
24662306a36Sopenharmony_ci	union orb *orb;
24762306a36Sopenharmony_ci	union scsw *scsw = &private->scsw;
24862306a36Sopenharmony_ci	struct ccw_io_region *io_region = private->io_region;
24962306a36Sopenharmony_ci	char *errstr = "request";
25062306a36Sopenharmony_ci	struct subchannel_id schid = get_schid(private);
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	private->state = VFIO_CCW_STATE_CP_PROCESSING;
25362306a36Sopenharmony_ci	memcpy(scsw, io_region->scsw_area, sizeof(*scsw));
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci	if (scsw->cmd.fctl & SCSW_FCTL_START_FUNC) {
25662306a36Sopenharmony_ci		orb = (union orb *)io_region->orb_area;
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci		/* Don't try to build a cp if transport mode is specified. */
25962306a36Sopenharmony_ci		if (orb->tm.b) {
26062306a36Sopenharmony_ci			io_region->ret_code = -EOPNOTSUPP;
26162306a36Sopenharmony_ci			VFIO_CCW_MSG_EVENT(2,
26262306a36Sopenharmony_ci					   "sch %x.%x.%04x: transport mode\n",
26362306a36Sopenharmony_ci					   schid.cssid,
26462306a36Sopenharmony_ci					   schid.ssid, schid.sch_no);
26562306a36Sopenharmony_ci			errstr = "transport mode";
26662306a36Sopenharmony_ci			goto err_out;
26762306a36Sopenharmony_ci		}
26862306a36Sopenharmony_ci		io_region->ret_code = cp_init(&private->cp, orb);
26962306a36Sopenharmony_ci		if (io_region->ret_code) {
27062306a36Sopenharmony_ci			VFIO_CCW_MSG_EVENT(2,
27162306a36Sopenharmony_ci					   "sch %x.%x.%04x: cp_init=%d\n",
27262306a36Sopenharmony_ci					   schid.cssid,
27362306a36Sopenharmony_ci					   schid.ssid, schid.sch_no,
27462306a36Sopenharmony_ci					   io_region->ret_code);
27562306a36Sopenharmony_ci			errstr = "cp init";
27662306a36Sopenharmony_ci			goto err_out;
27762306a36Sopenharmony_ci		}
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci		io_region->ret_code = cp_prefetch(&private->cp);
28062306a36Sopenharmony_ci		if (io_region->ret_code) {
28162306a36Sopenharmony_ci			VFIO_CCW_MSG_EVENT(2,
28262306a36Sopenharmony_ci					   "sch %x.%x.%04x: cp_prefetch=%d\n",
28362306a36Sopenharmony_ci					   schid.cssid,
28462306a36Sopenharmony_ci					   schid.ssid, schid.sch_no,
28562306a36Sopenharmony_ci					   io_region->ret_code);
28662306a36Sopenharmony_ci			errstr = "cp prefetch";
28762306a36Sopenharmony_ci			cp_free(&private->cp);
28862306a36Sopenharmony_ci			goto err_out;
28962306a36Sopenharmony_ci		}
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_ci		/* Start channel program and wait for I/O interrupt. */
29262306a36Sopenharmony_ci		io_region->ret_code = fsm_io_helper(private);
29362306a36Sopenharmony_ci		if (io_region->ret_code) {
29462306a36Sopenharmony_ci			VFIO_CCW_MSG_EVENT(2,
29562306a36Sopenharmony_ci					   "sch %x.%x.%04x: fsm_io_helper=%d\n",
29662306a36Sopenharmony_ci					   schid.cssid,
29762306a36Sopenharmony_ci					   schid.ssid, schid.sch_no,
29862306a36Sopenharmony_ci					   io_region->ret_code);
29962306a36Sopenharmony_ci			errstr = "cp fsm_io_helper";
30062306a36Sopenharmony_ci			cp_free(&private->cp);
30162306a36Sopenharmony_ci			goto err_out;
30262306a36Sopenharmony_ci		}
30362306a36Sopenharmony_ci		return;
30462306a36Sopenharmony_ci	} else if (scsw->cmd.fctl & SCSW_FCTL_HALT_FUNC) {
30562306a36Sopenharmony_ci		VFIO_CCW_MSG_EVENT(2,
30662306a36Sopenharmony_ci				   "sch %x.%x.%04x: halt on io_region\n",
30762306a36Sopenharmony_ci				   schid.cssid,
30862306a36Sopenharmony_ci				   schid.ssid, schid.sch_no);
30962306a36Sopenharmony_ci		/* halt is handled via the async cmd region */
31062306a36Sopenharmony_ci		io_region->ret_code = -EOPNOTSUPP;
31162306a36Sopenharmony_ci		goto err_out;
31262306a36Sopenharmony_ci	} else if (scsw->cmd.fctl & SCSW_FCTL_CLEAR_FUNC) {
31362306a36Sopenharmony_ci		VFIO_CCW_MSG_EVENT(2,
31462306a36Sopenharmony_ci				   "sch %x.%x.%04x: clear on io_region\n",
31562306a36Sopenharmony_ci				   schid.cssid,
31662306a36Sopenharmony_ci				   schid.ssid, schid.sch_no);
31762306a36Sopenharmony_ci		/* clear is handled via the async cmd region */
31862306a36Sopenharmony_ci		io_region->ret_code = -EOPNOTSUPP;
31962306a36Sopenharmony_ci		goto err_out;
32062306a36Sopenharmony_ci	}
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_cierr_out:
32362306a36Sopenharmony_ci	private->state = VFIO_CCW_STATE_IDLE;
32462306a36Sopenharmony_ci	trace_vfio_ccw_fsm_io_request(scsw->cmd.fctl, schid,
32562306a36Sopenharmony_ci				      io_region->ret_code, errstr);
32662306a36Sopenharmony_ci}
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_ci/*
32962306a36Sopenharmony_ci * Deal with an async request from userspace.
33062306a36Sopenharmony_ci */
33162306a36Sopenharmony_cistatic void fsm_async_request(struct vfio_ccw_private *private,
33262306a36Sopenharmony_ci			      enum vfio_ccw_event event)
33362306a36Sopenharmony_ci{
33462306a36Sopenharmony_ci	struct ccw_cmd_region *cmd_region = private->cmd_region;
33562306a36Sopenharmony_ci
33662306a36Sopenharmony_ci	switch (cmd_region->command) {
33762306a36Sopenharmony_ci	case VFIO_CCW_ASYNC_CMD_HSCH:
33862306a36Sopenharmony_ci		cmd_region->ret_code = fsm_do_halt(private);
33962306a36Sopenharmony_ci		break;
34062306a36Sopenharmony_ci	case VFIO_CCW_ASYNC_CMD_CSCH:
34162306a36Sopenharmony_ci		cmd_region->ret_code = fsm_do_clear(private);
34262306a36Sopenharmony_ci		break;
34362306a36Sopenharmony_ci	default:
34462306a36Sopenharmony_ci		/* should not happen? */
34562306a36Sopenharmony_ci		cmd_region->ret_code = -EINVAL;
34662306a36Sopenharmony_ci	}
34762306a36Sopenharmony_ci
34862306a36Sopenharmony_ci	trace_vfio_ccw_fsm_async_request(get_schid(private),
34962306a36Sopenharmony_ci					 cmd_region->command,
35062306a36Sopenharmony_ci					 cmd_region->ret_code);
35162306a36Sopenharmony_ci}
35262306a36Sopenharmony_ci
35362306a36Sopenharmony_ci/*
35462306a36Sopenharmony_ci * Got an interrupt for a normal io (state busy).
35562306a36Sopenharmony_ci */
35662306a36Sopenharmony_cistatic void fsm_irq(struct vfio_ccw_private *private,
35762306a36Sopenharmony_ci		    enum vfio_ccw_event event)
35862306a36Sopenharmony_ci{
35962306a36Sopenharmony_ci	struct subchannel *sch = to_subchannel(private->vdev.dev->parent);
36062306a36Sopenharmony_ci	struct irb *irb = this_cpu_ptr(&cio_irb);
36162306a36Sopenharmony_ci
36262306a36Sopenharmony_ci	VFIO_CCW_TRACE_EVENT(6, "IRQ");
36362306a36Sopenharmony_ci	VFIO_CCW_TRACE_EVENT(6, dev_name(&sch->dev));
36462306a36Sopenharmony_ci
36562306a36Sopenharmony_ci	memcpy(&private->irb, irb, sizeof(*irb));
36662306a36Sopenharmony_ci
36762306a36Sopenharmony_ci	queue_work(vfio_ccw_work_q, &private->io_work);
36862306a36Sopenharmony_ci
36962306a36Sopenharmony_ci	if (private->completion)
37062306a36Sopenharmony_ci		complete(private->completion);
37162306a36Sopenharmony_ci}
37262306a36Sopenharmony_ci
37362306a36Sopenharmony_cistatic void fsm_open(struct vfio_ccw_private *private,
37462306a36Sopenharmony_ci		     enum vfio_ccw_event event)
37562306a36Sopenharmony_ci{
37662306a36Sopenharmony_ci	struct subchannel *sch = to_subchannel(private->vdev.dev->parent);
37762306a36Sopenharmony_ci	int ret;
37862306a36Sopenharmony_ci
37962306a36Sopenharmony_ci	spin_lock_irq(sch->lock);
38062306a36Sopenharmony_ci	sch->isc = VFIO_CCW_ISC;
38162306a36Sopenharmony_ci	ret = cio_enable_subchannel(sch, (u32)(unsigned long)sch);
38262306a36Sopenharmony_ci	if (ret)
38362306a36Sopenharmony_ci		goto err_unlock;
38462306a36Sopenharmony_ci
38562306a36Sopenharmony_ci	private->state = VFIO_CCW_STATE_IDLE;
38662306a36Sopenharmony_ci	spin_unlock_irq(sch->lock);
38762306a36Sopenharmony_ci	return;
38862306a36Sopenharmony_ci
38962306a36Sopenharmony_cierr_unlock:
39062306a36Sopenharmony_ci	spin_unlock_irq(sch->lock);
39162306a36Sopenharmony_ci	vfio_ccw_fsm_event(private, VFIO_CCW_EVENT_NOT_OPER);
39262306a36Sopenharmony_ci}
39362306a36Sopenharmony_ci
39462306a36Sopenharmony_cistatic void fsm_close(struct vfio_ccw_private *private,
39562306a36Sopenharmony_ci		      enum vfio_ccw_event event)
39662306a36Sopenharmony_ci{
39762306a36Sopenharmony_ci	struct subchannel *sch = to_subchannel(private->vdev.dev->parent);
39862306a36Sopenharmony_ci	int ret;
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_ci	spin_lock_irq(sch->lock);
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_ci	if (!sch->schib.pmcw.ena)
40362306a36Sopenharmony_ci		goto err_unlock;
40462306a36Sopenharmony_ci
40562306a36Sopenharmony_ci	ret = cio_disable_subchannel(sch);
40662306a36Sopenharmony_ci	if (ret == -EBUSY)
40762306a36Sopenharmony_ci		ret = vfio_ccw_sch_quiesce(sch);
40862306a36Sopenharmony_ci	if (ret)
40962306a36Sopenharmony_ci		goto err_unlock;
41062306a36Sopenharmony_ci
41162306a36Sopenharmony_ci	private->state = VFIO_CCW_STATE_STANDBY;
41262306a36Sopenharmony_ci	spin_unlock_irq(sch->lock);
41362306a36Sopenharmony_ci	cp_free(&private->cp);
41462306a36Sopenharmony_ci	return;
41562306a36Sopenharmony_ci
41662306a36Sopenharmony_cierr_unlock:
41762306a36Sopenharmony_ci	spin_unlock_irq(sch->lock);
41862306a36Sopenharmony_ci	vfio_ccw_fsm_event(private, VFIO_CCW_EVENT_NOT_OPER);
41962306a36Sopenharmony_ci}
42062306a36Sopenharmony_ci
42162306a36Sopenharmony_ci/*
42262306a36Sopenharmony_ci * Device statemachine
42362306a36Sopenharmony_ci */
42462306a36Sopenharmony_cifsm_func_t *vfio_ccw_jumptable[NR_VFIO_CCW_STATES][NR_VFIO_CCW_EVENTS] = {
42562306a36Sopenharmony_ci	[VFIO_CCW_STATE_NOT_OPER] = {
42662306a36Sopenharmony_ci		[VFIO_CCW_EVENT_NOT_OPER]	= fsm_nop,
42762306a36Sopenharmony_ci		[VFIO_CCW_EVENT_IO_REQ]		= fsm_io_error,
42862306a36Sopenharmony_ci		[VFIO_CCW_EVENT_ASYNC_REQ]	= fsm_async_error,
42962306a36Sopenharmony_ci		[VFIO_CCW_EVENT_INTERRUPT]	= fsm_disabled_irq,
43062306a36Sopenharmony_ci		[VFIO_CCW_EVENT_OPEN]		= fsm_nop,
43162306a36Sopenharmony_ci		[VFIO_CCW_EVENT_CLOSE]		= fsm_nop,
43262306a36Sopenharmony_ci	},
43362306a36Sopenharmony_ci	[VFIO_CCW_STATE_STANDBY] = {
43462306a36Sopenharmony_ci		[VFIO_CCW_EVENT_NOT_OPER]	= fsm_notoper,
43562306a36Sopenharmony_ci		[VFIO_CCW_EVENT_IO_REQ]		= fsm_io_error,
43662306a36Sopenharmony_ci		[VFIO_CCW_EVENT_ASYNC_REQ]	= fsm_async_error,
43762306a36Sopenharmony_ci		[VFIO_CCW_EVENT_INTERRUPT]	= fsm_disabled_irq,
43862306a36Sopenharmony_ci		[VFIO_CCW_EVENT_OPEN]		= fsm_open,
43962306a36Sopenharmony_ci		[VFIO_CCW_EVENT_CLOSE]		= fsm_notoper,
44062306a36Sopenharmony_ci	},
44162306a36Sopenharmony_ci	[VFIO_CCW_STATE_IDLE] = {
44262306a36Sopenharmony_ci		[VFIO_CCW_EVENT_NOT_OPER]	= fsm_notoper,
44362306a36Sopenharmony_ci		[VFIO_CCW_EVENT_IO_REQ]		= fsm_io_request,
44462306a36Sopenharmony_ci		[VFIO_CCW_EVENT_ASYNC_REQ]	= fsm_async_request,
44562306a36Sopenharmony_ci		[VFIO_CCW_EVENT_INTERRUPT]	= fsm_irq,
44662306a36Sopenharmony_ci		[VFIO_CCW_EVENT_OPEN]		= fsm_notoper,
44762306a36Sopenharmony_ci		[VFIO_CCW_EVENT_CLOSE]		= fsm_close,
44862306a36Sopenharmony_ci	},
44962306a36Sopenharmony_ci	[VFIO_CCW_STATE_CP_PROCESSING] = {
45062306a36Sopenharmony_ci		[VFIO_CCW_EVENT_NOT_OPER]	= fsm_notoper,
45162306a36Sopenharmony_ci		[VFIO_CCW_EVENT_IO_REQ]		= fsm_io_retry,
45262306a36Sopenharmony_ci		[VFIO_CCW_EVENT_ASYNC_REQ]	= fsm_async_retry,
45362306a36Sopenharmony_ci		[VFIO_CCW_EVENT_INTERRUPT]	= fsm_irq,
45462306a36Sopenharmony_ci		[VFIO_CCW_EVENT_OPEN]		= fsm_notoper,
45562306a36Sopenharmony_ci		[VFIO_CCW_EVENT_CLOSE]		= fsm_close,
45662306a36Sopenharmony_ci	},
45762306a36Sopenharmony_ci	[VFIO_CCW_STATE_CP_PENDING] = {
45862306a36Sopenharmony_ci		[VFIO_CCW_EVENT_NOT_OPER]	= fsm_notoper,
45962306a36Sopenharmony_ci		[VFIO_CCW_EVENT_IO_REQ]		= fsm_io_busy,
46062306a36Sopenharmony_ci		[VFIO_CCW_EVENT_ASYNC_REQ]	= fsm_async_request,
46162306a36Sopenharmony_ci		[VFIO_CCW_EVENT_INTERRUPT]	= fsm_irq,
46262306a36Sopenharmony_ci		[VFIO_CCW_EVENT_OPEN]		= fsm_notoper,
46362306a36Sopenharmony_ci		[VFIO_CCW_EVENT_CLOSE]		= fsm_close,
46462306a36Sopenharmony_ci	},
46562306a36Sopenharmony_ci};
466