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; |
Sebastian Ott | 52df783 | 2018-10-15 13:31:39 +0200 | [diff] [blame] | 25 | static struct kmem_cache *vfio_ccw_io_region; |
Dong Jia Shi | e5f84db | 2017-03-17 04:17:39 +0100 | [diff] [blame] | 26 | |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 27 | /* |
| 28 | * Helpers |
| 29 | */ |
Dong Jia Shi | 84cd8fc | 2017-03-17 04:17:33 +0100 | [diff] [blame] | 30 | int vfio_ccw_sch_quiesce(struct subchannel *sch) |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 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; |
Dong Jia Shi | e5f84db | 2017-03-17 04:17:39 +0100 | [diff] [blame] | 59 | flush_workqueue(vfio_ccw_work_q); |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 60 | ret = cio_cancel_halt_clear(sch, &iretry); |
| 61 | }; |
| 62 | |
| 63 | ret = cio_disable_subchannel(sch); |
| 64 | } while (ret == -EBUSY); |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 65 | out_unlock: |
Dong Jia Shi | bbe37e4 | 2017-03-17 04:17:40 +0100 | [diff] [blame] | 66 | private->state = VFIO_CCW_STATE_NOT_OPER; |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 67 | spin_unlock_irq(sch->lock); |
| 68 | return ret; |
| 69 | } |
| 70 | |
Dong Jia Shi | e5f84db | 2017-03-17 04:17:39 +0100 | [diff] [blame] | 71 | static void vfio_ccw_sch_io_todo(struct work_struct *work) |
| 72 | { |
| 73 | struct vfio_ccw_private *private; |
Dong Jia Shi | e5f84db | 2017-03-17 04:17:39 +0100 | [diff] [blame] | 74 | struct irb *irb; |
Dong Jia Shi | 4e149e4 | 2017-03-17 04:17:35 +0100 | [diff] [blame] | 75 | |
Dong Jia Shi | e5f84db | 2017-03-17 04:17:39 +0100 | [diff] [blame] | 76 | private = container_of(work, struct vfio_ccw_private, io_work); |
| 77 | irb = &private->irb; |
Dong Jia Shi | 4e149e4 | 2017-03-17 04:17:35 +0100 | [diff] [blame] | 78 | |
Dong Jia Shi | e5f84db | 2017-03-17 04:17:39 +0100 | [diff] [blame] | 79 | if (scsw_is_solicited(&irb->scsw)) { |
| 80 | cp_update_scsw(&private->cp, &irb->scsw); |
| 81 | cp_free(&private->cp); |
| 82 | } |
Eric Farman | c98e16b | 2018-09-21 22:40:12 +0200 | [diff] [blame] | 83 | memcpy(private->io_region->irb_area, irb, sizeof(*irb)); |
Dong Jia Shi | e5f84db | 2017-03-17 04:17:39 +0100 | [diff] [blame] | 84 | |
| 85 | if (private->io_trigger) |
| 86 | eventfd_signal(private->io_trigger, 1); |
Dong Jia Shi | 4e149e4 | 2017-03-17 04:17:35 +0100 | [diff] [blame] | 87 | |
Dong Jia Shi | bbe37e4 | 2017-03-17 04:17:40 +0100 | [diff] [blame] | 88 | if (private->mdev) |
| 89 | private->state = VFIO_CCW_STATE_IDLE; |
Dong Jia Shi | 4e149e4 | 2017-03-17 04:17:35 +0100 | [diff] [blame] | 90 | } |
| 91 | |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 92 | /* |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 93 | * Css driver callbacks |
| 94 | */ |
| 95 | static void vfio_ccw_sch_irq(struct subchannel *sch) |
| 96 | { |
| 97 | struct vfio_ccw_private *private = dev_get_drvdata(&sch->dev); |
| 98 | |
| 99 | inc_irq_stat(IRQIO_CIO); |
Dong Jia Shi | bbe37e4 | 2017-03-17 04:17:40 +0100 | [diff] [blame] | 100 | vfio_ccw_fsm_event(private, VFIO_CCW_EVENT_INTERRUPT); |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 101 | } |
| 102 | |
| 103 | static int vfio_ccw_sch_probe(struct subchannel *sch) |
| 104 | { |
| 105 | struct pmcw *pmcw = &sch->schib.pmcw; |
| 106 | struct vfio_ccw_private *private; |
| 107 | int ret; |
| 108 | |
| 109 | if (pmcw->qf) { |
| 110 | dev_warn(&sch->dev, "vfio: ccw: does not support QDIO: %s\n", |
| 111 | dev_name(&sch->dev)); |
| 112 | return -ENODEV; |
| 113 | } |
| 114 | |
| 115 | private = kzalloc(sizeof(*private), GFP_KERNEL | GFP_DMA); |
| 116 | if (!private) |
| 117 | return -ENOMEM; |
Eric Farman | c98e16b | 2018-09-21 22:40:12 +0200 | [diff] [blame] | 118 | |
Eric Farman | bf42dae | 2018-09-21 22:40:13 +0200 | [diff] [blame] | 119 | private->io_region = kmem_cache_zalloc(vfio_ccw_io_region, |
| 120 | GFP_KERNEL | GFP_DMA); |
Eric Farman | c98e16b | 2018-09-21 22:40:12 +0200 | [diff] [blame] | 121 | if (!private->io_region) { |
| 122 | kfree(private); |
| 123 | return -ENOMEM; |
| 124 | } |
| 125 | |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 126 | private->sch = sch; |
| 127 | dev_set_drvdata(&sch->dev, private); |
| 128 | |
| 129 | spin_lock_irq(sch->lock); |
Dong Jia Shi | bbe37e4 | 2017-03-17 04:17:40 +0100 | [diff] [blame] | 130 | private->state = VFIO_CCW_STATE_NOT_OPER; |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 131 | sch->isc = VFIO_CCW_ISC; |
| 132 | ret = cio_enable_subchannel(sch, (u32)(unsigned long)sch); |
| 133 | spin_unlock_irq(sch->lock); |
| 134 | if (ret) |
| 135 | goto out_free; |
| 136 | |
Dong Jia Shi | e5f84db | 2017-03-17 04:17:39 +0100 | [diff] [blame] | 137 | INIT_WORK(&private->io_work, vfio_ccw_sch_io_todo); |
Dong Jia Shi | 84cd8fc | 2017-03-17 04:17:33 +0100 | [diff] [blame] | 138 | atomic_set(&private->avail, 1); |
Dong Jia Shi | bbe37e4 | 2017-03-17 04:17:40 +0100 | [diff] [blame] | 139 | private->state = VFIO_CCW_STATE_STANDBY; |
Dong Jia Shi | 84cd8fc | 2017-03-17 04:17:33 +0100 | [diff] [blame] | 140 | |
Pierre Morel | 55e93ec | 2018-10-25 19:15:20 +0200 | [diff] [blame^] | 141 | ret = vfio_ccw_mdev_reg(sch); |
| 142 | if (ret) |
| 143 | goto out_disable; |
| 144 | |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 145 | return 0; |
| 146 | |
| 147 | out_disable: |
| 148 | cio_disable_subchannel(sch); |
| 149 | out_free: |
| 150 | dev_set_drvdata(&sch->dev, NULL); |
Eric Farman | bf42dae | 2018-09-21 22:40:13 +0200 | [diff] [blame] | 151 | kmem_cache_free(vfio_ccw_io_region, private->io_region); |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 152 | kfree(private); |
| 153 | return ret; |
| 154 | } |
| 155 | |
| 156 | static int vfio_ccw_sch_remove(struct subchannel *sch) |
| 157 | { |
| 158 | struct vfio_ccw_private *private = dev_get_drvdata(&sch->dev); |
| 159 | |
| 160 | vfio_ccw_sch_quiesce(sch); |
| 161 | |
Dong Jia Shi | 84cd8fc | 2017-03-17 04:17:33 +0100 | [diff] [blame] | 162 | vfio_ccw_mdev_unreg(sch); |
| 163 | |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 164 | dev_set_drvdata(&sch->dev, NULL); |
| 165 | |
Eric Farman | bf42dae | 2018-09-21 22:40:13 +0200 | [diff] [blame] | 166 | kmem_cache_free(vfio_ccw_io_region, private->io_region); |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 167 | kfree(private); |
| 168 | |
| 169 | return 0; |
| 170 | } |
| 171 | |
| 172 | static void vfio_ccw_sch_shutdown(struct subchannel *sch) |
| 173 | { |
| 174 | vfio_ccw_sch_quiesce(sch); |
| 175 | } |
| 176 | |
| 177 | /** |
| 178 | * vfio_ccw_sch_event - process subchannel event |
| 179 | * @sch: subchannel |
| 180 | * @process: non-zero if function is called in process context |
| 181 | * |
| 182 | * An unspecified event occurred for this subchannel. Adjust data according |
| 183 | * to the current operational state of the subchannel. Return zero when the |
| 184 | * event has been handled sufficiently or -EAGAIN when this function should |
| 185 | * be called again in process context. |
| 186 | */ |
| 187 | static int vfio_ccw_sch_event(struct subchannel *sch, int process) |
| 188 | { |
Dong Jia Shi | bbe37e4 | 2017-03-17 04:17:40 +0100 | [diff] [blame] | 189 | struct vfio_ccw_private *private = dev_get_drvdata(&sch->dev); |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 190 | unsigned long flags; |
Dong Jia Shi | 2c861d8 | 2018-05-02 09:25:59 +0200 | [diff] [blame] | 191 | int rc = -EAGAIN; |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 192 | |
| 193 | spin_lock_irqsave(sch->lock, flags); |
| 194 | if (!device_is_registered(&sch->dev)) |
| 195 | goto out_unlock; |
| 196 | |
| 197 | if (work_pending(&sch->todo_work)) |
| 198 | goto out_unlock; |
| 199 | |
| 200 | if (cio_update_schib(sch)) { |
Dong Jia Shi | bbe37e4 | 2017-03-17 04:17:40 +0100 | [diff] [blame] | 201 | vfio_ccw_fsm_event(private, VFIO_CCW_EVENT_NOT_OPER); |
Dong Jia Shi | 2c861d8 | 2018-05-02 09:25:59 +0200 | [diff] [blame] | 202 | rc = 0; |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 203 | goto out_unlock; |
| 204 | } |
| 205 | |
Dong Jia Shi | bbe37e4 | 2017-03-17 04:17:40 +0100 | [diff] [blame] | 206 | private = dev_get_drvdata(&sch->dev); |
| 207 | if (private->state == VFIO_CCW_STATE_NOT_OPER) { |
| 208 | private->state = private->mdev ? VFIO_CCW_STATE_IDLE : |
| 209 | VFIO_CCW_STATE_STANDBY; |
| 210 | } |
Dong Jia Shi | 2c861d8 | 2018-05-02 09:25:59 +0200 | [diff] [blame] | 211 | rc = 0; |
Dong Jia Shi | bbe37e4 | 2017-03-17 04:17:40 +0100 | [diff] [blame] | 212 | |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 213 | out_unlock: |
| 214 | spin_unlock_irqrestore(sch->lock, flags); |
| 215 | |
Dong Jia Shi | 2c861d8 | 2018-05-02 09:25:59 +0200 | [diff] [blame] | 216 | return rc; |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 217 | } |
| 218 | |
| 219 | static struct css_device_id vfio_ccw_sch_ids[] = { |
| 220 | { .match_flags = 0x1, .type = SUBCHANNEL_TYPE_IO, }, |
| 221 | { /* end of list */ }, |
| 222 | }; |
| 223 | MODULE_DEVICE_TABLE(css, vfio_ccw_sch_ids); |
| 224 | |
| 225 | static struct css_driver vfio_ccw_sch_driver = { |
| 226 | .drv = { |
| 227 | .name = "vfio_ccw", |
| 228 | .owner = THIS_MODULE, |
| 229 | }, |
| 230 | .subchannel_type = vfio_ccw_sch_ids, |
| 231 | .irq = vfio_ccw_sch_irq, |
| 232 | .probe = vfio_ccw_sch_probe, |
| 233 | .remove = vfio_ccw_sch_remove, |
| 234 | .shutdown = vfio_ccw_sch_shutdown, |
| 235 | .sch_event = vfio_ccw_sch_event, |
| 236 | }; |
| 237 | |
| 238 | static int __init vfio_ccw_sch_init(void) |
| 239 | { |
| 240 | int ret; |
| 241 | |
Dong Jia Shi | e5f84db | 2017-03-17 04:17:39 +0100 | [diff] [blame] | 242 | vfio_ccw_work_q = create_singlethread_workqueue("vfio-ccw"); |
| 243 | if (!vfio_ccw_work_q) |
| 244 | return -ENOMEM; |
| 245 | |
Eric Farman | bf42dae | 2018-09-21 22:40:13 +0200 | [diff] [blame] | 246 | vfio_ccw_io_region = kmem_cache_create_usercopy("vfio_ccw_io_region", |
| 247 | sizeof(struct ccw_io_region), 0, |
| 248 | SLAB_ACCOUNT, 0, |
| 249 | sizeof(struct ccw_io_region), NULL); |
| 250 | if (!vfio_ccw_io_region) { |
| 251 | destroy_workqueue(vfio_ccw_work_q); |
| 252 | return -ENOMEM; |
| 253 | } |
| 254 | |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 255 | isc_register(VFIO_CCW_ISC); |
| 256 | ret = css_driver_register(&vfio_ccw_sch_driver); |
Dong Jia Shi | e5f84db | 2017-03-17 04:17:39 +0100 | [diff] [blame] | 257 | if (ret) { |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 258 | isc_unregister(VFIO_CCW_ISC); |
Eric Farman | bf42dae | 2018-09-21 22:40:13 +0200 | [diff] [blame] | 259 | kmem_cache_destroy(vfio_ccw_io_region); |
Dong Jia Shi | e5f84db | 2017-03-17 04:17:39 +0100 | [diff] [blame] | 260 | destroy_workqueue(vfio_ccw_work_q); |
| 261 | } |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 262 | |
| 263 | return ret; |
| 264 | } |
| 265 | |
| 266 | static void __exit vfio_ccw_sch_exit(void) |
| 267 | { |
| 268 | css_driver_unregister(&vfio_ccw_sch_driver); |
| 269 | isc_unregister(VFIO_CCW_ISC); |
Eric Farman | bf42dae | 2018-09-21 22:40:13 +0200 | [diff] [blame] | 270 | kmem_cache_destroy(vfio_ccw_io_region); |
Dong Jia Shi | e5f84db | 2017-03-17 04:17:39 +0100 | [diff] [blame] | 271 | destroy_workqueue(vfio_ccw_work_q); |
Dong Jia Shi | 63f1934 | 2017-03-17 04:17:31 +0100 | [diff] [blame] | 272 | } |
| 273 | module_init(vfio_ccw_sch_init); |
| 274 | module_exit(vfio_ccw_sch_exit); |
| 275 | |
| 276 | MODULE_LICENSE("GPL v2"); |