1fb1ff4c1SBharat Bhushan // SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) 2fb1ff4c1SBharat Bhushan /* 3fb1ff4c1SBharat Bhushan * Copyright 2013-2016 Freescale Semiconductor Inc. 4fb1ff4c1SBharat Bhushan * Copyright 2016-2017,2019-2020 NXP 5fb1ff4c1SBharat Bhushan */ 6fb1ff4c1SBharat Bhushan 7fb1ff4c1SBharat Bhushan #include <linux/device.h> 8fb1ff4c1SBharat Bhushan #include <linux/iommu.h> 9fb1ff4c1SBharat Bhushan #include <linux/module.h> 10fb1ff4c1SBharat Bhushan #include <linux/mutex.h> 11fb1ff4c1SBharat Bhushan #include <linux/slab.h> 12fb1ff4c1SBharat Bhushan #include <linux/types.h> 13fb1ff4c1SBharat Bhushan #include <linux/vfio.h> 14fb1ff4c1SBharat Bhushan #include <linux/fsl/mc.h> 151bb141edSDiana Craciun #include <linux/delay.h> 1683e49179SDiana Craciun #include <linux/io-64-nonatomic-hi-lo.h> 17fb1ff4c1SBharat Bhushan 18fb1ff4c1SBharat Bhushan #include "vfio_fsl_mc_private.h" 19fb1ff4c1SBharat Bhushan 20704f5082SDiana Craciun static struct fsl_mc_driver vfio_fsl_mc_driver; 21704f5082SDiana Craciun 22*da119f38SJason Gunthorpe static int vfio_fsl_mc_open_device(struct vfio_device *core_vdev) 23f2ba7e8cSDiana Craciun { 24*da119f38SJason Gunthorpe struct vfio_fsl_mc_device *vdev = 25*da119f38SJason Gunthorpe container_of(core_vdev, struct vfio_fsl_mc_device, vdev); 26df747bcdSDiana Craciun struct fsl_mc_device *mc_dev = vdev->mc_dev; 27df747bcdSDiana Craciun int count = mc_dev->obj_desc.region_count; 28df747bcdSDiana Craciun int i; 29df747bcdSDiana Craciun 30df747bcdSDiana Craciun vdev->regions = kcalloc(count, sizeof(struct vfio_fsl_mc_region), 31df747bcdSDiana Craciun GFP_KERNEL); 32df747bcdSDiana Craciun if (!vdev->regions) 33df747bcdSDiana Craciun return -ENOMEM; 34df747bcdSDiana Craciun 35df747bcdSDiana Craciun for (i = 0; i < count; i++) { 36df747bcdSDiana Craciun struct resource *res = &mc_dev->regions[i]; 3767247289SDiana Craciun int no_mmap = is_fsl_mc_bus_dprc(mc_dev); 38df747bcdSDiana Craciun 39df747bcdSDiana Craciun vdev->regions[i].addr = res->start; 40df747bcdSDiana Craciun vdev->regions[i].size = resource_size(res); 41df747bcdSDiana Craciun vdev->regions[i].type = mc_dev->regions[i].flags & IORESOURCE_BITS; 4267247289SDiana Craciun /* 4367247289SDiana Craciun * Only regions addressed with PAGE granularity may be 4467247289SDiana Craciun * MMAPed securely. 4567247289SDiana Craciun */ 4667247289SDiana Craciun if (!no_mmap && !(vdev->regions[i].addr & ~PAGE_MASK) && 4767247289SDiana Craciun !(vdev->regions[i].size & ~PAGE_MASK)) 4867247289SDiana Craciun vdev->regions[i].flags |= 4967247289SDiana Craciun VFIO_REGION_INFO_FLAG_MMAP; 501bb141edSDiana Craciun vdev->regions[i].flags |= VFIO_REGION_INFO_FLAG_READ; 511bb141edSDiana Craciun if (!(mc_dev->regions[i].flags & IORESOURCE_READONLY)) 521bb141edSDiana Craciun vdev->regions[i].flags |= VFIO_REGION_INFO_FLAG_WRITE; 53df747bcdSDiana Craciun } 54fb1ff4c1SBharat Bhushan 55fb1ff4c1SBharat Bhushan return 0; 56fb1ff4c1SBharat Bhushan } 57fb1ff4c1SBharat Bhushan 58df747bcdSDiana Craciun static void vfio_fsl_mc_regions_cleanup(struct vfio_fsl_mc_device *vdev) 59df747bcdSDiana Craciun { 601bb141edSDiana Craciun struct fsl_mc_device *mc_dev = vdev->mc_dev; 611bb141edSDiana Craciun int i; 621bb141edSDiana Craciun 631bb141edSDiana Craciun for (i = 0; i < mc_dev->obj_desc.region_count; i++) 641bb141edSDiana Craciun iounmap(vdev->regions[i].ioaddr); 65df747bcdSDiana Craciun kfree(vdev->regions); 66df747bcdSDiana Craciun } 67df747bcdSDiana Craciun 68*da119f38SJason Gunthorpe 69*da119f38SJason Gunthorpe static void vfio_fsl_mc_close_device(struct vfio_device *core_vdev) 70df747bcdSDiana Craciun { 716df62c5bSJason Gunthorpe struct vfio_fsl_mc_device *vdev = 726df62c5bSJason Gunthorpe container_of(core_vdev, struct vfio_fsl_mc_device, vdev); 73cc0ee20bSDiana Craciun struct fsl_mc_device *mc_dev = vdev->mc_dev; 74cc0ee20bSDiana Craciun struct device *cont_dev = fsl_mc_cont_dev(&mc_dev->dev); 75cc0ee20bSDiana Craciun struct fsl_mc_device *mc_cont = to_fsl_mc_device(cont_dev); 76*da119f38SJason Gunthorpe int ret; 77cc0ee20bSDiana Craciun 78df747bcdSDiana Craciun vfio_fsl_mc_regions_cleanup(vdev); 79df747bcdSDiana Craciun 80cc0ee20bSDiana Craciun /* reset the device before cleaning up the interrupts */ 81*da119f38SJason Gunthorpe ret = dprc_reset_container(mc_cont->mc_io, 0, mc_cont->mc_handle, 82cc0ee20bSDiana Craciun mc_cont->obj_desc.id, 83cc0ee20bSDiana Craciun DPRC_RESET_OPTION_NON_RECURSIVE); 84cc0ee20bSDiana Craciun 85*da119f38SJason Gunthorpe if (WARN_ON(ret)) 86*da119f38SJason Gunthorpe dev_warn(&mc_cont->dev, 87*da119f38SJason Gunthorpe "VFIO_FLS_MC: reset device has failed (%d)\n", ret); 88cc0ee20bSDiana Craciun 89cc0ee20bSDiana Craciun vfio_fsl_mc_irqs_cleanup(vdev); 90cc0ee20bSDiana Craciun 91cc0ee20bSDiana Craciun fsl_mc_cleanup_irq_pool(mc_cont); 92cc0ee20bSDiana Craciun } 93cc0ee20bSDiana Craciun 946df62c5bSJason Gunthorpe static long vfio_fsl_mc_ioctl(struct vfio_device *core_vdev, 956df62c5bSJason Gunthorpe unsigned int cmd, unsigned long arg) 96fb1ff4c1SBharat Bhushan { 97f97f4c04SDiana Craciun unsigned long minsz; 986df62c5bSJason Gunthorpe struct vfio_fsl_mc_device *vdev = 996df62c5bSJason Gunthorpe container_of(core_vdev, struct vfio_fsl_mc_device, vdev); 100f97f4c04SDiana Craciun struct fsl_mc_device *mc_dev = vdev->mc_dev; 101f97f4c04SDiana Craciun 102fb1ff4c1SBharat Bhushan switch (cmd) { 103fb1ff4c1SBharat Bhushan case VFIO_DEVICE_GET_INFO: 104fb1ff4c1SBharat Bhushan { 105f97f4c04SDiana Craciun struct vfio_device_info info; 106f97f4c04SDiana Craciun 107f97f4c04SDiana Craciun minsz = offsetofend(struct vfio_device_info, num_irqs); 108f97f4c04SDiana Craciun 109f97f4c04SDiana Craciun if (copy_from_user(&info, (void __user *)arg, minsz)) 110f97f4c04SDiana Craciun return -EFAULT; 111f97f4c04SDiana Craciun 112f97f4c04SDiana Craciun if (info.argsz < minsz) 113f97f4c04SDiana Craciun return -EINVAL; 114f97f4c04SDiana Craciun 115f97f4c04SDiana Craciun info.flags = VFIO_DEVICE_FLAGS_FSL_MC; 116ac93ab2bSDiana Craciun 117ac93ab2bSDiana Craciun if (is_fsl_mc_bus_dprc(mc_dev)) 118ac93ab2bSDiana Craciun info.flags |= VFIO_DEVICE_FLAGS_RESET; 119ac93ab2bSDiana Craciun 120f97f4c04SDiana Craciun info.num_regions = mc_dev->obj_desc.region_count; 121f97f4c04SDiana Craciun info.num_irqs = mc_dev->obj_desc.irq_count; 122f97f4c04SDiana Craciun 123f97f4c04SDiana Craciun return copy_to_user((void __user *)arg, &info, minsz) ? 124f97f4c04SDiana Craciun -EFAULT : 0; 125fb1ff4c1SBharat Bhushan } 126fb1ff4c1SBharat Bhushan case VFIO_DEVICE_GET_REGION_INFO: 127fb1ff4c1SBharat Bhushan { 128df747bcdSDiana Craciun struct vfio_region_info info; 129df747bcdSDiana Craciun 130df747bcdSDiana Craciun minsz = offsetofend(struct vfio_region_info, offset); 131df747bcdSDiana Craciun 132df747bcdSDiana Craciun if (copy_from_user(&info, (void __user *)arg, minsz)) 133df747bcdSDiana Craciun return -EFAULT; 134df747bcdSDiana Craciun 135df747bcdSDiana Craciun if (info.argsz < minsz) 136df747bcdSDiana Craciun return -EINVAL; 137df747bcdSDiana Craciun 138df747bcdSDiana Craciun if (info.index >= mc_dev->obj_desc.region_count) 139df747bcdSDiana Craciun return -EINVAL; 140df747bcdSDiana Craciun 141df747bcdSDiana Craciun /* map offset to the physical address */ 142df747bcdSDiana Craciun info.offset = VFIO_FSL_MC_INDEX_TO_OFFSET(info.index); 143df747bcdSDiana Craciun info.size = vdev->regions[info.index].size; 144df747bcdSDiana Craciun info.flags = vdev->regions[info.index].flags; 145df747bcdSDiana Craciun 14609699e56SDan Carpenter if (copy_to_user((void __user *)arg, &info, minsz)) 14709699e56SDan Carpenter return -EFAULT; 14809699e56SDan Carpenter return 0; 149fb1ff4c1SBharat Bhushan } 150fb1ff4c1SBharat Bhushan case VFIO_DEVICE_GET_IRQ_INFO: 151fb1ff4c1SBharat Bhushan { 1522e0d2956SDiana Craciun struct vfio_irq_info info; 1532e0d2956SDiana Craciun 1542e0d2956SDiana Craciun minsz = offsetofend(struct vfio_irq_info, count); 1552e0d2956SDiana Craciun if (copy_from_user(&info, (void __user *)arg, minsz)) 1562e0d2956SDiana Craciun return -EFAULT; 1572e0d2956SDiana Craciun 1582e0d2956SDiana Craciun if (info.argsz < minsz) 1592e0d2956SDiana Craciun return -EINVAL; 1602e0d2956SDiana Craciun 1612e0d2956SDiana Craciun if (info.index >= mc_dev->obj_desc.irq_count) 1622e0d2956SDiana Craciun return -EINVAL; 1632e0d2956SDiana Craciun 1642e0d2956SDiana Craciun info.flags = VFIO_IRQ_INFO_EVENTFD; 1652e0d2956SDiana Craciun info.count = 1; 1662e0d2956SDiana Craciun 16709699e56SDan Carpenter if (copy_to_user((void __user *)arg, &info, minsz)) 16809699e56SDan Carpenter return -EFAULT; 16909699e56SDan Carpenter return 0; 170fb1ff4c1SBharat Bhushan } 171fb1ff4c1SBharat Bhushan case VFIO_DEVICE_SET_IRQS: 172fb1ff4c1SBharat Bhushan { 1732e0d2956SDiana Craciun struct vfio_irq_set hdr; 1742e0d2956SDiana Craciun u8 *data = NULL; 1752e0d2956SDiana Craciun int ret = 0; 1762e0d2956SDiana Craciun size_t data_size = 0; 1772e0d2956SDiana Craciun 1782e0d2956SDiana Craciun minsz = offsetofend(struct vfio_irq_set, count); 1792e0d2956SDiana Craciun 1802e0d2956SDiana Craciun if (copy_from_user(&hdr, (void __user *)arg, minsz)) 1812e0d2956SDiana Craciun return -EFAULT; 1822e0d2956SDiana Craciun 1832e0d2956SDiana Craciun ret = vfio_set_irqs_validate_and_prepare(&hdr, mc_dev->obj_desc.irq_count, 1842e0d2956SDiana Craciun mc_dev->obj_desc.irq_count, &data_size); 1852e0d2956SDiana Craciun if (ret) 1862e0d2956SDiana Craciun return ret; 1872e0d2956SDiana Craciun 1882e0d2956SDiana Craciun if (data_size) { 1892e0d2956SDiana Craciun data = memdup_user((void __user *)(arg + minsz), 1902e0d2956SDiana Craciun data_size); 1912e0d2956SDiana Craciun if (IS_ERR(data)) 1922e0d2956SDiana Craciun return PTR_ERR(data); 1932e0d2956SDiana Craciun } 1942e0d2956SDiana Craciun 1952e0d2956SDiana Craciun mutex_lock(&vdev->igate); 1962e0d2956SDiana Craciun ret = vfio_fsl_mc_set_irqs_ioctl(vdev, hdr.flags, 1972e0d2956SDiana Craciun hdr.index, hdr.start, 1982e0d2956SDiana Craciun hdr.count, data); 1992e0d2956SDiana Craciun mutex_unlock(&vdev->igate); 2002e0d2956SDiana Craciun kfree(data); 2012e0d2956SDiana Craciun 2022e0d2956SDiana Craciun return ret; 203fb1ff4c1SBharat Bhushan } 204fb1ff4c1SBharat Bhushan case VFIO_DEVICE_RESET: 205fb1ff4c1SBharat Bhushan { 206ac93ab2bSDiana Craciun int ret; 207ac93ab2bSDiana Craciun struct fsl_mc_device *mc_dev = vdev->mc_dev; 208ac93ab2bSDiana Craciun 209ac93ab2bSDiana Craciun /* reset is supported only for the DPRC */ 210ac93ab2bSDiana Craciun if (!is_fsl_mc_bus_dprc(mc_dev)) 211fb1ff4c1SBharat Bhushan return -ENOTTY; 212ac93ab2bSDiana Craciun 213ac93ab2bSDiana Craciun ret = dprc_reset_container(mc_dev->mc_io, 0, 214ac93ab2bSDiana Craciun mc_dev->mc_handle, 215ac93ab2bSDiana Craciun mc_dev->obj_desc.id, 216ac93ab2bSDiana Craciun DPRC_RESET_OPTION_NON_RECURSIVE); 217ac93ab2bSDiana Craciun return ret; 218ac93ab2bSDiana Craciun 219fb1ff4c1SBharat Bhushan } 220fb1ff4c1SBharat Bhushan default: 221fb1ff4c1SBharat Bhushan return -ENOTTY; 222fb1ff4c1SBharat Bhushan } 223fb1ff4c1SBharat Bhushan } 224fb1ff4c1SBharat Bhushan 2256df62c5bSJason Gunthorpe static ssize_t vfio_fsl_mc_read(struct vfio_device *core_vdev, char __user *buf, 226fb1ff4c1SBharat Bhushan size_t count, loff_t *ppos) 227fb1ff4c1SBharat Bhushan { 2286df62c5bSJason Gunthorpe struct vfio_fsl_mc_device *vdev = 2296df62c5bSJason Gunthorpe container_of(core_vdev, struct vfio_fsl_mc_device, vdev); 2301bb141edSDiana Craciun unsigned int index = VFIO_FSL_MC_OFFSET_TO_INDEX(*ppos); 2311bb141edSDiana Craciun loff_t off = *ppos & VFIO_FSL_MC_OFFSET_MASK; 2321bb141edSDiana Craciun struct fsl_mc_device *mc_dev = vdev->mc_dev; 2331bb141edSDiana Craciun struct vfio_fsl_mc_region *region; 2341bb141edSDiana Craciun u64 data[8]; 2351bb141edSDiana Craciun int i; 2361bb141edSDiana Craciun 2371bb141edSDiana Craciun if (index >= mc_dev->obj_desc.region_count) 238fb1ff4c1SBharat Bhushan return -EINVAL; 2391bb141edSDiana Craciun 2401bb141edSDiana Craciun region = &vdev->regions[index]; 2411bb141edSDiana Craciun 2421bb141edSDiana Craciun if (!(region->flags & VFIO_REGION_INFO_FLAG_READ)) 2431bb141edSDiana Craciun return -EINVAL; 2441bb141edSDiana Craciun 2451bb141edSDiana Craciun if (!region->ioaddr) { 2461bb141edSDiana Craciun region->ioaddr = ioremap(region->addr, region->size); 2471bb141edSDiana Craciun if (!region->ioaddr) 2481bb141edSDiana Craciun return -ENOMEM; 2491bb141edSDiana Craciun } 2501bb141edSDiana Craciun 2511bb141edSDiana Craciun if (count != 64 || off != 0) 2521bb141edSDiana Craciun return -EINVAL; 2531bb141edSDiana Craciun 2541bb141edSDiana Craciun for (i = 7; i >= 0; i--) 2551bb141edSDiana Craciun data[i] = readq(region->ioaddr + i * sizeof(uint64_t)); 2561bb141edSDiana Craciun 2571bb141edSDiana Craciun if (copy_to_user(buf, data, 64)) 2581bb141edSDiana Craciun return -EFAULT; 2591bb141edSDiana Craciun 2601bb141edSDiana Craciun return count; 2611bb141edSDiana Craciun } 2621bb141edSDiana Craciun 2631bb141edSDiana Craciun #define MC_CMD_COMPLETION_TIMEOUT_MS 5000 2641bb141edSDiana Craciun #define MC_CMD_COMPLETION_POLLING_MAX_SLEEP_USECS 500 2651bb141edSDiana Craciun 2661bb141edSDiana Craciun static int vfio_fsl_mc_send_command(void __iomem *ioaddr, uint64_t *cmd_data) 2671bb141edSDiana Craciun { 2681bb141edSDiana Craciun int i; 2691bb141edSDiana Craciun enum mc_cmd_status status; 2701bb141edSDiana Craciun unsigned long timeout_usecs = MC_CMD_COMPLETION_TIMEOUT_MS * 1000; 2711bb141edSDiana Craciun 2721bb141edSDiana Craciun /* Write at command parameter into portal */ 2731bb141edSDiana Craciun for (i = 7; i >= 1; i--) 2741bb141edSDiana Craciun writeq_relaxed(cmd_data[i], ioaddr + i * sizeof(uint64_t)); 2751bb141edSDiana Craciun 2761bb141edSDiana Craciun /* Write command header in the end */ 2771bb141edSDiana Craciun writeq(cmd_data[0], ioaddr); 2781bb141edSDiana Craciun 2791bb141edSDiana Craciun /* Wait for response before returning to user-space 2801bb141edSDiana Craciun * This can be optimized in future to even prepare response 2811bb141edSDiana Craciun * before returning to user-space and avoid read ioctl. 2821bb141edSDiana Craciun */ 2831bb141edSDiana Craciun for (;;) { 2841bb141edSDiana Craciun u64 header; 2851bb141edSDiana Craciun struct mc_cmd_header *resp_hdr; 2861bb141edSDiana Craciun 2871bb141edSDiana Craciun header = cpu_to_le64(readq_relaxed(ioaddr)); 2881bb141edSDiana Craciun 2891bb141edSDiana Craciun resp_hdr = (struct mc_cmd_header *)&header; 2901bb141edSDiana Craciun status = (enum mc_cmd_status)resp_hdr->status; 2911bb141edSDiana Craciun if (status != MC_CMD_STATUS_READY) 2921bb141edSDiana Craciun break; 2931bb141edSDiana Craciun 2941bb141edSDiana Craciun udelay(MC_CMD_COMPLETION_POLLING_MAX_SLEEP_USECS); 2951bb141edSDiana Craciun timeout_usecs -= MC_CMD_COMPLETION_POLLING_MAX_SLEEP_USECS; 2961bb141edSDiana Craciun if (timeout_usecs == 0) 2971bb141edSDiana Craciun return -ETIMEDOUT; 2981bb141edSDiana Craciun } 2991bb141edSDiana Craciun 3001bb141edSDiana Craciun return 0; 301fb1ff4c1SBharat Bhushan } 302fb1ff4c1SBharat Bhushan 3036df62c5bSJason Gunthorpe static ssize_t vfio_fsl_mc_write(struct vfio_device *core_vdev, 3046df62c5bSJason Gunthorpe const char __user *buf, size_t count, 3056df62c5bSJason Gunthorpe loff_t *ppos) 306fb1ff4c1SBharat Bhushan { 3076df62c5bSJason Gunthorpe struct vfio_fsl_mc_device *vdev = 3086df62c5bSJason Gunthorpe container_of(core_vdev, struct vfio_fsl_mc_device, vdev); 3091bb141edSDiana Craciun unsigned int index = VFIO_FSL_MC_OFFSET_TO_INDEX(*ppos); 3101bb141edSDiana Craciun loff_t off = *ppos & VFIO_FSL_MC_OFFSET_MASK; 3111bb141edSDiana Craciun struct fsl_mc_device *mc_dev = vdev->mc_dev; 3121bb141edSDiana Craciun struct vfio_fsl_mc_region *region; 3131bb141edSDiana Craciun u64 data[8]; 3141bb141edSDiana Craciun int ret; 3151bb141edSDiana Craciun 3161bb141edSDiana Craciun if (index >= mc_dev->obj_desc.region_count) 317fb1ff4c1SBharat Bhushan return -EINVAL; 3181bb141edSDiana Craciun 3191bb141edSDiana Craciun region = &vdev->regions[index]; 3201bb141edSDiana Craciun 3211bb141edSDiana Craciun if (!(region->flags & VFIO_REGION_INFO_FLAG_WRITE)) 3221bb141edSDiana Craciun return -EINVAL; 3231bb141edSDiana Craciun 3241bb141edSDiana Craciun if (!region->ioaddr) { 3251bb141edSDiana Craciun region->ioaddr = ioremap(region->addr, region->size); 3261bb141edSDiana Craciun if (!region->ioaddr) 3271bb141edSDiana Craciun return -ENOMEM; 3281bb141edSDiana Craciun } 3291bb141edSDiana Craciun 3301bb141edSDiana Craciun if (count != 64 || off != 0) 3311bb141edSDiana Craciun return -EINVAL; 3321bb141edSDiana Craciun 3331bb141edSDiana Craciun if (copy_from_user(&data, buf, 64)) 3341bb141edSDiana Craciun return -EFAULT; 3351bb141edSDiana Craciun 3361bb141edSDiana Craciun ret = vfio_fsl_mc_send_command(region->ioaddr, data); 3371bb141edSDiana Craciun if (ret) 3381bb141edSDiana Craciun return ret; 3391bb141edSDiana Craciun 3401bb141edSDiana Craciun return count; 3411bb141edSDiana Craciun 342fb1ff4c1SBharat Bhushan } 343fb1ff4c1SBharat Bhushan 34467247289SDiana Craciun static int vfio_fsl_mc_mmap_mmio(struct vfio_fsl_mc_region region, 34567247289SDiana Craciun struct vm_area_struct *vma) 34667247289SDiana Craciun { 34767247289SDiana Craciun u64 size = vma->vm_end - vma->vm_start; 34867247289SDiana Craciun u64 pgoff, base; 34967247289SDiana Craciun u8 region_cacheable; 35067247289SDiana Craciun 35167247289SDiana Craciun pgoff = vma->vm_pgoff & 35267247289SDiana Craciun ((1U << (VFIO_FSL_MC_OFFSET_SHIFT - PAGE_SHIFT)) - 1); 35367247289SDiana Craciun base = pgoff << PAGE_SHIFT; 35467247289SDiana Craciun 35567247289SDiana Craciun if (region.size < PAGE_SIZE || base + size > region.size) 35667247289SDiana Craciun return -EINVAL; 35767247289SDiana Craciun 35867247289SDiana Craciun region_cacheable = (region.type & FSL_MC_REGION_CACHEABLE) && 35967247289SDiana Craciun (region.type & FSL_MC_REGION_SHAREABLE); 36067247289SDiana Craciun if (!region_cacheable) 36167247289SDiana Craciun vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); 36267247289SDiana Craciun 36367247289SDiana Craciun vma->vm_pgoff = (region.addr >> PAGE_SHIFT) + pgoff; 36467247289SDiana Craciun 36567247289SDiana Craciun return remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, 36667247289SDiana Craciun size, vma->vm_page_prot); 36767247289SDiana Craciun } 36867247289SDiana Craciun 3696df62c5bSJason Gunthorpe static int vfio_fsl_mc_mmap(struct vfio_device *core_vdev, 3706df62c5bSJason Gunthorpe struct vm_area_struct *vma) 371fb1ff4c1SBharat Bhushan { 3726df62c5bSJason Gunthorpe struct vfio_fsl_mc_device *vdev = 3736df62c5bSJason Gunthorpe container_of(core_vdev, struct vfio_fsl_mc_device, vdev); 37467247289SDiana Craciun struct fsl_mc_device *mc_dev = vdev->mc_dev; 37569848cd6SDan Carpenter unsigned int index; 37667247289SDiana Craciun 37767247289SDiana Craciun index = vma->vm_pgoff >> (VFIO_FSL_MC_OFFSET_SHIFT - PAGE_SHIFT); 37867247289SDiana Craciun 37967247289SDiana Craciun if (vma->vm_end < vma->vm_start) 380fb1ff4c1SBharat Bhushan return -EINVAL; 38167247289SDiana Craciun if (vma->vm_start & ~PAGE_MASK) 38267247289SDiana Craciun return -EINVAL; 38367247289SDiana Craciun if (vma->vm_end & ~PAGE_MASK) 38467247289SDiana Craciun return -EINVAL; 38567247289SDiana Craciun if (!(vma->vm_flags & VM_SHARED)) 38667247289SDiana Craciun return -EINVAL; 38767247289SDiana Craciun if (index >= mc_dev->obj_desc.region_count) 38867247289SDiana Craciun return -EINVAL; 38967247289SDiana Craciun 39067247289SDiana Craciun if (!(vdev->regions[index].flags & VFIO_REGION_INFO_FLAG_MMAP)) 39167247289SDiana Craciun return -EINVAL; 39267247289SDiana Craciun 39367247289SDiana Craciun if (!(vdev->regions[index].flags & VFIO_REGION_INFO_FLAG_READ) 39467247289SDiana Craciun && (vma->vm_flags & VM_READ)) 39567247289SDiana Craciun return -EINVAL; 39667247289SDiana Craciun 39767247289SDiana Craciun if (!(vdev->regions[index].flags & VFIO_REGION_INFO_FLAG_WRITE) 39867247289SDiana Craciun && (vma->vm_flags & VM_WRITE)) 39967247289SDiana Craciun return -EINVAL; 40067247289SDiana Craciun 40167247289SDiana Craciun vma->vm_private_data = mc_dev; 40267247289SDiana Craciun 40367247289SDiana Craciun return vfio_fsl_mc_mmap_mmio(vdev->regions[index], vma); 404fb1ff4c1SBharat Bhushan } 405fb1ff4c1SBharat Bhushan 406fb1ff4c1SBharat Bhushan static const struct vfio_device_ops vfio_fsl_mc_ops = { 407fb1ff4c1SBharat Bhushan .name = "vfio-fsl-mc", 408*da119f38SJason Gunthorpe .open_device = vfio_fsl_mc_open_device, 409*da119f38SJason Gunthorpe .close_device = vfio_fsl_mc_close_device, 410fb1ff4c1SBharat Bhushan .ioctl = vfio_fsl_mc_ioctl, 411fb1ff4c1SBharat Bhushan .read = vfio_fsl_mc_read, 412fb1ff4c1SBharat Bhushan .write = vfio_fsl_mc_write, 413fb1ff4c1SBharat Bhushan .mmap = vfio_fsl_mc_mmap, 414fb1ff4c1SBharat Bhushan }; 415fb1ff4c1SBharat Bhushan 416704f5082SDiana Craciun static int vfio_fsl_mc_bus_notifier(struct notifier_block *nb, 417704f5082SDiana Craciun unsigned long action, void *data) 418704f5082SDiana Craciun { 419704f5082SDiana Craciun struct vfio_fsl_mc_device *vdev = container_of(nb, 420704f5082SDiana Craciun struct vfio_fsl_mc_device, nb); 421704f5082SDiana Craciun struct device *dev = data; 422704f5082SDiana Craciun struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev); 423704f5082SDiana Craciun struct fsl_mc_device *mc_cont = to_fsl_mc_device(mc_dev->dev.parent); 424704f5082SDiana Craciun 425704f5082SDiana Craciun if (action == BUS_NOTIFY_ADD_DEVICE && 426704f5082SDiana Craciun vdev->mc_dev == mc_cont) { 427704f5082SDiana Craciun mc_dev->driver_override = kasprintf(GFP_KERNEL, "%s", 428704f5082SDiana Craciun vfio_fsl_mc_ops.name); 429704f5082SDiana Craciun if (!mc_dev->driver_override) 430704f5082SDiana Craciun dev_warn(dev, "VFIO_FSL_MC: Setting driver override for device in dprc %s failed\n", 431704f5082SDiana Craciun dev_name(&mc_cont->dev)); 432704f5082SDiana Craciun else 433704f5082SDiana Craciun dev_info(dev, "VFIO_FSL_MC: Setting driver override for device in dprc %s\n", 434704f5082SDiana Craciun dev_name(&mc_cont->dev)); 435704f5082SDiana Craciun } else if (action == BUS_NOTIFY_BOUND_DRIVER && 436704f5082SDiana Craciun vdev->mc_dev == mc_cont) { 437704f5082SDiana Craciun struct fsl_mc_driver *mc_drv = to_fsl_mc_driver(dev->driver); 438704f5082SDiana Craciun 439704f5082SDiana Craciun if (mc_drv && mc_drv != &vfio_fsl_mc_driver) 440704f5082SDiana Craciun dev_warn(dev, "VFIO_FSL_MC: Object %s bound to driver %s while DPRC bound to vfio-fsl-mc\n", 441704f5082SDiana Craciun dev_name(dev), mc_drv->driver.name); 442704f5082SDiana Craciun } 443704f5082SDiana Craciun 444704f5082SDiana Craciun return 0; 445704f5082SDiana Craciun } 446704f5082SDiana Craciun 447704f5082SDiana Craciun static int vfio_fsl_mc_init_device(struct vfio_fsl_mc_device *vdev) 448704f5082SDiana Craciun { 449704f5082SDiana Craciun struct fsl_mc_device *mc_dev = vdev->mc_dev; 450704f5082SDiana Craciun int ret; 451704f5082SDiana Craciun 452704f5082SDiana Craciun /* Non-dprc devices share mc_io from parent */ 453704f5082SDiana Craciun if (!is_fsl_mc_bus_dprc(mc_dev)) { 454704f5082SDiana Craciun struct fsl_mc_device *mc_cont = to_fsl_mc_device(mc_dev->dev.parent); 455704f5082SDiana Craciun 456704f5082SDiana Craciun mc_dev->mc_io = mc_cont->mc_io; 457704f5082SDiana Craciun return 0; 458704f5082SDiana Craciun } 459704f5082SDiana Craciun 460704f5082SDiana Craciun vdev->nb.notifier_call = vfio_fsl_mc_bus_notifier; 461704f5082SDiana Craciun ret = bus_register_notifier(&fsl_mc_bus_type, &vdev->nb); 462704f5082SDiana Craciun if (ret) 463704f5082SDiana Craciun return ret; 464704f5082SDiana Craciun 465704f5082SDiana Craciun /* open DPRC, allocate a MC portal */ 466704f5082SDiana Craciun ret = dprc_setup(mc_dev); 467704f5082SDiana Craciun if (ret) { 468704f5082SDiana Craciun dev_err(&mc_dev->dev, "VFIO_FSL_MC: Failed to setup DPRC (%d)\n", ret); 469704f5082SDiana Craciun goto out_nc_unreg; 470704f5082SDiana Craciun } 471704f5082SDiana Craciun return 0; 472704f5082SDiana Craciun 473704f5082SDiana Craciun out_nc_unreg: 474704f5082SDiana Craciun bus_unregister_notifier(&fsl_mc_bus_type, &vdev->nb); 475704f5082SDiana Craciun return ret; 476704f5082SDiana Craciun } 477704f5082SDiana Craciun 4782b1fe162SJason Gunthorpe static int vfio_fsl_mc_scan_container(struct fsl_mc_device *mc_dev) 4792b1fe162SJason Gunthorpe { 4802b1fe162SJason Gunthorpe int ret; 4812b1fe162SJason Gunthorpe 4822b1fe162SJason Gunthorpe /* non dprc devices do not scan for other devices */ 4832b1fe162SJason Gunthorpe if (!is_fsl_mc_bus_dprc(mc_dev)) 4842b1fe162SJason Gunthorpe return 0; 4852b1fe162SJason Gunthorpe ret = dprc_scan_container(mc_dev, false); 4862b1fe162SJason Gunthorpe if (ret) { 4872b1fe162SJason Gunthorpe dev_err(&mc_dev->dev, 4882b1fe162SJason Gunthorpe "VFIO_FSL_MC: Container scanning failed (%d)\n", ret); 4892b1fe162SJason Gunthorpe dprc_remove_devices(mc_dev, NULL, 0); 4902b1fe162SJason Gunthorpe return ret; 4912b1fe162SJason Gunthorpe } 4922b1fe162SJason Gunthorpe return 0; 4932b1fe162SJason Gunthorpe } 4942b1fe162SJason Gunthorpe 4952b1fe162SJason Gunthorpe static void vfio_fsl_uninit_device(struct vfio_fsl_mc_device *vdev) 4962b1fe162SJason Gunthorpe { 4972b1fe162SJason Gunthorpe struct fsl_mc_device *mc_dev = vdev->mc_dev; 4982b1fe162SJason Gunthorpe 4992b1fe162SJason Gunthorpe if (!is_fsl_mc_bus_dprc(mc_dev)) 5002b1fe162SJason Gunthorpe return; 5012b1fe162SJason Gunthorpe 5022b1fe162SJason Gunthorpe dprc_cleanup(mc_dev); 5032b1fe162SJason Gunthorpe bus_unregister_notifier(&fsl_mc_bus_type, &vdev->nb); 5042b1fe162SJason Gunthorpe } 5052b1fe162SJason Gunthorpe 506fb1ff4c1SBharat Bhushan static int vfio_fsl_mc_probe(struct fsl_mc_device *mc_dev) 507fb1ff4c1SBharat Bhushan { 508fb1ff4c1SBharat Bhushan struct iommu_group *group; 509fb1ff4c1SBharat Bhushan struct vfio_fsl_mc_device *vdev; 510fb1ff4c1SBharat Bhushan struct device *dev = &mc_dev->dev; 511fb1ff4c1SBharat Bhushan int ret; 512fb1ff4c1SBharat Bhushan 513fb1ff4c1SBharat Bhushan group = vfio_iommu_group_get(dev); 514fb1ff4c1SBharat Bhushan if (!group) { 515fb1ff4c1SBharat Bhushan dev_err(dev, "VFIO_FSL_MC: No IOMMU group\n"); 516fb1ff4c1SBharat Bhushan return -EINVAL; 517fb1ff4c1SBharat Bhushan } 518fb1ff4c1SBharat Bhushan 5190ca78666SJason Gunthorpe vdev = kzalloc(sizeof(*vdev), GFP_KERNEL); 520fb1ff4c1SBharat Bhushan if (!vdev) { 521fb1ff4c1SBharat Bhushan ret = -ENOMEM; 522fb1ff4c1SBharat Bhushan goto out_group_put; 523fb1ff4c1SBharat Bhushan } 524fb1ff4c1SBharat Bhushan 5251e04ec14SJason Gunthorpe vfio_init_group_dev(&vdev->vdev, dev, &vfio_fsl_mc_ops); 526fb1ff4c1SBharat Bhushan vdev->mc_dev = mc_dev; 5272b1fe162SJason Gunthorpe mutex_init(&vdev->igate); 528704f5082SDiana Craciun 529*da119f38SJason Gunthorpe if (is_fsl_mc_bus_dprc(mc_dev)) 530*da119f38SJason Gunthorpe ret = vfio_assign_device_set(&vdev->vdev, &mc_dev->dev); 531*da119f38SJason Gunthorpe else 532*da119f38SJason Gunthorpe ret = vfio_assign_device_set(&vdev->vdev, mc_dev->dev.parent); 533704f5082SDiana Craciun if (ret) 534ae03c377SMax Gurtovoy goto out_uninit; 535704f5082SDiana Craciun 536f2ba7e8cSDiana Craciun ret = vfio_fsl_mc_init_device(vdev); 537f2ba7e8cSDiana Craciun if (ret) 538*da119f38SJason Gunthorpe goto out_uninit; 539df747bcdSDiana Craciun 5400ca78666SJason Gunthorpe ret = vfio_register_group_dev(&vdev->vdev); 5412b1fe162SJason Gunthorpe if (ret) { 5422b1fe162SJason Gunthorpe dev_err(dev, "VFIO_FSL_MC: Failed to add to vfio group\n"); 5432b1fe162SJason Gunthorpe goto out_device; 5442b1fe162SJason Gunthorpe } 5452e0d2956SDiana Craciun 5462b1fe162SJason Gunthorpe ret = vfio_fsl_mc_scan_container(mc_dev); 5472b1fe162SJason Gunthorpe if (ret) 5482b1fe162SJason Gunthorpe goto out_group_dev; 5490ca78666SJason Gunthorpe dev_set_drvdata(dev, vdev); 550fb1ff4c1SBharat Bhushan return 0; 551fb1ff4c1SBharat Bhushan 552704f5082SDiana Craciun out_group_dev: 5530ca78666SJason Gunthorpe vfio_unregister_group_dev(&vdev->vdev); 5542b1fe162SJason Gunthorpe out_device: 5552b1fe162SJason Gunthorpe vfio_fsl_uninit_device(vdev); 556ae03c377SMax Gurtovoy out_uninit: 557ae03c377SMax Gurtovoy vfio_uninit_group_dev(&vdev->vdev); 5580ca78666SJason Gunthorpe kfree(vdev); 559fb1ff4c1SBharat Bhushan out_group_put: 560fb1ff4c1SBharat Bhushan vfio_iommu_group_put(group, dev); 561fb1ff4c1SBharat Bhushan return ret; 562fb1ff4c1SBharat Bhushan } 563fb1ff4c1SBharat Bhushan 564fb1ff4c1SBharat Bhushan static int vfio_fsl_mc_remove(struct fsl_mc_device *mc_dev) 565fb1ff4c1SBharat Bhushan { 566fb1ff4c1SBharat Bhushan struct device *dev = &mc_dev->dev; 5670ca78666SJason Gunthorpe struct vfio_fsl_mc_device *vdev = dev_get_drvdata(dev); 568fb1ff4c1SBharat Bhushan 5690ca78666SJason Gunthorpe vfio_unregister_group_dev(&vdev->vdev); 5702e0d2956SDiana Craciun mutex_destroy(&vdev->igate); 5712e0d2956SDiana Craciun 572704f5082SDiana Craciun dprc_remove_devices(mc_dev, NULL, 0); 5732b1fe162SJason Gunthorpe vfio_fsl_uninit_device(vdev); 574*da119f38SJason Gunthorpe 575ae03c377SMax Gurtovoy vfio_uninit_group_dev(&vdev->vdev); 5760ca78666SJason Gunthorpe kfree(vdev); 577fb1ff4c1SBharat Bhushan vfio_iommu_group_put(mc_dev->dev.iommu_group, dev); 578fb1ff4c1SBharat Bhushan 579fb1ff4c1SBharat Bhushan return 0; 580fb1ff4c1SBharat Bhushan } 581fb1ff4c1SBharat Bhushan 582fb1ff4c1SBharat Bhushan static struct fsl_mc_driver vfio_fsl_mc_driver = { 583fb1ff4c1SBharat Bhushan .probe = vfio_fsl_mc_probe, 584fb1ff4c1SBharat Bhushan .remove = vfio_fsl_mc_remove, 585fb1ff4c1SBharat Bhushan .driver = { 586fb1ff4c1SBharat Bhushan .name = "vfio-fsl-mc", 587fb1ff4c1SBharat Bhushan .owner = THIS_MODULE, 588fb1ff4c1SBharat Bhushan }, 589fb1ff4c1SBharat Bhushan }; 590fb1ff4c1SBharat Bhushan 591fb1ff4c1SBharat Bhushan static int __init vfio_fsl_mc_driver_init(void) 592fb1ff4c1SBharat Bhushan { 593fb1ff4c1SBharat Bhushan return fsl_mc_driver_register(&vfio_fsl_mc_driver); 594fb1ff4c1SBharat Bhushan } 595fb1ff4c1SBharat Bhushan 596fb1ff4c1SBharat Bhushan static void __exit vfio_fsl_mc_driver_exit(void) 597fb1ff4c1SBharat Bhushan { 598fb1ff4c1SBharat Bhushan fsl_mc_driver_unregister(&vfio_fsl_mc_driver); 599fb1ff4c1SBharat Bhushan } 600fb1ff4c1SBharat Bhushan 601fb1ff4c1SBharat Bhushan module_init(vfio_fsl_mc_driver_init); 602fb1ff4c1SBharat Bhushan module_exit(vfio_fsl_mc_driver_exit); 603fb1ff4c1SBharat Bhushan 604fb1ff4c1SBharat Bhushan MODULE_LICENSE("Dual BSD/GPL"); 605fb1ff4c1SBharat Bhushan MODULE_DESCRIPTION("VFIO for FSL-MC devices - User Level meta-driver"); 606