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