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