]>
Commit | Line | Data |
---|---|---|
724117b7 | 1 | // SPDX-License-Identifier: GPL-2.0 |
63f1934d DJS |
2 | /* |
3 | * VFIO based Physical Subchannel device driver | |
4 | * | |
5 | * Copyright IBM Corp. 2017 | |
6 | * | |
7 | * Author(s): Dong Jia Shi <bjsdjshi@linux.vnet.ibm.com> | |
8 | * Xiao Feng Ren <renxiaof@linux.vnet.ibm.com> | |
9 | */ | |
10 | ||
11 | #include <linux/module.h> | |
12 | #include <linux/init.h> | |
13 | #include <linux/device.h> | |
14 | #include <linux/slab.h> | |
4e149e43 DJS |
15 | #include <linux/uuid.h> |
16 | #include <linux/mdev.h> | |
63f1934d DJS |
17 | |
18 | #include <asm/isc.h> | |
19 | ||
4e149e43 DJS |
20 | #include "ioasm.h" |
21 | #include "css.h" | |
63f1934d DJS |
22 | #include "vfio_ccw_private.h" |
23 | ||
e5f84dba | 24 | struct workqueue_struct *vfio_ccw_work_q; |
52df7837 | 25 | static struct kmem_cache *vfio_ccw_io_region; |
e5f84dba | 26 | |
63f1934d DJS |
27 | /* |
28 | * Helpers | |
29 | */ | |
84cd8fc4 | 30 | int vfio_ccw_sch_quiesce(struct subchannel *sch) |
63f1934d DJS |
31 | { |
32 | struct vfio_ccw_private *private = dev_get_drvdata(&sch->dev); | |
33 | DECLARE_COMPLETION_ONSTACK(completion); | |
34 | int iretry, ret = 0; | |
35 | ||
36 | spin_lock_irq(sch->lock); | |
37 | if (!sch->schib.pmcw.ena) | |
38 | goto out_unlock; | |
39 | ret = cio_disable_subchannel(sch); | |
40 | if (ret != -EBUSY) | |
41 | goto out_unlock; | |
42 | ||
43 | do { | |
44 | iretry = 255; | |
45 | ||
46 | ret = cio_cancel_halt_clear(sch, &iretry); | |
47 | while (ret == -EBUSY) { | |
48 | /* | |
49 | * Flush all I/O and wait for | |
50 | * cancel/halt/clear completion. | |
51 | */ | |
52 | private->completion = &completion; | |
53 | spin_unlock_irq(sch->lock); | |
54 | ||
55 | wait_for_completion_timeout(&completion, 3*HZ); | |
56 | ||
57 | spin_lock_irq(sch->lock); | |
58 | private->completion = NULL; | |
e5f84dba | 59 | flush_workqueue(vfio_ccw_work_q); |
63f1934d DJS |
60 | ret = cio_cancel_halt_clear(sch, &iretry); |
61 | }; | |
62 | ||
63 | ret = cio_disable_subchannel(sch); | |
64 | } while (ret == -EBUSY); | |
63f1934d | 65 | out_unlock: |
bbe37e4c | 66 | private->state = VFIO_CCW_STATE_NOT_OPER; |
63f1934d DJS |
67 | spin_unlock_irq(sch->lock); |
68 | return ret; | |
69 | } | |
70 | ||
e5f84dba DJS |
71 | static void vfio_ccw_sch_io_todo(struct work_struct *work) |
72 | { | |
73 | struct vfio_ccw_private *private; | |
e5f84dba | 74 | struct irb *irb; |
50b7f1b7 | 75 | bool is_final; |
4e149e43 | 76 | |
e5f84dba DJS |
77 | private = container_of(work, struct vfio_ccw_private, io_work); |
78 | irb = &private->irb; | |
4e149e43 | 79 | |
50b7f1b7 CH |
80 | is_final = !(scsw_actl(&irb->scsw) & |
81 | (SCSW_ACTL_DEVACT | SCSW_ACTL_SCHACT)); | |
e5f84dba DJS |
82 | if (scsw_is_solicited(&irb->scsw)) { |
83 | cp_update_scsw(&private->cp, &irb->scsw); | |
50b7f1b7 CH |
84 | if (is_final) |
85 | cp_free(&private->cp); | |
e5f84dba | 86 | } |
c98e16b2 | 87 | memcpy(private->io_region->irb_area, irb, sizeof(*irb)); |
e5f84dba DJS |
88 | |
89 | if (private->io_trigger) | |
90 | eventfd_signal(private->io_trigger, 1); | |
4e149e43 | 91 | |
50b7f1b7 | 92 | if (private->mdev && is_final) |
bbe37e4c | 93 | private->state = VFIO_CCW_STATE_IDLE; |
4e149e43 DJS |
94 | } |
95 | ||
63f1934d DJS |
96 | /* |
97 | * Css driver callbacks | |
98 | */ | |
99 | static void vfio_ccw_sch_irq(struct subchannel *sch) | |
100 | { | |
101 | struct vfio_ccw_private *private = dev_get_drvdata(&sch->dev); | |
102 | ||
103 | inc_irq_stat(IRQIO_CIO); | |
bbe37e4c | 104 | vfio_ccw_fsm_event(private, VFIO_CCW_EVENT_INTERRUPT); |
63f1934d DJS |
105 | } |
106 | ||
107 | static int vfio_ccw_sch_probe(struct subchannel *sch) | |
108 | { | |
109 | struct pmcw *pmcw = &sch->schib.pmcw; | |
110 | struct vfio_ccw_private *private; | |
111 | int ret; | |
112 | ||
113 | if (pmcw->qf) { | |
114 | dev_warn(&sch->dev, "vfio: ccw: does not support QDIO: %s\n", | |
115 | dev_name(&sch->dev)); | |
116 | return -ENODEV; | |
117 | } | |
118 | ||
119 | private = kzalloc(sizeof(*private), GFP_KERNEL | GFP_DMA); | |
120 | if (!private) | |
121 | return -ENOMEM; | |
c98e16b2 | 122 | |
bf42daed EF |
123 | private->io_region = kmem_cache_zalloc(vfio_ccw_io_region, |
124 | GFP_KERNEL | GFP_DMA); | |
c98e16b2 EF |
125 | if (!private->io_region) { |
126 | kfree(private); | |
127 | return -ENOMEM; | |
128 | } | |
129 | ||
63f1934d DJS |
130 | private->sch = sch; |
131 | dev_set_drvdata(&sch->dev, private); | |
132 | ||
133 | spin_lock_irq(sch->lock); | |
bbe37e4c | 134 | private->state = VFIO_CCW_STATE_NOT_OPER; |
63f1934d DJS |
135 | sch->isc = VFIO_CCW_ISC; |
136 | ret = cio_enable_subchannel(sch, (u32)(unsigned long)sch); | |
137 | spin_unlock_irq(sch->lock); | |
138 | if (ret) | |
139 | goto out_free; | |
140 | ||
e5f84dba | 141 | INIT_WORK(&private->io_work, vfio_ccw_sch_io_todo); |
84cd8fc4 | 142 | atomic_set(&private->avail, 1); |
bbe37e4c | 143 | private->state = VFIO_CCW_STATE_STANDBY; |
84cd8fc4 | 144 | |
55e93ecd PM |
145 | ret = vfio_ccw_mdev_reg(sch); |
146 | if (ret) | |
147 | goto out_disable; | |
148 | ||
63f1934d DJS |
149 | return 0; |
150 | ||
151 | out_disable: | |
152 | cio_disable_subchannel(sch); | |
153 | out_free: | |
154 | dev_set_drvdata(&sch->dev, NULL); | |
bf42daed | 155 | kmem_cache_free(vfio_ccw_io_region, private->io_region); |
63f1934d DJS |
156 | kfree(private); |
157 | return ret; | |
158 | } | |
159 | ||
160 | static int vfio_ccw_sch_remove(struct subchannel *sch) | |
161 | { | |
162 | struct vfio_ccw_private *private = dev_get_drvdata(&sch->dev); | |
163 | ||
164 | vfio_ccw_sch_quiesce(sch); | |
165 | ||
84cd8fc4 DJS |
166 | vfio_ccw_mdev_unreg(sch); |
167 | ||
63f1934d DJS |
168 | dev_set_drvdata(&sch->dev, NULL); |
169 | ||
bf42daed | 170 | kmem_cache_free(vfio_ccw_io_region, private->io_region); |
63f1934d DJS |
171 | kfree(private); |
172 | ||
173 | return 0; | |
174 | } | |
175 | ||
176 | static void vfio_ccw_sch_shutdown(struct subchannel *sch) | |
177 | { | |
178 | vfio_ccw_sch_quiesce(sch); | |
179 | } | |
180 | ||
181 | /** | |
182 | * vfio_ccw_sch_event - process subchannel event | |
183 | * @sch: subchannel | |
184 | * @process: non-zero if function is called in process context | |
185 | * | |
186 | * An unspecified event occurred for this subchannel. Adjust data according | |
187 | * to the current operational state of the subchannel. Return zero when the | |
188 | * event has been handled sufficiently or -EAGAIN when this function should | |
189 | * be called again in process context. | |
190 | */ | |
191 | static int vfio_ccw_sch_event(struct subchannel *sch, int process) | |
192 | { | |
bbe37e4c | 193 | struct vfio_ccw_private *private = dev_get_drvdata(&sch->dev); |
63f1934d | 194 | unsigned long flags; |
2c861d89 | 195 | int rc = -EAGAIN; |
63f1934d DJS |
196 | |
197 | spin_lock_irqsave(sch->lock, flags); | |
198 | if (!device_is_registered(&sch->dev)) | |
199 | goto out_unlock; | |
200 | ||
201 | if (work_pending(&sch->todo_work)) | |
202 | goto out_unlock; | |
203 | ||
204 | if (cio_update_schib(sch)) { | |
bbe37e4c | 205 | vfio_ccw_fsm_event(private, VFIO_CCW_EVENT_NOT_OPER); |
2c861d89 | 206 | rc = 0; |
63f1934d DJS |
207 | goto out_unlock; |
208 | } | |
209 | ||
bbe37e4c DJS |
210 | private = dev_get_drvdata(&sch->dev); |
211 | if (private->state == VFIO_CCW_STATE_NOT_OPER) { | |
212 | private->state = private->mdev ? VFIO_CCW_STATE_IDLE : | |
213 | VFIO_CCW_STATE_STANDBY; | |
214 | } | |
2c861d89 | 215 | rc = 0; |
bbe37e4c | 216 | |
63f1934d DJS |
217 | out_unlock: |
218 | spin_unlock_irqrestore(sch->lock, flags); | |
219 | ||
2c861d89 | 220 | return rc; |
63f1934d DJS |
221 | } |
222 | ||
223 | static struct css_device_id vfio_ccw_sch_ids[] = { | |
224 | { .match_flags = 0x1, .type = SUBCHANNEL_TYPE_IO, }, | |
225 | { /* end of list */ }, | |
226 | }; | |
227 | MODULE_DEVICE_TABLE(css, vfio_ccw_sch_ids); | |
228 | ||
229 | static struct css_driver vfio_ccw_sch_driver = { | |
230 | .drv = { | |
231 | .name = "vfio_ccw", | |
232 | .owner = THIS_MODULE, | |
233 | }, | |
234 | .subchannel_type = vfio_ccw_sch_ids, | |
235 | .irq = vfio_ccw_sch_irq, | |
236 | .probe = vfio_ccw_sch_probe, | |
237 | .remove = vfio_ccw_sch_remove, | |
238 | .shutdown = vfio_ccw_sch_shutdown, | |
239 | .sch_event = vfio_ccw_sch_event, | |
240 | }; | |
241 | ||
242 | static int __init vfio_ccw_sch_init(void) | |
243 | { | |
244 | int ret; | |
245 | ||
e5f84dba DJS |
246 | vfio_ccw_work_q = create_singlethread_workqueue("vfio-ccw"); |
247 | if (!vfio_ccw_work_q) | |
248 | return -ENOMEM; | |
249 | ||
bf42daed EF |
250 | vfio_ccw_io_region = kmem_cache_create_usercopy("vfio_ccw_io_region", |
251 | sizeof(struct ccw_io_region), 0, | |
252 | SLAB_ACCOUNT, 0, | |
253 | sizeof(struct ccw_io_region), NULL); | |
254 | if (!vfio_ccw_io_region) { | |
255 | destroy_workqueue(vfio_ccw_work_q); | |
256 | return -ENOMEM; | |
257 | } | |
258 | ||
63f1934d DJS |
259 | isc_register(VFIO_CCW_ISC); |
260 | ret = css_driver_register(&vfio_ccw_sch_driver); | |
e5f84dba | 261 | if (ret) { |
63f1934d | 262 | isc_unregister(VFIO_CCW_ISC); |
bf42daed | 263 | kmem_cache_destroy(vfio_ccw_io_region); |
e5f84dba DJS |
264 | destroy_workqueue(vfio_ccw_work_q); |
265 | } | |
63f1934d DJS |
266 | |
267 | return ret; | |
268 | } | |
269 | ||
270 | static void __exit vfio_ccw_sch_exit(void) | |
271 | { | |
272 | css_driver_unregister(&vfio_ccw_sch_driver); | |
273 | isc_unregister(VFIO_CCW_ISC); | |
bf42daed | 274 | kmem_cache_destroy(vfio_ccw_io_region); |
e5f84dba | 275 | destroy_workqueue(vfio_ccw_work_q); |
63f1934d DJS |
276 | } |
277 | module_init(vfio_ccw_sch_init); | |
278 | module_exit(vfio_ccw_sch_exit); | |
279 | ||
280 | MODULE_LICENSE("GPL v2"); |