| /* SPDX-License-Identifier: (GPL-2.0 OR CDDL-1.0) */ |
| /* |
| * vboxguest core guest-device handling code, VBoxGuest.cpp in upstream svn. |
| * |
| * Copyright (C) 2007-2016 Oracle Corporation |
| */ |
| |
| #include <linux/device.h> |
| #include <linux/mm.h> |
| #include <linux/sched.h> |
| #include <linux/sizes.h> |
| #include <linux/slab.h> |
| #include <linux/vbox_err.h> |
| #include <linux/vbox_utils.h> |
| #include <linux/vmalloc.h> |
| #include "vboxguest_core.h" |
| #include "vboxguest_version.h" |
| |
| /* Get the pointer to the first HGCM parameter. */ |
| #define VBG_IOCTL_HGCM_CALL_PARMS(a) \ |
| ((struct vmmdev_hgcm_function_parameter *)( \ |
| (u8 *)(a) + sizeof(struct vbg_ioctl_hgcm_call))) |
| /* Get the pointer to the first HGCM parameter in a 32-bit request. */ |
| #define VBG_IOCTL_HGCM_CALL_PARMS32(a) \ |
| ((struct vmmdev_hgcm_function_parameter32 *)( \ |
| (u8 *)(a) + sizeof(struct vbg_ioctl_hgcm_call))) |
| |
| #define GUEST_MAPPINGS_TRIES 5 |
| |
| /** |
| * Reserves memory in which the VMM can relocate any guest mappings |
| * that are floating around. |
| * |
| * This operation is a little bit tricky since the VMM might not accept |
| * just any address because of address clashes between the three contexts |
| * it operates in, so we try several times. |
| * |
| * Failure to reserve the guest mappings is ignored. |
| * |
| * @gdev: The Guest extension device. |
| */ |
| static void vbg_guest_mappings_init(struct vbg_dev *gdev) |
| { |
| struct vmmdev_hypervisorinfo *req; |
| void *guest_mappings[GUEST_MAPPINGS_TRIES]; |
| struct page **pages = NULL; |
| u32 size, hypervisor_size; |
| int i, rc; |
| |
| /* Query the required space. */ |
| req = vbg_req_alloc(sizeof(*req), VMMDEVREQ_GET_HYPERVISOR_INFO); |
| if (!req) |
| return; |
| |
| req->hypervisor_start = 0; |
| req->hypervisor_size = 0; |
| rc = vbg_req_perform(gdev, req); |
| if (rc < 0) |
| goto out; |
| |
| /* |
| * The VMM will report back if there is nothing it wants to map, like |
| * for instance in VT-x and AMD-V mode. |
| */ |
| if (req->hypervisor_size == 0) |
| goto out; |
| |
| hypervisor_size = req->hypervisor_size; |
| /* Add 4M so that we can align the vmap to 4MiB as the host requires. */ |
| size = PAGE_ALIGN(req->hypervisor_size) + SZ_4M; |
| |
| pages = kmalloc_array(size >> PAGE_SHIFT, sizeof(*pages), GFP_KERNEL); |
| if (!pages) |
| goto out; |
| |
| gdev->guest_mappings_dummy_page = alloc_page(GFP_HIGHUSER); |
| if (!gdev->guest_mappings_dummy_page) |
| goto out; |
| |
| for (i = 0; i < (size >> PAGE_SHIFT); i++) |
| pages[i] = gdev->guest_mappings_dummy_page; |
| |
| /* |
| * Try several times, the VMM might not accept some addresses because |
| * of address clashes between the three contexts. |
| */ |
| for (i = 0; i < GUEST_MAPPINGS_TRIES; i++) { |
| guest_mappings[i] = vmap(pages, (size >> PAGE_SHIFT), |
| VM_MAP, PAGE_KERNEL_RO); |
| if (!guest_mappings[i]) |
| break; |
| |
| req->header.request_type = VMMDEVREQ_SET_HYPERVISOR_INFO; |
| req->header.rc = VERR_INTERNAL_ERROR; |
| req->hypervisor_size = hypervisor_size; |
| req->hypervisor_start = |
| (unsigned long)PTR_ALIGN(guest_mappings[i], SZ_4M); |
| |
| rc = vbg_req_perform(gdev, req); |
| if (rc >= 0) { |
| gdev->guest_mappings = guest_mappings[i]; |
| break; |
| } |
| } |
| |
| /* Free vmap's from failed attempts. */ |
| while (--i >= 0) |
| vunmap(guest_mappings[i]); |
| |
| /* On failure free the dummy-page backing the vmap */ |
| if (!gdev->guest_mappings) { |
| __free_page(gdev->guest_mappings_dummy_page); |
| gdev->guest_mappings_dummy_page = NULL; |
| } |
| |
| out: |
| vbg_req_free(req, sizeof(*req)); |
| kfree(pages); |
| } |
| |
| /** |
| * Undo what vbg_guest_mappings_init did. |
| * |
| * @gdev: The Guest extension device. |
| */ |
| static void vbg_guest_mappings_exit(struct vbg_dev *gdev) |
| { |
| struct vmmdev_hypervisorinfo *req; |
| int rc; |
| |
| if (!gdev->guest_mappings) |
| return; |
| |
| /* |
| * Tell the host that we're going to free the memory we reserved for |
| * it, the free it up. (Leak the memory if anything goes wrong here.) |
| */ |
| req = vbg_req_alloc(sizeof(*req), VMMDEVREQ_SET_HYPERVISOR_INFO); |
| if (!req) |
| return; |
| |
| req->hypervisor_start = 0; |
| req->hypervisor_size = 0; |
| |
| rc = vbg_req_perform(gdev, req); |
| |
| vbg_req_free(req, sizeof(*req)); |
| |
| if (rc < 0) { |
| vbg_err("%s error: %d\n", __func__, rc); |
| return; |
| } |
| |
| vunmap(gdev->guest_mappings); |
| gdev->guest_mappings = NULL; |
| |
| __free_page(gdev->guest_mappings_dummy_page); |
| gdev->guest_mappings_dummy_page = NULL; |
| } |
| |
| /** |
| * Report the guest information to the host. |
| * Return: 0 or negative errno value. |
| * @gdev: The Guest extension device. |
| */ |
| static int vbg_report_guest_info(struct vbg_dev *gdev) |
| { |
| /* |
| * Allocate and fill in the two guest info reports. |
| */ |
| struct vmmdev_guest_info *req1 = NULL; |
| struct vmmdev_guest_info2 *req2 = NULL; |
| int rc, ret = -ENOMEM; |
| |
| req1 = vbg_req_alloc(sizeof(*req1), VMMDEVREQ_REPORT_GUEST_INFO); |
| req2 = vbg_req_alloc(sizeof(*req2), VMMDEVREQ_REPORT_GUEST_INFO2); |
| if (!req1 || !req2) |
| goto out_free; |
| |
| req1->interface_version = VMMDEV_VERSION; |
| req1->os_type = VMMDEV_OSTYPE_LINUX26; |
| #if __BITS_PER_LONG == 64 |
| req1->os_type |= VMMDEV_OSTYPE_X64; |
| #endif |
| |
| req2->additions_major = VBG_VERSION_MAJOR; |
| req2->additions_minor = VBG_VERSION_MINOR; |
| req2->additions_build = VBG_VERSION_BUILD; |
| req2->additions_revision = VBG_SVN_REV; |
| /* (no features defined yet) */ |
| req2->additions_features = 0; |
| strlcpy(req2->name, VBG_VERSION_STRING, |
| sizeof(req2->name)); |
| |
| /* |
| * There are two protocols here: |
| * 1. INFO2 + INFO1. Supported by >=3.2.51. |
| * 2. INFO1 and optionally INFO2. The old protocol. |
| * |
| * We try protocol 2 first. It will fail with VERR_NOT_SUPPORTED |
| * if not supported by the VMMDev (message ordering requirement). |
| */ |
| rc = vbg_req_perform(gdev, req2); |
| if (rc >= 0) { |
| rc = vbg_req_perform(gdev, req1); |
| } else if (rc == VERR_NOT_SUPPORTED || rc == VERR_NOT_IMPLEMENTED) { |
| rc = vbg_req_perform(gdev, req1); |
| if (rc >= 0) { |
| rc = vbg_req_perform(gdev, req2); |
| if (rc == VERR_NOT_IMPLEMENTED) |
| rc = VINF_SUCCESS; |
| } |
| } |
| ret = vbg_status_code_to_errno(rc); |
| |
| out_free: |
| vbg_req_free(req2, sizeof(*req2)); |
| vbg_req_free(req1, sizeof(*req1)); |
| return ret; |
| } |
| |
| /** |
| * Report the guest driver status to the host. |
| * Return: 0 or negative errno value. |
| * @gdev: The Guest extension device. |
| * @active: Flag whether the driver is now active or not. |
| */ |
| static int vbg_report_driver_status(struct vbg_dev *gdev, bool active) |
| { |
| struct vmmdev_guest_status *req; |
| int rc; |
| |
| req = vbg_req_alloc(sizeof(*req), VMMDEVREQ_REPORT_GUEST_STATUS); |
| if (!req) |
| return -ENOMEM; |
| |
| req->facility = VBOXGUEST_FACILITY_TYPE_VBOXGUEST_DRIVER; |
| if (active) |
| req->status = VBOXGUEST_FACILITY_STATUS_ACTIVE; |
| else |
| req->status = VBOXGUEST_FACILITY_STATUS_INACTIVE; |
| req->flags = 0; |
| |
| rc = vbg_req_perform(gdev, req); |
| if (rc == VERR_NOT_IMPLEMENTED) /* Compatibility with older hosts. */ |
| rc = VINF_SUCCESS; |
| |
| vbg_req_free(req, sizeof(*req)); |
| |
| return vbg_status_code_to_errno(rc); |
| } |
| |
| /** |
| * Inflate the balloon by one chunk. The caller owns the balloon mutex. |
| * Return: 0 or negative errno value. |
| * @gdev: The Guest extension device. |
| * @chunk_idx: Index of the chunk. |
| */ |
| static int vbg_balloon_inflate(struct vbg_dev *gdev, u32 chunk_idx) |
| { |
| struct vmmdev_memballoon_change *req = gdev->mem_balloon.change_req; |
| struct page **pages; |
| int i, rc, ret; |
| |
| pages = kmalloc_array(VMMDEV_MEMORY_BALLOON_CHUNK_PAGES, |
| sizeof(*pages), |
| GFP_KERNEL | __GFP_NOWARN); |
| if (!pages) |
| return -ENOMEM; |
| |
| req->header.size = sizeof(*req); |
| req->inflate = true; |
| req->pages = VMMDEV_MEMORY_BALLOON_CHUNK_PAGES; |
| |
| for (i = 0; i < VMMDEV_MEMORY_BALLOON_CHUNK_PAGES; i++) { |
| pages[i] = alloc_page(GFP_KERNEL | __GFP_NOWARN); |
| if (!pages[i]) { |
| ret = -ENOMEM; |
| goto out_error; |
| } |
| |
| req->phys_page[i] = page_to_phys(pages[i]); |
| } |
| |
| rc = vbg_req_perform(gdev, req); |
| if (rc < 0) { |
| vbg_err("%s error, rc: %d\n", __func__, rc); |
| ret = vbg_status_code_to_errno(rc); |
| goto out_error; |
| } |
| |
| gdev->mem_balloon.pages[chunk_idx] = pages; |
| |
| return 0; |
| |
| out_error: |
| while (--i >= 0) |
| __free_page(pages[i]); |
| kfree(pages); |
| |
| return ret; |
| } |
| |
| /** |
| * Deflate the balloon by one chunk. The caller owns the balloon mutex. |
| * Return: 0 or negative errno value. |
| * @gdev: The Guest extension device. |
| * @chunk_idx: Index of the chunk. |
| */ |
| static int vbg_balloon_deflate(struct vbg_dev *gdev, u32 chunk_idx) |
| { |
| struct vmmdev_memballoon_change *req = gdev->mem_balloon.change_req; |
| struct page **pages = gdev->mem_balloon.pages[chunk_idx]; |
| int i, rc; |
| |
| req->header.size = sizeof(*req); |
| req->inflate = false; |
| req->pages = VMMDEV_MEMORY_BALLOON_CHUNK_PAGES; |
| |
| for (i = 0; i < VMMDEV_MEMORY_BALLOON_CHUNK_PAGES; i++) |
| req->phys_page[i] = page_to_phys(pages[i]); |
| |
| rc = vbg_req_perform(gdev, req); |
| if (rc < 0) { |
| vbg_err("%s error, rc: %d\n", __func__, rc); |
| return vbg_status_code_to_errno(rc); |
| } |
| |
| for (i = 0; i < VMMDEV_MEMORY_BALLOON_CHUNK_PAGES; i++) |
| __free_page(pages[i]); |
| kfree(pages); |
| gdev->mem_balloon.pages[chunk_idx] = NULL; |
| |
| return 0; |
| } |
| |
| /** |
| * Respond to VMMDEV_EVENT_BALLOON_CHANGE_REQUEST events, query the size |
| * the host wants the balloon to be and adjust accordingly. |
| */ |
| static void vbg_balloon_work(struct work_struct *work) |
| { |
| struct vbg_dev *gdev = |
| container_of(work, struct vbg_dev, mem_balloon.work); |
| struct vmmdev_memballoon_info *req = gdev->mem_balloon.get_req; |
| u32 i, chunks; |
| int rc, ret; |
| |
| /* |
| * Setting this bit means that we request the value from the host and |
| * change the guest memory balloon according to the returned value. |
| */ |
| req->event_ack = VMMDEV_EVENT_BALLOON_CHANGE_REQUEST; |
| rc = vbg_req_perform(gdev, req); |
| if (rc < 0) { |
| vbg_err("%s error, rc: %d)\n", __func__, rc); |
| return; |
| } |
| |
| /* |
| * The host always returns the same maximum amount of chunks, so |
| * we do this once. |
| */ |
| if (!gdev->mem_balloon.max_chunks) { |
| gdev->mem_balloon.pages = |
| devm_kcalloc(gdev->dev, req->phys_mem_chunks, |
| sizeof(struct page **), GFP_KERNEL); |
| if (!gdev->mem_balloon.pages) |
| return; |
| |
| gdev->mem_balloon.max_chunks = req->phys_mem_chunks; |
| } |
| |
| chunks = req->balloon_chunks; |
| if (chunks > gdev->mem_balloon.max_chunks) { |
| vbg_err("%s: illegal balloon size %u (max=%u)\n", |
| __func__, chunks, gdev->mem_balloon.max_chunks); |
| return; |
| } |
| |
| if (chunks > gdev->mem_balloon.chunks) { |
| /* inflate */ |
| for (i = gdev->mem_balloon.chunks; i < chunks; i++) { |
| ret = vbg_balloon_inflate(gdev, i); |
| if (ret < 0) |
| return; |
| |
| gdev->mem_balloon.chunks++; |
| } |
| } else { |
| /* deflate */ |
| for (i = gdev->mem_balloon.chunks; i-- > chunks;) { |
| ret = vbg_balloon_deflate(gdev, i); |
| if (ret < 0) |
| return; |
| |
| gdev->mem_balloon.chunks--; |
| } |
| } |
| } |
| |
| /** |
| * Callback for heartbeat timer. |
| */ |
| static void vbg_heartbeat_timer(struct timer_list *t) |
| { |
| struct vbg_dev *gdev = from_timer(gdev, t, heartbeat_timer); |
| |
| vbg_req_perform(gdev, gdev->guest_heartbeat_req); |
| mod_timer(&gdev->heartbeat_timer, |
| msecs_to_jiffies(gdev->heartbeat_interval_ms)); |
| } |
| |
| /** |
| * Configure the host to check guest's heartbeat |
| * and get heartbeat interval from the host. |
| * Return: 0 or negative errno value. |
| * @gdev: The Guest extension device. |
| * @enabled: Set true to enable guest heartbeat checks on host. |
| */ |
| static int vbg_heartbeat_host_config(struct vbg_dev *gdev, bool enabled) |
| { |
| struct vmmdev_heartbeat *req; |
| int rc; |
| |
| req = vbg_req_alloc(sizeof(*req), VMMDEVREQ_HEARTBEAT_CONFIGURE); |
| if (!req) |
| return -ENOMEM; |
| |
| req->enabled = enabled; |
| req->interval_ns = 0; |
| rc = vbg_req_perform(gdev, req); |
| do_div(req->interval_ns, 1000000); /* ns -> ms */ |
| gdev->heartbeat_interval_ms = req->interval_ns; |
| vbg_req_free(req, sizeof(*req)); |
| |
| return vbg_status_code_to_errno(rc); |
| } |
| |
| /** |
| * Initializes the heartbeat timer. This feature may be disabled by the host. |
| * Return: 0 or negative errno value. |
| * @gdev: The Guest extension device. |
| */ |
| static int vbg_heartbeat_init(struct vbg_dev *gdev) |
| { |
| int ret; |
| |
| /* Make sure that heartbeat checking is disabled if we fail. */ |
| ret = vbg_heartbeat_host_config(gdev, false); |
| if (ret < 0) |
| return ret; |
| |
| ret = vbg_heartbeat_host_config(gdev, true); |
| if (ret < 0) |
| return ret; |
| |
| gdev->guest_heartbeat_req = vbg_req_alloc( |
| sizeof(*gdev->guest_heartbeat_req), |
| VMMDEVREQ_GUEST_HEARTBEAT); |
| if (!gdev->guest_heartbeat_req) |
| return -ENOMEM; |
| |
| vbg_info("%s: Setting up heartbeat to trigger every %d milliseconds\n", |
| __func__, gdev->heartbeat_interval_ms); |
| mod_timer(&gdev->heartbeat_timer, 0); |
| |
| return 0; |
| } |
| |
| /** |
| * Cleanup hearbeat code, stop HB timer and disable host heartbeat checking. |
| * @gdev: The Guest extension device. |
| */ |
| static void vbg_heartbeat_exit(struct vbg_dev *gdev) |
| { |
| del_timer_sync(&gdev->heartbeat_timer); |
| vbg_heartbeat_host_config(gdev, false); |
| vbg_req_free(gdev->guest_heartbeat_req, |
| sizeof(*gdev->guest_heartbeat_req)); |
| } |
| |
| /** |
| * Applies a change to the bit usage tracker. |
| * Return: true if the mask changed, false if not. |
| * @tracker: The bit usage tracker. |
| * @changed: The bits to change. |
| * @previous: The previous value of the bits. |
| */ |
| static bool vbg_track_bit_usage(struct vbg_bit_usage_tracker *tracker, |
| u32 changed, u32 previous) |
| { |
| bool global_change = false; |
| |
| while (changed) { |
| u32 bit = ffs(changed) - 1; |
| u32 bitmask = BIT(bit); |
| |
| if (bitmask & previous) { |
| tracker->per_bit_usage[bit] -= 1; |
| if (tracker->per_bit_usage[bit] == 0) { |
| global_change = true; |
| tracker->mask &= ~bitmask; |
| } |
| } else { |
| tracker->per_bit_usage[bit] += 1; |
| if (tracker->per_bit_usage[bit] == 1) { |
| global_change = true; |
| tracker->mask |= bitmask; |
| } |
| } |
| |
| changed &= ~bitmask; |
| } |
| |
| return global_change; |
| } |
| |
| /** |
| * Init and termination worker for resetting the (host) event filter on the host |
| * Return: 0 or negative errno value. |
| * @gdev: The Guest extension device. |
| * @fixed_events: Fixed events (init time). |
| */ |
| static int vbg_reset_host_event_filter(struct vbg_dev *gdev, |
| u32 fixed_events) |
| { |
| struct vmmdev_mask *req; |
| int rc; |
| |
| req = vbg_req_alloc(sizeof(*req), VMMDEVREQ_CTL_GUEST_FILTER_MASK); |
| if (!req) |
| return -ENOMEM; |
| |
| req->not_mask = U32_MAX & ~fixed_events; |
| req->or_mask = fixed_events; |
| rc = vbg_req_perform(gdev, req); |
| if (rc < 0) |
| vbg_err("%s error, rc: %d\n", __func__, rc); |
| |
| vbg_req_free(req, sizeof(*req)); |
| return vbg_status_code_to_errno(rc); |
| } |
| |
| /** |
| * Changes the event filter mask for the given session. |
| * |
| * This is called in response to VBG_IOCTL_CHANGE_FILTER_MASK as well as to |
| * do session cleanup. Takes the session spinlock. |
| * |
| * Return: 0 or negative errno value. |
| * @gdev: The Guest extension device. |
| * @session: The session. |
| * @or_mask: The events to add. |
| * @not_mask: The events to remove. |
| * @session_termination: Set if we're called by the session cleanup code. |
| * This tweaks the error handling so we perform |
| * proper session cleanup even if the host |
| * misbehaves. |
| */ |
| static int vbg_set_session_event_filter(struct vbg_dev *gdev, |
| struct vbg_session *session, |
| u32 or_mask, u32 not_mask, |
| bool session_termination) |
| { |
| struct vmmdev_mask *req; |
| u32 changed, previous; |
| int rc, ret = 0; |
| |
| /* Allocate a request buffer before taking the spinlock */ |
| req = vbg_req_alloc(sizeof(*req), VMMDEVREQ_CTL_GUEST_FILTER_MASK); |
| if (!req) { |
| if (!session_termination) |
| return -ENOMEM; |
| /* Ignore allocation failure, we must do session cleanup. */ |
| } |
| |
| mutex_lock(&gdev->session_mutex); |
| |
| /* Apply the changes to the session mask. */ |
| previous = session->event_filter; |
| session->event_filter |= or_mask; |
| session->event_filter &= ~not_mask; |
| |
| /* If anything actually changed, update the global usage counters. */ |
| changed = previous ^ session->event_filter; |
| if (!changed) |
| goto out; |
| |
| vbg_track_bit_usage(&gdev->event_filter_tracker, changed, previous); |
| or_mask = gdev->fixed_events | gdev->event_filter_tracker.mask; |
| |
| if (gdev->event_filter_host == or_mask || !req) |
| goto out; |
| |
| gdev->event_filter_host = or_mask; |
| req->or_mask = or_mask; |
| req->not_mask = ~or_mask; |
| rc = vbg_req_perform(gdev, req); |
| if (rc < 0) { |
| ret = vbg_status_code_to_errno(rc); |
| |
| /* Failed, roll back (unless it's session termination time). */ |
| gdev->event_filter_host = U32_MAX; |
| if (session_termination) |
| goto out; |
| |
| vbg_track_bit_usage(&gdev->event_filter_tracker, changed, |
| session->event_filter); |
| session->event_filter = previous; |
| } |
| |
| out: |
| mutex_unlock(&gdev->session_mutex); |
| vbg_req_free(req, sizeof(*req)); |
| |
| return ret; |
| } |
| |
| /** |
| * Init and termination worker for set guest capabilities to zero on the host. |
| * Return: 0 or negative errno value. |
| * @gdev: The Guest extension device. |
| */ |
| static int vbg_reset_host_capabilities(struct vbg_dev *gdev) |
| { |
| struct vmmdev_mask *req; |
| int rc; |
| |
| req = vbg_req_alloc(sizeof(*req), VMMDEVREQ_SET_GUEST_CAPABILITIES); |
| if (!req) |
| return -ENOMEM; |
| |
| req->not_mask = U32_MAX; |
| req->or_mask = 0; |
| rc = vbg_req_perform(gdev, req); |
| if (rc < 0) |
| vbg_err("%s error, rc: %d\n", __func__, rc); |
| |
| vbg_req_free(req, sizeof(*req)); |
| return vbg_status_code_to_errno(rc); |
| } |
| |
| /** |
| * Sets the guest capabilities for a session. Takes the session spinlock. |
| * Return: 0 or negative errno value. |
| * @gdev: The Guest extension device. |
| * @session: The session. |
| * @or_mask: The capabilities to add. |
| * @not_mask: The capabilities to remove. |
| * @session_termination: Set if we're called by the session cleanup code. |
| * This tweaks the error handling so we perform |
| * proper session cleanup even if the host |
| * misbehaves. |
| */ |
| static int vbg_set_session_capabilities(struct vbg_dev *gdev, |
| struct vbg_session *session, |
| u32 or_mask, u32 not_mask, |
| bool session_termination) |
| { |
| struct vmmdev_mask *req; |
| u32 changed, previous; |
| int rc, ret = 0; |
| |
| /* Allocate a request buffer before taking the spinlock */ |
| req = vbg_req_alloc(sizeof(*req), VMMDEVREQ_SET_GUEST_CAPABILITIES); |
| if (!req) { |
| if (!session_termination) |
| return -ENOMEM; |
| /* Ignore allocation failure, we must do session cleanup. */ |
| } |
| |
| mutex_lock(&gdev->session_mutex); |
| |
| /* Apply the changes to the session mask. */ |
| previous = session->guest_caps; |
| session->guest_caps |= or_mask; |
| session->guest_caps &= ~not_mask; |
| |
| /* If anything actually changed, update the global usage counters. */ |
| changed = previous ^ session->guest_caps; |
| if (!changed) |
| goto out; |
| |
| vbg_track_bit_usage(&gdev->guest_caps_tracker, changed, previous); |
| or_mask = gdev->guest_caps_tracker.mask; |
| |
| if (gdev->guest_caps_host == or_mask || !req) |
| goto out; |
| |
| gdev->guest_caps_host = or_mask; |
| req->or_mask = or_mask; |
| req->not_mask = ~or_mask; |
| rc = vbg_req_perform(gdev, req); |
| if (rc < 0) { |
| ret = vbg_status_code_to_errno(rc); |
| |
| /* Failed, roll back (unless it's session termination time). */ |
| gdev->guest_caps_host = U32_MAX; |
| if (session_termination) |
| goto out; |
| |
| vbg_track_bit_usage(&gdev->guest_caps_tracker, changed, |
| session->guest_caps); |
| session->guest_caps = previous; |
| } |
| |
| out: |
| mutex_unlock(&gdev->session_mutex); |
| vbg_req_free(req, sizeof(*req)); |
| |
| return ret; |
| } |
| |
| /** |
| * vbg_query_host_version get the host feature mask and version information. |
| * Return: 0 or negative errno value. |
| * @gdev: The Guest extension device. |
| */ |
| static int vbg_query_host_version(struct vbg_dev *gdev) |
| { |
| struct vmmdev_host_version *req; |
| int rc, ret; |
| |
| req = vbg_req_alloc(sizeof(*req), VMMDEVREQ_GET_HOST_VERSION); |
| if (!req) |
| return -ENOMEM; |
| |
| rc = vbg_req_perform(gdev, req); |
| ret = vbg_status_code_to_errno(rc); |
| if (ret) { |
| vbg_err("%s error: %d\n", __func__, rc); |
| goto out; |
| } |
| |
| snprintf(gdev->host_version, sizeof(gdev->host_version), "%u.%u.%ur%u", |
| req->major, req->minor, req->build, req->revision); |
| gdev->host_features = req->features; |
| |
| vbg_info("vboxguest: host-version: %s %#x\n", gdev->host_version, |
| gdev->host_features); |
| |
| if (!(req->features & VMMDEV_HVF_HGCM_PHYS_PAGE_LIST)) { |
| vbg_err("vboxguest: Error host too old (does not support page-lists)\n"); |
| ret = -ENODEV; |
| } |
| |
| out: |
| vbg_req_free(req, sizeof(*req)); |
| return ret; |
| } |
| |
| /** |
| * Initializes the VBoxGuest device extension when the |
| * device driver is loaded. |
| * |
| * The native code locates the VMMDev on the PCI bus and retrieve |
| * the MMIO and I/O port ranges, this function will take care of |
| * mapping the MMIO memory (if present). Upon successful return |
| * the native code should set up the interrupt handler. |
| * |
| * Return: 0 or negative errno value. |
| * |
| * @gdev: The Guest extension device. |
| * @fixed_events: Events that will be enabled upon init and no client |
| * will ever be allowed to mask. |
| */ |
| int vbg_core_init(struct vbg_dev *gdev, u32 fixed_events) |
| { |
| int ret = -ENOMEM; |
| |
| gdev->fixed_events = fixed_events | VMMDEV_EVENT_HGCM; |
| gdev->event_filter_host = U32_MAX; /* forces a report */ |
| gdev->guest_caps_host = U32_MAX; /* forces a report */ |
| |
| init_waitqueue_head(&gdev->event_wq); |
| init_waitqueue_head(&gdev->hgcm_wq); |
| spin_lock_init(&gdev->event_spinlock); |
| mutex_init(&gdev->session_mutex); |
| mutex_init(&gdev->cancel_req_mutex); |
| timer_setup(&gdev->heartbeat_timer, vbg_heartbeat_timer, 0); |
| INIT_WORK(&gdev->mem_balloon.work, vbg_balloon_work); |
| |
| gdev->mem_balloon.get_req = |
| vbg_req_alloc(sizeof(*gdev->mem_balloon.get_req), |
| VMMDEVREQ_GET_MEMBALLOON_CHANGE_REQ); |
| gdev->mem_balloon.change_req = |
| vbg_req_alloc(sizeof(*gdev->mem_balloon.change_req), |
| VMMDEVREQ_CHANGE_MEMBALLOON); |
| gdev->cancel_req = |
| vbg_req_alloc(sizeof(*(gdev->cancel_req)), |
| VMMDEVREQ_HGCM_CANCEL2); |
| gdev->ack_events_req = |
| vbg_req_alloc(sizeof(*gdev->ack_events_req), |
| VMMDEVREQ_ACKNOWLEDGE_EVENTS); |
| gdev->mouse_status_req = |
| vbg_req_alloc(sizeof(*gdev->mouse_status_req), |
| VMMDEVREQ_GET_MOUSE_STATUS); |
| |
| if (!gdev->mem_balloon.get_req || !gdev->mem_balloon.change_req || |
| !gdev->cancel_req || !gdev->ack_events_req || |
| !gdev->mouse_status_req) |
| goto err_free_reqs; |
| |
| ret = vbg_query_host_version(gdev); |
| if (ret) |
| goto err_free_reqs; |
| |
| ret = vbg_report_guest_info(gdev); |
| if (ret) { |
| vbg_err("vboxguest: vbg_report_guest_info error: %d\n", ret); |
| goto err_free_reqs; |
| } |
| |
| ret = vbg_reset_host_event_filter(gdev, gdev->fixed_events); |
| if (ret) { |
| vbg_err("vboxguest: Error setting fixed event filter: %d\n", |
| ret); |
| goto err_free_reqs; |
| } |
| |
| ret = vbg_reset_host_capabilities(gdev); |
| if (ret) { |
| vbg_err("vboxguest: Error clearing guest capabilities: %d\n", |
| ret); |
| goto err_free_reqs; |
| } |
| |
| ret = vbg_core_set_mouse_status(gdev, 0); |
| if (ret) { |
| vbg_err("vboxguest: Error clearing mouse status: %d\n", ret); |
| goto err_free_reqs; |
| } |
| |
| /* These may fail without requiring the driver init to fail. */ |
| vbg_guest_mappings_init(gdev); |
| vbg_heartbeat_init(gdev); |
| |
| /* All Done! */ |
| ret = vbg_report_driver_status(gdev, true); |
| if (ret < 0) |
| vbg_err("vboxguest: Error reporting driver status: %d\n", ret); |
| |
| return 0; |
| |
| err_free_reqs: |
| vbg_req_free(gdev->mouse_status_req, |
| sizeof(*gdev->mouse_status_req)); |
| vbg_req_free(gdev->ack_events_req, |
| sizeof(*gdev->ack_events_req)); |
| vbg_req_free(gdev->cancel_req, |
| sizeof(*gdev->cancel_req)); |
| vbg_req_free(gdev->mem_balloon.change_req, |
| sizeof(*gdev->mem_balloon.change_req)); |
| vbg_req_free(gdev->mem_balloon.get_req, |
| sizeof(*gdev->mem_balloon.get_req)); |
| return ret; |
| } |
| |
| /** |
| * Call this on exit to clean-up vboxguest-core managed resources. |
| * |
| * The native code should call this before the driver is loaded, |
| * but don't call this on shutdown. |
| * @gdev: The Guest extension device. |
| */ |
| void vbg_core_exit(struct vbg_dev *gdev) |
| { |
| vbg_heartbeat_exit(gdev); |
| vbg_guest_mappings_exit(gdev); |
| |
| /* Clear the host flags (mouse status etc). */ |
| vbg_reset_host_event_filter(gdev, 0); |
| vbg_reset_host_capabilities(gdev); |
| vbg_core_set_mouse_status(gdev, 0); |
| |
| vbg_req_free(gdev->mouse_status_req, |
| sizeof(*gdev->mouse_status_req)); |
| vbg_req_free(gdev->ack_events_req, |
| sizeof(*gdev->ack_events_req)); |
| vbg_req_free(gdev->cancel_req, |
| sizeof(*gdev->cancel_req)); |
| vbg_req_free(gdev->mem_balloon.change_req, |
| sizeof(*gdev->mem_balloon.change_req)); |
| vbg_req_free(gdev->mem_balloon.get_req, |
| sizeof(*gdev->mem_balloon.get_req)); |
| } |
| |
| /** |
| * Creates a VBoxGuest user session. |
| * |
| * vboxguest_linux.c calls this when userspace opens the char-device. |
| * Return: A pointer to the new session or an ERR_PTR on error. |
| * @gdev: The Guest extension device. |
| * @user: Set if this is a session for the vboxuser device. |
| */ |
| struct vbg_session *vbg_core_open_session(struct vbg_dev *gdev, bool user) |
| { |
| struct vbg_session *session; |
| |
| session = kzalloc(sizeof(*session), GFP_KERNEL); |
| if (!session) |
| return ERR_PTR(-ENOMEM); |
| |
| session->gdev = gdev; |
| session->user_session = user; |
| |
| return session; |
| } |
| |
| /** |
| * Closes a VBoxGuest session. |
| * @session: The session to close (and free). |
| */ |
| void vbg_core_close_session(struct vbg_session *session) |
| { |
| struct vbg_dev *gdev = session->gdev; |
| int i, rc; |
| |
| vbg_set_session_capabilities(gdev, session, 0, U32_MAX, true); |
| vbg_set_session_event_filter(gdev, session, 0, U32_MAX, true); |
| |
| for (i = 0; i < ARRAY_SIZE(session->hgcm_client_ids); i++) { |
| if (!session->hgcm_client_ids[i]) |
| continue; |
| |
| vbg_hgcm_disconnect(gdev, session->hgcm_client_ids[i], &rc); |
| } |
| |
| kfree(session); |
| } |
| |
| static int vbg_ioctl_chk(struct vbg_ioctl_hdr *hdr, size_t in_size, |
| size_t out_size) |
| { |
| if (hdr->size_in != (sizeof(*hdr) + in_size) || |
| hdr->size_out != (sizeof(*hdr) + out_size)) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| static int vbg_ioctl_driver_version_info( |
| struct vbg_ioctl_driver_version_info *info) |
| { |
| const u16 vbg_maj_version = VBG_IOC_VERSION >> 16; |
| u16 min_maj_version, req_maj_version; |
| |
| if (vbg_ioctl_chk(&info->hdr, sizeof(info->u.in), sizeof(info->u.out))) |
| return -EINVAL; |
| |
| req_maj_version = info->u.in.req_version >> 16; |
| min_maj_version = info->u.in.min_version >> 16; |
| |
| if (info->u.in.min_version > info->u.in.req_version || |
| min_maj_version != req_maj_version) |
| return -EINVAL; |
| |
| if (info->u.in.min_version <= VBG_IOC_VERSION && |
| min_maj_version == vbg_maj_version) { |
| info->u.out.session_version = VBG_IOC_VERSION; |
| } else { |
| info->u.out.session_version = U32_MAX; |
| info->hdr.rc = VERR_VERSION_MISMATCH; |
| } |
| |
| info->u.out.driver_version = VBG_IOC_VERSION; |
| info->u.out.driver_revision = 0; |
| info->u.out.reserved1 = 0; |
| info->u.out.reserved2 = 0; |
| |
| return 0; |
| } |
| |
| static bool vbg_wait_event_cond(struct vbg_dev *gdev, |
| struct vbg_session *session, |
| u32 event_mask) |
| { |
| unsigned long flags; |
| bool wakeup; |
| u32 events; |
| |
| spin_lock_irqsave(&gdev->event_spinlock, flags); |
| |
| events = gdev->pending_events & event_mask; |
| wakeup = events || session->cancel_waiters; |
| |
| spin_unlock_irqrestore(&gdev->event_spinlock, flags); |
| |
| return wakeup; |
| } |
| |
| /* Must be called with the event_lock held */ |
| static u32 vbg_consume_events_locked(struct vbg_dev *gdev, |
| struct vbg_session *session, |
| u32 event_mask) |
| { |
| u32 events = gdev->pending_events & event_mask; |
| |
| gdev->pending_events &= ~events; |
| return events; |
| } |
| |
| static int vbg_ioctl_wait_for_events(struct vbg_dev *gdev, |
| struct vbg_session *session, |
| struct vbg_ioctl_wait_for_events *wait) |
| { |
| u32 timeout_ms = wait->u.in.timeout_ms; |
| u32 event_mask = wait->u.in.events; |
| unsigned long flags; |
| long timeout; |
| int ret = 0; |
| |
| if (vbg_ioctl_chk(&wait->hdr, sizeof(wait->u.in), sizeof(wait->u.out))) |
| return -EINVAL; |
| |
| if (timeout_ms == U32_MAX) |
| timeout = MAX_SCHEDULE_TIMEOUT; |
| else |
| timeout = msecs_to_jiffies(timeout_ms); |
| |
| wait->u.out.events = 0; |
| do { |
| timeout = wait_event_interruptible_timeout( |
| gdev->event_wq, |
| vbg_wait_event_cond(gdev, session, event_mask), |
| timeout); |
| |
| spin_lock_irqsave(&gdev->event_spinlock, flags); |
| |
| if (timeout < 0 || session->cancel_waiters) { |
| ret = -EINTR; |
| } else if (timeout == 0) { |
| ret = -ETIMEDOUT; |
| } else { |
| wait->u.out.events = |
| vbg_consume_events_locked(gdev, session, event_mask); |
| } |
| |
| spin_unlock_irqrestore(&gdev->event_spinlock, flags); |
| |
| /* |
| * Someone else may have consumed the event(s) first, in |
| * which case we go back to waiting. |
| */ |
| } while (ret == 0 && wait->u.out.events == 0); |
| |
| return ret; |
| } |
| |
| static int vbg_ioctl_interrupt_all_wait_events(struct vbg_dev *gdev, |
| struct vbg_session *session, |
| struct vbg_ioctl_hdr *hdr) |
| { |
| unsigned long flags; |
| |
| if (hdr->size_in != sizeof(*hdr) || hdr->size_out != sizeof(*hdr)) |
| return -EINVAL; |
| |
| spin_lock_irqsave(&gdev->event_spinlock, flags); |
| session->cancel_waiters = true; |
| spin_unlock_irqrestore(&gdev->event_spinlock, flags); |
| |
| wake_up(&gdev->event_wq); |
| |
| return 0; |
| } |
| |
| /** |
| * Checks if the VMM request is allowed in the context of the given session. |
| * Return: 0 or negative errno value. |
| * @gdev: The Guest extension device. |
| * @session: The calling session. |
| * @req: The request. |
| */ |
| static int vbg_req_allowed(struct vbg_dev *gdev, struct vbg_session *session, |
| const struct vmmdev_request_header *req) |
| { |
| const struct vmmdev_guest_status *guest_status; |
| bool trusted_apps_only; |
| |
| switch (req->request_type) { |
| /* Trusted users apps only. */ |
| case VMMDEVREQ_QUERY_CREDENTIALS: |
| case VMMDEVREQ_REPORT_CREDENTIALS_JUDGEMENT: |
| case VMMDEVREQ_REGISTER_SHARED_MODULE: |
| case VMMDEVREQ_UNREGISTER_SHARED_MODULE: |
| case VMMDEVREQ_WRITE_COREDUMP: |
| case VMMDEVREQ_GET_CPU_HOTPLUG_REQ: |
| case VMMDEVREQ_SET_CPU_HOTPLUG_STATUS: |
| case VMMDEVREQ_CHECK_SHARED_MODULES: |
| case VMMDEVREQ_GET_PAGE_SHARING_STATUS: |
| case VMMDEVREQ_DEBUG_IS_PAGE_SHARED: |
| case VMMDEVREQ_REPORT_GUEST_STATS: |
| case VMMDEVREQ_REPORT_GUEST_USER_STATE: |
| case VMMDEVREQ_GET_STATISTICS_CHANGE_REQ: |
| trusted_apps_only = true; |
| break; |
| |
| /* Anyone. */ |
| case VMMDEVREQ_GET_MOUSE_STATUS: |
| case VMMDEVREQ_SET_MOUSE_STATUS: |
| case VMMDEVREQ_SET_POINTER_SHAPE: |
| case VMMDEVREQ_GET_HOST_VERSION: |
| case VMMDEVREQ_IDLE: |
| case VMMDEVREQ_GET_HOST_TIME: |
| case VMMDEVREQ_SET_POWER_STATUS: |
| case VMMDEVREQ_ACKNOWLEDGE_EVENTS: |
| case VMMDEVREQ_CTL_GUEST_FILTER_MASK: |
| case VMMDEVREQ_REPORT_GUEST_STATUS: |
| case VMMDEVREQ_GET_DISPLAY_CHANGE_REQ: |
| case VMMDEVREQ_VIDEMODE_SUPPORTED: |
| case VMMDEVREQ_GET_HEIGHT_REDUCTION: |
| case VMMDEVREQ_GET_DISPLAY_CHANGE_REQ2: |
| case VMMDEVREQ_VIDEMODE_SUPPORTED2: |
| case VMMDEVREQ_VIDEO_ACCEL_ENABLE: |
| case VMMDEVREQ_VIDEO_ACCEL_FLUSH: |
| case VMMDEVREQ_VIDEO_SET_VISIBLE_REGION: |
| case VMMDEVREQ_GET_DISPLAY_CHANGE_REQEX: |
| case VMMDEVREQ_GET_SEAMLESS_CHANGE_REQ: |
| case VMMDEVREQ_GET_VRDPCHANGE_REQ: |
| case VMMDEVREQ_LOG_STRING: |
| case VMMDEVREQ_GET_SESSION_ID: |
| trusted_apps_only = false; |
| break; |
| |
| /* Depends on the request parameters... */ |
| case VMMDEVREQ_REPORT_GUEST_CAPABILITIES: |
| guest_status = (const struct vmmdev_guest_status *)req; |
| switch (guest_status->facility) { |
| case VBOXGUEST_FACILITY_TYPE_ALL: |
| case VBOXGUEST_FACILITY_TYPE_VBOXGUEST_DRIVER: |
| vbg_err("Denying userspace vmm report guest cap. call facility %#08x\n", |
| guest_status->facility); |
| return -EPERM; |
| case VBOXGUEST_FACILITY_TYPE_VBOX_SERVICE: |
| trusted_apps_only = true; |
| break; |
| case VBOXGUEST_FACILITY_TYPE_VBOX_TRAY_CLIENT: |
| case VBOXGUEST_FACILITY_TYPE_SEAMLESS: |
| case VBOXGUEST_FACILITY_TYPE_GRAPHICS: |
| default: |
| trusted_apps_only = false; |
| break; |
| } |
| break; |
| |
| /* Anything else is not allowed. */ |
| default: |
| vbg_err("Denying userspace vmm call type %#08x\n", |
| req->request_type); |
| return -EPERM; |
| } |
| |
| if (trusted_apps_only && session->user_session) { |
| vbg_err("Denying userspace vmm call type %#08x through vboxuser device node\n", |
| req->request_type); |
| return -EPERM; |
| } |
| |
| return 0; |
| } |
| |
| static int vbg_ioctl_vmmrequest(struct vbg_dev *gdev, |
| struct vbg_session *session, void *data) |
| { |
| struct vbg_ioctl_hdr *hdr = data; |
| int ret; |
| |
| if (hdr->size_in != hdr->size_out) |
| return -EINVAL; |
| |
| if (hdr->size_in > VMMDEV_MAX_VMMDEVREQ_SIZE) |
| return -E2BIG; |
| |
| if (hdr->type == VBG_IOCTL_HDR_TYPE_DEFAULT) |
| return -EINVAL; |
| |
| ret = vbg_req_allowed(gdev, session, data); |
| if (ret < 0) |
| return ret; |
| |
| vbg_req_perform(gdev, data); |
| WARN_ON(hdr->rc == VINF_HGCM_ASYNC_EXECUTE); |
| |
| return 0; |
| } |
| |
| static int vbg_ioctl_hgcm_connect(struct vbg_dev *gdev, |
| struct vbg_session *session, |
| struct vbg_ioctl_hgcm_connect *conn) |
| { |
| u32 client_id; |
| int i, ret; |
| |
| if (vbg_ioctl_chk(&conn->hdr, sizeof(conn->u.in), sizeof(conn->u.out))) |
| return -EINVAL; |
| |
| /* Find a free place in the sessions clients array and claim it */ |
| mutex_lock(&gdev->session_mutex); |
| for (i = 0; i < ARRAY_SIZE(session->hgcm_client_ids); i++) { |
| if (!session->hgcm_client_ids[i]) { |
| session->hgcm_client_ids[i] = U32_MAX; |
| break; |
| } |
| } |
| mutex_unlock(&gdev->session_mutex); |
| |
| if (i >= ARRAY_SIZE(session->hgcm_client_ids)) |
| return -EMFILE; |
| |
| ret = vbg_hgcm_connect(gdev, &conn->u.in.loc, &client_id, |
| &conn->hdr.rc); |
| |
| mutex_lock(&gdev->session_mutex); |
| if (ret == 0 && conn->hdr.rc >= 0) { |
| conn->u.out.client_id = client_id; |
| session->hgcm_client_ids[i] = client_id; |
| } else { |
| conn->u.out.client_id = 0; |
| session->hgcm_client_ids[i] = 0; |
| } |
| mutex_unlock(&gdev->session_mutex); |
| |
| return ret; |
| } |
| |
| static int vbg_ioctl_hgcm_disconnect(struct vbg_dev *gdev, |
| struct vbg_session *session, |
| struct vbg_ioctl_hgcm_disconnect *disconn) |
| { |
| u32 client_id; |
| int i, ret; |
| |
| if (vbg_ioctl_chk(&disconn->hdr, sizeof(disconn->u.in), 0)) |
| return -EINVAL; |
| |
| client_id = disconn->u.in.client_id; |
| if (client_id == 0 || client_id == U32_MAX) |
| return -EINVAL; |
| |
| mutex_lock(&gdev->session_mutex); |
| for (i = 0; i < ARRAY_SIZE(session->hgcm_client_ids); i++) { |
| if (session->hgcm_client_ids[i] == client_id) { |
| session->hgcm_client_ids[i] = U32_MAX; |
| break; |
| } |
| } |
| mutex_unlock(&gdev->session_mutex); |
| |
| if (i >= ARRAY_SIZE(session->hgcm_client_ids)) |
| return -EINVAL; |
| |
| ret = vbg_hgcm_disconnect(gdev, client_id, &disconn->hdr.rc); |
| |
| mutex_lock(&gdev->session_mutex); |
| if (ret == 0 && disconn->hdr.rc >= 0) |
| session->hgcm_client_ids[i] = 0; |
| else |
| session->hgcm_client_ids[i] = client_id; |
| mutex_unlock(&gdev->session_mutex); |
| |
| return ret; |
| } |
| |
| static bool vbg_param_valid(enum vmmdev_hgcm_function_parameter_type type) |
| { |
| switch (type) { |
| case VMMDEV_HGCM_PARM_TYPE_32BIT: |
| case VMMDEV_HGCM_PARM_TYPE_64BIT: |
| case VMMDEV_HGCM_PARM_TYPE_LINADDR: |
| case VMMDEV_HGCM_PARM_TYPE_LINADDR_IN: |
| case VMMDEV_HGCM_PARM_TYPE_LINADDR_OUT: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| static int vbg_ioctl_hgcm_call(struct vbg_dev *gdev, |
| struct vbg_session *session, bool f32bit, |
| struct vbg_ioctl_hgcm_call *call) |
| { |
| size_t actual_size; |
| u32 client_id; |
| int i, ret; |
| |
| if (call->hdr.size_in < sizeof(*call)) |
| return -EINVAL; |
| |
| if (call->hdr.size_in != call->hdr.size_out) |
| return -EINVAL; |
| |
| if (call->parm_count > VMMDEV_HGCM_MAX_PARMS) |
| return -E2BIG; |
| |
| client_id = call->client_id; |
| if (client_id == 0 || client_id == U32_MAX) |
| return -EINVAL; |
| |
| actual_size = sizeof(*call); |
| if (f32bit) |
| actual_size += call->parm_count * |
| sizeof(struct vmmdev_hgcm_function_parameter32); |
| else |
| actual_size += call->parm_count * |
| sizeof(struct vmmdev_hgcm_function_parameter); |
| if (call->hdr.size_in < actual_size) { |
| vbg_debug("VBG_IOCTL_HGCM_CALL: hdr.size_in %d required size is %zd\n", |
| call->hdr.size_in, actual_size); |
| return -EINVAL; |
| } |
| call->hdr.size_out = actual_size; |
| |
| /* Validate parameter types */ |
| if (f32bit) { |
| struct vmmdev_hgcm_function_parameter32 *parm = |
| VBG_IOCTL_HGCM_CALL_PARMS32(call); |
| |
| for (i = 0; i < call->parm_count; i++) |
| if (!vbg_param_valid(parm[i].type)) |
| return -EINVAL; |
| } else { |
| struct vmmdev_hgcm_function_parameter *parm = |
| VBG_IOCTL_HGCM_CALL_PARMS(call); |
| |
| for (i = 0; i < call->parm_count; i++) |
| if (!vbg_param_valid(parm[i].type)) |
| return -EINVAL; |
| } |
| |
| /* |
| * Validate the client id. |
| */ |
| mutex_lock(&gdev->session_mutex); |
| for (i = 0; i < ARRAY_SIZE(session->hgcm_client_ids); i++) |
| if (session->hgcm_client_ids[i] == client_id) |
| break; |
| mutex_unlock(&gdev->session_mutex); |
| if (i >= ARRAY_SIZE(session->hgcm_client_ids)) { |
| vbg_debug("VBG_IOCTL_HGCM_CALL: INVALID handle. u32Client=%#08x\n", |
| client_id); |
| return -EINVAL; |
| } |
| |
| if (IS_ENABLED(CONFIG_COMPAT) && f32bit) |
| ret = vbg_hgcm_call32(gdev, client_id, |
| call->function, call->timeout_ms, |
| VBG_IOCTL_HGCM_CALL_PARMS32(call), |
| call->parm_count, &call->hdr.rc); |
| else |
| ret = vbg_hgcm_call(gdev, client_id, |
| call->function, call->timeout_ms, |
| VBG_IOCTL_HGCM_CALL_PARMS(call), |
| call->parm_count, &call->hdr.rc); |
| |
| if (ret == -E2BIG) { |
| /* E2BIG needs to be reported through the hdr.rc field. */ |
| call->hdr.rc = VERR_OUT_OF_RANGE; |
| ret = 0; |
| } |
| |
| if (ret && ret != -EINTR && ret != -ETIMEDOUT) |
| vbg_err("VBG_IOCTL_HGCM_CALL error: %d\n", ret); |
| |
| return ret; |
| } |
| |
| static int vbg_ioctl_log(struct vbg_ioctl_log *log) |
| { |
| if (log->hdr.size_out != sizeof(log->hdr)) |
| return -EINVAL; |
| |
| vbg_info("%.*s", (int)(log->hdr.size_in - sizeof(log->hdr)), |
| log->u.in.msg); |
| |
| return 0; |
| } |
| |
| static int vbg_ioctl_change_filter_mask(struct vbg_dev *gdev, |
| struct vbg_session *session, |
| struct vbg_ioctl_change_filter *filter) |
| { |
| u32 or_mask, not_mask; |
| |
| if (vbg_ioctl_chk(&filter->hdr, sizeof(filter->u.in), 0)) |
| return -EINVAL; |
| |
| or_mask = filter->u.in.or_mask; |
| not_mask = filter->u.in.not_mask; |
| |
| if ((or_mask | not_mask) & ~VMMDEV_EVENT_VALID_EVENT_MASK) |
| return -EINVAL; |
| |
| return vbg_set_session_event_filter(gdev, session, or_mask, not_mask, |
| false); |
| } |
| |
| static int vbg_ioctl_change_guest_capabilities(struct vbg_dev *gdev, |
| struct vbg_session *session, struct vbg_ioctl_set_guest_caps *caps) |
| { |
| u32 or_mask, not_mask; |
| int ret; |
| |
| if (vbg_ioctl_chk(&caps->hdr, sizeof(caps->u.in), sizeof(caps->u.out))) |
| return -EINVAL; |
| |
| or_mask = caps->u.in.or_mask; |
| not_mask = caps->u.in.not_mask; |
| |
| if ((or_mask | not_mask) & ~VMMDEV_EVENT_VALID_EVENT_MASK) |
| return -EINVAL; |
| |
| ret = vbg_set_session_capabilities(gdev, session, or_mask, not_mask, |
| false); |
| if (ret) |
| return ret; |
| |
| caps->u.out.session_caps = session->guest_caps; |
| caps->u.out.global_caps = gdev->guest_caps_host; |
| |
| return 0; |
| } |
| |
| static int vbg_ioctl_check_balloon(struct vbg_dev *gdev, |
| struct vbg_ioctl_check_balloon *balloon_info) |
| { |
| if (vbg_ioctl_chk(&balloon_info->hdr, 0, sizeof(balloon_info->u.out))) |
| return -EINVAL; |
| |
| balloon_info->u.out.balloon_chunks = gdev->mem_balloon.chunks; |
| /* |
| * Under Linux we handle VMMDEV_EVENT_BALLOON_CHANGE_REQUEST |
| * events entirely in the kernel, see vbg_core_isr(). |
| */ |
| balloon_info->u.out.handle_in_r3 = false; |
| |
| return 0; |
| } |
| |
| static int vbg_ioctl_write_core_dump(struct vbg_dev *gdev, |
| struct vbg_ioctl_write_coredump *dump) |
| { |
| struct vmmdev_write_core_dump *req; |
| |
| if (vbg_ioctl_chk(&dump->hdr, sizeof(dump->u.in), 0)) |
| return -EINVAL; |
| |
| req = vbg_req_alloc(sizeof(*req), VMMDEVREQ_WRITE_COREDUMP); |
| if (!req) |
| return -ENOMEM; |
| |
| req->flags = dump->u.in.flags; |
| dump->hdr.rc = vbg_req_perform(gdev, req); |
| |
| vbg_req_free(req, sizeof(*req)); |
| return 0; |
| } |
| |
| /** |
| * Common IOCtl for user to kernel communication. |
| * Return: 0 or negative errno value. |
| * @session: The client session. |
| * @req: The requested function. |
| * @data: The i/o data buffer, minimum size sizeof(struct vbg_ioctl_hdr). |
| */ |
| int vbg_core_ioctl(struct vbg_session *session, unsigned int req, void *data) |
| { |
| unsigned int req_no_size = req & ~IOCSIZE_MASK; |
| struct vbg_dev *gdev = session->gdev; |
| struct vbg_ioctl_hdr *hdr = data; |
| bool f32bit = false; |
| |
| hdr->rc = VINF_SUCCESS; |
| if (!hdr->size_out) |
| hdr->size_out = hdr->size_in; |
| |
| /* |
| * hdr->version and hdr->size_in / hdr->size_out minimum size are |
| * already checked by vbg_misc_device_ioctl(). |
| */ |
| |
| /* For VMMDEV_REQUEST hdr->type != VBG_IOCTL_HDR_TYPE_DEFAULT */ |
| if (req_no_size == VBG_IOCTL_VMMDEV_REQUEST(0) || |
| req == VBG_IOCTL_VMMDEV_REQUEST_BIG) |
| return vbg_ioctl_vmmrequest(gdev, session, data); |
| |
| if (hdr->type != VBG_IOCTL_HDR_TYPE_DEFAULT) |
| return -EINVAL; |
| |
| /* Fixed size requests. */ |
| switch (req) { |
| case VBG_IOCTL_DRIVER_VERSION_INFO: |
| return vbg_ioctl_driver_version_info(data); |
| case VBG_IOCTL_HGCM_CONNECT: |
| return vbg_ioctl_hgcm_connect(gdev, session, data); |
| case VBG_IOCTL_HGCM_DISCONNECT: |
| return vbg_ioctl_hgcm_disconnect(gdev, session, data); |
| case VBG_IOCTL_WAIT_FOR_EVENTS: |
| return vbg_ioctl_wait_for_events(gdev, session, data); |
| case VBG_IOCTL_INTERRUPT_ALL_WAIT_FOR_EVENTS: |
| return vbg_ioctl_interrupt_all_wait_events(gdev, session, data); |
| case VBG_IOCTL_CHANGE_FILTER_MASK: |
| return vbg_ioctl_change_filter_mask(gdev, session, data); |
| case VBG_IOCTL_CHANGE_GUEST_CAPABILITIES: |
| return vbg_ioctl_change_guest_capabilities(gdev, session, data); |
| case VBG_IOCTL_CHECK_BALLOON: |
| return vbg_ioctl_check_balloon(gdev, data); |
| case VBG_IOCTL_WRITE_CORE_DUMP: |
| return vbg_ioctl_write_core_dump(gdev, data); |
| } |
| |
| /* Variable sized requests. */ |
| switch (req_no_size) { |
| #ifdef CONFIG_COMPAT |
| case VBG_IOCTL_HGCM_CALL_32(0): |
| f32bit = true; |
| /* Fall through */ |
| #endif |
| case VBG_IOCTL_HGCM_CALL(0): |
| return vbg_ioctl_hgcm_call(gdev, session, f32bit, data); |
| case VBG_IOCTL_LOG(0): |
| return vbg_ioctl_log(data); |
| } |
| |
| vbg_debug("VGDrvCommonIoCtl: Unknown req %#08x\n", req); |
| return -ENOTTY; |
| } |
| |
| /** |
| * Report guest supported mouse-features to the host. |
| * |
| * Return: 0 or negative errno value. |
| * @gdev: The Guest extension device. |
| * @features: The set of features to report to the host. |
| */ |
| int vbg_core_set_mouse_status(struct vbg_dev *gdev, u32 features) |
| { |
| struct vmmdev_mouse_status *req; |
| int rc; |
| |
| req = vbg_req_alloc(sizeof(*req), VMMDEVREQ_SET_MOUSE_STATUS); |
| if (!req) |
| return -ENOMEM; |
| |
| req->mouse_features = features; |
| req->pointer_pos_x = 0; |
| req->pointer_pos_y = 0; |
| |
| rc = vbg_req_perform(gdev, req); |
| if (rc < 0) |
| vbg_err("%s error, rc: %d\n", __func__, rc); |
| |
| vbg_req_free(req, sizeof(*req)); |
| return vbg_status_code_to_errno(rc); |
| } |
| |
| /** Core interrupt service routine. */ |
| irqreturn_t vbg_core_isr(int irq, void *dev_id) |
| { |
| struct vbg_dev *gdev = dev_id; |
| struct vmmdev_events *req = gdev->ack_events_req; |
| bool mouse_position_changed = false; |
| unsigned long flags; |
| u32 events = 0; |
| int rc; |
| |
| if (!gdev->mmio->V.V1_04.have_events) |
| return IRQ_NONE; |
| |
| /* Get and acknowlegde events. */ |
| req->header.rc = VERR_INTERNAL_ERROR; |
| req->events = 0; |
| rc = vbg_req_perform(gdev, req); |
| if (rc < 0) { |
| vbg_err("Error performing events req, rc: %d\n", rc); |
| return IRQ_NONE; |
| } |
| |
| events = req->events; |
| |
| if (events & VMMDEV_EVENT_MOUSE_POSITION_CHANGED) { |
| mouse_position_changed = true; |
| events &= ~VMMDEV_EVENT_MOUSE_POSITION_CHANGED; |
| } |
| |
| if (events & VMMDEV_EVENT_HGCM) { |
| wake_up(&gdev->hgcm_wq); |
| events &= ~VMMDEV_EVENT_HGCM; |
| } |
| |
| if (events & VMMDEV_EVENT_BALLOON_CHANGE_REQUEST) { |
| schedule_work(&gdev->mem_balloon.work); |
| events &= ~VMMDEV_EVENT_BALLOON_CHANGE_REQUEST; |
| } |
| |
| if (events) { |
| spin_lock_irqsave(&gdev->event_spinlock, flags); |
| gdev->pending_events |= events; |
| spin_unlock_irqrestore(&gdev->event_spinlock, flags); |
| |
| wake_up(&gdev->event_wq); |
| } |
| |
| if (mouse_position_changed) |
| vbg_linux_mouse_event(gdev); |
| |
| return IRQ_HANDLED; |
| } |