2017-11-06 17:11:51 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0+
|
2007-07-18 01:37:06 +00:00
|
|
|
/*
|
|
|
|
* xen console driver interface to hvc_console.c
|
|
|
|
*
|
|
|
|
* (c) 2007 Gerd Hoffmann <kraxel@suse.de>
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/console.h>
|
|
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/err.h>
|
2012-08-06 14:27:09 +00:00
|
|
|
#include <linux/irq.h>
|
2007-07-18 01:37:06 +00:00
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/types.h>
|
2012-01-30 16:02:31 +00:00
|
|
|
#include <linux/list.h>
|
2016-02-25 12:10:37 +00:00
|
|
|
#include <linux/serial_core.h>
|
2007-07-18 01:37:06 +00:00
|
|
|
|
2012-01-27 18:31:36 +00:00
|
|
|
#include <asm/io.h>
|
2007-07-18 01:37:06 +00:00
|
|
|
#include <asm/xen/hypervisor.h>
|
2009-10-06 22:11:14 +00:00
|
|
|
|
|
|
|
#include <xen/xen.h>
|
2012-01-27 18:31:36 +00:00
|
|
|
#include <xen/interface/xen.h>
|
|
|
|
#include <xen/hvm.h>
|
2012-01-30 16:02:31 +00:00
|
|
|
#include <xen/grant_table.h>
|
2007-07-18 01:37:06 +00:00
|
|
|
#include <xen/page.h>
|
|
|
|
#include <xen/events.h>
|
|
|
|
#include <xen/interface/io/console.h>
|
2012-08-06 14:27:09 +00:00
|
|
|
#include <xen/interface/sched.h>
|
2007-07-18 01:37:06 +00:00
|
|
|
#include <xen/hvc-console.h>
|
2012-01-30 16:02:31 +00:00
|
|
|
#include <xen/xenbus.h>
|
2007-07-18 01:37:06 +00:00
|
|
|
|
|
|
|
#include "hvc_console.h"
|
|
|
|
|
|
|
|
#define HVC_COOKIE 0x58656e /* "Xen" in hex */
|
|
|
|
|
2012-01-30 16:02:31 +00:00
|
|
|
struct xencons_info {
|
|
|
|
struct list_head list;
|
|
|
|
struct xenbus_device *xbdev;
|
|
|
|
struct xencons_interface *intf;
|
|
|
|
unsigned int evtchn;
|
2021-12-16 07:24:08 +00:00
|
|
|
XENCONS_RING_IDX out_cons;
|
|
|
|
unsigned int out_cons_same;
|
2012-01-30 16:02:31 +00:00
|
|
|
struct hvc_struct *hvc;
|
|
|
|
int irq;
|
|
|
|
int vtermno;
|
|
|
|
grant_ref_t gntref;
|
2022-11-30 15:09:11 +00:00
|
|
|
spinlock_t ring_lock;
|
2012-01-30 16:02:31 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static LIST_HEAD(xenconsoles);
|
|
|
|
static DEFINE_SPINLOCK(xencons_lock);
|
2007-07-18 01:37:06 +00:00
|
|
|
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
|
2012-01-30 16:02:31 +00:00
|
|
|
static struct xencons_info *vtermno_to_xencons(int vtermno)
|
|
|
|
{
|
2022-11-30 16:36:02 +00:00
|
|
|
struct xencons_info *entry, *ret = NULL;
|
|
|
|
unsigned long flags;
|
2012-01-30 16:02:31 +00:00
|
|
|
|
2022-11-30 16:36:02 +00:00
|
|
|
spin_lock_irqsave(&xencons_lock, flags);
|
|
|
|
if (list_empty(&xenconsoles)) {
|
|
|
|
spin_unlock_irqrestore(&xencons_lock, flags);
|
|
|
|
return NULL;
|
|
|
|
}
|
2012-01-30 16:02:31 +00:00
|
|
|
|
2022-11-30 16:36:02 +00:00
|
|
|
list_for_each_entry(entry, &xenconsoles, list) {
|
2012-01-30 16:02:31 +00:00
|
|
|
if (entry->vtermno == vtermno) {
|
|
|
|
ret = entry;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2022-11-30 16:36:02 +00:00
|
|
|
spin_unlock_irqrestore(&xencons_lock, flags);
|
2008-05-26 22:31:25 +00:00
|
|
|
|
2012-01-30 16:02:31 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int xenbus_devid_to_vtermno(int devid)
|
2007-07-18 01:37:06 +00:00
|
|
|
{
|
2012-01-30 16:02:31 +00:00
|
|
|
return devid + HVC_COOKIE;
|
2007-07-18 01:37:06 +00:00
|
|
|
}
|
|
|
|
|
2012-01-30 16:02:31 +00:00
|
|
|
static inline void notify_daemon(struct xencons_info *cons)
|
2007-07-18 01:37:06 +00:00
|
|
|
{
|
|
|
|
/* Use evtchn: this is called early, before irq is set up. */
|
2012-01-30 16:02:31 +00:00
|
|
|
notify_remote_via_evtchn(cons->evtchn);
|
2007-07-18 01:37:06 +00:00
|
|
|
}
|
|
|
|
|
2023-12-06 07:36:57 +00:00
|
|
|
static ssize_t __write_console(struct xencons_info *xencons,
|
|
|
|
const u8 *data, size_t len)
|
2007-07-18 01:37:06 +00:00
|
|
|
{
|
|
|
|
XENCONS_RING_IDX cons, prod;
|
2012-01-30 16:02:31 +00:00
|
|
|
struct xencons_interface *intf = xencons->intf;
|
2022-11-30 15:09:11 +00:00
|
|
|
unsigned long flags;
|
2023-12-06 07:36:57 +00:00
|
|
|
size_t sent = 0;
|
2007-07-18 01:37:06 +00:00
|
|
|
|
2022-11-30 15:09:11 +00:00
|
|
|
spin_lock_irqsave(&xencons->ring_lock, flags);
|
2007-07-18 01:37:06 +00:00
|
|
|
cons = intf->out_cons;
|
|
|
|
prod = intf->out_prod;
|
|
|
|
mb(); /* update queue values before going on */
|
2021-07-07 09:10:45 +00:00
|
|
|
|
|
|
|
if ((prod - cons) > sizeof(intf->out)) {
|
2022-11-30 15:09:11 +00:00
|
|
|
spin_unlock_irqrestore(&xencons->ring_lock, flags);
|
2021-07-07 09:10:45 +00:00
|
|
|
pr_err_once("xencons: Illegal ring page indices");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
2007-07-18 01:37:06 +00:00
|
|
|
|
|
|
|
while ((sent < len) && ((prod - cons) < sizeof(intf->out)))
|
|
|
|
intf->out[MASK_XENCONS_IDX(prod++, intf->out)] = data[sent++];
|
|
|
|
|
|
|
|
wmb(); /* write ring before updating pointer */
|
|
|
|
intf->out_prod = prod;
|
2022-11-30 15:09:11 +00:00
|
|
|
spin_unlock_irqrestore(&xencons->ring_lock, flags);
|
2007-07-18 01:37:06 +00:00
|
|
|
|
2010-10-14 18:38:47 +00:00
|
|
|
if (sent)
|
2012-01-30 16:02:31 +00:00
|
|
|
notify_daemon(xencons);
|
2007-07-18 01:37:06 +00:00
|
|
|
return sent;
|
|
|
|
}
|
|
|
|
|
2023-12-06 07:36:57 +00:00
|
|
|
static ssize_t domU_write_console(uint32_t vtermno, const u8 *data, size_t len)
|
2009-10-20 06:28:21 +00:00
|
|
|
{
|
2012-01-30 16:02:31 +00:00
|
|
|
struct xencons_info *cons = vtermno_to_xencons(vtermno);
|
2023-12-06 07:36:57 +00:00
|
|
|
size_t ret = len;
|
|
|
|
|
2012-01-30 16:02:31 +00:00
|
|
|
if (cons == NULL)
|
|
|
|
return -EINVAL;
|
2009-10-20 06:28:21 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Make sure the whole buffer is emitted, polling if
|
|
|
|
* necessary. We don't ever want to rely on the hvc daemon
|
|
|
|
* because the most interesting console output is when the
|
|
|
|
* kernel is crippled.
|
|
|
|
*/
|
|
|
|
while (len) {
|
2023-12-06 07:36:57 +00:00
|
|
|
ssize_t sent = __write_console(cons, data, len);
|
2021-07-07 09:10:45 +00:00
|
|
|
|
|
|
|
if (sent < 0)
|
|
|
|
return sent;
|
|
|
|
|
2009-10-20 06:28:21 +00:00
|
|
|
data += sent;
|
|
|
|
len -= sent;
|
|
|
|
|
|
|
|
if (unlikely(len))
|
|
|
|
HYPERVISOR_sched_op(SCHEDOP_yield, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2023-12-06 07:36:57 +00:00
|
|
|
static ssize_t domU_read_console(uint32_t vtermno, u8 *buf, size_t len)
|
2007-07-18 01:37:06 +00:00
|
|
|
{
|
2012-01-30 16:02:31 +00:00
|
|
|
struct xencons_interface *intf;
|
2007-07-18 01:37:06 +00:00
|
|
|
XENCONS_RING_IDX cons, prod;
|
2012-01-30 16:02:31 +00:00
|
|
|
struct xencons_info *xencons = vtermno_to_xencons(vtermno);
|
2021-12-16 07:24:08 +00:00
|
|
|
unsigned int eoiflag = 0;
|
2022-11-30 15:09:11 +00:00
|
|
|
unsigned long flags;
|
2023-12-06 07:36:57 +00:00
|
|
|
size_t recv = 0;
|
2021-12-16 07:24:08 +00:00
|
|
|
|
2012-01-30 16:02:31 +00:00
|
|
|
if (xencons == NULL)
|
|
|
|
return -EINVAL;
|
|
|
|
intf = xencons->intf;
|
2007-07-18 01:37:06 +00:00
|
|
|
|
2022-11-30 15:09:11 +00:00
|
|
|
spin_lock_irqsave(&xencons->ring_lock, flags);
|
2007-07-18 01:37:06 +00:00
|
|
|
cons = intf->in_cons;
|
|
|
|
prod = intf->in_prod;
|
|
|
|
mb(); /* get pointers before reading ring */
|
2021-07-07 09:10:45 +00:00
|
|
|
|
|
|
|
if ((prod - cons) > sizeof(intf->in)) {
|
2022-11-30 15:09:11 +00:00
|
|
|
spin_unlock_irqrestore(&xencons->ring_lock, flags);
|
2021-07-07 09:10:45 +00:00
|
|
|
pr_err_once("xencons: Illegal ring page indices");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
2007-07-18 01:37:06 +00:00
|
|
|
|
|
|
|
while (cons != prod && recv < len)
|
|
|
|
buf[recv++] = intf->in[MASK_XENCONS_IDX(cons++, intf->in)];
|
|
|
|
|
|
|
|
mb(); /* read ring before consuming */
|
|
|
|
intf->in_cons = cons;
|
|
|
|
|
2021-12-16 07:24:08 +00:00
|
|
|
/*
|
|
|
|
* When to mark interrupt having been spurious:
|
|
|
|
* - there was no new data to be read, and
|
|
|
|
* - the backend did not consume some output bytes, and
|
|
|
|
* - the previous round with no read data didn't see consumed bytes
|
|
|
|
* (we might have a race with an interrupt being in flight while
|
|
|
|
* updating xencons->out_cons, so account for that by allowing one
|
|
|
|
* round without any visible reason)
|
|
|
|
*/
|
|
|
|
if (intf->out_cons != xencons->out_cons) {
|
|
|
|
xencons->out_cons = intf->out_cons;
|
|
|
|
xencons->out_cons_same = 0;
|
|
|
|
}
|
2022-11-30 15:09:11 +00:00
|
|
|
if (!recv && xencons->out_cons_same++ > 1) {
|
|
|
|
eoiflag = XEN_EOI_FLAG_SPURIOUS;
|
|
|
|
}
|
|
|
|
spin_unlock_irqrestore(&xencons->ring_lock, flags);
|
|
|
|
|
2021-12-16 07:24:08 +00:00
|
|
|
if (recv) {
|
|
|
|
notify_daemon(xencons);
|
|
|
|
}
|
|
|
|
|
|
|
|
xen_irq_lateeoi(xencons->irq, eoiflag);
|
|
|
|
|
2007-07-18 01:37:06 +00:00
|
|
|
return recv;
|
|
|
|
}
|
|
|
|
|
2015-12-30 10:28:02 +00:00
|
|
|
static const struct hv_ops domU_hvc_ops = {
|
2010-09-02 15:17:06 +00:00
|
|
|
.get_chars = domU_read_console,
|
|
|
|
.put_chars = domU_write_console,
|
2008-06-20 13:24:08 +00:00
|
|
|
.notifier_add = notifier_add_irq,
|
|
|
|
.notifier_del = notifier_del_irq,
|
2008-10-13 23:12:48 +00:00
|
|
|
.notifier_hangup = notifier_hangup_irq,
|
2007-07-18 01:37:06 +00:00
|
|
|
};
|
|
|
|
|
2023-12-06 07:36:57 +00:00
|
|
|
static ssize_t dom0_read_console(uint32_t vtermno, u8 *buf, size_t len)
|
2010-09-02 15:17:06 +00:00
|
|
|
{
|
|
|
|
return HYPERVISOR_console_io(CONSOLEIO_read, len, buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Either for a dom0 to write to the system console, or a domU with a
|
|
|
|
* debug version of Xen
|
|
|
|
*/
|
2023-12-06 07:36:57 +00:00
|
|
|
static ssize_t dom0_write_console(uint32_t vtermno, const u8 *str, size_t len)
|
2010-09-02 15:17:06 +00:00
|
|
|
{
|
2023-12-06 07:36:57 +00:00
|
|
|
int rc = HYPERVISOR_console_io(CONSOLEIO_write, len, (u8 *)str);
|
2010-09-02 15:17:06 +00:00
|
|
|
if (rc < 0)
|
2013-09-27 21:18:13 +00:00
|
|
|
return rc;
|
2010-09-02 15:17:06 +00:00
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
2015-12-30 10:28:02 +00:00
|
|
|
static const struct hv_ops dom0_hvc_ops = {
|
2010-09-02 15:17:06 +00:00
|
|
|
.get_chars = dom0_read_console,
|
|
|
|
.put_chars = dom0_write_console,
|
|
|
|
.notifier_add = notifier_add_irq,
|
|
|
|
.notifier_del = notifier_del_irq,
|
|
|
|
.notifier_hangup = notifier_hangup_irq,
|
|
|
|
};
|
|
|
|
|
2012-01-27 18:31:36 +00:00
|
|
|
static int xen_hvm_console_init(void)
|
|
|
|
{
|
|
|
|
int r;
|
|
|
|
uint64_t v = 0;
|
2022-11-30 16:36:02 +00:00
|
|
|
unsigned long gfn, flags;
|
2012-01-30 16:02:31 +00:00
|
|
|
struct xencons_info *info;
|
2012-01-27 18:31:36 +00:00
|
|
|
|
|
|
|
if (!xen_hvm_domain())
|
|
|
|
return -ENODEV;
|
|
|
|
|
2012-01-30 16:02:31 +00:00
|
|
|
info = vtermno_to_xencons(HVC_COOKIE);
|
|
|
|
if (!info) {
|
2013-08-29 21:08:18 +00:00
|
|
|
info = kzalloc(sizeof(struct xencons_info), GFP_KERNEL);
|
2012-01-30 16:02:31 +00:00
|
|
|
if (!info)
|
|
|
|
return -ENOMEM;
|
2022-11-30 15:09:11 +00:00
|
|
|
spin_lock_init(&info->ring_lock);
|
2012-06-26 13:30:51 +00:00
|
|
|
} else if (info->intf != NULL) {
|
|
|
|
/* already configured */
|
2012-01-30 16:02:31 +00:00
|
|
|
return 0;
|
2012-06-26 13:30:51 +00:00
|
|
|
}
|
2012-05-23 16:56:59 +00:00
|
|
|
/*
|
|
|
|
* If the toolstack (or the hypervisor) hasn't set these values, the
|
2015-08-07 16:34:40 +00:00
|
|
|
* default value is 0. Even though gfn = 0 and evtchn = 0 are
|
2012-05-23 16:56:59 +00:00
|
|
|
* theoretically correct values, in practice they never are and they
|
|
|
|
* mean that a legacy toolstack hasn't initialized the pv console correctly.
|
|
|
|
*/
|
2012-01-27 18:31:36 +00:00
|
|
|
r = hvm_get_parameter(HVM_PARAM_CONSOLE_EVTCHN, &v);
|
2012-05-23 16:56:59 +00:00
|
|
|
if (r < 0 || v == 0)
|
2012-05-23 16:53:11 +00:00
|
|
|
goto err;
|
2012-01-30 16:02:31 +00:00
|
|
|
info->evtchn = v;
|
2012-05-23 16:55:38 +00:00
|
|
|
v = 0;
|
|
|
|
r = hvm_get_parameter(HVM_PARAM_CONSOLE_PFN, &v);
|
2012-05-23 16:56:59 +00:00
|
|
|
if (r < 0 || v == 0)
|
2012-05-23 16:53:11 +00:00
|
|
|
goto err;
|
2015-08-07 16:34:40 +00:00
|
|
|
gfn = v;
|
2022-05-30 08:26:34 +00:00
|
|
|
info->intf = memremap(gfn << XEN_PAGE_SHIFT, XEN_PAGE_SIZE, MEMREMAP_WB);
|
2012-05-23 16:53:11 +00:00
|
|
|
if (info->intf == NULL)
|
|
|
|
goto err;
|
2012-01-30 16:02:31 +00:00
|
|
|
info->vtermno = HVC_COOKIE;
|
|
|
|
|
2022-11-30 16:36:02 +00:00
|
|
|
spin_lock_irqsave(&xencons_lock, flags);
|
2012-01-30 16:02:31 +00:00
|
|
|
list_add_tail(&info->list, &xenconsoles);
|
2022-11-30 16:36:02 +00:00
|
|
|
spin_unlock_irqrestore(&xencons_lock, flags);
|
2012-01-30 16:02:31 +00:00
|
|
|
|
|
|
|
return 0;
|
2012-05-23 16:53:11 +00:00
|
|
|
err:
|
|
|
|
kfree(info);
|
|
|
|
return -ENODEV;
|
2012-01-30 16:02:31 +00:00
|
|
|
}
|
|
|
|
|
2016-02-25 12:10:38 +00:00
|
|
|
static int xencons_info_pv_init(struct xencons_info *info, int vtermno)
|
|
|
|
{
|
2022-11-30 15:09:11 +00:00
|
|
|
spin_lock_init(&info->ring_lock);
|
2016-02-25 12:10:38 +00:00
|
|
|
info->evtchn = xen_start_info->console.domU.evtchn;
|
|
|
|
/* GFN == MFN for PV guest */
|
|
|
|
info->intf = gfn_to_virt(xen_start_info->console.domU.mfn);
|
|
|
|
info->vtermno = vtermno;
|
|
|
|
|
|
|
|
list_add_tail(&info->list, &xenconsoles);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-01-30 16:02:31 +00:00
|
|
|
static int xen_pv_console_init(void)
|
|
|
|
{
|
|
|
|
struct xencons_info *info;
|
2022-11-30 16:36:02 +00:00
|
|
|
unsigned long flags;
|
2012-01-30 16:02:31 +00:00
|
|
|
|
|
|
|
if (!xen_pv_domain())
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
if (!xen_start_info->console.domU.evtchn)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
info = vtermno_to_xencons(HVC_COOKIE);
|
|
|
|
if (!info) {
|
2013-08-29 21:08:18 +00:00
|
|
|
info = kzalloc(sizeof(struct xencons_info), GFP_KERNEL);
|
2012-01-30 16:02:31 +00:00
|
|
|
if (!info)
|
|
|
|
return -ENOMEM;
|
2012-06-26 13:30:51 +00:00
|
|
|
} else if (info->intf != NULL) {
|
|
|
|
/* already configured */
|
2012-01-30 16:02:31 +00:00
|
|
|
return 0;
|
2012-06-26 13:30:51 +00:00
|
|
|
}
|
2022-11-30 16:36:02 +00:00
|
|
|
spin_lock_irqsave(&xencons_lock, flags);
|
2016-02-25 12:10:38 +00:00
|
|
|
xencons_info_pv_init(info, HVC_COOKIE);
|
2022-11-30 16:36:02 +00:00
|
|
|
spin_unlock_irqrestore(&xencons_lock, flags);
|
2012-01-30 16:02:31 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int xen_initial_domain_console_init(void)
|
|
|
|
{
|
|
|
|
struct xencons_info *info;
|
2022-11-30 16:36:02 +00:00
|
|
|
unsigned long flags;
|
2012-01-30 16:02:31 +00:00
|
|
|
|
|
|
|
if (!xen_initial_domain())
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
info = vtermno_to_xencons(HVC_COOKIE);
|
|
|
|
if (!info) {
|
2013-08-29 21:08:18 +00:00
|
|
|
info = kzalloc(sizeof(struct xencons_info), GFP_KERNEL);
|
2012-01-30 16:02:31 +00:00
|
|
|
if (!info)
|
|
|
|
return -ENOMEM;
|
2022-11-30 15:09:11 +00:00
|
|
|
spin_lock_init(&info->ring_lock);
|
2012-01-30 16:02:31 +00:00
|
|
|
}
|
|
|
|
|
2015-05-19 17:40:49 +00:00
|
|
|
info->irq = bind_virq_to_irq(VIRQ_CONSOLE, 0, false);
|
2012-01-30 16:02:31 +00:00
|
|
|
info->vtermno = HVC_COOKIE;
|
|
|
|
|
2022-11-30 16:36:02 +00:00
|
|
|
spin_lock_irqsave(&xencons_lock, flags);
|
2012-01-30 16:02:31 +00:00
|
|
|
list_add_tail(&info->list, &xenconsoles);
|
2022-11-30 16:36:02 +00:00
|
|
|
spin_unlock_irqrestore(&xencons_lock, flags);
|
2012-01-27 18:31:36 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-04-29 21:10:14 +00:00
|
|
|
static void xen_console_update_evtchn(struct xencons_info *info)
|
|
|
|
{
|
|
|
|
if (xen_hvm_domain()) {
|
2015-05-28 08:28:22 +00:00
|
|
|
uint64_t v = 0;
|
2015-04-29 21:10:14 +00:00
|
|
|
int err;
|
|
|
|
|
|
|
|
err = hvm_get_parameter(HVM_PARAM_CONSOLE_EVTCHN, &v);
|
|
|
|
if (!err && v)
|
|
|
|
info->evtchn = v;
|
|
|
|
} else
|
|
|
|
info->evtchn = xen_start_info->console.domU.evtchn;
|
|
|
|
}
|
|
|
|
|
2012-01-30 16:02:31 +00:00
|
|
|
void xen_console_resume(void)
|
|
|
|
{
|
|
|
|
struct xencons_info *info = vtermno_to_xencons(HVC_COOKIE);
|
2015-04-29 21:10:14 +00:00
|
|
|
if (info != NULL && info->irq) {
|
|
|
|
if (!xen_initial_domain())
|
|
|
|
xen_console_update_evtchn(info);
|
2012-01-30 16:02:31 +00:00
|
|
|
rebind_evtchn_irq(info->evtchn, info->irq);
|
2015-04-29 21:10:14 +00:00
|
|
|
}
|
2012-01-30 16:02:31 +00:00
|
|
|
}
|
|
|
|
|
2016-01-25 21:54:58 +00:00
|
|
|
#ifdef CONFIG_HVC_XEN_FRONTEND
|
2012-01-30 16:02:31 +00:00
|
|
|
static void xencons_disconnect_backend(struct xencons_info *info)
|
|
|
|
{
|
hvc/xen: fix console unplug
On unplug of a Xen console, xencons_disconnect_backend() unconditionally
calls free_irq() via unbind_from_irqhandler(), causing a warning of
freeing an already-free IRQ:
(qemu) device_del con1
[ 32.050919] ------------[ cut here ]------------
[ 32.050942] Trying to free already-free IRQ 33
[ 32.050990] WARNING: CPU: 0 PID: 51 at kernel/irq/manage.c:1895 __free_irq+0x1d4/0x330
It should be using evtchn_put() to tear down the event channel binding,
and let the Linux IRQ side of it be handled by notifier_del_irq() through
the HVC code.
On which topic... xencons_disconnect_backend() should call hvc_remove()
*first*, rather than tearing down the event channel and grant mapping
while they are in use. And then the IRQ is guaranteed to be freed by
the time it's torn down by evtchn_put().
Since evtchn_put() also closes the actual event channel, avoid calling
xenbus_free_evtchn() except in the failure path where the IRQ was not
successfully set up.
However, calling hvc_remove() at the start of xencons_disconnect_backend()
still isn't early enough. An unplug request is indicated by the backend
setting its state to XenbusStateClosing, which triggers a notification
to xencons_backend_changed(), which... does nothing except set its own
frontend state directly to XenbusStateClosed without *actually* tearing
down the HVC device or, you know, making sure it isn't actively in use.
So the backend sees the guest frontend set its state to XenbusStateClosed
and stops servicing the interrupt... and the guest spins for ever in the
domU_write_console() function waiting for the ring to drain.
Fix that one by calling hvc_remove() from xencons_backend_changed() before
signalling to the backend that it's OK to proceed with the removal.
Tested with 'dd if=/dev/zero of=/dev/hvc1' while telling Qemu to remove
the console device.
Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
Cc: stable@vger.kernel.org
Link: https://lore.kernel.org/r/20231020161529.355083-4-dwmw2@infradead.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2023-10-20 16:15:29 +00:00
|
|
|
if (info->hvc != NULL)
|
|
|
|
hvc_remove(info->hvc);
|
|
|
|
info->hvc = NULL;
|
|
|
|
if (info->irq > 0) {
|
|
|
|
evtchn_put(info->evtchn);
|
|
|
|
info->irq = 0;
|
|
|
|
info->evtchn = 0;
|
|
|
|
}
|
|
|
|
/* evtchn_put() will also close it so this is only an error path */
|
2012-01-30 16:02:31 +00:00
|
|
|
if (info->evtchn > 0)
|
|
|
|
xenbus_free_evtchn(info->xbdev, info->evtchn);
|
|
|
|
info->evtchn = 0;
|
|
|
|
if (info->gntref > 0)
|
|
|
|
gnttab_free_grant_references(info->gntref);
|
|
|
|
info->gntref = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void xencons_free(struct xencons_info *info)
|
|
|
|
{
|
|
|
|
free_page((unsigned long)info->intf);
|
|
|
|
info->intf = NULL;
|
|
|
|
info->vtermno = 0;
|
|
|
|
kfree(info);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int xen_console_remove(struct xencons_info *info)
|
|
|
|
{
|
2022-11-30 16:36:02 +00:00
|
|
|
unsigned long flags;
|
|
|
|
|
2012-01-30 16:02:31 +00:00
|
|
|
xencons_disconnect_backend(info);
|
2022-11-30 16:36:02 +00:00
|
|
|
spin_lock_irqsave(&xencons_lock, flags);
|
2012-01-30 16:02:31 +00:00
|
|
|
list_del(&info->list);
|
2022-11-30 16:36:02 +00:00
|
|
|
spin_unlock_irqrestore(&xencons_lock, flags);
|
2012-01-30 16:02:31 +00:00
|
|
|
if (info->xbdev != NULL)
|
|
|
|
xencons_free(info);
|
|
|
|
else {
|
|
|
|
if (xen_hvm_domain())
|
|
|
|
iounmap(info->intf);
|
|
|
|
kfree(info);
|
2010-09-02 15:17:06 +00:00
|
|
|
}
|
2012-01-30 16:02:31 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2008-05-26 22:31:25 +00:00
|
|
|
|
2022-12-13 15:46:52 +00:00
|
|
|
static void xencons_remove(struct xenbus_device *dev)
|
2012-01-30 16:02:31 +00:00
|
|
|
{
|
2022-12-13 15:46:52 +00:00
|
|
|
xen_console_remove(dev_get_drvdata(&dev->dev));
|
2012-01-30 16:02:31 +00:00
|
|
|
}
|
2007-07-18 01:37:06 +00:00
|
|
|
|
2012-01-30 16:02:31 +00:00
|
|
|
static int xencons_connect_backend(struct xenbus_device *dev,
|
|
|
|
struct xencons_info *info)
|
|
|
|
{
|
|
|
|
int ret, evtchn, devid, ref, irq;
|
|
|
|
struct xenbus_transaction xbt;
|
|
|
|
grant_ref_t gref_head;
|
|
|
|
|
|
|
|
ret = xenbus_alloc_evtchn(dev, &evtchn);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
info->evtchn = evtchn;
|
2023-10-20 16:15:27 +00:00
|
|
|
irq = bind_evtchn_to_irq_lateeoi(evtchn);
|
2012-01-30 16:02:31 +00:00
|
|
|
if (irq < 0)
|
|
|
|
return irq;
|
|
|
|
info->irq = irq;
|
|
|
|
devid = dev->nodename[strlen(dev->nodename) - 1] - '0';
|
|
|
|
info->hvc = hvc_alloc(xenbus_devid_to_vtermno(devid),
|
|
|
|
irq, &domU_hvc_ops, 256);
|
|
|
|
if (IS_ERR(info->hvc))
|
|
|
|
return PTR_ERR(info->hvc);
|
|
|
|
ret = gnttab_alloc_grant_references(1, &gref_head);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
info->gntref = gref_head;
|
|
|
|
ref = gnttab_claim_grant_reference(&gref_head);
|
|
|
|
if (ref < 0)
|
|
|
|
return ref;
|
|
|
|
gnttab_grant_foreign_access_ref(ref, info->xbdev->otherend_id,
|
2015-08-07 16:34:40 +00:00
|
|
|
virt_to_gfn(info->intf), 0);
|
2012-01-30 16:02:31 +00:00
|
|
|
|
|
|
|
again:
|
|
|
|
ret = xenbus_transaction_start(&xbt);
|
|
|
|
if (ret) {
|
|
|
|
xenbus_dev_fatal(dev, ret, "starting transaction");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
ret = xenbus_printf(xbt, dev->nodename, "ring-ref", "%d", ref);
|
|
|
|
if (ret)
|
|
|
|
goto error_xenbus;
|
|
|
|
ret = xenbus_printf(xbt, dev->nodename, "port", "%u",
|
|
|
|
evtchn);
|
|
|
|
if (ret)
|
|
|
|
goto error_xenbus;
|
|
|
|
ret = xenbus_transaction_end(xbt, 0);
|
|
|
|
if (ret) {
|
|
|
|
if (ret == -EAGAIN)
|
|
|
|
goto again;
|
|
|
|
xenbus_dev_fatal(dev, ret, "completing transaction");
|
|
|
|
return ret;
|
|
|
|
}
|
2008-05-26 22:31:25 +00:00
|
|
|
|
2012-01-30 16:02:31 +00:00
|
|
|
xenbus_switch_state(dev, XenbusStateInitialised);
|
2007-07-18 01:37:06 +00:00
|
|
|
return 0;
|
2012-01-30 16:02:31 +00:00
|
|
|
|
|
|
|
error_xenbus:
|
|
|
|
xenbus_transaction_end(xbt, 1);
|
|
|
|
xenbus_dev_fatal(dev, ret, "writing xenstore");
|
|
|
|
return ret;
|
2007-07-18 01:37:06 +00:00
|
|
|
}
|
|
|
|
|
2012-11-19 18:21:50 +00:00
|
|
|
static int xencons_probe(struct xenbus_device *dev,
|
2012-01-30 16:02:31 +00:00
|
|
|
const struct xenbus_device_id *id)
|
2008-05-26 22:31:25 +00:00
|
|
|
{
|
2012-01-30 16:02:31 +00:00
|
|
|
int ret, devid;
|
|
|
|
struct xencons_info *info;
|
2022-11-30 16:36:02 +00:00
|
|
|
unsigned long flags;
|
2012-01-30 16:02:31 +00:00
|
|
|
|
|
|
|
devid = dev->nodename[strlen(dev->nodename) - 1] - '0';
|
|
|
|
if (devid == 0)
|
|
|
|
return -ENODEV;
|
|
|
|
|
2012-05-15 08:47:47 +00:00
|
|
|
info = kzalloc(sizeof(struct xencons_info), GFP_KERNEL);
|
2012-01-30 16:02:31 +00:00
|
|
|
if (!info)
|
2012-05-15 08:47:47 +00:00
|
|
|
return -ENOMEM;
|
2022-11-30 15:09:11 +00:00
|
|
|
spin_lock_init(&info->ring_lock);
|
2012-01-30 16:02:31 +00:00
|
|
|
dev_set_drvdata(&dev->dev, info);
|
|
|
|
info->xbdev = dev;
|
|
|
|
info->vtermno = xenbus_devid_to_vtermno(devid);
|
|
|
|
info->intf = (void *)__get_free_page(GFP_KERNEL | __GFP_ZERO);
|
|
|
|
if (!info->intf)
|
|
|
|
goto error_nomem;
|
|
|
|
|
|
|
|
ret = xencons_connect_backend(dev, info);
|
|
|
|
if (ret < 0)
|
|
|
|
goto error;
|
2022-11-30 16:36:02 +00:00
|
|
|
spin_lock_irqsave(&xencons_lock, flags);
|
2012-01-30 16:02:31 +00:00
|
|
|
list_add_tail(&info->list, &xenconsoles);
|
2022-11-30 16:36:02 +00:00
|
|
|
spin_unlock_irqrestore(&xencons_lock, flags);
|
2012-01-30 16:02:31 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
error_nomem:
|
|
|
|
ret = -ENOMEM;
|
|
|
|
xenbus_dev_fatal(dev, ret, "allocating device memory");
|
|
|
|
error:
|
|
|
|
xencons_disconnect_backend(info);
|
|
|
|
xencons_free(info);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int xencons_resume(struct xenbus_device *dev)
|
|
|
|
{
|
|
|
|
struct xencons_info *info = dev_get_drvdata(&dev->dev);
|
|
|
|
|
|
|
|
xencons_disconnect_backend(info);
|
2015-05-05 15:58:43 +00:00
|
|
|
memset(info->intf, 0, XEN_PAGE_SIZE);
|
2012-01-30 16:02:31 +00:00
|
|
|
return xencons_connect_backend(dev, info);
|
2008-05-26 22:31:25 +00:00
|
|
|
}
|
|
|
|
|
2012-01-30 16:02:31 +00:00
|
|
|
static void xencons_backend_changed(struct xenbus_device *dev,
|
|
|
|
enum xenbus_state backend_state)
|
|
|
|
{
|
|
|
|
switch (backend_state) {
|
|
|
|
case XenbusStateReconfiguring:
|
|
|
|
case XenbusStateReconfigured:
|
|
|
|
case XenbusStateInitialising:
|
|
|
|
case XenbusStateInitialised:
|
|
|
|
case XenbusStateUnknown:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XenbusStateInitWait:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XenbusStateConnected:
|
|
|
|
xenbus_switch_state(dev, XenbusStateConnected);
|
|
|
|
break;
|
|
|
|
|
2012-09-21 16:04:24 +00:00
|
|
|
case XenbusStateClosed:
|
|
|
|
if (dev->state == XenbusStateClosed)
|
|
|
|
break;
|
2020-08-23 22:36:59 +00:00
|
|
|
fallthrough; /* Missed the backend's CLOSING state */
|
hvc/xen: fix console unplug
On unplug of a Xen console, xencons_disconnect_backend() unconditionally
calls free_irq() via unbind_from_irqhandler(), causing a warning of
freeing an already-free IRQ:
(qemu) device_del con1
[ 32.050919] ------------[ cut here ]------------
[ 32.050942] Trying to free already-free IRQ 33
[ 32.050990] WARNING: CPU: 0 PID: 51 at kernel/irq/manage.c:1895 __free_irq+0x1d4/0x330
It should be using evtchn_put() to tear down the event channel binding,
and let the Linux IRQ side of it be handled by notifier_del_irq() through
the HVC code.
On which topic... xencons_disconnect_backend() should call hvc_remove()
*first*, rather than tearing down the event channel and grant mapping
while they are in use. And then the IRQ is guaranteed to be freed by
the time it's torn down by evtchn_put().
Since evtchn_put() also closes the actual event channel, avoid calling
xenbus_free_evtchn() except in the failure path where the IRQ was not
successfully set up.
However, calling hvc_remove() at the start of xencons_disconnect_backend()
still isn't early enough. An unplug request is indicated by the backend
setting its state to XenbusStateClosing, which triggers a notification
to xencons_backend_changed(), which... does nothing except set its own
frontend state directly to XenbusStateClosed without *actually* tearing
down the HVC device or, you know, making sure it isn't actively in use.
So the backend sees the guest frontend set its state to XenbusStateClosed
and stops servicing the interrupt... and the guest spins for ever in the
domU_write_console() function waiting for the ring to drain.
Fix that one by calling hvc_remove() from xencons_backend_changed() before
signalling to the backend that it's OK to proceed with the removal.
Tested with 'dd if=/dev/zero of=/dev/hvc1' while telling Qemu to remove
the console device.
Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
Cc: stable@vger.kernel.org
Link: https://lore.kernel.org/r/20231020161529.355083-4-dwmw2@infradead.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2023-10-20 16:15:29 +00:00
|
|
|
case XenbusStateClosing: {
|
2024-03-15 09:17:34 +00:00
|
|
|
struct xencons_info *info = dev_get_drvdata(&dev->dev);
|
hvc/xen: fix console unplug
On unplug of a Xen console, xencons_disconnect_backend() unconditionally
calls free_irq() via unbind_from_irqhandler(), causing a warning of
freeing an already-free IRQ:
(qemu) device_del con1
[ 32.050919] ------------[ cut here ]------------
[ 32.050942] Trying to free already-free IRQ 33
[ 32.050990] WARNING: CPU: 0 PID: 51 at kernel/irq/manage.c:1895 __free_irq+0x1d4/0x330
It should be using evtchn_put() to tear down the event channel binding,
and let the Linux IRQ side of it be handled by notifier_del_irq() through
the HVC code.
On which topic... xencons_disconnect_backend() should call hvc_remove()
*first*, rather than tearing down the event channel and grant mapping
while they are in use. And then the IRQ is guaranteed to be freed by
the time it's torn down by evtchn_put().
Since evtchn_put() also closes the actual event channel, avoid calling
xenbus_free_evtchn() except in the failure path where the IRQ was not
successfully set up.
However, calling hvc_remove() at the start of xencons_disconnect_backend()
still isn't early enough. An unplug request is indicated by the backend
setting its state to XenbusStateClosing, which triggers a notification
to xencons_backend_changed(), which... does nothing except set its own
frontend state directly to XenbusStateClosed without *actually* tearing
down the HVC device or, you know, making sure it isn't actively in use.
So the backend sees the guest frontend set its state to XenbusStateClosed
and stops servicing the interrupt... and the guest spins for ever in the
domU_write_console() function waiting for the ring to drain.
Fix that one by calling hvc_remove() from xencons_backend_changed() before
signalling to the backend that it's OK to proceed with the removal.
Tested with 'dd if=/dev/zero of=/dev/hvc1' while telling Qemu to remove
the console device.
Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
Cc: stable@vger.kernel.org
Link: https://lore.kernel.org/r/20231020161529.355083-4-dwmw2@infradead.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2023-10-20 16:15:29 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Don't tear down the evtchn and grant ref before the other
|
|
|
|
* end has disconnected, but do stop userspace from trying
|
|
|
|
* to use the device before we allow the backend to close.
|
|
|
|
*/
|
|
|
|
if (info->hvc) {
|
|
|
|
hvc_remove(info->hvc);
|
|
|
|
info->hvc = NULL;
|
|
|
|
}
|
|
|
|
|
2012-01-30 16:02:31 +00:00
|
|
|
xenbus_frontend_closed(dev);
|
|
|
|
break;
|
|
|
|
}
|
hvc/xen: fix console unplug
On unplug of a Xen console, xencons_disconnect_backend() unconditionally
calls free_irq() via unbind_from_irqhandler(), causing a warning of
freeing an already-free IRQ:
(qemu) device_del con1
[ 32.050919] ------------[ cut here ]------------
[ 32.050942] Trying to free already-free IRQ 33
[ 32.050990] WARNING: CPU: 0 PID: 51 at kernel/irq/manage.c:1895 __free_irq+0x1d4/0x330
It should be using evtchn_put() to tear down the event channel binding,
and let the Linux IRQ side of it be handled by notifier_del_irq() through
the HVC code.
On which topic... xencons_disconnect_backend() should call hvc_remove()
*first*, rather than tearing down the event channel and grant mapping
while they are in use. And then the IRQ is guaranteed to be freed by
the time it's torn down by evtchn_put().
Since evtchn_put() also closes the actual event channel, avoid calling
xenbus_free_evtchn() except in the failure path where the IRQ was not
successfully set up.
However, calling hvc_remove() at the start of xencons_disconnect_backend()
still isn't early enough. An unplug request is indicated by the backend
setting its state to XenbusStateClosing, which triggers a notification
to xencons_backend_changed(), which... does nothing except set its own
frontend state directly to XenbusStateClosed without *actually* tearing
down the HVC device or, you know, making sure it isn't actively in use.
So the backend sees the guest frontend set its state to XenbusStateClosed
and stops servicing the interrupt... and the guest spins for ever in the
domU_write_console() function waiting for the ring to drain.
Fix that one by calling hvc_remove() from xencons_backend_changed() before
signalling to the backend that it's OK to proceed with the removal.
Tested with 'dd if=/dev/zero of=/dev/hvc1' while telling Qemu to remove
the console device.
Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
Cc: stable@vger.kernel.org
Link: https://lore.kernel.org/r/20231020161529.355083-4-dwmw2@infradead.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2023-10-20 16:15:29 +00:00
|
|
|
}
|
2012-01-30 16:02:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static const struct xenbus_device_id xencons_ids[] = {
|
|
|
|
{ "console" },
|
|
|
|
{ "" }
|
|
|
|
};
|
|
|
|
|
2014-09-08 16:30:41 +00:00
|
|
|
static struct xenbus_driver xencons_driver = {
|
|
|
|
.name = "xenconsole",
|
|
|
|
.ids = xencons_ids,
|
2012-02-21 11:30:42 +00:00
|
|
|
.probe = xencons_probe,
|
|
|
|
.remove = xencons_remove,
|
|
|
|
.resume = xencons_resume,
|
|
|
|
.otherend_changed = xencons_backend_changed,
|
2021-10-22 06:47:58 +00:00
|
|
|
.not_essential = true,
|
2014-09-08 16:30:41 +00:00
|
|
|
};
|
2012-02-21 11:30:42 +00:00
|
|
|
#endif /* CONFIG_HVC_XEN_FRONTEND */
|
|
|
|
|
|
|
|
static int __init xen_hvc_init(void)
|
|
|
|
{
|
|
|
|
int r;
|
|
|
|
struct xencons_info *info;
|
|
|
|
const struct hv_ops *ops;
|
|
|
|
|
|
|
|
if (!xen_domain())
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
if (xen_initial_domain()) {
|
|
|
|
ops = &dom0_hvc_ops;
|
|
|
|
r = xen_initial_domain_console_init();
|
|
|
|
if (r < 0)
|
2023-10-20 16:15:28 +00:00
|
|
|
goto register_fe;
|
2012-02-21 11:30:42 +00:00
|
|
|
info = vtermno_to_xencons(HVC_COOKIE);
|
|
|
|
} else {
|
|
|
|
ops = &domU_hvc_ops;
|
|
|
|
if (xen_hvm_domain())
|
|
|
|
r = xen_hvm_console_init();
|
|
|
|
else
|
|
|
|
r = xen_pv_console_init();
|
|
|
|
if (r < 0)
|
2023-10-20 16:15:28 +00:00
|
|
|
goto register_fe;
|
2012-02-21 11:30:42 +00:00
|
|
|
|
|
|
|
info = vtermno_to_xencons(HVC_COOKIE);
|
2021-12-16 07:24:08 +00:00
|
|
|
info->irq = bind_evtchn_to_irq_lateeoi(info->evtchn);
|
2012-02-21 11:30:42 +00:00
|
|
|
}
|
|
|
|
if (info->irq < 0)
|
|
|
|
info->irq = 0; /* NO_IRQ */
|
|
|
|
else
|
|
|
|
irq_set_noprobe(info->irq);
|
|
|
|
|
|
|
|
info->hvc = hvc_alloc(HVC_COOKIE, info->irq, ops, 256);
|
|
|
|
if (IS_ERR(info->hvc)) {
|
2022-11-30 16:36:02 +00:00
|
|
|
unsigned long flags;
|
|
|
|
|
2012-02-21 11:30:42 +00:00
|
|
|
r = PTR_ERR(info->hvc);
|
2022-11-30 16:36:02 +00:00
|
|
|
spin_lock_irqsave(&xencons_lock, flags);
|
2012-02-21 11:30:42 +00:00
|
|
|
list_del(&info->list);
|
2022-11-30 16:36:02 +00:00
|
|
|
spin_unlock_irqrestore(&xencons_lock, flags);
|
2012-02-21 11:30:42 +00:00
|
|
|
if (info->irq)
|
hvc/xen: fix console unplug
On unplug of a Xen console, xencons_disconnect_backend() unconditionally
calls free_irq() via unbind_from_irqhandler(), causing a warning of
freeing an already-free IRQ:
(qemu) device_del con1
[ 32.050919] ------------[ cut here ]------------
[ 32.050942] Trying to free already-free IRQ 33
[ 32.050990] WARNING: CPU: 0 PID: 51 at kernel/irq/manage.c:1895 __free_irq+0x1d4/0x330
It should be using evtchn_put() to tear down the event channel binding,
and let the Linux IRQ side of it be handled by notifier_del_irq() through
the HVC code.
On which topic... xencons_disconnect_backend() should call hvc_remove()
*first*, rather than tearing down the event channel and grant mapping
while they are in use. And then the IRQ is guaranteed to be freed by
the time it's torn down by evtchn_put().
Since evtchn_put() also closes the actual event channel, avoid calling
xenbus_free_evtchn() except in the failure path where the IRQ was not
successfully set up.
However, calling hvc_remove() at the start of xencons_disconnect_backend()
still isn't early enough. An unplug request is indicated by the backend
setting its state to XenbusStateClosing, which triggers a notification
to xencons_backend_changed(), which... does nothing except set its own
frontend state directly to XenbusStateClosed without *actually* tearing
down the HVC device or, you know, making sure it isn't actively in use.
So the backend sees the guest frontend set its state to XenbusStateClosed
and stops servicing the interrupt... and the guest spins for ever in the
domU_write_console() function waiting for the ring to drain.
Fix that one by calling hvc_remove() from xencons_backend_changed() before
signalling to the backend that it's OK to proceed with the removal.
Tested with 'dd if=/dev/zero of=/dev/hvc1' while telling Qemu to remove
the console device.
Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
Cc: stable@vger.kernel.org
Link: https://lore.kernel.org/r/20231020161529.355083-4-dwmw2@infradead.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2023-10-20 16:15:29 +00:00
|
|
|
evtchn_put(info->evtchn);
|
2012-02-21 11:30:42 +00:00
|
|
|
kfree(info);
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
r = 0;
|
2023-10-20 16:15:28 +00:00
|
|
|
register_fe:
|
2012-02-21 11:30:42 +00:00
|
|
|
#ifdef CONFIG_HVC_XEN_FRONTEND
|
|
|
|
r = xenbus_register_frontend(&xencons_driver);
|
|
|
|
#endif
|
|
|
|
return r;
|
|
|
|
}
|
2014-01-15 21:35:43 +00:00
|
|
|
device_initcall(xen_hvc_init);
|
2007-07-18 01:37:06 +00:00
|
|
|
|
|
|
|
static int xen_cons_init(void)
|
|
|
|
{
|
2012-01-27 18:31:36 +00:00
|
|
|
const struct hv_ops *ops;
|
2010-09-02 15:17:06 +00:00
|
|
|
|
2012-01-27 18:31:36 +00:00
|
|
|
if (!xen_domain())
|
2007-07-18 01:37:06 +00:00
|
|
|
return 0;
|
|
|
|
|
2010-09-02 15:17:06 +00:00
|
|
|
if (xen_initial_domain())
|
|
|
|
ops = &dom0_hvc_ops;
|
2012-01-27 18:31:36 +00:00
|
|
|
else {
|
2012-01-30 16:02:31 +00:00
|
|
|
int r;
|
2010-09-02 15:17:06 +00:00
|
|
|
ops = &domU_hvc_ops;
|
|
|
|
|
2012-01-30 16:02:31 +00:00
|
|
|
if (xen_hvm_domain())
|
|
|
|
r = xen_hvm_console_init();
|
2012-01-27 18:31:36 +00:00
|
|
|
else
|
2012-01-30 16:02:31 +00:00
|
|
|
r = xen_pv_console_init();
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
2012-01-27 18:31:36 +00:00
|
|
|
}
|
|
|
|
|
2010-09-02 15:17:06 +00:00
|
|
|
hvc_instantiate(HVC_COOKIE, 0, ops);
|
2007-07-18 01:37:06 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
console_initcall(xen_cons_init);
|
|
|
|
|
2016-02-25 12:10:39 +00:00
|
|
|
#ifdef CONFIG_X86
|
|
|
|
static void xen_hvm_early_write(uint32_t vtermno, const char *str, int len)
|
|
|
|
{
|
|
|
|
if (xen_cpuid_base())
|
|
|
|
outsb(0xe9, str, len);
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
static void xen_hvm_early_write(uint32_t vtermno, const char *str, int len) { }
|
|
|
|
#endif
|
|
|
|
|
2008-05-26 22:31:00 +00:00
|
|
|
#ifdef CONFIG_EARLY_PRINTK
|
2020-06-19 17:22:40 +00:00
|
|
|
static int __init xenboot_console_setup(struct console *console, char *string)
|
2016-02-25 12:10:38 +00:00
|
|
|
{
|
|
|
|
static struct xencons_info xenboot;
|
|
|
|
|
2021-09-30 12:18:45 +00:00
|
|
|
if (xen_initial_domain() || !xen_pv_domain())
|
2016-02-25 12:10:38 +00:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
return xencons_info_pv_init(&xenboot, 0);
|
|
|
|
}
|
|
|
|
|
2007-07-18 01:37:06 +00:00
|
|
|
static void xenboot_write_console(struct console *console, const char *string,
|
|
|
|
unsigned len)
|
|
|
|
{
|
|
|
|
unsigned int linelen, off = 0;
|
|
|
|
const char *pos;
|
|
|
|
|
2021-09-30 12:17:41 +00:00
|
|
|
if (dom0_write_console(0, string, len) >= 0)
|
|
|
|
return;
|
|
|
|
|
2016-02-25 12:10:39 +00:00
|
|
|
if (!xen_pv_domain()) {
|
|
|
|
xen_hvm_early_write(0, string, len);
|
2012-01-27 18:31:36 +00:00
|
|
|
return;
|
2016-02-25 12:10:39 +00:00
|
|
|
}
|
2012-01-27 18:31:36 +00:00
|
|
|
|
2021-09-30 12:17:41 +00:00
|
|
|
if (domU_write_console(0, "(early) ", 8) < 0)
|
2010-09-02 15:17:06 +00:00
|
|
|
return;
|
2007-07-18 01:37:06 +00:00
|
|
|
while (off < len && NULL != (pos = strchr(string+off, '\n'))) {
|
|
|
|
linelen = pos-string+off;
|
|
|
|
if (off + linelen > len)
|
|
|
|
break;
|
2010-09-02 15:17:06 +00:00
|
|
|
domU_write_console(0, string+off, linelen);
|
|
|
|
domU_write_console(0, "\r\n", 2);
|
2007-07-18 01:37:06 +00:00
|
|
|
off += linelen + 1;
|
|
|
|
}
|
|
|
|
if (off < len)
|
2010-09-02 15:17:06 +00:00
|
|
|
domU_write_console(0, string+off, len-off);
|
2007-07-18 01:37:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
struct console xenboot_console = {
|
|
|
|
.name = "xenboot",
|
|
|
|
.write = xenboot_write_console,
|
2020-06-19 17:22:40 +00:00
|
|
|
.setup = xenboot_console_setup,
|
2008-05-26 22:31:00 +00:00
|
|
|
.flags = CON_PRINTBUFFER | CON_BOOT | CON_ANYTIME,
|
2013-10-01 18:00:49 +00:00
|
|
|
.index = -1,
|
2007-07-18 01:37:06 +00:00
|
|
|
};
|
2008-05-26 22:31:00 +00:00
|
|
|
#endif /* CONFIG_EARLY_PRINTK */
|
2008-05-26 22:30:59 +00:00
|
|
|
|
|
|
|
void xen_raw_console_write(const char *str)
|
|
|
|
{
|
2013-09-27 21:18:13 +00:00
|
|
|
ssize_t len = strlen(str);
|
|
|
|
int rc = 0;
|
|
|
|
|
|
|
|
if (xen_domain()) {
|
|
|
|
rc = dom0_write_console(0, str, len);
|
2016-02-25 12:10:39 +00:00
|
|
|
if (rc != -ENOSYS || !xen_hvm_domain())
|
|
|
|
return;
|
2013-09-27 21:18:13 +00:00
|
|
|
}
|
2016-02-25 12:10:39 +00:00
|
|
|
xen_hvm_early_write(0, str, len);
|
2008-05-26 22:30:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void xen_raw_printk(const char *fmt, ...)
|
|
|
|
{
|
|
|
|
static char buf[512];
|
|
|
|
va_list ap;
|
|
|
|
|
|
|
|
va_start(ap, fmt);
|
|
|
|
vsnprintf(buf, sizeof(buf), fmt, ap);
|
|
|
|
va_end(ap);
|
|
|
|
|
|
|
|
xen_raw_console_write(buf);
|
|
|
|
}
|
2016-02-25 12:10:37 +00:00
|
|
|
|
|
|
|
static void xenboot_earlycon_write(struct console *console,
|
|
|
|
const char *string,
|
|
|
|
unsigned len)
|
|
|
|
{
|
|
|
|
dom0_write_console(0, string, len);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int __init xenboot_earlycon_setup(struct earlycon_device *device,
|
|
|
|
const char *opt)
|
|
|
|
{
|
|
|
|
device->con->write = xenboot_earlycon_write;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EARLYCON_DECLARE(xenboot, xenboot_earlycon_setup);
|