diff mbox series

[v2] xen/console: Skip switching serial input to non existing domains

Message ID 20230316102635.6497-1-michal.orzel@amd.com (mailing list archive)
State Superseded
Headers show
Series [v2] xen/console: Skip switching serial input to non existing domains | expand

Commit Message

Michal Orzel March 16, 2023, 10:26 a.m. UTC
At the moment, we direct serial input to hardware domain by default.
This does not make any sense when running in true dom0less mode, since
such domain does not exist. As a result, users wishing to write to
an emulated UART of a domU are always forced to execute CTRL-AAA first.
The same issue is when rotating among serial inputs, where we always
have to go through hardware domain case. This problem can be elaborated
further to all the domains that no longer exist.

Modify switch_serial_input() so that we skip switching serial input to
non existing domains.

For now, to minimize the required changes and to match the current
behavior with hwdom, the default input goes to the first real domain.
The choice is more or less arbitrary since dom0less domUs are supposedly
equal. This will be handled in the future by adding support in boot time
configuration for marking a specific domain preferred in terms of
directing serial input to.

Signed-off-by: Michal Orzel <michal.orzel@amd.com>
---
Changes in v2:
 - was: xen/console: Handle true dom0less case when switching serial input
 - use a more generic approach to handle all non-existing domains
---
 xen/drivers/char/console.c | 19 ++++++++++++++++++-
 1 file changed, 18 insertions(+), 1 deletion(-)

Comments

Jan Beulich March 16, 2023, 11:11 a.m. UTC | #1
On 16.03.2023 11:26, Michal Orzel wrote:
> --- a/xen/drivers/char/console.c
> +++ b/xen/drivers/char/console.c
> @@ -490,7 +490,24 @@ static void switch_serial_input(void)
>      }
>      else
>      {
> -        console_rx++;
> +        unsigned int next_rx = console_rx + 1;
> +
> +        /* Skip switching serial input to non existing domains */
> +        while ( next_rx < max_init_domid + 1 )
> +        {
> +            struct domain *d = rcu_lock_domain_by_id(next_rx - 1);
> +
> +            if ( d )
> +            {
> +                rcu_unlock_domain(d);
> +                break;
> +            }
> +
> +            next_rx++;
> +        }
> +
> +        console_rx = next_rx;
> +
>          printk("*** Serial input to DOM%d", console_rx - 1);
>      }

While at the first glance (when you sent it in reply to v1) it looked okay,
I'm afraid it really isn't: Please consider what happens when the last of
the DomU-s doesn't exist anymore. (You don't really check whether it still
exists, because the range check comes ahead of the existence one.) In that
case you want to move from second-to-last to Xen. I expect the entire
if/else construct wants to be inside the loop.

Jan
Michal Orzel March 16, 2023, 2:15 p.m. UTC | #2
On 16/03/2023 12:11, Jan Beulich wrote:
> Caution: This message originated from an External Source. Use proper caution when opening attachments, clicking links, or responding.
> 
> 
> On 16.03.2023 11:26, Michal Orzel wrote:
>> --- a/xen/drivers/char/console.c
>> +++ b/xen/drivers/char/console.c
>> @@ -490,7 +490,24 @@ static void switch_serial_input(void)
>>      }
>>      else
>>      {
>> -        console_rx++;
>> +        unsigned int next_rx = console_rx + 1;
>> +
>> +        /* Skip switching serial input to non existing domains */
>> +        while ( next_rx < max_init_domid + 1 )
>> +        {
>> +            struct domain *d = rcu_lock_domain_by_id(next_rx - 1);
>> +
>> +            if ( d )
>> +            {
>> +                rcu_unlock_domain(d);
>> +                break;
>> +            }
>> +
>> +            next_rx++;
>> +        }
>> +
>> +        console_rx = next_rx;
>> +
>>          printk("*** Serial input to DOM%d", console_rx - 1);
>>      }
> 
> While at the first glance (when you sent it in reply to v1) it looked okay,
> I'm afraid it really isn't: Please consider what happens when the last of
> the DomU-s doesn't exist anymore. (You don't really check whether it still
> exists, because the range check comes ahead of the existence one.) In that
> case you want to move from second-to-last to Xen. I expect the entire
> if/else construct wants to be inside the loop.
I did this deliberately because I do not think the situation you describe is possible
(i.e. no domains at all - Xen still usable). With hardware domain in place, we can e.g. destroy the domain
which would invoke domain_kill() -> domain_destroy() that would free domain struct.
Without hwdom, the domain cannot kill/destroy itself. It can do the shutdown but it will not
destroy it (at least this is what I tested). So I do not think there can be a scenario where
there is not a single domain while Xen running and be usable.

~Michal
George Dunlap March 16, 2023, 2:22 p.m. UTC | #3
On Thu, Mar 16, 2023 at 2:15 PM Michal Orzel <michal.orzel@amd.com> wrote:

>
>
> On 16/03/2023 12:11, Jan Beulich wrote:
> > Caution: This message originated from an External Source. Use proper
> caution when opening attachments, clicking links, or responding.
> >
> >
> > On 16.03.2023 11:26, Michal Orzel wrote:
> >> --- a/xen/drivers/char/console.c
> >> +++ b/xen/drivers/char/console.c
> >> @@ -490,7 +490,24 @@ static void switch_serial_input(void)
> >>      }
> >>      else
> >>      {
> >> -        console_rx++;
> >> +        unsigned int next_rx = console_rx + 1;
> >> +
> >> +        /* Skip switching serial input to non existing domains */
> >> +        while ( next_rx < max_init_domid + 1 )
> >> +        {
> >> +            struct domain *d = rcu_lock_domain_by_id(next_rx - 1);
> >> +
> >> +            if ( d )
> >> +            {
> >> +                rcu_unlock_domain(d);
> >> +                break;
> >> +            }
> >> +
> >> +            next_rx++;
> >> +        }
> >> +
> >> +        console_rx = next_rx;
> >> +
> >>          printk("*** Serial input to DOM%d", console_rx - 1);
> >>      }
> >
> > While at the first glance (when you sent it in reply to v1) it looked
> okay,
> > I'm afraid it really isn't: Please consider what happens when the last of
> > the DomU-s doesn't exist anymore. (You don't really check whether it
> still
> > exists, because the range check comes ahead of the existence one.) In
> that
> > case you want to move from second-to-last to Xen. I expect the entire
> > if/else construct wants to be inside the loop.
> I did this deliberately because I do not think the situation you describe
> is possible
> (i.e. no domains at all - Xen still usable). With hardware domain in
> place, we can e.g. destroy the domain
> which would invoke domain_kill() -> domain_destroy() that would free
> domain struct.
> Without hwdom, the domain cannot kill/destroy itself. It can do the
> shutdown but it will not
> destroy it (at least this is what I tested). So I do not think there can
> be a scenario where
> there is not a single domain while Xen running and be usable.


We've actually been discussing something like this.  Consider if someone
wanted to use Xen as part of a system architected like Amazon's Nitro: You
could have a DPU that ran the "toolstack", and gave Xen commands to create
or destroy domains.  It could dynamically create SRIOV PCI devices to be
passed directly through to guests.  In this case, you might run into a
situation where no VMs existed, and yet the system was not dead.

 -George
Jan Beulich March 16, 2023, 4:02 p.m. UTC | #4
On 16.03.2023 15:15, Michal Orzel wrote:
> 
> 
> On 16/03/2023 12:11, Jan Beulich wrote:
>> Caution: This message originated from an External Source. Use proper caution when opening attachments, clicking links, or responding.
>>
>>
>> On 16.03.2023 11:26, Michal Orzel wrote:
>>> --- a/xen/drivers/char/console.c
>>> +++ b/xen/drivers/char/console.c
>>> @@ -490,7 +490,24 @@ static void switch_serial_input(void)
>>>      }
>>>      else
>>>      {
>>> -        console_rx++;
>>> +        unsigned int next_rx = console_rx + 1;
>>> +
>>> +        /* Skip switching serial input to non existing domains */
>>> +        while ( next_rx < max_init_domid + 1 )
>>> +        {
>>> +            struct domain *d = rcu_lock_domain_by_id(next_rx - 1);
>>> +
>>> +            if ( d )
>>> +            {
>>> +                rcu_unlock_domain(d);
>>> +                break;
>>> +            }
>>> +
>>> +            next_rx++;
>>> +        }
>>> +
>>> +        console_rx = next_rx;
>>> +
>>>          printk("*** Serial input to DOM%d", console_rx - 1);
>>>      }
>>
>> While at the first glance (when you sent it in reply to v1) it looked okay,
>> I'm afraid it really isn't: Please consider what happens when the last of
>> the DomU-s doesn't exist anymore. (You don't really check whether it still
>> exists, because the range check comes ahead of the existence one.) In that
>> case you want to move from second-to-last to Xen. I expect the entire
>> if/else construct wants to be inside the loop.
> I did this deliberately because I do not think the situation you describe is possible
> (i.e. no domains at all - Xen still usable). With hardware domain in place, we can e.g. destroy the domain
> which would invoke domain_kill() -> domain_destroy() that would free domain struct.
> Without hwdom, the domain cannot kill/destroy itself. It can do the shutdown but it will not
> destroy it (at least this is what I tested). So I do not think there can be a scenario where
> there is not a single domain while Xen running and be usable.

I didn't talk about "no domain left at all", but about the case where the
domain with the highest domain ID is gone.

Jan
Stefano Stabellini March 16, 2023, 10:59 p.m. UTC | #5
On Thu, 16 Mar 2023, Jan Beulich wrote:
> On 16.03.2023 11:26, Michal Orzel wrote:
> > --- a/xen/drivers/char/console.c
> > +++ b/xen/drivers/char/console.c
> > @@ -490,7 +490,24 @@ static void switch_serial_input(void)
> >      }
> >      else
> >      {
> > -        console_rx++;
> > +        unsigned int next_rx = console_rx + 1;
> > +
> > +        /* Skip switching serial input to non existing domains */
> > +        while ( next_rx < max_init_domid + 1 )
> > +        {
> > +            struct domain *d = rcu_lock_domain_by_id(next_rx - 1);
> > +
> > +            if ( d )
> > +            {
> > +                rcu_unlock_domain(d);
> > +                break;
> > +            }
> > +
> > +            next_rx++;
> > +        }
> > +
> > +        console_rx = next_rx;
> > +
> >          printk("*** Serial input to DOM%d", console_rx - 1);
> >      }
> 
> While at the first glance (when you sent it in reply to v1) it looked okay,
> I'm afraid it really isn't: Please consider what happens when the last of
> the DomU-s doesn't exist anymore. (You don't really check whether it still
> exists, because the range check comes ahead of the existence one.) In that
> case you want to move from second-to-last to Xen. I expect the entire
> if/else construct wants to be inside the loop.

I don't think we need another loop, just a check if we found a domain or
not. E.g.:


    unsigned int next_rx = console_rx + 1;

    /* Skip switching serial input to non existing domains */
    while ( next_rx < max_init_domid + 1 )
    {
        struct domain *d = rcu_lock_domain_by_id(next_rx - 1);

        if ( d )
        {
            rcu_unlock_domain(d);
            console_rx = next_rx;
            printk("*** Serial input to DOM%d", console_rx - 1);
            break;
        }

        next_rx++;
    }

    /* no domain found */
    console_rx = 0;
    printk("*** Serial input to Xen");
Jan Beulich March 17, 2023, 8:36 a.m. UTC | #6
On 16.03.2023 23:59, Stefano Stabellini wrote:
> On Thu, 16 Mar 2023, Jan Beulich wrote:
>> On 16.03.2023 11:26, Michal Orzel wrote:
>>> --- a/xen/drivers/char/console.c
>>> +++ b/xen/drivers/char/console.c
>>> @@ -490,7 +490,24 @@ static void switch_serial_input(void)
>>>      }
>>>      else
>>>      {
>>> -        console_rx++;
>>> +        unsigned int next_rx = console_rx + 1;
>>> +
>>> +        /* Skip switching serial input to non existing domains */
>>> +        while ( next_rx < max_init_domid + 1 )
>>> +        {
>>> +            struct domain *d = rcu_lock_domain_by_id(next_rx - 1);
>>> +
>>> +            if ( d )
>>> +            {
>>> +                rcu_unlock_domain(d);
>>> +                break;
>>> +            }
>>> +
>>> +            next_rx++;
>>> +        }
>>> +
>>> +        console_rx = next_rx;
>>> +
>>>          printk("*** Serial input to DOM%d", console_rx - 1);
>>>      }
>>
>> While at the first glance (when you sent it in reply to v1) it looked okay,
>> I'm afraid it really isn't: Please consider what happens when the last of
>> the DomU-s doesn't exist anymore. (You don't really check whether it still
>> exists, because the range check comes ahead of the existence one.) In that
>> case you want to move from second-to-last to Xen. I expect the entire
>> if/else construct wants to be inside the loop.
> 
> I don't think we need another loop, just a check if we found a domain or

I didn't say "another loop", but I suggested that the loop needs to be
around the if/else. Of course this can be transformed into equivalent
forms, like ...

> not. E.g.:
> 
> 
>     unsigned int next_rx = console_rx + 1;
> 
>     /* Skip switching serial input to non existing domains */
>     while ( next_rx < max_init_domid + 1 )
>     {
>         struct domain *d = rcu_lock_domain_by_id(next_rx - 1);
> 
>         if ( d )
>         {
>             rcu_unlock_domain(d);
>             console_rx = next_rx;
>             printk("*** Serial input to DOM%d", console_rx - 1);
>             break;
>         }
> 
>         next_rx++;
>     }
> 
>     /* no domain found */
>     console_rx = 0;
>     printk("*** Serial input to Xen");

... what you suggest (or at least almost, because the way it's written
we'd always switch to Xen).

Jan
Michal Orzel March 17, 2023, 9:32 a.m. UTC | #7
On 17/03/2023 09:36, Jan Beulich wrote:
> 
> 
> On 16.03.2023 23:59, Stefano Stabellini wrote:
>> On Thu, 16 Mar 2023, Jan Beulich wrote:
>>> On 16.03.2023 11:26, Michal Orzel wrote:
>>>> --- a/xen/drivers/char/console.c
>>>> +++ b/xen/drivers/char/console.c
>>>> @@ -490,7 +490,24 @@ static void switch_serial_input(void)
>>>>      }
>>>>      else
>>>>      {
>>>> -        console_rx++;
>>>> +        unsigned int next_rx = console_rx + 1;
>>>> +
>>>> +        /* Skip switching serial input to non existing domains */
>>>> +        while ( next_rx < max_init_domid + 1 )
>>>> +        {
>>>> +            struct domain *d = rcu_lock_domain_by_id(next_rx - 1);
>>>> +
>>>> +            if ( d )
>>>> +            {
>>>> +                rcu_unlock_domain(d);
>>>> +                break;
>>>> +            }
>>>> +
>>>> +            next_rx++;
>>>> +        }
>>>> +
>>>> +        console_rx = next_rx;
>>>> +
>>>>          printk("*** Serial input to DOM%d", console_rx - 1);
>>>>      }
>>>
>>> While at the first glance (when you sent it in reply to v1) it looked okay,
>>> I'm afraid it really isn't: Please consider what happens when the last of
>>> the DomU-s doesn't exist anymore. (You don't really check whether it still
>>> exists, because the range check comes ahead of the existence one.) In that
>>> case you want to move from second-to-last to Xen. I expect the entire
>>> if/else construct wants to be inside the loop.
>>
>> I don't think we need another loop, just a check if we found a domain or
> 
> I didn't say "another loop", but I suggested that the loop needs to be
> around the if/else. Of course this can be transformed into equivalent
> forms, like ...
> 
>> not. E.g.:
>>
>>
>>     unsigned int next_rx = console_rx + 1;
>>
>>     /* Skip switching serial input to non existing domains */
>>     while ( next_rx < max_init_domid + 1 )
>>     {
>>         struct domain *d = rcu_lock_domain_by_id(next_rx - 1);
>>
>>         if ( d )
>>         {
>>             rcu_unlock_domain(d);
>>             console_rx = next_rx;
>>             printk("*** Serial input to DOM%d", console_rx - 1);
>>             break;
>>         }
>>
>>         next_rx++;
>>     }
>>
>>     /* no domain found */
>>     console_rx = 0;
>>     printk("*** Serial input to Xen");
> 
> ... what you suggest (or at least almost, because the way it's written
> we'd always switch to Xen).

I would prefer a loop with if/else inside. If you are ok with the following code
that handles all the cases, I will push a patch in a minute:

diff --git a/xen/drivers/char/console.c b/xen/drivers/char/console.c
index 51e5408f2114..96ec3bbcf541 100644
--- a/xen/drivers/char/console.c
+++ b/xen/drivers/char/console.c
@@ -483,15 +483,34 @@ struct domain *console_input_domain(void)
 
 static void switch_serial_input(void)
 {
-    if ( console_rx == max_init_domid + 1 )
-    {
-        console_rx = 0;
-        printk("*** Serial input to Xen");
-    }
-    else
+    unsigned int next_rx = console_rx + 1;
+
+    /*
+     * Rotate among Xen, dom0 and boot-time created domUs while skipping
+     * switching serial input to non existing domains.
+     */
+    while ( next_rx <= max_init_domid + 2 )
     {
-        console_rx++;
-        printk("*** Serial input to DOM%d", console_rx - 1);
+        if ( next_rx == max_init_domid + 2 )
+        {
+            console_rx = 0;
+            printk("*** Serial input to Xen");
+            break;
+        }
+        else
+        {
+            struct domain *d = rcu_lock_domain_by_id(next_rx - 1);
+
+            if ( d )
+            {
+                rcu_unlock_domain(d);
+                console_rx = next_rx;
+                printk("*** Serial input to DOM%d", console_rx - 1);
+                break;
+            }
+
+            next_rx++;
+        }
     }
 
     if ( switch_code )

~Michal
Jan Beulich March 17, 2023, 2:55 p.m. UTC | #8
On 17.03.2023 10:32, Michal Orzel wrote:
> 
> 
> On 17/03/2023 09:36, Jan Beulich wrote:
>>
>>
>> On 16.03.2023 23:59, Stefano Stabellini wrote:
>>> On Thu, 16 Mar 2023, Jan Beulich wrote:
>>>> On 16.03.2023 11:26, Michal Orzel wrote:
>>>>> --- a/xen/drivers/char/console.c
>>>>> +++ b/xen/drivers/char/console.c
>>>>> @@ -490,7 +490,24 @@ static void switch_serial_input(void)
>>>>>      }
>>>>>      else
>>>>>      {
>>>>> -        console_rx++;
>>>>> +        unsigned int next_rx = console_rx + 1;
>>>>> +
>>>>> +        /* Skip switching serial input to non existing domains */
>>>>> +        while ( next_rx < max_init_domid + 1 )
>>>>> +        {
>>>>> +            struct domain *d = rcu_lock_domain_by_id(next_rx - 1);
>>>>> +
>>>>> +            if ( d )
>>>>> +            {
>>>>> +                rcu_unlock_domain(d);
>>>>> +                break;
>>>>> +            }
>>>>> +
>>>>> +            next_rx++;
>>>>> +        }
>>>>> +
>>>>> +        console_rx = next_rx;
>>>>> +
>>>>>          printk("*** Serial input to DOM%d", console_rx - 1);
>>>>>      }
>>>>
>>>> While at the first glance (when you sent it in reply to v1) it looked okay,
>>>> I'm afraid it really isn't: Please consider what happens when the last of
>>>> the DomU-s doesn't exist anymore. (You don't really check whether it still
>>>> exists, because the range check comes ahead of the existence one.) In that
>>>> case you want to move from second-to-last to Xen. I expect the entire
>>>> if/else construct wants to be inside the loop.
>>>
>>> I don't think we need another loop, just a check if we found a domain or
>>
>> I didn't say "another loop", but I suggested that the loop needs to be
>> around the if/else. Of course this can be transformed into equivalent
>> forms, like ...
>>
>>> not. E.g.:
>>>
>>>
>>>     unsigned int next_rx = console_rx + 1;
>>>
>>>     /* Skip switching serial input to non existing domains */
>>>     while ( next_rx < max_init_domid + 1 )
>>>     {
>>>         struct domain *d = rcu_lock_domain_by_id(next_rx - 1);
>>>
>>>         if ( d )
>>>         {
>>>             rcu_unlock_domain(d);
>>>             console_rx = next_rx;
>>>             printk("*** Serial input to DOM%d", console_rx - 1);
>>>             break;
>>>         }
>>>
>>>         next_rx++;
>>>     }
>>>
>>>     /* no domain found */
>>>     console_rx = 0;
>>>     printk("*** Serial input to Xen");
>>
>> ... what you suggest (or at least almost, because the way it's written
>> we'd always switch to Xen).
> 
> I would prefer a loop with if/else inside. If you are ok with the following code
> that handles all the cases, I will push a patch in a minute:

Looks roughly okay, but recall I said so also when you "pre-posted" the
previous version.

Jan
diff mbox series

Patch

diff --git a/xen/drivers/char/console.c b/xen/drivers/char/console.c
index e8468c121ad0..d843b8baf162 100644
--- a/xen/drivers/char/console.c
+++ b/xen/drivers/char/console.c
@@ -490,7 +490,24 @@  static void switch_serial_input(void)
     }
     else
     {
-        console_rx++;
+        unsigned int next_rx = console_rx + 1;
+
+        /* Skip switching serial input to non existing domains */
+        while ( next_rx < max_init_domid + 1 )
+        {
+            struct domain *d = rcu_lock_domain_by_id(next_rx - 1);
+
+            if ( d )
+            {
+                rcu_unlock_domain(d);
+                break;
+            }
+
+            next_rx++;
+        }
+
+        console_rx = next_rx;
+
         printk("*** Serial input to DOM%d", console_rx - 1);
     }