Greg Kroah-Hartman | 724117b | 2017-11-14 18:38:02 +0100 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0 |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 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> |
Dong Jia Shi | 4e149e4 | 2017-03-17 04:17:35 +0100 | [diff] [blame] | 15 | #include <linux/uuid.h> |
| 16 | #include <linux/mdev.h> |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 17 | |
| 18 | #include <asm/isc.h> |
| 19 | |
Dong Jia Shi | 4e149e4 | 2017-03-17 04:17:35 +0100 | [diff] [blame] | 20 | #include "ioasm.h" |
| 21 | #include "css.h" |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 22 | #include "vfio_ccw_private.h" |
| 23 | |
Dong Jia Shi | e5f84db | 2017-03-17 04:17:39 +0100 | [diff] [blame] | 24 | struct workqueue_struct *vfio_ccw_work_q; |
| 25 | |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 26 | /* |
| 27 | * Helpers |
| 28 | */ |
Dong Jia Shi | 84cd8fc | 2017-03-17 04:17:33 +0100 | [diff] [blame] | 29 | int vfio_ccw_sch_quiesce(struct subchannel *sch) |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 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; |
Dong Jia Shi | e5f84db | 2017-03-17 04:17:39 +0100 | [diff] [blame] | 58 | flush_workqueue(vfio_ccw_work_q); |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 59 | ret = cio_cancel_halt_clear(sch, &iretry); |
| 60 | }; |
| 61 | |
| 62 | ret = cio_disable_subchannel(sch); |
| 63 | } while (ret == -EBUSY); |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 64 | out_unlock: |
Dong Jia Shi | bbe37e4 | 2017-03-17 04:17:40 +0100 | [diff] [blame] | 65 | private->state = VFIO_CCW_STATE_NOT_OPER; |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 66 | spin_unlock_irq(sch->lock); |
| 67 | return ret; |
| 68 | } |
| 69 | |
Dong Jia Shi | e5f84db | 2017-03-17 04:17:39 +0100 | [diff] [blame] | 70 | static void vfio_ccw_sch_io_todo(struct work_struct *work) |
| 71 | { |
| 72 | struct vfio_ccw_private *private; |
Dong Jia Shi | e5f84db | 2017-03-17 04:17:39 +0100 | [diff] [blame] | 73 | struct irb *irb; |
Dong Jia Shi | 4e149e4 | 2017-03-17 04:17:35 +0100 | [diff] [blame] | 74 | |
Dong Jia Shi | e5f84db | 2017-03-17 04:17:39 +0100 | [diff] [blame] | 75 | private = container_of(work, struct vfio_ccw_private, io_work); |
| 76 | irb = &private->irb; |
Dong Jia Shi | 4e149e4 | 2017-03-17 04:17:35 +0100 | [diff] [blame] | 77 | |
Dong Jia Shi | e5f84db | 2017-03-17 04:17:39 +0100 | [diff] [blame] | 78 | if (scsw_is_solicited(&irb->scsw)) { |
| 79 | cp_update_scsw(&private->cp, &irb->scsw); |
| 80 | cp_free(&private->cp); |
| 81 | } |
| 82 | memcpy(private->io_region.irb_area, irb, sizeof(*irb)); |
| 83 | |
| 84 | if (private->io_trigger) |
| 85 | eventfd_signal(private->io_trigger, 1); |
Dong Jia Shi | 4e149e4 | 2017-03-17 04:17:35 +0100 | [diff] [blame] | 86 | |
Dong Jia Shi | bbe37e4 | 2017-03-17 04:17:40 +0100 | [diff] [blame] | 87 | if (private->mdev) |
| 88 | private->state = VFIO_CCW_STATE_IDLE; |
Dong Jia Shi | 4e149e4 | 2017-03-17 04:17:35 +0100 | [diff] [blame] | 89 | } |
| 90 | |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 91 | /* |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 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); |
Dong Jia Shi | bbe37e4 | 2017-03-17 04:17:40 +0100 | [diff] [blame] | 99 | vfio_ccw_fsm_event(private, VFIO_CCW_EVENT_INTERRUPT); |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 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; |
| 117 | private->sch = sch; |
| 118 | dev_set_drvdata(&sch->dev, private); |
| 119 | |
| 120 | spin_lock_irq(sch->lock); |
Dong Jia Shi | bbe37e4 | 2017-03-17 04:17:40 +0100 | [diff] [blame] | 121 | private->state = VFIO_CCW_STATE_NOT_OPER; |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 122 | sch->isc = VFIO_CCW_ISC; |
| 123 | ret = cio_enable_subchannel(sch, (u32)(unsigned long)sch); |
| 124 | spin_unlock_irq(sch->lock); |
| 125 | if (ret) |
| 126 | goto out_free; |
| 127 | |
Dong Jia Shi | 84cd8fc | 2017-03-17 04:17:33 +0100 | [diff] [blame] | 128 | ret = vfio_ccw_mdev_reg(sch); |
| 129 | if (ret) |
Sebastian Ott | 36f6237 | 2017-05-15 15:49:07 +0200 | [diff] [blame] | 130 | goto out_disable; |
Dong Jia Shi | 84cd8fc | 2017-03-17 04:17:33 +0100 | [diff] [blame] | 131 | |
Dong Jia Shi | e5f84db | 2017-03-17 04:17:39 +0100 | [diff] [blame] | 132 | INIT_WORK(&private->io_work, vfio_ccw_sch_io_todo); |
Dong Jia Shi | 84cd8fc | 2017-03-17 04:17:33 +0100 | [diff] [blame] | 133 | atomic_set(&private->avail, 1); |
Dong Jia Shi | bbe37e4 | 2017-03-17 04:17:40 +0100 | [diff] [blame] | 134 | private->state = VFIO_CCW_STATE_STANDBY; |
Dong Jia Shi | 84cd8fc | 2017-03-17 04:17:33 +0100 | [diff] [blame] | 135 | |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 136 | return 0; |
| 137 | |
| 138 | out_disable: |
| 139 | cio_disable_subchannel(sch); |
| 140 | out_free: |
| 141 | dev_set_drvdata(&sch->dev, NULL); |
| 142 | kfree(private); |
| 143 | return ret; |
| 144 | } |
| 145 | |
| 146 | static int vfio_ccw_sch_remove(struct subchannel *sch) |
| 147 | { |
| 148 | struct vfio_ccw_private *private = dev_get_drvdata(&sch->dev); |
| 149 | |
| 150 | vfio_ccw_sch_quiesce(sch); |
| 151 | |
Dong Jia Shi | 84cd8fc | 2017-03-17 04:17:33 +0100 | [diff] [blame] | 152 | vfio_ccw_mdev_unreg(sch); |
| 153 | |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 154 | dev_set_drvdata(&sch->dev, NULL); |
| 155 | |
| 156 | kfree(private); |
| 157 | |
| 158 | return 0; |
| 159 | } |
| 160 | |
| 161 | static void vfio_ccw_sch_shutdown(struct subchannel *sch) |
| 162 | { |
| 163 | vfio_ccw_sch_quiesce(sch); |
| 164 | } |
| 165 | |
| 166 | /** |
| 167 | * vfio_ccw_sch_event - process subchannel event |
| 168 | * @sch: subchannel |
| 169 | * @process: non-zero if function is called in process context |
| 170 | * |
| 171 | * An unspecified event occurred for this subchannel. Adjust data according |
| 172 | * to the current operational state of the subchannel. Return zero when the |
| 173 | * event has been handled sufficiently or -EAGAIN when this function should |
| 174 | * be called again in process context. |
| 175 | */ |
| 176 | static int vfio_ccw_sch_event(struct subchannel *sch, int process) |
| 177 | { |
Dong Jia Shi | bbe37e4 | 2017-03-17 04:17:40 +0100 | [diff] [blame] | 178 | struct vfio_ccw_private *private = dev_get_drvdata(&sch->dev); |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 179 | unsigned long flags; |
Dong Jia Shi | 2c861d8 | 2018-05-02 09:25:59 +0200 | [diff] [blame] | 180 | int rc = -EAGAIN; |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 181 | |
| 182 | spin_lock_irqsave(sch->lock, flags); |
| 183 | if (!device_is_registered(&sch->dev)) |
| 184 | goto out_unlock; |
| 185 | |
| 186 | if (work_pending(&sch->todo_work)) |
| 187 | goto out_unlock; |
| 188 | |
| 189 | if (cio_update_schib(sch)) { |
Dong Jia Shi | bbe37e4 | 2017-03-17 04:17:40 +0100 | [diff] [blame] | 190 | vfio_ccw_fsm_event(private, VFIO_CCW_EVENT_NOT_OPER); |
Dong Jia Shi | 2c861d8 | 2018-05-02 09:25:59 +0200 | [diff] [blame] | 191 | rc = 0; |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 192 | goto out_unlock; |
| 193 | } |
| 194 | |
Dong Jia Shi | bbe37e4 | 2017-03-17 04:17:40 +0100 | [diff] [blame] | 195 | private = dev_get_drvdata(&sch->dev); |
| 196 | if (private->state == VFIO_CCW_STATE_NOT_OPER) { |
| 197 | private->state = private->mdev ? VFIO_CCW_STATE_IDLE : |
| 198 | VFIO_CCW_STATE_STANDBY; |
| 199 | } |
Dong Jia Shi | 2c861d8 | 2018-05-02 09:25:59 +0200 | [diff] [blame] | 200 | rc = 0; |
Dong Jia Shi | bbe37e4 | 2017-03-17 04:17:40 +0100 | [diff] [blame] | 201 | |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 202 | out_unlock: |
| 203 | spin_unlock_irqrestore(sch->lock, flags); |
| 204 | |
Dong Jia Shi | 2c861d8 | 2018-05-02 09:25:59 +0200 | [diff] [blame] | 205 | return rc; |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 206 | } |
| 207 | |
| 208 | static struct css_device_id vfio_ccw_sch_ids[] = { |
| 209 | { .match_flags = 0x1, .type = SUBCHANNEL_TYPE_IO, }, |
| 210 | { /* end of list */ }, |
| 211 | }; |
| 212 | MODULE_DEVICE_TABLE(css, vfio_ccw_sch_ids); |
| 213 | |
| 214 | static struct css_driver vfio_ccw_sch_driver = { |
| 215 | .drv = { |
| 216 | .name = "vfio_ccw", |
| 217 | .owner = THIS_MODULE, |
| 218 | }, |
| 219 | .subchannel_type = vfio_ccw_sch_ids, |
| 220 | .irq = vfio_ccw_sch_irq, |
| 221 | .probe = vfio_ccw_sch_probe, |
| 222 | .remove = vfio_ccw_sch_remove, |
| 223 | .shutdown = vfio_ccw_sch_shutdown, |
| 224 | .sch_event = vfio_ccw_sch_event, |
| 225 | }; |
| 226 | |
| 227 | static int __init vfio_ccw_sch_init(void) |
| 228 | { |
| 229 | int ret; |
| 230 | |
Dong Jia Shi | e5f84db | 2017-03-17 04:17:39 +0100 | [diff] [blame] | 231 | vfio_ccw_work_q = create_singlethread_workqueue("vfio-ccw"); |
| 232 | if (!vfio_ccw_work_q) |
| 233 | return -ENOMEM; |
| 234 | |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 235 | isc_register(VFIO_CCW_ISC); |
| 236 | ret = css_driver_register(&vfio_ccw_sch_driver); |
Dong Jia Shi | e5f84db | 2017-03-17 04:17:39 +0100 | [diff] [blame] | 237 | if (ret) { |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 238 | isc_unregister(VFIO_CCW_ISC); |
Dong Jia Shi | e5f84db | 2017-03-17 04:17:39 +0100 | [diff] [blame] | 239 | destroy_workqueue(vfio_ccw_work_q); |
| 240 | } |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 241 | |
| 242 | return ret; |
| 243 | } |
| 244 | |
| 245 | static void __exit vfio_ccw_sch_exit(void) |
| 246 | { |
| 247 | css_driver_unregister(&vfio_ccw_sch_driver); |
| 248 | isc_unregister(VFIO_CCW_ISC); |
Dong Jia Shi | e5f84db | 2017-03-17 04:17:39 +0100 | [diff] [blame] | 249 | destroy_workqueue(vfio_ccw_work_q); |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 250 | } |
| 251 | module_init(vfio_ccw_sch_init); |
| 252 | module_exit(vfio_ccw_sch_exit); |
| 253 | |
| 254 | MODULE_LICENSE("GPL v2"); |