xref: /openbmc/linux/drivers/vfio/fsl-mc/vfio_fsl_mc.c (revision da119f387e94642da959a22ae9c22e09abe34926)
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