/* Copyright (c) 2014 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "adc.h" #include "atomic.h" #include "charge_manager.h" #include "common.h" #include "console.h" #include "flash.h" #include "gpio.h" #include "hooks.h" #include "host_command.h" #include "registers.h" #include "rsa.h" #include "sha256.h" #include "system.h" #include "task.h" #include "timer.h" #include "util.h" #include "usb_api.h" #include "usb_pd.h" #include "usb_pd_config.h" #include "version.h" #ifdef CONFIG_COMMON_RUNTIME #define CPRINTS(format, args...) cprints(CC_USBPD, format, ## args) #define CPRINTF(format, args...) cprintf(CC_USBPD, format, ## args) #else #define CPRINTS(format, args...) #define CPRINTF(format, args...) #endif static int rw_flash_changed = 1; #ifdef CONFIG_USB_PD_DUAL_ROLE /* Cap on the max voltage requested as a sink (in millivolts) */ static unsigned max_request_mv = -1; /* no cap */ /** * Find PDO index that offers the most amount of power and stays within * max_mv voltage. * * @param cnt the number of Power Data Objects. * @param src_caps Power Data Objects representing the source capabilities. * @param max_mv maximum voltage (or -1 if no limit) * @return index of PDO within source cap packet */ static int pd_find_pdo_index(int cnt, uint32_t *src_caps, int max_mv) { int i, uw, max_uw = 0, mv, ma; int ret = -1; /* max_mv of -1 represents max limit */ if (max_mv == -1) max_mv = PD_MAX_VOLTAGE_MV; /* max voltage is always limited by this boards max request */ max_mv = MIN(max_mv, PD_MAX_VOLTAGE_MV); /* Get max power that is under our max voltage input */ for (i = 0; i < cnt; i++) { mv = ((src_caps[i] >> 10) & 0x3FF) * 50; if ((src_caps[i] & PDO_TYPE_MASK) == PDO_TYPE_BATTERY) { uw = 250000 * (src_caps[i] & 0x3FF); } else { ma = (src_caps[i] & 0x3FF) * 10; uw = ma * mv; } if ((uw > max_uw) && (mv <= max_mv)) { ret = i; max_uw = uw; } } return ret; } /** * Extract power information out of a Power Data Object (PDO) * * @pdo Power data object * @ma Current we can request from that PDO * @mv Voltage of the PDO */ static void pd_extract_pdo_power(uint32_t pdo, uint32_t *ma, uint32_t *mv) { int max_ma, uw; *mv = ((pdo >> 10) & 0x3FF) * 50; if ((pdo & PDO_TYPE_MASK) == PDO_TYPE_BATTERY) { uw = 250000 * (pdo & 0x3FF); max_ma = 1000 * MIN(1000 * uw, PD_MAX_POWER_MW) / *mv; } else { max_ma = 10 * (pdo & 0x3FF); max_ma = MIN(max_ma, PD_MAX_CURRENT_MA); } *ma = max_ma; } int pd_build_request(int cnt, uint32_t *src_caps, uint32_t *rdo, uint32_t *ma, uint32_t *mv, enum pd_request_type req_type) { int pdo_index, flags = 0; int uw; if (req_type == PD_REQUEST_VSAFE5V) /* src cap 0 should be vSafe5V */ pdo_index = 0; else /* find pdo index for max voltage we can request */ pdo_index = pd_find_pdo_index(cnt, src_caps, max_request_mv); /* If could not find desired pdo_index, then return error */ if (pdo_index == -1) return -EC_ERROR_UNKNOWN; pd_extract_pdo_power(src_caps[pdo_index], ma, mv); uw = *ma * *mv; if (uw < (1000 * PD_OPERATING_POWER_MW)) flags |= RDO_CAP_MISMATCH; if ((src_caps[pdo_index] & PDO_TYPE_MASK) == PDO_TYPE_BATTERY) { int mw = uw / 1000; *rdo = RDO_BATT(pdo_index + 1, mw, mw, flags); CPRINTF("Request [%d] %dmV %dmW", pdo_index, *mv, mw); } else { *rdo = RDO_FIXED(pdo_index + 1, *ma, *ma, flags); CPRINTF("Request [%d] %dmV %dmA", pdo_index, *mv, *ma); } /* Mismatch bit set if less power offered than the operating power */ if (flags & RDO_CAP_MISMATCH) CPRINTF(" Mismatch"); CPRINTF("\n"); return EC_SUCCESS; } void pd_process_source_cap(int port, int cnt, uint32_t *src_caps) { #ifdef CONFIG_CHARGE_MANAGER uint32_t ma, mv; int pdo_index; /* Get max power info that we could request */ pdo_index = pd_find_pdo_index(cnt, src_caps, -1); if (pdo_index < 0) pdo_index = 0; pd_extract_pdo_power(src_caps[pdo_index], &ma, &mv); /* Set max. limit, but apply 500mA ceiling */ charge_manager_set_ceil(port, PD_MIN_MA); pd_set_input_current_limit(port, ma, mv); #endif } void pd_set_max_voltage(unsigned mv) { max_request_mv = mv; } unsigned pd_get_max_voltage(void) { return max_request_mv; } #endif /* CONFIG_USB_PD_DUAL_ROLE */ #ifdef CONFIG_USB_PD_ALT_MODE #ifdef CONFIG_USB_PD_ALT_MODE_DFP static struct pd_policy pe[PD_PORT_COUNT]; #define AMODE_VALID(port) (pe[port].amode.index != -1) static void pe_init(int port) { memset(&pe[port], 0, sizeof(struct pd_policy)); pe[port].amode.index = -1; } static void dfp_consume_identity(int port, uint32_t *payload) { int ptype = PD_IDH_PTYPE(payload[VDO_I(IDH)]); pe_init(port); memcpy(&pe[port].identity, payload + 1, sizeof(pe[port].identity)); switch (ptype) { case IDH_PTYPE_AMA: /* TODO(tbroch) do I disable VBUS here if power contract * requested it */ if (!PD_VDO_AMA_VBUS_REQ(payload[VDO_I(AMA)])) pd_power_supply_reset(port); break; /* TODO(crosbug.com/p/30645) provide vconn support here */ default: break; } } static int dfp_discover_svids(int port, uint32_t *payload) { payload[0] = VDO(USB_SID_PD, 1, CMD_DISCOVER_SVID); return 1; } static void dfp_consume_svids(int port, uint32_t *payload) { int i; uint32_t *ptr = payload + 1; uint16_t svid0, svid1; for (i = pe[port].svid_cnt; i < pe[port].svid_cnt + 12; i += 2) { if (i == SVID_DISCOVERY_MAX) { CPRINTF("ERR: too many svids discovered\n"); break; } svid0 = PD_VDO_SVID_SVID0(*ptr); if (!svid0) break; pe[port].svids[i].svid = svid0; pe[port].svid_cnt++; svid1 = PD_VDO_SVID_SVID1(*ptr); if (!svid1) break; pe[port].svids[i + 1].svid = svid1; pe[port].svid_cnt++; ptr++; } /* TODO(tbroch) need to re-issue discover svids if > 12 */ if (i && ((i % 12) == 0)) CPRINTF("TODO: need to re-issue discover svids > 12\n"); } static int dfp_discover_modes(int port, uint32_t *payload) { uint16_t svid = pe[port].svids[pe[port].svid_idx].svid; if (!pe[port].svid_cnt) return 0; payload[0] = VDO(svid, 1, CMD_DISCOVER_MODES); return 1; } static int dfp_consume_modes(int port, int cnt, uint32_t *payload) { int idx = pe[port].svid_idx; pe[port].svids[idx].mode_cnt = cnt - 1; if (pe[port].svids[idx].mode_cnt < 0) { CPRINTF("PE ERR: no modes provided for SVID\n"); } else { memcpy(pe[port].svids[pe[port].svid_idx].mode_vdo, &payload[1], sizeof(uint32_t) * pe[port].svids[idx].mode_cnt); } pe[port].svid_idx++; return (pe[port].svid_idx < pe[port].svid_cnt); } int pd_alt_mode(int port) { if (!AMODE_VALID(port)) /* zero is reserved */ return 0; return pe[port].amode.index + 1; } /* TODO(tbroch) this function likely needs to move up the stack to where system * policy decisions are made. */ static int dfp_enter_mode(int port, uint32_t *payload) { int i, j, done; struct svdm_amode_data *modep = &pe[port].amode; for (i = 0, done = 0; !done && (i < supported_modes_cnt); i++) { for (j = 0; j < pe[port].svid_cnt; j++) { if (pe[port].svids[j].svid != supported_modes[i].svid) continue; pe[port].amode.fx = &supported_modes[i]; pe[port].amode.mode_caps = pe[port].svids[j].mode_vdo[0]; pe[port].amode.index = 0; done = 1; break; } } if (!AMODE_VALID(port)) return 0; if (modep->fx->enter(port, modep->mode_caps) == -1) return 0; payload[0] = VDO(modep->fx->svid, 1, CMD_ENTER_MODE | VDO_OPOS(pd_alt_mode(port))); return 1; } static void dfp_consume_attention(int port, uint32_t *payload) { int svid = PD_VDO_VID(payload[0]); int opos = PD_VDO_OPOS(payload[0]); if (!AMODE_VALID(port)) return; if (svid != pe[port].amode.fx->svid) { CPRINTF("PE ERR: svid s:0x%04x != m:0x%04x\n", svid, pe[port].amode.fx->svid); return; } if (opos != pd_alt_mode(port)) { CPRINTF("PE ERR: opos s:%d != m:%d\n", opos, pd_alt_mode(port)); return; } if (pe[port].amode.fx->attention) pe[port].amode.fx->attention(port, payload); } int pd_exit_mode(int port, uint32_t *payload) { struct svdm_amode_data *modep = &pe[port].amode; if (!modep->fx) return 0; modep->fx->exit(port); if (payload) payload[0] = VDO(modep->fx->svid, 1, CMD_EXIT_MODE | VDO_OPOS(pd_alt_mode(port))); modep->index = -1; return 1; } static void dump_pe(int port) { const char * const idh_ptype_names[] = { "UNDEF", "Hub", "Periph", "PCable", "ACable", "AMA", "RSV6", "RSV7"}; int i, j, idh_ptype; if (pe[port].identity[0] == 0) { ccprintf("No identity discovered yet.\n"); return; } idh_ptype = PD_IDH_PTYPE(pe[port].identity[0]); ccprintf("IDENT:\n"); ccprintf("\t[ID Header] %08x :: %s, VID:%04x\n", pe[port].identity[0], idh_ptype_names[idh_ptype], PD_IDH_VID(pe[port].identity[0])); ccprintf("\t[Cert Stat] %08x\n", pe[port].identity[1]); for (i = 2; i < ARRAY_SIZE(pe[port].identity); i++) { ccprintf("\t"); if (pe[port].identity[i]) ccprintf("[%d] %08x ", i, pe[port].identity[i]); } ccprintf("\n"); if (pe[port].svid_cnt < 1) { ccprintf("No SVIDS discovered yet.\n"); return; } for (i = 0; i < pe[port].svid_cnt; i++) { ccprintf("SVID[%d]: %04x MODES:", i, pe[port].svids[i].svid); for (j = 0; j < pe[port].svids[j].mode_cnt; j++) ccprintf(" [%d] %08x", j + 1, pe[port].svids[i].mode_vdo[j]); ccprintf("\n"); } if (!AMODE_VALID(port)) { ccprintf("No mode chosen yet.\n"); return; } ccprintf("MODE[%d]: svid:%04x caps:%08x\n", pd_alt_mode(port), pe[port].amode.fx->svid, pe[port].amode.mode_caps); } static int command_pe(int argc, char **argv) { int port; char *e; if (argc < 3) return EC_ERROR_PARAM_COUNT; /* command: pe */ port = strtoi(argv[1], &e, 10); if (*e || port >= PD_PORT_COUNT) return EC_ERROR_PARAM2; if (!strncasecmp(argv[2], "dump", 4)) dump_pe(port); return EC_SUCCESS; } DECLARE_CONSOLE_COMMAND(pe, command_pe, " dump", "USB PE", NULL); #endif /* CONFIG_USB_PD_ALT_MODE_DFP */ int pd_svdm(int port, int cnt, uint32_t *payload, uint32_t **rpayload) { int i; int cmd = PD_VDO_CMD(payload[0]); int cmd_type = PD_VDO_CMDT(payload[0]); int (*func)(int port, uint32_t *payload) = NULL; int rsize = 1; /* VDM header at a minimum */ CPRINTF("SVDM/%d [%d] %08x", cnt, cmd, payload[0]); for (i = 1; i < cnt; i++) CPRINTF(" %08x", payload[i]); CPRINTF("\n"); payload[0] &= ~VDO_CMDT_MASK; *rpayload = payload; if (cmd_type == CMDT_INIT) { switch (cmd) { case CMD_DISCOVER_IDENT: func = svdm_rsp.identity; break; case CMD_DISCOVER_SVID: func = svdm_rsp.svids; break; case CMD_DISCOVER_MODES: func = svdm_rsp.modes; break; case CMD_ENTER_MODE: func = svdm_rsp.enter_mode; break; case CMD_DP_STATUS: func = svdm_rsp.amode->status; break; case CMD_DP_CONFIG: func = svdm_rsp.amode->config; break; case CMD_EXIT_MODE: func = svdm_rsp.exit_mode; break; #ifdef CONFIG_USB_PD_ALT_MODE_DFP case CMD_ATTENTION: /* * attention is only SVDM with no response * (just goodCRC) return zero here. */ dfp_consume_attention(port, payload); return 0; #endif default: CPRINTF("PE ERR: unknown command %d\n", cmd); rsize = 0; } if (func) rsize = func(port, payload); else /* not supported : NACK it */ rsize = 0; if (rsize >= 1) payload[0] |= VDO_CMDT(CMDT_RSP_ACK); else if (!rsize) { payload[0] |= VDO_CMDT(CMDT_RSP_NAK); rsize = 1; } else { payload[0] |= VDO_CMDT(CMDT_RSP_BUSY); rsize = 1; } } else if (cmd_type == CMDT_RSP_ACK) { switch (cmd) { #ifdef CONFIG_USB_PD_ALT_MODE_DFP case CMD_DISCOVER_IDENT: dfp_consume_identity(port, payload); rsize = dfp_discover_svids(port, payload); break; case CMD_DISCOVER_SVID: dfp_consume_svids(port, payload); rsize = dfp_discover_modes(port, payload); break; case CMD_DISCOVER_MODES: if (dfp_consume_modes(port, cnt, payload)) rsize = dfp_discover_modes(port, payload); else rsize = dfp_enter_mode(port, payload); break; case CMD_ENTER_MODE: if (AMODE_VALID(port)) { rsize = pe[port].amode.fx->status(port, payload); payload[0] |= VDO_OPOS(pd_alt_mode(port)); } else { rsize = 0; } break; case CMD_DP_STATUS: /* DP status response & UFP's DP attention have same payload */ dfp_consume_attention(port, payload); if (AMODE_VALID(port)) rsize = pe[port].amode.fx->config(port, payload); else rsize = 0; break; case CMD_DP_CONFIG: /* no response after DFPs ack */ rsize = 0; break; case CMD_EXIT_MODE: /* no response after DFPs ack */ rsize = 0; break; #endif case CMD_ATTENTION: /* no response after DFPs ack */ rsize = 0; break; default: CPRINTF("PE ERR: unknown command %d\n", cmd); rsize = 0; } payload[0] |= VDO_CMDT(CMDT_INIT); #ifdef CONFIG_USB_PD_ALT_MODE_DFP } else if (cmd_type == CMDT_RSP_BUSY) { switch (cmd) { case CMD_DISCOVER_IDENT: case CMD_DISCOVER_SVID: case CMD_DISCOVER_MODES: /* resend if its discovery */ payload[0] |= VDO_CMDT(CMDT_INIT); rsize = 1; break; case CMD_ENTER_MODE: /* Error */ CPRINTF("PE ERR: received BUSY for Enter mode\n"); rsize = 0; break; case CMD_EXIT_MODE: rsize = 0; break; default: rsize = 0; } } else if (cmd_type == CMDT_RSP_NAK) { /* nothing to do */ rsize = 0; #endif /* CONFIG_USB_PD_ALT_MODE_DFP */ } else { CPRINTF("PE ERR: unknown cmd type %d\n", cmd); } CPRINTS("DONE"); return rsize; } #else int pd_svdm(int port, int cnt, uint32_t *payload, uint32_t **rpayload) { return 0; } #endif /* CONFIG_USB_PD_ALT_MODE */ #ifndef CONFIG_USB_PD_CUSTOM_VDM int pd_vdm(int port, int cnt, uint32_t *payload, uint32_t **rpayload) { return 0; } #endif /* !CONFIG_USB_PD_CUSTOM_VDM */ void pd_usb_billboard_deferred(void) { #if defined(CONFIG_USB_PD_ALT_MODE) && !defined(CONFIG_USB_PD_ALT_MODE_DFP) \ && !defined(CONFIG_USB_PD_SIMPLE_DFP) /* port always zero for these UFPs */ if (!pd_alt_mode(0)) usb_connect(); #endif } DECLARE_DEFERRED(pd_usb_billboard_deferred); #ifndef CONFIG_USB_PD_ALT_MODE_DFP int pd_exit_mode(int port, uint32_t *payload) { #ifdef CONFIG_USB_PD_ALT_MODE svdm_rsp.exit_mode(port, payload); #endif return 0; } #endif /* !CONFIG_USB_PD_ALT_MODE_DFP */ #ifdef CONFIG_USB_PD_ALT_MODE_DFP static int hc_remote_pd_discovery(struct host_cmd_handler_args *args) { const uint8_t *port = args->params; struct ec_params_usb_pd_discovery_entry *r = args->response; if (*port >= PD_PORT_COUNT) return EC_RES_INVALID_PARAM; r->vid = PD_IDH_VID(pe[*port].identity[0]); r->ptype = PD_IDH_PTYPE(pe[*port].identity[0]); /* pid only included if vid is assigned */ if (r->vid) r->pid = PD_PRODUCT_PID(pe[*port].identity[2]); args->response_size = sizeof(*r); return EC_RES_SUCCESS; } DECLARE_HOST_COMMAND(EC_CMD_USB_PD_DISCOVERY, hc_remote_pd_discovery, EC_VER_MASK(0)); #endif #define FW_RW_END (CONFIG_FW_RW_OFF + CONFIG_FW_RW_SIZE) uint8_t *flash_hash_rw(void) { static struct sha256_ctx ctx; /* re-calculate RW hash when changed as its time consuming */ if (rw_flash_changed) { rw_flash_changed = 0; SHA256_init(&ctx); SHA256_update(&ctx, (void *)CONFIG_FLASH_BASE + CONFIG_FW_RW_OFF, CONFIG_FW_RW_SIZE - RSANUMBYTES); return SHA256_final(&ctx); } else { return ctx.buf; } } void pd_get_info(uint32_t *info_data) { void *rw_hash = flash_hash_rw(); /* copy first 20 bytes of RW hash */ memcpy(info_data, rw_hash, 5 * sizeof(uint32_t)); /* copy other info into data msg */ #if defined(CONFIG_USB_PD_HW_DEV_ID_BOARD_MAJOR) && \ defined(CONFIG_USB_PD_HW_DEV_ID_BOARD_MINOR) info_data[5] = VDO_INFO(CONFIG_USB_PD_HW_DEV_ID_BOARD_MAJOR, CONFIG_USB_PD_HW_DEV_ID_BOARD_MINOR, ver_get_numcommits(), (system_get_image_copy() != SYSTEM_IMAGE_RO)); #else info_data[5] = 0; #endif } int pd_custom_flash_vdm(int port, int cnt, uint32_t *payload) { static int flash_offset; int rsize = 1; /* default is just VDM header returned */ switch (PD_VDO_CMD(payload[0])) { case VDO_CMD_VERSION: memcpy(payload + 1, &version_data.version, 24); rsize = 7; break; case VDO_CMD_REBOOT: /* ensure the power supply is in a safe state */ pd_power_supply_reset(0); system_reset(0); break; case VDO_CMD_READ_INFO: /* copy info into response */ pd_get_info(payload + 1); rsize = 7; break; case VDO_CMD_FLASH_ERASE: /* do not kill the code under our feet */ if (system_get_image_copy() != SYSTEM_IMAGE_RO) break; flash_offset = CONFIG_FW_RW_OFF; flash_physical_erase(CONFIG_FW_RW_OFF, CONFIG_FW_RW_SIZE); rw_flash_changed = 1; break; case VDO_CMD_FLASH_WRITE: /* do not kill the code under our feet */ if ((system_get_image_copy() != SYSTEM_IMAGE_RO) || (flash_offset < CONFIG_FW_RW_OFF)) break; flash_physical_write(flash_offset, 4*(cnt - 1), (const char *)(payload+1)); flash_offset += 4*(cnt - 1); rw_flash_changed = 1; break; case VDO_CMD_ERASE_SIG: /* this is not touching the code area */ { uint32_t zero = 0; int offset; /* zeroes the area containing the RSA signature */ for (offset = FW_RW_END - RSANUMBYTES; offset < FW_RW_END; offset += 4) flash_physical_write(offset, 4, (const char *)&zero); } break; default: /* Unknown : do not answer */ return 0; } return rsize; }