/* SPDX-License-Identifier: GPL-2.0-only */ #include <console/console.h> #include <stdint.h> #include <string.h> #include "ec.h" #include "ec_commands.h" #include "ec_message.h" /* Common utilities */ void *__weak crosec_get_buffer(size_t size, int req) { printk(BIOS_DEBUG, "crosec_get_buffer() implementation required.\n"); return NULL; } /* Dumps EC command / response data into debug output. * * @param name Message prefix name. * @param cmd Command code, or -1 to ignore cmd message. * @param data Data buffer to print. * @param len Length of data. */ static void cros_ec_dump_data(const char *name, int cmd, const uint8_t *data, int len) { int i; printk(BIOS_DEBUG, "%s: ", name); if (cmd != -1) printk(BIOS_DEBUG, "cmd=%#x: ", cmd); for (i = 0; i < len; i++) printk(BIOS_DEBUG, "%02x ", data[i]); printk(BIOS_DEBUG, "\n"); } /* Calculate a simple 8-bit checksum of a data block * * @param data Data block to checksum * @param size Size of data block in bytes * @return checksum value (0 to 255) */ static int cros_ec_calc_checksum(const uint8_t *data, int size) { int csum, i; for (i = csum = 0; i < size; i++) csum += data[i]; return csum & 0xff; } /* Standard Chrome EC protocol, version 3 */ struct ec_command_v3 { struct ec_host_request header; uint8_t data[MSG_BYTES]; }; struct ec_response_v3 { struct ec_host_response header; uint8_t data[MSG_BYTES]; }; /** * Create a request packet for protocol version 3. * * @param cec_command Command description. * @param cmd Packed command bit stream. * @return packet size in bytes, or <0 if error. */ static int create_proto3_request(const struct chromeec_command *cec_command, struct ec_command_v3 *cmd) { struct ec_host_request *rq = &cmd->header; int out_bytes = cec_command->cmd_size_in + sizeof(*rq); /* Fail if output size is too big */ if (out_bytes > sizeof(*cmd)) { printk(BIOS_ERR, "%s: Cannot send %d bytes\n", __func__, cec_command->cmd_size_in); return -EC_RES_REQUEST_TRUNCATED; } /* Fill in request packet */ rq->struct_version = EC_HOST_REQUEST_VERSION; rq->checksum = 0; rq->command = cec_command->cmd_code; rq->command_version = cec_command->cmd_version; rq->reserved = 0; rq->data_len = cec_command->cmd_size_in; /* Copy data after header */ memcpy(cmd->data, cec_command->cmd_data_in, cec_command->cmd_size_in); /* Write checksum field so the entire packet sums to 0 */ rq->checksum = (uint8_t)(-cros_ec_calc_checksum( (const uint8_t*)cmd, out_bytes)); cros_ec_dump_data("out", rq->command, (const uint8_t *)cmd, out_bytes); /* Return size of request packet */ return out_bytes; } /** * Prepare the device to receive a protocol version 3 response. * * @param cec_command Command description. * @param resp Response buffer. * @return maximum expected number of bytes in response, or <0 if error. */ static int prepare_proto3_response_buffer( const struct chromeec_command *cec_command, struct ec_response_v3 *resp) { int in_bytes = cec_command->cmd_size_out + sizeof(resp->header); /* Fail if input size is too big */ if (in_bytes > sizeof(*resp)) { printk(BIOS_ERR, "%s: Cannot receive %d bytes\n", __func__, cec_command->cmd_size_out); return -EC_RES_RESPONSE_TOO_BIG; } /* Return expected size of response packet */ return in_bytes; } /** * Handle a protocol version 3 response packet. * * The packet must already be stored in the response buffer. * * @param resp Response buffer. * @param cec_command Command structure to receive valid response. * @return number of bytes of response data, or <0 if error */ static int handle_proto3_response(struct ec_response_v3 *resp, struct chromeec_command *cec_command) { struct ec_host_response *rs = &resp->header; int in_bytes; int csum; cros_ec_dump_data("in-header", -1, (const uint8_t*)rs, sizeof(*rs)); /* Check input data */ if (rs->struct_version != EC_HOST_RESPONSE_VERSION) { printk(BIOS_ERR, "%s: EC response version mismatch\n", __func__); return -EC_RES_INVALID_RESPONSE; } if (rs->reserved) { printk(BIOS_ERR, "%s: EC response reserved != 0\n", __func__); return -EC_RES_INVALID_RESPONSE; } if (rs->data_len > sizeof(resp->data) || rs->data_len > cec_command->cmd_size_out) { printk(BIOS_ERR, "%s: EC returned too much data\n", __func__); return -EC_RES_RESPONSE_TOO_BIG; } cros_ec_dump_data("in-data", -1, resp->data, rs->data_len); /* Update in_bytes to actual data size */ in_bytes = sizeof(*rs) + rs->data_len; /* Verify checksum */ csum = cros_ec_calc_checksum((const uint8_t *)resp, in_bytes); if (csum) { printk(BIOS_ERR, "%s: EC response checksum invalid: 0x%02x\n", __func__, csum); return -EC_RES_INVALID_CHECKSUM; } /* Return raw response. */ cec_command->cmd_code = rs->result; cec_command->cmd_size_out = rs->data_len; memcpy(cec_command->cmd_data_out, resp->data, rs->data_len); /* Return error result, if any */ if (rs->result) { printk(BIOS_ERR, "%s: EC response with error code: %d\n", __func__, rs->result); return -(int)rs->result; } return rs->data_len; } static int send_command_proto3(struct chromeec_command *cec_command, crosec_io_t crosec_io, void *context) { int out_bytes, in_bytes; int rv; struct ec_command_v3 *cmd; struct ec_response_v3 *resp; if ((cmd = crosec_get_buffer(sizeof(*cmd), 1)) == NULL) return -EC_RES_ERROR; if ((resp = crosec_get_buffer(sizeof(*resp), 0)) == NULL) return -EC_RES_ERROR; /* Create request packet */ out_bytes = create_proto3_request(cec_command, cmd); if (out_bytes < 0) { return out_bytes; } /* Prepare response buffer */ in_bytes = prepare_proto3_response_buffer(cec_command, resp); if (in_bytes < 0) { return in_bytes; } rv = crosec_io(out_bytes, in_bytes, context); if (rv != 0) { printk(BIOS_ERR, "%s: failed to complete I/O: Err = %#x.\n", __func__, rv >= 0 ? rv : -rv); return -EC_RES_ERROR; } /* Process the response */ return handle_proto3_response(resp, cec_command); } static int crosec_command_proto_v3(struct chromeec_command *cec_command, crosec_io_t crosec_io, void *context) { int rv = send_command_proto3(cec_command, crosec_io, context); if (rv < 0) { cec_command->cmd_code = rv; return 1; } return 0; } int crosec_command_proto(struct chromeec_command *cec_command, crosec_io_t crosec_io, void *context) { // TODO(hungte) Detect and fallback to v2 if we need. return crosec_command_proto_v3(cec_command, crosec_io, context); }