|  | // 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/list.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/nospec.h> | 
|  | #include <linux/sched.h> | 
|  | #include <linux/slab.h> | 
|  | #include <linux/rculist.h> | 
|  |  | 
|  | #include "vmci_driver.h" | 
|  | #include "vmci_event.h" | 
|  |  | 
|  | #define EVENT_MAGIC 0xEABE0000 | 
|  | #define VMCI_EVENT_MAX_ATTEMPTS 10 | 
|  |  | 
|  | struct vmci_subscription { | 
|  | u32 id; | 
|  | u32 event; | 
|  | vmci_event_cb callback; | 
|  | void *callback_data; | 
|  | struct list_head node;	/* on one of subscriber lists */ | 
|  | }; | 
|  |  | 
|  | static struct list_head subscriber_array[VMCI_EVENT_MAX]; | 
|  | static DEFINE_MUTEX(subscriber_mutex); | 
|  |  | 
|  | int __init vmci_event_init(void) | 
|  | { | 
|  | int i; | 
|  |  | 
|  | for (i = 0; i < VMCI_EVENT_MAX; i++) | 
|  | INIT_LIST_HEAD(&subscriber_array[i]); | 
|  |  | 
|  | return VMCI_SUCCESS; | 
|  | } | 
|  |  | 
|  | void vmci_event_exit(void) | 
|  | { | 
|  | int e; | 
|  |  | 
|  | /* We free all memory at exit. */ | 
|  | for (e = 0; e < VMCI_EVENT_MAX; e++) { | 
|  | struct vmci_subscription *cur, *p2; | 
|  | list_for_each_entry_safe(cur, p2, &subscriber_array[e], node) { | 
|  |  | 
|  | /* | 
|  | * We should never get here because all events | 
|  | * should have been unregistered before we try | 
|  | * to unload the driver module. | 
|  | */ | 
|  | pr_warn("Unexpected free events occurring\n"); | 
|  | list_del(&cur->node); | 
|  | kfree(cur); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Find entry. Assumes subscriber_mutex is held. | 
|  | */ | 
|  | static struct vmci_subscription *event_find(u32 sub_id) | 
|  | { | 
|  | int e; | 
|  |  | 
|  | for (e = 0; e < VMCI_EVENT_MAX; e++) { | 
|  | struct vmci_subscription *cur; | 
|  | list_for_each_entry(cur, &subscriber_array[e], node) { | 
|  | if (cur->id == sub_id) | 
|  | return cur; | 
|  | } | 
|  | } | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Actually delivers the events to the subscribers. | 
|  | * The callback function for each subscriber is invoked. | 
|  | */ | 
|  | static void event_deliver(struct vmci_event_msg *event_msg) | 
|  | { | 
|  | struct vmci_subscription *cur; | 
|  | struct list_head *subscriber_list; | 
|  | u32 sanitized_event, max_vmci_event; | 
|  |  | 
|  | rcu_read_lock(); | 
|  | max_vmci_event = ARRAY_SIZE(subscriber_array); | 
|  | sanitized_event = array_index_nospec(event_msg->event_data.event, max_vmci_event); | 
|  | subscriber_list = &subscriber_array[sanitized_event]; | 
|  | list_for_each_entry_rcu(cur, subscriber_list, node) { | 
|  | cur->callback(cur->id, &event_msg->event_data, | 
|  | cur->callback_data); | 
|  | } | 
|  | rcu_read_unlock(); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Dispatcher for the VMCI_EVENT_RECEIVE datagrams. Calls all | 
|  | * subscribers for given event. | 
|  | */ | 
|  | int vmci_event_dispatch(struct vmci_datagram *msg) | 
|  | { | 
|  | struct vmci_event_msg *event_msg = (struct vmci_event_msg *)msg; | 
|  |  | 
|  | if (msg->payload_size < sizeof(u32) || | 
|  | msg->payload_size > sizeof(struct vmci_event_data_max)) | 
|  | return VMCI_ERROR_INVALID_ARGS; | 
|  |  | 
|  | if (!VMCI_EVENT_VALID(event_msg->event_data.event)) | 
|  | return VMCI_ERROR_EVENT_UNKNOWN; | 
|  |  | 
|  | event_deliver(event_msg); | 
|  | return VMCI_SUCCESS; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * vmci_event_subscribe() - Subscribe to a given event. | 
|  | * @event:      The event to subscribe to. | 
|  | * @callback:   The callback to invoke upon the event. | 
|  | * @callback_data:      Data to pass to the callback. | 
|  | * @subscription_id:    ID used to track subscription.  Used with | 
|  | *              vmci_event_unsubscribe() | 
|  | * | 
|  | * Subscribes to the provided event. The callback specified will be | 
|  | * fired from RCU critical section and therefore must not sleep. | 
|  | */ | 
|  | int vmci_event_subscribe(u32 event, | 
|  | vmci_event_cb callback, | 
|  | void *callback_data, | 
|  | u32 *new_subscription_id) | 
|  | { | 
|  | struct vmci_subscription *sub; | 
|  | int attempts; | 
|  | int retval; | 
|  | bool have_new_id = false; | 
|  |  | 
|  | if (!new_subscription_id) { | 
|  | pr_devel("%s: Invalid subscription (NULL)\n", __func__); | 
|  | return VMCI_ERROR_INVALID_ARGS; | 
|  | } | 
|  |  | 
|  | if (!VMCI_EVENT_VALID(event) || !callback) { | 
|  | pr_devel("%s: Failed to subscribe to event (type=%d) (callback=%p) (data=%p)\n", | 
|  | __func__, event, callback, callback_data); | 
|  | return VMCI_ERROR_INVALID_ARGS; | 
|  | } | 
|  |  | 
|  | sub = kzalloc(sizeof(*sub), GFP_KERNEL); | 
|  | if (!sub) | 
|  | return VMCI_ERROR_NO_MEM; | 
|  |  | 
|  | sub->id = VMCI_EVENT_MAX; | 
|  | sub->event = event; | 
|  | sub->callback = callback; | 
|  | sub->callback_data = callback_data; | 
|  | INIT_LIST_HEAD(&sub->node); | 
|  |  | 
|  | mutex_lock(&subscriber_mutex); | 
|  |  | 
|  | /* Creation of a new event is always allowed. */ | 
|  | for (attempts = 0; attempts < VMCI_EVENT_MAX_ATTEMPTS; attempts++) { | 
|  | static u32 subscription_id; | 
|  | /* | 
|  | * We try to get an id a couple of time before | 
|  | * claiming we are out of resources. | 
|  | */ | 
|  |  | 
|  | /* Test for duplicate id. */ | 
|  | if (!event_find(++subscription_id)) { | 
|  | sub->id = subscription_id; | 
|  | have_new_id = true; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (have_new_id) { | 
|  | list_add_rcu(&sub->node, &subscriber_array[event]); | 
|  | retval = VMCI_SUCCESS; | 
|  | } else { | 
|  | retval = VMCI_ERROR_NO_RESOURCES; | 
|  | } | 
|  |  | 
|  | mutex_unlock(&subscriber_mutex); | 
|  |  | 
|  | *new_subscription_id = sub->id; | 
|  | return retval; | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(vmci_event_subscribe); | 
|  |  | 
|  | /* | 
|  | * vmci_event_unsubscribe() - unsubscribe from an event. | 
|  | * @sub_id:     A subscription ID as provided by vmci_event_subscribe() | 
|  | * | 
|  | * Unsubscribe from given event. Removes it from list and frees it. | 
|  | * Will return callback_data if requested by caller. | 
|  | */ | 
|  | int vmci_event_unsubscribe(u32 sub_id) | 
|  | { | 
|  | struct vmci_subscription *s; | 
|  |  | 
|  | mutex_lock(&subscriber_mutex); | 
|  | s = event_find(sub_id); | 
|  | if (s) | 
|  | list_del_rcu(&s->node); | 
|  | mutex_unlock(&subscriber_mutex); | 
|  |  | 
|  | if (!s) | 
|  | return VMCI_ERROR_NOT_FOUND; | 
|  |  | 
|  | kvfree_rcu_mightsleep(s); | 
|  |  | 
|  | return VMCI_SUCCESS; | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(vmci_event_unsubscribe); |