libpayload-x86: Add common i8042 driver

Add a common i8042 driver that uses multiple overflowing
fifos to seperate PS/2 port and PS/2 aux port.

Required to support PC keyboard and PC mouse at the same time.

Tested on Lenovo T500.

Change-Id: I4ca803bfa3ed45111776eef1f4dccd3fab02ea39
Signed-off-by: Patrick Rudolph <siro@das-labor.org>
Reviewed-on: https://review.coreboot.org/18594
Reviewed-by: Philipp Deppenwiese <zaolin.daisuki@gmail.com>
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
This commit is contained in:
Patrick Rudolph 2017-03-01 19:07:37 +01:00 committed by Patrick Georgi
parent 2b2f89565e
commit e6a3821b97
4 changed files with 425 additions and 0 deletions

View File

@ -329,6 +329,12 @@ config FONT_SCALE_FACTOR
By default (value of 0), the scale factor is automatically By default (value of 0), the scale factor is automatically
calculated to ensure at least 130 columns (when possible). calculated to ensure at least 130 columns (when possible).
config PC_I8042
bool "A common PC i8042 driver"
default n
help
To be used by PC_KEYBOARD and PC_MOUSE.
config PC_KEYBOARD config PC_KEYBOARD
bool "Allow input from a PC keyboard" bool "Allow input from a PC keyboard"
default y if ARCH_X86 # uses IO default y if ARCH_X86 # uses IO

View File

@ -38,6 +38,7 @@ libc-$(CONFIG_LP_S5P_SERIAL_CONSOLE) += serial/s5p.c serial/serial.c
libc-$(CONFIG_LP_IPQ806X_SERIAL_CONSOLE) += serial/ipq806x.c serial/serial.c libc-$(CONFIG_LP_IPQ806X_SERIAL_CONSOLE) += serial/ipq806x.c serial/serial.c
libc-$(CONFIG_LP_IPQ40XX_SERIAL_CONSOLE) += serial/ipq40xx.c serial/serial.c libc-$(CONFIG_LP_IPQ40XX_SERIAL_CONSOLE) += serial/ipq40xx.c serial/serial.c
libc-$(CONFIG_LP_BG4CD_SERIAL_CONSOLE) += serial/bg4cd.c serial/serial.c libc-$(CONFIG_LP_BG4CD_SERIAL_CONSOLE) += serial/bg4cd.c serial/serial.c
libc-$(CONFIG_LP_PC_I8042) += i8042/i8042.c
libc-$(CONFIG_LP_PC_KEYBOARD) += keyboard.c libc-$(CONFIG_LP_PC_KEYBOARD) += keyboard.c
libc-$(CONFIG_LP_CBMEM_CONSOLE) += cbmem_console.c libc-$(CONFIG_LP_CBMEM_CONSOLE) += cbmem_console.c

View File

@ -0,0 +1,393 @@
/*
* This file is part of the libpayload project.
*
* Patrick Rudolph 2017 <siro@das-labor.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <libpayload-config.h>
#include <libpayload.h>
#include <stddef.h>
/* Overflowing FIFO implementation */
struct fifo {
u8 *buf;
size_t tx;
size_t rx;
size_t len;
};
/** Initialize a new fifo queue.
* Initialize a new fifo with length @len.
* @len: Length of new fifo
* Returns NULL on error.
*/
static struct fifo *fifo_init(size_t len)
{
struct fifo *ret;
ret = malloc(sizeof(*ret));
if (!ret)
return NULL;
memset(ret, 0, sizeof(*ret));
ret->buf = malloc(len);
if (!ret->buf) {
free(ret);
return NULL;
}
ret->len = len;
return ret;
}
/** Push object onto fifo queue.
* Pushes a new object onto the fifo. In case the fifo
* is full the oldest object is overwritten.
* @fifo: Fifo to use
* @c: Element to push
*/
static void fifo_push(struct fifo *fifo, u8 c)
{
fifo->buf[fifo->tx++] = c;
fifo->tx = fifo->tx % fifo->len;
if (fifo->tx == fifo->rx)
fifo->rx++;
fifo->rx = fifo->rx % fifo->len;
}
/** Test fifo queue element count.
* Returns 1 if fifo is empty.
* @fifo: Fifo to use
*/
static int fifo_is_empty(struct fifo *fifo)
{
return fifo->tx == fifo->rx;
}
/** Pop element from fifo queue.
* Returns the oldest object from queue if any.
* In case the queue is empty 0 is returned.
* @fifo: Fifo to use
*/
static u8 fifo_pop(struct fifo *fifo)
{
u8 ret;
if (fifo_is_empty(fifo))
return 0;
ret = fifo->buf[fifo->rx++];
fifo->rx = fifo->rx % fifo->len;
return ret;
}
/** Destroys a fifo queue.
* @fifo: Fifo to use
*/
static void fifo_destroy(struct fifo *fifo)
{
if (fifo && fifo->buf)
free(fifo->buf);
if (fifo)
free(fifo);
}
/* i8042 keyboard controller implementation */
static inline u8 read_status(void) { return inb(0x64); }
static inline u8 read_data(void) { return inb(0x60); }
static inline void write_cmd(u8 cmd) { outb(cmd, 0x64); }
static inline void write_data(u8 data) { outb(data, 0x60); }
#define OBF 1
#define IBF 2
/* Keyboard controller methods */
static int initialized = 0;
static int kbc_init = 0;
static struct fifo *aux_fifo = NULL;
static struct fifo *ps2_fifo = NULL;
static int i8042_cmd_with_response(u8 cmd);
/** Wait for command ready.
* Wait for the keyboard controller to accept a new command.
* Returns: 0 on timeout
*/
static u8 i8042_wait_cmd_rdy(void)
{
int retries = 10000;
while (retries-- && (read_status() & IBF))
udelay(50);
return retries > 0;
}
/** Wait for data ready.
* Wait for the keyboard controller to accept new data.
* Returns: 0 on timeout
*/
static u8 i8042_wait_data_rdy(void)
{
int retries = 10000;
while (retries-- && !(read_status() & OBF))
udelay(50);
return retries > 0;
}
/** Keyboard controller has a ps2 port.
* Returns if ps2 port is available.
*/
size_t i8042_has_ps2(void)
{
return !!ps2_fifo;
}
/** Keyboard controller has an aux port.
* Returns if aux port is available.
*/
size_t i8042_has_aux(void)
{
return !!aux_fifo;
}
/**
* Probe for keyboard controller
* Returns: 1 for success, 0 for failure
*/
u8 i8042_probe(void)
{
if (initialized)
return 1;
aux_fifo = NULL;
ps2_fifo = NULL;
/* If 0x64 returns 0xff, then we have no keyboard
* controller */
if (read_status() == 0xFF)
return 0;
if (!i8042_wait_cmd_rdy())
return 0;
kbc_init = 1;
/* Disable first device */
if (i8042_cmd(0xad) != 0) {
kbc_init = 0;
return 0;
}
/* Disable second device */
if (i8042_cmd(0xa7) != 0) {
kbc_init = 0;
return 0;
}
/* Flush buffer */
while (read_status() & OBF)
read_data();
/* Self test. */
if (i8042_cmd_with_response(0xaa) != 0x55) {
kbc_init = 0;
return 0;
}
/* Test secondary port */
if (i8042_cmd_with_response(0xa9) == 0)
aux_fifo = fifo_init(4 * 32);
/* Test first PS/2 port */
if (i8042_cmd_with_response(0xab) == 0)
ps2_fifo = fifo_init(2 * 16);
kbc_init = 0;
initialized = aux_fifo || ps2_fifo;
return initialized;
}
/* Close the keyboard controller */
void i8042_close(void)
{
if (!initialized)
return;
fifo_destroy(aux_fifo);
fifo_destroy(ps2_fifo);
initialized = 0;
aux_fifo = NULL;
ps2_fifo = NULL;
}
/** Send command to keyboard controller.
* @param cmd: The command to be send.
* returns: 0 on success, -1 on failure.
*/
int i8042_cmd(u8 cmd)
{
if (!initialized && !kbc_init)
return -1;
if (!i8042_wait_cmd_rdy())
return -1;
write_cmd(cmd);
if (!i8042_wait_cmd_rdy())
return -1;
return 0;
}
/** Send command to keyboard controller.
* @param cmd: The command to be send.
* returns: Response on success, -1 on failure.
*/
static int i8042_cmd_with_response(u8 cmd)
{
const int ret = i8042_cmd(cmd);
if (ret != 0)
return ret;
if (!i8042_wait_data_rdy())
return -1;
return read_data();
}
/** Send additional data to keyboard controller.
* @param data The data to be send.
*/
void i8042_write_data(u8 data)
{
if (!initialized)
return;
if (!i8042_wait_cmd_rdy())
return;
write_data(data);
if (!i8042_wait_cmd_rdy())
return;
}
/**
* Probe for keyboard controller data and queue it.
*/
static void i8042_data_poll(void)
{
u8 c;
if (!initialized)
return;
c = read_status();
while ((c != 0xFF) && (c & OBF)) {
const u8 in = read_data();
/* Assume "second PS/2 port output buffer full" flag works */
struct fifo *const fifo = (c & 0x20) ? aux_fifo : ps2_fifo;
if (fifo)
fifo_push(fifo, in);
c = read_status();
}
}
/** Keyboard controller data ready status.
* Signals that keyboard data is ready for reading.
*/
u8 i8042_data_ready_ps2(void)
{
i8042_data_poll();
return !fifo_is_empty(ps2_fifo);
}
/** Keyboard controller data ready status.
* Signals that mouse data is ready for reading.
*/
u8 i8042_data_ready_aux(void)
{
i8042_data_poll();
return !fifo_is_empty(aux_fifo);
}
/**
* Returns available keyboard data, if any.
*/
u8 i8042_read_data_ps2(void)
{
i8042_data_poll();
return fifo_pop(ps2_fifo);
}
/**
* Returns available mouse data, if any.
*/
u8 i8042_read_data_aux(void)
{
i8042_data_poll();
return fifo_pop(aux_fifo);
}
/**
* Waits for keyboard data.
* Waits for up to 500msec to receive data.
* Returns: -1 on timeout, data received otherwise
*/
int i8042_wait_read_ps2(void)
{
int retries = 10000;
while (retries-- && !i8042_data_ready_ps2())
udelay(50);
return (retries <= 0) ? -1 : i8042_read_data_ps2();
}
/** Waits for mouse data.
* Waits for up to 500msec to receive data.
* Returns: -1 on timeout, data received otherwise
*/
int i8042_wait_read_aux(void)
{
int retries = 10000;
while (retries-- && !i8042_data_ready_aux())
udelay(50);
return (retries <= 0) ? -1 : i8042_read_data_aux();
}

View File

@ -178,6 +178,31 @@ void mouse_cursor_set_acceleration(u8 val);
u8 mouse_cursor_get_acceleration(void); u8 mouse_cursor_get_acceleration(void);
/** @} */ /** @} */
/**
* @defgroup i8042 controller functions
* @ingroup input
* @{
*/
size_t i8042_has_ps2(void);
size_t i8042_has_aux(void);
u8 i8042_probe(void);
void i8042_close(void);
int i8042_cmd(u8 cmd);
void i8042_write_data(u8 data);
u8 i8042_data_ready_ps2(void);
u8 i8042_data_ready_aux(void);
u8 i8042_read_data_ps2(void);
u8 i8042_read_data_aux(void);
int i8042_wait_read_ps2(void);
int i8042_wait_read_aux(void);
/** @} */
/** /**
* @defgroup serial Serial functions * @defgroup serial Serial functions
* @ingroup input * @ingroup input