Changeset View
Changeset View
Standalone View
Standalone View
sys/dev/vmware/vmci/vmci_driver.c
- This file was added.
Property | Old Value | New Value |
---|---|---|
svn:eol-style | null | native \ No newline at end of property |
svn:executable | null | * \ No newline at end of property |
svn:keywords | null | FreeBSD=%H \ No newline at end of property |
svn:mime-type | null | text/plain \ No newline at end of property |
/*- | |||||
* Copyright (c) 2018 VMware, Inc. All Rights Reserved. | |||||
* | |||||
* SPDX-License-Identifier: (BSD-2-Clause AND GPL-2.0) | |||||
*/ | |||||
/* VMCI initialization. */ | |||||
#include "vmci.h" | |||||
#include "vmci_doorbell.h" | |||||
#include "vmci_driver.h" | |||||
#include "vmci_event.h" | |||||
#include "vmci_kernel_api.h" | |||||
#include "vmci_kernel_defs.h" | |||||
#include "vmci_resource.h" | |||||
#define LGPFX "vmci: " | |||||
#define VMCI_UTIL_NUM_RESOURCES 1 | |||||
static vmci_id ctx_update_sub_id = VMCI_INVALID_ID; | |||||
static volatile int vm_context_id = VMCI_INVALID_ID; | |||||
/* | |||||
*------------------------------------------------------------------------------ | |||||
* | |||||
* vmci_util_cid_update -- | |||||
* | |||||
* Gets called with the new context id if updated or resumed. | |||||
* | |||||
* Results: | |||||
* Context id. | |||||
* | |||||
* Side effects: | |||||
* None. | |||||
* | |||||
*------------------------------------------------------------------------------ | |||||
*/ | |||||
static void | |||||
vmci_util_cid_update(vmci_id sub_id, struct vmci_event_data *event_data, | |||||
void *client_data) | |||||
{ | |||||
struct vmci_event_payload_context *ev_payload; | |||||
ev_payload = vmci_event_data_payload(event_data); | |||||
if (sub_id != ctx_update_sub_id) { | |||||
VMCI_LOG_DEBUG(LGPFX"Invalid subscriber (ID=0x%x).\n", sub_id); | |||||
return; | |||||
} | |||||
if (event_data == NULL || ev_payload->context_id == VMCI_INVALID_ID) { | |||||
VMCI_LOG_DEBUG(LGPFX"Invalid event data.\n"); | |||||
return; | |||||
} | |||||
VMCI_LOG_INFO(LGPFX"Updating context from (ID=0x%x) to (ID=0x%x) on " | |||||
"event (type=%d).\n", atomic_load_int(&vm_context_id), | |||||
ev_payload->context_id, event_data->event); | |||||
atomic_store_int(&vm_context_id, ev_payload->context_id); | |||||
} | |||||
/* | |||||
*------------------------------------------------------------------------------ | |||||
* | |||||
* vmci_util_init -- | |||||
* | |||||
* Subscribe to context id update event. | |||||
* | |||||
* Results: | |||||
* None. | |||||
* | |||||
* Side effects: | |||||
* None. | |||||
* | |||||
*------------------------------------------------------------------------------ | |||||
*/ | |||||
void | |||||
vmci_util_init(void) | |||||
{ | |||||
/* | |||||
* We subscribe to the VMCI_EVENT_CTX_ID_UPDATE here so we can update | |||||
* the internal context id when needed. | |||||
*/ | |||||
if (vmci_event_subscribe(VMCI_EVENT_CTX_ID_UPDATE, | |||||
vmci_util_cid_update, NULL, &ctx_update_sub_id) < VMCI_SUCCESS) { | |||||
VMCI_LOG_WARNING(LGPFX"Failed to subscribe to event " | |||||
"(type=%d).\n", VMCI_EVENT_CTX_ID_UPDATE); | |||||
} | |||||
} | |||||
/* | |||||
*------------------------------------------------------------------------------ | |||||
* | |||||
* vmci_util_exit -- | |||||
* | |||||
* Cleanup | |||||
* | |||||
* Results: | |||||
* None. | |||||
* | |||||
* Side effects: | |||||
* None. | |||||
* | |||||
*------------------------------------------------------------------------------ | |||||
*/ | |||||
void | |||||
vmci_util_exit(void) | |||||
{ | |||||
if (vmci_event_unsubscribe(ctx_update_sub_id) < VMCI_SUCCESS) | |||||
VMCI_LOG_WARNING(LGPFX"Failed to unsubscribe to event " | |||||
"(type=%d) with subscriber (ID=0x%x).\n", | |||||
VMCI_EVENT_CTX_ID_UPDATE, ctx_update_sub_id); | |||||
} | |||||
/* | |||||
*------------------------------------------------------------------------------ | |||||
* | |||||
* vmci_util_check_host_capabilities -- | |||||
* | |||||
* Verify that the host supports the hypercalls we need. If it does not, try | |||||
* to find fallback hypercalls and use those instead. | |||||
* | |||||
* Results: | |||||
* true if required hypercalls (or fallback hypercalls) are supported by the | |||||
* host, false otherwise. | |||||
* | |||||
* Side effects: | |||||
* None. | |||||
* | |||||
*------------------------------------------------------------------------------ | |||||
*/ | |||||
static bool | |||||
vmci_util_check_host_capabilities(void) | |||||
{ | |||||
struct vmci_resources_query_msg *msg; | |||||
struct vmci_datagram *check_msg; | |||||
int result; | |||||
uint32_t msg_size; | |||||
msg_size = sizeof(struct vmci_resources_query_hdr) + | |||||
VMCI_UTIL_NUM_RESOURCES * sizeof(vmci_resource); | |||||
check_msg = vmci_alloc_kernel_mem(msg_size, VMCI_MEMORY_NORMAL); | |||||
if (check_msg == NULL) { | |||||
VMCI_LOG_WARNING(LGPFX"Check host: Insufficient memory.\n"); | |||||
return (false); | |||||
} | |||||
check_msg->dst = VMCI_MAKE_HANDLE(VMCI_HYPERVISOR_CONTEXT_ID, | |||||
VMCI_RESOURCES_QUERY); | |||||
check_msg->src = VMCI_ANON_SRC_HANDLE; | |||||
check_msg->payload_size = msg_size - VMCI_DG_HEADERSIZE; | |||||
msg = (struct vmci_resources_query_msg *)VMCI_DG_PAYLOAD(check_msg); | |||||
msg->num_resources = VMCI_UTIL_NUM_RESOURCES; | |||||
msg->resources[0] = VMCI_GET_CONTEXT_ID; | |||||
result = vmci_send_datagram(check_msg); | |||||
vmci_free_kernel_mem(check_msg, msg_size); | |||||
/* We need the vector. There are no fallbacks. */ | |||||
return (result == 0x1); | |||||
} | |||||
/* | |||||
*------------------------------------------------------------------------------ | |||||
* | |||||
* vmci_check_host_capabilities -- | |||||
* | |||||
* Tell host which guestcalls we support and let each API check that the | |||||
* host supports the hypercalls it needs. If a hypercall is not supported, | |||||
* the API can check for a fallback hypercall, or fail the check. | |||||
* | |||||
* Results: | |||||
* true if successful, false otherwise. | |||||
* | |||||
* Side effects: | |||||
* Fallback mechanisms may be enabled in the API and vmmon. | |||||
* | |||||
*------------------------------------------------------------------------------ | |||||
*/ | |||||
bool | |||||
vmci_check_host_capabilities(void) | |||||
{ | |||||
bool result; | |||||
result = vmci_event_check_host_capabilities(); | |||||
result &= vmci_datagram_check_host_capabilities(); | |||||
result &= vmci_util_check_host_capabilities(); | |||||
if (!result) { | |||||
/* | |||||
* If it failed, then make sure this goes to the system event | |||||
* log. | |||||
*/ | |||||
VMCI_LOG_WARNING(LGPFX"Host capability checked failed.\n"); | |||||
} else | |||||
VMCI_LOG_DEBUG(LGPFX"Host capability check passed.\n"); | |||||
return (result); | |||||
} | |||||
/* | |||||
*------------------------------------------------------------------------------ | |||||
* | |||||
* vmci_read_datagrams_from_port -- | |||||
* | |||||
* Reads datagrams from the data in port and dispatches them. We always | |||||
* start reading datagrams into only the first page of the datagram buffer. | |||||
* If the datagrams don't fit into one page, we use the maximum datagram | |||||
* buffer size for the remainder of the invocation. This is a simple | |||||
* heuristic for not penalizing small datagrams. | |||||
* | |||||
* This function assumes that it has exclusive access to the data in port | |||||
* for the duration of the call. | |||||
* | |||||
* Results: | |||||
* No result. | |||||
* | |||||
* Side effects: | |||||
* Datagram handlers may be invoked. | |||||
* | |||||
*------------------------------------------------------------------------------ | |||||
*/ | |||||
void | |||||
vmci_read_datagrams_from_port(vmci_io_handle io_handle, vmci_io_port dg_in_port, | |||||
uint8_t *dg_in_buffer, size_t dg_in_buffer_size) | |||||
{ | |||||
struct vmci_datagram *dg; | |||||
size_t current_dg_in_buffer_size; | |||||
size_t remaining_bytes; | |||||
current_dg_in_buffer_size = PAGE_SIZE; | |||||
ASSERT(dg_in_buffer_size >= PAGE_SIZE); | |||||
vmci_read_port_bytes(io_handle, dg_in_port, dg_in_buffer, | |||||
current_dg_in_buffer_size); | |||||
dg = (struct vmci_datagram *)dg_in_buffer; | |||||
remaining_bytes = current_dg_in_buffer_size; | |||||
while (dg->dst.resource != VMCI_INVALID_ID || | |||||
remaining_bytes > PAGE_SIZE) { | |||||
size_t dg_in_size; | |||||
/* | |||||
* When the input buffer spans multiple pages, a datagram can | |||||
* start on any page boundary in the buffer. | |||||
*/ | |||||
if (dg->dst.resource == VMCI_INVALID_ID) { | |||||
ASSERT(remaining_bytes > PAGE_SIZE); | |||||
dg = (struct vmci_datagram *)ROUNDUP((uintptr_t)dg + 1, | |||||
PAGE_SIZE); | |||||
ASSERT((uint8_t *)dg < dg_in_buffer + | |||||
current_dg_in_buffer_size); | |||||
remaining_bytes = (size_t)(dg_in_buffer + | |||||
current_dg_in_buffer_size - (uint8_t *)dg); | |||||
continue; | |||||
} | |||||
dg_in_size = VMCI_DG_SIZE_ALIGNED(dg); | |||||
if (dg_in_size <= dg_in_buffer_size) { | |||||
int result; | |||||
/* | |||||
* If the remaining bytes in the datagram buffer doesn't | |||||
* contain the complete datagram, we first make sure we | |||||
* have enough room for it and then we read the reminder | |||||
* of the datagram and possibly any following datagrams. | |||||
*/ | |||||
if (dg_in_size > remaining_bytes) { | |||||
if (remaining_bytes != | |||||
current_dg_in_buffer_size) { | |||||
/* | |||||
* We move the partial datagram to the | |||||
* front and read the reminder of the | |||||
* datagram and possibly following calls | |||||
* into the following bytes. | |||||
*/ | |||||
memmove(dg_in_buffer, dg_in_buffer + | |||||
current_dg_in_buffer_size - | |||||
remaining_bytes, | |||||
remaining_bytes); | |||||
dg = (struct vmci_datagram *) | |||||
dg_in_buffer; | |||||
} | |||||
if (current_dg_in_buffer_size != | |||||
dg_in_buffer_size) | |||||
current_dg_in_buffer_size = | |||||
dg_in_buffer_size; | |||||
vmci_read_port_bytes(io_handle, dg_in_port, | |||||
dg_in_buffer + remaining_bytes, | |||||
current_dg_in_buffer_size - | |||||
remaining_bytes); | |||||
} | |||||
/* | |||||
* We special case event datagrams from the | |||||
* hypervisor. | |||||
*/ | |||||
if (dg->src.context == VMCI_HYPERVISOR_CONTEXT_ID && | |||||
dg->dst.resource == VMCI_EVENT_HANDLER) | |||||
result = vmci_event_dispatch(dg); | |||||
else | |||||
result = | |||||
vmci_datagram_invoke_guest_handler(dg); | |||||
if (result < VMCI_SUCCESS) | |||||
VMCI_LOG_DEBUG(LGPFX"Datagram with resource" | |||||
" (ID=0x%x) failed (err=%d).\n", | |||||
dg->dst.resource, result); | |||||
/* On to the next datagram. */ | |||||
dg = (struct vmci_datagram *)((uint8_t *)dg + | |||||
dg_in_size); | |||||
} else { | |||||
size_t bytes_to_skip; | |||||
/* | |||||
* Datagram doesn't fit in datagram buffer of maximal | |||||
* size. We drop it. | |||||
*/ | |||||
VMCI_LOG_DEBUG(LGPFX"Failed to receive datagram " | |||||
"(size=%zu bytes).\n", dg_in_size); | |||||
bytes_to_skip = dg_in_size - remaining_bytes; | |||||
if (current_dg_in_buffer_size != dg_in_buffer_size) | |||||
current_dg_in_buffer_size = dg_in_buffer_size; | |||||
for (;;) { | |||||
vmci_read_port_bytes(io_handle, dg_in_port, | |||||
dg_in_buffer, current_dg_in_buffer_size); | |||||
if (bytes_to_skip <= | |||||
current_dg_in_buffer_size) | |||||
break; | |||||
bytes_to_skip -= current_dg_in_buffer_size; | |||||
} | |||||
dg = (struct vmci_datagram *)(dg_in_buffer + | |||||
bytes_to_skip); | |||||
} | |||||
remaining_bytes = (size_t) (dg_in_buffer + | |||||
current_dg_in_buffer_size - (uint8_t *)dg); | |||||
if (remaining_bytes < VMCI_DG_HEADERSIZE) { | |||||
/* Get the next batch of datagrams. */ | |||||
vmci_read_port_bytes(io_handle, dg_in_port, | |||||
dg_in_buffer, current_dg_in_buffer_size); | |||||
dg = (struct vmci_datagram *)dg_in_buffer; | |||||
remaining_bytes = current_dg_in_buffer_size; | |||||
} | |||||
} | |||||
} | |||||
/* | |||||
*------------------------------------------------------------------------------ | |||||
* | |||||
* vmci_get_context_id -- | |||||
* | |||||
* Returns the current context ID. Note that since this is accessed only | |||||
* from code running in the host, this always returns the host context ID. | |||||
* | |||||
* Results: | |||||
* Context ID. | |||||
* | |||||
* Side effects: | |||||
* None. | |||||
* | |||||
*------------------------------------------------------------------------------ | |||||
*/ | |||||
vmci_id | |||||
vmci_get_context_id(void) | |||||
{ | |||||
if (atomic_load_int(&vm_context_id) == VMCI_INVALID_ID) { | |||||
uint32_t result; | |||||
struct vmci_datagram get_cid_msg; | |||||
get_cid_msg.dst = VMCI_MAKE_HANDLE(VMCI_HYPERVISOR_CONTEXT_ID, | |||||
VMCI_GET_CONTEXT_ID); | |||||
get_cid_msg.src = VMCI_ANON_SRC_HANDLE; | |||||
get_cid_msg.payload_size = 0; | |||||
result = vmci_send_datagram(&get_cid_msg); | |||||
atomic_store_int(&vm_context_id, result); | |||||
} | |||||
return (atomic_load_int(&vm_context_id)); | |||||
} | |||||
/* | |||||
*------------------------------------------------------------------------------ | |||||
* | |||||
* vmci_components_init -- | |||||
* | |||||
* Initializes VMCI components and registers core hypercalls. | |||||
* | |||||
* Results: | |||||
* VMCI_SUCCESS if successful, appropriate error code otherwise. | |||||
* | |||||
* Side effects: | |||||
* None. | |||||
* | |||||
*------------------------------------------------------------------------------ | |||||
*/ | |||||
int | |||||
vmci_components_init(void) | |||||
{ | |||||
int result; | |||||
result = vmci_resource_init(); | |||||
if (result < VMCI_SUCCESS) { | |||||
VMCI_LOG_WARNING(LGPFX"Failed to initialize vmci_resource " | |||||
"(result=%d).\n", result); | |||||
goto error_exit; | |||||
} | |||||
result = vmci_event_init(); | |||||
if (result < VMCI_SUCCESS) { | |||||
VMCI_LOG_WARNING(LGPFX"Failed to initialize vmci_event " | |||||
"(result=%d).\n", result); | |||||
goto resource_exit; | |||||
} | |||||
result = vmci_doorbell_init(); | |||||
if (result < VMCI_SUCCESS) { | |||||
VMCI_LOG_WARNING(LGPFX"Failed to initialize vmci_doorbell " | |||||
"(result=%d).\n", result); | |||||
goto event_exit; | |||||
} | |||||
VMCI_LOG_DEBUG(LGPFX"components initialized.\n"); | |||||
return (VMCI_SUCCESS); | |||||
event_exit: | |||||
vmci_event_exit(); | |||||
resource_exit: | |||||
vmci_resource_exit(); | |||||
error_exit: | |||||
return (result); | |||||
} | |||||
/* | |||||
*------------------------------------------------------------------------------ | |||||
* | |||||
* vmci_components_cleanup -- | |||||
* | |||||
* Cleans up VMCI components. | |||||
* | |||||
* Results: | |||||
* None. | |||||
* | |||||
* Side effects: | |||||
* None. | |||||
* | |||||
*------------------------------------------------------------------------------ | |||||
*/ | |||||
void | |||||
vmci_components_cleanup(void) | |||||
{ | |||||
vmci_doorbell_exit(); | |||||
vmci_event_exit(); | |||||
vmci_resource_exit(); | |||||
} |