brintos

brintos / linux-shallow public Read only

0
0
Text · 11.2 KiB · 4d7988e Raw
466 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Finite state machine for vfio-ccw device handling4 *5 * Copyright IBM Corp. 20176 * Copyright Red Hat, Inc. 20197 *8 * Author(s): Dong Jia Shi <bjsdjshi@linux.vnet.ibm.com>9 *            Cornelia Huck <cohuck@redhat.com>10 */11 12#include <linux/vfio.h>13 14#include <asm/isc.h>15 16#include "ioasm.h"17#include "vfio_ccw_private.h"18 19static int fsm_io_helper(struct vfio_ccw_private *private)20{21	struct subchannel *sch = to_subchannel(private->vdev.dev->parent);22	union orb *orb;23	int ccode;24	__u8 lpm;25	unsigned long flags;26	int ret;27 28	spin_lock_irqsave(&sch->lock, flags);29 30	orb = cp_get_orb(&private->cp, sch);31	if (!orb) {32		ret = -EIO;33		goto out;34	}35 36	VFIO_CCW_TRACE_EVENT(5, "stIO");37	VFIO_CCW_TRACE_EVENT(5, dev_name(&sch->dev));38 39	/* Issue "Start Subchannel" */40	ccode = ssch(sch->schid, orb);41 42	VFIO_CCW_HEX_EVENT(5, &ccode, sizeof(ccode));43 44	switch (ccode) {45	case 0:46		/*47		 * Initialize device status information48		 */49		sch->schib.scsw.cmd.actl |= SCSW_ACTL_START_PEND;50		ret = 0;51		private->state = VFIO_CCW_STATE_CP_PENDING;52		break;53	case 1:		/* Status pending */54	case 2:		/* Busy */55		ret = -EBUSY;56		break;57	case 3:		/* Device/path not operational */58	{59		lpm = orb->cmd.lpm;60		if (lpm != 0)61			sch->lpm &= ~lpm;62		else63			sch->lpm = 0;64 65		if (cio_update_schib(sch))66			ret = -ENODEV;67		else68			ret = sch->lpm ? -EACCES : -ENODEV;69		break;70	}71	default:72		ret = ccode;73	}74out:75	spin_unlock_irqrestore(&sch->lock, flags);76	return ret;77}78 79static int fsm_do_halt(struct vfio_ccw_private *private)80{81	struct subchannel *sch = to_subchannel(private->vdev.dev->parent);82	unsigned long flags;83	int ccode;84	int ret;85 86	spin_lock_irqsave(&sch->lock, flags);87 88	VFIO_CCW_TRACE_EVENT(2, "haltIO");89	VFIO_CCW_TRACE_EVENT(2, dev_name(&sch->dev));90 91	/* Issue "Halt Subchannel" */92	ccode = hsch(sch->schid);93 94	VFIO_CCW_HEX_EVENT(2, &ccode, sizeof(ccode));95 96	switch (ccode) {97	case 0:98		/*99		 * Initialize device status information100		 */101		sch->schib.scsw.cmd.actl |= SCSW_ACTL_HALT_PEND;102		ret = 0;103		break;104	case 1:		/* Status pending */105	case 2:		/* Busy */106		ret = -EBUSY;107		break;108	case 3:		/* Device not operational */109		ret = -ENODEV;110		break;111	default:112		ret = ccode;113	}114	spin_unlock_irqrestore(&sch->lock, flags);115	return ret;116}117 118static int fsm_do_clear(struct vfio_ccw_private *private)119{120	struct subchannel *sch = to_subchannel(private->vdev.dev->parent);121	unsigned long flags;122	int ccode;123	int ret;124 125	spin_lock_irqsave(&sch->lock, flags);126 127	VFIO_CCW_TRACE_EVENT(2, "clearIO");128	VFIO_CCW_TRACE_EVENT(2, dev_name(&sch->dev));129 130	/* Issue "Clear Subchannel" */131	ccode = csch(sch->schid);132 133	VFIO_CCW_HEX_EVENT(2, &ccode, sizeof(ccode));134 135	switch (ccode) {136	case 0:137		/*138		 * Initialize device status information139		 */140		sch->schib.scsw.cmd.actl = SCSW_ACTL_CLEAR_PEND;141		/* TODO: check what else we might need to clear */142		ret = 0;143		break;144	case 3:		/* Device not operational */145		ret = -ENODEV;146		break;147	default:148		ret = ccode;149	}150	spin_unlock_irqrestore(&sch->lock, flags);151	return ret;152}153 154static void fsm_notoper(struct vfio_ccw_private *private,155			enum vfio_ccw_event event)156{157	struct subchannel *sch = to_subchannel(private->vdev.dev->parent);158 159	VFIO_CCW_MSG_EVENT(2, "sch %x.%x.%04x: notoper event %x state %x\n",160			   sch->schid.cssid,161			   sch->schid.ssid,162			   sch->schid.sch_no,163			   event,164			   private->state);165 166	/*167	 * TODO:168	 * Probably we should send the machine check to the guest.169	 */170	css_sched_sch_todo(sch, SCH_TODO_UNREG);171	private->state = VFIO_CCW_STATE_NOT_OPER;172 173	/* This is usually handled during CLOSE event */174	cp_free(&private->cp);175}176 177/*178 * No operation action.179 */180static void fsm_nop(struct vfio_ccw_private *private,181		    enum vfio_ccw_event event)182{183}184 185static void fsm_io_error(struct vfio_ccw_private *private,186			 enum vfio_ccw_event event)187{188	pr_err("vfio-ccw: FSM: I/O request from state:%d\n", private->state);189	private->io_region->ret_code = -EIO;190}191 192static void fsm_io_busy(struct vfio_ccw_private *private,193			enum vfio_ccw_event event)194{195	private->io_region->ret_code = -EBUSY;196}197 198static void fsm_io_retry(struct vfio_ccw_private *private,199			 enum vfio_ccw_event event)200{201	private->io_region->ret_code = -EAGAIN;202}203 204static void fsm_async_error(struct vfio_ccw_private *private,205			    enum vfio_ccw_event event)206{207	struct ccw_cmd_region *cmd_region = private->cmd_region;208 209	pr_err("vfio-ccw: FSM: %s request from state:%d\n",210	       cmd_region->command == VFIO_CCW_ASYNC_CMD_HSCH ? "halt" :211	       cmd_region->command == VFIO_CCW_ASYNC_CMD_CSCH ? "clear" :212	       "<unknown>", private->state);213	cmd_region->ret_code = -EIO;214}215 216static void fsm_async_retry(struct vfio_ccw_private *private,217			    enum vfio_ccw_event event)218{219	private->cmd_region->ret_code = -EAGAIN;220}221 222static void fsm_disabled_irq(struct vfio_ccw_private *private,223			     enum vfio_ccw_event event)224{225	struct subchannel *sch = to_subchannel(private->vdev.dev->parent);226 227	/*228	 * An interrupt in a disabled state means a previous disable was not229	 * successful - should not happen, but we try to disable again.230	 */231	cio_disable_subchannel(sch);232}233inline struct subchannel_id get_schid(struct vfio_ccw_private *p)234{235	struct subchannel *sch = to_subchannel(p->vdev.dev->parent);236 237	return sch->schid;238}239 240/*241 * Deal with the ccw command request from the userspace.242 */243static void fsm_io_request(struct vfio_ccw_private *private,244			   enum vfio_ccw_event event)245{246	union orb *orb;247	union scsw *scsw = &private->scsw;248	struct ccw_io_region *io_region = private->io_region;249	char *errstr = "request";250	struct subchannel_id schid = get_schid(private);251 252	private->state = VFIO_CCW_STATE_CP_PROCESSING;253	memcpy(scsw, io_region->scsw_area, sizeof(*scsw));254 255	if (scsw->cmd.fctl & SCSW_FCTL_START_FUNC) {256		orb = (union orb *)io_region->orb_area;257 258		/* Don't try to build a cp if transport mode is specified. */259		if (orb->tm.b) {260			io_region->ret_code = -EOPNOTSUPP;261			VFIO_CCW_MSG_EVENT(2,262					   "sch %x.%x.%04x: transport mode\n",263					   schid.cssid,264					   schid.ssid, schid.sch_no);265			errstr = "transport mode";266			goto err_out;267		}268		io_region->ret_code = cp_init(&private->cp, orb);269		if (io_region->ret_code) {270			VFIO_CCW_MSG_EVENT(2,271					   "sch %x.%x.%04x: cp_init=%d\n",272					   schid.cssid,273					   schid.ssid, schid.sch_no,274					   io_region->ret_code);275			errstr = "cp init";276			goto err_out;277		}278 279		io_region->ret_code = cp_prefetch(&private->cp);280		if (io_region->ret_code) {281			VFIO_CCW_MSG_EVENT(2,282					   "sch %x.%x.%04x: cp_prefetch=%d\n",283					   schid.cssid,284					   schid.ssid, schid.sch_no,285					   io_region->ret_code);286			errstr = "cp prefetch";287			cp_free(&private->cp);288			goto err_out;289		}290 291		/* Start channel program and wait for I/O interrupt. */292		io_region->ret_code = fsm_io_helper(private);293		if (io_region->ret_code) {294			VFIO_CCW_MSG_EVENT(2,295					   "sch %x.%x.%04x: fsm_io_helper=%d\n",296					   schid.cssid,297					   schid.ssid, schid.sch_no,298					   io_region->ret_code);299			errstr = "cp fsm_io_helper";300			cp_free(&private->cp);301			goto err_out;302		}303		return;304	} else if (scsw->cmd.fctl & SCSW_FCTL_HALT_FUNC) {305		VFIO_CCW_MSG_EVENT(2,306				   "sch %x.%x.%04x: halt on io_region\n",307				   schid.cssid,308				   schid.ssid, schid.sch_no);309		/* halt is handled via the async cmd region */310		io_region->ret_code = -EOPNOTSUPP;311		goto err_out;312	} else if (scsw->cmd.fctl & SCSW_FCTL_CLEAR_FUNC) {313		VFIO_CCW_MSG_EVENT(2,314				   "sch %x.%x.%04x: clear on io_region\n",315				   schid.cssid,316				   schid.ssid, schid.sch_no);317		/* clear is handled via the async cmd region */318		io_region->ret_code = -EOPNOTSUPP;319		goto err_out;320	}321 322err_out:323	private->state = VFIO_CCW_STATE_IDLE;324	trace_vfio_ccw_fsm_io_request(scsw->cmd.fctl, schid,325				      io_region->ret_code, errstr);326}327 328/*329 * Deal with an async request from userspace.330 */331static void fsm_async_request(struct vfio_ccw_private *private,332			      enum vfio_ccw_event event)333{334	struct ccw_cmd_region *cmd_region = private->cmd_region;335 336	switch (cmd_region->command) {337	case VFIO_CCW_ASYNC_CMD_HSCH:338		cmd_region->ret_code = fsm_do_halt(private);339		break;340	case VFIO_CCW_ASYNC_CMD_CSCH:341		cmd_region->ret_code = fsm_do_clear(private);342		break;343	default:344		/* should not happen? */345		cmd_region->ret_code = -EINVAL;346	}347 348	trace_vfio_ccw_fsm_async_request(get_schid(private),349					 cmd_region->command,350					 cmd_region->ret_code);351}352 353/*354 * Got an interrupt for a normal io (state busy).355 */356static void fsm_irq(struct vfio_ccw_private *private,357		    enum vfio_ccw_event event)358{359	struct subchannel *sch = to_subchannel(private->vdev.dev->parent);360	struct irb *irb = this_cpu_ptr(&cio_irb);361 362	VFIO_CCW_TRACE_EVENT(6, "IRQ");363	VFIO_CCW_TRACE_EVENT(6, dev_name(&sch->dev));364 365	memcpy(&private->irb, irb, sizeof(*irb));366 367	queue_work(vfio_ccw_work_q, &private->io_work);368 369	if (private->completion)370		complete(private->completion);371}372 373static void fsm_open(struct vfio_ccw_private *private,374		     enum vfio_ccw_event event)375{376	struct subchannel *sch = to_subchannel(private->vdev.dev->parent);377	int ret;378 379	spin_lock_irq(&sch->lock);380	sch->isc = VFIO_CCW_ISC;381	ret = cio_enable_subchannel(sch, (u32)virt_to_phys(sch));382	if (ret)383		goto err_unlock;384 385	private->state = VFIO_CCW_STATE_IDLE;386	spin_unlock_irq(&sch->lock);387	return;388 389err_unlock:390	spin_unlock_irq(&sch->lock);391	vfio_ccw_fsm_event(private, VFIO_CCW_EVENT_NOT_OPER);392}393 394static void fsm_close(struct vfio_ccw_private *private,395		      enum vfio_ccw_event event)396{397	struct subchannel *sch = to_subchannel(private->vdev.dev->parent);398	int ret;399 400	spin_lock_irq(&sch->lock);401 402	if (!sch->schib.pmcw.ena)403		goto err_unlock;404 405	ret = cio_disable_subchannel(sch);406	if (ret == -EBUSY)407		ret = vfio_ccw_sch_quiesce(sch);408	if (ret)409		goto err_unlock;410 411	private->state = VFIO_CCW_STATE_STANDBY;412	spin_unlock_irq(&sch->lock);413	cp_free(&private->cp);414	return;415 416err_unlock:417	spin_unlock_irq(&sch->lock);418	vfio_ccw_fsm_event(private, VFIO_CCW_EVENT_NOT_OPER);419}420 421/*422 * Device statemachine423 */424fsm_func_t *vfio_ccw_jumptable[NR_VFIO_CCW_STATES][NR_VFIO_CCW_EVENTS] = {425	[VFIO_CCW_STATE_NOT_OPER] = {426		[VFIO_CCW_EVENT_NOT_OPER]	= fsm_nop,427		[VFIO_CCW_EVENT_IO_REQ]		= fsm_io_error,428		[VFIO_CCW_EVENT_ASYNC_REQ]	= fsm_async_error,429		[VFIO_CCW_EVENT_INTERRUPT]	= fsm_disabled_irq,430		[VFIO_CCW_EVENT_OPEN]		= fsm_nop,431		[VFIO_CCW_EVENT_CLOSE]		= fsm_nop,432	},433	[VFIO_CCW_STATE_STANDBY] = {434		[VFIO_CCW_EVENT_NOT_OPER]	= fsm_notoper,435		[VFIO_CCW_EVENT_IO_REQ]		= fsm_io_error,436		[VFIO_CCW_EVENT_ASYNC_REQ]	= fsm_async_error,437		[VFIO_CCW_EVENT_INTERRUPT]	= fsm_disabled_irq,438		[VFIO_CCW_EVENT_OPEN]		= fsm_open,439		[VFIO_CCW_EVENT_CLOSE]		= fsm_notoper,440	},441	[VFIO_CCW_STATE_IDLE] = {442		[VFIO_CCW_EVENT_NOT_OPER]	= fsm_notoper,443		[VFIO_CCW_EVENT_IO_REQ]		= fsm_io_request,444		[VFIO_CCW_EVENT_ASYNC_REQ]	= fsm_async_request,445		[VFIO_CCW_EVENT_INTERRUPT]	= fsm_irq,446		[VFIO_CCW_EVENT_OPEN]		= fsm_notoper,447		[VFIO_CCW_EVENT_CLOSE]		= fsm_close,448	},449	[VFIO_CCW_STATE_CP_PROCESSING] = {450		[VFIO_CCW_EVENT_NOT_OPER]	= fsm_notoper,451		[VFIO_CCW_EVENT_IO_REQ]		= fsm_io_retry,452		[VFIO_CCW_EVENT_ASYNC_REQ]	= fsm_async_retry,453		[VFIO_CCW_EVENT_INTERRUPT]	= fsm_irq,454		[VFIO_CCW_EVENT_OPEN]		= fsm_notoper,455		[VFIO_CCW_EVENT_CLOSE]		= fsm_close,456	},457	[VFIO_CCW_STATE_CP_PENDING] = {458		[VFIO_CCW_EVENT_NOT_OPER]	= fsm_notoper,459		[VFIO_CCW_EVENT_IO_REQ]		= fsm_io_busy,460		[VFIO_CCW_EVENT_ASYNC_REQ]	= fsm_async_request,461		[VFIO_CCW_EVENT_INTERRUPT]	= fsm_irq,462		[VFIO_CCW_EVENT_OPEN]		= fsm_notoper,463		[VFIO_CCW_EVENT_CLOSE]		= fsm_close,464	},465};466