| /* |
| * 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 <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); |
| |
| /* 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; |
| } |
| |
| /* Wait for it to complete */ |
| if (wilco_ec_response_timed_out()) { |
| printk(BIOS_ERR, "%s: response timed out\n", __func__); |
| return -1; |
| } |
| |
| /* 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; |
| } |