diff options
Diffstat (limited to 'src/ec/google/wilco/mailbox.c')
-rw-r--r-- | src/ec/google/wilco/mailbox.c | 257 |
1 files changed, 257 insertions, 0 deletions
diff --git a/src/ec/google/wilco/mailbox.c b/src/ec/google/wilco/mailbox.c new file mode 100644 index 0000000000..1c38a5b880 --- /dev/null +++ b/src/ec/google/wilco/mailbox.c @@ -0,0 +1,257 @@ +/* + * This file is part of the coreboot project. + * + * Copyright 2018 Google LLC + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <arch/io.h> +#include <console/console.h> +#include <delay.h> +#include <ec/google/common/mec.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <timer.h> +#include <types.h> + +#include "ec.h" + +/* Mailbox ID */ +#define EC_MAILBOX_ID 0x00f0 + +/* Version of mailbox interface */ +#define EC_MAILBOX_VERSION 0 + +/* Command to start mailbox transaction */ +#define EC_MAILBOX_START_COMMAND 0xda + +/* Version of EC protocol */ +#define EC_MAILBOX_PROTO_VERSION 3 + +/* Max number of bytes in protocol data payload */ +#define EC_MAILBOX_DATA_SIZE 32 + +/* Number of header bytes to be counted as data bytes */ +#define EC_MAILBOX_DATA_EXTRA 2 + +/* Maximum timeout */ +#define EC_MAILBOX_TIMEOUT_MS MSECS_PER_SEC + +/* EC response flags */ +#define EC_CMDR_DATA BIT(0) /* Data ready for host to read */ +#define EC_CMDR_PENDING BIT(1) /* Write pending to EC */ +#define EC_CMDR_BUSY BIT(2) /* EC is busy processing a command */ +#define EC_CMDR_CMD BIT(3) /* Last host write was a command */ + +/* Request to EC */ +struct wilco_ec_request { + uint8_t struct_version; /* version (=3) */ + uint8_t checksum; /* sum of all bytes must be 0 */ + uint16_t mailbox_id; /* mailbox identifier */ + uint8_t mailbox_version; /* mailbox version (=0) */ + uint8_t reserved1; /* unused (=0) */ + uint16_t data_size; /* length (data + 2 bytes of header) */ + uint8_t command; /* mailbox command */ + uint8_t reserved2; /* unused (=0) */ +} __packed; + +/* Response from EC */ +struct wilco_ec_response { + uint8_t struct_version; /* version (=3) */ + uint8_t checksum; /* sum of all bytes must be 0 */ + uint16_t result; /* result code */ + uint16_t data_size; /* length of data buffer (always 32) */ + uint8_t reserved[3]; /* unused (=0) */ + uint8_t data[EC_MAILBOX_DATA_SIZE]; +} __packed; + +struct wilco_ec_message { + uint8_t command; /* mailbox command code */ + uint8_t result; /* request result */ + size_t request_size; /* bytes to send to the EC */ + size_t response_size; /* bytes expected from the EC */ + enum wilco_ec_msg_type type; /* message type */ + /* + * This data buffer will contain the request data when passed to + * wilco_ec_message() and will contain the response data on return. + */ + uint8_t data[EC_MAILBOX_DATA_SIZE]; +}; + +static bool wilco_ec_response_timed_out(void) +{ + uint8_t mask = EC_CMDR_PENDING | EC_CMDR_BUSY; + struct stopwatch sw; + + stopwatch_init_msecs_expire(&sw, EC_MAILBOX_TIMEOUT_MS); + + while (inb(CONFIG_EC_BASE_HOST_COMMAND) & mask) { + if (stopwatch_expired(&sw)) { + printk(BIOS_ERR, "%s: Command timeout\n", __func__); + return true; /* Timed out */ + } + mdelay(1); + } + + return false; /* Did not time out */ +} + +static uint8_t wilco_ec_checksum(void *data, size_t size) +{ + uint8_t *data_bytes = (uint8_t *)data; + uint8_t checksum = 0; + size_t i; + + for (i = 0; i < size; i++) + checksum += data_bytes[i]; + + return checksum; +} + +static void wilco_ec_prepare(struct wilco_ec_message *msg, + struct wilco_ec_request *rq) +{ + memset(rq, 0, sizeof(*rq)); + + /* Fill in request packet */ + rq->struct_version = EC_MAILBOX_PROTO_VERSION; + rq->mailbox_id = EC_MAILBOX_ID; + rq->mailbox_version = EC_MAILBOX_VERSION; + rq->data_size = msg->request_size + EC_MAILBOX_DATA_EXTRA; + rq->command = msg->command; + + /* Checksum header and data */ + rq->checksum = wilco_ec_checksum(rq, sizeof(*rq)); + rq->checksum += wilco_ec_checksum(msg->data, msg->request_size); + rq->checksum = -rq->checksum; +} + +static int wilco_ec_transfer(struct wilco_ec_message *msg) +{ + struct wilco_ec_request rq; + struct wilco_ec_response rs; + uint8_t checksum; + size_t skip_size; + + /* Prepare request packet */ + wilco_ec_prepare(msg, &rq); + + /* Write request header */ + mec_io_bytes(MEC_IO_WRITE, CONFIG_EC_BASE_PACKET, 0, &rq, sizeof(rq)); + + /* Write request data */ + mec_io_bytes(MEC_IO_WRITE, CONFIG_EC_BASE_PACKET, sizeof(rq), + msg->data, msg->request_size); + + /* Start the command */ + outb(EC_MAILBOX_START_COMMAND, CONFIG_EC_BASE_HOST_COMMAND); + + /* Wait for it to complete */ + if (wilco_ec_response_timed_out()) { + printk(BIOS_ERR, "%s: response timed out\n", __func__); + return -1; + } + + /* Some commands will put the EC into a state where it cannot respond */ + if (msg->type == WILCO_EC_MSG_NO_RESPONSE) { + printk(BIOS_DEBUG, "%s: EC does not respond to this command\n", + __func__); + return 0; + } + + /* Check result */ + msg->result = inb(CONFIG_EC_BASE_HOST_DATA); + if (msg->result != 0) { + printk(BIOS_ERR, "%s: bad response: 0x%02x\n", + __func__, msg->result); + return -1; + } + + /* Read back response */ + checksum = mec_io_bytes(MEC_IO_READ, CONFIG_EC_BASE_PACKET, 0, + &rs, sizeof(rs)); + if (checksum) { + printk(BIOS_ERR, "%s: bad checksum %02x\n", __func__, checksum); + return -1; + } + msg->result = rs.result; + + /* EC always returns EC_MAILBOX_DATA_SIZE bytes */ + if (rs.data_size > EC_MAILBOX_DATA_SIZE) { + printk(BIOS_ERR, "%s: packet too long (%d bytes, expected %d)", + __func__, rs.data_size, EC_MAILBOX_DATA_SIZE); + return -1; + } + + /* Skip response data bytes as requested */ + skip_size = (msg->type == WILCO_EC_MSG_DEFAULT) ? 1 : 0; + + if (msg->response_size > rs.data_size - skip_size) { + printk(BIOS_ERR, "%s: data too short (%lu bytes, expected %zu)", + __func__, rs.data_size - skip_size, msg->response_size); + return -1; + } + + memcpy(msg->data, rs.data + skip_size, msg->response_size); + + /* Return actual amount of data received */ + return msg->response_size; +} + +int wilco_ec_mailbox(enum wilco_ec_msg_type type, uint8_t command, + const void *request_data, size_t request_size, + void *response_data, size_t response_size) +{ + struct wilco_ec_message msg = { + .command = command, + .request_size = request_size, + .response_size = response_size, + .type = type, + }; + int ret; + + if (request_size > EC_MAILBOX_DATA_SIZE) { + printk(BIOS_ERR, "%s: provided request data too large: %zu\n", + __func__, request_size); + return -1; + } + if (response_size > EC_MAILBOX_DATA_SIZE) { + printk(BIOS_ERR, "%s: expected response data too large: %zu\n", + __func__, response_size); + return -1; + } + if (request_size && !request_data) { + printk(BIOS_ERR, "%s: request data missing\n", __func__); + return -1; + } + if (response_size && !response_data) { + printk(BIOS_ERR, "%s: request data missing\n", __func__); + return -1; + } + + /* Copy request data if present */ + if (request_size) + memcpy(msg.data, request_data, request_size); + + /* Do the EC transfer */ + ret = wilco_ec_transfer(&msg); + + /* Copy response data if present */ + if (ret > 0 && response_size) + memcpy(response_data, msg.data, response_size); + + /* Return error if message result is non-zero */ + if (ret >= 0 && msg.result) + ret = -1; + + return ret; +} |