static long privcmd_ioctl_mmap_resource()

in privcmd.c [720:833]


static long privcmd_ioctl_mmap_resource(struct file *file,
				struct privcmd_mmap_resource __user *udata)
{
	struct privcmd_data *data = file->private_data;
	struct mm_struct *mm = current->mm;
	struct vm_area_struct *vma;
	struct privcmd_mmap_resource kdata;
	xen_pfn_t *pfns = NULL;
	struct xen_mem_acquire_resource xdata = { };
	int rc;

	if (copy_from_user(&kdata, udata, sizeof(kdata)))
		return -EFAULT;

	/* If restriction is in place, check the domid matches */
	if (data->domid != DOMID_INVALID && data->domid != kdata.dom)
		return -EPERM;

	/* Both fields must be set or unset */
	if (!!kdata.addr != !!kdata.num)
		return -EINVAL;

	xdata.domid = kdata.dom;
	xdata.type = kdata.type;
	xdata.id = kdata.id;

	if (!kdata.addr && !kdata.num) {
		/* Query the size of the resource. */
		rc = HYPERVISOR_memory_op(XENMEM_acquire_resource, &xdata);
		if (rc)
			return rc;
		return __put_user(xdata.nr_frames, &udata->num);
	}

	mmap_write_lock(mm);

	vma = find_vma(mm, kdata.addr);
	if (!vma || vma->vm_ops != &privcmd_vm_ops) {
		rc = -EINVAL;
		goto out;
	}

	pfns = kcalloc(kdata.num, sizeof(*pfns), GFP_KERNEL);
	if (!pfns) {
		rc = -ENOMEM;
		goto out;
	}

	if (IS_ENABLED(CONFIG_XEN_AUTO_XLATE) &&
	    xen_feature(XENFEAT_auto_translated_physmap)) {
		unsigned int nr = DIV_ROUND_UP(kdata.num, XEN_PFN_PER_PAGE);
		struct page **pages;
		unsigned int i;

		rc = alloc_empty_pages(vma, nr);
		if (rc < 0)
			goto out;

		pages = vma->vm_private_data;
		for (i = 0; i < kdata.num; i++) {
			xen_pfn_t pfn =
				page_to_xen_pfn(pages[i / XEN_PFN_PER_PAGE]);

			pfns[i] = pfn + (i % XEN_PFN_PER_PAGE);
		}
	} else
		vma->vm_private_data = PRIV_VMA_LOCKED;

	xdata.frame = kdata.idx;
	xdata.nr_frames = kdata.num;
	set_xen_guest_handle(xdata.frame_list, pfns);

	xen_preemptible_hcall_begin();
	rc = HYPERVISOR_memory_op(XENMEM_acquire_resource, &xdata);
	xen_preemptible_hcall_end();

	if (rc)
		goto out;

	if (IS_ENABLED(CONFIG_XEN_AUTO_XLATE) &&
	    xen_feature(XENFEAT_auto_translated_physmap)) {
		rc = xen_remap_vma_range(vma, kdata.addr, kdata.num << PAGE_SHIFT);
	} else {
		unsigned int domid =
			(xdata.flags & XENMEM_rsrc_acq_caller_owned) ?
			DOMID_SELF : kdata.dom;
		int num, *errs = (int *)pfns;

		BUILD_BUG_ON(sizeof(*errs) > sizeof(*pfns));
		num = xen_remap_domain_mfn_array(vma,
						 kdata.addr & PAGE_MASK,
						 pfns, kdata.num, errs,
						 vma->vm_page_prot,
						 domid);
		if (num < 0)
			rc = num;
		else if (num != kdata.num) {
			unsigned int i;

			for (i = 0; i < num; i++) {
				rc = errs[i];
				if (rc < 0)
					break;
			}
		} else
			rc = 0;
	}

out:
	mmap_write_unlock(mm);
	kfree(pfns);

	return rc;
}