diff --git a/sys/conf/files.x86 b/sys/conf/files.x86 --- a/sys/conf/files.x86 +++ b/sys/conf/files.x86 @@ -117,6 +117,7 @@ dev/hyperv/hvsock/hv_sock.c optional hyperv dev/hyperv/input/hv_kbd.c optional hyperv dev/hyperv/input/hv_kbdc.c optional hyperv +dev/hyperv/input/hv_ms.c optional hyperv dev/hyperv/pcib/vmbus_pcib.c optional hyperv pci dev/hyperv/netvsc/hn_nvs.c optional hyperv dev/hyperv/netvsc/hn_rndis.c optional hyperv @@ -129,8 +130,8 @@ dev/hyperv/utilities/vmbus_shutdown.c optional hyperv dev/hyperv/utilities/vmbus_timesync.c optional hyperv dev/hyperv/vmbus/hyperv.c optional hyperv -dev/hyperv/vmbus/x86/hyperv_x86.c optional hyperv -dev/hyperv/vmbus/x86/vmbus_x86.c optional hyperv +dev/hyperv/vmbus/x86/hyperv_x86.c optional hyperv +dev/hyperv/vmbus/x86/vmbus_x86.c optional hyperv dev/hyperv/vmbus/hyperv_busdma.c optional hyperv dev/hyperv/vmbus/vmbus.c optional hyperv pci dev/hyperv/vmbus/vmbus_br.c optional hyperv diff --git a/sys/dev/hyperv/input/hv_ms.c b/sys/dev/hyperv/input/hv_ms.c new file mode 100644 --- /dev/null +++ b/sys/dev/hyperv/input/hv_ms.c @@ -0,0 +1,402 @@ +/*- + * Copyright (c) 2017 Microsoft Corp. + * All rights reserved. + * + * 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 "vmbus_if.h" + +#define HV_MS_DRIVER_NAME "hvms" + +#define HV_MS_VER_MAJOR 2 +#define HV_MS_VER_MINOR 0 +#define HV_MS_VER (HV_MS_VER_MINOR | (HV_MS_VER_MAJOR) << 16) + +#define HV_BUFSIZ (4 * PAGE_SIZE) +#define HV_MS_RINGBUFF_SEND_SZ (10 * PAGE_SIZE) +#define HV_MS_RINGBUFF_RECV_SZ (10 * PAGE_SIZE) + +typedef struct { + device_t dev; + struct vmbus_channel *hs_chan; + struct vmbus_xact_ctx *hs_xact_ctx; + uint8_t *buf; + int32_t buflen; +} hv_ms_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; /* XXX le16toh when used */ +} __packed sh_hcdesc; + +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t bcdHID; /* XXX le16toh when used */ + uint8_t bCountryCode; + uint8_t bNumDescriptors; + sh_hcdesc hcdesc; +} __packed sh_hdesc; + +typedef struct { + sh_msg_hdr hdr; + u_char rsvd; +} __packed sh_devinfo_ack; + +typedef struct { + sh_msg_hdr hdr; + sh_devinfo devinfo; + sh_hdesc hdesc; +} __packed sh_devinfo_resp; + +typedef struct { + sh_msg_hdr hdr; + char buffer[]; +} __packed sh_input_report; + +typedef enum { + HV_MS_MSG_INVALID, + HV_MS_MSG_DATA, +} hv_ms_msg_type; + +typedef struct { + hv_ms_msg_type type; + uint32_t size; + char data[]; +} hv_ms_pmsg; + +typedef struct { + hv_ms_msg_type type; + uint32_t size; + union { + sh_msg msg; + sh_proto_req req; + sh_proto_resp resp; + sh_devinfo_resp devinfo; + sh_devinfo_ack ack; + sh_input_report irep; + }; +} hv_ms_msg; + +#define HV_MS_ACK_SZ (sizeof(hv_ms_pmsg) + sizeof(sh_devinfo_ack)) +#define HV_MS_DRESP_SZ (sizeof(hv_ms_pmsg) + sizeof(sh_devinfo_resp)) +#define HV_MS_REQ_SZ (sizeof(hv_ms_pmsg) + sizeof(sh_proto_req)) +#define HV_MS_PRESP_SZ (sizeof(hv_ms_pmsg) + sizeof(sh_proto_resp)) + +static const struct vmbus_ic_desc vmbus_ms_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 Mouse" + }, + VMBUS_IC_DESC_END +}; + +static int hv_ms_attach(device_t dev); +static int hv_ms_detach(device_t dev); + +static int +hv_ms_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_ms_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_ms_connect_vsp(hv_ms_sc *sc) +{ + struct vmbus_xact *xact; + hv_ms_msg *req; + const hv_ms_msg *resp; + size_t resplen; + int ret; + + xact = vmbus_xact_get(sc->hs_xact_ctx, HV_MS_REQ_SZ); + if (xact == NULL) { + device_printf(sc->dev, "no xact for init"); + return (ENODEV); + } + req = vmbus_xact_req_data(xact); + req->type = HV_MS_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_MS_VER; + + vmbus_xact_activate(xact); + ret = vmbus_chan_send(sc->hs_chan, + VMBUS_CHANPKT_TYPE_INBAND, + VMBUS_CHANPKT_FLAG_RC, + req, HV_MS_REQ_SZ, (uint64_t)(uintptr_t)xact); + if (ret != 0) { + device_printf(sc->dev, "failed to send\n"); + vmbus_xact_deactivate(xact); + return (ret); + } + resp = vmbus_chan_xact_wait(sc->hs_chan, xact, &resplen, true); + if (resplen != HV_MS_PRESP_SZ || !resp->resp.app) { + device_printf(sc->dev, "protocol request failed\n"); + ret = ENODEV; + } + + vmbus_xact_put(xact); + return (ret); +} + +static void +hv_ms_receive(hv_ms_sc *sc, struct vmbus_chanpkt_hdr *pkt) +{ + const hv_ms_msg *msg; + sh_msg_type msg_type; + uint32_t msg_len; + + msg = VMBUS_CHANPKT_CONST_DATA(pkt); + msg_len = VMBUS_CHANPKT_DATALEN(pkt); + + if (msg->type != HV_MS_MSG_DATA) + return; + + if (msg_len <= sizeof(hv_ms_pmsg)) { + device_printf(sc->dev, "illegal packet\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; + hv_ms_msg ack; + + /* TODO process devinfo? */ + + /* Send the ack */ + ack.type = HV_MS_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_MS_ACK_SZ); + if (xact == NULL) + break; + vmbus_xact_activate(xact); + (void) vmbus_chan_send(sc->hs_chan, VMBUS_CHANPKT_TYPE_INBAND, + 0, &ack, HV_MS_ACK_SZ, (uint64_t)(uintptr_t)xact); + vmbus_xact_deactivate(xact); + vmbus_xact_put(xact); + break; + } + case SH_INPUT_REPORT: + device_printf(sc->dev, "report: "); + for (int i = 0; i < msg->irep.hdr.size; i++) + printf("%02d:", msg->irep.buffer[i]); + printf("\n"); + break; + default: + break; + } +} + +static void +hv_ms_read_channel(struct vmbus_channel *channel, void *ctx) +{ + hv_ms_sc *sc; + uint8_t *buf; + uint32_t buflen; + int ret; + + sc = ctx; + buf = sc->buf; + buflen = sc->buflen; + for (;;) { + struct vmbus_chanpkt_hdr *pkt; + uint32_t 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_ms_receive(sc, pkt); + break; + default: + device_printf(sc->dev, "unknown event: %d\n", pkt->cph_type); + break; + } + } +} + +static int +hv_ms_attach(device_t dev) +{ + hv_ms_sc *sc; + int ret; + + sc = device_get_softc(dev); + sc->hs_chan = vmbus_get_channel(dev); + sc->dev = dev; + sc->hs_xact_ctx = vmbus_xact_ctx_create(bus_get_dma_tag(dev), + MAX(HV_MS_ACK_SZ, HV_MS_REQ_SZ), + MAX(HV_MS_DRESP_SZ, HV_MS_PRESP_SZ), 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_MS_RINGBUFF_SEND_SZ, + HV_MS_RINGBUFF_RECV_SZ, NULL, 0, hv_ms_read_channel, sc); + if (ret != 0) + goto out; + ret = hv_ms_connect_vsp(sc); +out: + if (ret != 0) + hv_ms_detach(dev); + return (ret); +} + +static int +hv_ms_detach(device_t dev) +{ + hv_ms_sc *sc; + + sc = device_get_softc(dev); + 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); + + return (0); +} + +static device_method_t hv_ms_methods[] = { + DEVMETHOD(device_probe, hv_ms_probe), + DEVMETHOD(device_attach, hv_ms_attach), + DEVMETHOD(device_detach, hv_ms_detach), + { 0, 0 } +}; + +static driver_t hv_ms_driver = + { HV_MS_DRIVER_NAME, hv_ms_methods, sizeof(hv_ms_sc) }; + +DRIVER_MODULE(hv_ms, vmbus, hv_ms_driver, NULL, NULL); +MODULE_VERSION(hv_ms, 1); +MODULE_DEPEND(hv_ms, vmbus, 1, 1, 1); +//MODULE_DEPEND(hv_ms, hid, 1, 1, 1); +//MODULE_DEPEND(hv_ms, hidbus, 1, 1, 1); diff --git a/sys/modules/hyperv/hv_ms/Makefile b/sys/modules/hyperv/hv_ms/Makefile new file mode 100644 --- /dev/null +++ b/sys/modules/hyperv/hv_ms/Makefile @@ -0,0 +1,9 @@ +.PATH: ${SRCTOP}/sys/dev/hyperv/input + +KMOD= hv_ms +SRCS = hv_ms.c +SRCS+= bus_if.h device_if.h vmbus_if.h + +CFLAGS+= -I${SRCTOP}/sys/dev/hyperv/include + +.include