static int qp_alloc_guest_work()

in vmw_vmci/vmci_queue_pair.c [1092:1261]


static int qp_alloc_guest_work(struct vmci_handle *handle,
			       struct vmci_queue **produce_q,
			       u64 produce_size,
			       struct vmci_queue **consume_q,
			       u64 consume_size,
			       u32 peer,
			       u32 flags,
			       u32 priv_flags)
{
	const u64 num_produce_pages =
	    DIV_ROUND_UP(produce_size, PAGE_SIZE) + 1;
	const u64 num_consume_pages =
	    DIV_ROUND_UP(consume_size, PAGE_SIZE) + 1;
	void *my_produce_q = NULL;
	void *my_consume_q = NULL;
	int result;
	struct qp_guest_endpoint *queue_pair_entry = NULL;

	if (priv_flags != VMCI_NO_PRIVILEGE_FLAGS)
		return VMCI_ERROR_NO_ACCESS;

	mutex_lock(&qp_guest_endpoints.mutex);

	queue_pair_entry = qp_guest_handle_to_entry(*handle);
	if (queue_pair_entry) {
		if (queue_pair_entry->qp.flags & VMCI_QPFLAG_LOCAL) {
			/* Local attach case. */
			if (queue_pair_entry->qp.ref_count > 1) {
				pr_devel("Error attempting to attach more than once\n");
				result = VMCI_ERROR_UNAVAILABLE;
				goto error_keep_entry;
			}

			if (queue_pair_entry->qp.produce_size != consume_size ||
			    queue_pair_entry->qp.consume_size !=
			    produce_size ||
			    queue_pair_entry->qp.flags !=
			    (flags & ~VMCI_QPFLAG_ATTACH_ONLY)) {
				pr_devel("Error mismatched queue pair in local attach\n");
				result = VMCI_ERROR_QUEUEPAIR_MISMATCH;
				goto error_keep_entry;
			}

			/*
			 * Do a local attach.  We swap the consume and
			 * produce queues for the attacher and deliver
			 * an attach event.
			 */
			result = qp_notify_peer_local(true, *handle);
			if (result < VMCI_SUCCESS)
				goto error_keep_entry;

			my_produce_q = queue_pair_entry->consume_q;
			my_consume_q = queue_pair_entry->produce_q;
			goto out;
		}

		result = VMCI_ERROR_ALREADY_EXISTS;
		goto error_keep_entry;
	}

	my_produce_q = qp_alloc_queue(produce_size, flags);
	if (!my_produce_q) {
		pr_warn("Error allocating pages for produce queue\n");
		result = VMCI_ERROR_NO_MEM;
		goto error;
	}

	my_consume_q = qp_alloc_queue(consume_size, flags);
	if (!my_consume_q) {
		pr_warn("Error allocating pages for consume queue\n");
		result = VMCI_ERROR_NO_MEM;
		goto error;
	}

	queue_pair_entry = qp_guest_endpoint_create(*handle, peer, flags,
						    produce_size, consume_size,
						    my_produce_q, my_consume_q);
	if (!queue_pair_entry) {
		pr_warn("Error allocating memory in %s\n", __func__);
		result = VMCI_ERROR_NO_MEM;
		goto error;
	}

	result = qp_alloc_ppn_set(my_produce_q, num_produce_pages, my_consume_q,
				  num_consume_pages,
				  &queue_pair_entry->ppn_set);
	if (result < VMCI_SUCCESS) {
		pr_warn("qp_alloc_ppn_set failed\n");
		goto error;
	}

	/*
	 * It's only necessary to notify the host if this queue pair will be
	 * attached to from another context.
	 */
	if (queue_pair_entry->qp.flags & VMCI_QPFLAG_LOCAL) {
		/* Local create case. */
		u32 context_id = vmci_get_context_id();

		/*
		 * Enforce similar checks on local queue pairs as we
		 * do for regular ones.  The handle's context must
		 * match the creator or attacher context id (here they
		 * are both the current context id) and the
		 * attach-only flag cannot exist during create.  We
		 * also ensure specified peer is this context or an
		 * invalid one.
		 */
		if (queue_pair_entry->qp.handle.context != context_id ||
		    (queue_pair_entry->qp.peer != VMCI_INVALID_ID &&
		     queue_pair_entry->qp.peer != context_id)) {
			result = VMCI_ERROR_NO_ACCESS;
			goto error;
		}

		if (queue_pair_entry->qp.flags & VMCI_QPFLAG_ATTACH_ONLY) {
			result = VMCI_ERROR_NOT_FOUND;
			goto error;
		}
	} else {
		result = qp_alloc_hypercall(queue_pair_entry);
		if (result < VMCI_SUCCESS) {
			pr_devel("qp_alloc_hypercall result = %d\n", result);
			goto error;
		}
	}

	qp_init_queue_mutex((struct vmci_queue *)my_produce_q,
			    (struct vmci_queue *)my_consume_q);

	qp_list_add_entry(&qp_guest_endpoints, &queue_pair_entry->qp);

 out:
	queue_pair_entry->qp.ref_count++;
	*handle = queue_pair_entry->qp.handle;
	*produce_q = (struct vmci_queue *)my_produce_q;
	*consume_q = (struct vmci_queue *)my_consume_q;

	/*
	 * We should initialize the queue pair header pages on a local
	 * queue pair create.  For non-local queue pairs, the
	 * hypervisor initializes the header pages in the create step.
	 */
	if ((queue_pair_entry->qp.flags & VMCI_QPFLAG_LOCAL) &&
	    queue_pair_entry->qp.ref_count == 1) {
		vmci_q_header_init((*produce_q)->q_header, *handle);
		vmci_q_header_init((*consume_q)->q_header, *handle);
	}

	mutex_unlock(&qp_guest_endpoints.mutex);

	return VMCI_SUCCESS;

 error:
	mutex_unlock(&qp_guest_endpoints.mutex);
	if (queue_pair_entry) {
		/* The queues will be freed inside the destroy routine. */
		qp_guest_endpoint_destroy(queue_pair_entry);
	} else {
		qp_free_queue(my_produce_q, produce_size);
		qp_free_queue(my_consume_q, consume_size);
	}
	return result;

 error_keep_entry:
	/* This path should only be used when an existing entry was found. */
	mutex_unlock(&qp_guest_endpoints.mutex);
	return result;
}