Quelle vmci_doorbell.c
Sprache: C
// SPDX-License-Identifier: GPL-2.0-only
/*
* VMware VMCI Driver
*
* Copyright ( C ) 2012 VMware , Inc . All rights reserved .
*/
#include <linux/vmw_vmci_defs.h>
#include <linux/vmw_vmci_api.h>
#include <linux/completion.h>
#include <linux/hash.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include "vmci_datagram.h"
#include "vmci_doorbell.h"
#include "vmci_resource.h"
#include "vmci_driver.h"
#include "vmci_route.h"
#define VMCI_DOORBELL_INDEX_BITS 6
#define VMCI_DOORBELL_INDEX_TABLE_SIZE (1 << VMCI_DOORBELL_INDEX_BITS)
#define VMCI_DOORBELL_HASH(_idx) hash_32(_idx, VMCI_DOORBELL_INDEX_BITS)
/*
* DoorbellEntry describes the a doorbell notification handle allocated by the
* host .
*/
struct dbell_entry {
struct vmci_resource resource;
struct hlist_node node;
struct work_struct work;
vmci_callback notify_cb;
void *client_data;
u32 idx;
u32 priv_flags;
bool run_delayed;
atomic_t active; /* Only used by guest personality */
};
/* The VMCI index table keeps track of currently registered doorbells. */
struct dbell_index_table {
spinlock_t lock; /* Index table lock */
struct hlist_head entries[VMCI_DOORBELL_INDEX_TABLE_SIZE];
};
static struct dbell_index_table vmci_doorbell_it = {
.lock = __SPIN_LOCK_UNLOCKED(vmci_doorbell_it.lock),
};
/*
* The max_notify_idx is one larger than the currently known bitmap index in
* use , and is used to determine how much of the bitmap needs to be scanned .
*/
static u32 max_notify_idx;
/*
* The notify_idx_count is used for determining whether there are free entries
* within the bitmap ( if notify_idx_count + 1 < max_notify_idx ) .
*/
static u32 notify_idx_count;
/*
* The last_notify_idx_reserved is used to track the last index handed out - in
* the case where multiple handles share a notification index , we hand out
* indexes round robin based on last_notify_idx_reserved .
*/
static u32 last_notify_idx_reserved;
/* This is a one entry cache used to by the index allocation. */
static u32 last_notify_idx_released = PAGE_SIZE;
/*
* Utility function that retrieves the privilege flags associated
* with a given doorbell handle . For guest endpoints , the
* privileges are determined by the context ID , but for host
* endpoints privileges are associated with the complete
* handle . Hypervisor endpoints are not yet supported .
*/
int vmci_dbell_get_priv_flags(struct vmci_handle handle, u32 *priv_flags)
{
if (priv_flags == NULL || handle.context == VMCI_INVALID_ID)
return VMCI_ERROR_INVALID_ARGS;
if (handle.context == VMCI_HOST_CONTEXT_ID) {
struct dbell_entry *entry;
struct vmci_resource *resource;
resource = vmci_resource_by_handle(handle,
VMCI_RESOURCE_TYPE_DOORBELL);
if (!resource)
return VMCI_ERROR_NOT_FOUND;
entry = container_of(resource, struct dbell_entry, resource);
*priv_flags = entry->priv_flags;
vmci_resource_put(resource);
} else if (handle.context == VMCI_HYPERVISOR_CONTEXT_ID) {
/*
* Hypervisor endpoints for notifications are not
* supported ( yet ) .
*/
return VMCI_ERROR_INVALID_ARGS;
} else {
*priv_flags = vmci_context_get_priv_flags(handle.context);
}
return VMCI_SUCCESS;
}
/*
* Find doorbell entry by bitmap index .
*/
static struct dbell_entry *dbell_index_table_find(u32 idx)
{
u32 bucket = VMCI_DOORBELL_HASH(idx);
struct dbell_entry *dbell;
hlist_for_each_entry(dbell, &vmci_doorbell_it.entries[bucket],
node) {
if (idx == dbell->idx)
return dbell;
}
return NULL;
}
/*
* Add the given entry to the index table . This willi take a reference to the
* entry ' s resource so that the entry is not deleted before it is removed from
* the * table .
*/
static void dbell_index_table_add(struct dbell_entry *entry)
{
u32 bucket;
u32 new_notify_idx;
vmci_resource_get(&entry->resource);
spin_lock_bh(&vmci_doorbell_it.lock);
/*
* Below we try to allocate an index in the notification
* bitmap with " not too much " sharing between resources . If we
* use less that the full bitmap , we either add to the end if
* there are no unused flags within the currently used area ,
* or we search for unused ones . If we use the full bitmap , we
* allocate the index round robin .
*/
if (max_notify_idx < PAGE_SIZE || notify_idx_count < PAGE_SIZE) {
if (last_notify_idx_released < max_notify_idx &&
!dbell_index_table_find(last_notify_idx_released)) {
new_notify_idx = last_notify_idx_released;
last_notify_idx_released = PAGE_SIZE;
} else {
bool reused = false ;
new_notify_idx = last_notify_idx_reserved;
if (notify_idx_count + 1 < max_notify_idx) {
do {
if (!dbell_index_table_find
(new_notify_idx)) {
reused = true ;
break ;
}
new_notify_idx = (new_notify_idx + 1 ) %
max_notify_idx;
} while (new_notify_idx !=
last_notify_idx_released);
}
if (!reused) {
new_notify_idx = max_notify_idx;
max_notify_idx++;
}
}
} else {
new_notify_idx = (last_notify_idx_reserved + 1 ) % PAGE_SIZE;
}
last_notify_idx_reserved = new_notify_idx;
notify_idx_count++;
entry->idx = new_notify_idx;
bucket = VMCI_DOORBELL_HASH(entry->idx);
hlist_add_head(&entry->node, &vmci_doorbell_it.entries[bucket]);
spin_unlock_bh(&vmci_doorbell_it.lock);
}
/*
* Remove the given entry from the index table . This will release ( ) the
* entry ' s resource .
*/
static void dbell_index_table_remove(struct dbell_entry *entry)
{
spin_lock_bh(&vmci_doorbell_it.lock);
hlist_del_init(&entry->node);
notify_idx_count--;
if (entry->idx == max_notify_idx - 1 ) {
/*
* If we delete an entry with the maximum known
* notification index , we take the opportunity to
* prune the current max . As there might be other
* unused indices immediately below , we lower the
* maximum until we hit an index in use .
*/
while (max_notify_idx > 0 &&
!dbell_index_table_find(max_notify_idx - 1 ))
max_notify_idx--;
}
last_notify_idx_released = entry->idx;
spin_unlock_bh(&vmci_doorbell_it.lock);
vmci_resource_put(&entry->resource);
}
/*
* Creates a link between the given doorbell handle and the given
* index in the bitmap in the device backend . A notification state
* is created in hypervisor .
*/
static int dbell_link(struct vmci_handle handle, u32 notify_idx)
{
struct vmci_doorbell_link_msg link_msg;
link_msg.hdr.dst = vmci_make_handle(VMCI_HYPERVISOR_CONTEXT_ID,
VMCI_DOORBELL_LINK);
link_msg.hdr.src = VMCI_ANON_SRC_HANDLE;
link_msg.hdr.payload_size = sizeof (link_msg) - VMCI_DG_HEADERSIZE;
link_msg.handle = handle;
link_msg.notify_idx = notify_idx;
return vmci_send_datagram(&link_msg.hdr);
}
/*
* Unlinks the given doorbell handle from an index in the bitmap in
* the device backend . The notification state is destroyed in hypervisor .
*/
static int dbell_unlink(struct vmci_handle handle)
{
struct vmci_doorbell_unlink_msg unlink_msg;
unlink_msg.hdr.dst = vmci_make_handle(VMCI_HYPERVISOR_CONTEXT_ID,
VMCI_DOORBELL_UNLINK);
unlink_msg.hdr.src = VMCI_ANON_SRC_HANDLE;
unlink_msg.hdr.payload_size = sizeof (unlink_msg) - VMCI_DG_HEADERSIZE;
unlink_msg.handle = handle;
return vmci_send_datagram(&unlink_msg.hdr);
}
/*
* Calls the specified callback in a delayed context .
*/
static void dbell_delayed_dispatch(struct work_struct *work)
{
struct dbell_entry *entry = container_of(work,
struct dbell_entry, work);
entry->notify_cb(entry->client_data);
vmci_resource_put(&entry->resource);
}
/*
* Dispatches a doorbell notification to the host context .
*/
int vmci_dbell_host_context_notify(u32 src_cid, struct vmci_handle handle)
{
struct dbell_entry *entry;
struct vmci_resource *resource;
if (vmci_handle_is_invalid(handle)) {
pr_devel("Notifying an invalid doorbell (handle=0x%x:0x%x)\n" ,
handle.context, handle.resource);
return VMCI_ERROR_INVALID_ARGS;
}
resource = vmci_resource_by_handle(handle,
VMCI_RESOURCE_TYPE_DOORBELL);
if (!resource) {
pr_devel("Notifying an unknown doorbell (handle=0x%x:0x%x)\n" ,
handle.context, handle.resource);
return VMCI_ERROR_NOT_FOUND;
}
entry = container_of(resource, struct dbell_entry, resource);
if (entry->run_delayed) {
if (!schedule_work(&entry->work))
vmci_resource_put(resource);
} else {
entry->notify_cb(entry->client_data);
vmci_resource_put(resource);
}
return VMCI_SUCCESS;
}
/*
* Register the notification bitmap with the host .
*/
bool vmci_dbell_register_notification_bitmap(u64 bitmap_ppn)
{
int result;
struct vmci_notify_bm_set_msg bitmap_set_msg = { };
bitmap_set_msg.hdr.dst = vmci_make_handle(VMCI_HYPERVISOR_CONTEXT_ID,
VMCI_SET_NOTIFY_BITMAP);
bitmap_set_msg.hdr.src = VMCI_ANON_SRC_HANDLE;
bitmap_set_msg.hdr.payload_size = sizeof (bitmap_set_msg) -
VMCI_DG_HEADERSIZE;
if (vmci_use_ppn64())
bitmap_set_msg.bitmap_ppn64 = bitmap_ppn;
else
bitmap_set_msg.bitmap_ppn32 = (u32) bitmap_ppn;
result = vmci_send_datagram(&bitmap_set_msg.hdr);
if (result != VMCI_SUCCESS) {
pr_devel("Failed to register (PPN=%llu) as notification bitmap (error=%d)\n" ,
bitmap_ppn, result);
return false ;
}
return true ;
}
/*
* Executes or schedules the handlers for a given notify index .
*/
static void dbell_fire_entries(u32 notify_idx)
{
u32 bucket = VMCI_DOORBELL_HASH(notify_idx);
struct dbell_entry *dbell;
spin_lock_bh(&vmci_doorbell_it.lock);
hlist_for_each_entry(dbell, &vmci_doorbell_it.entries[bucket], node) {
if (dbell->idx == notify_idx &&
atomic_read(&dbell->active) == 1 ) {
if (dbell->run_delayed) {
vmci_resource_get(&dbell->resource);
if (!schedule_work(&dbell->work))
vmci_resource_put(&dbell->resource);
} else {
dbell->notify_cb(dbell->client_data);
}
}
}
spin_unlock_bh(&vmci_doorbell_it.lock);
}
/*
* Scans the notification bitmap , collects pending notifications ,
* resets the bitmap and invokes appropriate callbacks .
*/
void vmci_dbell_scan_notification_entries(u8 *bitmap)
{
u32 idx;
for (idx = 0 ; idx < max_notify_idx; idx++) {
if (bitmap[idx] & 0 x1) {
bitmap[idx] &= ~1 ;
dbell_fire_entries(idx);
}
}
}
/*
* vmci_doorbell_create ( ) - Creates a doorbell
* @ handle : A handle used to track the resource . Can be invalid .
* @ flags : Flag that determines context of callback .
* @ priv_flags : Privileges flags .
* @ notify_cb : The callback to be ivoked when the doorbell fires .
* @ client_data : A parameter to be passed to the callback .
*
* Creates a doorbell with the given callback . If the handle is
* VMCI_INVALID_HANDLE , a free handle will be assigned , if
* possible . The callback can be run immediately ( potentially with
* locks held - the default ) or delayed ( in a kernel thread ) by
* specifying the flag VMCI_FLAG_DELAYED_CB . If delayed execution
* is selected , a given callback may not be run if the kernel is
* unable to allocate memory for the delayed execution ( highly
* unlikely ) .
*/
int vmci_doorbell_create(struct vmci_handle *handle,
u32 flags,
u32 priv_flags,
vmci_callback notify_cb, void *client_data)
{
struct dbell_entry *entry;
struct vmci_handle new_handle;
int result;
if (!handle || !notify_cb || flags & ~VMCI_FLAG_DELAYED_CB ||
priv_flags & ~VMCI_PRIVILEGE_ALL_FLAGS)
return VMCI_ERROR_INVALID_ARGS;
entry = kmalloc(sizeof (*entry), GFP_KERNEL);
if (entry == NULL) {
pr_warn("Failed allocating memory for datagram entry\n" );
return VMCI_ERROR_NO_MEM;
}
if (vmci_handle_is_invalid(*handle)) {
u32 context_id = vmci_get_context_id();
if (context_id == VMCI_INVALID_ID) {
pr_warn("Failed to get context ID\n" );
result = VMCI_ERROR_NO_RESOURCES;
goto free_mem;
}
/* Let resource code allocate a free ID for us */
new_handle = vmci_make_handle(context_id, VMCI_INVALID_ID);
} else {
bool valid_context = false ;
/*
* Validate the handle . We must do both of the checks below
* because we can be acting as both a host and a guest at the
* same time . We always allow the host context ID , since the
* host functionality is in practice always there with the
* unified driver .
*/
if (handle->context == VMCI_HOST_CONTEXT_ID ||
(vmci_guest_code_active() &&
vmci_get_context_id() == handle->context)) {
valid_context = true ;
}
if (!valid_context || handle->resource == VMCI_INVALID_ID) {
pr_devel("Invalid argument (handle=0x%x:0x%x)\n" ,
handle->context, handle->resource);
result = VMCI_ERROR_INVALID_ARGS;
goto free_mem;
}
new_handle = *handle;
}
entry->idx = 0 ;
INIT_HLIST_NODE(&entry->node);
entry->priv_flags = priv_flags;
INIT_WORK(&entry->work, dbell_delayed_dispatch);
entry->run_delayed = flags & VMCI_FLAG_DELAYED_CB;
entry->notify_cb = notify_cb;
entry->client_data = client_data;
atomic_set(&entry->active, 0 );
result = vmci_resource_add(&entry->resource,
VMCI_RESOURCE_TYPE_DOORBELL,
new_handle);
if (result != VMCI_SUCCESS) {
pr_warn("Failed to add new resource (handle=0x%x:0x%x), error: %d\n" ,
new_handle.context, new_handle.resource, result);
goto free_mem;
}
new_handle = vmci_resource_handle(&entry->resource);
if (vmci_guest_code_active()) {
dbell_index_table_add(entry);
result = dbell_link(new_handle, entry->idx);
if (VMCI_SUCCESS != result)
goto destroy_resource;
atomic_set(&entry->active, 1 );
}
*handle = new_handle;
return result;
destroy_resource:
dbell_index_table_remove(entry);
vmci_resource_remove(&entry->resource);
free_mem:
kfree(entry);
return result;
}
EXPORT_SYMBOL_GPL(vmci_doorbell_create);
/*
* vmci_doorbell_destroy ( ) - Destroy a doorbell .
* @ handle : The handle tracking the resource .
*
* Destroys a doorbell previously created with vmcii_doorbell_create . This
* operation may block waiting for a callback to finish .
*/
int vmci_doorbell_destroy(struct vmci_handle handle)
{
struct dbell_entry *entry;
struct vmci_resource *resource;
if (vmci_handle_is_invalid(handle))
return VMCI_ERROR_INVALID_ARGS;
resource = vmci_resource_by_handle(handle,
VMCI_RESOURCE_TYPE_DOORBELL);
if (!resource) {
pr_devel("Failed to destroy doorbell (handle=0x%x:0x%x)\n" ,
handle.context, handle.resource);
return VMCI_ERROR_NOT_FOUND;
}
entry = container_of(resource, struct dbell_entry, resource);
if (!hlist_unhashed(&entry->node)) {
int result;
dbell_index_table_remove(entry);
result = dbell_unlink(handle);
if (VMCI_SUCCESS != result) {
/*
* The only reason this should fail would be
* an inconsistency between guest and
* hypervisor state , where the guest believes
* it has an active registration whereas the
* hypervisor doesn ' t . One case where this may
* happen is if a doorbell is unregistered
* following a hibernation at a time where the
* doorbell state hasn ' t been restored on the
* hypervisor side yet . Since the handle has
* now been removed in the guest , we just
* print a warning and return success .
*/
pr_devel("Unlink of doorbell (handle=0x%x:0x%x) unknown by hypervisor (error=%d)\n" ,
handle.context, handle.resource, result);
}
}
/*
* Now remove the resource from the table . It might still be in use
* after this , in a callback or still on the delayed work queue .
*/
vmci_resource_put(&entry->resource);
vmci_resource_remove(&entry->resource);
kfree(entry);
return VMCI_SUCCESS;
}
EXPORT_SYMBOL_GPL(vmci_doorbell_destroy);
Messung V0.5 in Prozent C=95 H=90 G=92
¤ Dauer der Verarbeitung: 0.13 Sekunden
(vorverarbeitet am 2026-09-27)
¤
*© Formatika GbR, Deutschland
2026-10-10
Neuigkeiten
Aktuelles
Motto des Tages
Open Source Software
Jenseits des Üblichen ....
Besucherstatistik
Statistik
#Sources=1126864
#Domains=1897691