diff options
Diffstat (limited to 'hw')
-rw-r--r-- | hw/hyperv/hyperv.c | 84 |
1 files changed, 84 insertions, 0 deletions
diff --git a/hw/hyperv/hyperv.c b/hw/hyperv/hyperv.c index 7a1c1bbee4..a28e7249d8 100644 --- a/hw/hyperv/hyperv.c +++ b/hw/hyperv/hyperv.c @@ -455,6 +455,14 @@ int hyperv_sint_route_set_sint(HvSintRoute *sint_route) return event_notifier_set(&sint_route->sint_set_notifier); } +typedef struct MsgHandler { + struct rcu_head rcu; + QLIST_ENTRY(MsgHandler) link; + uint32_t conn_id; + HvMsgHandler handler; + void *data; +} MsgHandler; + typedef struct EventFlagHandler { struct rcu_head rcu; QLIST_ENTRY(EventFlagHandler) link; @@ -462,15 +470,91 @@ typedef struct EventFlagHandler { EventNotifier *notifier; } EventFlagHandler; +static QLIST_HEAD(, MsgHandler) msg_handlers; static QLIST_HEAD(, EventFlagHandler) event_flag_handlers; static QemuMutex handlers_mutex; static void __attribute__((constructor)) hv_init(void) { + QLIST_INIT(&msg_handlers); QLIST_INIT(&event_flag_handlers); qemu_mutex_init(&handlers_mutex); } +int hyperv_set_msg_handler(uint32_t conn_id, HvMsgHandler handler, void *data) +{ + int ret; + MsgHandler *mh; + + qemu_mutex_lock(&handlers_mutex); + QLIST_FOREACH(mh, &msg_handlers, link) { + if (mh->conn_id == conn_id) { + if (handler) { + ret = -EEXIST; + } else { + QLIST_REMOVE_RCU(mh, link); + g_free_rcu(mh, rcu); + ret = 0; + } + goto unlock; + } + } + + if (handler) { + mh = g_new(MsgHandler, 1); + mh->conn_id = conn_id; + mh->handler = handler; + mh->data = data; + QLIST_INSERT_HEAD_RCU(&msg_handlers, mh, link); + ret = 0; + } else { + ret = -ENOENT; + } +unlock: + qemu_mutex_unlock(&handlers_mutex); + return ret; +} + +uint16_t hyperv_hcall_post_message(uint64_t param, bool fast) +{ + uint16_t ret; + hwaddr len; + struct hyperv_post_message_input *msg; + MsgHandler *mh; + + if (fast) { + return HV_STATUS_INVALID_HYPERCALL_CODE; + } + if (param & (__alignof__(*msg) - 1)) { + return HV_STATUS_INVALID_ALIGNMENT; + } + + len = sizeof(*msg); + msg = cpu_physical_memory_map(param, &len, 0); + if (len < sizeof(*msg)) { + ret = HV_STATUS_INSUFFICIENT_MEMORY; + goto unmap; + } + if (msg->payload_size > sizeof(msg->payload)) { + ret = HV_STATUS_INVALID_HYPERCALL_INPUT; + goto unmap; + } + + ret = HV_STATUS_INVALID_CONNECTION_ID; + rcu_read_lock(); + QLIST_FOREACH_RCU(mh, &msg_handlers, link) { + if (mh->conn_id == (msg->connection_id & HV_CONNECTION_ID_MASK)) { + ret = mh->handler(msg, mh->data); + break; + } + } + rcu_read_unlock(); + +unmap: + cpu_physical_memory_unmap(msg, len, 0, 0); + return ret; +} + static int set_event_flag_handler(uint32_t conn_id, EventNotifier *notifier) { int ret; |