On Tue, 20 Jan 2026, Jan Beulich wrote:
> On 20.01.2026 00:23, Stefano Stabellini wrote:
> > On Mon, 19 Jan 2026, Jan Beulich wrote:
> >> On 14.01.2026 01:39, Stefano Stabellini wrote:
> >>> @@ -815,6 +831,11 @@ long do_console_io(
> >>> if ( count > INT_MAX )
> >>> break;
> >>>
> >>> + d = console_get_domain();
> >>> + console_put_domain(d);
> >>> + if ( d != current->domain )
> >>> + return 0;
> >>
> >> This isn't atomic (as in: in a suitably locked region) with ...
> >>
> >>> @@ -830,7 +851,10 @@ long do_console_io(
> >>> break;
> >>> }
> >>> rc += len;
> >>> - serial_rx_cons += len;
> >>> + nrspin_lock_irq(&console_lock);
> >>> + if ( serial_rx_cons != serial_rx_prod )
> >>> + serial_rx_cons += len;
> >>> + nrspin_unlock_irq(&console_lock);
> >>> }
> >>> break;
> >>
> >> ... this. If the focus domain changes after the check in the earlier hunk,
> >> I think you need to also return with no input here. Or you need to acquire
> >> the lock earlier (and then similarly in console_switch_input()), albeit
> >> that would then mean holding it across a copy-to-guest. Which technically
> >> is perhaps not a problem, but it leaves an uneasy feeling.
> >
> > I thought about it when writing this patch and I had the same feeling as
> > you. However, especially considering the other feedback, I don't see
> > another viable solution.
>
> Taking just the logic here, an option might be to re-check the focus domain
> once holding the lock, and discard the most recent chunk of input from what
> would go back to the caller if the focus changed. But that would come with
> its own new complexities.
I attempted to make changes on top of v4 so that copy_to_guest_offset is
not called with the lock held.
I did introduce a focus domain check in the CONSOLEIO_read while loop,
but didn't discard input because now the focus domain is only changed
with the console_lock held and also the while loop is executed with the
console_lock held (except for copy_to_guest_offset) so as far as I can
tell it shouldn't be possible that a domain is reading someone else's
input data.
>From 71de8ddd0ce31090362115a3d54d1ebe161c229f Mon Sep 17 00:00:00 2001
From: Stefano Stabellini <[email protected]>
Date: Fri, 16 Jan 2026 13:07:43 -0800
Subject: [PATCH v5] xen/console: handle multiple domains using console_io
hypercalls
Allow multiple dom0less domains to use the console_io hypercalls to
print to the console. Handle them in a similar way to vpl011: only the
domain which has focus can read from the console. All domains can write
to the console but the ones without focus have a prefix. In this case
the prefix is applied by using guest_printk instead of printk or
console_puts which is what the original code was already doing.
When switching focus using Ctrl-AAA, discard any unread data in the
input buffer. Input is read quickly and the user would be aware of it
being slow or stuck as they use Ctrl-AAA to switch focus domain.
In that situation, it is to be expected that the unread input is lost.
The domain writes are buffered when the domain is not in focus. Push out
the buffer when the domain enters focus.
Add the console_lock around serial_rx_prod and serial_rx_cons
modifications to protect it against concurrent writes to it. Also
protect against changes of domain with focus while reading from the
serial.
Signed-off-by: Stefano Stabellini <[email protected]>
---
xen/drivers/char/console.c | 56 ++++++++++++++++++++++++++++++++------
1 file changed, 47 insertions(+), 9 deletions(-)
diff --git a/xen/drivers/char/console.c b/xen/drivers/char/console.c
index 7b176da71a..5c621b39bd 100644
--- a/xen/drivers/char/console.c
+++ b/xen/drivers/char/console.c
@@ -521,6 +521,8 @@ struct domain *console_get_domain(void)
{
struct domain *d;
+ nrspin_lock_irq(&console_lock);
+
if ( console_rx == 0 )
return NULL;
@@ -540,6 +542,8 @@ void console_put_domain(struct domain *d)
{
if ( d )
rcu_unlock_domain(d);
+
+ nrspin_unlock_irq(&console_lock);
}
static void console_switch_input(void)
@@ -574,8 +578,12 @@ static void console_switch_input(void)
if ( !d->console.input_allowed )
continue;
- console_rx = next_rx;
printk("*** Serial input to DOM%u", domid);
+ nrspin_lock_irq(&console_lock);
+ console_rx = next_rx;
+ /* Don't let the next dom read the previous dom's unread data. */
+ serial_rx_cons = serial_rx_prod;
+ nrspin_unlock_irq(&console_lock);
break;
}
}
@@ -596,8 +604,19 @@ static void __serial_rx(char c)
d = console_get_domain();
if ( !d )
+ {
+ console_put_domain(d);
return;
+ }
+#ifdef CONFIG_SBSA_VUART_CONSOLE
+ /* Prioritize vpl011 if enabled for this domain */
+ if ( d->arch.vpl011.base_addr )
+ {
+ /* Deliver input to the emulated UART. */
+ rc = vpl011_rx_char_xen(d, c);
+ } else
+#endif
if ( is_hardware_domain(d) || IS_ENABLED(CONFIG_DOM0LESS_BOOT) )
{
/*
@@ -613,11 +632,6 @@ static void __serial_rx(char c)
*/
send_guest_domain_virq(d, VIRQ_CONSOLE);
}
-#ifdef CONFIG_SBSA_VUART_CONSOLE
- else
- /* Deliver input to the emulated UART. */
- rc = vpl011_rx_char_xen(d, c);
-#endif
if ( consoled_is_enabled() )
/* Deliver input to the PV shim console. */
@@ -729,6 +743,7 @@ static long
guest_console_write(XEN_GUEST_HANDLE_PARAM(char) buffer,
unsigned int flags = opt_console_to_ring
? CONSOLE_ALL : CONSOLE_DEFAULT;
struct domain *cd = current->domain;
+ struct domain *input;
while ( count > 0 )
{
@@ -741,17 +756,23 @@ static long
guest_console_write(XEN_GUEST_HANDLE_PARAM(char) buffer,
if ( copy_from_guest(kbuf, buffer, kcount) )
return -EFAULT;
- if ( is_hardware_domain(cd) )
+ input = console_get_domain();
+ if ( input && cd == input )
{
+ if ( cd->pbuf_idx )
+ {
+ console_send(cd->pbuf, cd->pbuf_idx, flags);
+ cd->pbuf_idx = 0;
+ }
/* Use direct console output as it could be interactive */
- nrspin_lock_irq(&console_lock);
console_send(kbuf, kcount, flags);
- nrspin_unlock_irq(&console_lock);
+ console_put_domain(input);
}
else
{
char *kin = kbuf, *kout = kbuf, c;
+ console_put_domain(input);
/* Strip non-printable characters */
do
{
@@ -793,6 +814,7 @@ long do_console_io(
{
long rc;
unsigned int idx, len;
+ struct domain *d;
rc = xsm_console_io(XSM_OTHER, current->domain, cmd);
if ( rc )
@@ -813,6 +835,13 @@ long do_console_io(
if ( count > INT_MAX )
break;
+ d = console_get_domain();
+ if ( d != current->domain )
+ {
+ console_put_domain(d);
+ return 0;
+ }
+
rc = 0;
while ( (serial_rx_cons != serial_rx_prod) && (rc < count) )
{
@@ -822,14 +851,23 @@ long do_console_io(
len = SERIAL_RX_SIZE - idx;
if ( (rc + len) > count )
len = count - rc;
+
+ console_put_domain(d);
if ( copy_to_guest_offset(buffer, rc, &serial_rx_ring[idx], len) )
{
rc = -EFAULT;
break;
}
+ d = console_get_domain();
+ if ( d != current->domain )
+ {
+ console_put_domain(d);
+ break;
+ }
rc += len;
serial_rx_cons += len;
}
+ console_put_domain(d);
break;
default:
rc = -ENOSYS;
--
2.25.1diff --git a/xen/drivers/char/console.c b/xen/drivers/char/console.c
index acfc49d558..5c621b39bd 100644
--- a/xen/drivers/char/console.c
+++ b/xen/drivers/char/console.c
@@ -521,6 +521,8 @@ struct domain *console_get_domain(void)
{
struct domain *d;
+ nrspin_lock_irq(&console_lock);
+
if ( console_rx == 0 )
return NULL;
@@ -540,6 +542,8 @@ void console_put_domain(struct domain *d)
{
if ( d )
rcu_unlock_domain(d);
+
+ nrspin_unlock_irq(&console_lock);
}
static void console_switch_input(void)
@@ -598,11 +602,10 @@ static void __serial_rx(char c)
if ( console_rx == 0 )
return handle_keypress(c, false);
- nrspin_lock_irq(&console_lock);
d = console_get_domain();
if ( !d )
{
- nrspin_unlock_irq(&console_lock);
+ console_put_domain(d);
return;
}
@@ -640,7 +643,6 @@ static void __serial_rx(char c)
rc);
console_put_domain(d);
- nrspin_unlock_irq(&console_lock);
}
static void cf_check serial_rx(char c)
@@ -754,7 +756,6 @@ static long guest_console_write(XEN_GUEST_HANDLE_PARAM(char) buffer,
if ( copy_from_guest(kbuf, buffer, kcount) )
return -EFAULT;
- nrspin_lock_irq(&console_lock);
input = console_get_domain();
if ( input && cd == input )
{
@@ -766,14 +767,12 @@ static long guest_console_write(XEN_GUEST_HANDLE_PARAM(char) buffer,
/* Use direct console output as it could be interactive */
console_send(kbuf, kcount, flags);
console_put_domain(input);
- nrspin_unlock_irq(&console_lock);
}
else
{
char *kin = kbuf, *kout = kbuf, c;
console_put_domain(input);
- nrspin_unlock_irq(&console_lock);
/* Strip non-printable characters */
do
{
@@ -836,12 +835,10 @@ long do_console_io(
if ( count > INT_MAX )
break;
- nrspin_lock_irq(&console_lock);
d = console_get_domain();
if ( d != current->domain )
{
console_put_domain(d);
- nrspin_unlock_irq(&console_lock);
return 0;
}
@@ -854,18 +851,23 @@ long do_console_io(
len = SERIAL_RX_SIZE - idx;
if ( (rc + len) > count )
len = count - rc;
+
+ console_put_domain(d);
if ( copy_to_guest_offset(buffer, rc, &serial_rx_ring[idx], len) )
{
rc = -EFAULT;
+ break;
+ }
+ d = console_get_domain();
+ if ( d != current->domain )
+ {
console_put_domain(d);
- nrspin_unlock_irq(&console_lock);
break;
}
rc += len;
serial_rx_cons += len;
}
console_put_domain(d);
- nrspin_unlock_irq(&console_lock);
break;
default:
rc = -ENOSYS;