diff mbox series

[v2,3/4] hyperv: Add support to process syndbg commands

Message ID 20220216102500.692781-4-arilou@gmail.com
State New
Headers show
Series HyperV: Synthetic Debugging device | expand

Commit Message

Jon Doron Feb. 16, 2022, 10:24 a.m. UTC
SynDbg commands can come from two different flows:
1. Hypercalls, in this mode the data being sent is fully
   encapsulated network packets.
2. SynDbg specific MSRs, in this mode only the data that needs to be
   transfered is passed.

Signed-off-by: Jon Doron <arilou@gmail.com>
---
 docs/hyperv.txt               |  15 +++
 hw/hyperv/hyperv.c            | 243 ++++++++++++++++++++++++++++++++++
 include/hw/hyperv/hyperv.h    |  58 ++++++++
 target/i386/cpu.c             |   2 +
 target/i386/cpu.h             |   7 +
 target/i386/kvm/hyperv-stub.c |   6 +
 target/i386/kvm/hyperv.c      |  52 +++++++-
 target/i386/kvm/kvm.c         |  76 ++++++++++-
 8 files changed, 451 insertions(+), 8 deletions(-)

Comments

Emanuele Giuseppe Esposito Feb. 24, 2022, 4:36 p.m. UTC | #1
On 16/02/2022 11:24, Jon Doron wrote:
> SynDbg commands can come from two different flows:
> 1. Hypercalls, in this mode the data being sent is fully
>    encapsulated network packets.
> 2. SynDbg specific MSRs, in this mode only the data that needs to be
>    transfered is passed.
> 
> Signed-off-by: Jon Doron <arilou@gmail.com>
> ---
>  docs/hyperv.txt               |  15 +++
>  hw/hyperv/hyperv.c            | 243 ++++++++++++++++++++++++++++++++++
>  include/hw/hyperv/hyperv.h    |  58 ++++++++
>  target/i386/cpu.c             |   2 +
>  target/i386/cpu.h             |   7 +
>  target/i386/kvm/hyperv-stub.c |   6 +
>  target/i386/kvm/hyperv.c      |  52 +++++++-
>  target/i386/kvm/kvm.c         |  76 ++++++++++-
>  8 files changed, 451 insertions(+), 8 deletions(-)
> 
> diff --git a/docs/hyperv.txt b/docs/hyperv.txt
> index 0417c183a3..33588a0396 100644
> --- a/docs/hyperv.txt
> +++ b/docs/hyperv.txt
> @@ -225,6 +225,21 @@ default (WS2016).
>  Note: hv-version-id-* are not enlightenments and thus don't enable Hyper-V
>  identification when specified without any other enlightenments.
>  
> +3.21. hv-syndbg
> +===============
> +Enables Hyper-V synthetic debugger interface, this is a special interface used
> +by Windows Kernel debugger to send the packets through, rather than sending
> +them via serial/network .
> +When enabled, this enlightenment provides additional communication facilities
> +to the guest: SynDbg messages.
> +This new communication is used by Windows Kernel debugger rather than sending
> +packets via serial/network, adding significant performance boost over the other
> +comm channels.
> +This enlightenment requires a VMBus device (-device vmbus-bridge,irq=15)
> +and the follow enlightenments to work:
> +hv-relaxed,hv_time,hv-vapic,hv-vpindex,hv-synic,hv-runtime,hv-stimer
> +
> +
>  4. Supplementary features
>  =========================
>  
> diff --git a/hw/hyperv/hyperv.c b/hw/hyperv/hyperv.c
> index aaba6b4901..86d295395e 100644
> --- a/hw/hyperv/hyperv.c
> +++ b/hw/hyperv/hyperv.c
> @@ -704,3 +704,246 @@ uint16_t hyperv_hcall_signal_event(uint64_t param, bool fast)
>      }
>      return HV_STATUS_INVALID_CONNECTION_ID;
>  }
> +
> +static HvSynDbgHandler hv_syndbg_handler;
> +static void *hv_syndbg_context;
> +
> +void hyperv_set_syndbg_handler(HvSynDbgHandler handler, void *context)
> +{
> +    assert(!hv_syndbg_handler);
> +    hv_syndbg_handler = handler;
> +    hv_syndbg_context = context;
> +}
> +
> +uint16_t hyperv_hcall_reset_dbg_session(uint64_t outgpa)
> +{
> +    uint16_t ret;
> +    HvSynDbgMsg msg;
> +    struct hyperv_reset_debug_session_output *reset_dbg_session = NULL;
> +    hwaddr len;
> +
> +    if (!hv_syndbg_handler) {
> +        ret = HV_STATUS_INVALID_HYPERCALL_CODE;
> +        goto cleanup;
> +    }
> +
> +    len = sizeof(*reset_dbg_session);
> +    reset_dbg_session = cpu_physical_memory_map(outgpa, &len, 1);
> +    if (!reset_dbg_session || len < sizeof(*reset_dbg_session)) {
> +        ret = HV_STATUS_INSUFFICIENT_MEMORY;
> +        goto cleanup;
> +    }
> +
> +    msg.type = HV_SYNDBG_MSG_CONNECTION_INFO;
> +    ret = hv_syndbg_handler(hv_syndbg_context, &msg);
> +    if (ret) {
> +        goto cleanup;
> +    }
> +
> +    reset_dbg_session->host_ip = msg.u.connection_info.host_ip;
> +    reset_dbg_session->host_port = msg.u.connection_info.host_port;
> +    /* The following fields are only used as validation for KDVM */
> +    memset(&reset_dbg_session->host_mac, 0,
> +           sizeof(reset_dbg_session->host_mac));
> +    reset_dbg_session->target_ip = msg.u.connection_info.host_ip;
> +    reset_dbg_session->target_port = msg.u.connection_info.host_port;
> +    memset(&reset_dbg_session->target_mac, 0,
> +           sizeof(reset_dbg_session->target_mac));
> +cleanup:
> +    if (reset_dbg_session) {
> +        cpu_physical_memory_unmap(reset_dbg_session,
> +                                  sizeof(*reset_dbg_session), 1, len);
> +    }
> +
> +    return ret;
> +}
> +
> +uint16_t hyperv_hcall_retreive_dbg_data(uint64_t ingpa, uint64_t outgpa,
> +                                        bool fast)
> +{
> +    uint16_t ret;
> +    struct hyperv_retrieve_debug_data_input *debug_data_in = NULL;
> +    struct hyperv_retrieve_debug_data_output *debug_data_out = NULL;
> +    hwaddr in_len, out_len;
> +    HvSynDbgMsg msg;
> +
> +    if (fast || !hv_syndbg_handler) {
> +        ret = HV_STATUS_INVALID_HYPERCALL_CODE;
> +        goto cleanup;
> +    }
> +
> +    in_len = sizeof(*debug_data_in);
> +    debug_data_in = cpu_physical_memory_map(ingpa, &in_len, 0);
> +    if (!debug_data_in || in_len < sizeof(*debug_data_in)) {
> +        ret = HV_STATUS_INSUFFICIENT_MEMORY;
> +        goto cleanup;
> +    }
> +
> +    out_len = sizeof(*debug_data_out);
> +    debug_data_out = cpu_physical_memory_map(outgpa, &out_len, 1);
> +    if (!debug_data_out || out_len < sizeof(*debug_data_out)) {
> +        ret = HV_STATUS_INSUFFICIENT_MEMORY;
> +        goto cleanup;
> +    }
> +
> +    msg.type = HV_SYNDBG_MSG_RECV;
> +    msg.u.recv.buf_gpa = outgpa + sizeof(*debug_data_out);
> +    msg.u.recv.count = TARGET_PAGE_SIZE - sizeof(*debug_data_out);
> +    msg.u.recv.options = debug_data_in->options;
> +    msg.u.recv.timeout = debug_data_in->timeout;
> +    msg.u.recv.is_raw = true;
> +    ret = hv_syndbg_handler(hv_syndbg_context, &msg);
> +    if (ret == HV_STATUS_NO_DATA) {
> +        debug_data_out->retrieved_count = 0;
> +        debug_data_out->remaining_count = debug_data_in->count;
> +        goto cleanup;
> +    } else if (ret != HV_STATUS_SUCCESS) {
> +        goto cleanup;
> +    }
> +
> +    debug_data_out->retrieved_count = msg.u.recv.retrieved_count;
> +    debug_data_out->remaining_count =
> +        debug_data_in->count - msg.u.recv.retrieved_count;
> +cleanup:
> +    if (debug_data_out) {
> +        cpu_physical_memory_unmap(debug_data_out, sizeof(*debug_data_out), 1,
> +                                  out_len);
> +    }
> +
> +    if (debug_data_in) {
> +        cpu_physical_memory_unmap(debug_data_in, sizeof(*debug_data_in), 0,
> +                                  in_len);
> +    }
> +
> +    return ret;
> +}
> +
> +uint16_t hyperv_hcall_post_dbg_data(uint64_t ingpa, uint64_t outgpa, bool fast)
> +{
> +    uint16_t ret;
> +    struct hyperv_post_debug_data_input *post_data_in = NULL;
> +    struct hyperv_post_debug_data_output *post_data_out = NULL;
> +    hwaddr in_len, out_len;
> +    HvSynDbgMsg msg;
> +
> +    if (fast || !hv_syndbg_handler) {
> +        ret = HV_STATUS_INVALID_HYPERCALL_CODE;
> +        goto cleanup;
> +    }
> +
> +    in_len = sizeof(*post_data_in);
> +    post_data_in = cpu_physical_memory_map(ingpa, &in_len, 0);
> +    if (!post_data_in || in_len < sizeof(*post_data_in)) {
> +        ret = HV_STATUS_INSUFFICIENT_MEMORY;
> +        goto cleanup;
> +    }
> +
> +    if (post_data_in->count > TARGET_PAGE_SIZE - sizeof(*post_data_in)) {
> +        ret = HV_STATUS_INVALID_PARAMETER;
> +        goto cleanup;
> +    }
> +
> +    out_len = sizeof(*post_data_out);
> +    post_data_out = cpu_physical_memory_map(outgpa, &out_len, 1);
> +    if (!post_data_out || out_len < sizeof(*post_data_out)) {
> +        ret = HV_STATUS_INSUFFICIENT_MEMORY;
> +        goto cleanup;
> +    }
> +
> +    msg.type = HV_SYNDBG_MSG_SEND;
> +    msg.u.send.buf_gpa = ingpa + sizeof(*post_data_in);
> +    msg.u.send.count = post_data_in->count;
> +    msg.u.send.is_raw = true;
> +    ret = hv_syndbg_handler(hv_syndbg_context, &msg);
> +    if (ret != HV_STATUS_SUCCESS) {
> +        goto cleanup;
> +    }
> +
> +    post_data_out->pending_count = msg.u.send.pending_count;
> +    ret = post_data_out->pending_count ? HV_STATUS_INSUFFICIENT_BUFFERS :
> +                                         HV_STATUS_SUCCESS;
> +cleanup:
> +    if (post_data_out) {
> +        cpu_physical_memory_unmap(post_data_out,
> +                                  sizeof(*post_data_out), 1, out_len);
> +    }
> +
> +    if (post_data_in) {
> +        cpu_physical_memory_unmap(post_data_in,
> +                                  sizeof(*post_data_in), 0, in_len);
> +    }
> +
> +    return ret;
> +}
> +
> +uint32_t hyperv_syndbg_send(uint64_t ingpa, uint32_t count)
> +{
> +    HvSynDbgMsg msg;
> +
> +    if (!hv_syndbg_handler) {
> +        return HV_SYNDBG_STATUS_INVALID;
> +    }
> +
> +    msg.type = HV_SYNDBG_MSG_SEND;
> +    msg.u.send.buf_gpa = ingpa;
> +    msg.u.send.count = count;
> +    msg.u.send.is_raw = false;
> +    if (hv_syndbg_handler(hv_syndbg_context, &msg)) {
> +        return HV_SYNDBG_STATUS_INVALID;
> +    }
> +
> +    return HV_SYNDBG_STATUS_SEND_SUCCESS;
> +}
> +
> +uint32_t hyperv_syndbg_recv(uint64_t ingpa, uint32_t count)
> +{
> +    uint16_t ret;
> +    HvSynDbgMsg msg;
> +
> +    if (!hv_syndbg_handler) {
> +        return HV_SYNDBG_STATUS_INVALID;
> +    }
> +
> +    msg.type = HV_SYNDBG_MSG_RECV;
> +    msg.u.recv.buf_gpa = ingpa;
> +    msg.u.recv.count = count;
> +    msg.u.recv.options = 0;
> +    msg.u.recv.timeout = 0;
> +    msg.u.recv.is_raw = false;
> +    ret = hv_syndbg_handler(hv_syndbg_context, &msg);
> +    if (ret != HV_STATUS_SUCCESS) {
> +        return 0;
> +    }
> +
> +    return HV_SYNDBG_STATUS_SET_SIZE(HV_SYNDBG_STATUS_RECV_SUCCESS,
> +                                     msg.u.recv.retrieved_count);
> +}
> +
> +void hyperv_syndbg_set_pending_page(uint64_t ingpa)
> +{
> +    HvSynDbgMsg msg;
> +
> +    if (!hv_syndbg_handler) {
> +        return;
> +    }
> +
> +    msg.type = HV_SYNDBG_MSG_SET_PENDING_PAGE;
> +    msg.u.pending_page.buf_gpa = ingpa;
> +    hv_syndbg_handler(hv_syndbg_context, &msg);
> +}
> +
> +uint64_t hyperv_syndbg_query_options(void)
> +{
> +    HvSynDbgMsg msg;
> +
> +    if (!hv_syndbg_handler) {
> +        return 0;
> +    }
> +
> +    msg.type = HV_SYNDBG_MSG_QUERY_OPTIONS;
> +    if (hv_syndbg_handler(hv_syndbg_context, &msg) != HV_STATUS_SUCCESS) {
> +        return 0;
> +    }
> +
> +    return msg.u.query_options.options;
> +}
> diff --git a/include/hw/hyperv/hyperv.h b/include/hw/hyperv/hyperv.h
> index a63ee0003c..015c3524b1 100644
> --- a/include/hw/hyperv/hyperv.h
> +++ b/include/hw/hyperv/hyperv.h
> @@ -81,4 +81,62 @@ void hyperv_synic_update(CPUState *cs, bool enable,
>                           hwaddr msg_page_addr, hwaddr event_page_addr);
>  bool hyperv_is_synic_enabled(void);
>  
> +/*
> + * Process HVCALL_RESET_DEBUG_SESSION hypercall.
> + */
> +uint16_t hyperv_hcall_reset_dbg_session(uint64_t outgpa);
> +/*
> + * Process HVCALL_RETREIVE_DEBUG_DATA hypercall.
> + */
> +uint16_t hyperv_hcall_retreive_dbg_data(uint64_t ingpa, uint64_t outgpa,
> +                                        bool fast);
> +/*
> + * Process HVCALL_POST_DEBUG_DATA hypercall.
> + */
> +uint16_t hyperv_hcall_post_dbg_data(uint64_t ingpa, uint64_t outgpa, bool fast);
> +
> +uint32_t hyperv_syndbg_send(uint64_t ingpa, uint32_t count);
> +uint32_t hyperv_syndbg_recv(uint64_t ingpa, uint32_t count);
> +void hyperv_syndbg_set_pending_page(uint64_t ingpa);
> +uint64_t hyperv_syndbg_query_options(void);
> +
> +typedef enum HvSynthDbgMsgType {
> +    HV_SYNDBG_MSG_CONNECTION_INFO,
> +    HV_SYNDBG_MSG_SEND,
> +    HV_SYNDBG_MSG_RECV,
> +    HV_SYNDBG_MSG_SET_PENDING_PAGE,
> +    HV_SYNDBG_MSG_QUERY_OPTIONS
> +} HvDbgSynthMsgType;
> +
> +typedef struct HvSynDbgMsg {
> +    HvDbgSynthMsgType type;
> +    union {
> +        struct {
> +            uint32_t host_ip;
> +            uint16_t host_port;
> +        } connection_info;
> +        struct {
> +            uint64_t buf_gpa;
> +            uint32_t count;
> +            uint32_t pending_count;
> +            bool is_raw;
> +        } send;
> +        struct {
> +            uint64_t buf_gpa;
> +            uint32_t count;
> +            uint32_t options;
> +            uint64_t timeout;
> +            uint32_t retrieved_count;
> +            bool is_raw;
> +        } recv;
> +        struct {
> +            uint64_t buf_gpa;
> +        } pending_page;
> +        struct {
> +            uint64_t options;
> +        } query_options;
> +    } u;
> +} HvSynDbgMsg;
> +typedef uint16_t (*HvSynDbgHandler)(void *context, HvSynDbgMsg *msg);
> +void hyperv_set_syndbg_handler(HvSynDbgHandler handler, void *context);
>  #endif
> diff --git a/target/i386/cpu.c b/target/i386/cpu.c
> index aa9e636800..9529a6389a 100644
> --- a/target/i386/cpu.c
> +++ b/target/i386/cpu.c
> @@ -6841,6 +6841,8 @@ static Property x86_cpu_properties[] = {
>                        HYPERV_FEAT_AVIC, 0),
>      DEFINE_PROP_ON_OFF_AUTO("hv-no-nonarch-coresharing", X86CPU,
>                              hyperv_no_nonarch_cs, ON_OFF_AUTO_OFF),
> +    DEFINE_PROP_BIT64("hv-syndbg", X86CPU, hyperv_features,
> +                      HYPERV_FEAT_SYNDBG, 0),
>      DEFINE_PROP_BOOL("hv-passthrough", X86CPU, hyperv_passthrough, false),
>      DEFINE_PROP_BOOL("hv-enforce-cpuid", X86CPU, hyperv_enforce_cpuid, false),
>  
> diff --git a/target/i386/cpu.h b/target/i386/cpu.h
> index 9911d7c871..56e0317924 100644
> --- a/target/i386/cpu.h
> +++ b/target/i386/cpu.h
> @@ -1060,6 +1060,7 @@ typedef uint64_t FeatureWordArray[FEATURE_WORDS];
>  #define HYPERV_FEAT_IPI                 13
>  #define HYPERV_FEAT_STIMER_DIRECT       14
>  #define HYPERV_FEAT_AVIC                15
> +#define HYPERV_FEAT_SYNDBG              16
>  
>  #ifndef HYPERV_SPINLOCK_NEVER_NOTIFY
>  #define HYPERV_SPINLOCK_NEVER_NOTIFY             0xFFFFFFFF
> @@ -1560,6 +1561,12 @@ typedef struct CPUX86State {
>      uint64_t msr_hv_hypercall;
>      uint64_t msr_hv_guest_os_id;
>      uint64_t msr_hv_tsc;
> +    uint64_t msr_hv_syndbg_control;
> +    uint64_t msr_hv_syndbg_status;
> +    uint64_t msr_hv_syndbg_send_page;
> +    uint64_t msr_hv_syndbg_recv_page;
> +    uint64_t msr_hv_syndbg_pending_page;
> +    uint64_t msr_hv_syndbg_options;
>  
>      /* Per-VCPU HV MSRs */
>      uint64_t msr_hv_vapic;
> diff --git a/target/i386/kvm/hyperv-stub.c b/target/i386/kvm/hyperv-stub.c
> index 0028527e79..778ed782e6 100644
> --- a/target/i386/kvm/hyperv-stub.c
> +++ b/target/i386/kvm/hyperv-stub.c
> @@ -27,6 +27,12 @@ int kvm_hv_handle_exit(X86CPU *cpu, struct kvm_hyperv_exit *exit)
>          return 0;
>      case KVM_EXIT_HYPERV_HCALL:
>          exit->u.hcall.result = HV_STATUS_INVALID_HYPERCALL_CODE;
> +        return 0;
> +    case KVM_EXIT_HYPERV_SYNDBG:
> +        if (!hyperv_feat_enabled(cpu, HYPERV_FEAT_SYNDBG)) {
> +            return -1;
> +        }
> +
>          return 0;
>      default:
>          return -1;
> diff --git a/target/i386/kvm/hyperv.c b/target/i386/kvm/hyperv.c
> index 26efc1e0e6..9026ef3a81 100644
> --- a/target/i386/kvm/hyperv.c
> +++ b/target/i386/kvm/hyperv.c
> @@ -81,20 +81,66 @@ int kvm_hv_handle_exit(X86CPU *cpu, struct kvm_hyperv_exit *exit)
>      case KVM_EXIT_HYPERV_HCALL: {
>          uint16_t code = exit->u.hcall.input & 0xffff;
>          bool fast = exit->u.hcall.input & HV_HYPERCALL_FAST;
> -        uint64_t param = exit->u.hcall.params[0];
> +        uint64_t in_param = exit->u.hcall.params[0];
> +        uint64_t out_param = exit->u.hcall.params[1];
>  
>          switch (code) {
>          case HV_POST_MESSAGE:
> -            exit->u.hcall.result = hyperv_hcall_post_message(param, fast);
> +            exit->u.hcall.result = hyperv_hcall_post_message(in_param, fast);
>              break;
>          case HV_SIGNAL_EVENT:
> -            exit->u.hcall.result = hyperv_hcall_signal_event(param, fast);
> +            exit->u.hcall.result = hyperv_hcall_signal_event(in_param, fast);
> +            break;
> +        case HV_POST_DEBUG_DATA:
> +            exit->u.hcall.result =
> +                hyperv_hcall_post_dbg_data(in_param, out_param, fast);
> +            break;
> +        case HV_RETRIEVE_DEBUG_DATA:
> +            exit->u.hcall.result =
> +                hyperv_hcall_retreive_dbg_data(in_param, out_param, fast);
> +            break;
> +        case HV_RESET_DEBUG_SESSION:
> +            exit->u.hcall.result =
> +                hyperv_hcall_reset_dbg_session(out_param);
>              break;
>          default:
>              exit->u.hcall.result = HV_STATUS_INVALID_HYPERCALL_CODE;
>          }
>          return 0;
>      }
> +
> +    case KVM_EXIT_HYPERV_SYNDBG:
> +        if (!hyperv_feat_enabled(cpu, HYPERV_FEAT_SYNDBG)) {
> +            return -1;
> +        }
> +
> +        switch (exit->u.syndbg.msr) {
> +        case HV_X64_MSR_SYNDBG_CONTROL: {
> +            uint64_t control = exit->u.syndbg.control;
> +            env->msr_hv_syndbg_control = control;
> +            env->msr_hv_syndbg_send_page = exit->u.syndbg.send_page;
> +            env->msr_hv_syndbg_recv_page = exit->u.syndbg.recv_page;
> +            exit->u.syndbg.status = HV_STATUS_SUCCESS;
> +            if (control & HV_SYNDBG_CONTROL_SEND) {
> +                exit->u.syndbg.status =
> +                    hyperv_syndbg_send(env->msr_hv_syndbg_send_page,
> +                            HV_SYNDBG_CONTROL_SEND_SIZE(control));
> +            } else if (control & HV_SYNDBG_CONTROL_RECV) {
> +                exit->u.syndbg.status =
> +                    hyperv_syndbg_recv(env->msr_hv_syndbg_recv_page,
> +                            TARGET_PAGE_SIZE);
> +            }
> +            break;
> +        }
> +        case HV_X64_MSR_SYNDBG_PENDING_BUFFER:
> +            env->msr_hv_syndbg_pending_page = exit->u.syndbg.pending_page;
> +            hyperv_syndbg_set_pending_page(env->msr_hv_syndbg_pending_page);
> +            break;
> +        default:
> +            return -1;
> +        }
> +
> +        return 0;
>      default:
>          return -1;
>      }
> diff --git a/target/i386/kvm/kvm.c b/target/i386/kvm/kvm.c
> index 2c8feb4a6f..1c6f3b53af 100644
> --- a/target/i386/kvm/kvm.c
> +++ b/target/i386/kvm/kvm.c
> @@ -102,6 +102,7 @@ static bool has_msr_hv_synic;
>  static bool has_msr_hv_stimer;
>  static bool has_msr_hv_frequencies;
>  static bool has_msr_hv_reenlightenment;
> +static bool has_msr_hv_syndbg_options;
>  static bool has_msr_xss;
>  static bool has_msr_umwait;
>  static bool has_msr_spec_ctrl;
> @@ -932,6 +933,14 @@ static struct {
>               .bits = HV_DEPRECATING_AEOI_RECOMMENDED}
>          }
>      },
> +    [HYPERV_FEAT_SYNDBG] = {
> +        .desc = "Enable synthetic kernel debugger channel (hv-syndbg)",
> +        .flags = {
> +            {.func = HV_CPUID_FEATURES, .reg = R_EDX,
> +             .bits = HV_FEATURE_DEBUG_MSRS_AVAILABLE}
> +        },
> +        .dependencies = BIT(HYPERV_FEAT_SYNIC) | BIT(HYPERV_FEAT_RELAXED)
> +    },
>  };
>  
>  static struct kvm_cpuid2 *try_get_hv_cpuid(CPUState *cs, int max,
> @@ -972,8 +981,8 @@ static struct kvm_cpuid2 *try_get_hv_cpuid(CPUState *cs, int max,
>  static struct kvm_cpuid2 *get_supported_hv_cpuid(CPUState *cs)
>  {
>      struct kvm_cpuid2 *cpuid;
> -    /* 0x40000000..0x40000005, 0x4000000A, 0x40000080..0x40000080 leaves */
> -    int max = 10;
> +    /* 0x40000000..0x40000005, 0x4000000A, 0x40000080..0x40000082 leaves */
> +    int max = 11;
>      int i;
>      bool do_sys_ioctl;
>  
> @@ -1086,6 +1095,12 @@ static struct kvm_cpuid2 *get_supported_hv_cpuid_legacy(CPUState *cs)
>          entry_feat->eax |= HV_SYNTIMERS_AVAILABLE;
>      }
>  
> +    if (has_msr_hv_syndbg_options) {
> +        entry_feat->edx |= HV_GUEST_DEBUGGING_AVAILABLE;
> +        entry_feat->edx |= HV_FEATURE_DEBUG_MSRS_AVAILABLE;
> +        entry_feat->ebx |= HV_PARTITION_DEBUGGING_ALLOWED;
> +    }
> +
>      if (kvm_check_extension(cs->kvm_state,
>                              KVM_CAP_HYPERV_TLBFLUSH) > 0) {
>          entry_recomm->eax |= HV_REMOTE_TLB_FLUSH_RECOMMENDED;
> @@ -1337,12 +1352,22 @@ static int hyperv_fill_cpuids(CPUState *cs,
>  {
>      X86CPU *cpu = X86_CPU(cs);
>      struct kvm_cpuid_entry2 *c;
> -    uint32_t cpuid_i = 0;
> +    uint32_t signature[3];
> +    uint32_t cpuid_i = 0, max_cpuid_leaf = 0;
> +
> +    max_cpuid_leaf = HV_CPUID_IMPLEMENT_LIMITS;
> +    if (hyperv_feat_enabled(cpu, HYPERV_FEAT_EVMCS)) {
> +        max_cpuid_leaf = MAX(max_cpuid_leaf, HV_CPUID_NESTED_FEATURES);
> +    }
> +
> +    if (hyperv_feat_enabled(cpu, HYPERV_FEAT_SYNDBG)) {
> +        max_cpuid_leaf =
> +            MAX(max_cpuid_leaf, HV_CPUID_SYNDBG_PLATFORM_CAPABILITIES);
> +    }
>  
>      c = &cpuid_ent[cpuid_i++];
>      c->function = HV_CPUID_VENDOR_AND_MAX_FUNCTIONS;
> -    c->eax = hyperv_feat_enabled(cpu, HYPERV_FEAT_EVMCS) ?
> -        HV_CPUID_NESTED_FEATURES : HV_CPUID_IMPLEMENT_LIMITS;
> +    c->eax = max_cpuid_leaf;
>      c->ebx = cpu->hyperv_vendor_id[0];
>      c->ecx = cpu->hyperv_vendor_id[1];
>      c->edx = cpu->hyperv_vendor_id[2];
> @@ -1421,6 +1446,33 @@ static int hyperv_fill_cpuids(CPUState *cs,
>          c->eax = cpu->hyperv_nested[0];
>      }
>  
> +    if (hyperv_feat_enabled(cpu, HYPERV_FEAT_SYNDBG)) {
> +        c = &cpuid_ent[cpuid_i++];
> +        c->function = HV_CPUID_SYNDBG_VENDOR_AND_MAX_FUNCTIONS;
> +        c->eax = hyperv_feat_enabled(cpu, HYPERV_FEAT_EVMCS) ?
> +            HV_CPUID_NESTED_FEATURES : HV_CPUID_IMPLEMENT_LIMITS;
> +        memcpy(signature, "Microsoft VS", 12);
> +        c->eax = 0;
> +        c->ebx = signature[0];
> +        c->ecx = signature[1];
> +        c->edx = signature[2];
> +
> +        c = &cpuid_ent[cpuid_i++];
> +        c->function = HV_CPUID_SYNDBG_INTERFACE;
> +        memcpy(signature, "VS#1\0\0\0\0\0\0\0\0", 12);
> +        c->eax = signature[0];
> +        c->ebx = 0;
> +        c->ecx = 0;
> +        c->edx = 0;
> +
> +        c = &cpuid_ent[cpuid_i++];
> +        c->function = HV_CPUID_SYNDBG_PLATFORM_CAPABILITIES;
> +        c->eax = HV_SYNDBG_CAP_ALLOW_KERNEL_DEBUGGING;
> +        c->ebx = 0;
> +        c->ecx = 0;
> +        c->edx = 0;
> +    }
> +
>      return cpuid_i;
>  }
>  
> @@ -2215,6 +2267,9 @@ static int kvm_get_supported_msrs(KVMState *s)
>              case HV_X64_MSR_REENLIGHTENMENT_CONTROL:
>                  has_msr_hv_reenlightenment = true;
>                  break;
> +            case HV_X64_MSR_SYNDBG_OPTIONS:
> +                has_msr_hv_syndbg_options = true;
> +                break;
>              case MSR_IA32_SPEC_CTRL:
>                  has_msr_spec_ctrl = true;
>                  break;
> @@ -3132,6 +3187,11 @@ static int kvm_put_msrs(X86CPU *cpu, int level)
>                  kvm_msr_entry_add(cpu, HV_X64_MSR_TSC_EMULATION_STATUS,
>                                    env->msr_hv_tsc_emulation_status);
>              }
> +            if (hyperv_feat_enabled(cpu, HYPERV_FEAT_SYNDBG) &&
> +                has_msr_hv_syndbg_options) {
> +                kvm_msr_entry_add(cpu, HV_X64_MSR_SYNDBG_OPTIONS,
> +                                  hyperv_syndbg_query_options());
> +            }
>          }
>          if (hyperv_feat_enabled(cpu, HYPERV_FEAT_VAPIC)) {
>              kvm_msr_entry_add(cpu, HV_X64_MSR_APIC_ASSIST_PAGE,
> @@ -3565,6 +3625,9 @@ static int kvm_get_msrs(X86CPU *cpu)
>          kvm_msr_entry_add(cpu, HV_X64_MSR_TSC_EMULATION_CONTROL, 0);
>          kvm_msr_entry_add(cpu, HV_X64_MSR_TSC_EMULATION_STATUS, 0);
>      }
> +    if (has_msr_hv_syndbg_options) {
> +        kvm_msr_entry_add(cpu, HV_X64_MSR_SYNDBG_OPTIONS, 0);
> +    }
>      if (has_msr_hv_crash) {
>          int j;
>  
> @@ -3851,6 +3914,9 @@ static int kvm_get_msrs(X86CPU *cpu)
>          case HV_X64_MSR_TSC_EMULATION_STATUS:
>              env->msr_hv_tsc_emulation_status = msrs[i].data;
>              break;
> +        case HV_X64_MSR_SYNDBG_OPTIONS:
> +            env->msr_hv_syndbg_options = msrs[i].data;
> +            break;
>          case MSR_MTRRdefType:
>              env->mtrr_deftype = msrs[i].data;
>              break;
> 

Reviewed-by: Emanuele Giuseppe Esposito <eesposit@redhat.com>
diff mbox series

Patch

diff --git a/docs/hyperv.txt b/docs/hyperv.txt
index 0417c183a3..33588a0396 100644
--- a/docs/hyperv.txt
+++ b/docs/hyperv.txt
@@ -225,6 +225,21 @@  default (WS2016).
 Note: hv-version-id-* are not enlightenments and thus don't enable Hyper-V
 identification when specified without any other enlightenments.
 
+3.21. hv-syndbg
+===============
+Enables Hyper-V synthetic debugger interface, this is a special interface used
+by Windows Kernel debugger to send the packets through, rather than sending
+them via serial/network .
+When enabled, this enlightenment provides additional communication facilities
+to the guest: SynDbg messages.
+This new communication is used by Windows Kernel debugger rather than sending
+packets via serial/network, adding significant performance boost over the other
+comm channels.
+This enlightenment requires a VMBus device (-device vmbus-bridge,irq=15)
+and the follow enlightenments to work:
+hv-relaxed,hv_time,hv-vapic,hv-vpindex,hv-synic,hv-runtime,hv-stimer
+
+
 4. Supplementary features
 =========================
 
diff --git a/hw/hyperv/hyperv.c b/hw/hyperv/hyperv.c
index aaba6b4901..86d295395e 100644
--- a/hw/hyperv/hyperv.c
+++ b/hw/hyperv/hyperv.c
@@ -704,3 +704,246 @@  uint16_t hyperv_hcall_signal_event(uint64_t param, bool fast)
     }
     return HV_STATUS_INVALID_CONNECTION_ID;
 }
+
+static HvSynDbgHandler hv_syndbg_handler;
+static void *hv_syndbg_context;
+
+void hyperv_set_syndbg_handler(HvSynDbgHandler handler, void *context)
+{
+    assert(!hv_syndbg_handler);
+    hv_syndbg_handler = handler;
+    hv_syndbg_context = context;
+}
+
+uint16_t hyperv_hcall_reset_dbg_session(uint64_t outgpa)
+{
+    uint16_t ret;
+    HvSynDbgMsg msg;
+    struct hyperv_reset_debug_session_output *reset_dbg_session = NULL;
+    hwaddr len;
+
+    if (!hv_syndbg_handler) {
+        ret = HV_STATUS_INVALID_HYPERCALL_CODE;
+        goto cleanup;
+    }
+
+    len = sizeof(*reset_dbg_session);
+    reset_dbg_session = cpu_physical_memory_map(outgpa, &len, 1);
+    if (!reset_dbg_session || len < sizeof(*reset_dbg_session)) {
+        ret = HV_STATUS_INSUFFICIENT_MEMORY;
+        goto cleanup;
+    }
+
+    msg.type = HV_SYNDBG_MSG_CONNECTION_INFO;
+    ret = hv_syndbg_handler(hv_syndbg_context, &msg);
+    if (ret) {
+        goto cleanup;
+    }
+
+    reset_dbg_session->host_ip = msg.u.connection_info.host_ip;
+    reset_dbg_session->host_port = msg.u.connection_info.host_port;
+    /* The following fields are only used as validation for KDVM */
+    memset(&reset_dbg_session->host_mac, 0,
+           sizeof(reset_dbg_session->host_mac));
+    reset_dbg_session->target_ip = msg.u.connection_info.host_ip;
+    reset_dbg_session->target_port = msg.u.connection_info.host_port;
+    memset(&reset_dbg_session->target_mac, 0,
+           sizeof(reset_dbg_session->target_mac));
+cleanup:
+    if (reset_dbg_session) {
+        cpu_physical_memory_unmap(reset_dbg_session,
+                                  sizeof(*reset_dbg_session), 1, len);
+    }
+
+    return ret;
+}
+
+uint16_t hyperv_hcall_retreive_dbg_data(uint64_t ingpa, uint64_t outgpa,
+                                        bool fast)
+{
+    uint16_t ret;
+    struct hyperv_retrieve_debug_data_input *debug_data_in = NULL;
+    struct hyperv_retrieve_debug_data_output *debug_data_out = NULL;
+    hwaddr in_len, out_len;
+    HvSynDbgMsg msg;
+
+    if (fast || !hv_syndbg_handler) {
+        ret = HV_STATUS_INVALID_HYPERCALL_CODE;
+        goto cleanup;
+    }
+
+    in_len = sizeof(*debug_data_in);
+    debug_data_in = cpu_physical_memory_map(ingpa, &in_len, 0);
+    if (!debug_data_in || in_len < sizeof(*debug_data_in)) {
+        ret = HV_STATUS_INSUFFICIENT_MEMORY;
+        goto cleanup;
+    }
+
+    out_len = sizeof(*debug_data_out);
+    debug_data_out = cpu_physical_memory_map(outgpa, &out_len, 1);
+    if (!debug_data_out || out_len < sizeof(*debug_data_out)) {
+        ret = HV_STATUS_INSUFFICIENT_MEMORY;
+        goto cleanup;
+    }
+
+    msg.type = HV_SYNDBG_MSG_RECV;
+    msg.u.recv.buf_gpa = outgpa + sizeof(*debug_data_out);
+    msg.u.recv.count = TARGET_PAGE_SIZE - sizeof(*debug_data_out);
+    msg.u.recv.options = debug_data_in->options;
+    msg.u.recv.timeout = debug_data_in->timeout;
+    msg.u.recv.is_raw = true;
+    ret = hv_syndbg_handler(hv_syndbg_context, &msg);
+    if (ret == HV_STATUS_NO_DATA) {
+        debug_data_out->retrieved_count = 0;
+        debug_data_out->remaining_count = debug_data_in->count;
+        goto cleanup;
+    } else if (ret != HV_STATUS_SUCCESS) {
+        goto cleanup;
+    }
+
+    debug_data_out->retrieved_count = msg.u.recv.retrieved_count;
+    debug_data_out->remaining_count =
+        debug_data_in->count - msg.u.recv.retrieved_count;
+cleanup:
+    if (debug_data_out) {
+        cpu_physical_memory_unmap(debug_data_out, sizeof(*debug_data_out), 1,
+                                  out_len);
+    }
+
+    if (debug_data_in) {
+        cpu_physical_memory_unmap(debug_data_in, sizeof(*debug_data_in), 0,
+                                  in_len);
+    }
+
+    return ret;
+}
+
+uint16_t hyperv_hcall_post_dbg_data(uint64_t ingpa, uint64_t outgpa, bool fast)
+{
+    uint16_t ret;
+    struct hyperv_post_debug_data_input *post_data_in = NULL;
+    struct hyperv_post_debug_data_output *post_data_out = NULL;
+    hwaddr in_len, out_len;
+    HvSynDbgMsg msg;
+
+    if (fast || !hv_syndbg_handler) {
+        ret = HV_STATUS_INVALID_HYPERCALL_CODE;
+        goto cleanup;
+    }
+
+    in_len = sizeof(*post_data_in);
+    post_data_in = cpu_physical_memory_map(ingpa, &in_len, 0);
+    if (!post_data_in || in_len < sizeof(*post_data_in)) {
+        ret = HV_STATUS_INSUFFICIENT_MEMORY;
+        goto cleanup;
+    }
+
+    if (post_data_in->count > TARGET_PAGE_SIZE - sizeof(*post_data_in)) {
+        ret = HV_STATUS_INVALID_PARAMETER;
+        goto cleanup;
+    }
+
+    out_len = sizeof(*post_data_out);
+    post_data_out = cpu_physical_memory_map(outgpa, &out_len, 1);
+    if (!post_data_out || out_len < sizeof(*post_data_out)) {
+        ret = HV_STATUS_INSUFFICIENT_MEMORY;
+        goto cleanup;
+    }
+
+    msg.type = HV_SYNDBG_MSG_SEND;
+    msg.u.send.buf_gpa = ingpa + sizeof(*post_data_in);
+    msg.u.send.count = post_data_in->count;
+    msg.u.send.is_raw = true;
+    ret = hv_syndbg_handler(hv_syndbg_context, &msg);
+    if (ret != HV_STATUS_SUCCESS) {
+        goto cleanup;
+    }
+
+    post_data_out->pending_count = msg.u.send.pending_count;
+    ret = post_data_out->pending_count ? HV_STATUS_INSUFFICIENT_BUFFERS :
+                                         HV_STATUS_SUCCESS;
+cleanup:
+    if (post_data_out) {
+        cpu_physical_memory_unmap(post_data_out,
+                                  sizeof(*post_data_out), 1, out_len);
+    }
+
+    if (post_data_in) {
+        cpu_physical_memory_unmap(post_data_in,
+                                  sizeof(*post_data_in), 0, in_len);
+    }
+
+    return ret;
+}
+
+uint32_t hyperv_syndbg_send(uint64_t ingpa, uint32_t count)
+{
+    HvSynDbgMsg msg;
+
+    if (!hv_syndbg_handler) {
+        return HV_SYNDBG_STATUS_INVALID;
+    }
+
+    msg.type = HV_SYNDBG_MSG_SEND;
+    msg.u.send.buf_gpa = ingpa;
+    msg.u.send.count = count;
+    msg.u.send.is_raw = false;
+    if (hv_syndbg_handler(hv_syndbg_context, &msg)) {
+        return HV_SYNDBG_STATUS_INVALID;
+    }
+
+    return HV_SYNDBG_STATUS_SEND_SUCCESS;
+}
+
+uint32_t hyperv_syndbg_recv(uint64_t ingpa, uint32_t count)
+{
+    uint16_t ret;
+    HvSynDbgMsg msg;
+
+    if (!hv_syndbg_handler) {
+        return HV_SYNDBG_STATUS_INVALID;
+    }
+
+    msg.type = HV_SYNDBG_MSG_RECV;
+    msg.u.recv.buf_gpa = ingpa;
+    msg.u.recv.count = count;
+    msg.u.recv.options = 0;
+    msg.u.recv.timeout = 0;
+    msg.u.recv.is_raw = false;
+    ret = hv_syndbg_handler(hv_syndbg_context, &msg);
+    if (ret != HV_STATUS_SUCCESS) {
+        return 0;
+    }
+
+    return HV_SYNDBG_STATUS_SET_SIZE(HV_SYNDBG_STATUS_RECV_SUCCESS,
+                                     msg.u.recv.retrieved_count);
+}
+
+void hyperv_syndbg_set_pending_page(uint64_t ingpa)
+{
+    HvSynDbgMsg msg;
+
+    if (!hv_syndbg_handler) {
+        return;
+    }
+
+    msg.type = HV_SYNDBG_MSG_SET_PENDING_PAGE;
+    msg.u.pending_page.buf_gpa = ingpa;
+    hv_syndbg_handler(hv_syndbg_context, &msg);
+}
+
+uint64_t hyperv_syndbg_query_options(void)
+{
+    HvSynDbgMsg msg;
+
+    if (!hv_syndbg_handler) {
+        return 0;
+    }
+
+    msg.type = HV_SYNDBG_MSG_QUERY_OPTIONS;
+    if (hv_syndbg_handler(hv_syndbg_context, &msg) != HV_STATUS_SUCCESS) {
+        return 0;
+    }
+
+    return msg.u.query_options.options;
+}
diff --git a/include/hw/hyperv/hyperv.h b/include/hw/hyperv/hyperv.h
index a63ee0003c..015c3524b1 100644
--- a/include/hw/hyperv/hyperv.h
+++ b/include/hw/hyperv/hyperv.h
@@ -81,4 +81,62 @@  void hyperv_synic_update(CPUState *cs, bool enable,
                          hwaddr msg_page_addr, hwaddr event_page_addr);
 bool hyperv_is_synic_enabled(void);
 
+/*
+ * Process HVCALL_RESET_DEBUG_SESSION hypercall.
+ */
+uint16_t hyperv_hcall_reset_dbg_session(uint64_t outgpa);
+/*
+ * Process HVCALL_RETREIVE_DEBUG_DATA hypercall.
+ */
+uint16_t hyperv_hcall_retreive_dbg_data(uint64_t ingpa, uint64_t outgpa,
+                                        bool fast);
+/*
+ * Process HVCALL_POST_DEBUG_DATA hypercall.
+ */
+uint16_t hyperv_hcall_post_dbg_data(uint64_t ingpa, uint64_t outgpa, bool fast);
+
+uint32_t hyperv_syndbg_send(uint64_t ingpa, uint32_t count);
+uint32_t hyperv_syndbg_recv(uint64_t ingpa, uint32_t count);
+void hyperv_syndbg_set_pending_page(uint64_t ingpa);
+uint64_t hyperv_syndbg_query_options(void);
+
+typedef enum HvSynthDbgMsgType {
+    HV_SYNDBG_MSG_CONNECTION_INFO,
+    HV_SYNDBG_MSG_SEND,
+    HV_SYNDBG_MSG_RECV,
+    HV_SYNDBG_MSG_SET_PENDING_PAGE,
+    HV_SYNDBG_MSG_QUERY_OPTIONS
+} HvDbgSynthMsgType;
+
+typedef struct HvSynDbgMsg {
+    HvDbgSynthMsgType type;
+    union {
+        struct {
+            uint32_t host_ip;
+            uint16_t host_port;
+        } connection_info;
+        struct {
+            uint64_t buf_gpa;
+            uint32_t count;
+            uint32_t pending_count;
+            bool is_raw;
+        } send;
+        struct {
+            uint64_t buf_gpa;
+            uint32_t count;
+            uint32_t options;
+            uint64_t timeout;
+            uint32_t retrieved_count;
+            bool is_raw;
+        } recv;
+        struct {
+            uint64_t buf_gpa;
+        } pending_page;
+        struct {
+            uint64_t options;
+        } query_options;
+    } u;
+} HvSynDbgMsg;
+typedef uint16_t (*HvSynDbgHandler)(void *context, HvSynDbgMsg *msg);
+void hyperv_set_syndbg_handler(HvSynDbgHandler handler, void *context);
 #endif
diff --git a/target/i386/cpu.c b/target/i386/cpu.c
index aa9e636800..9529a6389a 100644
--- a/target/i386/cpu.c
+++ b/target/i386/cpu.c
@@ -6841,6 +6841,8 @@  static Property x86_cpu_properties[] = {
                       HYPERV_FEAT_AVIC, 0),
     DEFINE_PROP_ON_OFF_AUTO("hv-no-nonarch-coresharing", X86CPU,
                             hyperv_no_nonarch_cs, ON_OFF_AUTO_OFF),
+    DEFINE_PROP_BIT64("hv-syndbg", X86CPU, hyperv_features,
+                      HYPERV_FEAT_SYNDBG, 0),
     DEFINE_PROP_BOOL("hv-passthrough", X86CPU, hyperv_passthrough, false),
     DEFINE_PROP_BOOL("hv-enforce-cpuid", X86CPU, hyperv_enforce_cpuid, false),
 
diff --git a/target/i386/cpu.h b/target/i386/cpu.h
index 9911d7c871..56e0317924 100644
--- a/target/i386/cpu.h
+++ b/target/i386/cpu.h
@@ -1060,6 +1060,7 @@  typedef uint64_t FeatureWordArray[FEATURE_WORDS];
 #define HYPERV_FEAT_IPI                 13
 #define HYPERV_FEAT_STIMER_DIRECT       14
 #define HYPERV_FEAT_AVIC                15
+#define HYPERV_FEAT_SYNDBG              16
 
 #ifndef HYPERV_SPINLOCK_NEVER_NOTIFY
 #define HYPERV_SPINLOCK_NEVER_NOTIFY             0xFFFFFFFF
@@ -1560,6 +1561,12 @@  typedef struct CPUX86State {
     uint64_t msr_hv_hypercall;
     uint64_t msr_hv_guest_os_id;
     uint64_t msr_hv_tsc;
+    uint64_t msr_hv_syndbg_control;
+    uint64_t msr_hv_syndbg_status;
+    uint64_t msr_hv_syndbg_send_page;
+    uint64_t msr_hv_syndbg_recv_page;
+    uint64_t msr_hv_syndbg_pending_page;
+    uint64_t msr_hv_syndbg_options;
 
     /* Per-VCPU HV MSRs */
     uint64_t msr_hv_vapic;
diff --git a/target/i386/kvm/hyperv-stub.c b/target/i386/kvm/hyperv-stub.c
index 0028527e79..778ed782e6 100644
--- a/target/i386/kvm/hyperv-stub.c
+++ b/target/i386/kvm/hyperv-stub.c
@@ -27,6 +27,12 @@  int kvm_hv_handle_exit(X86CPU *cpu, struct kvm_hyperv_exit *exit)
         return 0;
     case KVM_EXIT_HYPERV_HCALL:
         exit->u.hcall.result = HV_STATUS_INVALID_HYPERCALL_CODE;
+        return 0;
+    case KVM_EXIT_HYPERV_SYNDBG:
+        if (!hyperv_feat_enabled(cpu, HYPERV_FEAT_SYNDBG)) {
+            return -1;
+        }
+
         return 0;
     default:
         return -1;
diff --git a/target/i386/kvm/hyperv.c b/target/i386/kvm/hyperv.c
index 26efc1e0e6..9026ef3a81 100644
--- a/target/i386/kvm/hyperv.c
+++ b/target/i386/kvm/hyperv.c
@@ -81,20 +81,66 @@  int kvm_hv_handle_exit(X86CPU *cpu, struct kvm_hyperv_exit *exit)
     case KVM_EXIT_HYPERV_HCALL: {
         uint16_t code = exit->u.hcall.input & 0xffff;
         bool fast = exit->u.hcall.input & HV_HYPERCALL_FAST;
-        uint64_t param = exit->u.hcall.params[0];
+        uint64_t in_param = exit->u.hcall.params[0];
+        uint64_t out_param = exit->u.hcall.params[1];
 
         switch (code) {
         case HV_POST_MESSAGE:
-            exit->u.hcall.result = hyperv_hcall_post_message(param, fast);
+            exit->u.hcall.result = hyperv_hcall_post_message(in_param, fast);
             break;
         case HV_SIGNAL_EVENT:
-            exit->u.hcall.result = hyperv_hcall_signal_event(param, fast);
+            exit->u.hcall.result = hyperv_hcall_signal_event(in_param, fast);
+            break;
+        case HV_POST_DEBUG_DATA:
+            exit->u.hcall.result =
+                hyperv_hcall_post_dbg_data(in_param, out_param, fast);
+            break;
+        case HV_RETRIEVE_DEBUG_DATA:
+            exit->u.hcall.result =
+                hyperv_hcall_retreive_dbg_data(in_param, out_param, fast);
+            break;
+        case HV_RESET_DEBUG_SESSION:
+            exit->u.hcall.result =
+                hyperv_hcall_reset_dbg_session(out_param);
             break;
         default:
             exit->u.hcall.result = HV_STATUS_INVALID_HYPERCALL_CODE;
         }
         return 0;
     }
+
+    case KVM_EXIT_HYPERV_SYNDBG:
+        if (!hyperv_feat_enabled(cpu, HYPERV_FEAT_SYNDBG)) {
+            return -1;
+        }
+
+        switch (exit->u.syndbg.msr) {
+        case HV_X64_MSR_SYNDBG_CONTROL: {
+            uint64_t control = exit->u.syndbg.control;
+            env->msr_hv_syndbg_control = control;
+            env->msr_hv_syndbg_send_page = exit->u.syndbg.send_page;
+            env->msr_hv_syndbg_recv_page = exit->u.syndbg.recv_page;
+            exit->u.syndbg.status = HV_STATUS_SUCCESS;
+            if (control & HV_SYNDBG_CONTROL_SEND) {
+                exit->u.syndbg.status =
+                    hyperv_syndbg_send(env->msr_hv_syndbg_send_page,
+                            HV_SYNDBG_CONTROL_SEND_SIZE(control));
+            } else if (control & HV_SYNDBG_CONTROL_RECV) {
+                exit->u.syndbg.status =
+                    hyperv_syndbg_recv(env->msr_hv_syndbg_recv_page,
+                            TARGET_PAGE_SIZE);
+            }
+            break;
+        }
+        case HV_X64_MSR_SYNDBG_PENDING_BUFFER:
+            env->msr_hv_syndbg_pending_page = exit->u.syndbg.pending_page;
+            hyperv_syndbg_set_pending_page(env->msr_hv_syndbg_pending_page);
+            break;
+        default:
+            return -1;
+        }
+
+        return 0;
     default:
         return -1;
     }
diff --git a/target/i386/kvm/kvm.c b/target/i386/kvm/kvm.c
index 2c8feb4a6f..1c6f3b53af 100644
--- a/target/i386/kvm/kvm.c
+++ b/target/i386/kvm/kvm.c
@@ -102,6 +102,7 @@  static bool has_msr_hv_synic;
 static bool has_msr_hv_stimer;
 static bool has_msr_hv_frequencies;
 static bool has_msr_hv_reenlightenment;
+static bool has_msr_hv_syndbg_options;
 static bool has_msr_xss;
 static bool has_msr_umwait;
 static bool has_msr_spec_ctrl;
@@ -932,6 +933,14 @@  static struct {
              .bits = HV_DEPRECATING_AEOI_RECOMMENDED}
         }
     },
+    [HYPERV_FEAT_SYNDBG] = {
+        .desc = "Enable synthetic kernel debugger channel (hv-syndbg)",
+        .flags = {
+            {.func = HV_CPUID_FEATURES, .reg = R_EDX,
+             .bits = HV_FEATURE_DEBUG_MSRS_AVAILABLE}
+        },
+        .dependencies = BIT(HYPERV_FEAT_SYNIC) | BIT(HYPERV_FEAT_RELAXED)
+    },
 };
 
 static struct kvm_cpuid2 *try_get_hv_cpuid(CPUState *cs, int max,
@@ -972,8 +981,8 @@  static struct kvm_cpuid2 *try_get_hv_cpuid(CPUState *cs, int max,
 static struct kvm_cpuid2 *get_supported_hv_cpuid(CPUState *cs)
 {
     struct kvm_cpuid2 *cpuid;
-    /* 0x40000000..0x40000005, 0x4000000A, 0x40000080..0x40000080 leaves */
-    int max = 10;
+    /* 0x40000000..0x40000005, 0x4000000A, 0x40000080..0x40000082 leaves */
+    int max = 11;
     int i;
     bool do_sys_ioctl;
 
@@ -1086,6 +1095,12 @@  static struct kvm_cpuid2 *get_supported_hv_cpuid_legacy(CPUState *cs)
         entry_feat->eax |= HV_SYNTIMERS_AVAILABLE;
     }
 
+    if (has_msr_hv_syndbg_options) {
+        entry_feat->edx |= HV_GUEST_DEBUGGING_AVAILABLE;
+        entry_feat->edx |= HV_FEATURE_DEBUG_MSRS_AVAILABLE;
+        entry_feat->ebx |= HV_PARTITION_DEBUGGING_ALLOWED;
+    }
+
     if (kvm_check_extension(cs->kvm_state,
                             KVM_CAP_HYPERV_TLBFLUSH) > 0) {
         entry_recomm->eax |= HV_REMOTE_TLB_FLUSH_RECOMMENDED;
@@ -1337,12 +1352,22 @@  static int hyperv_fill_cpuids(CPUState *cs,
 {
     X86CPU *cpu = X86_CPU(cs);
     struct kvm_cpuid_entry2 *c;
-    uint32_t cpuid_i = 0;
+    uint32_t signature[3];
+    uint32_t cpuid_i = 0, max_cpuid_leaf = 0;
+
+    max_cpuid_leaf = HV_CPUID_IMPLEMENT_LIMITS;
+    if (hyperv_feat_enabled(cpu, HYPERV_FEAT_EVMCS)) {
+        max_cpuid_leaf = MAX(max_cpuid_leaf, HV_CPUID_NESTED_FEATURES);
+    }
+
+    if (hyperv_feat_enabled(cpu, HYPERV_FEAT_SYNDBG)) {
+        max_cpuid_leaf =
+            MAX(max_cpuid_leaf, HV_CPUID_SYNDBG_PLATFORM_CAPABILITIES);
+    }
 
     c = &cpuid_ent[cpuid_i++];
     c->function = HV_CPUID_VENDOR_AND_MAX_FUNCTIONS;
-    c->eax = hyperv_feat_enabled(cpu, HYPERV_FEAT_EVMCS) ?
-        HV_CPUID_NESTED_FEATURES : HV_CPUID_IMPLEMENT_LIMITS;
+    c->eax = max_cpuid_leaf;
     c->ebx = cpu->hyperv_vendor_id[0];
     c->ecx = cpu->hyperv_vendor_id[1];
     c->edx = cpu->hyperv_vendor_id[2];
@@ -1421,6 +1446,33 @@  static int hyperv_fill_cpuids(CPUState *cs,
         c->eax = cpu->hyperv_nested[0];
     }
 
+    if (hyperv_feat_enabled(cpu, HYPERV_FEAT_SYNDBG)) {
+        c = &cpuid_ent[cpuid_i++];
+        c->function = HV_CPUID_SYNDBG_VENDOR_AND_MAX_FUNCTIONS;
+        c->eax = hyperv_feat_enabled(cpu, HYPERV_FEAT_EVMCS) ?
+            HV_CPUID_NESTED_FEATURES : HV_CPUID_IMPLEMENT_LIMITS;
+        memcpy(signature, "Microsoft VS", 12);
+        c->eax = 0;
+        c->ebx = signature[0];
+        c->ecx = signature[1];
+        c->edx = signature[2];
+
+        c = &cpuid_ent[cpuid_i++];
+        c->function = HV_CPUID_SYNDBG_INTERFACE;
+        memcpy(signature, "VS#1\0\0\0\0\0\0\0\0", 12);
+        c->eax = signature[0];
+        c->ebx = 0;
+        c->ecx = 0;
+        c->edx = 0;
+
+        c = &cpuid_ent[cpuid_i++];
+        c->function = HV_CPUID_SYNDBG_PLATFORM_CAPABILITIES;
+        c->eax = HV_SYNDBG_CAP_ALLOW_KERNEL_DEBUGGING;
+        c->ebx = 0;
+        c->ecx = 0;
+        c->edx = 0;
+    }
+
     return cpuid_i;
 }
 
@@ -2215,6 +2267,9 @@  static int kvm_get_supported_msrs(KVMState *s)
             case HV_X64_MSR_REENLIGHTENMENT_CONTROL:
                 has_msr_hv_reenlightenment = true;
                 break;
+            case HV_X64_MSR_SYNDBG_OPTIONS:
+                has_msr_hv_syndbg_options = true;
+                break;
             case MSR_IA32_SPEC_CTRL:
                 has_msr_spec_ctrl = true;
                 break;
@@ -3132,6 +3187,11 @@  static int kvm_put_msrs(X86CPU *cpu, int level)
                 kvm_msr_entry_add(cpu, HV_X64_MSR_TSC_EMULATION_STATUS,
                                   env->msr_hv_tsc_emulation_status);
             }
+            if (hyperv_feat_enabled(cpu, HYPERV_FEAT_SYNDBG) &&
+                has_msr_hv_syndbg_options) {
+                kvm_msr_entry_add(cpu, HV_X64_MSR_SYNDBG_OPTIONS,
+                                  hyperv_syndbg_query_options());
+            }
         }
         if (hyperv_feat_enabled(cpu, HYPERV_FEAT_VAPIC)) {
             kvm_msr_entry_add(cpu, HV_X64_MSR_APIC_ASSIST_PAGE,
@@ -3565,6 +3625,9 @@  static int kvm_get_msrs(X86CPU *cpu)
         kvm_msr_entry_add(cpu, HV_X64_MSR_TSC_EMULATION_CONTROL, 0);
         kvm_msr_entry_add(cpu, HV_X64_MSR_TSC_EMULATION_STATUS, 0);
     }
+    if (has_msr_hv_syndbg_options) {
+        kvm_msr_entry_add(cpu, HV_X64_MSR_SYNDBG_OPTIONS, 0);
+    }
     if (has_msr_hv_crash) {
         int j;
 
@@ -3851,6 +3914,9 @@  static int kvm_get_msrs(X86CPU *cpu)
         case HV_X64_MSR_TSC_EMULATION_STATUS:
             env->msr_hv_tsc_emulation_status = msrs[i].data;
             break;
+        case HV_X64_MSR_SYNDBG_OPTIONS:
+            env->msr_hv_syndbg_options = msrs[i].data;
+            break;
         case MSR_MTRRdefType:
             env->mtrr_deftype = msrs[i].data;
             break;