Home | History | Annotate | Line # | Download | only in hyperv
      1 /*	$NetBSD: vmbusic.c,v 1.2 2019/10/01 18:00:08 chs Exp $	*/
      2 /*-
      3  * Copyright (c) 2014,2016 Microsoft Corp.
      4  * All rights reserved.
      5  *
      6  * Redistribution and use in source and binary forms, with or without
      7  * modification, are permitted provided that the following conditions
      8  * are met:
      9  * 1. Redistributions of source code must retain the above copyright
     10  *    notice unmodified, this list of conditions, and the following
     11  *    disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
     17  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     18  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
     19  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
     20  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
     21  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     22  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     23  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     25  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     26  */
     27 
     28 #include <sys/cdefs.h>
     29 #ifdef __KERNEL_RCSID
     30 __KERNEL_RCSID(0, "$NetBSD: vmbusic.c,v 1.2 2019/10/01 18:00:08 chs Exp $");
     31 #endif
     32 #ifdef __FBSDID
     33 __FBSDID("$FreeBSD: head/sys/dev/hyperv/utilities/vmbus_ic.c 310317 2016-12-20 07:14:24Z sephe $");
     34 #endif
     35 
     36 #include <sys/param.h>
     37 #include <sys/systm.h>
     38 #include <sys/kernel.h>
     39 #include <sys/device.h>
     40 #include <sys/kmem.h>
     41 #include <sys/reboot.h>
     42 
     43 #include <dev/hyperv/vmbusvar.h>
     44 #include <dev/hyperv/vmbusicreg.h>
     45 #include <dev/hyperv/vmbusicvar.h>
     46 
     47 #define VMBUS_IC_BRSIZE		(4 * PAGE_SIZE)
     48 
     49 #define VMBUS_IC_VERCNT         2
     50 #define VMBUS_IC_NEGOSZ		\
     51     offsetof(struct vmbus_icmsg_negotiate, ic_ver[VMBUS_IC_VERCNT])
     52 __CTASSERT(VMBUS_IC_NEGOSZ < VMBUS_IC_BRSIZE);
     53 
     54 
     55 int
     56 vmbusic_probe(struct vmbus_attach_args *aa, const struct hyperv_guid *guid)
     57 {
     58 
     59 	if (memcmp(aa->aa_type, guid, sizeof(*aa->aa_type)) != 0)
     60 		return 0;
     61 	return 1;
     62 }
     63 
     64 int
     65 vmbusic_attach(device_t dv, struct vmbus_attach_args *aa,
     66     vmbus_channel_callback_t cb)
     67 {
     68 	struct vmbusic_softc *sc = device_private(dv);
     69 
     70 	sc->sc_dev = dv;
     71 	sc->sc_chan = aa->aa_chan;
     72 
     73 	sc->sc_buflen = VMBUS_IC_BRSIZE;
     74 	sc->sc_buf = kmem_alloc(sc->sc_buflen, KM_SLEEP);
     75 
     76 	/*
     77 	 * These services are not performance critical and do not need
     78 	 * batched reading. Furthermore, some services such as KVP can
     79 	 * only handle one message from the host at a time.
     80 	 * Turn off batched reading for all util drivers before we open the
     81 	 * channel.
     82 	 */
     83 	sc->sc_chan->ch_flags &= ~CHF_BATCHED;
     84 
     85 	if (vmbus_channel_open(sc->sc_chan, sc->sc_buflen, NULL, 0, cb, sc)) {
     86 		aprint_error_dev(dv, "failed to open channel\n");
     87 		kmem_free(sc->sc_buf, sc->sc_buflen);
     88 		sc->sc_buf = NULL;
     89 		return ENXIO;
     90 	}
     91 
     92 	return 0;
     93 }
     94 
     95 int
     96 vmbusic_detach(device_t dv, int flags)
     97 {
     98 	struct vmbusic_softc *sc = device_private(dv);
     99 	int error;
    100 
    101 	error = vmbus_channel_close(sc->sc_chan);
    102 	if (error != 0)
    103 		return error;
    104 
    105 	if (sc->sc_buf != NULL) {
    106 		kmem_free(sc->sc_buf, sc->sc_buflen);
    107 		sc->sc_buf = NULL;
    108 	}
    109 
    110 	if (sc->sc_log != NULL) {
    111 		sysctl_teardown(&sc->sc_log);
    112 		sc->sc_log = NULL;
    113 	}
    114 
    115 	return 0;
    116 }
    117 
    118 int
    119 vmbusic_negotiate(struct vmbusic_softc *sc, void *data, uint32_t *dlen0,
    120     uint32_t fw_ver, uint32_t msg_ver)
    121 {
    122 	struct vmbus_icmsg_negotiate *nego;
    123 	uint32_t sel_fw_ver = 0, sel_msg_ver = 0;
    124 	int i, cnt, dlen = *dlen0, error;
    125 	bool has_fw_ver, has_msg_ver = false;
    126 
    127 	/*
    128 	 * Preliminary message verification.
    129 	 */
    130 	if (dlen < sizeof(*nego)) {
    131 		device_printf(sc->sc_dev, "truncated ic negotiate, len %d\n",
    132 		    dlen);
    133 		return EINVAL;
    134 	}
    135 	nego = data;
    136 
    137 	if (nego->ic_fwver_cnt == 0) {
    138 		device_printf(sc->sc_dev, "ic negotiate does not contain "
    139 		    "framework version %u\n", nego->ic_fwver_cnt);
    140 		return EINVAL;
    141 	}
    142 	if (nego->ic_msgver_cnt == 0) {
    143 		device_printf(sc->sc_dev, "ic negotiate does not contain "
    144 		    "message version %u\n", nego->ic_msgver_cnt);
    145 		return EINVAL;
    146 	}
    147 
    148 	cnt = nego->ic_fwver_cnt + nego->ic_msgver_cnt;
    149 	if (dlen < offsetof(struct vmbus_icmsg_negotiate, ic_ver[cnt])) {
    150 		device_printf(sc->sc_dev, "ic negotiate does not contain "
    151 		    "versions %d\n", dlen);
    152 		return EINVAL;
    153 	}
    154 
    155 	error = EOPNOTSUPP;
    156 
    157 	/*
    158 	 * Find the best match framework version.
    159 	 */
    160 	has_fw_ver = false;
    161 	for (i = 0; i < nego->ic_fwver_cnt; ++i) {
    162 		if (VMBUS_ICVER_LE(nego->ic_ver[i], fw_ver)) {
    163 			if (!has_fw_ver) {
    164 				sel_fw_ver = nego->ic_ver[i];
    165 				has_fw_ver = true;
    166 			} else if (VMBUS_ICVER_GT(nego->ic_ver[i],
    167 			    sel_fw_ver)) {
    168 				sel_fw_ver = nego->ic_ver[i];
    169 			}
    170 		}
    171 	}
    172 	if (!has_fw_ver) {
    173 		device_printf(sc->sc_dev, "failed to select framework "
    174 		    "version\n");
    175 		goto done;
    176 	}
    177 
    178 	/*
    179 	 * Fine the best match message version.
    180 	 */
    181 	has_msg_ver = false;
    182 	for (i = nego->ic_fwver_cnt;
    183 	    i < nego->ic_fwver_cnt + nego->ic_msgver_cnt; ++i) {
    184 		if (VMBUS_ICVER_LE(nego->ic_ver[i], msg_ver)) {
    185 			if (!has_msg_ver) {
    186 				sel_msg_ver = nego->ic_ver[i];
    187 				has_msg_ver = true;
    188 			} else if (VMBUS_ICVER_GT(nego->ic_ver[i],
    189 			    sel_msg_ver)) {
    190 				sel_msg_ver = nego->ic_ver[i];
    191 			}
    192 		}
    193 	}
    194 	if (!has_msg_ver) {
    195 		device_printf(sc->sc_dev, "failed to select message version\n");
    196 		goto done;
    197 	}
    198 
    199 	error = 0;
    200 done:
    201 	if (bootverbose || !has_fw_ver || !has_msg_ver) {
    202 		if (has_fw_ver) {
    203 			device_printf(sc->sc_dev,
    204 			    "sel framework version: %u.%u\n",
    205 			    VMBUS_ICVER_MAJOR(sel_fw_ver),
    206 			    VMBUS_ICVER_MINOR(sel_fw_ver));
    207 		}
    208 		for (i = 0; i < nego->ic_fwver_cnt; i++) {
    209 			device_printf(sc->sc_dev,
    210 			    "supp framework version: %u.%u\n",
    211 			    VMBUS_ICVER_MAJOR(nego->ic_ver[i]),
    212 			    VMBUS_ICVER_MINOR(nego->ic_ver[i]));
    213 		}
    214 
    215 		if (has_msg_ver) {
    216 			device_printf(sc->sc_dev,
    217 			    "sel message version: %u.%u\n",
    218 			    VMBUS_ICVER_MAJOR(sel_msg_ver),
    219 			    VMBUS_ICVER_MINOR(sel_msg_ver));
    220 		}
    221 		for (i = nego->ic_fwver_cnt;
    222 		    i < nego->ic_fwver_cnt + nego->ic_msgver_cnt; i++) {
    223 			device_printf(sc->sc_dev,
    224 			    "supp message version: %u.%u\n",
    225 			    VMBUS_ICVER_MAJOR(nego->ic_ver[i]),
    226 			    VMBUS_ICVER_MINOR(nego->ic_ver[i]));
    227 		}
    228 	}
    229 	if (error)
    230 		return error;
    231 
    232 	/* Record the selected versions. */
    233 	sc->sc_fwver = sel_fw_ver;
    234 	sc->sc_msgver = sel_msg_ver;
    235 
    236 	/* One framework version. */
    237 	nego->ic_fwver_cnt = 1;
    238 	nego->ic_ver[0] = sel_fw_ver;
    239 
    240 	/* One message version. */
    241 	nego->ic_msgver_cnt = 1;
    242 	nego->ic_ver[1] = sel_msg_ver;
    243 
    244 	/* Update data size. */
    245 	nego->ic_hdr.ic_dsize = VMBUS_IC_NEGOSZ -
    246 	    sizeof(struct vmbus_icmsg_hdr);
    247 
    248 	/* Update total size, if necessary. */
    249 	if (dlen < VMBUS_IC_NEGOSZ)
    250 		*dlen0 = VMBUS_IC_NEGOSZ;
    251 
    252 	return 0;
    253 }
    254 
    255 int
    256 vmbusic_sendresp(struct vmbusic_softc *sc, struct vmbus_channel *chan,
    257     void *data, uint32_t dlen, uint64_t rid)
    258 {
    259 	struct vmbus_icmsg_hdr *hdr;
    260 	int error;
    261 
    262 	KASSERTMSG(dlen >= sizeof(*hdr), "invalid data length %d", dlen);
    263 	hdr = data;
    264 
    265 	hdr->ic_flags = VMBUS_ICMSG_FLAG_TRANSACTION|VMBUS_ICMSG_FLAG_RESPONSE;
    266 	error = vmbus_channel_send(chan, data, dlen, rid,
    267 	    VMBUS_CHANPKT_TYPE_INBAND, 0);
    268 	if (error != 0)
    269 		device_printf(sc->sc_dev, "resp send failed: %d\n", error);
    270 	return error;
    271 }
    272