[PATCH] hw/input/ps2: answer unknown mouse commands with a resend

Christian Quante posted 1 patch 1 month ago
Patches applied successfully (tree, apply log)
git fetch https://github.com/patchew-project/qemu tags/patchew/20260823071529.16389-1-christian@quante.one
There is a newer version of this series
hw/input/ps2.c | 8 ++++++++
1 file changed, 8 insertions(+)
[PATCH] hw/input/ps2: answer unknown mouse commands with a resend
Posted by Christian Quante 1 month ago
ps2_write_mouse() ends its command switch with a bare "default: break;",
so an unknown command draws no reply at all. A real PS/2 device answers
every byte it is given -- ACK (0xFA) when it understood one, resend
(0xFE) when it did not -- and a guest that gets nothing back is left
waiting out its reply timeout. The keyboard path in the same file has
always answered unknown commands with KBD_REPLY_RESEND.

Two guests were measured on this.

OS/2 probes the mouse with the vendor command 0xBB, which QEMU does not
implement, and then polls the status port until its own timeout runs
out. On a Warp 3 guest that wait costs about 25 ms of every boot under
TCG, and 2.1 s under KVM, where each of those polls leaves the guest.
With this patch the wait ends on the first read: the guest takes the
same error path an unexpected reply would, and does not retry.

Linux probes for a TrackPoint with 0xE1 and fails the same way. Timing
the psmouse detection from a mark written to /dev/kmsg to the kernel's
"input:" line, three boots each of a 6.18.35 kernel under TCG:
426.7/428.8/441.6 ms without this patch, 21.4/21.6/21.2 ms with it. The
mouse is detected identically either way; libps2 caps its retries at
two attempts and ends in the same -EPROTO the timeout produced.

The specification's second stage -- 0xFC (Error) when the byte after a
rejected one is invalid as well -- is deliberately left out. It would
need state that has to survive migration, no guest is known to test for
it, and the keyboard path has answered unknown commands with a bare
resend for twenty years.

Signed-off-by: Christian Quante <christian@quante.one>
---
 hw/input/ps2.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/hw/input/ps2.c b/hw/input/ps2.c
index 5516eb262d..1e8c6d983a 100644
--- a/hw/input/ps2.c
+++ b/hw/input/ps2.c
@@ -73,6 +73,7 @@
 #define AUX_SET_DEFAULT     0xF6
 #define AUX_RESET           0xFF    /* Reset aux device */
 #define AUX_ACK             0xFA    /* Command byte ACK. */
+#define AUX_RESEND          0xFE    /* Command NACK, send the cmd again */
 
 #define MOUSE_STATUS_REMOTE     0x40
 #define MOUSE_STATUS_ENABLED    0x20
@@ -955,6 +956,13 @@ void ps2_write_mouse(PS2MouseState *s, int val)
                 s->mouse_type);
             break;
         default:
+            /*
+             * A PS/2 device answers every command it is given; an unknown
+             * one draws a resend. Staying silent leaves the guest waiting
+             * out its reply timeout. The keyboard path above answers
+             * unknown commands with KBD_REPLY_RESEND.
+             */
+            ps2_queue(ps2, AUX_RESEND);
             break;
         }
         break;
-- 
2.53.0
Re: [PATCH] hw/input/ps2: answer unknown mouse commands with a resend
Posted by Akihiko Odaki 1 month ago
On 2026/08/23 16:15, Christian Quante wrote:
> ps2_write_mouse() ends its command switch with a bare "default: break;",
> so an unknown command draws no reply at all. A real PS/2 device answers
> every byte it is given -- ACK (0xFA) when it understood one, resend
> (0xFE) when it did not -- and a guest that gets nothing back is left
> waiting out its reply timeout. The keyboard path in the same file has
> always answered unknown commands with KBD_REPLY_RESEND.
> 
> Two guests were measured on this.
> 
> OS/2 probes the mouse with the vendor command 0xBB, which QEMU does not
> implement, and then polls the status port until its own timeout runs
> out. On a Warp 3 guest that wait costs about 25 ms of every boot under
> TCG, and 2.1 s under KVM, where each of those polls leaves the guest.
> With this patch the wait ends on the first read: the guest takes the
> same error path an unexpected reply would, and does not retry.
> 
> Linux probes for a TrackPoint with 0xE1 and fails the same way. Timing
> the psmouse detection from a mark written to /dev/kmsg to the kernel's
> "input:" line, three boots each of a 6.18.35 kernel under TCG:
> 426.7/428.8/441.6 ms without this patch, 21.4/21.6/21.2 ms with it. The
> mouse is detected identically either way; libps2 caps its retries at
> two attempts and ends in the same -EPROTO the timeout produced.

With no response, __ps2_command() in libps2 returns -EIO. With this 
patch, two 0xfe responses instead make it return -EPROTO. The two cases 
therefore do not return the same error.

> 
> The specification's second stage -- 0xFC (Error) when the byte after a
> rejected one is invalid as well -- is deliberately left out. It would
> need state that has to survive migration, no guest is known to test for
> it, and the keyboard path has answered unknown commands with a bare
> resend for twenty years.

Commit 06b3611fc2a3 ("ps2: reject unknown commands, instead of
blindly accepting them") changed unknown keyboard commands from ACK
to Resend in 2016, so this behavior is ten years old, not twenty.

> 
> Signed-off-by: Christian Quante <christian@quante.one>
> ---
>   hw/input/ps2.c | 8 ++++++++
>   1 file changed, 8 insertions(+)
> 
> diff --git a/hw/input/ps2.c b/hw/input/ps2.c
> index 5516eb262d..1e8c6d983a 100644
> --- a/hw/input/ps2.c
> +++ b/hw/input/ps2.c
> @@ -73,6 +73,7 @@
>   #define AUX_SET_DEFAULT     0xF6
>   #define AUX_RESET           0xFF    /* Reset aux device */
>   #define AUX_ACK             0xFA    /* Command byte ACK. */
> +#define AUX_RESEND          0xFE    /* Command NACK, send the cmd again */
>   
>   #define MOUSE_STATUS_REMOTE     0x40
>   #define MOUSE_STATUS_ENABLED    0x20
> @@ -955,6 +956,13 @@ void ps2_write_mouse(PS2MouseState *s, int val)
>                   s->mouse_type);
>               break;
>           default:
> +            /*
> +             * A PS/2 device answers every command it is given; an unknown
> +             * one draws a resend. Staying silent leaves the guest waiting
> +             * out its reply timeout. The keyboard path above answers
> +             * unknown commands with KBD_REPLY_RESEND.
> +             */

Mentioning the mismatch with the keyboard path in the patch message 
makes sense, but keeping it in the inline comment is somewhat extraneous 
since consistent behavior across both paths is an obvious expectation. I 
suggest dropping the final sentence.

Additionally, it would be better to ensure the inline rationale is 
documented consistently across both the mouse and keyboard paths.

Otherwise, the patch looks solid to me.

Regards,
Akihiko Odaki
> +            ps2_queue(ps2, AUX_RESEND);
>               break;
>           }
>           break;
Re: [PATCH] hw/input/ps2: answer unknown mouse commands with a resend
Posted by Akihiko Odaki 1 month ago
On 2026/08/25 1:58, Akihiko Odaki wrote:
> On 2026/08/23 16:15, Christian Quante wrote:
>> ps2_write_mouse() ends its command switch with a bare "default: break;",
>> so an unknown command draws no reply at all. A real PS/2 device answers
>> every byte it is given -- ACK (0xFA) when it understood one, resend
>> (0xFE) when it did not -- and a guest that gets nothing back is left
>> waiting out its reply timeout. The keyboard path in the same file has
>> always answered unknown commands with KBD_REPLY_RESEND.
>>
>> Two guests were measured on this.
>>
>> OS/2 probes the mouse with the vendor command 0xBB, which QEMU does not
>> implement, and then polls the status port until its own timeout runs
>> out. On a Warp 3 guest that wait costs about 25 ms of every boot under
>> TCG, and 2.1 s under KVM, where each of those polls leaves the guest.
>> With this patch the wait ends on the first read: the guest takes the
>> same error path an unexpected reply would, and does not retry.
>>
>> Linux probes for a TrackPoint with 0xE1 and fails the same way. Timing
>> the psmouse detection from a mark written to /dev/kmsg to the kernel's
>> "input:" line, three boots each of a 6.18.35 kernel under TCG:
>> 426.7/428.8/441.6 ms without this patch, 21.4/21.6/21.2 ms with it. The
>> mouse is detected identically either way; libps2 caps its retries at
>> two attempts and ends in the same -EPROTO the timeout produced.
> 
> With no response, __ps2_command() in libps2 returns -EIO. With this 
> patch, two 0xfe responses instead make it return -EPROTO. The two cases 
> therefore do not return the same error.
> 
>>
>> The specification's second stage -- 0xFC (Error) when the byte after a
>> rejected one is invalid as well -- is deliberately left out. It would
>> need state that has to survive migration, no guest is known to test for
>> it, and the keyboard path has answered unknown commands with a bare
>> resend for twenty years.
> 
> Commit 06b3611fc2a3 ("ps2: reject unknown commands, instead of
> blindly accepting them") changed unknown keyboard commands from ACK
> to Resend in 2016, so this behavior is ten years old, not twenty.

Also, let's add Cc: qemu-stable@nongnu.org

> 
>>
>> Signed-off-by: Christian Quante <christian@quante.one>
>> ---
>>   hw/input/ps2.c | 8 ++++++++
>>   1 file changed, 8 insertions(+)
>>
>> diff --git a/hw/input/ps2.c b/hw/input/ps2.c
>> index 5516eb262d..1e8c6d983a 100644
>> --- a/hw/input/ps2.c
>> +++ b/hw/input/ps2.c
>> @@ -73,6 +73,7 @@
>>   #define AUX_SET_DEFAULT     0xF6
>>   #define AUX_RESET           0xFF    /* Reset aux device */
>>   #define AUX_ACK             0xFA    /* Command byte ACK. */
>> +#define AUX_RESEND          0xFE    /* Command NACK, send the cmd 
>> again */
>>   #define MOUSE_STATUS_REMOTE     0x40
>>   #define MOUSE_STATUS_ENABLED    0x20
>> @@ -955,6 +956,13 @@ void ps2_write_mouse(PS2MouseState *s, int val)
>>                   s->mouse_type);
>>               break;
>>           default:
>> +            /*
>> +             * A PS/2 device answers every command it is given; an 
>> unknown
>> +             * one draws a resend. Staying silent leaves the guest 
>> waiting
>> +             * out its reply timeout. The keyboard path above answers
>> +             * unknown commands with KBD_REPLY_RESEND.
>> +             */
> 
> Mentioning the mismatch with the keyboard path in the patch message 
> makes sense, but keeping it in the inline comment is somewhat extraneous 
> since consistent behavior across both paths is an obvious expectation. I 
> suggest dropping the final sentence.
> 
> Additionally, it would be better to ensure the inline rationale is 
> documented consistently across both the mouse and keyboard paths.
> 
> Otherwise, the patch looks solid to me.
> 
> Regards,
> Akihiko Odaki
>> +            ps2_queue(ps2, AUX_RESEND);
>>               break;
>>           }
>>           break;
> 


Re: [PATCH] hw/input/ps2: answer unknown mouse commands with a resend
Posted by Christian Quante 1 month ago
Hi Akihiko,

thanks for reading it that closely.

Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp> wrote:
> With no response, __ps2_command() in libps2 returns -EIO. With this
> patch, two 0xfe responses instead make it return -EPROTO. The two cases
> therefore do not return the same error.

Correct, and the sentence claiming otherwise was wrong. ps2_do_sendbyte()
stops at -EAGAIN once the resends have used up max_attempts, and
__ps2_command() maps that to -EPROTO on its way out so the resend does
not leak to callers.

The traces show the same split from the device side: without the patch
each unknown command is written once -- nothing comes back, ps2dev->nak
stays 1, -EIO -- and with it twice, because 0xFE makes libps2 retry once
before it gives up.

v2 says -EIO -> -EPROTO and drops the "same error" claim.

> Commit 06b3611fc2a3 ("ps2: reject unknown commands, instead of
> blindly accepting them") changed unknown keyboard commands from ACK
> to Resend in 2016, so this behavior is ten years old, not twenty.

Corrected, and v2 cites the commit instead of counting decades.

> I suggest dropping the final sentence.
>
> Additionally, it would be better to ensure the inline rationale is
> documented consistently across both the mouse and keyboard paths.

Dropped -- and the sentence before it as well. It described the silence
this patch removes, which reads oddly right next to the code that no
longer produces it, and the commit message makes that point already.
What is left is the sentence that says why a resend rather than an ACK,
which is the part a reader cannot derive from the line below it. The
keyboard path gets that same comment as patch 2 of v2, which keeps
patch 1 a pure fix for stable.

> Also, let's add Cc: qemu-stable@nongnu.org

Added.

One thing the re-measuring turned up: Linux runs into two unknown
commands during mouse detection, not one. The ALPS probe sends 0xEC,
which ps2_write_mouse() only answers while the mouse is in wrap mode,
and the TrackPoint probe sends 0xE1. Each costs a 200 ms reply timeout,
which is what the ~430 ms consist of. v2 says so.

Christian
Re: [PATCH] hw/input/ps2: answer unknown mouse commands with a resend
Posted by Marc-André Lureau 1 month ago
On Sun, Aug 23, 2026 at 11:17 AM Christian Quante <christian@quante.one> wrote:
>
> ps2_write_mouse() ends its command switch with a bare "default: break;",
> so an unknown command draws no reply at all. A real PS/2 device answers
> every byte it is given -- ACK (0xFA) when it understood one, resend
> (0xFE) when it did not -- and a guest that gets nothing back is left
> waiting out its reply timeout. The keyboard path in the same file has
> always answered unknown commands with KBD_REPLY_RESEND.
>
> Two guests were measured on this.
>
> OS/2 probes the mouse with the vendor command 0xBB, which QEMU does not
> implement, and then polls the status port until its own timeout runs
> out. On a Warp 3 guest that wait costs about 25 ms of every boot under
> TCG, and 2.1 s under KVM, where each of those polls leaves the guest.
> With this patch the wait ends on the first read: the guest takes the
> same error path an unexpected reply would, and does not retry.
>
> Linux probes for a TrackPoint with 0xE1 and fails the same way. Timing
> the psmouse detection from a mark written to /dev/kmsg to the kernel's
> "input:" line, three boots each of a 6.18.35 kernel under TCG:
> 426.7/428.8/441.6 ms without this patch, 21.4/21.6/21.2 ms with it. The
> mouse is detected identically either way; libps2 caps its retries at
> two attempts and ends in the same -EPROTO the timeout produced.
>
> The specification's second stage -- 0xFC (Error) when the byte after a
> rejected one is invalid as well -- is deliberately left out. It would
> need state that has to survive migration, no guest is known to test for
> it, and the keyboard path has answered unknown commands with a bare
> resend for twenty years.
>
> Signed-off-by: Christian Quante <christian@quante.one>

Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>

> ---
>  hw/input/ps2.c | 8 ++++++++
>  1 file changed, 8 insertions(+)
>
> diff --git a/hw/input/ps2.c b/hw/input/ps2.c
> index 5516eb262d..1e8c6d983a 100644
> --- a/hw/input/ps2.c
> +++ b/hw/input/ps2.c
> @@ -73,6 +73,7 @@
>  #define AUX_SET_DEFAULT     0xF6
>  #define AUX_RESET           0xFF    /* Reset aux device */
>  #define AUX_ACK             0xFA    /* Command byte ACK. */
> +#define AUX_RESEND          0xFE    /* Command NACK, send the cmd again */
>
>  #define MOUSE_STATUS_REMOTE     0x40
>  #define MOUSE_STATUS_ENABLED    0x20
> @@ -955,6 +956,13 @@ void ps2_write_mouse(PS2MouseState *s, int val)
>                  s->mouse_type);
>              break;
>          default:
> +            /*
> +             * A PS/2 device answers every command it is given; an unknown
> +             * one draws a resend. Staying silent leaves the guest waiting
> +             * out its reply timeout. The keyboard path above answers
> +             * unknown commands with KBD_REPLY_RESEND.
> +             */
> +            ps2_queue(ps2, AUX_RESEND);
>              break;
>          }
>          break;
> --
> 2.53.0
>
>
Re: [PATCH] hw/input/ps2: answer unknown mouse commands with a resend
Posted by Marc-André Lureau 1 month ago
Hi

On Sun, Aug 23, 2026 at 11:17 AM Christian Quante <christian@quante.one> wrote:
>
> ps2_write_mouse() ends its command switch with a bare "default: break;",
> so an unknown command draws no reply at all. A real PS/2 device answers
> every byte it is given -- ACK (0xFA) when it understood one, resend
> (0xFE) when it did not -- and a guest that gets nothing back is left
> waiting out its reply timeout. The keyboard path in the same file has
> always answered unknown commands with KBD_REPLY_RESEND.
>
> Two guests were measured on this.
>
> OS/2 probes the mouse with the vendor command 0xBB, which QEMU does not
> implement, and then polls the status port until its own timeout runs
> out. On a Warp 3 guest that wait costs about 25 ms of every boot under
> TCG, and 2.1 s under KVM, where each of those polls leaves the guest.
> With this patch the wait ends on the first read: the guest takes the
> same error path an unexpected reply would, and does not retry.
>
> Linux probes for a TrackPoint with 0xE1 and fails the same way. Timing
> the psmouse detection from a mark written to /dev/kmsg to the kernel's
> "input:" line, three boots each of a 6.18.35 kernel under TCG:
> 426.7/428.8/441.6 ms without this patch, 21.4/21.6/21.2 ms with it. The
> mouse is detected identically either way; libps2 caps its retries at
> two attempts and ends in the same -EPROTO the timeout produced.
>
> The specification's second stage -- 0xFC (Error) when the byte after a
> rejected one is invalid as well -- is deliberately left out. It would
> need state that has to survive migration, no guest is known to test for
> it, and the keyboard path has answered unknown commands with a bare
> resend for twenty years.
>
> Signed-off-by: Christian Quante <christian@quante.one>
> ---
>  hw/input/ps2.c | 8 ++++++++
>  1 file changed, 8 insertions(+)
>
> diff --git a/hw/input/ps2.c b/hw/input/ps2.c
> index 5516eb262d..1e8c6d983a 100644
> --- a/hw/input/ps2.c
> +++ b/hw/input/ps2.c
> @@ -73,6 +73,7 @@
>  #define AUX_SET_DEFAULT     0xF6
>  #define AUX_RESET           0xFF    /* Reset aux device */
>  #define AUX_ACK             0xFA    /* Command byte ACK. */
> +#define AUX_RESEND          0xFE    /* Command NACK, send the cmd again */
>
>  #define MOUSE_STATUS_REMOTE     0x40
>  #define MOUSE_STATUS_ENABLED    0x20
> @@ -955,6 +956,13 @@ void ps2_write_mouse(PS2MouseState *s, int val)
>                  s->mouse_type);
>              break;
>          default:
> +            /*
> +             * A PS/2 device answers every command it is given; an unknown
> +             * one draws a resend. Staying silent leaves the guest waiting
> +             * out its reply timeout. The keyboard path above answers
> +             * unknown commands with KBD_REPLY_RESEND.
> +             */
> +            ps2_queue(ps2, AUX_RESEND);
>              break;

lgtm,

maybe add ?
        case AUX_RESEND:
            /* Resending the last packet is not implemented. */
            break;
Re: [PATCH] hw/input/ps2: answer unknown mouse commands with a resend
Posted by Christian Quante 1 month ago
Hi Marc-André,

thanks for looking at it.

Marc-André Lureau <marcandre.lureau@redhat.com> wrote:
> maybe add ?
>         case AUX_RESEND:
>             /* Resending the last packet is not implemented. */
>             break;

I'd rather not -- a silent case is exactly what this patch removes,
and 0xFE is the worst byte to leave silent: a guest only sends it
once it has decided the previous reply was bad, so it is already in
its error path when it ends up waiting out the timeout. The keyboard
path settled the same question in 06b3611fc2a3 ("ps2: reject unknown
commands, instead of blindly accepting them"); the mouse path was
simply not touched. And answering 0xFE properly means remembering the
last byte sent and migrating it, which is why the 0xFC stage is left
out too.

If you are happy with the patch, would you mind adding your
Reviewed-by and taking it through your tree? hw/input/ps2.c has no
F: entry in MAINTAINERS, but the recent ps2.c changes went in
through you, so you seemed the right person to ask.

Thanks,
Christian