From e4d3f1e40ab3327e4ff6f689f54a64bd2ebc0568 Mon Sep 17 00:00:00 2001 From: Yuri Date: Sun, 5 Feb 2023 18:32:08 +0300 Subject: [PATCH] hv_hid: Hyper-V HID driver Hyper-V HID driver using hidbus/hms. Reviewed by: wulf MFC after: 1 week PR: 221074 Differential revision: https://reviews.freebsd.org/D38140 --- sys/amd64/conf/NOTES | 1 + sys/conf/files.x86 | 1 + sys/dev/hid/hidbus.c | 1 + sys/dev/hyperv/input/hv_hid.c | 564 ++++++++++++++++++++++++++++++++ sys/i386/conf/NOTES | 1 + sys/modules/hyperv/Makefile | 2 +- sys/modules/hyperv/hid/Makefile | 12 + 7 files changed, 581 insertions(+), 1 deletion(-) create mode 100644 sys/dev/hyperv/input/hv_hid.c create mode 100644 sys/modules/hyperv/hid/Makefile diff --git a/sys/amd64/conf/NOTES b/sys/amd64/conf/NOTES index 0781d03900ca..4b107b19ac42 100644 --- a/sys/amd64/conf/NOTES +++ b/sys/amd64/conf/NOTES @@ -501,6 +501,7 @@ device kvm_clock # KVM paravirtual clock driver # Microsoft Hyper-V enhancement support device hyperv # HyperV drivers +device hvhid # HyperV HID device # Xen HVM Guest Optimizations options XENHVM # Xen HVM kernel infrastructure diff --git a/sys/conf/files.x86 b/sys/conf/files.x86 index 37d0534aa67a..0a32c888cdb2 100644 --- a/sys/conf/files.x86 +++ b/sys/conf/files.x86 @@ -115,6 +115,7 @@ dev/hwpmc/hwpmc_uncore.c optional hwpmc dev/hwpmc/hwpmc_tsc.c optional hwpmc dev/hwpmc/hwpmc_x86.c optional hwpmc dev/hyperv/hvsock/hv_sock.c optional hyperv +dev/hyperv/input/hv_hid.c optional hyperv hvhid dev/hyperv/input/hv_kbd.c optional hyperv dev/hyperv/input/hv_kbdc.c optional hyperv dev/hyperv/pcib/vmbus_pcib.c optional hyperv pci diff --git a/sys/dev/hid/hidbus.c b/sys/dev/hid/hidbus.c index 92a96b10bdbd..b6a9e9f2192f 100644 --- a/sys/dev/hid/hidbus.c +++ b/sys/dev/hid/hidbus.c @@ -924,5 +924,6 @@ driver_t hidbus_driver = { MODULE_DEPEND(hidbus, hid, 1, 1, 1); MODULE_VERSION(hidbus, 1); +DRIVER_MODULE(hidbus, hvhid, hidbus_driver, 0, 0); DRIVER_MODULE(hidbus, iichid, hidbus_driver, 0, 0); DRIVER_MODULE(hidbus, usbhid, hidbus_driver, 0, 0); diff --git a/sys/dev/hyperv/input/hv_hid.c b/sys/dev/hyperv/input/hv_hid.c new file mode 100644 index 000000000000..d576b292e12e --- /dev/null +++ b/sys/dev/hyperv/input/hv_hid.c @@ -0,0 +1,564 @@ +/*- + * Copyright (c) 2017 Microsoft Corp. + * Copyright (c) 2023 Yuri + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include + +#include "hid_if.h" +#include "vmbus_if.h" + +#define HV_HID_VER_MAJOR 2 +#define HV_HID_VER_MINOR 0 +#define HV_HID_VER (HV_HID_VER_MINOR | (HV_HID_VER_MAJOR) << 16) + +#define HV_BUFSIZ (4 * PAGE_SIZE) +#define HV_HID_RINGBUFF_SEND_SZ (10 * PAGE_SIZE) +#define HV_HID_RINGBUFF_RECV_SZ (10 * PAGE_SIZE) + +typedef struct { + device_t dev; + struct mtx mtx; + /* vmbus */ + struct vmbus_channel *hs_chan; + struct vmbus_xact_ctx *hs_xact_ctx; + uint8_t *buf; + int buflen; + /* hid */ + struct hid_device_info hdi; + hid_intr_t *intr; + bool intr_on; + void *intr_ctx; + uint8_t *rdesc; +} hv_hid_sc; + +typedef enum { + SH_PROTO_REQ, + SH_PROTO_RESP, + SH_DEVINFO, + SH_DEVINFO_ACK, + SH_INPUT_REPORT, +} sh_msg_type; + +typedef struct { + sh_msg_type type; + uint32_t size; +} __packed sh_msg_hdr; + +typedef struct { + sh_msg_hdr hdr; + char data[]; +} __packed sh_msg; + +typedef struct { + sh_msg_hdr hdr; + uint32_t ver; +} __packed sh_proto_req; + +typedef struct { + sh_msg_hdr hdr; + uint32_t ver; + uint32_t app; +} __packed sh_proto_resp; + +typedef struct { + u_int size; + u_short vendor; + u_short product; + u_short version; + u_short reserved[11]; +} __packed sh_devinfo; + +/* Copied from linux/hid.h */ +typedef struct { + uint8_t bDescriptorType; + uint16_t wDescriptorLength; +} __packed sh_hcdesc; + +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t bcdHID; + uint8_t bCountryCode; + uint8_t bNumDescriptors; + sh_hcdesc hcdesc[1]; +} __packed sh_hdesc; + +typedef struct { + sh_msg_hdr hdr; + sh_devinfo devinfo; + sh_hdesc hdesc; +} __packed sh_devinfo_resp; + +typedef struct { + sh_msg_hdr hdr; + uint8_t rsvd; +} __packed sh_devinfo_ack; + +typedef struct { + sh_msg_hdr hdr; + char buffer[]; +} __packed sh_input_report; + +typedef enum { + HV_HID_MSG_INVALID, + HV_HID_MSG_DATA, +} hv_hid_msg_type; + +typedef struct { + hv_hid_msg_type type; + uint32_t size; + char data[]; +} hv_hid_pmsg; + +typedef struct { + hv_hid_msg_type type; + uint32_t size; + union { + sh_msg msg; + sh_proto_req req; + sh_proto_resp resp; + sh_devinfo_resp dresp; + sh_devinfo_ack ack; + sh_input_report irep; + }; +} hv_hid_msg; + +#define HV_HID_REQ_SZ (sizeof(hv_hid_pmsg) + sizeof(sh_proto_req)) +#define HV_HID_RESP_SZ (sizeof(hv_hid_pmsg) + sizeof(sh_proto_resp)) +#define HV_HID_ACK_SZ (sizeof(hv_hid_pmsg) + sizeof(sh_devinfo_ack)) + +/* Somewhat arbitrary, enough to get the devinfo response */ +#define HV_HID_REQ_MAX 256 +#define HV_HID_RESP_MAX 256 + +static const struct vmbus_ic_desc vmbus_hid_descs[] = { + { + .ic_guid = { .hv_guid = { + 0x9e, 0xb6, 0xa8, 0xcf, 0x4a, 0x5b, 0xc0, 0x4c, + 0xb9, 0x8b, 0x8b, 0xa1, 0xa1, 0xf3, 0xf9, 0x5a} }, + .ic_desc = "Hyper-V HID device" + }, + VMBUS_IC_DESC_END +}; + +/* TODO: add GUID support to devmatch(8) to export vmbus_hid_descs directly */ +const struct { + char *guid; +} vmbus_hid_descs_pnp[] = {{ "cfa8b69e-5b4a-4cc0-b98b-8ba1a1f3f95a" }}; + +static int hv_hid_attach(device_t dev); +static int hv_hid_detach(device_t dev); + +static int +hv_hid_connect_vsp(hv_hid_sc *sc) +{ + struct vmbus_xact *xact; + hv_hid_msg *req; + const hv_hid_msg *resp; + size_t resplen; + int ret; + + xact = vmbus_xact_get(sc->hs_xact_ctx, HV_HID_REQ_SZ); + if (xact == NULL) { + device_printf(sc->dev, "no xact for init"); + return (ENODEV); + } + req = vmbus_xact_req_data(xact); + req->type = HV_HID_MSG_DATA; + req->size = sizeof(sh_proto_req); + req->req.hdr.type = SH_PROTO_REQ; + req->req.hdr.size = sizeof(u_int); + req->req.ver = HV_HID_VER; + + vmbus_xact_activate(xact); + ret = vmbus_chan_send(sc->hs_chan, + VMBUS_CHANPKT_TYPE_INBAND, + VMBUS_CHANPKT_FLAG_RC, + req, HV_HID_REQ_SZ, (uint64_t)(uintptr_t)xact); + if (ret != 0) { + device_printf(sc->dev, "failed to send proto req\n"); + vmbus_xact_deactivate(xact); + return (ret); + } + resp = vmbus_chan_xact_wait(sc->hs_chan, xact, &resplen, true); + if (resplen != HV_HID_RESP_SZ || !resp->resp.app) { + device_printf(sc->dev, "proto req failed\n"); + ret = ENODEV; + } + + vmbus_xact_put(xact); + return (ret); +} + +static void +hv_hid_receive(hv_hid_sc *sc, struct vmbus_chanpkt_hdr *pkt) +{ + const hv_hid_msg *msg; + sh_msg_type msg_type; + uint32_t msg_len; + void *rdesc; + + msg = VMBUS_CHANPKT_CONST_DATA(pkt); + msg_len = VMBUS_CHANPKT_DATALEN(pkt); + + if (msg->type != HV_HID_MSG_DATA) + return; + + if (msg_len <= sizeof(hv_hid_pmsg)) { + device_printf(sc->dev, "invalid packet length\n"); + return; + } + msg_type = msg->msg.hdr.type; + switch (msg_type) { + case SH_PROTO_RESP: { + struct vmbus_xact_ctx *xact_ctx; + + xact_ctx = sc->hs_xact_ctx; + if (xact_ctx != NULL) { + vmbus_xact_ctx_wakeup(xact_ctx, + VMBUS_CHANPKT_CONST_DATA(pkt), + VMBUS_CHANPKT_DATALEN(pkt)); + } + break; + } + case SH_DEVINFO: { + struct vmbus_xact *xact; + struct hid_device_info *hdi; + hv_hid_msg ack; + const sh_devinfo *devinfo; + const sh_hdesc *hdesc; + + /* Send ack */ + ack.type = HV_HID_MSG_DATA; + ack.size = sizeof(sh_devinfo_ack); + ack.ack.hdr.type = SH_DEVINFO_ACK; + ack.ack.hdr.size = 1; + ack.ack.rsvd = 0; + + xact = vmbus_xact_get(sc->hs_xact_ctx, HV_HID_ACK_SZ); + if (xact == NULL) + break; + vmbus_xact_activate(xact); + (void) vmbus_chan_send(sc->hs_chan, VMBUS_CHANPKT_TYPE_INBAND, + 0, &ack, HV_HID_ACK_SZ, (uint64_t)(uintptr_t)xact); + vmbus_xact_deactivate(xact); + vmbus_xact_put(xact); + + /* Check for resume from hibernation */ + if (sc->rdesc != NULL) + break; + + /* Parse devinfo response */ + devinfo = &msg->dresp.devinfo; + hdesc = &msg->dresp.hdesc; + if (hdesc->bLength == 0) + break; + hdi = &sc->hdi; + memset(hdi, 0, sizeof(*hdi)); + hdi->rdescsize = le16toh(hdesc->hcdesc[0].wDescriptorLength); + if (hdi->rdescsize == 0) + break; + strlcpy(hdi->name, "Hyper-V", sizeof(hdi->name)); + hdi->idBus = BUS_VIRTUAL; + hdi->idVendor = le16toh(devinfo->vendor); + hdi->idProduct = le16toh(devinfo->product); + hdi->idVersion = le16toh(devinfo->version); + /* Save rdesc copy */ + rdesc = malloc(hdi->rdescsize, M_DEVBUF, M_WAITOK | M_ZERO); + memcpy(rdesc, (const uint8_t *)hdesc + hdesc->bLength, + hdi->rdescsize); + mtx_lock(&sc->mtx); + sc->rdesc = rdesc; + wakeup(sc); + mtx_unlock(&sc->mtx); + break; + } + case SH_INPUT_REPORT: { + mtx_lock(&sc->mtx); + if (sc->intr != NULL && sc->intr_on) + sc->intr(sc->intr_ctx, + __DECONST(void *, msg->irep.buffer), + msg->irep.hdr.size); + mtx_unlock(&sc->mtx); + break; + } + default: + break; + } +} + +static void +hv_hid_read_channel(struct vmbus_channel *channel, void *ctx) +{ + hv_hid_sc *sc; + uint8_t *buf; + int buflen; + int ret; + + sc = ctx; + buf = sc->buf; + buflen = sc->buflen; + for (;;) { + struct vmbus_chanpkt_hdr *pkt; + int rcvd; + + pkt = (struct vmbus_chanpkt_hdr *)buf; + rcvd = buflen; + ret = vmbus_chan_recv_pkt(channel, pkt, &rcvd); + if (__predict_false(ret == ENOBUFS)) { + buflen = sc->buflen * 2; + while (buflen < rcvd) + buflen *= 2; + buf = malloc(buflen, M_DEVBUF, M_WAITOK | M_ZERO); + device_printf(sc->dev, "expand recvbuf %d -> %d\n", + sc->buflen, buflen); + free(sc->buf, M_DEVBUF); + sc->buf = buf; + sc->buflen = buflen; + continue; + } else if (__predict_false(ret == EAGAIN)) { + /* No more channel packets; done! */ + break; + } + KASSERT(ret == 0, ("vmbus_chan_recv_pkt failed: %d", ret)); + + switch (pkt->cph_type) { + case VMBUS_CHANPKT_TYPE_COMP: + case VMBUS_CHANPKT_TYPE_RXBUF: + device_printf(sc->dev, "unhandled event: %d\n", + pkt->cph_type); + break; + case VMBUS_CHANPKT_TYPE_INBAND: + hv_hid_receive(sc, pkt); + break; + default: + device_printf(sc->dev, "unknown event: %d\n", + pkt->cph_type); + break; + } + } +} + +static int +hv_hid_probe(device_t dev) +{ + device_t bus; + const struct vmbus_ic_desc *d; + + if (resource_disabled(device_get_name(dev), 0)) + return (ENXIO); + + bus = device_get_parent(dev); + for (d = vmbus_hid_descs; d->ic_desc != NULL; ++d) { + if (VMBUS_PROBE_GUID(bus, dev, &d->ic_guid) == 0) { + device_set_desc(dev, d->ic_desc); + return (BUS_PROBE_DEFAULT); + } + } + + return (ENXIO); +} + +static int +hv_hid_attach(device_t dev) +{ + device_t child; + hv_hid_sc *sc; + int ret; + + sc = device_get_softc(dev); + sc->dev = dev; + mtx_init(&sc->mtx, "hvhid lock", NULL, MTX_DEF); + sc->hs_chan = vmbus_get_channel(dev); + sc->hs_xact_ctx = vmbus_xact_ctx_create(bus_get_dma_tag(dev), + HV_HID_REQ_MAX, HV_HID_RESP_MAX, 0); + if (sc->hs_xact_ctx == NULL) { + ret = ENOMEM; + goto out; + } + sc->buflen = HV_BUFSIZ; + sc->buf = malloc(sc->buflen, M_DEVBUF, M_WAITOK | M_ZERO); + vmbus_chan_set_readbatch(sc->hs_chan, false); + ret = vmbus_chan_open(sc->hs_chan, HV_HID_RINGBUFF_SEND_SZ, + HV_HID_RINGBUFF_RECV_SZ, NULL, 0, hv_hid_read_channel, sc); + if (ret != 0) + goto out; + ret = hv_hid_connect_vsp(sc); + if (ret != 0) + goto out; + + /* Wait until we have devinfo (or arbitrary timeout of 3s) */ + mtx_lock(&sc->mtx); + if (sc->rdesc == NULL) + ret = mtx_sleep(sc, &sc->mtx, 0, "hvhid", hz * 3); + mtx_unlock(&sc->mtx); + if (ret != 0) { + ret = ENODEV; + goto out; + } + child = device_add_child(sc->dev, "hidbus", -1); + if (child == NULL) { + device_printf(sc->dev, "failed to add hidbus\n"); + ret = ENOMEM; + goto out; + } + device_set_ivars(child, &sc->hdi); + ret = bus_generic_attach(dev); + if (ret != 0) + device_printf(sc->dev, "failed to attach hidbus\n"); +out: + if (ret != 0) + hv_hid_detach(dev); + return (ret); +} + +static int +hv_hid_detach(device_t dev) +{ + hv_hid_sc *sc; + int ret; + + sc = device_get_softc(dev); + ret = device_delete_children(dev); + if (ret != 0) + return (ret); + if (sc->hs_xact_ctx != NULL) + vmbus_xact_ctx_destroy(sc->hs_xact_ctx); + vmbus_chan_close(vmbus_get_channel(dev)); + free(sc->buf, M_DEVBUF); + free(sc->rdesc, M_DEVBUF); + mtx_destroy(&sc->mtx); + + return (0); +} + +static void +hv_hid_intr_setup(device_t dev, hid_intr_t intr, void *ctx, + struct hid_rdesc_info *rdesc) +{ + hv_hid_sc *sc; + + if (intr == NULL) + return; + + sc = device_get_softc(dev); + sc->intr = intr; + sc->intr_on = false; + sc->intr_ctx = ctx; + rdesc->rdsize = rdesc->isize; +} + +static void +hv_hid_intr_unsetup(device_t dev) +{ + hv_hid_sc *sc; + + sc = device_get_softc(dev); + sc->intr = NULL; + sc->intr_on = false; + sc->intr_ctx = NULL; +} + +static int +hv_hid_intr_start(device_t dev) +{ + hv_hid_sc *sc; + + sc = device_get_softc(dev); + mtx_lock(&sc->mtx); + sc->intr_on = true; + mtx_unlock(&sc->mtx); + return (0); +} + +static int +hv_hid_intr_stop(device_t dev) +{ + hv_hid_sc *sc; + + sc = device_get_softc(dev); + mtx_lock(&sc->mtx); + sc->intr_on = false; + mtx_unlock(&sc->mtx); + return (0); +} + +static int +hv_hid_get_rdesc(device_t dev, void *buf, hid_size_t len) +{ + hv_hid_sc *sc; + + sc = device_get_softc(dev); + if (len < sc->hdi.rdescsize) + return (EMSGSIZE); + memcpy(buf, sc->rdesc, len); + return (0); +} + +static device_method_t hv_hid_methods[] = { + DEVMETHOD(device_probe, hv_hid_probe), + DEVMETHOD(device_attach, hv_hid_attach), + DEVMETHOD(device_detach, hv_hid_detach), + + DEVMETHOD(hid_intr_setup, hv_hid_intr_setup), + DEVMETHOD(hid_intr_unsetup, hv_hid_intr_unsetup), + DEVMETHOD(hid_intr_start, hv_hid_intr_start), + DEVMETHOD(hid_intr_stop, hv_hid_intr_stop), + + DEVMETHOD(hid_get_rdesc, hv_hid_get_rdesc), + DEVMETHOD_END, +}; + +static driver_t hv_hid_driver = { + .name = "hvhid", + .methods = hv_hid_methods, + .size = sizeof(hv_hid_sc), +}; + +DRIVER_MODULE(hv_hid, vmbus, hv_hid_driver, NULL, NULL); +MODULE_VERSION(hv_hid, 1); +MODULE_DEPEND(hv_hid, hidbus, 1, 1, 1); +MODULE_DEPEND(hv_hid, hms, 1, 1, 1); +MODULE_DEPEND(hv_hid, vmbus, 1, 1, 1); +MODULE_PNP_INFO("Z:classid", vmbus, hv_hid, vmbus_hid_descs_pnp, + nitems(vmbus_hid_descs_pnp)); diff --git a/sys/i386/conf/NOTES b/sys/i386/conf/NOTES index 367af20d3a09..4e4c6193ebab 100644 --- a/sys/i386/conf/NOTES +++ b/sys/i386/conf/NOTES @@ -703,6 +703,7 @@ device kvm_clock # KVM paravirtual clock driver options HYPERV device hyperv # HyperV drivers +device hvhid # HyperV HID device ##################################################################### diff --git a/sys/modules/hyperv/Makefile b/sys/modules/hyperv/Makefile index e94b441e876d..0e9ca2495ef6 100644 --- a/sys/modules/hyperv/Makefile +++ b/sys/modules/hyperv/Makefile @@ -1,5 +1,5 @@ # $FreeBSD$ -SUBDIR = vmbus netvsc storvsc utilities hvsock +SUBDIR = vmbus netvsc storvsc utilities hvsock hid .include diff --git a/sys/modules/hyperv/hid/Makefile b/sys/modules/hyperv/hid/Makefile new file mode 100644 index 000000000000..40b991065d2a --- /dev/null +++ b/sys/modules/hyperv/hid/Makefile @@ -0,0 +1,12 @@ +# $FreeBSD$ + +.PATH: ${SRCTOP}/sys/dev/hyperv/input + +KMOD= hv_hid +SRCS= hv_hid.c +SRCS+= bus_if.h device_if.h hid_if.h vmbus_if.h + +CFLAGS+= -I${SRCTOP}/sys/dev/hyperv/include \ + -I${SRCTOP}/sys/dev/hyperv/vmbus + +.include