diff --git a/payloads/libpayload/Kconfig b/payloads/libpayload/Kconfig index 8fd6a9c763..e0aa26d1a7 100644 --- a/payloads/libpayload/Kconfig +++ b/payloads/libpayload/Kconfig @@ -329,6 +329,12 @@ config FONT_SCALE_FACTOR By default (value of 0), the scale factor is automatically 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 bool "Allow input from a PC keyboard" default y if ARCH_X86 # uses IO diff --git a/payloads/libpayload/drivers/Makefile.inc b/payloads/libpayload/drivers/Makefile.inc index 58546e521e..cf8eda8aa4 100644 --- a/payloads/libpayload/drivers/Makefile.inc +++ b/payloads/libpayload/drivers/Makefile.inc @@ -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_IPQ40XX_SERIAL_CONSOLE) += serial/ipq40xx.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_CBMEM_CONSOLE) += cbmem_console.c diff --git a/payloads/libpayload/drivers/i8042/i8042.c b/payloads/libpayload/drivers/i8042/i8042.c new file mode 100644 index 0000000000..1bf18556f2 --- /dev/null +++ b/payloads/libpayload/drivers/i8042/i8042.c @@ -0,0 +1,393 @@ +/* + * This file is part of the libpayload project. + * + * Patrick Rudolph 2017 + * + * 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 +#include +#include + +/* 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(); +} diff --git a/payloads/libpayload/include/libpayload.h b/payloads/libpayload/include/libpayload.h index 43ecd05b02..96cc5f8165 100644 --- a/payloads/libpayload/include/libpayload.h +++ b/payloads/libpayload/include/libpayload.h @@ -178,6 +178,31 @@ void mouse_cursor_set_acceleration(u8 val); 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 * @ingroup input