xref: /openbmc/linux/drivers/vfio/fsl-mc/vfio_fsl_mc.c (revision c900529f3d9161bfde5cca0754f83b4d3c3e0220)
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 
vfio_fsl_mc_open_device(struct vfio_device * core_vdev)22da119f38SJason Gunthorpe static int vfio_fsl_mc_open_device(struct vfio_device *core_vdev)
23f2ba7e8cSDiana Craciun {
24da119f38SJason Gunthorpe 	struct vfio_fsl_mc_device *vdev =
25da119f38SJason 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),
314a6c971aSYishai Hadas 				GFP_KERNEL_ACCOUNT);
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 
vfio_fsl_mc_regions_cleanup(struct vfio_fsl_mc_device * vdev)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 
vfio_fsl_mc_reset_device(struct vfio_fsl_mc_device * vdev)688798a803SDiana Craciun static int vfio_fsl_mc_reset_device(struct vfio_fsl_mc_device *vdev)
698798a803SDiana Craciun {
708798a803SDiana Craciun 	struct fsl_mc_device *mc_dev = vdev->mc_dev;
718798a803SDiana Craciun 	int ret = 0;
728798a803SDiana Craciun 
738798a803SDiana Craciun 	if (is_fsl_mc_bus_dprc(vdev->mc_dev)) {
748798a803SDiana Craciun 		return dprc_reset_container(mc_dev->mc_io, 0,
758798a803SDiana Craciun 					mc_dev->mc_handle,
768798a803SDiana Craciun 					mc_dev->obj_desc.id,
778798a803SDiana Craciun 					DPRC_RESET_OPTION_NON_RECURSIVE);
788798a803SDiana Craciun 	} else {
798798a803SDiana Craciun 		u16 token;
808798a803SDiana Craciun 
818798a803SDiana Craciun 		ret = fsl_mc_obj_open(mc_dev->mc_io, 0, mc_dev->obj_desc.id,
828798a803SDiana Craciun 				      mc_dev->obj_desc.type,
838798a803SDiana Craciun 				      &token);
848798a803SDiana Craciun 		if (ret)
858798a803SDiana Craciun 			goto out;
868798a803SDiana Craciun 		ret = fsl_mc_obj_reset(mc_dev->mc_io, 0, token);
878798a803SDiana Craciun 		if (ret) {
888798a803SDiana Craciun 			fsl_mc_obj_close(mc_dev->mc_io, 0, token);
898798a803SDiana Craciun 			goto out;
908798a803SDiana Craciun 		}
918798a803SDiana Craciun 		ret = fsl_mc_obj_close(mc_dev->mc_io, 0, token);
928798a803SDiana Craciun 	}
938798a803SDiana Craciun out:
948798a803SDiana Craciun 	return ret;
958798a803SDiana Craciun }
96da119f38SJason Gunthorpe 
vfio_fsl_mc_close_device(struct vfio_device * core_vdev)97da119f38SJason Gunthorpe static void vfio_fsl_mc_close_device(struct vfio_device *core_vdev)
98df747bcdSDiana Craciun {
996df62c5bSJason Gunthorpe 	struct vfio_fsl_mc_device *vdev =
1006df62c5bSJason Gunthorpe 		container_of(core_vdev, struct vfio_fsl_mc_device, vdev);
101cc0ee20bSDiana Craciun 	struct fsl_mc_device *mc_dev = vdev->mc_dev;
102cc0ee20bSDiana Craciun 	struct device *cont_dev = fsl_mc_cont_dev(&mc_dev->dev);
103cc0ee20bSDiana Craciun 	struct fsl_mc_device *mc_cont = to_fsl_mc_device(cont_dev);
104da119f38SJason Gunthorpe 	int ret;
105cc0ee20bSDiana Craciun 
106df747bcdSDiana Craciun 	vfio_fsl_mc_regions_cleanup(vdev);
107df747bcdSDiana Craciun 
108cc0ee20bSDiana Craciun 	/* reset the device before cleaning up the interrupts */
1098798a803SDiana Craciun 	ret = vfio_fsl_mc_reset_device(vdev);
110cc0ee20bSDiana Craciun 
111eab60bbcSChristophe JAILLET 	if (ret)
112da119f38SJason Gunthorpe 		dev_warn(&mc_cont->dev,
113eab60bbcSChristophe JAILLET 			 "VFIO_FSL_MC: reset device has failed (%d)\n", ret);
114cc0ee20bSDiana Craciun 
115cc0ee20bSDiana Craciun 	vfio_fsl_mc_irqs_cleanup(vdev);
116cc0ee20bSDiana Craciun 
117cc0ee20bSDiana Craciun 	fsl_mc_cleanup_irq_pool(mc_cont);
118cc0ee20bSDiana Craciun }
119cc0ee20bSDiana Craciun 
vfio_fsl_mc_ioctl(struct vfio_device * core_vdev,unsigned int cmd,unsigned long arg)1206df62c5bSJason Gunthorpe static long vfio_fsl_mc_ioctl(struct vfio_device *core_vdev,
1216df62c5bSJason Gunthorpe 			      unsigned int cmd, unsigned long arg)
122fb1ff4c1SBharat Bhushan {
123f97f4c04SDiana Craciun 	unsigned long minsz;
1246df62c5bSJason Gunthorpe 	struct vfio_fsl_mc_device *vdev =
1256df62c5bSJason Gunthorpe 		container_of(core_vdev, struct vfio_fsl_mc_device, vdev);
126f97f4c04SDiana Craciun 	struct fsl_mc_device *mc_dev = vdev->mc_dev;
127f97f4c04SDiana Craciun 
128fb1ff4c1SBharat Bhushan 	switch (cmd) {
129fb1ff4c1SBharat Bhushan 	case VFIO_DEVICE_GET_INFO:
130fb1ff4c1SBharat Bhushan 	{
131f97f4c04SDiana Craciun 		struct vfio_device_info info;
132f97f4c04SDiana Craciun 
133f97f4c04SDiana Craciun 		minsz = offsetofend(struct vfio_device_info, num_irqs);
134f97f4c04SDiana Craciun 
135f97f4c04SDiana Craciun 		if (copy_from_user(&info, (void __user *)arg, minsz))
136f97f4c04SDiana Craciun 			return -EFAULT;
137f97f4c04SDiana Craciun 
138f97f4c04SDiana Craciun 		if (info.argsz < minsz)
139f97f4c04SDiana Craciun 			return -EINVAL;
140f97f4c04SDiana Craciun 
141f97f4c04SDiana Craciun 		info.flags = VFIO_DEVICE_FLAGS_FSL_MC;
142ac93ab2bSDiana Craciun 
143ac93ab2bSDiana Craciun 		if (is_fsl_mc_bus_dprc(mc_dev))
144ac93ab2bSDiana Craciun 			info.flags |= VFIO_DEVICE_FLAGS_RESET;
145ac93ab2bSDiana Craciun 
146f97f4c04SDiana Craciun 		info.num_regions = mc_dev->obj_desc.region_count;
147f97f4c04SDiana Craciun 		info.num_irqs = mc_dev->obj_desc.irq_count;
148f97f4c04SDiana Craciun 
149f97f4c04SDiana Craciun 		return copy_to_user((void __user *)arg, &info, minsz) ?
150f97f4c04SDiana Craciun 			-EFAULT : 0;
151fb1ff4c1SBharat Bhushan 	}
152fb1ff4c1SBharat Bhushan 	case VFIO_DEVICE_GET_REGION_INFO:
153fb1ff4c1SBharat Bhushan 	{
154df747bcdSDiana Craciun 		struct vfio_region_info info;
155df747bcdSDiana Craciun 
156df747bcdSDiana Craciun 		minsz = offsetofend(struct vfio_region_info, offset);
157df747bcdSDiana Craciun 
158df747bcdSDiana Craciun 		if (copy_from_user(&info, (void __user *)arg, minsz))
159df747bcdSDiana Craciun 			return -EFAULT;
160df747bcdSDiana Craciun 
161df747bcdSDiana Craciun 		if (info.argsz < minsz)
162df747bcdSDiana Craciun 			return -EINVAL;
163df747bcdSDiana Craciun 
164df747bcdSDiana Craciun 		if (info.index >= mc_dev->obj_desc.region_count)
165df747bcdSDiana Craciun 			return -EINVAL;
166df747bcdSDiana Craciun 
167df747bcdSDiana Craciun 		/* map offset to the physical address  */
168df747bcdSDiana Craciun 		info.offset = VFIO_FSL_MC_INDEX_TO_OFFSET(info.index);
169df747bcdSDiana Craciun 		info.size = vdev->regions[info.index].size;
170df747bcdSDiana Craciun 		info.flags = vdev->regions[info.index].flags;
171df747bcdSDiana Craciun 
17209699e56SDan Carpenter 		if (copy_to_user((void __user *)arg, &info, minsz))
17309699e56SDan Carpenter 			return -EFAULT;
17409699e56SDan Carpenter 		return 0;
175fb1ff4c1SBharat Bhushan 	}
176fb1ff4c1SBharat Bhushan 	case VFIO_DEVICE_GET_IRQ_INFO:
177fb1ff4c1SBharat Bhushan 	{
1782e0d2956SDiana Craciun 		struct vfio_irq_info info;
1792e0d2956SDiana Craciun 
1802e0d2956SDiana Craciun 		minsz = offsetofend(struct vfio_irq_info, count);
1812e0d2956SDiana Craciun 		if (copy_from_user(&info, (void __user *)arg, minsz))
1822e0d2956SDiana Craciun 			return -EFAULT;
1832e0d2956SDiana Craciun 
1842e0d2956SDiana Craciun 		if (info.argsz < minsz)
1852e0d2956SDiana Craciun 			return -EINVAL;
1862e0d2956SDiana Craciun 
1872e0d2956SDiana Craciun 		if (info.index >= mc_dev->obj_desc.irq_count)
1882e0d2956SDiana Craciun 			return -EINVAL;
1892e0d2956SDiana Craciun 
1902e0d2956SDiana Craciun 		info.flags = VFIO_IRQ_INFO_EVENTFD;
1912e0d2956SDiana Craciun 		info.count = 1;
1922e0d2956SDiana Craciun 
19309699e56SDan Carpenter 		if (copy_to_user((void __user *)arg, &info, minsz))
19409699e56SDan Carpenter 			return -EFAULT;
19509699e56SDan Carpenter 		return 0;
196fb1ff4c1SBharat Bhushan 	}
197fb1ff4c1SBharat Bhushan 	case VFIO_DEVICE_SET_IRQS:
198fb1ff4c1SBharat Bhushan 	{
1992e0d2956SDiana Craciun 		struct vfio_irq_set hdr;
2002e0d2956SDiana Craciun 		u8 *data = NULL;
2012e0d2956SDiana Craciun 		int ret = 0;
2022e0d2956SDiana Craciun 		size_t data_size = 0;
2032e0d2956SDiana Craciun 
2042e0d2956SDiana Craciun 		minsz = offsetofend(struct vfio_irq_set, count);
2052e0d2956SDiana Craciun 
2062e0d2956SDiana Craciun 		if (copy_from_user(&hdr, (void __user *)arg, minsz))
2072e0d2956SDiana Craciun 			return -EFAULT;
2082e0d2956SDiana Craciun 
2092e0d2956SDiana Craciun 		ret = vfio_set_irqs_validate_and_prepare(&hdr, mc_dev->obj_desc.irq_count,
2102e0d2956SDiana Craciun 					mc_dev->obj_desc.irq_count, &data_size);
2112e0d2956SDiana Craciun 		if (ret)
2122e0d2956SDiana Craciun 			return ret;
2132e0d2956SDiana Craciun 
2142e0d2956SDiana Craciun 		if (data_size) {
2152e0d2956SDiana Craciun 			data = memdup_user((void __user *)(arg + minsz),
2162e0d2956SDiana Craciun 				   data_size);
2172e0d2956SDiana Craciun 			if (IS_ERR(data))
2182e0d2956SDiana Craciun 				return PTR_ERR(data);
2192e0d2956SDiana Craciun 		}
2202e0d2956SDiana Craciun 
2212e0d2956SDiana Craciun 		mutex_lock(&vdev->igate);
2222e0d2956SDiana Craciun 		ret = vfio_fsl_mc_set_irqs_ioctl(vdev, hdr.flags,
2232e0d2956SDiana Craciun 						 hdr.index, hdr.start,
2242e0d2956SDiana Craciun 						 hdr.count, data);
2252e0d2956SDiana Craciun 		mutex_unlock(&vdev->igate);
2262e0d2956SDiana Craciun 		kfree(data);
2272e0d2956SDiana Craciun 
2282e0d2956SDiana Craciun 		return ret;
229fb1ff4c1SBharat Bhushan 	}
230fb1ff4c1SBharat Bhushan 	case VFIO_DEVICE_RESET:
231fb1ff4c1SBharat Bhushan 	{
2328798a803SDiana Craciun 		return vfio_fsl_mc_reset_device(vdev);
233ac93ab2bSDiana Craciun 
234fb1ff4c1SBharat Bhushan 	}
235fb1ff4c1SBharat Bhushan 	default:
236fb1ff4c1SBharat Bhushan 		return -ENOTTY;
237fb1ff4c1SBharat Bhushan 	}
238fb1ff4c1SBharat Bhushan }
239fb1ff4c1SBharat Bhushan 
vfio_fsl_mc_read(struct vfio_device * core_vdev,char __user * buf,size_t count,loff_t * ppos)2406df62c5bSJason Gunthorpe static ssize_t vfio_fsl_mc_read(struct vfio_device *core_vdev, char __user *buf,
241fb1ff4c1SBharat Bhushan 				size_t count, loff_t *ppos)
242fb1ff4c1SBharat Bhushan {
2436df62c5bSJason Gunthorpe 	struct vfio_fsl_mc_device *vdev =
2446df62c5bSJason Gunthorpe 		container_of(core_vdev, struct vfio_fsl_mc_device, vdev);
2451bb141edSDiana Craciun 	unsigned int index = VFIO_FSL_MC_OFFSET_TO_INDEX(*ppos);
2461bb141edSDiana Craciun 	loff_t off = *ppos & VFIO_FSL_MC_OFFSET_MASK;
2471bb141edSDiana Craciun 	struct fsl_mc_device *mc_dev = vdev->mc_dev;
2481bb141edSDiana Craciun 	struct vfio_fsl_mc_region *region;
2491bb141edSDiana Craciun 	u64 data[8];
2501bb141edSDiana Craciun 	int i;
2511bb141edSDiana Craciun 
2521bb141edSDiana Craciun 	if (index >= mc_dev->obj_desc.region_count)
253fb1ff4c1SBharat Bhushan 		return -EINVAL;
2541bb141edSDiana Craciun 
2551bb141edSDiana Craciun 	region = &vdev->regions[index];
2561bb141edSDiana Craciun 
2571bb141edSDiana Craciun 	if (!(region->flags & VFIO_REGION_INFO_FLAG_READ))
2581bb141edSDiana Craciun 		return -EINVAL;
2591bb141edSDiana Craciun 
2601bb141edSDiana Craciun 	if (!region->ioaddr) {
2611bb141edSDiana Craciun 		region->ioaddr = ioremap(region->addr, region->size);
2621bb141edSDiana Craciun 		if (!region->ioaddr)
2631bb141edSDiana Craciun 			return -ENOMEM;
2641bb141edSDiana Craciun 	}
2651bb141edSDiana Craciun 
2661bb141edSDiana Craciun 	if (count != 64 || off != 0)
2671bb141edSDiana Craciun 		return -EINVAL;
2681bb141edSDiana Craciun 
2691bb141edSDiana Craciun 	for (i = 7; i >= 0; i--)
2701bb141edSDiana Craciun 		data[i] = readq(region->ioaddr + i * sizeof(uint64_t));
2711bb141edSDiana Craciun 
2721bb141edSDiana Craciun 	if (copy_to_user(buf, data, 64))
2731bb141edSDiana Craciun 		return -EFAULT;
2741bb141edSDiana Craciun 
2751bb141edSDiana Craciun 	return count;
2761bb141edSDiana Craciun }
2771bb141edSDiana Craciun 
2781bb141edSDiana Craciun #define MC_CMD_COMPLETION_TIMEOUT_MS    5000
2791bb141edSDiana Craciun #define MC_CMD_COMPLETION_POLLING_MAX_SLEEP_USECS    500
2801bb141edSDiana Craciun 
vfio_fsl_mc_send_command(void __iomem * ioaddr,uint64_t * cmd_data)2811bb141edSDiana Craciun static int vfio_fsl_mc_send_command(void __iomem *ioaddr, uint64_t *cmd_data)
2821bb141edSDiana Craciun {
2831bb141edSDiana Craciun 	int i;
2841bb141edSDiana Craciun 	enum mc_cmd_status status;
2851bb141edSDiana Craciun 	unsigned long timeout_usecs = MC_CMD_COMPLETION_TIMEOUT_MS * 1000;
2861bb141edSDiana Craciun 
2871bb141edSDiana Craciun 	/* Write at command parameter into portal */
2881bb141edSDiana Craciun 	for (i = 7; i >= 1; i--)
2891bb141edSDiana Craciun 		writeq_relaxed(cmd_data[i], ioaddr + i * sizeof(uint64_t));
2901bb141edSDiana Craciun 
2911bb141edSDiana Craciun 	/* Write command header in the end */
2921bb141edSDiana Craciun 	writeq(cmd_data[0], ioaddr);
2931bb141edSDiana Craciun 
2941bb141edSDiana Craciun 	/* Wait for response before returning to user-space
2951bb141edSDiana Craciun 	 * This can be optimized in future to even prepare response
2961bb141edSDiana Craciun 	 * before returning to user-space and avoid read ioctl.
2971bb141edSDiana Craciun 	 */
2981bb141edSDiana Craciun 	for (;;) {
2991bb141edSDiana Craciun 		u64 header;
3001bb141edSDiana Craciun 		struct mc_cmd_header *resp_hdr;
3011bb141edSDiana Craciun 
3021bb141edSDiana Craciun 		header = cpu_to_le64(readq_relaxed(ioaddr));
3031bb141edSDiana Craciun 
3041bb141edSDiana Craciun 		resp_hdr = (struct mc_cmd_header *)&header;
3051bb141edSDiana Craciun 		status = (enum mc_cmd_status)resp_hdr->status;
3061bb141edSDiana Craciun 		if (status != MC_CMD_STATUS_READY)
3071bb141edSDiana Craciun 			break;
3081bb141edSDiana Craciun 
3091bb141edSDiana Craciun 		udelay(MC_CMD_COMPLETION_POLLING_MAX_SLEEP_USECS);
3101bb141edSDiana Craciun 		timeout_usecs -= MC_CMD_COMPLETION_POLLING_MAX_SLEEP_USECS;
3111bb141edSDiana Craciun 		if (timeout_usecs == 0)
3121bb141edSDiana Craciun 			return -ETIMEDOUT;
3131bb141edSDiana Craciun 	}
3141bb141edSDiana Craciun 
3151bb141edSDiana Craciun 	return 0;
316fb1ff4c1SBharat Bhushan }
317fb1ff4c1SBharat Bhushan 
vfio_fsl_mc_write(struct vfio_device * core_vdev,const char __user * buf,size_t count,loff_t * ppos)3186df62c5bSJason Gunthorpe static ssize_t vfio_fsl_mc_write(struct vfio_device *core_vdev,
3196df62c5bSJason Gunthorpe 				 const char __user *buf, size_t count,
3206df62c5bSJason Gunthorpe 				 loff_t *ppos)
321fb1ff4c1SBharat Bhushan {
3226df62c5bSJason Gunthorpe 	struct vfio_fsl_mc_device *vdev =
3236df62c5bSJason Gunthorpe 		container_of(core_vdev, struct vfio_fsl_mc_device, vdev);
3241bb141edSDiana Craciun 	unsigned int index = VFIO_FSL_MC_OFFSET_TO_INDEX(*ppos);
3251bb141edSDiana Craciun 	loff_t off = *ppos & VFIO_FSL_MC_OFFSET_MASK;
3261bb141edSDiana Craciun 	struct fsl_mc_device *mc_dev = vdev->mc_dev;
3271bb141edSDiana Craciun 	struct vfio_fsl_mc_region *region;
3281bb141edSDiana Craciun 	u64 data[8];
3291bb141edSDiana Craciun 	int ret;
3301bb141edSDiana Craciun 
3311bb141edSDiana Craciun 	if (index >= mc_dev->obj_desc.region_count)
332fb1ff4c1SBharat Bhushan 		return -EINVAL;
3331bb141edSDiana Craciun 
3341bb141edSDiana Craciun 	region = &vdev->regions[index];
3351bb141edSDiana Craciun 
3361bb141edSDiana Craciun 	if (!(region->flags & VFIO_REGION_INFO_FLAG_WRITE))
3371bb141edSDiana Craciun 		return -EINVAL;
3381bb141edSDiana Craciun 
3391bb141edSDiana Craciun 	if (!region->ioaddr) {
3401bb141edSDiana Craciun 		region->ioaddr = ioremap(region->addr, region->size);
3411bb141edSDiana Craciun 		if (!region->ioaddr)
3421bb141edSDiana Craciun 			return -ENOMEM;
3431bb141edSDiana Craciun 	}
3441bb141edSDiana Craciun 
3451bb141edSDiana Craciun 	if (count != 64 || off != 0)
3461bb141edSDiana Craciun 		return -EINVAL;
3471bb141edSDiana Craciun 
3481bb141edSDiana Craciun 	if (copy_from_user(&data, buf, 64))
3491bb141edSDiana Craciun 		return -EFAULT;
3501bb141edSDiana Craciun 
3511bb141edSDiana Craciun 	ret = vfio_fsl_mc_send_command(region->ioaddr, data);
3521bb141edSDiana Craciun 	if (ret)
3531bb141edSDiana Craciun 		return ret;
3541bb141edSDiana Craciun 
3551bb141edSDiana Craciun 	return count;
3561bb141edSDiana Craciun 
357fb1ff4c1SBharat Bhushan }
358fb1ff4c1SBharat Bhushan 
vfio_fsl_mc_mmap_mmio(struct vfio_fsl_mc_region region,struct vm_area_struct * vma)35967247289SDiana Craciun static int vfio_fsl_mc_mmap_mmio(struct vfio_fsl_mc_region region,
36067247289SDiana Craciun 				 struct vm_area_struct *vma)
36167247289SDiana Craciun {
36267247289SDiana Craciun 	u64 size = vma->vm_end - vma->vm_start;
36367247289SDiana Craciun 	u64 pgoff, base;
36467247289SDiana Craciun 	u8 region_cacheable;
36567247289SDiana Craciun 
36667247289SDiana Craciun 	pgoff = vma->vm_pgoff &
36767247289SDiana Craciun 		((1U << (VFIO_FSL_MC_OFFSET_SHIFT - PAGE_SHIFT)) - 1);
36867247289SDiana Craciun 	base = pgoff << PAGE_SHIFT;
36967247289SDiana Craciun 
37067247289SDiana Craciun 	if (region.size < PAGE_SIZE || base + size > region.size)
37167247289SDiana Craciun 		return -EINVAL;
37267247289SDiana Craciun 
37367247289SDiana Craciun 	region_cacheable = (region.type & FSL_MC_REGION_CACHEABLE) &&
37467247289SDiana Craciun 			   (region.type & FSL_MC_REGION_SHAREABLE);
37567247289SDiana Craciun 	if (!region_cacheable)
37667247289SDiana Craciun 		vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
37767247289SDiana Craciun 
37867247289SDiana Craciun 	vma->vm_pgoff = (region.addr >> PAGE_SHIFT) + pgoff;
37967247289SDiana Craciun 
38067247289SDiana Craciun 	return remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff,
38167247289SDiana Craciun 			       size, vma->vm_page_prot);
38267247289SDiana Craciun }
38367247289SDiana Craciun 
vfio_fsl_mc_mmap(struct vfio_device * core_vdev,struct vm_area_struct * vma)3846df62c5bSJason Gunthorpe static int vfio_fsl_mc_mmap(struct vfio_device *core_vdev,
3856df62c5bSJason Gunthorpe 			    struct vm_area_struct *vma)
386fb1ff4c1SBharat Bhushan {
3876df62c5bSJason Gunthorpe 	struct vfio_fsl_mc_device *vdev =
3886df62c5bSJason Gunthorpe 		container_of(core_vdev, struct vfio_fsl_mc_device, vdev);
38967247289SDiana Craciun 	struct fsl_mc_device *mc_dev = vdev->mc_dev;
39069848cd6SDan Carpenter 	unsigned int index;
39167247289SDiana Craciun 
39267247289SDiana Craciun 	index = vma->vm_pgoff >> (VFIO_FSL_MC_OFFSET_SHIFT - PAGE_SHIFT);
39367247289SDiana Craciun 
39467247289SDiana Craciun 	if (vma->vm_end < vma->vm_start)
395fb1ff4c1SBharat Bhushan 		return -EINVAL;
39667247289SDiana Craciun 	if (vma->vm_start & ~PAGE_MASK)
39767247289SDiana Craciun 		return -EINVAL;
39867247289SDiana Craciun 	if (vma->vm_end & ~PAGE_MASK)
39967247289SDiana Craciun 		return -EINVAL;
40067247289SDiana Craciun 	if (!(vma->vm_flags & VM_SHARED))
40167247289SDiana Craciun 		return -EINVAL;
40267247289SDiana Craciun 	if (index >= mc_dev->obj_desc.region_count)
40367247289SDiana Craciun 		return -EINVAL;
40467247289SDiana Craciun 
40567247289SDiana Craciun 	if (!(vdev->regions[index].flags & VFIO_REGION_INFO_FLAG_MMAP))
40667247289SDiana Craciun 		return -EINVAL;
40767247289SDiana Craciun 
40867247289SDiana Craciun 	if (!(vdev->regions[index].flags & VFIO_REGION_INFO_FLAG_READ)
40967247289SDiana Craciun 			&& (vma->vm_flags & VM_READ))
41067247289SDiana Craciun 		return -EINVAL;
41167247289SDiana Craciun 
41267247289SDiana Craciun 	if (!(vdev->regions[index].flags & VFIO_REGION_INFO_FLAG_WRITE)
41367247289SDiana Craciun 			&& (vma->vm_flags & VM_WRITE))
41467247289SDiana Craciun 		return -EINVAL;
41567247289SDiana Craciun 
41667247289SDiana Craciun 	vma->vm_private_data = mc_dev;
41767247289SDiana Craciun 
41867247289SDiana Craciun 	return vfio_fsl_mc_mmap_mmio(vdev->regions[index], vma);
419fb1ff4c1SBharat Bhushan }
420fb1ff4c1SBharat Bhushan 
4217566692cSYi Liu static const struct vfio_device_ops vfio_fsl_mc_ops;
vfio_fsl_mc_bus_notifier(struct notifier_block * nb,unsigned long action,void * data)422704f5082SDiana Craciun static int vfio_fsl_mc_bus_notifier(struct notifier_block *nb,
423704f5082SDiana Craciun 				    unsigned long action, void *data)
424704f5082SDiana Craciun {
425704f5082SDiana Craciun 	struct vfio_fsl_mc_device *vdev = container_of(nb,
426704f5082SDiana Craciun 					struct vfio_fsl_mc_device, nb);
427704f5082SDiana Craciun 	struct device *dev = data;
428704f5082SDiana Craciun 	struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev);
429704f5082SDiana Craciun 	struct fsl_mc_device *mc_cont = to_fsl_mc_device(mc_dev->dev.parent);
430704f5082SDiana Craciun 
431704f5082SDiana Craciun 	if (action == BUS_NOTIFY_ADD_DEVICE &&
432704f5082SDiana Craciun 	    vdev->mc_dev == mc_cont) {
433704f5082SDiana Craciun 		mc_dev->driver_override = kasprintf(GFP_KERNEL, "%s",
434704f5082SDiana Craciun 						    vfio_fsl_mc_ops.name);
435704f5082SDiana Craciun 		if (!mc_dev->driver_override)
436704f5082SDiana Craciun 			dev_warn(dev, "VFIO_FSL_MC: Setting driver override for device in dprc %s failed\n",
437704f5082SDiana Craciun 				 dev_name(&mc_cont->dev));
438704f5082SDiana Craciun 		else
439704f5082SDiana Craciun 			dev_info(dev, "VFIO_FSL_MC: Setting driver override for device in dprc %s\n",
440704f5082SDiana Craciun 				 dev_name(&mc_cont->dev));
441704f5082SDiana Craciun 	} else if (action == BUS_NOTIFY_BOUND_DRIVER &&
442704f5082SDiana Craciun 		vdev->mc_dev == mc_cont) {
443704f5082SDiana Craciun 		struct fsl_mc_driver *mc_drv = to_fsl_mc_driver(dev->driver);
444704f5082SDiana Craciun 
445704f5082SDiana Craciun 		if (mc_drv && mc_drv != &vfio_fsl_mc_driver)
446704f5082SDiana Craciun 			dev_warn(dev, "VFIO_FSL_MC: Object %s bound to driver %s while DPRC bound to vfio-fsl-mc\n",
447704f5082SDiana Craciun 				 dev_name(dev), mc_drv->driver.name);
448704f5082SDiana Craciun 	}
449704f5082SDiana Craciun 
450704f5082SDiana Craciun 	return 0;
451704f5082SDiana Craciun }
452704f5082SDiana Craciun 
vfio_fsl_mc_init_device(struct vfio_fsl_mc_device * vdev)453704f5082SDiana Craciun static int vfio_fsl_mc_init_device(struct vfio_fsl_mc_device *vdev)
454704f5082SDiana Craciun {
455704f5082SDiana Craciun 	struct fsl_mc_device *mc_dev = vdev->mc_dev;
456704f5082SDiana Craciun 	int ret;
457704f5082SDiana Craciun 
458704f5082SDiana Craciun 	/* Non-dprc devices share mc_io from parent */
459704f5082SDiana Craciun 	if (!is_fsl_mc_bus_dprc(mc_dev)) {
460704f5082SDiana Craciun 		struct fsl_mc_device *mc_cont = to_fsl_mc_device(mc_dev->dev.parent);
461704f5082SDiana Craciun 
462704f5082SDiana Craciun 		mc_dev->mc_io = mc_cont->mc_io;
463704f5082SDiana Craciun 		return 0;
464704f5082SDiana Craciun 	}
465704f5082SDiana Craciun 
466704f5082SDiana Craciun 	vdev->nb.notifier_call = vfio_fsl_mc_bus_notifier;
467704f5082SDiana Craciun 	ret = bus_register_notifier(&fsl_mc_bus_type, &vdev->nb);
468704f5082SDiana Craciun 	if (ret)
469704f5082SDiana Craciun 		return ret;
470704f5082SDiana Craciun 
471704f5082SDiana Craciun 	/* open DPRC, allocate a MC portal */
472704f5082SDiana Craciun 	ret = dprc_setup(mc_dev);
473704f5082SDiana Craciun 	if (ret) {
474704f5082SDiana Craciun 		dev_err(&mc_dev->dev, "VFIO_FSL_MC: Failed to setup DPRC (%d)\n", ret);
475704f5082SDiana Craciun 		goto out_nc_unreg;
476704f5082SDiana Craciun 	}
477704f5082SDiana Craciun 	return 0;
478704f5082SDiana Craciun 
479704f5082SDiana Craciun out_nc_unreg:
480704f5082SDiana Craciun 	bus_unregister_notifier(&fsl_mc_bus_type, &vdev->nb);
481704f5082SDiana Craciun 	return ret;
482704f5082SDiana Craciun }
483704f5082SDiana Craciun 
vfio_fsl_mc_scan_container(struct fsl_mc_device * mc_dev)4842b1fe162SJason Gunthorpe static int vfio_fsl_mc_scan_container(struct fsl_mc_device *mc_dev)
4852b1fe162SJason Gunthorpe {
4862b1fe162SJason Gunthorpe 	int ret;
4872b1fe162SJason Gunthorpe 
4882b1fe162SJason Gunthorpe 	/* non dprc devices do not scan for other devices */
4892b1fe162SJason Gunthorpe 	if (!is_fsl_mc_bus_dprc(mc_dev))
4902b1fe162SJason Gunthorpe 		return 0;
4912b1fe162SJason Gunthorpe 	ret = dprc_scan_container(mc_dev, false);
4922b1fe162SJason Gunthorpe 	if (ret) {
4932b1fe162SJason Gunthorpe 		dev_err(&mc_dev->dev,
4942b1fe162SJason Gunthorpe 			"VFIO_FSL_MC: Container scanning failed (%d)\n", ret);
4952b1fe162SJason Gunthorpe 		dprc_remove_devices(mc_dev, NULL, 0);
4962b1fe162SJason Gunthorpe 		return ret;
4972b1fe162SJason Gunthorpe 	}
4982b1fe162SJason Gunthorpe 	return 0;
4992b1fe162SJason Gunthorpe }
5002b1fe162SJason Gunthorpe 
vfio_fsl_uninit_device(struct vfio_fsl_mc_device * vdev)5012b1fe162SJason Gunthorpe static void vfio_fsl_uninit_device(struct vfio_fsl_mc_device *vdev)
5022b1fe162SJason Gunthorpe {
5032b1fe162SJason Gunthorpe 	struct fsl_mc_device *mc_dev = vdev->mc_dev;
5042b1fe162SJason Gunthorpe 
5052b1fe162SJason Gunthorpe 	if (!is_fsl_mc_bus_dprc(mc_dev))
5062b1fe162SJason Gunthorpe 		return;
5072b1fe162SJason Gunthorpe 
5082b1fe162SJason Gunthorpe 	dprc_cleanup(mc_dev);
5092b1fe162SJason Gunthorpe 	bus_unregister_notifier(&fsl_mc_bus_type, &vdev->nb);
5102b1fe162SJason Gunthorpe }
5112b1fe162SJason Gunthorpe 
vfio_fsl_mc_init_dev(struct vfio_device * core_vdev)5127566692cSYi Liu static int vfio_fsl_mc_init_dev(struct vfio_device *core_vdev)
5137566692cSYi Liu {
5147566692cSYi Liu 	struct vfio_fsl_mc_device *vdev =
5157566692cSYi Liu 		container_of(core_vdev, struct vfio_fsl_mc_device, vdev);
5167566692cSYi Liu 	struct fsl_mc_device *mc_dev = to_fsl_mc_device(core_vdev->dev);
5177566692cSYi Liu 	int ret;
5187566692cSYi Liu 
5197566692cSYi Liu 	vdev->mc_dev = mc_dev;
5207566692cSYi Liu 	mutex_init(&vdev->igate);
5217566692cSYi Liu 
5227566692cSYi Liu 	if (is_fsl_mc_bus_dprc(mc_dev))
5237566692cSYi Liu 		ret = vfio_assign_device_set(core_vdev, &mc_dev->dev);
5247566692cSYi Liu 	else
5257566692cSYi Liu 		ret = vfio_assign_device_set(core_vdev, mc_dev->dev.parent);
5267566692cSYi Liu 
5277566692cSYi Liu 	if (ret)
5287566692cSYi Liu 		return ret;
5297566692cSYi Liu 
5307566692cSYi Liu 	/* device_set is released by vfio core if @init fails */
5317566692cSYi Liu 	return vfio_fsl_mc_init_device(vdev);
5327566692cSYi Liu }
5337566692cSYi Liu 
vfio_fsl_mc_probe(struct fsl_mc_device * mc_dev)534fb1ff4c1SBharat Bhushan static int vfio_fsl_mc_probe(struct fsl_mc_device *mc_dev)
535fb1ff4c1SBharat Bhushan {
536fb1ff4c1SBharat Bhushan 	struct vfio_fsl_mc_device *vdev;
537fb1ff4c1SBharat Bhushan 	struct device *dev = &mc_dev->dev;
538fb1ff4c1SBharat Bhushan 	int ret;
539fb1ff4c1SBharat Bhushan 
5407566692cSYi Liu 	vdev = vfio_alloc_device(vfio_fsl_mc_device, vdev, dev,
5417566692cSYi Liu 				 &vfio_fsl_mc_ops);
5427566692cSYi Liu 	if (IS_ERR(vdev))
5437566692cSYi Liu 		return PTR_ERR(vdev);
544df747bcdSDiana Craciun 
5450ca78666SJason Gunthorpe 	ret = vfio_register_group_dev(&vdev->vdev);
5462b1fe162SJason Gunthorpe 	if (ret) {
5472b1fe162SJason Gunthorpe 		dev_err(dev, "VFIO_FSL_MC: Failed to add to vfio group\n");
5487566692cSYi Liu 		goto out_put_vdev;
5492b1fe162SJason Gunthorpe 	}
5502e0d2956SDiana Craciun 
5512b1fe162SJason Gunthorpe 	ret = vfio_fsl_mc_scan_container(mc_dev);
5522b1fe162SJason Gunthorpe 	if (ret)
5532b1fe162SJason Gunthorpe 		goto out_group_dev;
5540ca78666SJason Gunthorpe 	dev_set_drvdata(dev, vdev);
555fb1ff4c1SBharat Bhushan 	return 0;
556fb1ff4c1SBharat Bhushan 
557704f5082SDiana Craciun out_group_dev:
5580ca78666SJason Gunthorpe 	vfio_unregister_group_dev(&vdev->vdev);
5597566692cSYi Liu out_put_vdev:
5607566692cSYi Liu 	vfio_put_device(&vdev->vdev);
561fb1ff4c1SBharat Bhushan 	return ret;
562fb1ff4c1SBharat Bhushan }
563fb1ff4c1SBharat Bhushan 
vfio_fsl_mc_release_dev(struct vfio_device * core_vdev)5647566692cSYi Liu static void vfio_fsl_mc_release_dev(struct vfio_device *core_vdev)
5657566692cSYi Liu {
5667566692cSYi Liu 	struct vfio_fsl_mc_device *vdev =
5677566692cSYi Liu 		container_of(core_vdev, struct vfio_fsl_mc_device, vdev);
5687566692cSYi Liu 
5697566692cSYi Liu 	vfio_fsl_uninit_device(vdev);
5707566692cSYi Liu 	mutex_destroy(&vdev->igate);
5717566692cSYi Liu }
5727566692cSYi Liu 
vfio_fsl_mc_remove(struct fsl_mc_device * mc_dev)57359272ad8SUwe Kleine-König static void vfio_fsl_mc_remove(struct fsl_mc_device *mc_dev)
574fb1ff4c1SBharat Bhushan {
575fb1ff4c1SBharat Bhushan 	struct device *dev = &mc_dev->dev;
5760ca78666SJason Gunthorpe 	struct vfio_fsl_mc_device *vdev = dev_get_drvdata(dev);
577fb1ff4c1SBharat Bhushan 
5780ca78666SJason Gunthorpe 	vfio_unregister_group_dev(&vdev->vdev);
579704f5082SDiana Craciun 	dprc_remove_devices(mc_dev, NULL, 0);
5807566692cSYi Liu 	vfio_put_device(&vdev->vdev);
581fb1ff4c1SBharat Bhushan }
582fb1ff4c1SBharat Bhushan 
5837566692cSYi Liu static const struct vfio_device_ops vfio_fsl_mc_ops = {
5847566692cSYi Liu 	.name		= "vfio-fsl-mc",
5857566692cSYi Liu 	.init		= vfio_fsl_mc_init_dev,
5867566692cSYi Liu 	.release	= vfio_fsl_mc_release_dev,
5877566692cSYi Liu 	.open_device	= vfio_fsl_mc_open_device,
5887566692cSYi Liu 	.close_device	= vfio_fsl_mc_close_device,
5897566692cSYi Liu 	.ioctl		= vfio_fsl_mc_ioctl,
5907566692cSYi Liu 	.read		= vfio_fsl_mc_read,
5917566692cSYi Liu 	.write		= vfio_fsl_mc_write,
5927566692cSYi Liu 	.mmap		= vfio_fsl_mc_mmap,
593a4d1f91dSJason Gunthorpe 	.bind_iommufd	= vfio_iommufd_physical_bind,
594a4d1f91dSJason Gunthorpe 	.unbind_iommufd	= vfio_iommufd_physical_unbind,
595a4d1f91dSJason Gunthorpe 	.attach_ioas	= vfio_iommufd_physical_attach_ioas,
5969048c734SYi Liu 	.detach_ioas	= vfio_iommufd_physical_detach_ioas,
5977566692cSYi Liu };
5987566692cSYi Liu 
599fb1ff4c1SBharat Bhushan static struct fsl_mc_driver vfio_fsl_mc_driver = {
600fb1ff4c1SBharat Bhushan 	.probe		= vfio_fsl_mc_probe,
601fb1ff4c1SBharat Bhushan 	.remove		= vfio_fsl_mc_remove,
602fb1ff4c1SBharat Bhushan 	.driver	= {
603fb1ff4c1SBharat Bhushan 		.name	= "vfio-fsl-mc",
604fb1ff4c1SBharat Bhushan 	},
60570693f47SLu Baolu 	.driver_managed_dma = true,
606fb1ff4c1SBharat Bhushan };
607fb1ff4c1SBharat Bhushan 
608*6c092088SLi Zetao module_fsl_mc_driver(vfio_fsl_mc_driver);
609fb1ff4c1SBharat Bhushan 
610fb1ff4c1SBharat Bhushan MODULE_LICENSE("Dual BSD/GPL");
611fb1ff4c1SBharat Bhushan MODULE_DESCRIPTION("VFIO for FSL-MC devices - User Level meta-driver");
612