Hung-Te Lin | 23fb997 | 2013-06-21 20:11:47 +0800 | [diff] [blame] | 1 | /* |
| 2 | * This file is part of the coreboot project. |
| 3 | * |
| 4 | * Copyright (C) 2013 Google Inc. All rights reserved. |
| 5 | * |
| 6 | * This program is free software; you can redistribute it and/or modify |
| 7 | * it under the terms of the GNU General Public License as published by |
| 8 | * the Free Software Foundation; version 2 of the License |
| 9 | * |
| 10 | * This program is distributed in the hope that it will be useful, |
| 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 13 | * GNU General Public License for more details. |
Hung-Te Lin | 23fb997 | 2013-06-21 20:11:47 +0800 | [diff] [blame] | 14 | */ |
| 15 | |
| 16 | #include <arch/io.h> |
Aaron Durbin | 6403167 | 2018-04-21 14:45:32 -0600 | [diff] [blame] | 17 | #include <compiler.h> |
Hung-Te Lin | 23fb997 | 2013-06-21 20:11:47 +0800 | [diff] [blame] | 18 | #include <console/console.h> |
| 19 | #include <delay.h> |
| 20 | #include <stdint.h> |
| 21 | #include <string.h> |
| 22 | #include "ec.h" |
| 23 | #include "ec_commands.h" |
| 24 | #include "ec_message.h" |
| 25 | |
Hung-Te Lin | e946f98 | 2013-06-22 11:18:39 +0800 | [diff] [blame] | 26 | /* Common utilities */ |
Aaron Durbin | 6403167 | 2018-04-21 14:45:32 -0600 | [diff] [blame] | 27 | void * __weak crosec_get_buffer(size_t size, int req) |
Aaron Durbin | 8282727 | 2014-08-06 14:34:57 -0500 | [diff] [blame] | 28 | { |
| 29 | printk(BIOS_DEBUG, "crosec_get_buffer() implementation required.\n"); |
| 30 | return NULL; |
| 31 | } |
Hung-Te Lin | e946f98 | 2013-06-22 11:18:39 +0800 | [diff] [blame] | 32 | |
| 33 | /* Dumps EC command / response data into debug output. |
| 34 | * |
| 35 | * @param name Message prefix name. |
| 36 | * @param cmd Command code, or -1 to ignore cmd message. |
| 37 | * @param data Data buffer to print. |
| 38 | * @param len Length of data. |
| 39 | */ |
| 40 | static void cros_ec_dump_data(const char *name, int cmd, const uint8_t *data, |
| 41 | int len) |
| 42 | { |
| 43 | int i; |
| 44 | |
| 45 | printk(BIOS_DEBUG, "%s: ", name); |
| 46 | if (cmd != -1) |
| 47 | printk(BIOS_DEBUG, "cmd=%#x: ", cmd); |
| 48 | for (i = 0; i < len; i++) |
| 49 | printk(BIOS_DEBUG, "%02x ", data[i]); |
| 50 | printk(BIOS_DEBUG, "\n"); |
| 51 | } |
| 52 | |
| 53 | /* Calculate a simple 8-bit checksum of a data block |
| 54 | * |
| 55 | * @param data Data block to checksum |
| 56 | * @param size Size of data block in bytes |
| 57 | * @return checksum value (0 to 255) |
| 58 | */ |
| 59 | static int cros_ec_calc_checksum(const uint8_t *data, int size) |
| 60 | { |
| 61 | int csum, i; |
| 62 | |
| 63 | for (i = csum = 0; i < size; i++) |
| 64 | csum += data[i]; |
| 65 | return csum & 0xff; |
| 66 | } |
| 67 | |
| 68 | /* Standard Chrome EC protocol, version 3 */ |
| 69 | |
| 70 | struct ec_command_v3 { |
| 71 | struct ec_host_request header; |
| 72 | uint8_t data[MSG_BYTES]; |
| 73 | }; |
| 74 | |
| 75 | struct ec_response_v3 { |
| 76 | struct ec_host_response header; |
| 77 | uint8_t data[MSG_BYTES]; |
| 78 | }; |
| 79 | |
| 80 | /** |
| 81 | * Create a request packet for protocol version 3. |
| 82 | * |
| 83 | * @param cec_command Command description. |
| 84 | * @param cmd Packed command bit stream. |
| 85 | * @return packet size in bytes, or <0 if error. |
| 86 | */ |
| 87 | static int create_proto3_request(const struct chromeec_command *cec_command, |
| 88 | struct ec_command_v3 *cmd) |
| 89 | { |
| 90 | struct ec_host_request *rq = &cmd->header; |
| 91 | int out_bytes = cec_command->cmd_size_in + sizeof(*rq); |
| 92 | |
| 93 | /* Fail if output size is too big */ |
| 94 | if (out_bytes > sizeof(*cmd)) { |
| 95 | printk(BIOS_ERR, "%s: Cannot send %d bytes\n", __func__, |
| 96 | cec_command->cmd_size_in); |
| 97 | return -EC_RES_REQUEST_TRUNCATED; |
| 98 | } |
| 99 | |
| 100 | /* Fill in request packet */ |
| 101 | rq->struct_version = EC_HOST_REQUEST_VERSION; |
| 102 | rq->checksum = 0; |
| 103 | rq->command = cec_command->cmd_code; |
| 104 | rq->command_version = cec_command->cmd_version; |
| 105 | rq->reserved = 0; |
| 106 | rq->data_len = cec_command->cmd_size_in; |
| 107 | |
| 108 | /* Copy data after header */ |
| 109 | memcpy(cmd->data, cec_command->cmd_data_in, cec_command->cmd_size_in); |
| 110 | |
| 111 | /* Write checksum field so the entire packet sums to 0 */ |
| 112 | rq->checksum = (uint8_t)(-cros_ec_calc_checksum( |
| 113 | (const uint8_t*)cmd, out_bytes)); |
| 114 | |
| 115 | cros_ec_dump_data("out", rq->command, (const uint8_t *)cmd, out_bytes); |
| 116 | |
| 117 | /* Return size of request packet */ |
| 118 | return out_bytes; |
| 119 | } |
| 120 | |
| 121 | /** |
| 122 | * Prepare the device to receive a protocol version 3 response. |
| 123 | * |
| 124 | * @param cec_command Command description. |
| 125 | * @param resp Response buffer. |
| 126 | * @return maximum expected number of bytes in response, or <0 if error. |
| 127 | */ |
| 128 | static int prepare_proto3_response_buffer( |
| 129 | const struct chromeec_command *cec_command, |
| 130 | struct ec_response_v3 *resp) |
| 131 | { |
| 132 | int in_bytes = cec_command->cmd_size_out + sizeof(resp->header); |
| 133 | |
| 134 | /* Fail if input size is too big */ |
| 135 | if (in_bytes > sizeof(*resp)) { |
| 136 | printk(BIOS_ERR, "%s: Cannot receive %d bytes\n", __func__, |
| 137 | cec_command->cmd_size_out); |
| 138 | return -EC_RES_RESPONSE_TOO_BIG; |
| 139 | } |
| 140 | |
| 141 | /* Return expected size of response packet */ |
| 142 | return in_bytes; |
| 143 | } |
| 144 | |
| 145 | /** |
| 146 | * Handle a protocol version 3 response packet. |
| 147 | * |
| 148 | * The packet must already be stored in the response buffer. |
| 149 | * |
| 150 | * @param resp Response buffer. |
| 151 | * @param cec_command Command structure to receive valid response. |
| 152 | * @return number of bytes of response data, or <0 if error |
| 153 | */ |
| 154 | static int handle_proto3_response(struct ec_response_v3 *resp, |
| 155 | struct chromeec_command *cec_command) |
| 156 | { |
| 157 | struct ec_host_response *rs = &resp->header; |
| 158 | int in_bytes; |
| 159 | int csum; |
| 160 | |
| 161 | cros_ec_dump_data("in-header", -1, (const uint8_t*)rs, sizeof(*rs)); |
| 162 | |
| 163 | /* Check input data */ |
| 164 | if (rs->struct_version != EC_HOST_RESPONSE_VERSION) { |
| 165 | printk(BIOS_ERR, "%s: EC response version mismatch\n", __func__); |
| 166 | return -EC_RES_INVALID_RESPONSE; |
| 167 | } |
| 168 | |
| 169 | if (rs->reserved) { |
| 170 | printk(BIOS_ERR, "%s: EC response reserved != 0\n", __func__); |
| 171 | return -EC_RES_INVALID_RESPONSE; |
| 172 | } |
| 173 | |
| 174 | if (rs->data_len > sizeof(resp->data) || |
| 175 | rs->data_len > cec_command->cmd_size_out) { |
| 176 | printk(BIOS_ERR, "%s: EC returned too much data\n", __func__); |
| 177 | return -EC_RES_RESPONSE_TOO_BIG; |
| 178 | } |
| 179 | |
| 180 | cros_ec_dump_data("in-data", -1, resp->data, rs->data_len); |
| 181 | |
| 182 | /* Update in_bytes to actual data size */ |
| 183 | in_bytes = sizeof(*rs) + rs->data_len; |
| 184 | |
| 185 | /* Verify checksum */ |
| 186 | csum = cros_ec_calc_checksum((const uint8_t *)resp, in_bytes); |
| 187 | if (csum) { |
| 188 | printk(BIOS_ERR, "%s: EC response checksum invalid: 0x%02x\n", |
| 189 | __func__, csum); |
| 190 | return -EC_RES_INVALID_CHECKSUM; |
| 191 | } |
| 192 | |
| 193 | /* Return raw response. */ |
| 194 | cec_command->cmd_code = rs->result; |
| 195 | cec_command->cmd_size_out = rs->data_len; |
| 196 | memcpy(cec_command->cmd_data_out, resp->data, rs->data_len); |
| 197 | |
| 198 | /* Return error result, if any */ |
| 199 | if (rs->result) { |
| 200 | printk(BIOS_ERR, "%s: EC response with error code: %d\n", |
| 201 | __func__, rs->result); |
| 202 | return -(int)rs->result; |
| 203 | } |
| 204 | |
| 205 | return rs->data_len; |
| 206 | } |
| 207 | |
| 208 | static int send_command_proto3(struct chromeec_command *cec_command, |
| 209 | crosec_io_t crosec_io, void *context) |
| 210 | { |
| 211 | int out_bytes, in_bytes; |
| 212 | int rv; |
Aaron Durbin | 8282727 | 2014-08-06 14:34:57 -0500 | [diff] [blame] | 213 | struct ec_command_v3 *cmd; |
| 214 | struct ec_response_v3 *resp; |
| 215 | |
| 216 | if ((cmd = crosec_get_buffer(sizeof(*cmd), 1)) == NULL) |
| 217 | return -EC_RES_ERROR; |
| 218 | if ((resp = crosec_get_buffer(sizeof(*resp), 0)) == NULL) |
| 219 | return -EC_RES_ERROR; |
Hung-Te Lin | e946f98 | 2013-06-22 11:18:39 +0800 | [diff] [blame] | 220 | |
| 221 | /* Create request packet */ |
Aaron Durbin | 8282727 | 2014-08-06 14:34:57 -0500 | [diff] [blame] | 222 | out_bytes = create_proto3_request(cec_command, cmd); |
Hung-Te Lin | e946f98 | 2013-06-22 11:18:39 +0800 | [diff] [blame] | 223 | if (out_bytes < 0) { |
| 224 | return out_bytes; |
| 225 | } |
| 226 | |
| 227 | /* Prepare response buffer */ |
Aaron Durbin | 8282727 | 2014-08-06 14:34:57 -0500 | [diff] [blame] | 228 | in_bytes = prepare_proto3_response_buffer(cec_command, resp); |
Hung-Te Lin | e946f98 | 2013-06-22 11:18:39 +0800 | [diff] [blame] | 229 | if (in_bytes < 0) { |
| 230 | return in_bytes; |
| 231 | } |
| 232 | |
Aaron Durbin | 8282727 | 2014-08-06 14:34:57 -0500 | [diff] [blame] | 233 | rv = crosec_io(out_bytes, in_bytes, context); |
Hung-Te Lin | e946f98 | 2013-06-22 11:18:39 +0800 | [diff] [blame] | 234 | if (rv != 0) { |
Julius Werner | 02e847b | 2015-02-20 13:36:11 -0800 | [diff] [blame] | 235 | printk(BIOS_ERR, "%s: failed to complete I/O: Err = %#x.\n", |
Hung-Te Lin | e946f98 | 2013-06-22 11:18:39 +0800 | [diff] [blame] | 236 | __func__, rv >= 0 ? rv : -rv); |
| 237 | return -EC_RES_ERROR; |
| 238 | } |
| 239 | |
| 240 | /* Process the response */ |
Aaron Durbin | 8282727 | 2014-08-06 14:34:57 -0500 | [diff] [blame] | 241 | return handle_proto3_response(resp, cec_command); |
Hung-Te Lin | e946f98 | 2013-06-22 11:18:39 +0800 | [diff] [blame] | 242 | } |
| 243 | |
| 244 | static int crosec_command_proto_v3(struct chromeec_command *cec_command, |
| 245 | crosec_io_t crosec_io, void *context) |
| 246 | { |
| 247 | int rv = send_command_proto3(cec_command, crosec_io, context); |
| 248 | if (rv < 0) { |
| 249 | cec_command->cmd_code = rv; |
| 250 | return 1; |
| 251 | } |
| 252 | return 0; |
| 253 | } |
| 254 | |
Hung-Te Lin | 23fb997 | 2013-06-21 20:11:47 +0800 | [diff] [blame] | 255 | int crosec_command_proto(struct chromeec_command *cec_command, |
| 256 | crosec_io_t crosec_io, void *context) |
| 257 | { |
Hung-Te Lin | e946f98 | 2013-06-22 11:18:39 +0800 | [diff] [blame] | 258 | // TODO(hungte) Detect and fallback to v2 if we need. |
| 259 | return crosec_command_proto_v3(cec_command, crosec_io, context); |
Hung-Te Lin | 23fb997 | 2013-06-21 20:11:47 +0800 | [diff] [blame] | 260 | } |