/* * Copyright (C) International Business Machines Corp., 2005 * Author(s): Anthony Liguori * * Copyright (C) Red Hat 2007 * * Xen Console * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; under version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, see . */ #include "qemu/osdep.h" #include #include #include "qapi/error.h" #include "hw/hw.h" #include "chardev/char-fe.h" #include "hw/xen/xen_backend.h" #include struct buffer { uint8_t *data; size_t consumed; size_t size; size_t capacity; size_t max_capacity; }; struct XenConsole { struct XenDevice xendev; /* must be first */ struct buffer buffer; char console[XEN_BUFSIZE]; int ring_ref; void *sring; CharBackend chr; int backlog; }; static void buffer_append(struct XenConsole *con) { struct buffer *buffer = &con->buffer; XENCONS_RING_IDX cons, prod, size; struct xencons_interface *intf = con->sring; cons = intf->out_cons; prod = intf->out_prod; xen_mb(); size = prod - cons; if ((size == 0) || (size > sizeof(intf->out))) return; if ((buffer->capacity - buffer->size) < size) { buffer->capacity += (size + 1024); buffer->data = g_realloc(buffer->data, buffer->capacity); } while (cons != prod) buffer->data[buffer->size++] = intf->out[ MASK_XENCONS_IDX(cons++, intf->out)]; xen_mb(); intf->out_cons = cons; xen_pv_send_notify(&con->xendev); if (buffer->max_capacity && buffer->size > buffer->max_capacity) { /* Discard the middle of the data. */ size_t over = buffer->size - buffer->max_capacity; uint8_t *maxpos = buffer->data + buffer->max_capacity; memmove(maxpos - over, maxpos, over); buffer->data = g_realloc(buffer->data, buffer->max_capacity); buffer->size = buffer->capacity = buffer->max_capacity; if (buffer->consumed > buffer->max_capacity - over) buffer->consumed = buffer->max_capacity - over; } } static void buffer_advance(struct buffer *buffer, size_t len) { buffer->consumed += len; if (buffer->consumed == buffer->size) { buffer->consumed = 0; buffer->size = 0; } } static int ring_free_bytes(struct XenConsole *con) { struct xencons_interface *intf = con->sring; XENCONS_RING_IDX cons, prod, space; cons = intf->in_cons; prod = intf->in_prod; xen_mb(); space = prod - cons; if (space > sizeof(intf->in)) return 0; /* ring is screwed: ignore it */ return (sizeof(intf->in) - space); } static int xencons_can_receive(void *opaque) { struct XenConsole *con = opaque; return ring_free_bytes(con); } static void xencons_receive(void *opaque, const uint8_t *buf, int len) { struct XenConsole *con = opaque; struct xencons_interface *intf = con->sring; XENCONS_RING_IDX prod; int i, max; max = ring_free_bytes(con); /* The can_receive() func limits this, but check again anyway */ if (max < len) len = max; prod = intf->in_prod; for (i = 0; i < len; i++) { intf->in[MASK_XENCONS_IDX(prod++, intf->in)] = buf[i]; } xen_wmb(); intf->in_prod = prod; xen_pv_send_notify(&con->xendev); } static void xencons_send(struct XenConsole *con) { ssize_t len, size; size = con->buffer.size - con->buffer.consumed; if (qemu_chr_fe_backend_connected(&con->chr)) { len = qemu_chr_fe_write(&con->chr, con->buffer.data + con->buffer.consumed, size); } else { len = size; } if (len < 1) { if (!con->backlog) { con->backlog = 1; xen_pv_printf(&con->xendev, 1, "backlog piling up, nobody listening?\n"); } } else { buffer_advance(&con->buffer, len); if (con->backlog && len == size) { con->backlog = 0; xen_pv_printf(&con->xendev, 1, "backlog is gone\n"); } } } /* -------------------------------------------------------------------- */ static int con_init(struct XenDevice *xendev) { struct XenConsole *con = container_of(xendev, struct XenConsole, xendev); char *type, *dom, label[32]; int ret = 0; const char *output; /* setup */ dom = xs_get_domain_path(xenstore, con->xendev.dom); if (!xendev->dev) { snprintf(con->console, sizeof(con->console), "%s/console", dom); } else { snprintf(con->console, sizeof(con->console), "%s/device/console/%d", dom, xendev->dev); } free(dom); type = xenstore_read_str(con->console, "type"); if (!type || strcmp(type, "ioemu") != 0) { xen_pv_printf(xendev, 1, "not for me (type=%s)\n", type); ret = -1; goto out; } output = xenstore_read_str(con->console, "output"); /* no Xen override, use qemu output device */ if (output == NULL) { if (con->xendev.dev) { qemu_chr_fe_init(&con->chr, serial_hd(con->xendev.dev), &error_abort); } } else { snprintf(label, sizeof(label), "xencons%d", con->xendev.dev); qemu_chr_fe_init(&con->chr, qemu_chr_new(label, output), &error_abort); } xenstore_store_pv_console_info(con->xendev.dev, qemu_chr_fe_get_driver(&con->chr)); out: g_free(type); return ret; } static int con_initialise(struct XenDevice *xendev) { struct XenConsole *con = container_of(xendev, struct XenConsole, xendev); int limit; if (xenstore_read_int(con->console, "ring-ref", &con->ring_ref) == -1) return -1; if (xenstore_read_int(con->console, "port", &con->xendev.remote_port) == -1) return -1; if (xenstore_read_int(con->console, "limit", &limit) == 0) con->buffer.max_capacity = limit; if (!xendev->dev) { xen_pfn_t mfn = con->ring_ref; con->sring = xenforeignmemory_map(xen_fmem, con->xendev.dom, PROT_READ | PROT_WRITE, 1, &mfn, NULL); } else { con->sring = xen_be_map_grant_ref(xendev, con->ring_ref, PROT_READ | PROT_WRITE); } if (!con->sring) return -1; xen_be_bind_evtchn(&con->xendev); qemu_chr_fe_set_handlers(&con->chr, xencons_can_receive, xencons_receive, NULL, NULL, con, NULL, true); xen_pv_printf(xendev, 1, "ring mfn %d, remote port %d, local port %d, limit %zd\n", con->ring_ref, con->xendev.remote_port, con->xendev.local_port, con->buffer.max_capacity); return 0; } static void con_disconnect(struct XenDevice *xendev) { struct XenConsole *con = container_of(xendev, struct XenConsole, xendev); qemu_chr_fe_deinit(&con->chr, false); xen_pv_unbind_evtchn(&con->xendev); if (con->sring) { if (!xendev->dev) { xenforeignmemory_unmap(xen_fmem, con->sring, 1); } else { xen_be_unmap_grant_ref(xendev, con->sring); } con->sring = NULL; } } static void con_event(struct XenDevice *xendev) { struct XenConsole *con = container_of(xendev, struct XenConsole, xendev); buffer_append(con); if (con->buffer.size - con->buffer.consumed) xencons_send(con); } /* -------------------------------------------------------------------- */ struct XenDevOps xen_console_ops = { .size = sizeof(struct XenConsole), .flags = DEVOPS_FLAG_IGNORE_STATE|DEVOPS_FLAG_NEED_GNTDEV, .init = con_init, .initialise = con_initialise, .event = con_event, .disconnect = con_disconnect, };