Skip site navigation (1)Skip section navigation (2)
Date:      Sun, 5 Jan 2014 20:07:12 +0000 (UTC)
From:      Bryan Venteicher <bryanv@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-projects@freebsd.org
Subject:   svn commit: r260330 - in projects/virtio/sys: dev/virtio/console modules/virtio modules/virtio/console
Message-ID:  <201401052007.s05K7CJJ053098@svn.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: bryanv
Date: Sun Jan  5 20:07:12 2014
New Revision: 260330
URL: http://svnweb.freebsd.org/changeset/base/260330

Log:
  Add WIP virtio_console(4) driver for serial like devices
  
  Initial support for the multiport feature is implemented, but hot plug
  and remove is still somewhat racey, so that feature is disabled. And
  unfortunately the spec is very lacking.

Added:
  projects/virtio/sys/dev/virtio/console/
  projects/virtio/sys/dev/virtio/console/virtio_console.c   (contents, props changed)
  projects/virtio/sys/dev/virtio/console/virtio_console.h   (contents, props changed)
  projects/virtio/sys/modules/virtio/console/
  projects/virtio/sys/modules/virtio/console/Makefile   (contents, props changed)
Modified:
  projects/virtio/sys/modules/virtio/Makefile

Added: projects/virtio/sys/dev/virtio/console/virtio_console.c
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ projects/virtio/sys/dev/virtio/console/virtio_console.c	Sun Jan  5 20:07:12 2014	(r260330)
@@ -0,0 +1,1217 @@
+/*-
+ * Copyright (c) 2013, Bryan Venteicher <bryanv@FreeBSD.org>
+ * 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.
+ */
+
+/* Driver for VirtIO console devices. */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/sglist.h>
+#include <sys/sysctl.h>
+#include <sys/taskqueue.h>
+#include <sys/queue.h>
+
+#include <sys/conf.h>
+#include <sys/cons.h>
+#include <sys/tty.h>
+
+#include <machine/bus.h>
+#include <machine/resource.h>
+#include <sys/bus.h>
+
+#include <dev/virtio/virtio.h>
+#include <dev/virtio/virtqueue.h>
+#include <dev/virtio/console/virtio_console.h>
+
+#include "virtio_if.h"
+
+#define VTCON_MAX_PORTS	1
+#define VTCON_TTY_PREFIX "V"
+#define VTCON_BULK_BUFSZ 128
+
+struct vtcon_softc;
+
+struct vtcon_port {
+	struct vtcon_softc	*vtcport_sc;
+	TAILQ_ENTRY(vtcon_port)  vtcport_next;
+	struct mtx		 vtcport_mtx;
+	int			 vtcport_id;
+	struct tty		*vtcport_tty;
+	struct virtqueue	*vtcport_invq;
+	struct virtqueue	*vtcport_outvq;
+};
+
+#define VTCON_PORT_MTX(_port)		&(_port)->vtcport_mtx
+#define VTCON_PORT_LOCK(_port)		mtx_lock(VTCON_PORT_MTX((_port)))
+#define VTCON_PORT_UNLOCK(_port)	mtx_unlock(VTCON_PORT_MTX((_port)))
+#define VTCON_PORT_LOCK_DESTROY(_port)	mtx_destroy(VTCON_PORT_MTX((_port)))
+#define VTCON_PORT_LOCK_ASSERT(_port) \
+    mtx_assert(VTCON_PORT_MTX((_port)), MA_OWNED)
+#define VTCON_PORT_LOCK_ASSERT_NOTOWNED(_port) \
+    mtx_assert(VTCON_PORT_MTX((_port)), MA_NOTOWNED)
+
+struct vtcon_softc {
+	device_t		 vtcon_dev;
+	struct mtx		 vtcon_mtx;
+	uint64_t		 vtcon_features;
+	uint32_t		 vtcon_flags;
+#define VTCON_FLAG_DETACHED	0x0001
+#define VTCON_FLAG_SIZE		0x0010
+#define VTCON_FLAG_MULTIPORT	0x0020
+
+	struct task		 vtcon_ctrl_task;
+	struct virtqueue	*vtcon_ctrl_rxvq;
+	struct virtqueue	*vtcon_ctrl_txvq;
+
+	uint32_t		 vtcon_max_ports;
+	TAILQ_HEAD(, vtcon_port)
+				 vtcon_ports;
+
+	/*
+	 * Ports can be added and removed during runtime, but we have
+	 * to allocate all the virtqueues during attach. This array is
+	 * indexed by the port ID.
+	 */
+	struct vtcon_port_extra {
+		struct vtcon_port	*port;
+		struct virtqueue	*invq;
+		struct virtqueue	*outvq;
+	}			*vtcon_portsx;
+};
+
+#define VTCON_MTX(_sc)		&(_sc)->vtcon_mtx
+#define VTCON_LOCK_INIT(_sc, _name) \
+				mtx_init(VTCON_MTX((_sc)), (_name), \
+				    "VirtIO Console Lock", MTX_DEF)
+#define VTCON_LOCK(_sc)		mtx_lock(VTCON_MTX((_sc)))
+#define VTCON_UNLOCK(_sc)	mtx_unlock(VTCON_MTX((_sc)))
+#define VTCON_LOCK_DESTROY(_sc)	mtx_destroy(VTCON_MTX((_sc)))
+#define VTCON_LOCK_ASSERT(_sc)	mtx_assert(VTCON_MTX((_sc)), MA_OWNED)
+#define VTCON_LOCK_ASSERT_NOTOWNED(_sc) \
+				mtx_assert(VTCON_MTX((_sc)), MA_NOTOWNED)
+
+#define VTCON_ASSERT_VALID_PORTID(_sc, _id)			\
+    KASSERT((_id) >= 0 && (_id) < (_sc)->vtcon_max_ports,	\
+        ("%s: port ID %d out of range", __func__, _id))
+
+#define VTCON_FEATURES  0
+
+static struct virtio_feature_desc vtcon_feature_desc[] = {
+	{ VIRTIO_CONSOLE_F_SIZE,	"ConsoleSize"	},
+	{ VIRTIO_CONSOLE_F_MULTIPORT,	"MultiplePorts"	},
+
+	{ 0, NULL }
+};
+
+static int	 vtcon_modevent(module_t, int, void *);
+
+static int	 vtcon_probe(device_t);
+static int	 vtcon_attach(device_t);
+static int	 vtcon_detach(device_t);
+static int	 vtcon_config_change(device_t);
+
+static void	 vtcon_negotiate_features(struct vtcon_softc *);
+static int	 vtcon_alloc_virtqueues(struct vtcon_softc *);
+static void	 vtcon_read_config(struct vtcon_softc *,
+		     struct virtio_console_config *);
+
+static void	 vtcon_determine_max_ports(struct vtcon_softc *,
+		     struct virtio_console_config *);
+static void	 vtcon_deinit_ports(struct vtcon_softc *);
+static void	 vtcon_stop(struct vtcon_softc *);
+
+static void	 vtcon_ctrl_rx_vq_intr(void *);
+static int	 vtcon_ctrl_enqueue_msg(struct vtcon_softc *,
+		     struct virtio_console_control *);
+static int	 vtcon_ctrl_add_msg(struct vtcon_softc *);
+static void	 vtcon_ctrl_readd_msg(struct vtcon_softc *,
+		     struct virtio_console_control *);
+static int	 vtcon_ctrl_populate(struct vtcon_softc *);
+static void	 vtcon_ctrl_send_msg(struct vtcon_softc *,
+		     struct virtio_console_control *control);
+static void	 vtcon_ctrl_send_event(struct vtcon_softc *, uint32_t,
+		     uint16_t, uint16_t);
+static int	 vtcon_ctrl_init(struct vtcon_softc *);
+static void	 vtcon_ctrl_drain(struct vtcon_softc *);
+static void	 vtcon_ctrl_deinit(struct vtcon_softc *);
+
+static void	 vtcon_ctrl_process_msg(struct vtcon_softc *,
+		     struct virtio_console_control *);
+static void	 vtcon_ctrl_task_cb(void *, int);
+
+static int	 vtcon_port_add_inbuf(struct vtcon_port *);
+static void	 vtcon_port_readd_inbuf(struct vtcon_port *, void *);
+static int	 vtcon_port_populate(struct vtcon_port *);
+static void	 vtcon_port_destroy(struct vtcon_port *);
+static int	 vtcon_port_create(struct vtcon_softc *, int,
+		     struct vtcon_port **);
+static void	 vtcon_port_drain_inbufs(struct vtcon_port *);
+static void	 vtcon_port_teardown(struct vtcon_port *, int);
+static void	 vtcon_port_change_size(struct vtcon_port *, uint16_t,
+		     uint16_t);
+static void	 vtcon_port_enable_intr(struct vtcon_port *);
+static void	 vtcon_port_disable_intr(struct vtcon_port *);
+static void	 vtcon_port_intr(struct vtcon_port *);
+static void	 vtcon_port_in_vq_intr(void *);
+static void	 vtcon_port_put(struct vtcon_port *, void *, int);
+static void	 vtcon_port_send_ctrl_msg(struct vtcon_port *, uint16_t,
+		     uint16_t);
+static struct vtcon_port *vtcon_port_lookup_by_id(struct vtcon_softc *, int);
+
+static int	 vtcon_tty_open(struct tty *);
+static void	 vtcon_tty_close(struct tty *);
+static void	 vtcon_tty_outwakeup(struct tty *);
+static void	 vtcon_tty_free(void *);
+
+static void	 vtcon_get_console_size(struct vtcon_softc *, uint16_t *,
+		     uint16_t *);
+
+static void	 vtcon_enable_interrupts(struct vtcon_softc *);
+static void	 vtcon_disable_interrupts(struct vtcon_softc *);
+
+static int	 vtcon_pending_free;
+
+static struct ttydevsw vtcon_tty_class = {
+	.tsw_flags	= 0,
+	.tsw_open	= vtcon_tty_open,
+	.tsw_close	= vtcon_tty_close,
+	.tsw_outwakeup	= vtcon_tty_outwakeup,
+	.tsw_free	= vtcon_tty_free,
+};
+
+static device_method_t vtcon_methods[] = {
+	/* Device methods. */
+	DEVMETHOD(device_probe,		vtcon_probe),
+	DEVMETHOD(device_attach,	vtcon_attach),
+	DEVMETHOD(device_detach,	vtcon_detach),
+
+	/* VirtIO methods. */
+	DEVMETHOD(virtio_config_change,	vtcon_config_change),
+
+	DEVMETHOD_END
+};
+
+static driver_t vtcon_driver = {
+	"vtcon",
+	vtcon_methods,
+	sizeof(struct vtcon_softc)
+};
+static devclass_t vtcon_devclass;
+
+DRIVER_MODULE(virtio_console, virtio_pci, vtcon_driver, vtcon_devclass,
+    vtcon_modevent, 0);
+MODULE_VERSION(virtio_console, 1);
+MODULE_DEPEND(virtio_console, virtio, 1, 1, 1);
+
+static int
+vtcon_modevent(module_t mod, int type, void *unused)
+{
+	int error;
+
+	switch (type) {
+	case MOD_LOAD:
+		error = 0;
+		break;
+	case MOD_QUIESCE:
+	case MOD_UNLOAD:
+		/* error = vtcon_pending_free != 0 ? EBUSY : 0; */
+		error = EOPNOTSUPP;
+		break;
+	case MOD_SHUTDOWN:
+		error = 0;
+		break;
+	default:
+		error = EOPNOTSUPP;
+		break;
+	}
+
+	return (error);
+}
+
+static int
+vtcon_probe(device_t dev)
+{
+
+	if (virtio_get_device_type(dev) != VIRTIO_ID_CONSOLE)
+		return (ENXIO);
+
+	device_set_desc(dev, "VirtIO Console Adapter");
+
+	return (BUS_PROBE_DEFAULT);
+}
+
+static int
+vtcon_attach(device_t dev)
+{
+	struct vtcon_softc *sc;
+	struct virtio_console_config concfg;
+	int error;
+
+	sc = device_get_softc(dev);
+	sc->vtcon_dev = dev;
+
+	VTCON_LOCK_INIT(sc, device_get_nameunit(dev));
+	TASK_INIT(&sc->vtcon_ctrl_task, 0, vtcon_ctrl_task_cb, sc);
+	TAILQ_INIT(&sc->vtcon_ports);
+
+	virtio_set_feature_desc(dev, vtcon_feature_desc);
+	vtcon_negotiate_features(sc);
+
+	if (virtio_with_feature(dev, VIRTIO_CONSOLE_F_SIZE))
+		sc->vtcon_flags |= VTCON_FLAG_SIZE;
+	if (virtio_with_feature(dev, VIRTIO_CONSOLE_F_MULTIPORT))
+		sc->vtcon_flags |= VTCON_FLAG_MULTIPORT;
+
+	vtcon_read_config(sc, &concfg);
+	vtcon_determine_max_ports(sc, &concfg);
+
+	error = vtcon_alloc_virtqueues(sc);
+	if (error) {
+		device_printf(dev, "cannot allocate virtqueues\n");
+		goto fail;
+	}
+
+	if (sc->vtcon_flags & VTCON_FLAG_MULTIPORT)
+		error = vtcon_ctrl_init(sc);
+	else
+		error = vtcon_port_create(sc, 0, NULL);
+	if (error)
+		goto fail;
+
+	error = virtio_setup_intr(dev, INTR_TYPE_TTY);
+	if (error) {
+		device_printf(dev, "cannot setup virtqueue interrupts\n");
+		goto fail;
+	}
+
+	vtcon_enable_interrupts(sc);
+
+	vtcon_ctrl_send_event(sc, VIRTIO_CONSOLE_BAD_ID,
+	    VIRTIO_CONSOLE_DEVICE_READY, 1);
+
+fail:
+	if (error)
+		vtcon_detach(dev);
+
+	return (error);
+}
+
+static int
+vtcon_detach(device_t dev)
+{
+	struct vtcon_softc *sc;
+
+	sc = device_get_softc(dev);
+
+	VTCON_LOCK(sc);
+	sc->vtcon_flags |= VTCON_FLAG_DETACHED;
+	if (device_is_attached(dev))
+		vtcon_stop(sc);
+	VTCON_UNLOCK(sc);
+
+	taskqueue_drain(taskqueue_thread, &sc->vtcon_ctrl_task);
+
+	if (sc->vtcon_flags & VTCON_FLAG_MULTIPORT)
+		vtcon_ctrl_deinit(sc);
+
+	vtcon_deinit_ports(sc);
+
+	VTCON_LOCK_DESTROY(sc);
+
+	return (0);
+}
+
+static int
+vtcon_config_change(device_t dev)
+{
+	struct vtcon_softc *sc;
+	struct vtcon_port *port;
+	uint16_t cols, rows;
+
+	sc = device_get_softc(dev);
+
+	/*
+	 * With the multiport feature, all configuration changes are
+	 * done through control virtqueue events.
+	 */
+	if (sc->vtcon_flags & VTCON_FLAG_MULTIPORT)
+		return (0);
+
+	if (sc->vtcon_flags & VTCON_FLAG_SIZE) {
+		/*
+		 * For now, assume the first (only) port is the 'console'.
+		 * Note QEMU does not implement this feature yet.
+		 */
+		VTCON_LOCK(sc);
+		if ((port = vtcon_port_lookup_by_id(sc, 0)) != NULL) {
+			vtcon_get_console_size(sc, &cols, &rows);
+			vtcon_port_change_size(port, cols, rows);
+		}
+		VTCON_UNLOCK(sc);
+	}
+
+	return (0);
+}
+
+static void
+vtcon_negotiate_features(struct vtcon_softc *sc)
+{
+	device_t dev;
+	uint64_t features;
+
+	dev = sc->vtcon_dev;
+	features = VTCON_FEATURES;
+
+	sc->vtcon_features = virtio_negotiate_features(dev, features);
+}
+
+#define VTCON_GET_CONFIG(_dev, _feature, _field, _cfg)			\
+	if (virtio_with_feature(_dev, _feature)) {			\
+		virtio_read_device_config(_dev,				\
+		    offsetof(struct virtio_console_config, _field),	\
+		    &(_cfg)->_field, sizeof((_cfg)->_field));		\
+	}
+
+static void
+vtcon_read_config(struct vtcon_softc *sc, struct virtio_console_config *concfg)
+{
+	device_t dev;
+
+	dev = sc->vtcon_dev;
+
+	bzero(concfg, sizeof(struct virtio_console_config));
+
+	/* Read the configuration if the feature was negotiated. */
+	VTCON_GET_CONFIG(dev, VIRTIO_CONSOLE_F_SIZE, cols, concfg);
+	VTCON_GET_CONFIG(dev, VIRTIO_CONSOLE_F_SIZE, rows, concfg);
+	VTCON_GET_CONFIG(dev, VIRTIO_CONSOLE_F_MULTIPORT, max_nr_ports, concfg);
+}
+
+#undef VTCON_GET_CONFIG
+
+static int
+vtcon_alloc_virtqueues(struct vtcon_softc *sc)
+{
+	device_t dev;
+	struct vq_alloc_info *info;
+	struct vtcon_port_extra *portx;
+	int i, idx, portidx, nvqs, error;
+
+	dev = sc->vtcon_dev;
+
+	sc->vtcon_portsx = malloc(sizeof(struct vtcon_port_extra) *
+	    sc->vtcon_max_ports, M_DEVBUF, M_NOWAIT | M_ZERO);
+	if (sc->vtcon_portsx == NULL)
+		return (ENOMEM);
+
+	nvqs = sc->vtcon_max_ports * 2;
+	if (sc->vtcon_flags & VTCON_FLAG_MULTIPORT)
+		nvqs += 2;
+
+	info = malloc(sizeof(struct vq_alloc_info) * nvqs, M_TEMP, M_NOWAIT);
+	if (info == NULL)
+		return (ENOMEM);
+
+	for (i = 0, idx = 0, portidx = 0; i < nvqs / 2; i++, idx+=2) {
+
+		if (i == 1) {
+			/* The control virtqueues are after the first port. */
+			VQ_ALLOC_INFO_INIT(&info[idx], 0,
+			    vtcon_ctrl_rx_vq_intr, sc, &sc->vtcon_ctrl_rxvq,
+			    "%s-control rx", device_get_nameunit(dev));
+			VQ_ALLOC_INFO_INIT(&info[idx+1], 0,
+			    NULL, sc, &sc->vtcon_ctrl_txvq,
+			    "%s-control tx", device_get_nameunit(dev));
+			continue;
+		}
+
+		portx = &sc->vtcon_portsx[portidx];
+
+		VQ_ALLOC_INFO_INIT(&info[idx], 0, vtcon_port_in_vq_intr,
+		    portx, &portx->invq, "%s-port%d in",
+		    device_get_nameunit(dev), portidx);
+		VQ_ALLOC_INFO_INIT(&info[idx+1], 0, NULL,
+		    NULL, &portx->outvq, "%s-port%d out",
+		    device_get_nameunit(dev), portidx);
+
+		portidx++;
+	}
+
+	error = virtio_alloc_virtqueues(dev, 0, nvqs, info);
+	free(info, M_TEMP);
+
+	return (error);
+}
+
+static void
+vtcon_determine_max_ports(struct vtcon_softc *sc,
+    struct virtio_console_config *concfg)
+{
+
+	if (sc->vtcon_flags & VTCON_FLAG_MULTIPORT) {
+		sc->vtcon_max_ports =
+		    min(concfg->max_nr_ports, VTCON_MAX_PORTS);
+		if (sc->vtcon_max_ports == 0)
+			sc->vtcon_max_ports = 1;
+	} else
+		sc->vtcon_max_ports = 1;
+}
+
+static void
+vtcon_deinit_ports(struct vtcon_softc *sc)
+{
+	struct vtcon_port *port, *tmp;
+
+	TAILQ_FOREACH_SAFE(port, &sc->vtcon_ports, vtcport_next, tmp) {
+		vtcon_port_teardown(port, 1);
+	}
+
+	if (sc->vtcon_portsx != NULL) {
+		free(sc->vtcon_portsx, M_DEVBUF);
+		sc->vtcon_portsx = NULL;
+	}
+}
+
+static void
+vtcon_stop(struct vtcon_softc *sc)
+{
+
+	vtcon_disable_interrupts(sc);
+	virtio_stop(sc->vtcon_dev);
+}
+
+static void
+vtcon_ctrl_rx_vq_intr(void *xsc)
+{
+	struct vtcon_softc *sc;
+
+	sc = xsc;
+
+	/*
+	 * Some events require us to potentially block, but it easier
+	 * to just defer all event handling to a seperate thread.
+	 */
+	taskqueue_enqueue(taskqueue_thread, &sc->vtcon_ctrl_task);
+}
+
+static int
+vtcon_ctrl_enqueue_msg(struct vtcon_softc *sc,
+    struct virtio_console_control *control)
+{
+	struct sglist_seg segs[1];
+	struct sglist sg;
+	struct virtqueue *vq;
+	int error __unused;
+
+	vq = sc->vtcon_ctrl_rxvq;
+
+	sglist_init(&sg, 1, segs);
+	error = sglist_append(&sg, control, sizeof(*control));
+	KASSERT(error == 0 && sg.sg_nseg == 1,
+	    ("%s: error %d adding control msg to sglist", __func__, error));
+
+	return (virtqueue_enqueue(vq, control, &sg, 0, 1));
+}
+
+static int
+vtcon_ctrl_add_msg(struct vtcon_softc *sc)
+{
+	struct virtio_console_control *control;
+	int error;
+
+	control = malloc(sizeof(*control), M_DEVBUF, M_ZERO | M_NOWAIT);
+	if (control == NULL)
+		return (ENOMEM);
+
+	error = vtcon_ctrl_enqueue_msg(sc, control);
+	if (error)
+		free(control, M_DEVBUF);
+
+	return (error);
+}
+
+static void
+vtcon_ctrl_readd_msg(struct vtcon_softc *sc,
+    struct virtio_console_control *control)
+{
+	int error __unused;
+
+	bzero(control, sizeof(*control));
+
+	error = vtcon_ctrl_enqueue_msg(sc, control);
+	KASSERT(error == 0,
+	    ("%s: cannot requeue control buffer %d", __func__, error));
+}
+
+static int
+vtcon_ctrl_populate(struct vtcon_softc *sc)
+{
+	struct virtqueue *vq;
+	int nbufs, error;
+
+	vq = sc->vtcon_ctrl_rxvq;
+	error = ENOSPC;
+
+	for (nbufs = 0; !virtqueue_full(vq); nbufs++) {
+		error = vtcon_ctrl_add_msg(sc);
+		if (error)
+			break;
+	}
+
+	if (nbufs > 0) {
+		virtqueue_notify(vq);
+		/*
+		 * EMSGSIZE signifies the virtqueue did not have enough
+		 * entries available to hold the last buf. This is not
+		 * an error.
+		 */
+		if (error == EMSGSIZE)
+			error = 0;
+	}
+
+	return (error);
+}
+
+static void
+vtcon_ctrl_send_msg(struct vtcon_softc *sc,
+    struct virtio_console_control *control)
+{
+	struct sglist_seg segs[1];
+	struct sglist sg;
+	struct virtqueue *vq;
+	int error __unused;
+
+	vq = sc->vtcon_ctrl_txvq;
+
+	sglist_init(&sg, 1, segs);
+	error = sglist_append(&sg, control, sizeof(*control));
+	KASSERT(error == 0 && sg.sg_nseg == 1,
+	    ("%s: error %d adding control msg to sglist", __func__, error));
+
+	KASSERT(virtqueue_empty(vq),
+	    ("%s: virtqueue is not emtpy", __func__));
+
+	if (virtqueue_enqueue(vq, control, &sg, 1, 0) == 0) {
+		virtqueue_notify(vq);
+		virtqueue_poll(vq, NULL);
+	}
+}
+
+static void
+vtcon_ctrl_send_event(struct vtcon_softc *sc, uint32_t portid, uint16_t event,
+    uint16_t value)
+{
+	struct virtio_console_control control;
+
+	if ((sc->vtcon_flags & VTCON_FLAG_MULTIPORT) == 0)
+		return;
+
+	control.id = portid;
+	control.event = event;
+	control.value = value;
+
+	vtcon_ctrl_send_msg(sc, &control);
+}
+
+static int
+vtcon_ctrl_init(struct vtcon_softc *sc)
+{
+	int error;
+
+	error = vtcon_ctrl_populate(sc);
+
+	return (error);
+}
+
+static void
+vtcon_ctrl_drain(struct vtcon_softc *sc)
+{
+	struct virtio_console_control *control;
+	struct virtqueue *vq;
+	int last;
+
+	vq = sc->vtcon_ctrl_rxvq;
+	last = 0;
+
+	if (vq == NULL)
+		return;
+
+	while ((control = virtqueue_drain(vq, &last)) != NULL)
+		free(control, M_DEVBUF);
+}
+
+static void
+vtcon_ctrl_deinit(struct vtcon_softc *sc)
+{
+
+	vtcon_ctrl_drain(sc);
+}
+
+static void
+vtcon_ctrl_port_add_event(struct vtcon_softc *sc, int id)
+{
+	device_t dev;
+	struct vtcon_port *port;
+	int error;
+
+	dev = sc->vtcon_dev;
+
+	if (vtcon_port_lookup_by_id(sc, id) != NULL) {
+		device_printf(dev, "%s: adding port %d, but already exists\n",
+		    __func__, id);
+		return;
+	}
+
+	error = vtcon_port_create(sc, id, &port);
+	if (error) {
+		device_printf(dev, "%s: cannot create port %d: %d\n",
+		    __func__, id, error);
+		return;
+	}
+
+	vtcon_port_send_ctrl_msg(port, VIRTIO_CONSOLE_PORT_READY, 1);
+}
+
+static void
+vtcon_ctrl_port_remove_event(struct vtcon_softc *sc, int id)
+{
+	device_t dev;
+	struct vtcon_port *port;
+
+	dev = sc->vtcon_dev;
+
+	port = vtcon_port_lookup_by_id(sc, id);
+	if (port == NULL) {
+		device_printf(dev, "%s: remove port %d, but does not exist\n",
+		    __func__, id);
+		return;
+	}
+
+	vtcon_port_teardown(port, 1);
+}
+
+static void
+vtcon_ctrl_port_open_event(struct vtcon_softc *sc, int id)
+{
+	device_t dev;
+	struct vtcon_port *port;
+
+	dev = sc->vtcon_dev;
+
+	port = vtcon_port_lookup_by_id(sc, id);
+	if (port == NULL) {
+		device_printf(dev, "%s: remove port %d, but does not exist\n",
+		    __func__, id);
+		return;
+	}
+
+	vtcon_port_enable_intr(port);
+}
+
+static void
+vtcon_ctrl_process_msg(struct vtcon_softc *sc,
+    struct virtio_console_control *control)
+{
+	device_t dev;
+	int id;
+
+	dev = sc->vtcon_dev;
+	id = control->id;
+
+	if (id < 0 || id >= sc->vtcon_max_ports) {
+		device_printf(dev, "%s: invalid port ID %d\n", __func__, id);
+		return;
+	}
+
+	switch (control->event) {
+	case VIRTIO_CONSOLE_PORT_ADD:
+		vtcon_ctrl_port_add_event(sc, id);
+		break;
+
+	case VIRTIO_CONSOLE_PORT_REMOVE:
+		vtcon_ctrl_port_remove_event(sc, id);
+		break;
+
+	case VIRTIO_CONSOLE_CONSOLE_PORT:
+		device_printf(dev, "%s: port %d console event\n", __func__, id);
+		break;
+
+	case VIRTIO_CONSOLE_RESIZE:
+		break;
+
+	case VIRTIO_CONSOLE_PORT_OPEN:
+		vtcon_ctrl_port_open_event(sc, id);
+		break;
+
+	case VIRTIO_CONSOLE_PORT_NAME:
+		break;
+	}
+}
+
+static void
+vtcon_ctrl_task_cb(void *xsc, int pending)
+{
+	struct vtcon_softc *sc;
+	struct virtqueue *vq;
+	struct virtio_console_control *control;
+
+	sc = xsc;
+	vq = sc->vtcon_ctrl_rxvq;
+
+	VTCON_LOCK(sc);
+	while ((sc->vtcon_flags & VTCON_FLAG_DETACHED) == 0) {
+		control = virtqueue_dequeue(vq, NULL);
+		if (control == NULL)
+			break;
+
+		VTCON_UNLOCK(sc);
+		vtcon_ctrl_process_msg(sc, control);
+		VTCON_LOCK(sc);
+		vtcon_ctrl_readd_msg(sc, control);
+	}
+	VTCON_UNLOCK(sc);
+
+	if (virtqueue_enable_intr(vq) != 0)
+		taskqueue_enqueue(taskqueue_thread, &sc->vtcon_ctrl_task);
+}
+
+static int
+vtcon_port_enqueue_inbuf(struct vtcon_port *port, void *buf, size_t len)
+{
+	struct sglist_seg segs[1];
+	struct sglist sg;
+	struct virtqueue *vq;
+	int error;
+
+	vq = port->vtcport_invq;
+
+	sglist_init(&sg, 1, segs);
+	error = sglist_append(&sg, buf, len);
+	KASSERT(error == 0 && sg.sg_nseg == 1,
+	    ("%s: error %d adding buffer to sglist", __func__, error));
+
+	return (virtqueue_enqueue(vq, buf, &sg, 0, 1));
+}
+
+static int
+vtcon_port_add_inbuf(struct vtcon_port *port)
+{
+	void *buf;
+	int error;
+
+	buf = malloc(VTCON_BULK_BUFSZ, M_DEVBUF, M_ZERO | M_NOWAIT);
+	if (buf == NULL)
+		return (ENOMEM);
+
+	error = vtcon_port_enqueue_inbuf(port, buf, VTCON_BULK_BUFSZ);
+	if (error)
+		free(buf, M_DEVBUF);
+
+	return (error);
+}
+
+static void
+vtcon_port_readd_inbuf(struct vtcon_port *port, void *buf)
+{
+	int error __unused;
+
+	error = vtcon_port_enqueue_inbuf(port, buf, VTCON_BULK_BUFSZ);
+	KASSERT(error == 0,
+	    ("%s: cannot requeue input buffer %d", __func__, error));
+}
+
+static int
+vtcon_port_populate(struct vtcon_port *port)
+{
+	struct virtqueue *vq;
+	int nbufs, error;
+
+	vq = port->vtcport_invq;
+	error = ENOSPC;
+
+	for (nbufs = 0; !virtqueue_full(vq); nbufs++) {
+		error = vtcon_port_add_inbuf(port);
+		if (error)
+			break;
+	}
+
+	if (nbufs > 0) {
+		virtqueue_notify(vq);
+		/*
+		 * EMSGSIZE signifies the virtqueue did not have enough
+		 * entries available to hold the last buf. This is not
+		 * an error.
+		 */
+		if (error == EMSGSIZE)
+			error = 0;
+	}
+
+	return (error);
+}
+
+static void
+vtcon_port_destroy(struct vtcon_port *port)
+{
+
+	port->vtcport_sc = NULL;
+	port->vtcport_id = -1;
+	mtx_destroy(&port->vtcport_mtx);
+	free(port, M_DEVBUF);
+}
+
+static int
+vtcon_port_create(struct vtcon_softc *sc, int id, struct vtcon_port **portp)
+{
+	device_t dev;
+	struct vtcon_port_extra *portx;
+	struct vtcon_port *port;
+	int error;
+
+	MPASS(id < sc->vtcon_max_ports);
+	dev = sc->vtcon_dev;
+	portx = &sc->vtcon_portsx[id];
+
+	if (portx->port != NULL)
+		return (EEXIST);
+
+	port = malloc(sizeof(struct vtcon_port), M_DEVBUF, M_NOWAIT | M_ZERO);
+	if (port == NULL)
+		return (ENOMEM);
+
+	port->vtcport_sc = sc;
+	port->vtcport_id = id;
+	mtx_init(&port->vtcport_mtx, "vtpmtx", NULL, MTX_DEF);
+	port->vtcport_tty = tty_alloc_mutex(&vtcon_tty_class, port,
+	    &port->vtcport_mtx);
+
+	/*
+	 * Assign virtqueues saved from attach. To be safe, clear the
+	 * virtqueue too.
+	 */
+	port->vtcport_invq = portx->invq;
+	port->vtcport_outvq = portx->outvq;
+	vtcon_port_drain_inbufs(port);
+
+	error = vtcon_port_populate(port);
+	if (error) {
+		vtcon_port_teardown(port, 0);
+		return (error);
+	}
+
+	tty_makedev(port->vtcport_tty, NULL, "%s%r.%r", VTCON_TTY_PREFIX,
+	    device_get_unit(dev), id);
+
+	VTCON_LOCK(sc);
+	portx->port = port;
+	TAILQ_INSERT_TAIL(&sc->vtcon_ports, port, vtcport_next);
+	VTCON_UNLOCK(sc);
+
+	if (portp != NULL)
+		*portp = port;
+
+	return (0);
+}
+
+static void
+vtcon_port_drain_inbufs(struct vtcon_port *port)
+{
+	struct virtqueue *vq;
+	void *buf;
+	int last;
+
+	vq = port->vtcport_invq;
+	last = 0;
+
+	if (vq == NULL)
+		return;
+
+	while ((buf = virtqueue_drain(vq, &last)) != NULL)
+		free(buf, M_DEVBUF);
+}
+
+static void
+vtcon_port_teardown(struct vtcon_port *port, int ontailq)
+{
+	struct vtcon_softc *sc;
+	struct vtcon_port_extra *portx;
+	struct tty *tp;
+	int id;
+
+	sc = port->vtcport_sc;
+	id = port->vtcport_id;
+	tp = port->vtcport_tty;
+
+	VTCON_ASSERT_VALID_PORTID(sc, id);
+	portx = &sc->vtcon_portsx[id];
+
+	VTCON_PORT_LOCK(port);
+	vtcon_port_drain_inbufs(port);
+	VTCON_PORT_UNLOCK(port);
+
+	VTCON_LOCK(sc);
+	KASSERT(portx->port == NULL || portx->port == port,
+	    ("%s: port %d mismatch %p/%p", __func__, id, portx->port, port));
+	portx->port = NULL;
+	if (ontailq != 0)
+		TAILQ_REMOVE(&sc->vtcon_ports, port, vtcport_next);
+	VTCON_UNLOCK(sc);
+
+	if (tp != NULL) {
+		port->vtcport_tty = NULL;
+		atomic_add_int(&vtcon_pending_free, 1);
+
+		VTCON_PORT_LOCK(port);
+		tty_rel_gone(tp);
+	} else
+		vtcon_port_destroy(port);
+}

*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201401052007.s05K7CJJ053098>