ps2: use a separate keyboard command reply queue

A PS/2 keyboard has a separate command reply queue that is
independent of the key queue. This prevents that command replies
and keyboard input mix. Keyboard command replies take precedence
over queued keystrokes. A new keyboard command removes any
remaining command replies from the command reply queue.

Implement a separate keyboard command reply queue and clear the
command reply queue before command execution. This brings the
PS/2 keyboard emulation much closer to a real PS/2 keyboard.

The command reply queue is located in a few free bytes directly
in front of the scancode queue. Because the scancode queue has
a maximum length of 16 bytes there are 240 bytes available for
the command reply queue. At the moment only a maximum of 3 bytes
are required. For compatibility reasons rptr, wptr and count kept
their function. rptr is the start, wptr is the end and count is
the length of the entire keyboard queue. The new variable cwptr
is the end of the command reply queue or -1 if the queue is
empty. To write to the command reply queue, rptr is moved
backward by the number of required bytes and the command replies
are written to the buffer starting at the new rptr position.
After writing, cwptr is at the old rptr position. Copying cwptr
to rptr clears the command reply queue. The command reply queue
can't overflow because each new keyboard command clears the
command reply queue.

Resolves: https://gitlab.com/qemu-project/qemu/-/issues/501
Resolves: https://gitlab.com/qemu-project/qemu/-/issues/502
Signed-off-by: Volker Rümelin <vr_qemu@t-online.de>
Message-Id: <20210810133258.8231-2-vr_qemu@t-online.de>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
This commit is contained in:
Volker Rümelin 2021-08-10 15:32:57 +02:00 committed by Gerd Hoffmann
parent 47db243233
commit 9e24b2dd77

View File

@ -91,7 +91,7 @@
typedef struct {
uint8_t data[PS2_BUFFER_SIZE];
int rptr, wptr, count;
int rptr, wptr, cwptr, count;
} PS2Queue;
struct PS2State {
@ -186,6 +186,7 @@ static void ps2_reset_queue(PS2State *s)
q->rptr = 0;
q->wptr = 0;
q->cwptr = -1;
q->count = 0;
}
@ -198,7 +199,7 @@ void ps2_queue_noirq(PS2State *s, int b)
{
PS2Queue *q = &s->queue;
if (q->count == PS2_QUEUE_SIZE) {
if (q->count >= PS2_QUEUE_SIZE) {
return;
}
@ -260,6 +261,63 @@ void ps2_queue_4(PS2State *s, int b1, int b2, int b3, int b4)
ps2_raise_irq(s);
}
static void ps2_cqueue_data(PS2Queue *q, int b)
{
q->data[q->cwptr] = b;
if (++q->cwptr >= PS2_BUFFER_SIZE) {
q->cwptr = 0;
}
q->count++;
}
static void ps2_cqueue_1(PS2State *s, int b1)
{
PS2Queue *q = &s->queue;
q->rptr = (q->rptr - 1) & (PS2_BUFFER_SIZE - 1);
q->cwptr = q->rptr;
ps2_cqueue_data(q, b1);
ps2_raise_irq(s);
}
static void ps2_cqueue_2(PS2State *s, int b1, int b2)
{
PS2Queue *q = &s->queue;
q->rptr = (q->rptr - 2) & (PS2_BUFFER_SIZE - 1);
q->cwptr = q->rptr;
ps2_cqueue_data(q, b1);
ps2_cqueue_data(q, b2);
ps2_raise_irq(s);
}
static void ps2_cqueue_3(PS2State *s, int b1, int b2, int b3)
{
PS2Queue *q = &s->queue;
q->rptr = (q->rptr - 3) & (PS2_BUFFER_SIZE - 1);
q->cwptr = q->rptr;
ps2_cqueue_data(q, b1);
ps2_cqueue_data(q, b2);
ps2_cqueue_data(q, b3);
ps2_raise_irq(s);
}
static void ps2_cqueue_reset(PS2State *s)
{
PS2Queue *q = &s->queue;
int ccount;
if (q->cwptr == -1) {
return;
}
ccount = (q->cwptr - q->rptr) & (PS2_BUFFER_SIZE - 1);
q->count -= ccount;
q->rptr = q->cwptr;
q->cwptr = -1;
}
/* keycode is the untranslated scancode in the current scancode set. */
static void ps2_put_keycode(void *opaque, int keycode)
{
@ -523,6 +581,10 @@ uint32_t ps2_read_data(PS2State *s)
q->rptr = 0;
}
q->count--;
if (q->rptr == q->cwptr) {
/* command reply queue is empty */
q->cwptr = -1;
}
/* reading deasserts IRQ */
s->update_irq(s->update_arg, 0);
/* reassert IRQs if data left */
@ -554,92 +616,83 @@ void ps2_write_keyboard(void *opaque, int val)
PS2KbdState *s = (PS2KbdState *)opaque;
trace_ps2_write_keyboard(opaque, val);
ps2_cqueue_reset(&s->common);
switch(s->common.write_cmd) {
default:
case -1:
switch(val) {
case 0x00:
ps2_queue(&s->common, KBD_REPLY_ACK);
ps2_cqueue_1(&s->common, KBD_REPLY_ACK);
break;
case 0x05:
ps2_queue(&s->common, KBD_REPLY_RESEND);
ps2_cqueue_1(&s->common, KBD_REPLY_RESEND);
break;
case KBD_CMD_GET_ID:
/* We emulate a MF2 AT keyboard here */
if (s->translate)
ps2_queue_3(&s->common,
KBD_REPLY_ACK,
KBD_REPLY_ID,
0x41);
else
ps2_queue_3(&s->common,
KBD_REPLY_ACK,
KBD_REPLY_ID,
0x83);
ps2_cqueue_3(&s->common, KBD_REPLY_ACK, KBD_REPLY_ID,
s->translate ? 0x41 : 0x83);
break;
case KBD_CMD_ECHO:
ps2_queue(&s->common, KBD_CMD_ECHO);
ps2_cqueue_1(&s->common, KBD_CMD_ECHO);
break;
case KBD_CMD_ENABLE:
s->scan_enabled = 1;
ps2_queue(&s->common, KBD_REPLY_ACK);
ps2_cqueue_1(&s->common, KBD_REPLY_ACK);
break;
case KBD_CMD_SCANCODE:
case KBD_CMD_SET_LEDS:
case KBD_CMD_SET_RATE:
case KBD_CMD_SET_MAKE_BREAK:
s->common.write_cmd = val;
ps2_queue(&s->common, KBD_REPLY_ACK);
ps2_cqueue_1(&s->common, KBD_REPLY_ACK);
break;
case KBD_CMD_RESET_DISABLE:
ps2_reset_keyboard(s);
s->scan_enabled = 0;
ps2_queue(&s->common, KBD_REPLY_ACK);
ps2_cqueue_1(&s->common, KBD_REPLY_ACK);
break;
case KBD_CMD_RESET_ENABLE:
ps2_reset_keyboard(s);
s->scan_enabled = 1;
ps2_queue(&s->common, KBD_REPLY_ACK);
ps2_cqueue_1(&s->common, KBD_REPLY_ACK);
break;
case KBD_CMD_RESET:
ps2_reset_keyboard(s);
ps2_queue_2(&s->common,
ps2_cqueue_2(&s->common,
KBD_REPLY_ACK,
KBD_REPLY_POR);
break;
case KBD_CMD_SET_TYPEMATIC:
ps2_queue(&s->common, KBD_REPLY_ACK);
ps2_cqueue_1(&s->common, KBD_REPLY_ACK);
break;
default:
ps2_queue(&s->common, KBD_REPLY_RESEND);
ps2_cqueue_1(&s->common, KBD_REPLY_RESEND);
break;
}
break;
case KBD_CMD_SET_MAKE_BREAK:
ps2_queue(&s->common, KBD_REPLY_ACK);
ps2_cqueue_1(&s->common, KBD_REPLY_ACK);
s->common.write_cmd = -1;
break;
case KBD_CMD_SCANCODE:
if (val == 0) {
if (s->common.queue.count <= PS2_QUEUE_SIZE - 2) {
ps2_queue(&s->common, KBD_REPLY_ACK);
ps2_put_keycode(s, s->scancode_set);
}
ps2_cqueue_2(&s->common, KBD_REPLY_ACK, s->translate ?
translate_table[s->scancode_set] : s->scancode_set);
} else if (val >= 1 && val <= 3) {
s->scancode_set = val;
ps2_queue(&s->common, KBD_REPLY_ACK);
ps2_cqueue_1(&s->common, KBD_REPLY_ACK);
} else {
ps2_queue(&s->common, KBD_REPLY_RESEND);
ps2_cqueue_1(&s->common, KBD_REPLY_RESEND);
}
s->common.write_cmd = -1;
break;
case KBD_CMD_SET_LEDS:
ps2_set_ledstate(s, val);
ps2_queue(&s->common, KBD_REPLY_ACK);
ps2_cqueue_1(&s->common, KBD_REPLY_ACK);
s->common.write_cmd = -1;
break;
case KBD_CMD_SET_RATE:
ps2_queue(&s->common, KBD_REPLY_ACK);
ps2_cqueue_1(&s->common, KBD_REPLY_ACK);
s->common.write_cmd = -1;
break;
}