mirror of
https://github.com/projectacrn/acrn-hypervisor.git
synced 2025-08-17 15:51:34 +00:00
The current code would cause infinite calls to vq_getchain() because that: - error check of vq_getchain() return value is missing. - virtqueue misunderstand that there are avaliable descripters even though the idx of avail ring is invalid. This patch fixes it by checking validity of the return of vq_getchain() and jump out of the loop for invalid return value. This patch alse add validity check in judgment of avaliable descriptor, and check if the diff between idx of avail ring and the last idx is greater than size of this queue. Tracked-On: #7038 Signed-off-by: Wen Qian <qian.wen@intel.com> Signed-off-by: Li Fei <fei1.li@intel.com> Acked-by: Wang Yu1 <yu1.wang@intel.com>
1214 lines
29 KiB
C
1214 lines
29 KiB
C
/*-
|
|
* Copyright (c) 2016 iXsystems Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This software was developed by Jakub Klama <jceel@FreeBSD.org>
|
|
* under sponsorship from iXsystems Inc.
|
|
*
|
|
* 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, this list of conditions and the following disclaimer
|
|
* in this position and unchanged.
|
|
* 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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 <sys/uio.h>
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/un.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <pthread.h>
|
|
#include <termios.h>
|
|
#include <limits.h>
|
|
|
|
#include "dm.h"
|
|
#include "pci_core.h"
|
|
#include "virtio.h"
|
|
#include "mevent.h"
|
|
|
|
#define VIRTIO_CONSOLE_RINGSZ 64
|
|
#define VIRTIO_CONSOLE_MAXPORTS 16
|
|
#define VIRTIO_CONSOLE_MAXQ (VIRTIO_CONSOLE_MAXPORTS * 2 + 2)
|
|
|
|
#define VIRTIO_CONSOLE_DEVICE_READY 0
|
|
#define VIRTIO_CONSOLE_DEVICE_ADD 1
|
|
#define VIRTIO_CONSOLE_DEVICE_REMOVE 2
|
|
#define VIRTIO_CONSOLE_PORT_READY 3
|
|
#define VIRTIO_CONSOLE_CONSOLE_PORT 4
|
|
#define VIRTIO_CONSOLE_CONSOLE_RESIZE 5
|
|
#define VIRTIO_CONSOLE_PORT_OPEN 6
|
|
#define VIRTIO_CONSOLE_PORT_NAME 7
|
|
|
|
#define VIRTIO_CONSOLE_F_SIZE 0
|
|
#define VIRTIO_CONSOLE_F_MULTIPORT 1
|
|
#define VIRTIO_CONSOLE_F_EMERG_WRITE 2
|
|
#define VIRTIO_CONSOLE_S_HOSTCAPS \
|
|
(VIRTIO_CONSOLE_F_SIZE | \
|
|
VIRTIO_CONSOLE_F_MULTIPORT | \
|
|
VIRTIO_CONSOLE_F_EMERG_WRITE)
|
|
|
|
static int virtio_console_debug;
|
|
#define DPRINTF(params) do { \
|
|
if (virtio_console_debug) \
|
|
pr_dbg params; \
|
|
} while (0)
|
|
#define WPRINTF(params) (pr_err params)
|
|
|
|
struct virtio_console;
|
|
struct virtio_console_port;
|
|
struct virtio_console_config;
|
|
typedef void (virtio_console_cb_t)(struct virtio_console_port *, void *,
|
|
struct iovec *, int);
|
|
|
|
enum virtio_console_be_type {
|
|
VIRTIO_CONSOLE_BE_STDIO = 0,
|
|
VIRTIO_CONSOLE_BE_TTY,
|
|
VIRTIO_CONSOLE_BE_PTY,
|
|
VIRTIO_CONSOLE_BE_FILE,
|
|
VIRTIO_CONSOLE_BE_SOCKET,
|
|
VIRTIO_CONSOLE_BE_MAX,
|
|
VIRTIO_CONSOLE_BE_INVALID = VIRTIO_CONSOLE_BE_MAX
|
|
};
|
|
|
|
struct virtio_console_port {
|
|
struct virtio_console *console;
|
|
int id;
|
|
const char *name;
|
|
bool enabled;
|
|
bool is_console;
|
|
bool rx_ready;
|
|
bool open;
|
|
int rxq;
|
|
int txq;
|
|
void *arg;
|
|
virtio_console_cb_t *cb;
|
|
};
|
|
|
|
struct virtio_console_backend {
|
|
struct virtio_console_port *port;
|
|
struct mevent *evp;
|
|
struct mevent *conn_evp;
|
|
int fd;
|
|
int server_fd;
|
|
bool open;
|
|
enum virtio_console_be_type be_type;
|
|
int pts_fd; /* only valid for PTY */
|
|
const char *portpath;
|
|
const char *socket_type;
|
|
};
|
|
|
|
struct virtio_console {
|
|
struct virtio_base base;
|
|
struct virtio_vq_info queues[VIRTIO_CONSOLE_MAXQ];
|
|
pthread_mutex_t mtx;
|
|
uint64_t cfg;
|
|
uint64_t features;
|
|
int nports;
|
|
bool ready;
|
|
struct virtio_console_port control_port;
|
|
struct virtio_console_port ports[VIRTIO_CONSOLE_MAXPORTS];
|
|
struct virtio_console_config *config;
|
|
int ref_count;
|
|
};
|
|
|
|
struct virtio_console_config {
|
|
uint16_t cols;
|
|
uint16_t rows;
|
|
uint32_t max_nr_ports;
|
|
uint32_t emerg_wr;
|
|
} __attribute__((packed));
|
|
|
|
struct virtio_console_control {
|
|
uint32_t id;
|
|
uint16_t event;
|
|
uint16_t value;
|
|
} __attribute__((packed));
|
|
|
|
struct virtio_console_console_resize {
|
|
uint16_t cols;
|
|
uint16_t rows;
|
|
} __attribute__((packed));
|
|
|
|
static void virtio_console_reset(void *);
|
|
static void virtio_console_notify_rx(void *, struct virtio_vq_info *);
|
|
static void virtio_console_notify_tx(void *, struct virtio_vq_info *);
|
|
static int virtio_console_cfgread(void *, int, int, uint32_t *);
|
|
static void virtio_console_neg_features(void *, uint64_t);
|
|
static void virtio_console_control_send(struct virtio_console *,
|
|
struct virtio_console_control *, const void *, size_t);
|
|
static void virtio_console_announce_port(struct virtio_console_port *);
|
|
static void virtio_console_open_port(struct virtio_console_port *, bool);
|
|
static void virtio_console_teardown_backend(void *);
|
|
|
|
static struct virtio_ops virtio_console_ops = {
|
|
"vtcon", /* our name */
|
|
VIRTIO_CONSOLE_MAXQ, /* we support VTCON_MAXQ virtqueues */
|
|
sizeof(struct virtio_console_config), /* config reg size */
|
|
virtio_console_reset, /* reset */
|
|
NULL, /* device-wide qnotify */
|
|
virtio_console_cfgread, /* read virtio config */
|
|
NULL, /* write virtio config */
|
|
virtio_console_neg_features, /* apply negotiated features */
|
|
NULL, /* called on guest set status */
|
|
};
|
|
|
|
static const char *virtio_console_be_table[VIRTIO_CONSOLE_BE_MAX] = {
|
|
[VIRTIO_CONSOLE_BE_STDIO] = "stdio",
|
|
[VIRTIO_CONSOLE_BE_TTY] = "tty",
|
|
[VIRTIO_CONSOLE_BE_PTY] = "pty",
|
|
[VIRTIO_CONSOLE_BE_FILE] = "file",
|
|
[VIRTIO_CONSOLE_BE_SOCKET] = "socket"
|
|
};
|
|
|
|
static struct termios virtio_console_saved_tio;
|
|
static int virtio_console_saved_flags;
|
|
|
|
static void
|
|
virtio_console_reset(void *vdev)
|
|
{
|
|
struct virtio_console *console;
|
|
|
|
console = vdev;
|
|
|
|
DPRINTF(("vtcon: device reset requested!\n"));
|
|
virtio_reset_dev(&console->base);
|
|
}
|
|
|
|
static void
|
|
virtio_console_neg_features(void *vdev, uint64_t negotiated_features)
|
|
{
|
|
struct virtio_console *console = vdev;
|
|
|
|
console->features = negotiated_features;
|
|
}
|
|
|
|
static int
|
|
virtio_console_cfgread(void *vdev, int offset, int size, uint32_t *retval)
|
|
{
|
|
struct virtio_console *console = vdev;
|
|
void *ptr;
|
|
|
|
ptr = (uint8_t *)console->config + offset;
|
|
memcpy(retval, ptr, size);
|
|
return 0;
|
|
}
|
|
|
|
static inline struct virtio_console_port *
|
|
virtio_console_vq_to_port(struct virtio_console *console,
|
|
struct virtio_vq_info *vq)
|
|
{
|
|
uint16_t num = vq->num;
|
|
|
|
if (num == 0 || num == 1)
|
|
return &console->ports[0];
|
|
|
|
if (num == 2 || num == 3)
|
|
return &console->control_port;
|
|
|
|
return &console->ports[(num / 2) - 1];
|
|
}
|
|
|
|
static inline struct virtio_vq_info *
|
|
virtio_console_port_to_vq(struct virtio_console_port *port, bool tx_queue)
|
|
{
|
|
int qnum;
|
|
|
|
qnum = tx_queue ? port->txq : port->rxq;
|
|
return &port->console->queues[qnum];
|
|
}
|
|
|
|
static struct virtio_console_port *
|
|
virtio_console_add_port(struct virtio_console *console, const char *name,
|
|
virtio_console_cb_t *cb, void *arg, bool is_console)
|
|
{
|
|
struct virtio_console_port *port;
|
|
|
|
if (console->nports == VIRTIO_CONSOLE_MAXPORTS) {
|
|
errno = EBUSY;
|
|
return NULL;
|
|
}
|
|
|
|
port = &console->ports[console->nports++];
|
|
port->id = console->nports - 1;
|
|
port->console = console;
|
|
port->name = name;
|
|
port->cb = cb;
|
|
port->arg = arg;
|
|
port->is_console = is_console;
|
|
|
|
if (port->id == 0) {
|
|
/* port0 */
|
|
port->txq = 0;
|
|
port->rxq = 1;
|
|
} else {
|
|
port->txq = console->nports * 2;
|
|
port->rxq = port->txq + 1;
|
|
}
|
|
|
|
port->enabled = true;
|
|
return port;
|
|
}
|
|
|
|
static void
|
|
virtio_console_control_tx(struct virtio_console_port *port, void *arg,
|
|
struct iovec *iov, int niov __attribute__((unused)))
|
|
{
|
|
struct virtio_console *console;
|
|
struct virtio_console_port *tmp;
|
|
struct virtio_console_control resp, *ctrl;
|
|
int i;
|
|
|
|
console = port->console;
|
|
ctrl = (struct virtio_console_control *)iov->iov_base;
|
|
|
|
if ((console == NULL) || (ctrl == NULL))
|
|
return;
|
|
|
|
switch (ctrl->event) {
|
|
case VIRTIO_CONSOLE_DEVICE_READY:
|
|
console->ready = true;
|
|
/* set port ready events for registered ports */
|
|
for (i = 0; i < VIRTIO_CONSOLE_MAXPORTS; i++) {
|
|
tmp = &console->ports[i];
|
|
if (tmp->enabled)
|
|
virtio_console_announce_port(tmp);
|
|
|
|
if (tmp->open)
|
|
virtio_console_open_port(tmp, true);
|
|
}
|
|
break;
|
|
|
|
case VIRTIO_CONSOLE_PORT_READY:
|
|
if (ctrl->id >= console->nports) {
|
|
WPRINTF(("VTCONSOLE_PORT_READY for unknown port %d\n",
|
|
ctrl->id));
|
|
return;
|
|
}
|
|
|
|
tmp = &console->ports[ctrl->id];
|
|
if (tmp->is_console) {
|
|
resp.event = VIRTIO_CONSOLE_CONSOLE_PORT;
|
|
resp.id = ctrl->id;
|
|
resp.value = 1;
|
|
virtio_console_control_send(console, &resp, NULL, 0);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
virtio_console_announce_port(struct virtio_console_port *port)
|
|
{
|
|
struct virtio_console_control event;
|
|
|
|
event.id = port->id;
|
|
event.event = VIRTIO_CONSOLE_DEVICE_ADD;
|
|
event.value = 1;
|
|
virtio_console_control_send(port->console, &event, NULL, 0);
|
|
|
|
event.event = VIRTIO_CONSOLE_PORT_NAME;
|
|
virtio_console_control_send(port->console, &event, port->name,
|
|
strnlen(port->name, NAME_MAX));
|
|
}
|
|
|
|
static void
|
|
virtio_console_open_port(struct virtio_console_port *port, bool open)
|
|
{
|
|
struct virtio_console_control event;
|
|
|
|
if (!port->console->ready) {
|
|
port->open = true;
|
|
return;
|
|
}
|
|
|
|
event.id = port->id;
|
|
event.event = VIRTIO_CONSOLE_PORT_OPEN;
|
|
event.value = (int)open;
|
|
virtio_console_control_send(port->console, &event, NULL, 0);
|
|
}
|
|
|
|
static void
|
|
virtio_console_control_send(struct virtio_console *console,
|
|
struct virtio_console_control *ctrl,
|
|
const void *payload, size_t len)
|
|
{
|
|
struct virtio_vq_info *vq;
|
|
struct iovec iov;
|
|
uint16_t idx;
|
|
int n;
|
|
|
|
vq = virtio_console_port_to_vq(&console->control_port, true);
|
|
|
|
if (!vq_has_descs(vq))
|
|
return;
|
|
|
|
n = vq_getchain(vq, &idx, &iov, 1, NULL);
|
|
if (n < 1) {
|
|
WPRINTF(("vtcon: control_send vq_getchain error %d\n", n));
|
|
return;
|
|
}
|
|
|
|
memcpy(iov.iov_base, ctrl, sizeof(struct virtio_console_control));
|
|
if (payload != NULL && len > 0)
|
|
memcpy(iov.iov_base + sizeof(struct virtio_console_control),
|
|
payload, len);
|
|
|
|
vq_relchain(vq, idx, sizeof(struct virtio_console_control) + len);
|
|
vq_endchains(vq, 1);
|
|
}
|
|
|
|
static void
|
|
virtio_console_notify_tx(void *vdev, struct virtio_vq_info *vq)
|
|
{
|
|
struct virtio_console *console;
|
|
struct virtio_console_port *port;
|
|
struct iovec iov[1];
|
|
uint16_t idx;
|
|
uint16_t flags[8];
|
|
|
|
console = vdev;
|
|
port = virtio_console_vq_to_port(console, vq);
|
|
|
|
while (vq_has_descs(vq)) {
|
|
if (vq_getchain(vq, &idx, iov, 1, flags) < 1) {
|
|
pr_err("%s: fail to getchain!\n", __func__);
|
|
break;
|
|
}
|
|
if ((port != NULL) && (port->cb != NULL))
|
|
port->cb(port, port->arg, iov, 1);
|
|
|
|
/*
|
|
* Release this chain and handle more
|
|
*/
|
|
vq_relchain(vq, idx, 0);
|
|
}
|
|
vq_endchains(vq, 1); /* Generate interrupt if appropriate. */
|
|
}
|
|
|
|
static void
|
|
virtio_console_notify_rx(void *vdev, struct virtio_vq_info *vq)
|
|
{
|
|
struct virtio_console *console;
|
|
struct virtio_console_port *port;
|
|
|
|
console = vdev;
|
|
port = virtio_console_vq_to_port(console, vq);
|
|
|
|
if (!port->rx_ready) {
|
|
port->rx_ready = 1;
|
|
if (vq_has_descs(vq)) {
|
|
vq->used->flags |= VRING_USED_F_NO_NOTIFY;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
virtio_console_reset_backend(struct virtio_console_backend *be)
|
|
{
|
|
if (!be)
|
|
return;
|
|
|
|
if (be->evp)
|
|
mevent_disable(be->evp);
|
|
if (be->fd != STDIN_FILENO)
|
|
close(be->fd);
|
|
be->fd = -1;
|
|
be->open = false;
|
|
}
|
|
|
|
static void
|
|
virtio_console_socket_clear(struct virtio_console_backend *be)
|
|
{
|
|
if (be->conn_evp) {
|
|
mevent_delete(be->conn_evp);
|
|
be->conn_evp = NULL;
|
|
}
|
|
if (be->fd != -1) {
|
|
close(be->fd);
|
|
be->fd = -1;
|
|
}
|
|
}
|
|
|
|
static void
|
|
virtio_console_backend_read(int fd __attribute__((unused)),
|
|
enum ev_type t __attribute__((unused)),
|
|
void *arg)
|
|
{
|
|
struct virtio_console_port *port;
|
|
struct virtio_console_backend *be = arg;
|
|
struct virtio_vq_info *vq;
|
|
struct iovec iov;
|
|
static char dummybuf[2048];
|
|
int len, n;
|
|
uint16_t idx;
|
|
|
|
port = be->port;
|
|
vq = virtio_console_port_to_vq(port, true);
|
|
|
|
if (!be->open || !port->rx_ready || !vq_ring_ready(vq)) {
|
|
len = read(be->fd, dummybuf, sizeof(dummybuf));
|
|
if (len == 0)
|
|
goto close;
|
|
return;
|
|
}
|
|
|
|
if (!vq_has_descs(vq)) {
|
|
len = read(be->fd, dummybuf, sizeof(dummybuf));
|
|
vq_endchains(vq, 1);
|
|
if (len == 0)
|
|
goto close;
|
|
return;
|
|
}
|
|
|
|
do {
|
|
n = vq_getchain(vq, &idx, &iov, 1, NULL);
|
|
if (n < 1){
|
|
pr_err("%s: fail to getchain!\n", __func__);
|
|
break;
|
|
}
|
|
len = readv(be->fd, &iov, n);
|
|
if (len <= 0) {
|
|
vq_retchain(vq);
|
|
vq_endchains(vq, 0);
|
|
|
|
/* no data available */
|
|
if (len == -1 && errno == EAGAIN)
|
|
return;
|
|
|
|
/* when client User VM reboot or shutdown,
|
|
* be->fd will be closed, then the return
|
|
* value of readv function will be 0 */
|
|
if (len == 0 || errno == ECONNRESET)
|
|
goto clear;
|
|
/* any other errors */
|
|
goto close;
|
|
}
|
|
|
|
vq_relchain(vq, idx, len);
|
|
} while (vq_has_descs(vq));
|
|
|
|
vq_endchains(vq, 1);
|
|
return;
|
|
|
|
close:
|
|
virtio_console_reset_backend(be);
|
|
WPRINTF(("vtcon: be read failed and close! len = %d, errno = %d\n",
|
|
len, errno));
|
|
clear:
|
|
if (be->be_type == VIRTIO_CONSOLE_BE_SOCKET && (be->socket_type == NULL
|
|
|| !strcmp(be->socket_type,"server"))) {
|
|
virtio_console_socket_clear(be);
|
|
} else if (be->be_type == VIRTIO_CONSOLE_BE_SOCKET
|
|
&& !strcmp(be->socket_type,"client")) {
|
|
virtio_console_reset_backend(be);
|
|
WPRINTF(("vtcon: be read failed and close! len = %d, errno = %d\n",
|
|
len, errno));
|
|
}
|
|
}
|
|
|
|
static void
|
|
virtio_console_backend_write(struct virtio_console_port *port, void *arg,
|
|
struct iovec *iov, int niov)
|
|
{
|
|
struct virtio_console_backend *be;
|
|
int ret;
|
|
|
|
be = arg;
|
|
|
|
if (be->fd == -1)
|
|
return;
|
|
|
|
ret = writev(be->fd, iov, niov);
|
|
if (ret <= 0) {
|
|
/* Case 1:backend cannot receive more data. For example when pts is
|
|
* not connected to any client, its tty buffer will become full.
|
|
* In this case we just drop data from guest hvc console.
|
|
*
|
|
* Case 2: Backend connection not yet setup. For example, when
|
|
* virtio-console is used as console port with socket backend, guest
|
|
* kernel tries to hook it up with hvc console and sets it up. It
|
|
* doesn't check if a client is connected and can result in ENOTCONN
|
|
* with virtio-console backend being reset. This will prevent
|
|
* client connection at a later point. To avoid this, ignore
|
|
* ENOTCONN error.
|
|
*
|
|
* PS: For Kata, the runtime first launches VM and then proxy which
|
|
* acts as a client connects to this socket.
|
|
*/
|
|
if (ret == -1 && (errno == EAGAIN || errno == ENOTCONN))
|
|
return;
|
|
|
|
if (ret == -1 && errno == EBADF) {
|
|
if (be->be_type == VIRTIO_CONSOLE_BE_SOCKET && (be->socket_type == NULL
|
|
|| !strcmp(be->socket_type,"server"))) {
|
|
virtio_console_socket_clear(be);
|
|
return;
|
|
}
|
|
}
|
|
virtio_console_reset_backend(be);
|
|
WPRINTF(("vtcon: be write failed! errno = %d\n", errno));
|
|
}
|
|
}
|
|
|
|
static void
|
|
virtio_console_restore_stdio(void)
|
|
{
|
|
tcsetattr(STDIN_FILENO, TCSANOW, &virtio_console_saved_tio);
|
|
fcntl(STDIN_FILENO, F_SETFL, virtio_console_saved_flags);
|
|
stdio_in_use = false;
|
|
}
|
|
|
|
static bool
|
|
virtio_console_backend_can_read(enum virtio_console_be_type be_type)
|
|
{
|
|
return (be_type == VIRTIO_CONSOLE_BE_FILE) ? false : true;
|
|
}
|
|
|
|
static int
|
|
virtio_console_open_backend(const char *path,
|
|
enum virtio_console_be_type be_type)
|
|
{
|
|
int fd = -1;
|
|
|
|
switch (be_type) {
|
|
case VIRTIO_CONSOLE_BE_PTY:
|
|
fd = posix_openpt(O_RDWR | O_NOCTTY);
|
|
if (fd == -1)
|
|
WPRINTF(("vtcon: posix_openpt failed, errno = %d\n",
|
|
errno));
|
|
else if (grantpt(fd) == -1 || unlockpt(fd) == -1) {
|
|
WPRINTF(("vtcon: grant/unlock failed, errno = %d\n",
|
|
errno));
|
|
close(fd);
|
|
fd = -1;
|
|
}
|
|
break;
|
|
case VIRTIO_CONSOLE_BE_STDIO:
|
|
if (stdio_in_use) {
|
|
WPRINTF(("vtcon: stdio is used by other device\n"));
|
|
break;
|
|
}
|
|
fd = STDIN_FILENO;
|
|
stdio_in_use = true;
|
|
break;
|
|
case VIRTIO_CONSOLE_BE_TTY:
|
|
fd = open(path, O_RDWR | O_NONBLOCK);
|
|
if (fd < 0)
|
|
WPRINTF(("vtcon: open failed: %s\n", path));
|
|
else if (!isatty(fd)) {
|
|
WPRINTF(("vtcon: not a tty: %s\n", path));
|
|
close(fd);
|
|
fd = -1;
|
|
}
|
|
break;
|
|
case VIRTIO_CONSOLE_BE_FILE:
|
|
fd = open(path, O_WRONLY|O_CREAT|O_APPEND|O_NONBLOCK, 0666);
|
|
if (fd < 0)
|
|
WPRINTF(("vtcon: open failed: %s\n", path));
|
|
break;
|
|
case VIRTIO_CONSOLE_BE_SOCKET:
|
|
fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
if (fd < 0)
|
|
WPRINTF(("vtcon: socket open failed \n"));
|
|
break;
|
|
default:
|
|
WPRINTF(("not supported backend %d!\n", be_type));
|
|
}
|
|
|
|
return fd;
|
|
}
|
|
|
|
static int
|
|
make_socket_non_blocking(int fd)
|
|
{
|
|
int flags, s;
|
|
|
|
flags = fcntl(fd, F_GETFL, 0);
|
|
if (flags == -1) {
|
|
WPRINTF(("fcntl get failed =%d\n", errno));
|
|
return -1;
|
|
}
|
|
|
|
flags |= O_NONBLOCK;
|
|
s = fcntl(fd, F_SETFL, flags);
|
|
if (s == -1) {
|
|
WPRINTF(("fcntl set failed =%d\n", errno));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
virtio_console_accept_new_connection(int fd __attribute__((unused)),
|
|
enum ev_type t __attribute__((unused)), void *arg)
|
|
{
|
|
|
|
int accepted_fd;
|
|
uint32_t len;
|
|
struct sockaddr_un addr;
|
|
struct virtio_console_backend *be = arg;
|
|
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sun_family = AF_UNIX;
|
|
strncpy(addr.sun_path, be->portpath, sizeof(addr.sun_path));
|
|
addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
|
|
|
|
len = sizeof(addr);
|
|
/* be->server_fd is kept for client User VM reconnect again */
|
|
accepted_fd = accept(be->server_fd, (struct sockaddr *)&addr, &len);
|
|
if (accepted_fd == -1) {
|
|
WPRINTF(("accept error= %d, addr.sun_path=%s\n", errno, addr.sun_path));
|
|
return;
|
|
} else {
|
|
be->fd = accepted_fd;
|
|
}
|
|
|
|
be->conn_evp = mevent_add(be->fd, EVF_READ, virtio_console_backend_read, be,
|
|
NULL, NULL);
|
|
if (be->conn_evp == NULL) {
|
|
WPRINTF(("accepted fd mevent_add failed\n"));
|
|
return;
|
|
}
|
|
|
|
if (make_socket_non_blocking(be->fd) == -1) {
|
|
WPRINTF(("accepted fd non-blocking failed\n"));
|
|
return;
|
|
}
|
|
}
|
|
|
|
static int
|
|
virtio_console_config_backend(struct virtio_console_backend *be)
|
|
{
|
|
int fd, flags;
|
|
char *pts_name = NULL;
|
|
int client_fd = -1;
|
|
struct termios tio, saved_tio;
|
|
struct sockaddr_un addr;
|
|
|
|
if (!be || be->fd == -1)
|
|
return -1;
|
|
|
|
fd = be->fd;
|
|
switch (be->be_type) {
|
|
case VIRTIO_CONSOLE_BE_PTY:
|
|
pts_name = ptsname(fd);
|
|
if (pts_name == NULL) {
|
|
WPRINTF(("vtcon: ptsname return NULL, errno = %d\n",
|
|
errno));
|
|
return -1;
|
|
}
|
|
|
|
client_fd = open(pts_name, O_RDWR);
|
|
if (client_fd == -1) {
|
|
WPRINTF(("vtcon: client_fd open failed, errno = %d\n",
|
|
errno));
|
|
return -1;
|
|
}
|
|
|
|
tcgetattr(client_fd, &tio);
|
|
cfmakeraw(&tio);
|
|
tcsetattr(client_fd, TCSAFLUSH, &tio);
|
|
be->pts_fd = client_fd;
|
|
|
|
WPRINTF(("***********************************************\n"));
|
|
WPRINTF(("virt-console backend redirected to %s\n", pts_name));
|
|
WPRINTF(("***********************************************\n"));
|
|
|
|
flags = fcntl(fd, F_GETFL);
|
|
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
|
break;
|
|
case VIRTIO_CONSOLE_BE_TTY:
|
|
case VIRTIO_CONSOLE_BE_STDIO:
|
|
tcgetattr(fd, &tio);
|
|
saved_tio = tio;
|
|
cfmakeraw(&tio);
|
|
tio.c_cflag |= CLOCAL;
|
|
tcsetattr(fd, TCSANOW, &tio);
|
|
|
|
if (be->be_type == VIRTIO_CONSOLE_BE_STDIO) {
|
|
flags = fcntl(fd, F_GETFL);
|
|
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
|
|
|
virtio_console_saved_flags = flags;
|
|
virtio_console_saved_tio = saved_tio;
|
|
atexit(virtio_console_restore_stdio);
|
|
}
|
|
break;
|
|
case VIRTIO_CONSOLE_BE_SOCKET:
|
|
if (be->portpath == NULL) {
|
|
WPRINTF(("vtcon: portpath is NULL\n"));
|
|
return -1;
|
|
}
|
|
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sun_family = AF_UNIX;
|
|
strncpy(addr.sun_path, be->portpath, sizeof(addr.sun_path));
|
|
addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
|
|
|
|
if (be->socket_type == NULL || !strcmp(be->socket_type,"server")) {
|
|
unlink(be->portpath);
|
|
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
|
|
WPRINTF(("Bind Error = %d\n", errno));
|
|
return -1;
|
|
}
|
|
if (listen(fd, 64) == -1) {
|
|
WPRINTF(("Listen Error= %d\n", errno));
|
|
return -1;
|
|
}
|
|
if (make_socket_non_blocking(fd) == -1) {
|
|
WPRINTF(("Backend config: fcntl Error\n"));
|
|
return -1;
|
|
}
|
|
} else if (!strcmp(be->socket_type,"client")) {
|
|
if (access(be->portpath,0)) {
|
|
WPRINTF(("%s not exist\n", be->portpath));
|
|
return -1;
|
|
}
|
|
if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
|
|
WPRINTF(("vtcon: connect error[%d] \n", errno));
|
|
} else {
|
|
if (make_socket_non_blocking(fd) == -1) {
|
|
WPRINTF(("Backend config: fcntl Error\n"));
|
|
}
|
|
}
|
|
} else {
|
|
WPRINTF(("Socket type not exist\n"));
|
|
return -1;
|
|
}
|
|
|
|
default:
|
|
break; /* nothing to do */
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static enum virtio_console_be_type
|
|
virtio_console_get_be_type(const char *backend)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < VIRTIO_CONSOLE_BE_MAX; i++)
|
|
if (strcasecmp(backend, virtio_console_be_table[i]) == 0)
|
|
return i;
|
|
|
|
return VIRTIO_CONSOLE_BE_INVALID;
|
|
}
|
|
|
|
static int
|
|
virtio_console_add_backend(struct virtio_console *console, char *opt)
|
|
{
|
|
struct virtio_console_backend *be;
|
|
int error = 0, fd = -1;
|
|
bool is_console = false;
|
|
char *backend = NULL;
|
|
char *portname = NULL;
|
|
char *portpath = NULL;
|
|
char *socket_type = NULL;
|
|
enum virtio_console_be_type be_type = VIRTIO_CONSOLE_BE_INVALID;
|
|
|
|
backend = strsep(&opt, ":");
|
|
if (backend == NULL) {
|
|
WPRINTF(("vtcon: no backend is specified!\n"));
|
|
error = -1;
|
|
goto parse_fail;
|
|
}
|
|
|
|
if (backend[0] == '@') {
|
|
is_console = true;
|
|
backend++;
|
|
} else
|
|
is_console = false;
|
|
|
|
be_type = virtio_console_get_be_type(backend);
|
|
if (be_type == VIRTIO_CONSOLE_BE_INVALID) {
|
|
WPRINTF(("vtcon: invalid backend %s!\n",
|
|
backend));
|
|
error = -1;
|
|
goto parse_fail;
|
|
}
|
|
|
|
if (opt != NULL) {
|
|
if (be_type == VIRTIO_CONSOLE_BE_SOCKET) {
|
|
portname = strsep(&opt, "=");
|
|
portpath = strsep(&opt, ":");
|
|
socket_type = opt;
|
|
} else {
|
|
portname = strsep(&opt, "=");
|
|
portpath = opt;
|
|
}
|
|
if (portname == NULL) {
|
|
WPRINTF(("vtcon: portname missing \n"));
|
|
error = -1;
|
|
goto parse_fail;
|
|
}
|
|
if (portpath == NULL
|
|
&& be_type != VIRTIO_CONSOLE_BE_STDIO
|
|
&& be_type != VIRTIO_CONSOLE_BE_PTY
|
|
&& be_type != VIRTIO_CONSOLE_BE_SOCKET) {
|
|
WPRINTF(("vtcon: portpath missing for %s\n",
|
|
portname));
|
|
error = -1;
|
|
goto parse_fail;
|
|
}
|
|
} else {
|
|
WPRINTF(("vtcon: please at least config portname\n"));
|
|
error = -1;
|
|
goto parse_fail;
|
|
}
|
|
|
|
be = calloc(1, sizeof(struct virtio_console_backend));
|
|
if (be == NULL) {
|
|
error = -1;
|
|
goto out;
|
|
}
|
|
|
|
fd = virtio_console_open_backend(portpath, be_type);
|
|
if (fd < 0) {
|
|
error = -1;
|
|
goto out;
|
|
}
|
|
|
|
be->fd = fd;
|
|
be->server_fd = fd;
|
|
be->be_type = be_type;
|
|
be->portpath = portpath;
|
|
be->socket_type = socket_type;
|
|
|
|
if (virtio_console_config_backend(be) < 0) {
|
|
WPRINTF(("vtcon: virtio_console_config_backend failed\n"));
|
|
error = -1;
|
|
goto out;
|
|
}
|
|
|
|
be->port = virtio_console_add_port(console, portname,
|
|
virtio_console_backend_write, be, is_console);
|
|
if (be->port == NULL) {
|
|
WPRINTF(("vtcon: virtio_console_add_port failed\n"));
|
|
error = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (virtio_console_backend_can_read(be_type)) {
|
|
if (be->be_type == VIRTIO_CONSOLE_BE_SOCKET && (be->socket_type == NULL
|
|
|| !strcmp(be->socket_type,"server"))) {
|
|
be->evp = mevent_add(fd, EVF_READ,
|
|
virtio_console_accept_new_connection, be,
|
|
virtio_console_teardown_backend, be);
|
|
if (be->evp == NULL) {
|
|
WPRINTF(("vtcon: mevent_add failed\n"));
|
|
error = -1;
|
|
goto out;
|
|
}
|
|
console->ref_count++;
|
|
}
|
|
else if (isatty(fd) || (be->be_type == VIRTIO_CONSOLE_BE_SOCKET
|
|
&& !strcmp(be->socket_type,"client"))) {
|
|
be->evp = mevent_add(fd, EVF_READ,
|
|
virtio_console_backend_read, be,
|
|
virtio_console_teardown_backend, be);
|
|
if (be->evp == NULL) {
|
|
WPRINTF(("vtcon: mevent_add failed\n"));
|
|
error = -1;
|
|
goto out;
|
|
}
|
|
console->ref_count++;
|
|
}
|
|
}
|
|
|
|
virtio_console_open_port(be->port, true);
|
|
be->open = true;
|
|
|
|
out:
|
|
if (error != 0) {
|
|
if (be) {
|
|
if (be->port) {
|
|
be->port->enabled = false;
|
|
be->port->arg = NULL;
|
|
}
|
|
if (be->be_type == VIRTIO_CONSOLE_BE_PTY &&
|
|
be->pts_fd > 0)
|
|
close(be->pts_fd);
|
|
free(be);
|
|
}
|
|
if (fd != -1 && fd != STDIN_FILENO)
|
|
close(fd);
|
|
}
|
|
|
|
parse_fail:
|
|
return error;
|
|
}
|
|
|
|
static int
|
|
virtio_console_add_backends(struct virtio_console *console, char *opts)
|
|
{
|
|
char *opt;
|
|
|
|
/* virtio-console,[@]stdio|tty|pty|file:portname[=portpath]
|
|
* [,[@]stdio|tty|pty|file:portname[=portpath][:socket_type]]
|
|
*/
|
|
while ((opt = strsep(&opts, ",")) != NULL) {
|
|
if (virtio_console_add_backend(console, opt))
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
virtio_console_close_backend(struct virtio_console_backend *be)
|
|
{
|
|
if (!be)
|
|
return;
|
|
|
|
switch (be->be_type) {
|
|
case VIRTIO_CONSOLE_BE_PTY:
|
|
if (be->pts_fd > 0) {
|
|
close(be->pts_fd);
|
|
be->pts_fd = -1;
|
|
}
|
|
break;
|
|
case VIRTIO_CONSOLE_BE_STDIO:
|
|
virtio_console_restore_stdio();
|
|
break;
|
|
case VIRTIO_CONSOLE_BE_SOCKET:
|
|
if (be->socket_type == NULL || !strcmp(be->socket_type,"server")) {
|
|
virtio_console_socket_clear(be);
|
|
if (be->server_fd > 0) {
|
|
close(be->server_fd);
|
|
be->server_fd = -1;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (be->be_type != VIRTIO_CONSOLE_BE_STDIO && be->fd > 0) {
|
|
close(be->fd);
|
|
be->fd = -1;
|
|
}
|
|
|
|
memset(be->port, 0, sizeof(*be->port));
|
|
free(be);
|
|
}
|
|
|
|
static void
|
|
virtio_console_destroy(struct virtio_console *console)
|
|
{
|
|
if (console) {
|
|
virtio_console_reset(console);
|
|
if (console->config)
|
|
free(console->config);
|
|
free(console);
|
|
console = NULL;
|
|
}
|
|
}
|
|
|
|
static void
|
|
virtio_console_teardown_backend(void *param)
|
|
{
|
|
struct virtio_console *console = NULL;
|
|
struct virtio_console_backend *be;
|
|
|
|
be = (struct virtio_console_backend *)param;
|
|
if (!be)
|
|
return;
|
|
|
|
if (be->port)
|
|
console = be->port->console;
|
|
|
|
virtio_console_close_backend(be);
|
|
|
|
if (console) {
|
|
console->ref_count--;
|
|
/* free virtio_console if this is the last backend */
|
|
if (console->ref_count == 0)
|
|
virtio_console_destroy(console);
|
|
}
|
|
}
|
|
|
|
static int
|
|
virtio_console_close_all(struct virtio_console *console)
|
|
{
|
|
int i, rc = 0, nports_num;
|
|
struct virtio_console_port *port;
|
|
struct virtio_console_backend *be;
|
|
|
|
/*
|
|
* we should close ports without mevent first.
|
|
* port->enabled is reset to false when a backend is closed.
|
|
*/
|
|
for (i = 0; i < console->nports; i++) {
|
|
port = &console->ports[i];
|
|
|
|
if (!port->enabled)
|
|
continue;
|
|
|
|
be = (struct virtio_console_backend *)port->arg;
|
|
if (be && !be->evp)
|
|
virtio_console_close_backend(be);
|
|
}
|
|
/*
|
|
* The mevent_delete function will call teardown to delete
|
|
* the console if the console->nports = 1, after the console
|
|
* be destroyed there will be a NULL pointer issue.
|
|
* Why don't check the console ? because the console delete
|
|
* is not at the same thread.
|
|
* */
|
|
|
|
nports_num = console->nports;
|
|
for (i = 0; i < nports_num; i++) {
|
|
port = &console->ports[i];
|
|
|
|
if (!port->enabled)
|
|
continue;
|
|
|
|
be = (struct virtio_console_backend *)port->arg;
|
|
if (be && be->evp) {
|
|
/* resources will be freed in the teardown callback */
|
|
mevent_delete(be->evp);
|
|
rc = 1;
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int
|
|
virtio_console_init(struct vmctx *ctx, struct pci_vdev *dev, char *opts)
|
|
{
|
|
struct virtio_console *console;
|
|
int i;
|
|
pthread_mutexattr_t attr;
|
|
int rc;
|
|
|
|
if (!opts) {
|
|
WPRINTF(("vtcon: invalid opts\n"));
|
|
return -1;
|
|
}
|
|
|
|
console = calloc(1, sizeof(struct virtio_console));
|
|
if (!console) {
|
|
WPRINTF(("vtcon: calloc returns NULL\n"));
|
|
return -1;
|
|
}
|
|
|
|
console->config = calloc(1, sizeof(struct virtio_console_config));
|
|
if (!console->config) {
|
|
WPRINTF(("vtcon->config: calloc returns NULL\n"));
|
|
free(console);
|
|
return -1;
|
|
}
|
|
|
|
console->config->max_nr_ports = VIRTIO_CONSOLE_MAXPORTS;
|
|
console->config->cols = 80;
|
|
console->config->rows = 25;
|
|
|
|
/* init mutex attribute properly to avoid deadlock */
|
|
rc = pthread_mutexattr_init(&attr);
|
|
if (rc)
|
|
DPRINTF(("mutexattr init failed with erro %d!\n", rc));
|
|
rc = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
|
|
if (rc)
|
|
DPRINTF(("virtio_console: mutexattr_settype failed with "
|
|
"error %d!\n", rc));
|
|
|
|
rc = pthread_mutex_init(&console->mtx, &attr);
|
|
if (rc)
|
|
DPRINTF(("virtio_console: pthread_mutex_init failed with "
|
|
"error %d!\n", rc));
|
|
|
|
virtio_linkup(&console->base, &virtio_console_ops, console, dev,
|
|
console->queues, BACKEND_VBSU);
|
|
console->base.mtx = &console->mtx;
|
|
console->base.device_caps = VIRTIO_CONSOLE_S_HOSTCAPS;
|
|
|
|
for (i = 0; i < VIRTIO_CONSOLE_MAXQ; i++) {
|
|
console->queues[i].qsize = VIRTIO_CONSOLE_RINGSZ;
|
|
console->queues[i].notify = i % 2 == 0
|
|
? virtio_console_notify_rx
|
|
: virtio_console_notify_tx;
|
|
}
|
|
|
|
/* initialize config space */
|
|
pci_set_cfgdata16(dev, PCIR_DEVICE, VIRTIO_DEV_CONSOLE);
|
|
pci_set_cfgdata16(dev, PCIR_VENDOR, VIRTIO_VENDOR);
|
|
pci_set_cfgdata8(dev, PCIR_CLASS, PCIC_SIMPLECOMM);
|
|
pci_set_cfgdata8(dev, PCIR_SUBCLASS, PCIS_SIMPLECOMM_OTHER);
|
|
pci_set_cfgdata16(dev, PCIR_SUBDEV_0, VIRTIO_TYPE_CONSOLE);
|
|
pci_set_cfgdata16(dev, PCIR_SUBVEND_0, VIRTIO_VENDOR);
|
|
|
|
if (virtio_interrupt_init(&console->base, virtio_uses_msix())) {
|
|
if (console) {
|
|
if (console->config)
|
|
free(console->config);
|
|
free(console);
|
|
}
|
|
return -1;
|
|
}
|
|
virtio_set_io_bar(&console->base, 0);
|
|
|
|
/* create control port */
|
|
console->control_port.console = console;
|
|
console->control_port.txq = 2;
|
|
console->control_port.rxq = 3;
|
|
console->control_port.cb = virtio_console_control_tx;
|
|
console->control_port.enabled = true;
|
|
if (virtio_console_add_backends(console, opts) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
virtio_console_deinit(struct vmctx *ctx, struct pci_vdev *dev, char *opts)
|
|
{
|
|
struct virtio_console *console;
|
|
int rc;
|
|
|
|
console = (struct virtio_console *)dev->arg;
|
|
if (console) {
|
|
rc = virtio_console_close_all(console);
|
|
/*
|
|
* if all the ports are without mevent attached,
|
|
* no teardown will be called, we should destroy
|
|
* console here explicitly.
|
|
*/
|
|
if (!rc)
|
|
virtio_console_destroy(console);
|
|
}
|
|
}
|
|
|
|
struct pci_vdev_ops pci_ops_virtio_console = {
|
|
.class_name = "virtio-console",
|
|
.vdev_init = virtio_console_init,
|
|
.vdev_deinit = virtio_console_deinit,
|
|
.vdev_barwrite = virtio_pci_write,
|
|
.vdev_barread = virtio_pci_read
|
|
};
|
|
DEFINE_PCI_DEVTYPE(pci_ops_virtio_console);
|