Commit | Line | Data |
---|---|---|
b2441318 | 1 | // SPDX-License-Identifier: GPL-2.0 |
bbe37e4c DJS |
2 | /* |
3 | * Finite state machine for vfio-ccw device handling | |
4 | * | |
5 | * Copyright IBM Corp. 2017 | |
6 | * | |
7 | * Author(s): Dong Jia Shi <bjsdjshi@linux.vnet.ibm.com> | |
8 | */ | |
9 | ||
10 | #include <linux/vfio.h> | |
11 | #include <linux/mdev.h> | |
12 | ||
13 | #include "ioasm.h" | |
14 | #include "vfio_ccw_private.h" | |
15 | ||
16 | static int fsm_io_helper(struct vfio_ccw_private *private) | |
17 | { | |
18 | struct subchannel *sch; | |
19 | union orb *orb; | |
20 | int ccode; | |
21 | __u8 lpm; | |
22 | unsigned long flags; | |
3368e547 | 23 | int ret; |
bbe37e4c DJS |
24 | |
25 | sch = private->sch; | |
26 | ||
27 | spin_lock_irqsave(sch->lock, flags); | |
28 | private->state = VFIO_CCW_STATE_BUSY; | |
bbe37e4c DJS |
29 | |
30 | orb = cp_get_orb(&private->cp, (u32)(addr_t)sch, sch->lpm); | |
31 | ||
32 | /* Issue "Start Subchannel" */ | |
33 | ccode = ssch(sch->schid, orb); | |
34 | ||
35 | switch (ccode) { | |
36 | case 0: | |
37 | /* | |
38 | * Initialize device status information | |
39 | */ | |
40 | sch->schib.scsw.cmd.actl |= SCSW_ACTL_START_PEND; | |
3368e547 CH |
41 | ret = 0; |
42 | break; | |
bbe37e4c DJS |
43 | case 1: /* Status pending */ |
44 | case 2: /* Busy */ | |
3368e547 CH |
45 | ret = -EBUSY; |
46 | break; | |
bbe37e4c DJS |
47 | case 3: /* Device/path not operational */ |
48 | { | |
49 | lpm = orb->cmd.lpm; | |
50 | if (lpm != 0) | |
51 | sch->lpm &= ~lpm; | |
52 | else | |
53 | sch->lpm = 0; | |
54 | ||
55 | if (cio_update_schib(sch)) | |
3368e547 CH |
56 | ret = -ENODEV; |
57 | else | |
58 | ret = sch->lpm ? -EACCES : -ENODEV; | |
59 | break; | |
bbe37e4c DJS |
60 | } |
61 | default: | |
3368e547 | 62 | ret = ccode; |
bbe37e4c | 63 | } |
3368e547 CH |
64 | spin_unlock_irqrestore(sch->lock, flags); |
65 | return ret; | |
bbe37e4c DJS |
66 | } |
67 | ||
68 | static void fsm_notoper(struct vfio_ccw_private *private, | |
69 | enum vfio_ccw_event event) | |
70 | { | |
71 | struct subchannel *sch = private->sch; | |
72 | ||
73 | /* | |
74 | * TODO: | |
75 | * Probably we should send the machine check to the guest. | |
76 | */ | |
77 | css_sched_sch_todo(sch, SCH_TODO_UNREG); | |
78 | private->state = VFIO_CCW_STATE_NOT_OPER; | |
79 | } | |
80 | ||
81 | /* | |
82 | * No operation action. | |
83 | */ | |
84 | static void fsm_nop(struct vfio_ccw_private *private, | |
85 | enum vfio_ccw_event event) | |
86 | { | |
87 | } | |
88 | ||
89 | static void fsm_io_error(struct vfio_ccw_private *private, | |
90 | enum vfio_ccw_event event) | |
91 | { | |
92 | pr_err("vfio-ccw: FSM: I/O request from state:%d\n", private->state); | |
93 | private->io_region.ret_code = -EIO; | |
94 | } | |
95 | ||
96 | static void fsm_io_busy(struct vfio_ccw_private *private, | |
97 | enum vfio_ccw_event event) | |
98 | { | |
99 | private->io_region.ret_code = -EBUSY; | |
100 | } | |
101 | ||
102 | static void fsm_disabled_irq(struct vfio_ccw_private *private, | |
103 | enum vfio_ccw_event event) | |
104 | { | |
105 | struct subchannel *sch = private->sch; | |
106 | ||
107 | /* | |
108 | * An interrupt in a disabled state means a previous disable was not | |
109 | * successful - should not happen, but we try to disable again. | |
110 | */ | |
111 | cio_disable_subchannel(sch); | |
112 | } | |
113 | ||
114 | /* | |
115 | * Deal with the ccw command request from the userspace. | |
116 | */ | |
117 | static void fsm_io_request(struct vfio_ccw_private *private, | |
118 | enum vfio_ccw_event event) | |
119 | { | |
120 | union orb *orb; | |
121 | union scsw *scsw = &private->scsw; | |
122 | struct ccw_io_region *io_region = &private->io_region; | |
123 | struct mdev_device *mdev = private->mdev; | |
124 | ||
125 | private->state = VFIO_CCW_STATE_BOXED; | |
126 | ||
127 | memcpy(scsw, io_region->scsw_area, sizeof(*scsw)); | |
128 | ||
129 | if (scsw->cmd.fctl & SCSW_FCTL_START_FUNC) { | |
130 | orb = (union orb *)io_region->orb_area; | |
131 | ||
9851bc77 CH |
132 | /* Don't try to build a cp if transport mode is specified. */ |
133 | if (orb->tm.b) { | |
134 | io_region->ret_code = -EOPNOTSUPP; | |
135 | goto err_out; | |
136 | } | |
bbe37e4c DJS |
137 | io_region->ret_code = cp_init(&private->cp, mdev_dev(mdev), |
138 | orb); | |
139 | if (io_region->ret_code) | |
140 | goto err_out; | |
141 | ||
142 | io_region->ret_code = cp_prefetch(&private->cp); | |
143 | if (io_region->ret_code) { | |
144 | cp_free(&private->cp); | |
145 | goto err_out; | |
146 | } | |
147 | ||
148 | /* Start channel program and wait for I/O interrupt. */ | |
149 | io_region->ret_code = fsm_io_helper(private); | |
150 | if (io_region->ret_code) { | |
151 | cp_free(&private->cp); | |
152 | goto err_out; | |
153 | } | |
154 | return; | |
155 | } else if (scsw->cmd.fctl & SCSW_FCTL_HALT_FUNC) { | |
156 | /* XXX: Handle halt. */ | |
157 | io_region->ret_code = -EOPNOTSUPP; | |
158 | goto err_out; | |
159 | } else if (scsw->cmd.fctl & SCSW_FCTL_CLEAR_FUNC) { | |
160 | /* XXX: Handle clear. */ | |
161 | io_region->ret_code = -EOPNOTSUPP; | |
162 | goto err_out; | |
163 | } | |
164 | ||
165 | err_out: | |
166 | private->state = VFIO_CCW_STATE_IDLE; | |
167 | } | |
168 | ||
169 | /* | |
170 | * Got an interrupt for a normal io (state busy). | |
171 | */ | |
172 | static void fsm_irq(struct vfio_ccw_private *private, | |
173 | enum vfio_ccw_event event) | |
174 | { | |
c9c31b07 | 175 | struct irb *irb = this_cpu_ptr(&cio_irb); |
bbe37e4c | 176 | |
bbe37e4c DJS |
177 | memcpy(&private->irb, irb, sizeof(*irb)); |
178 | ||
179 | queue_work(vfio_ccw_work_q, &private->io_work); | |
180 | ||
181 | if (private->completion) | |
182 | complete(private->completion); | |
183 | } | |
184 | ||
185 | /* | |
186 | * Device statemachine | |
187 | */ | |
188 | fsm_func_t *vfio_ccw_jumptable[NR_VFIO_CCW_STATES][NR_VFIO_CCW_EVENTS] = { | |
189 | [VFIO_CCW_STATE_NOT_OPER] = { | |
190 | [VFIO_CCW_EVENT_NOT_OPER] = fsm_nop, | |
191 | [VFIO_CCW_EVENT_IO_REQ] = fsm_io_error, | |
192 | [VFIO_CCW_EVENT_INTERRUPT] = fsm_disabled_irq, | |
193 | }, | |
194 | [VFIO_CCW_STATE_STANDBY] = { | |
195 | [VFIO_CCW_EVENT_NOT_OPER] = fsm_notoper, | |
196 | [VFIO_CCW_EVENT_IO_REQ] = fsm_io_error, | |
197 | [VFIO_CCW_EVENT_INTERRUPT] = fsm_irq, | |
198 | }, | |
199 | [VFIO_CCW_STATE_IDLE] = { | |
200 | [VFIO_CCW_EVENT_NOT_OPER] = fsm_notoper, | |
201 | [VFIO_CCW_EVENT_IO_REQ] = fsm_io_request, | |
202 | [VFIO_CCW_EVENT_INTERRUPT] = fsm_irq, | |
203 | }, | |
204 | [VFIO_CCW_STATE_BOXED] = { | |
205 | [VFIO_CCW_EVENT_NOT_OPER] = fsm_notoper, | |
206 | [VFIO_CCW_EVENT_IO_REQ] = fsm_io_busy, | |
207 | [VFIO_CCW_EVENT_INTERRUPT] = fsm_irq, | |
208 | }, | |
209 | [VFIO_CCW_STATE_BUSY] = { | |
210 | [VFIO_CCW_EVENT_NOT_OPER] = fsm_notoper, | |
211 | [VFIO_CCW_EVENT_IO_REQ] = fsm_io_busy, | |
212 | [VFIO_CCW_EVENT_INTERRUPT] = fsm_irq, | |
213 | }, | |
214 | }; |