xref: /openbmc/linux/drivers/vfio/fsl-mc/vfio_fsl_mc.c (revision 1bb141ed5e14f773f9e08b80fe5db3fd405399db)
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>
15*1bb141edSDiana Craciun #include <linux/delay.h>
16fb1ff4c1SBharat Bhushan 
17fb1ff4c1SBharat Bhushan #include "vfio_fsl_mc_private.h"
18fb1ff4c1SBharat Bhushan 
19704f5082SDiana Craciun static struct fsl_mc_driver vfio_fsl_mc_driver;
20704f5082SDiana Craciun 
21f2ba7e8cSDiana Craciun static DEFINE_MUTEX(reflck_lock);
22f2ba7e8cSDiana Craciun 
23f2ba7e8cSDiana Craciun static void vfio_fsl_mc_reflck_get(struct vfio_fsl_mc_reflck *reflck)
24f2ba7e8cSDiana Craciun {
25f2ba7e8cSDiana Craciun 	kref_get(&reflck->kref);
26f2ba7e8cSDiana Craciun }
27f2ba7e8cSDiana Craciun 
28f2ba7e8cSDiana Craciun static void vfio_fsl_mc_reflck_release(struct kref *kref)
29f2ba7e8cSDiana Craciun {
30f2ba7e8cSDiana Craciun 	struct vfio_fsl_mc_reflck *reflck = container_of(kref,
31f2ba7e8cSDiana Craciun 						      struct vfio_fsl_mc_reflck,
32f2ba7e8cSDiana Craciun 						      kref);
33f2ba7e8cSDiana Craciun 
34f2ba7e8cSDiana Craciun 	mutex_destroy(&reflck->lock);
35f2ba7e8cSDiana Craciun 	kfree(reflck);
36f2ba7e8cSDiana Craciun 	mutex_unlock(&reflck_lock);
37f2ba7e8cSDiana Craciun }
38f2ba7e8cSDiana Craciun 
39f2ba7e8cSDiana Craciun static void vfio_fsl_mc_reflck_put(struct vfio_fsl_mc_reflck *reflck)
40f2ba7e8cSDiana Craciun {
41f2ba7e8cSDiana Craciun 	kref_put_mutex(&reflck->kref, vfio_fsl_mc_reflck_release, &reflck_lock);
42f2ba7e8cSDiana Craciun }
43f2ba7e8cSDiana Craciun 
44f2ba7e8cSDiana Craciun static struct vfio_fsl_mc_reflck *vfio_fsl_mc_reflck_alloc(void)
45f2ba7e8cSDiana Craciun {
46f2ba7e8cSDiana Craciun 	struct vfio_fsl_mc_reflck *reflck;
47f2ba7e8cSDiana Craciun 
48f2ba7e8cSDiana Craciun 	reflck = kzalloc(sizeof(*reflck), GFP_KERNEL);
49f2ba7e8cSDiana Craciun 	if (!reflck)
50f2ba7e8cSDiana Craciun 		return ERR_PTR(-ENOMEM);
51f2ba7e8cSDiana Craciun 
52f2ba7e8cSDiana Craciun 	kref_init(&reflck->kref);
53f2ba7e8cSDiana Craciun 	mutex_init(&reflck->lock);
54f2ba7e8cSDiana Craciun 
55f2ba7e8cSDiana Craciun 	return reflck;
56f2ba7e8cSDiana Craciun }
57f2ba7e8cSDiana Craciun 
58f2ba7e8cSDiana Craciun static int vfio_fsl_mc_reflck_attach(struct vfio_fsl_mc_device *vdev)
59f2ba7e8cSDiana Craciun {
60f2ba7e8cSDiana Craciun 	int ret;
61f2ba7e8cSDiana Craciun 
62f2ba7e8cSDiana Craciun 	mutex_lock(&reflck_lock);
63f2ba7e8cSDiana Craciun 	if (is_fsl_mc_bus_dprc(vdev->mc_dev)) {
64f2ba7e8cSDiana Craciun 		vdev->reflck = vfio_fsl_mc_reflck_alloc();
65f2ba7e8cSDiana Craciun 		ret = PTR_ERR_OR_ZERO(vdev->reflck);
66f2ba7e8cSDiana Craciun 	} else {
67f2ba7e8cSDiana Craciun 		struct device *mc_cont_dev = vdev->mc_dev->dev.parent;
68f2ba7e8cSDiana Craciun 		struct vfio_device *device;
69f2ba7e8cSDiana Craciun 		struct vfio_fsl_mc_device *cont_vdev;
70f2ba7e8cSDiana Craciun 
71f2ba7e8cSDiana Craciun 		device = vfio_device_get_from_dev(mc_cont_dev);
72f2ba7e8cSDiana Craciun 		if (!device) {
73f2ba7e8cSDiana Craciun 			ret = -ENODEV;
74f2ba7e8cSDiana Craciun 			goto unlock;
75f2ba7e8cSDiana Craciun 		}
76f2ba7e8cSDiana Craciun 
77f2ba7e8cSDiana Craciun 		cont_vdev = vfio_device_data(device);
78f2ba7e8cSDiana Craciun 		if (!cont_vdev || !cont_vdev->reflck) {
79f2ba7e8cSDiana Craciun 			vfio_device_put(device);
80f2ba7e8cSDiana Craciun 			ret = -ENODEV;
81f2ba7e8cSDiana Craciun 			goto unlock;
82f2ba7e8cSDiana Craciun 		}
83f2ba7e8cSDiana Craciun 		vfio_fsl_mc_reflck_get(cont_vdev->reflck);
84f2ba7e8cSDiana Craciun 		vdev->reflck = cont_vdev->reflck;
85f2ba7e8cSDiana Craciun 		vfio_device_put(device);
86f2ba7e8cSDiana Craciun 	}
87f2ba7e8cSDiana Craciun 
88f2ba7e8cSDiana Craciun unlock:
89f2ba7e8cSDiana Craciun 	mutex_unlock(&reflck_lock);
90f2ba7e8cSDiana Craciun 	return ret;
91f2ba7e8cSDiana Craciun }
92f2ba7e8cSDiana Craciun 
93df747bcdSDiana Craciun static int vfio_fsl_mc_regions_init(struct vfio_fsl_mc_device *vdev)
94fb1ff4c1SBharat Bhushan {
95df747bcdSDiana Craciun 	struct fsl_mc_device *mc_dev = vdev->mc_dev;
96df747bcdSDiana Craciun 	int count = mc_dev->obj_desc.region_count;
97df747bcdSDiana Craciun 	int i;
98df747bcdSDiana Craciun 
99df747bcdSDiana Craciun 	vdev->regions = kcalloc(count, sizeof(struct vfio_fsl_mc_region),
100df747bcdSDiana Craciun 				GFP_KERNEL);
101df747bcdSDiana Craciun 	if (!vdev->regions)
102df747bcdSDiana Craciun 		return -ENOMEM;
103df747bcdSDiana Craciun 
104df747bcdSDiana Craciun 	for (i = 0; i < count; i++) {
105df747bcdSDiana Craciun 		struct resource *res = &mc_dev->regions[i];
10667247289SDiana Craciun 		int no_mmap = is_fsl_mc_bus_dprc(mc_dev);
107df747bcdSDiana Craciun 
108df747bcdSDiana Craciun 		vdev->regions[i].addr = res->start;
109df747bcdSDiana Craciun 		vdev->regions[i].size = resource_size(res);
110df747bcdSDiana Craciun 		vdev->regions[i].type = mc_dev->regions[i].flags & IORESOURCE_BITS;
11167247289SDiana Craciun 		/*
11267247289SDiana Craciun 		 * Only regions addressed with PAGE granularity may be
11367247289SDiana Craciun 		 * MMAPed securely.
11467247289SDiana Craciun 		 */
11567247289SDiana Craciun 		if (!no_mmap && !(vdev->regions[i].addr & ~PAGE_MASK) &&
11667247289SDiana Craciun 				!(vdev->regions[i].size & ~PAGE_MASK))
11767247289SDiana Craciun 			vdev->regions[i].flags |=
11867247289SDiana Craciun 					VFIO_REGION_INFO_FLAG_MMAP;
119*1bb141edSDiana Craciun 		vdev->regions[i].flags |= VFIO_REGION_INFO_FLAG_READ;
120*1bb141edSDiana Craciun 		if (!(mc_dev->regions[i].flags & IORESOURCE_READONLY))
121*1bb141edSDiana Craciun 			vdev->regions[i].flags |= VFIO_REGION_INFO_FLAG_WRITE;
122df747bcdSDiana Craciun 	}
123fb1ff4c1SBharat Bhushan 
124fb1ff4c1SBharat Bhushan 	return 0;
125fb1ff4c1SBharat Bhushan }
126fb1ff4c1SBharat Bhushan 
127df747bcdSDiana Craciun static void vfio_fsl_mc_regions_cleanup(struct vfio_fsl_mc_device *vdev)
128df747bcdSDiana Craciun {
129*1bb141edSDiana Craciun 	struct fsl_mc_device *mc_dev = vdev->mc_dev;
130*1bb141edSDiana Craciun 	int i;
131*1bb141edSDiana Craciun 
132*1bb141edSDiana Craciun 	for (i = 0; i < mc_dev->obj_desc.region_count; i++)
133*1bb141edSDiana Craciun 		iounmap(vdev->regions[i].ioaddr);
134df747bcdSDiana Craciun 	kfree(vdev->regions);
135df747bcdSDiana Craciun }
136df747bcdSDiana Craciun 
137df747bcdSDiana Craciun static int vfio_fsl_mc_open(void *device_data)
138df747bcdSDiana Craciun {
139df747bcdSDiana Craciun 	struct vfio_fsl_mc_device *vdev = device_data;
140df747bcdSDiana Craciun 	int ret;
141df747bcdSDiana Craciun 
142df747bcdSDiana Craciun 	if (!try_module_get(THIS_MODULE))
143df747bcdSDiana Craciun 		return -ENODEV;
144df747bcdSDiana Craciun 
145f2ba7e8cSDiana Craciun 	mutex_lock(&vdev->reflck->lock);
146df747bcdSDiana Craciun 	if (!vdev->refcnt) {
147df747bcdSDiana Craciun 		ret = vfio_fsl_mc_regions_init(vdev);
148df747bcdSDiana Craciun 		if (ret)
149df747bcdSDiana Craciun 			goto err_reg_init;
150df747bcdSDiana Craciun 	}
151df747bcdSDiana Craciun 	vdev->refcnt++;
152df747bcdSDiana Craciun 
153f2ba7e8cSDiana Craciun 	mutex_unlock(&vdev->reflck->lock);
154df747bcdSDiana Craciun 
155df747bcdSDiana Craciun 	return 0;
156df747bcdSDiana Craciun 
157df747bcdSDiana Craciun err_reg_init:
158f2ba7e8cSDiana Craciun 	mutex_unlock(&vdev->reflck->lock);
159df747bcdSDiana Craciun 	module_put(THIS_MODULE);
160df747bcdSDiana Craciun 	return ret;
161df747bcdSDiana Craciun }
162df747bcdSDiana Craciun 
163fb1ff4c1SBharat Bhushan static void vfio_fsl_mc_release(void *device_data)
164fb1ff4c1SBharat Bhushan {
165df747bcdSDiana Craciun 	struct vfio_fsl_mc_device *vdev = device_data;
166cc0ee20bSDiana Craciun 	int ret;
167df747bcdSDiana Craciun 
168f2ba7e8cSDiana Craciun 	mutex_lock(&vdev->reflck->lock);
169df747bcdSDiana Craciun 
170cc0ee20bSDiana Craciun 	if (!(--vdev->refcnt)) {
171cc0ee20bSDiana Craciun 		struct fsl_mc_device *mc_dev = vdev->mc_dev;
172cc0ee20bSDiana Craciun 		struct device *cont_dev = fsl_mc_cont_dev(&mc_dev->dev);
173cc0ee20bSDiana Craciun 		struct fsl_mc_device *mc_cont = to_fsl_mc_device(cont_dev);
174cc0ee20bSDiana Craciun 
175df747bcdSDiana Craciun 		vfio_fsl_mc_regions_cleanup(vdev);
176df747bcdSDiana Craciun 
177cc0ee20bSDiana Craciun 		/* reset the device before cleaning up the interrupts */
178cc0ee20bSDiana Craciun 		ret = dprc_reset_container(mc_cont->mc_io, 0,
179cc0ee20bSDiana Craciun 		      mc_cont->mc_handle,
180cc0ee20bSDiana Craciun 			  mc_cont->obj_desc.id,
181cc0ee20bSDiana Craciun 			  DPRC_RESET_OPTION_NON_RECURSIVE);
182cc0ee20bSDiana Craciun 
183cc0ee20bSDiana Craciun 		if (ret) {
184cc0ee20bSDiana Craciun 			dev_warn(&mc_cont->dev, "VFIO_FLS_MC: reset device has failed (%d)\n",
185cc0ee20bSDiana Craciun 				 ret);
186cc0ee20bSDiana Craciun 			WARN_ON(1);
187cc0ee20bSDiana Craciun 		}
188cc0ee20bSDiana Craciun 
189cc0ee20bSDiana Craciun 		vfio_fsl_mc_irqs_cleanup(vdev);
190cc0ee20bSDiana Craciun 
191cc0ee20bSDiana Craciun 		fsl_mc_cleanup_irq_pool(mc_cont);
192cc0ee20bSDiana Craciun 	}
193cc0ee20bSDiana Craciun 
194f2ba7e8cSDiana Craciun 	mutex_unlock(&vdev->reflck->lock);
195df747bcdSDiana Craciun 
196fb1ff4c1SBharat Bhushan 	module_put(THIS_MODULE);
197fb1ff4c1SBharat Bhushan }
198fb1ff4c1SBharat Bhushan 
199fb1ff4c1SBharat Bhushan static long vfio_fsl_mc_ioctl(void *device_data, unsigned int cmd,
200fb1ff4c1SBharat Bhushan 			      unsigned long arg)
201fb1ff4c1SBharat Bhushan {
202f97f4c04SDiana Craciun 	unsigned long minsz;
203f97f4c04SDiana Craciun 	struct vfio_fsl_mc_device *vdev = device_data;
204f97f4c04SDiana Craciun 	struct fsl_mc_device *mc_dev = vdev->mc_dev;
205f97f4c04SDiana Craciun 
206fb1ff4c1SBharat Bhushan 	switch (cmd) {
207fb1ff4c1SBharat Bhushan 	case VFIO_DEVICE_GET_INFO:
208fb1ff4c1SBharat Bhushan 	{
209f97f4c04SDiana Craciun 		struct vfio_device_info info;
210f97f4c04SDiana Craciun 
211f97f4c04SDiana Craciun 		minsz = offsetofend(struct vfio_device_info, num_irqs);
212f97f4c04SDiana Craciun 
213f97f4c04SDiana Craciun 		if (copy_from_user(&info, (void __user *)arg, minsz))
214f97f4c04SDiana Craciun 			return -EFAULT;
215f97f4c04SDiana Craciun 
216f97f4c04SDiana Craciun 		if (info.argsz < minsz)
217f97f4c04SDiana Craciun 			return -EINVAL;
218f97f4c04SDiana Craciun 
219f97f4c04SDiana Craciun 		info.flags = VFIO_DEVICE_FLAGS_FSL_MC;
220f97f4c04SDiana Craciun 		info.num_regions = mc_dev->obj_desc.region_count;
221f97f4c04SDiana Craciun 		info.num_irqs = mc_dev->obj_desc.irq_count;
222f97f4c04SDiana Craciun 
223f97f4c04SDiana Craciun 		return copy_to_user((void __user *)arg, &info, minsz) ?
224f97f4c04SDiana Craciun 			-EFAULT : 0;
225fb1ff4c1SBharat Bhushan 	}
226fb1ff4c1SBharat Bhushan 	case VFIO_DEVICE_GET_REGION_INFO:
227fb1ff4c1SBharat Bhushan 	{
228df747bcdSDiana Craciun 		struct vfio_region_info info;
229df747bcdSDiana Craciun 
230df747bcdSDiana Craciun 		minsz = offsetofend(struct vfio_region_info, offset);
231df747bcdSDiana Craciun 
232df747bcdSDiana Craciun 		if (copy_from_user(&info, (void __user *)arg, minsz))
233df747bcdSDiana Craciun 			return -EFAULT;
234df747bcdSDiana Craciun 
235df747bcdSDiana Craciun 		if (info.argsz < minsz)
236df747bcdSDiana Craciun 			return -EINVAL;
237df747bcdSDiana Craciun 
238df747bcdSDiana Craciun 		if (info.index >= mc_dev->obj_desc.region_count)
239df747bcdSDiana Craciun 			return -EINVAL;
240df747bcdSDiana Craciun 
241df747bcdSDiana Craciun 		/* map offset to the physical address  */
242df747bcdSDiana Craciun 		info.offset = VFIO_FSL_MC_INDEX_TO_OFFSET(info.index);
243df747bcdSDiana Craciun 		info.size = vdev->regions[info.index].size;
244df747bcdSDiana Craciun 		info.flags = vdev->regions[info.index].flags;
245df747bcdSDiana Craciun 
246df747bcdSDiana Craciun 		return copy_to_user((void __user *)arg, &info, minsz);
247fb1ff4c1SBharat Bhushan 	}
248fb1ff4c1SBharat Bhushan 	case VFIO_DEVICE_GET_IRQ_INFO:
249fb1ff4c1SBharat Bhushan 	{
2502e0d2956SDiana Craciun 		struct vfio_irq_info info;
2512e0d2956SDiana Craciun 
2522e0d2956SDiana Craciun 		minsz = offsetofend(struct vfio_irq_info, count);
2532e0d2956SDiana Craciun 		if (copy_from_user(&info, (void __user *)arg, minsz))
2542e0d2956SDiana Craciun 			return -EFAULT;
2552e0d2956SDiana Craciun 
2562e0d2956SDiana Craciun 		if (info.argsz < minsz)
2572e0d2956SDiana Craciun 			return -EINVAL;
2582e0d2956SDiana Craciun 
2592e0d2956SDiana Craciun 		if (info.index >= mc_dev->obj_desc.irq_count)
2602e0d2956SDiana Craciun 			return -EINVAL;
2612e0d2956SDiana Craciun 
2622e0d2956SDiana Craciun 		info.flags = VFIO_IRQ_INFO_EVENTFD;
2632e0d2956SDiana Craciun 		info.count = 1;
2642e0d2956SDiana Craciun 
2652e0d2956SDiana Craciun 		return copy_to_user((void __user *)arg, &info, minsz);
266fb1ff4c1SBharat Bhushan 	}
267fb1ff4c1SBharat Bhushan 	case VFIO_DEVICE_SET_IRQS:
268fb1ff4c1SBharat Bhushan 	{
2692e0d2956SDiana Craciun 		struct vfio_irq_set hdr;
2702e0d2956SDiana Craciun 		u8 *data = NULL;
2712e0d2956SDiana Craciun 		int ret = 0;
2722e0d2956SDiana Craciun 		size_t data_size = 0;
2732e0d2956SDiana Craciun 
2742e0d2956SDiana Craciun 		minsz = offsetofend(struct vfio_irq_set, count);
2752e0d2956SDiana Craciun 
2762e0d2956SDiana Craciun 		if (copy_from_user(&hdr, (void __user *)arg, minsz))
2772e0d2956SDiana Craciun 			return -EFAULT;
2782e0d2956SDiana Craciun 
2792e0d2956SDiana Craciun 		ret = vfio_set_irqs_validate_and_prepare(&hdr, mc_dev->obj_desc.irq_count,
2802e0d2956SDiana Craciun 					mc_dev->obj_desc.irq_count, &data_size);
2812e0d2956SDiana Craciun 		if (ret)
2822e0d2956SDiana Craciun 			return ret;
2832e0d2956SDiana Craciun 
2842e0d2956SDiana Craciun 		if (data_size) {
2852e0d2956SDiana Craciun 			data = memdup_user((void __user *)(arg + minsz),
2862e0d2956SDiana Craciun 				   data_size);
2872e0d2956SDiana Craciun 			if (IS_ERR(data))
2882e0d2956SDiana Craciun 				return PTR_ERR(data);
2892e0d2956SDiana Craciun 		}
2902e0d2956SDiana Craciun 
2912e0d2956SDiana Craciun 		mutex_lock(&vdev->igate);
2922e0d2956SDiana Craciun 		ret = vfio_fsl_mc_set_irqs_ioctl(vdev, hdr.flags,
2932e0d2956SDiana Craciun 						 hdr.index, hdr.start,
2942e0d2956SDiana Craciun 						 hdr.count, data);
2952e0d2956SDiana Craciun 		mutex_unlock(&vdev->igate);
2962e0d2956SDiana Craciun 		kfree(data);
2972e0d2956SDiana Craciun 
2982e0d2956SDiana Craciun 		return ret;
299fb1ff4c1SBharat Bhushan 	}
300fb1ff4c1SBharat Bhushan 	case VFIO_DEVICE_RESET:
301fb1ff4c1SBharat Bhushan 	{
302fb1ff4c1SBharat Bhushan 		return -ENOTTY;
303fb1ff4c1SBharat Bhushan 	}
304fb1ff4c1SBharat Bhushan 	default:
305fb1ff4c1SBharat Bhushan 		return -ENOTTY;
306fb1ff4c1SBharat Bhushan 	}
307fb1ff4c1SBharat Bhushan }
308fb1ff4c1SBharat Bhushan 
309fb1ff4c1SBharat Bhushan static ssize_t vfio_fsl_mc_read(void *device_data, char __user *buf,
310fb1ff4c1SBharat Bhushan 				size_t count, loff_t *ppos)
311fb1ff4c1SBharat Bhushan {
312*1bb141edSDiana Craciun 	struct vfio_fsl_mc_device *vdev = device_data;
313*1bb141edSDiana Craciun 	unsigned int index = VFIO_FSL_MC_OFFSET_TO_INDEX(*ppos);
314*1bb141edSDiana Craciun 	loff_t off = *ppos & VFIO_FSL_MC_OFFSET_MASK;
315*1bb141edSDiana Craciun 	struct fsl_mc_device *mc_dev = vdev->mc_dev;
316*1bb141edSDiana Craciun 	struct vfio_fsl_mc_region *region;
317*1bb141edSDiana Craciun 	u64 data[8];
318*1bb141edSDiana Craciun 	int i;
319*1bb141edSDiana Craciun 
320*1bb141edSDiana Craciun 	if (index >= mc_dev->obj_desc.region_count)
321fb1ff4c1SBharat Bhushan 		return -EINVAL;
322*1bb141edSDiana Craciun 
323*1bb141edSDiana Craciun 	region = &vdev->regions[index];
324*1bb141edSDiana Craciun 
325*1bb141edSDiana Craciun 	if (!(region->flags & VFIO_REGION_INFO_FLAG_READ))
326*1bb141edSDiana Craciun 		return -EINVAL;
327*1bb141edSDiana Craciun 
328*1bb141edSDiana Craciun 	if (!region->ioaddr) {
329*1bb141edSDiana Craciun 		region->ioaddr = ioremap(region->addr, region->size);
330*1bb141edSDiana Craciun 		if (!region->ioaddr)
331*1bb141edSDiana Craciun 			return -ENOMEM;
332*1bb141edSDiana Craciun 	}
333*1bb141edSDiana Craciun 
334*1bb141edSDiana Craciun 	if (count != 64 || off != 0)
335*1bb141edSDiana Craciun 		return -EINVAL;
336*1bb141edSDiana Craciun 
337*1bb141edSDiana Craciun 	for (i = 7; i >= 0; i--)
338*1bb141edSDiana Craciun 		data[i] = readq(region->ioaddr + i * sizeof(uint64_t));
339*1bb141edSDiana Craciun 
340*1bb141edSDiana Craciun 	if (copy_to_user(buf, data, 64))
341*1bb141edSDiana Craciun 		return -EFAULT;
342*1bb141edSDiana Craciun 
343*1bb141edSDiana Craciun 	return count;
344*1bb141edSDiana Craciun }
345*1bb141edSDiana Craciun 
346*1bb141edSDiana Craciun #define MC_CMD_COMPLETION_TIMEOUT_MS    5000
347*1bb141edSDiana Craciun #define MC_CMD_COMPLETION_POLLING_MAX_SLEEP_USECS    500
348*1bb141edSDiana Craciun 
349*1bb141edSDiana Craciun static int vfio_fsl_mc_send_command(void __iomem *ioaddr, uint64_t *cmd_data)
350*1bb141edSDiana Craciun {
351*1bb141edSDiana Craciun 	int i;
352*1bb141edSDiana Craciun 	enum mc_cmd_status status;
353*1bb141edSDiana Craciun 	unsigned long timeout_usecs = MC_CMD_COMPLETION_TIMEOUT_MS * 1000;
354*1bb141edSDiana Craciun 
355*1bb141edSDiana Craciun 	/* Write at command parameter into portal */
356*1bb141edSDiana Craciun 	for (i = 7; i >= 1; i--)
357*1bb141edSDiana Craciun 		writeq_relaxed(cmd_data[i], ioaddr + i * sizeof(uint64_t));
358*1bb141edSDiana Craciun 
359*1bb141edSDiana Craciun 	/* Write command header in the end */
360*1bb141edSDiana Craciun 	writeq(cmd_data[0], ioaddr);
361*1bb141edSDiana Craciun 
362*1bb141edSDiana Craciun 	/* Wait for response before returning to user-space
363*1bb141edSDiana Craciun 	 * This can be optimized in future to even prepare response
364*1bb141edSDiana Craciun 	 * before returning to user-space and avoid read ioctl.
365*1bb141edSDiana Craciun 	 */
366*1bb141edSDiana Craciun 	for (;;) {
367*1bb141edSDiana Craciun 		u64 header;
368*1bb141edSDiana Craciun 		struct mc_cmd_header *resp_hdr;
369*1bb141edSDiana Craciun 
370*1bb141edSDiana Craciun 		header = cpu_to_le64(readq_relaxed(ioaddr));
371*1bb141edSDiana Craciun 
372*1bb141edSDiana Craciun 		resp_hdr = (struct mc_cmd_header *)&header;
373*1bb141edSDiana Craciun 		status = (enum mc_cmd_status)resp_hdr->status;
374*1bb141edSDiana Craciun 		if (status != MC_CMD_STATUS_READY)
375*1bb141edSDiana Craciun 			break;
376*1bb141edSDiana Craciun 
377*1bb141edSDiana Craciun 		udelay(MC_CMD_COMPLETION_POLLING_MAX_SLEEP_USECS);
378*1bb141edSDiana Craciun 		timeout_usecs -= MC_CMD_COMPLETION_POLLING_MAX_SLEEP_USECS;
379*1bb141edSDiana Craciun 		if (timeout_usecs == 0)
380*1bb141edSDiana Craciun 			return -ETIMEDOUT;
381*1bb141edSDiana Craciun 	}
382*1bb141edSDiana Craciun 
383*1bb141edSDiana Craciun 	return 0;
384fb1ff4c1SBharat Bhushan }
385fb1ff4c1SBharat Bhushan 
386fb1ff4c1SBharat Bhushan static ssize_t vfio_fsl_mc_write(void *device_data, const char __user *buf,
387fb1ff4c1SBharat Bhushan 				 size_t count, loff_t *ppos)
388fb1ff4c1SBharat Bhushan {
389*1bb141edSDiana Craciun 	struct vfio_fsl_mc_device *vdev = device_data;
390*1bb141edSDiana Craciun 	unsigned int index = VFIO_FSL_MC_OFFSET_TO_INDEX(*ppos);
391*1bb141edSDiana Craciun 	loff_t off = *ppos & VFIO_FSL_MC_OFFSET_MASK;
392*1bb141edSDiana Craciun 	struct fsl_mc_device *mc_dev = vdev->mc_dev;
393*1bb141edSDiana Craciun 	struct vfio_fsl_mc_region *region;
394*1bb141edSDiana Craciun 	u64 data[8];
395*1bb141edSDiana Craciun 	int ret;
396*1bb141edSDiana Craciun 
397*1bb141edSDiana Craciun 	if (index >= mc_dev->obj_desc.region_count)
398fb1ff4c1SBharat Bhushan 		return -EINVAL;
399*1bb141edSDiana Craciun 
400*1bb141edSDiana Craciun 	region = &vdev->regions[index];
401*1bb141edSDiana Craciun 
402*1bb141edSDiana Craciun 	if (!(region->flags & VFIO_REGION_INFO_FLAG_WRITE))
403*1bb141edSDiana Craciun 		return -EINVAL;
404*1bb141edSDiana Craciun 
405*1bb141edSDiana Craciun 	if (!region->ioaddr) {
406*1bb141edSDiana Craciun 		region->ioaddr = ioremap(region->addr, region->size);
407*1bb141edSDiana Craciun 		if (!region->ioaddr)
408*1bb141edSDiana Craciun 			return -ENOMEM;
409*1bb141edSDiana Craciun 	}
410*1bb141edSDiana Craciun 
411*1bb141edSDiana Craciun 	if (count != 64 || off != 0)
412*1bb141edSDiana Craciun 		return -EINVAL;
413*1bb141edSDiana Craciun 
414*1bb141edSDiana Craciun 	if (copy_from_user(&data, buf, 64))
415*1bb141edSDiana Craciun 		return -EFAULT;
416*1bb141edSDiana Craciun 
417*1bb141edSDiana Craciun 	ret = vfio_fsl_mc_send_command(region->ioaddr, data);
418*1bb141edSDiana Craciun 	if (ret)
419*1bb141edSDiana Craciun 		return ret;
420*1bb141edSDiana Craciun 
421*1bb141edSDiana Craciun 	return count;
422*1bb141edSDiana Craciun 
423fb1ff4c1SBharat Bhushan }
424fb1ff4c1SBharat Bhushan 
42567247289SDiana Craciun static int vfio_fsl_mc_mmap_mmio(struct vfio_fsl_mc_region region,
42667247289SDiana Craciun 				 struct vm_area_struct *vma)
42767247289SDiana Craciun {
42867247289SDiana Craciun 	u64 size = vma->vm_end - vma->vm_start;
42967247289SDiana Craciun 	u64 pgoff, base;
43067247289SDiana Craciun 	u8 region_cacheable;
43167247289SDiana Craciun 
43267247289SDiana Craciun 	pgoff = vma->vm_pgoff &
43367247289SDiana Craciun 		((1U << (VFIO_FSL_MC_OFFSET_SHIFT - PAGE_SHIFT)) - 1);
43467247289SDiana Craciun 	base = pgoff << PAGE_SHIFT;
43567247289SDiana Craciun 
43667247289SDiana Craciun 	if (region.size < PAGE_SIZE || base + size > region.size)
43767247289SDiana Craciun 		return -EINVAL;
43867247289SDiana Craciun 
43967247289SDiana Craciun 	region_cacheable = (region.type & FSL_MC_REGION_CACHEABLE) &&
44067247289SDiana Craciun 			   (region.type & FSL_MC_REGION_SHAREABLE);
44167247289SDiana Craciun 	if (!region_cacheable)
44267247289SDiana Craciun 		vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
44367247289SDiana Craciun 
44467247289SDiana Craciun 	vma->vm_pgoff = (region.addr >> PAGE_SHIFT) + pgoff;
44567247289SDiana Craciun 
44667247289SDiana Craciun 	return remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff,
44767247289SDiana Craciun 			       size, vma->vm_page_prot);
44867247289SDiana Craciun }
44967247289SDiana Craciun 
450fb1ff4c1SBharat Bhushan static int vfio_fsl_mc_mmap(void *device_data, struct vm_area_struct *vma)
451fb1ff4c1SBharat Bhushan {
45267247289SDiana Craciun 	struct vfio_fsl_mc_device *vdev = device_data;
45367247289SDiana Craciun 	struct fsl_mc_device *mc_dev = vdev->mc_dev;
45467247289SDiana Craciun 	int index;
45567247289SDiana Craciun 
45667247289SDiana Craciun 	index = vma->vm_pgoff >> (VFIO_FSL_MC_OFFSET_SHIFT - PAGE_SHIFT);
45767247289SDiana Craciun 
45867247289SDiana Craciun 	if (vma->vm_end < vma->vm_start)
459fb1ff4c1SBharat Bhushan 		return -EINVAL;
46067247289SDiana Craciun 	if (vma->vm_start & ~PAGE_MASK)
46167247289SDiana Craciun 		return -EINVAL;
46267247289SDiana Craciun 	if (vma->vm_end & ~PAGE_MASK)
46367247289SDiana Craciun 		return -EINVAL;
46467247289SDiana Craciun 	if (!(vma->vm_flags & VM_SHARED))
46567247289SDiana Craciun 		return -EINVAL;
46667247289SDiana Craciun 	if (index >= mc_dev->obj_desc.region_count)
46767247289SDiana Craciun 		return -EINVAL;
46867247289SDiana Craciun 
46967247289SDiana Craciun 	if (!(vdev->regions[index].flags & VFIO_REGION_INFO_FLAG_MMAP))
47067247289SDiana Craciun 		return -EINVAL;
47167247289SDiana Craciun 
47267247289SDiana Craciun 	if (!(vdev->regions[index].flags & VFIO_REGION_INFO_FLAG_READ)
47367247289SDiana Craciun 			&& (vma->vm_flags & VM_READ))
47467247289SDiana Craciun 		return -EINVAL;
47567247289SDiana Craciun 
47667247289SDiana Craciun 	if (!(vdev->regions[index].flags & VFIO_REGION_INFO_FLAG_WRITE)
47767247289SDiana Craciun 			&& (vma->vm_flags & VM_WRITE))
47867247289SDiana Craciun 		return -EINVAL;
47967247289SDiana Craciun 
48067247289SDiana Craciun 	vma->vm_private_data = mc_dev;
48167247289SDiana Craciun 
48267247289SDiana Craciun 	return vfio_fsl_mc_mmap_mmio(vdev->regions[index], vma);
483fb1ff4c1SBharat Bhushan }
484fb1ff4c1SBharat Bhushan 
485fb1ff4c1SBharat Bhushan static const struct vfio_device_ops vfio_fsl_mc_ops = {
486fb1ff4c1SBharat Bhushan 	.name		= "vfio-fsl-mc",
487fb1ff4c1SBharat Bhushan 	.open		= vfio_fsl_mc_open,
488fb1ff4c1SBharat Bhushan 	.release	= vfio_fsl_mc_release,
489fb1ff4c1SBharat Bhushan 	.ioctl		= vfio_fsl_mc_ioctl,
490fb1ff4c1SBharat Bhushan 	.read		= vfio_fsl_mc_read,
491fb1ff4c1SBharat Bhushan 	.write		= vfio_fsl_mc_write,
492fb1ff4c1SBharat Bhushan 	.mmap		= vfio_fsl_mc_mmap,
493fb1ff4c1SBharat Bhushan };
494fb1ff4c1SBharat Bhushan 
495704f5082SDiana Craciun static int vfio_fsl_mc_bus_notifier(struct notifier_block *nb,
496704f5082SDiana Craciun 				    unsigned long action, void *data)
497704f5082SDiana Craciun {
498704f5082SDiana Craciun 	struct vfio_fsl_mc_device *vdev = container_of(nb,
499704f5082SDiana Craciun 					struct vfio_fsl_mc_device, nb);
500704f5082SDiana Craciun 	struct device *dev = data;
501704f5082SDiana Craciun 	struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev);
502704f5082SDiana Craciun 	struct fsl_mc_device *mc_cont = to_fsl_mc_device(mc_dev->dev.parent);
503704f5082SDiana Craciun 
504704f5082SDiana Craciun 	if (action == BUS_NOTIFY_ADD_DEVICE &&
505704f5082SDiana Craciun 	    vdev->mc_dev == mc_cont) {
506704f5082SDiana Craciun 		mc_dev->driver_override = kasprintf(GFP_KERNEL, "%s",
507704f5082SDiana Craciun 						    vfio_fsl_mc_ops.name);
508704f5082SDiana Craciun 		if (!mc_dev->driver_override)
509704f5082SDiana Craciun 			dev_warn(dev, "VFIO_FSL_MC: Setting driver override for device in dprc %s failed\n",
510704f5082SDiana Craciun 				 dev_name(&mc_cont->dev));
511704f5082SDiana Craciun 		else
512704f5082SDiana Craciun 			dev_info(dev, "VFIO_FSL_MC: Setting driver override for device in dprc %s\n",
513704f5082SDiana Craciun 				 dev_name(&mc_cont->dev));
514704f5082SDiana Craciun 	} else if (action == BUS_NOTIFY_BOUND_DRIVER &&
515704f5082SDiana Craciun 		vdev->mc_dev == mc_cont) {
516704f5082SDiana Craciun 		struct fsl_mc_driver *mc_drv = to_fsl_mc_driver(dev->driver);
517704f5082SDiana Craciun 
518704f5082SDiana Craciun 		if (mc_drv && mc_drv != &vfio_fsl_mc_driver)
519704f5082SDiana Craciun 			dev_warn(dev, "VFIO_FSL_MC: Object %s bound to driver %s while DPRC bound to vfio-fsl-mc\n",
520704f5082SDiana Craciun 				 dev_name(dev), mc_drv->driver.name);
521704f5082SDiana Craciun 	}
522704f5082SDiana Craciun 
523704f5082SDiana Craciun 	return 0;
524704f5082SDiana Craciun }
525704f5082SDiana Craciun 
526704f5082SDiana Craciun static int vfio_fsl_mc_init_device(struct vfio_fsl_mc_device *vdev)
527704f5082SDiana Craciun {
528704f5082SDiana Craciun 	struct fsl_mc_device *mc_dev = vdev->mc_dev;
529704f5082SDiana Craciun 	int ret;
530704f5082SDiana Craciun 
531704f5082SDiana Craciun 	/* Non-dprc devices share mc_io from parent */
532704f5082SDiana Craciun 	if (!is_fsl_mc_bus_dprc(mc_dev)) {
533704f5082SDiana Craciun 		struct fsl_mc_device *mc_cont = to_fsl_mc_device(mc_dev->dev.parent);
534704f5082SDiana Craciun 
535704f5082SDiana Craciun 		mc_dev->mc_io = mc_cont->mc_io;
536704f5082SDiana Craciun 		return 0;
537704f5082SDiana Craciun 	}
538704f5082SDiana Craciun 
539704f5082SDiana Craciun 	vdev->nb.notifier_call = vfio_fsl_mc_bus_notifier;
540704f5082SDiana Craciun 	ret = bus_register_notifier(&fsl_mc_bus_type, &vdev->nb);
541704f5082SDiana Craciun 	if (ret)
542704f5082SDiana Craciun 		return ret;
543704f5082SDiana Craciun 
544704f5082SDiana Craciun 	/* open DPRC, allocate a MC portal */
545704f5082SDiana Craciun 	ret = dprc_setup(mc_dev);
546704f5082SDiana Craciun 	if (ret) {
547704f5082SDiana Craciun 		dev_err(&mc_dev->dev, "VFIO_FSL_MC: Failed to setup DPRC (%d)\n", ret);
548704f5082SDiana Craciun 		goto out_nc_unreg;
549704f5082SDiana Craciun 	}
550704f5082SDiana Craciun 
551704f5082SDiana Craciun 	ret = dprc_scan_container(mc_dev, false);
552704f5082SDiana Craciun 	if (ret) {
553704f5082SDiana Craciun 		dev_err(&mc_dev->dev, "VFIO_FSL_MC: Container scanning failed (%d)\n", ret);
554704f5082SDiana Craciun 		goto out_dprc_cleanup;
555704f5082SDiana Craciun 	}
556704f5082SDiana Craciun 
557704f5082SDiana Craciun 	return 0;
558704f5082SDiana Craciun 
559704f5082SDiana Craciun out_dprc_cleanup:
560704f5082SDiana Craciun 	dprc_remove_devices(mc_dev, NULL, 0);
561704f5082SDiana Craciun 	dprc_cleanup(mc_dev);
562704f5082SDiana Craciun out_nc_unreg:
563704f5082SDiana Craciun 	bus_unregister_notifier(&fsl_mc_bus_type, &vdev->nb);
564704f5082SDiana Craciun 	vdev->nb.notifier_call = NULL;
565704f5082SDiana Craciun 
566704f5082SDiana Craciun 	return ret;
567704f5082SDiana Craciun }
568704f5082SDiana Craciun 
569fb1ff4c1SBharat Bhushan static int vfio_fsl_mc_probe(struct fsl_mc_device *mc_dev)
570fb1ff4c1SBharat Bhushan {
571fb1ff4c1SBharat Bhushan 	struct iommu_group *group;
572fb1ff4c1SBharat Bhushan 	struct vfio_fsl_mc_device *vdev;
573fb1ff4c1SBharat Bhushan 	struct device *dev = &mc_dev->dev;
574fb1ff4c1SBharat Bhushan 	int ret;
575fb1ff4c1SBharat Bhushan 
576fb1ff4c1SBharat Bhushan 	group = vfio_iommu_group_get(dev);
577fb1ff4c1SBharat Bhushan 	if (!group) {
578fb1ff4c1SBharat Bhushan 		dev_err(dev, "VFIO_FSL_MC: No IOMMU group\n");
579fb1ff4c1SBharat Bhushan 		return -EINVAL;
580fb1ff4c1SBharat Bhushan 	}
581fb1ff4c1SBharat Bhushan 
582fb1ff4c1SBharat Bhushan 	vdev = devm_kzalloc(dev, sizeof(*vdev), GFP_KERNEL);
583fb1ff4c1SBharat Bhushan 	if (!vdev) {
584fb1ff4c1SBharat Bhushan 		ret = -ENOMEM;
585fb1ff4c1SBharat Bhushan 		goto out_group_put;
586fb1ff4c1SBharat Bhushan 	}
587fb1ff4c1SBharat Bhushan 
588fb1ff4c1SBharat Bhushan 	vdev->mc_dev = mc_dev;
589fb1ff4c1SBharat Bhushan 
590fb1ff4c1SBharat Bhushan 	ret = vfio_add_group_dev(dev, &vfio_fsl_mc_ops, vdev);
591fb1ff4c1SBharat Bhushan 	if (ret) {
592fb1ff4c1SBharat Bhushan 		dev_err(dev, "VFIO_FSL_MC: Failed to add to vfio group\n");
593fb1ff4c1SBharat Bhushan 		goto out_group_put;
594fb1ff4c1SBharat Bhushan 	}
595704f5082SDiana Craciun 
596f2ba7e8cSDiana Craciun 	ret = vfio_fsl_mc_reflck_attach(vdev);
597704f5082SDiana Craciun 	if (ret)
598704f5082SDiana Craciun 		goto out_group_dev;
599704f5082SDiana Craciun 
600f2ba7e8cSDiana Craciun 	ret = vfio_fsl_mc_init_device(vdev);
601f2ba7e8cSDiana Craciun 	if (ret)
602f2ba7e8cSDiana Craciun 		goto out_reflck;
603df747bcdSDiana Craciun 
6042e0d2956SDiana Craciun 	mutex_init(&vdev->igate);
6052e0d2956SDiana Craciun 
606fb1ff4c1SBharat Bhushan 	return 0;
607fb1ff4c1SBharat Bhushan 
608f2ba7e8cSDiana Craciun out_reflck:
609f2ba7e8cSDiana Craciun 	vfio_fsl_mc_reflck_put(vdev->reflck);
610704f5082SDiana Craciun out_group_dev:
611704f5082SDiana Craciun 	vfio_del_group_dev(dev);
612fb1ff4c1SBharat Bhushan out_group_put:
613fb1ff4c1SBharat Bhushan 	vfio_iommu_group_put(group, dev);
614fb1ff4c1SBharat Bhushan 	return ret;
615fb1ff4c1SBharat Bhushan }
616fb1ff4c1SBharat Bhushan 
617fb1ff4c1SBharat Bhushan static int vfio_fsl_mc_remove(struct fsl_mc_device *mc_dev)
618fb1ff4c1SBharat Bhushan {
619fb1ff4c1SBharat Bhushan 	struct vfio_fsl_mc_device *vdev;
620fb1ff4c1SBharat Bhushan 	struct device *dev = &mc_dev->dev;
621fb1ff4c1SBharat Bhushan 
622fb1ff4c1SBharat Bhushan 	vdev = vfio_del_group_dev(dev);
623fb1ff4c1SBharat Bhushan 	if (!vdev)
624fb1ff4c1SBharat Bhushan 		return -EINVAL;
625fb1ff4c1SBharat Bhushan 
6262e0d2956SDiana Craciun 	mutex_destroy(&vdev->igate);
6272e0d2956SDiana Craciun 
628f2ba7e8cSDiana Craciun 	vfio_fsl_mc_reflck_put(vdev->reflck);
629df747bcdSDiana Craciun 
630704f5082SDiana Craciun 	if (is_fsl_mc_bus_dprc(mc_dev)) {
631704f5082SDiana Craciun 		dprc_remove_devices(mc_dev, NULL, 0);
632704f5082SDiana Craciun 		dprc_cleanup(mc_dev);
633704f5082SDiana Craciun 	}
634704f5082SDiana Craciun 
635704f5082SDiana Craciun 	if (vdev->nb.notifier_call)
636704f5082SDiana Craciun 		bus_unregister_notifier(&fsl_mc_bus_type, &vdev->nb);
637704f5082SDiana Craciun 
638fb1ff4c1SBharat Bhushan 	vfio_iommu_group_put(mc_dev->dev.iommu_group, dev);
639fb1ff4c1SBharat Bhushan 
640fb1ff4c1SBharat Bhushan 	return 0;
641fb1ff4c1SBharat Bhushan }
642fb1ff4c1SBharat Bhushan 
643fb1ff4c1SBharat Bhushan static struct fsl_mc_driver vfio_fsl_mc_driver = {
644fb1ff4c1SBharat Bhushan 	.probe		= vfio_fsl_mc_probe,
645fb1ff4c1SBharat Bhushan 	.remove		= vfio_fsl_mc_remove,
646fb1ff4c1SBharat Bhushan 	.driver	= {
647fb1ff4c1SBharat Bhushan 		.name	= "vfio-fsl-mc",
648fb1ff4c1SBharat Bhushan 		.owner	= THIS_MODULE,
649fb1ff4c1SBharat Bhushan 	},
650fb1ff4c1SBharat Bhushan };
651fb1ff4c1SBharat Bhushan 
652fb1ff4c1SBharat Bhushan static int __init vfio_fsl_mc_driver_init(void)
653fb1ff4c1SBharat Bhushan {
654fb1ff4c1SBharat Bhushan 	return fsl_mc_driver_register(&vfio_fsl_mc_driver);
655fb1ff4c1SBharat Bhushan }
656fb1ff4c1SBharat Bhushan 
657fb1ff4c1SBharat Bhushan static void __exit vfio_fsl_mc_driver_exit(void)
658fb1ff4c1SBharat Bhushan {
659fb1ff4c1SBharat Bhushan 	fsl_mc_driver_unregister(&vfio_fsl_mc_driver);
660fb1ff4c1SBharat Bhushan }
661fb1ff4c1SBharat Bhushan 
662fb1ff4c1SBharat Bhushan module_init(vfio_fsl_mc_driver_init);
663fb1ff4c1SBharat Bhushan module_exit(vfio_fsl_mc_driver_exit);
664fb1ff4c1SBharat Bhushan 
665fb1ff4c1SBharat Bhushan MODULE_LICENSE("Dual BSD/GPL");
666fb1ff4c1SBharat Bhushan MODULE_DESCRIPTION("VFIO for FSL-MC devices - User Level meta-driver");
667