162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Thunderbolt driver - eeprom access 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com> 662306a36Sopenharmony_ci * Copyright (C) 2018, Intel Corporation 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/crc32.h> 1062306a36Sopenharmony_ci#include <linux/delay.h> 1162306a36Sopenharmony_ci#include <linux/property.h> 1262306a36Sopenharmony_ci#include <linux/slab.h> 1362306a36Sopenharmony_ci#include "tb.h" 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci/* 1662306a36Sopenharmony_ci * tb_eeprom_ctl_write() - write control word 1762306a36Sopenharmony_ci */ 1862306a36Sopenharmony_cistatic int tb_eeprom_ctl_write(struct tb_switch *sw, struct tb_eeprom_ctl *ctl) 1962306a36Sopenharmony_ci{ 2062306a36Sopenharmony_ci return tb_sw_write(sw, ctl, TB_CFG_SWITCH, sw->cap_plug_events + ROUTER_CS_4, 1); 2162306a36Sopenharmony_ci} 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci/* 2462306a36Sopenharmony_ci * tb_eeprom_ctl_write() - read control word 2562306a36Sopenharmony_ci */ 2662306a36Sopenharmony_cistatic int tb_eeprom_ctl_read(struct tb_switch *sw, struct tb_eeprom_ctl *ctl) 2762306a36Sopenharmony_ci{ 2862306a36Sopenharmony_ci return tb_sw_read(sw, ctl, TB_CFG_SWITCH, sw->cap_plug_events + ROUTER_CS_4, 1); 2962306a36Sopenharmony_ci} 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_cienum tb_eeprom_transfer { 3262306a36Sopenharmony_ci TB_EEPROM_IN, 3362306a36Sopenharmony_ci TB_EEPROM_OUT, 3462306a36Sopenharmony_ci}; 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci/* 3762306a36Sopenharmony_ci * tb_eeprom_active - enable rom access 3862306a36Sopenharmony_ci * 3962306a36Sopenharmony_ci * WARNING: Always disable access after usage. Otherwise the controller will 4062306a36Sopenharmony_ci * fail to reprobe. 4162306a36Sopenharmony_ci */ 4262306a36Sopenharmony_cistatic int tb_eeprom_active(struct tb_switch *sw, bool enable) 4362306a36Sopenharmony_ci{ 4462306a36Sopenharmony_ci struct tb_eeprom_ctl ctl; 4562306a36Sopenharmony_ci int res = tb_eeprom_ctl_read(sw, &ctl); 4662306a36Sopenharmony_ci if (res) 4762306a36Sopenharmony_ci return res; 4862306a36Sopenharmony_ci if (enable) { 4962306a36Sopenharmony_ci ctl.bit_banging_enable = 1; 5062306a36Sopenharmony_ci res = tb_eeprom_ctl_write(sw, &ctl); 5162306a36Sopenharmony_ci if (res) 5262306a36Sopenharmony_ci return res; 5362306a36Sopenharmony_ci ctl.fl_cs = 0; 5462306a36Sopenharmony_ci return tb_eeprom_ctl_write(sw, &ctl); 5562306a36Sopenharmony_ci } else { 5662306a36Sopenharmony_ci ctl.fl_cs = 1; 5762306a36Sopenharmony_ci res = tb_eeprom_ctl_write(sw, &ctl); 5862306a36Sopenharmony_ci if (res) 5962306a36Sopenharmony_ci return res; 6062306a36Sopenharmony_ci ctl.bit_banging_enable = 0; 6162306a36Sopenharmony_ci return tb_eeprom_ctl_write(sw, &ctl); 6262306a36Sopenharmony_ci } 6362306a36Sopenharmony_ci} 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci/* 6662306a36Sopenharmony_ci * tb_eeprom_transfer - transfer one bit 6762306a36Sopenharmony_ci * 6862306a36Sopenharmony_ci * If TB_EEPROM_IN is passed, then the bit can be retrieved from ctl->fl_do. 6962306a36Sopenharmony_ci * If TB_EEPROM_OUT is passed, then ctl->fl_di will be written. 7062306a36Sopenharmony_ci */ 7162306a36Sopenharmony_cistatic int tb_eeprom_transfer(struct tb_switch *sw, struct tb_eeprom_ctl *ctl, 7262306a36Sopenharmony_ci enum tb_eeprom_transfer direction) 7362306a36Sopenharmony_ci{ 7462306a36Sopenharmony_ci int res; 7562306a36Sopenharmony_ci if (direction == TB_EEPROM_OUT) { 7662306a36Sopenharmony_ci res = tb_eeprom_ctl_write(sw, ctl); 7762306a36Sopenharmony_ci if (res) 7862306a36Sopenharmony_ci return res; 7962306a36Sopenharmony_ci } 8062306a36Sopenharmony_ci ctl->fl_sk = 1; 8162306a36Sopenharmony_ci res = tb_eeprom_ctl_write(sw, ctl); 8262306a36Sopenharmony_ci if (res) 8362306a36Sopenharmony_ci return res; 8462306a36Sopenharmony_ci if (direction == TB_EEPROM_IN) { 8562306a36Sopenharmony_ci res = tb_eeprom_ctl_read(sw, ctl); 8662306a36Sopenharmony_ci if (res) 8762306a36Sopenharmony_ci return res; 8862306a36Sopenharmony_ci } 8962306a36Sopenharmony_ci ctl->fl_sk = 0; 9062306a36Sopenharmony_ci return tb_eeprom_ctl_write(sw, ctl); 9162306a36Sopenharmony_ci} 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci/* 9462306a36Sopenharmony_ci * tb_eeprom_out - write one byte to the bus 9562306a36Sopenharmony_ci */ 9662306a36Sopenharmony_cistatic int tb_eeprom_out(struct tb_switch *sw, u8 val) 9762306a36Sopenharmony_ci{ 9862306a36Sopenharmony_ci struct tb_eeprom_ctl ctl; 9962306a36Sopenharmony_ci int i; 10062306a36Sopenharmony_ci int res = tb_eeprom_ctl_read(sw, &ctl); 10162306a36Sopenharmony_ci if (res) 10262306a36Sopenharmony_ci return res; 10362306a36Sopenharmony_ci for (i = 0; i < 8; i++) { 10462306a36Sopenharmony_ci ctl.fl_di = val & 0x80; 10562306a36Sopenharmony_ci res = tb_eeprom_transfer(sw, &ctl, TB_EEPROM_OUT); 10662306a36Sopenharmony_ci if (res) 10762306a36Sopenharmony_ci return res; 10862306a36Sopenharmony_ci val <<= 1; 10962306a36Sopenharmony_ci } 11062306a36Sopenharmony_ci return 0; 11162306a36Sopenharmony_ci} 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci/* 11462306a36Sopenharmony_ci * tb_eeprom_in - read one byte from the bus 11562306a36Sopenharmony_ci */ 11662306a36Sopenharmony_cistatic int tb_eeprom_in(struct tb_switch *sw, u8 *val) 11762306a36Sopenharmony_ci{ 11862306a36Sopenharmony_ci struct tb_eeprom_ctl ctl; 11962306a36Sopenharmony_ci int i; 12062306a36Sopenharmony_ci int res = tb_eeprom_ctl_read(sw, &ctl); 12162306a36Sopenharmony_ci if (res) 12262306a36Sopenharmony_ci return res; 12362306a36Sopenharmony_ci *val = 0; 12462306a36Sopenharmony_ci for (i = 0; i < 8; i++) { 12562306a36Sopenharmony_ci *val <<= 1; 12662306a36Sopenharmony_ci res = tb_eeprom_transfer(sw, &ctl, TB_EEPROM_IN); 12762306a36Sopenharmony_ci if (res) 12862306a36Sopenharmony_ci return res; 12962306a36Sopenharmony_ci *val |= ctl.fl_do; 13062306a36Sopenharmony_ci } 13162306a36Sopenharmony_ci return 0; 13262306a36Sopenharmony_ci} 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci/* 13562306a36Sopenharmony_ci * tb_eeprom_get_drom_offset - get drom offset within eeprom 13662306a36Sopenharmony_ci */ 13762306a36Sopenharmony_cistatic int tb_eeprom_get_drom_offset(struct tb_switch *sw, u16 *offset) 13862306a36Sopenharmony_ci{ 13962306a36Sopenharmony_ci struct tb_cap_plug_events cap; 14062306a36Sopenharmony_ci int res; 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci if (!sw->cap_plug_events) { 14362306a36Sopenharmony_ci tb_sw_warn(sw, "no TB_CAP_PLUG_EVENTS, cannot read eeprom\n"); 14462306a36Sopenharmony_ci return -ENODEV; 14562306a36Sopenharmony_ci } 14662306a36Sopenharmony_ci res = tb_sw_read(sw, &cap, TB_CFG_SWITCH, sw->cap_plug_events, 14762306a36Sopenharmony_ci sizeof(cap) / 4); 14862306a36Sopenharmony_ci if (res) 14962306a36Sopenharmony_ci return res; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci if (!cap.eeprom_ctl.present || cap.eeprom_ctl.not_present) { 15262306a36Sopenharmony_ci tb_sw_warn(sw, "no NVM\n"); 15362306a36Sopenharmony_ci return -ENODEV; 15462306a36Sopenharmony_ci } 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci if (cap.drom_offset > 0xffff) { 15762306a36Sopenharmony_ci tb_sw_warn(sw, "drom offset is larger than 0xffff: %#x\n", 15862306a36Sopenharmony_ci cap.drom_offset); 15962306a36Sopenharmony_ci return -ENXIO; 16062306a36Sopenharmony_ci } 16162306a36Sopenharmony_ci *offset = cap.drom_offset; 16262306a36Sopenharmony_ci return 0; 16362306a36Sopenharmony_ci} 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci/* 16662306a36Sopenharmony_ci * tb_eeprom_read_n - read count bytes from offset into val 16762306a36Sopenharmony_ci */ 16862306a36Sopenharmony_cistatic int tb_eeprom_read_n(struct tb_switch *sw, u16 offset, u8 *val, 16962306a36Sopenharmony_ci size_t count) 17062306a36Sopenharmony_ci{ 17162306a36Sopenharmony_ci u16 drom_offset; 17262306a36Sopenharmony_ci int i, res; 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci res = tb_eeprom_get_drom_offset(sw, &drom_offset); 17562306a36Sopenharmony_ci if (res) 17662306a36Sopenharmony_ci return res; 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci offset += drom_offset; 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci res = tb_eeprom_active(sw, true); 18162306a36Sopenharmony_ci if (res) 18262306a36Sopenharmony_ci return res; 18362306a36Sopenharmony_ci res = tb_eeprom_out(sw, 3); 18462306a36Sopenharmony_ci if (res) 18562306a36Sopenharmony_ci return res; 18662306a36Sopenharmony_ci res = tb_eeprom_out(sw, offset >> 8); 18762306a36Sopenharmony_ci if (res) 18862306a36Sopenharmony_ci return res; 18962306a36Sopenharmony_ci res = tb_eeprom_out(sw, offset); 19062306a36Sopenharmony_ci if (res) 19162306a36Sopenharmony_ci return res; 19262306a36Sopenharmony_ci for (i = 0; i < count; i++) { 19362306a36Sopenharmony_ci res = tb_eeprom_in(sw, val + i); 19462306a36Sopenharmony_ci if (res) 19562306a36Sopenharmony_ci return res; 19662306a36Sopenharmony_ci } 19762306a36Sopenharmony_ci return tb_eeprom_active(sw, false); 19862306a36Sopenharmony_ci} 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_cistatic u8 tb_crc8(u8 *data, int len) 20162306a36Sopenharmony_ci{ 20262306a36Sopenharmony_ci int i, j; 20362306a36Sopenharmony_ci u8 val = 0xff; 20462306a36Sopenharmony_ci for (i = 0; i < len; i++) { 20562306a36Sopenharmony_ci val ^= data[i]; 20662306a36Sopenharmony_ci for (j = 0; j < 8; j++) 20762306a36Sopenharmony_ci val = (val << 1) ^ ((val & 0x80) ? 7 : 0); 20862306a36Sopenharmony_ci } 20962306a36Sopenharmony_ci return val; 21062306a36Sopenharmony_ci} 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_cistatic u32 tb_crc32(void *data, size_t len) 21362306a36Sopenharmony_ci{ 21462306a36Sopenharmony_ci return ~__crc32c_le(~0, data, len); 21562306a36Sopenharmony_ci} 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci#define TB_DROM_DATA_START 13 21862306a36Sopenharmony_ci#define TB_DROM_HEADER_SIZE 22 21962306a36Sopenharmony_ci#define USB4_DROM_HEADER_SIZE 16 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_cistruct tb_drom_header { 22262306a36Sopenharmony_ci /* BYTE 0 */ 22362306a36Sopenharmony_ci u8 uid_crc8; /* checksum for uid */ 22462306a36Sopenharmony_ci /* BYTES 1-8 */ 22562306a36Sopenharmony_ci u64 uid; 22662306a36Sopenharmony_ci /* BYTES 9-12 */ 22762306a36Sopenharmony_ci u32 data_crc32; /* checksum for data_len bytes starting at byte 13 */ 22862306a36Sopenharmony_ci /* BYTE 13 */ 22962306a36Sopenharmony_ci u8 device_rom_revision; /* should be <= 1 */ 23062306a36Sopenharmony_ci u16 data_len:12; 23162306a36Sopenharmony_ci u8 reserved:4; 23262306a36Sopenharmony_ci /* BYTES 16-21 - Only for TBT DROM, nonexistent in USB4 DROM */ 23362306a36Sopenharmony_ci u16 vendor_id; 23462306a36Sopenharmony_ci u16 model_id; 23562306a36Sopenharmony_ci u8 model_rev; 23662306a36Sopenharmony_ci u8 eeprom_rev; 23762306a36Sopenharmony_ci} __packed; 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_cienum tb_drom_entry_type { 24062306a36Sopenharmony_ci /* force unsigned to prevent "one-bit signed bitfield" warning */ 24162306a36Sopenharmony_ci TB_DROM_ENTRY_GENERIC = 0U, 24262306a36Sopenharmony_ci TB_DROM_ENTRY_PORT, 24362306a36Sopenharmony_ci}; 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_cistruct tb_drom_entry_header { 24662306a36Sopenharmony_ci u8 len; 24762306a36Sopenharmony_ci u8 index:6; 24862306a36Sopenharmony_ci bool port_disabled:1; /* only valid if type is TB_DROM_ENTRY_PORT */ 24962306a36Sopenharmony_ci enum tb_drom_entry_type type:1; 25062306a36Sopenharmony_ci} __packed; 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_cistruct tb_drom_entry_generic { 25362306a36Sopenharmony_ci struct tb_drom_entry_header header; 25462306a36Sopenharmony_ci u8 data[]; 25562306a36Sopenharmony_ci} __packed; 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_cistruct tb_drom_entry_port { 25862306a36Sopenharmony_ci /* BYTES 0-1 */ 25962306a36Sopenharmony_ci struct tb_drom_entry_header header; 26062306a36Sopenharmony_ci /* BYTE 2 */ 26162306a36Sopenharmony_ci u8 dual_link_port_rid:4; 26262306a36Sopenharmony_ci u8 link_nr:1; 26362306a36Sopenharmony_ci u8 unknown1:2; 26462306a36Sopenharmony_ci bool has_dual_link_port:1; 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci /* BYTE 3 */ 26762306a36Sopenharmony_ci u8 dual_link_port_nr:6; 26862306a36Sopenharmony_ci u8 unknown2:2; 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci /* BYTES 4 - 5 TODO decode */ 27162306a36Sopenharmony_ci u8 micro2:4; 27262306a36Sopenharmony_ci u8 micro1:4; 27362306a36Sopenharmony_ci u8 micro3; 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci /* BYTES 6-7, TODO: verify (find hardware that has these set) */ 27662306a36Sopenharmony_ci u8 peer_port_rid:4; 27762306a36Sopenharmony_ci u8 unknown3:3; 27862306a36Sopenharmony_ci bool has_peer_port:1; 27962306a36Sopenharmony_ci u8 peer_port_nr:6; 28062306a36Sopenharmony_ci u8 unknown4:2; 28162306a36Sopenharmony_ci} __packed; 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ci/* USB4 product descriptor */ 28462306a36Sopenharmony_cistruct tb_drom_entry_desc { 28562306a36Sopenharmony_ci struct tb_drom_entry_header header; 28662306a36Sopenharmony_ci u16 bcdUSBSpec; 28762306a36Sopenharmony_ci u16 idVendor; 28862306a36Sopenharmony_ci u16 idProduct; 28962306a36Sopenharmony_ci u16 bcdProductFWRevision; 29062306a36Sopenharmony_ci u32 TID; 29162306a36Sopenharmony_ci u8 productHWRevision; 29262306a36Sopenharmony_ci}; 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci/** 29562306a36Sopenharmony_ci * tb_drom_read_uid_only() - Read UID directly from DROM 29662306a36Sopenharmony_ci * @sw: Router whose UID to read 29762306a36Sopenharmony_ci * @uid: UID is placed here 29862306a36Sopenharmony_ci * 29962306a36Sopenharmony_ci * Does not use the cached copy in sw->drom. Used during resume to check switch 30062306a36Sopenharmony_ci * identity. 30162306a36Sopenharmony_ci */ 30262306a36Sopenharmony_ciint tb_drom_read_uid_only(struct tb_switch *sw, u64 *uid) 30362306a36Sopenharmony_ci{ 30462306a36Sopenharmony_ci u8 data[9]; 30562306a36Sopenharmony_ci u8 crc; 30662306a36Sopenharmony_ci int res; 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ci /* read uid */ 30962306a36Sopenharmony_ci res = tb_eeprom_read_n(sw, 0, data, 9); 31062306a36Sopenharmony_ci if (res) 31162306a36Sopenharmony_ci return res; 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci crc = tb_crc8(data + 1, 8); 31462306a36Sopenharmony_ci if (crc != data[0]) { 31562306a36Sopenharmony_ci tb_sw_warn(sw, "uid crc8 mismatch (expected: %#x, got: %#x)\n", 31662306a36Sopenharmony_ci data[0], crc); 31762306a36Sopenharmony_ci return -EIO; 31862306a36Sopenharmony_ci } 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci *uid = *(u64 *)(data+1); 32162306a36Sopenharmony_ci return 0; 32262306a36Sopenharmony_ci} 32362306a36Sopenharmony_ci 32462306a36Sopenharmony_cistatic int tb_drom_parse_entry_generic(struct tb_switch *sw, 32562306a36Sopenharmony_ci struct tb_drom_entry_header *header) 32662306a36Sopenharmony_ci{ 32762306a36Sopenharmony_ci const struct tb_drom_entry_generic *entry = 32862306a36Sopenharmony_ci (const struct tb_drom_entry_generic *)header; 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_ci switch (header->index) { 33162306a36Sopenharmony_ci case 1: 33262306a36Sopenharmony_ci /* Length includes 2 bytes header so remove it before copy */ 33362306a36Sopenharmony_ci sw->vendor_name = kstrndup(entry->data, 33462306a36Sopenharmony_ci header->len - sizeof(*header), GFP_KERNEL); 33562306a36Sopenharmony_ci if (!sw->vendor_name) 33662306a36Sopenharmony_ci return -ENOMEM; 33762306a36Sopenharmony_ci break; 33862306a36Sopenharmony_ci 33962306a36Sopenharmony_ci case 2: 34062306a36Sopenharmony_ci sw->device_name = kstrndup(entry->data, 34162306a36Sopenharmony_ci header->len - sizeof(*header), GFP_KERNEL); 34262306a36Sopenharmony_ci if (!sw->device_name) 34362306a36Sopenharmony_ci return -ENOMEM; 34462306a36Sopenharmony_ci break; 34562306a36Sopenharmony_ci case 9: { 34662306a36Sopenharmony_ci const struct tb_drom_entry_desc *desc = 34762306a36Sopenharmony_ci (const struct tb_drom_entry_desc *)entry; 34862306a36Sopenharmony_ci 34962306a36Sopenharmony_ci if (!sw->vendor && !sw->device) { 35062306a36Sopenharmony_ci sw->vendor = desc->idVendor; 35162306a36Sopenharmony_ci sw->device = desc->idProduct; 35262306a36Sopenharmony_ci } 35362306a36Sopenharmony_ci break; 35462306a36Sopenharmony_ci } 35562306a36Sopenharmony_ci } 35662306a36Sopenharmony_ci 35762306a36Sopenharmony_ci return 0; 35862306a36Sopenharmony_ci} 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_cistatic int tb_drom_parse_entry_port(struct tb_switch *sw, 36162306a36Sopenharmony_ci struct tb_drom_entry_header *header) 36262306a36Sopenharmony_ci{ 36362306a36Sopenharmony_ci struct tb_port *port; 36462306a36Sopenharmony_ci int res; 36562306a36Sopenharmony_ci enum tb_port_type type; 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_ci /* 36862306a36Sopenharmony_ci * Some DROMs list more ports than the controller actually has 36962306a36Sopenharmony_ci * so we skip those but allow the parser to continue. 37062306a36Sopenharmony_ci */ 37162306a36Sopenharmony_ci if (header->index > sw->config.max_port_number) { 37262306a36Sopenharmony_ci dev_info_once(&sw->dev, "ignoring unnecessary extra entries in DROM\n"); 37362306a36Sopenharmony_ci return 0; 37462306a36Sopenharmony_ci } 37562306a36Sopenharmony_ci 37662306a36Sopenharmony_ci port = &sw->ports[header->index]; 37762306a36Sopenharmony_ci port->disabled = header->port_disabled; 37862306a36Sopenharmony_ci if (port->disabled) 37962306a36Sopenharmony_ci return 0; 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_ci res = tb_port_read(port, &type, TB_CFG_PORT, 2, 1); 38262306a36Sopenharmony_ci if (res) 38362306a36Sopenharmony_ci return res; 38462306a36Sopenharmony_ci type &= 0xffffff; 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_ci if (type == TB_TYPE_PORT) { 38762306a36Sopenharmony_ci struct tb_drom_entry_port *entry = (void *) header; 38862306a36Sopenharmony_ci if (header->len != sizeof(*entry)) { 38962306a36Sopenharmony_ci tb_sw_warn(sw, 39062306a36Sopenharmony_ci "port entry has size %#x (expected %#zx)\n", 39162306a36Sopenharmony_ci header->len, sizeof(struct tb_drom_entry_port)); 39262306a36Sopenharmony_ci return -EIO; 39362306a36Sopenharmony_ci } 39462306a36Sopenharmony_ci port->link_nr = entry->link_nr; 39562306a36Sopenharmony_ci if (entry->has_dual_link_port) 39662306a36Sopenharmony_ci port->dual_link_port = 39762306a36Sopenharmony_ci &port->sw->ports[entry->dual_link_port_nr]; 39862306a36Sopenharmony_ci } 39962306a36Sopenharmony_ci return 0; 40062306a36Sopenharmony_ci} 40162306a36Sopenharmony_ci 40262306a36Sopenharmony_ci/* 40362306a36Sopenharmony_ci * tb_drom_parse_entries - parse the linked list of drom entries 40462306a36Sopenharmony_ci * 40562306a36Sopenharmony_ci * Drom must have been copied to sw->drom. 40662306a36Sopenharmony_ci */ 40762306a36Sopenharmony_cistatic int tb_drom_parse_entries(struct tb_switch *sw, size_t header_size) 40862306a36Sopenharmony_ci{ 40962306a36Sopenharmony_ci struct tb_drom_header *header = (void *) sw->drom; 41062306a36Sopenharmony_ci u16 pos = header_size; 41162306a36Sopenharmony_ci u16 drom_size = header->data_len + TB_DROM_DATA_START; 41262306a36Sopenharmony_ci int res; 41362306a36Sopenharmony_ci 41462306a36Sopenharmony_ci while (pos < drom_size) { 41562306a36Sopenharmony_ci struct tb_drom_entry_header *entry = (void *) (sw->drom + pos); 41662306a36Sopenharmony_ci if (pos + 1 == drom_size || pos + entry->len > drom_size 41762306a36Sopenharmony_ci || !entry->len) { 41862306a36Sopenharmony_ci tb_sw_warn(sw, "DROM buffer overrun\n"); 41962306a36Sopenharmony_ci return -EIO; 42062306a36Sopenharmony_ci } 42162306a36Sopenharmony_ci 42262306a36Sopenharmony_ci switch (entry->type) { 42362306a36Sopenharmony_ci case TB_DROM_ENTRY_GENERIC: 42462306a36Sopenharmony_ci res = tb_drom_parse_entry_generic(sw, entry); 42562306a36Sopenharmony_ci break; 42662306a36Sopenharmony_ci case TB_DROM_ENTRY_PORT: 42762306a36Sopenharmony_ci res = tb_drom_parse_entry_port(sw, entry); 42862306a36Sopenharmony_ci break; 42962306a36Sopenharmony_ci } 43062306a36Sopenharmony_ci if (res) 43162306a36Sopenharmony_ci return res; 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci pos += entry->len; 43462306a36Sopenharmony_ci } 43562306a36Sopenharmony_ci return 0; 43662306a36Sopenharmony_ci} 43762306a36Sopenharmony_ci 43862306a36Sopenharmony_ci/* 43962306a36Sopenharmony_ci * tb_drom_copy_efi - copy drom supplied by EFI to sw->drom if present 44062306a36Sopenharmony_ci */ 44162306a36Sopenharmony_cistatic int tb_drom_copy_efi(struct tb_switch *sw, u16 *size) 44262306a36Sopenharmony_ci{ 44362306a36Sopenharmony_ci struct device *dev = &sw->tb->nhi->pdev->dev; 44462306a36Sopenharmony_ci int len, res; 44562306a36Sopenharmony_ci 44662306a36Sopenharmony_ci len = device_property_count_u8(dev, "ThunderboltDROM"); 44762306a36Sopenharmony_ci if (len < 0 || len < sizeof(struct tb_drom_header)) 44862306a36Sopenharmony_ci return -EINVAL; 44962306a36Sopenharmony_ci 45062306a36Sopenharmony_ci sw->drom = kmalloc(len, GFP_KERNEL); 45162306a36Sopenharmony_ci if (!sw->drom) 45262306a36Sopenharmony_ci return -ENOMEM; 45362306a36Sopenharmony_ci 45462306a36Sopenharmony_ci res = device_property_read_u8_array(dev, "ThunderboltDROM", sw->drom, 45562306a36Sopenharmony_ci len); 45662306a36Sopenharmony_ci if (res) 45762306a36Sopenharmony_ci goto err; 45862306a36Sopenharmony_ci 45962306a36Sopenharmony_ci *size = ((struct tb_drom_header *)sw->drom)->data_len + 46062306a36Sopenharmony_ci TB_DROM_DATA_START; 46162306a36Sopenharmony_ci if (*size > len) 46262306a36Sopenharmony_ci goto err; 46362306a36Sopenharmony_ci 46462306a36Sopenharmony_ci return 0; 46562306a36Sopenharmony_ci 46662306a36Sopenharmony_cierr: 46762306a36Sopenharmony_ci kfree(sw->drom); 46862306a36Sopenharmony_ci sw->drom = NULL; 46962306a36Sopenharmony_ci return -EINVAL; 47062306a36Sopenharmony_ci} 47162306a36Sopenharmony_ci 47262306a36Sopenharmony_cistatic int tb_drom_copy_nvm(struct tb_switch *sw, u16 *size) 47362306a36Sopenharmony_ci{ 47462306a36Sopenharmony_ci u16 drom_offset; 47562306a36Sopenharmony_ci int ret; 47662306a36Sopenharmony_ci 47762306a36Sopenharmony_ci if (!sw->dma_port) 47862306a36Sopenharmony_ci return -ENODEV; 47962306a36Sopenharmony_ci 48062306a36Sopenharmony_ci ret = tb_eeprom_get_drom_offset(sw, &drom_offset); 48162306a36Sopenharmony_ci if (ret) 48262306a36Sopenharmony_ci return ret; 48362306a36Sopenharmony_ci 48462306a36Sopenharmony_ci if (!drom_offset) 48562306a36Sopenharmony_ci return -ENODEV; 48662306a36Sopenharmony_ci 48762306a36Sopenharmony_ci ret = dma_port_flash_read(sw->dma_port, drom_offset + 14, size, 48862306a36Sopenharmony_ci sizeof(*size)); 48962306a36Sopenharmony_ci if (ret) 49062306a36Sopenharmony_ci return ret; 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_ci /* Size includes CRC8 + UID + CRC32 */ 49362306a36Sopenharmony_ci *size += 1 + 8 + 4; 49462306a36Sopenharmony_ci sw->drom = kzalloc(*size, GFP_KERNEL); 49562306a36Sopenharmony_ci if (!sw->drom) 49662306a36Sopenharmony_ci return -ENOMEM; 49762306a36Sopenharmony_ci 49862306a36Sopenharmony_ci ret = dma_port_flash_read(sw->dma_port, drom_offset, sw->drom, *size); 49962306a36Sopenharmony_ci if (ret) 50062306a36Sopenharmony_ci goto err_free; 50162306a36Sopenharmony_ci 50262306a36Sopenharmony_ci /* 50362306a36Sopenharmony_ci * Read UID from the minimal DROM because the one in NVM is just 50462306a36Sopenharmony_ci * a placeholder. 50562306a36Sopenharmony_ci */ 50662306a36Sopenharmony_ci tb_drom_read_uid_only(sw, &sw->uid); 50762306a36Sopenharmony_ci return 0; 50862306a36Sopenharmony_ci 50962306a36Sopenharmony_cierr_free: 51062306a36Sopenharmony_ci kfree(sw->drom); 51162306a36Sopenharmony_ci sw->drom = NULL; 51262306a36Sopenharmony_ci return ret; 51362306a36Sopenharmony_ci} 51462306a36Sopenharmony_ci 51562306a36Sopenharmony_cistatic int usb4_copy_drom(struct tb_switch *sw, u16 *size) 51662306a36Sopenharmony_ci{ 51762306a36Sopenharmony_ci int ret; 51862306a36Sopenharmony_ci 51962306a36Sopenharmony_ci ret = usb4_switch_drom_read(sw, 14, size, sizeof(*size)); 52062306a36Sopenharmony_ci if (ret) 52162306a36Sopenharmony_ci return ret; 52262306a36Sopenharmony_ci 52362306a36Sopenharmony_ci /* Size includes CRC8 + UID + CRC32 */ 52462306a36Sopenharmony_ci *size += 1 + 8 + 4; 52562306a36Sopenharmony_ci sw->drom = kzalloc(*size, GFP_KERNEL); 52662306a36Sopenharmony_ci if (!sw->drom) 52762306a36Sopenharmony_ci return -ENOMEM; 52862306a36Sopenharmony_ci 52962306a36Sopenharmony_ci ret = usb4_switch_drom_read(sw, 0, sw->drom, *size); 53062306a36Sopenharmony_ci if (ret) { 53162306a36Sopenharmony_ci kfree(sw->drom); 53262306a36Sopenharmony_ci sw->drom = NULL; 53362306a36Sopenharmony_ci } 53462306a36Sopenharmony_ci 53562306a36Sopenharmony_ci return ret; 53662306a36Sopenharmony_ci} 53762306a36Sopenharmony_ci 53862306a36Sopenharmony_cistatic int tb_drom_bit_bang(struct tb_switch *sw, u16 *size) 53962306a36Sopenharmony_ci{ 54062306a36Sopenharmony_ci int ret; 54162306a36Sopenharmony_ci 54262306a36Sopenharmony_ci ret = tb_eeprom_read_n(sw, 14, (u8 *)size, 2); 54362306a36Sopenharmony_ci if (ret) 54462306a36Sopenharmony_ci return ret; 54562306a36Sopenharmony_ci 54662306a36Sopenharmony_ci *size &= 0x3ff; 54762306a36Sopenharmony_ci *size += TB_DROM_DATA_START; 54862306a36Sopenharmony_ci 54962306a36Sopenharmony_ci tb_sw_dbg(sw, "reading DROM (length: %#x)\n", *size); 55062306a36Sopenharmony_ci if (*size < sizeof(struct tb_drom_header)) { 55162306a36Sopenharmony_ci tb_sw_warn(sw, "DROM too small, aborting\n"); 55262306a36Sopenharmony_ci return -EIO; 55362306a36Sopenharmony_ci } 55462306a36Sopenharmony_ci 55562306a36Sopenharmony_ci sw->drom = kzalloc(*size, GFP_KERNEL); 55662306a36Sopenharmony_ci if (!sw->drom) 55762306a36Sopenharmony_ci return -ENOMEM; 55862306a36Sopenharmony_ci 55962306a36Sopenharmony_ci ret = tb_eeprom_read_n(sw, 0, sw->drom, *size); 56062306a36Sopenharmony_ci if (ret) 56162306a36Sopenharmony_ci goto err; 56262306a36Sopenharmony_ci 56362306a36Sopenharmony_ci return 0; 56462306a36Sopenharmony_ci 56562306a36Sopenharmony_cierr: 56662306a36Sopenharmony_ci kfree(sw->drom); 56762306a36Sopenharmony_ci sw->drom = NULL; 56862306a36Sopenharmony_ci return ret; 56962306a36Sopenharmony_ci} 57062306a36Sopenharmony_ci 57162306a36Sopenharmony_cistatic int tb_drom_parse_v1(struct tb_switch *sw) 57262306a36Sopenharmony_ci{ 57362306a36Sopenharmony_ci const struct tb_drom_header *header = 57462306a36Sopenharmony_ci (const struct tb_drom_header *)sw->drom; 57562306a36Sopenharmony_ci u32 crc; 57662306a36Sopenharmony_ci 57762306a36Sopenharmony_ci crc = tb_crc8((u8 *) &header->uid, 8); 57862306a36Sopenharmony_ci if (crc != header->uid_crc8) { 57962306a36Sopenharmony_ci tb_sw_warn(sw, 58062306a36Sopenharmony_ci "DROM UID CRC8 mismatch (expected: %#x, got: %#x)\n", 58162306a36Sopenharmony_ci header->uid_crc8, crc); 58262306a36Sopenharmony_ci return -EIO; 58362306a36Sopenharmony_ci } 58462306a36Sopenharmony_ci if (!sw->uid) 58562306a36Sopenharmony_ci sw->uid = header->uid; 58662306a36Sopenharmony_ci sw->vendor = header->vendor_id; 58762306a36Sopenharmony_ci sw->device = header->model_id; 58862306a36Sopenharmony_ci 58962306a36Sopenharmony_ci crc = tb_crc32(sw->drom + TB_DROM_DATA_START, header->data_len); 59062306a36Sopenharmony_ci if (crc != header->data_crc32) { 59162306a36Sopenharmony_ci tb_sw_warn(sw, 59262306a36Sopenharmony_ci "DROM data CRC32 mismatch (expected: %#x, got: %#x), continuing\n", 59362306a36Sopenharmony_ci header->data_crc32, crc); 59462306a36Sopenharmony_ci } 59562306a36Sopenharmony_ci 59662306a36Sopenharmony_ci return tb_drom_parse_entries(sw, TB_DROM_HEADER_SIZE); 59762306a36Sopenharmony_ci} 59862306a36Sopenharmony_ci 59962306a36Sopenharmony_cistatic int usb4_drom_parse(struct tb_switch *sw) 60062306a36Sopenharmony_ci{ 60162306a36Sopenharmony_ci const struct tb_drom_header *header = 60262306a36Sopenharmony_ci (const struct tb_drom_header *)sw->drom; 60362306a36Sopenharmony_ci u32 crc; 60462306a36Sopenharmony_ci 60562306a36Sopenharmony_ci crc = tb_crc32(sw->drom + TB_DROM_DATA_START, header->data_len); 60662306a36Sopenharmony_ci if (crc != header->data_crc32) { 60762306a36Sopenharmony_ci tb_sw_warn(sw, 60862306a36Sopenharmony_ci "DROM data CRC32 mismatch (expected: %#x, got: %#x), continuing\n", 60962306a36Sopenharmony_ci header->data_crc32, crc); 61062306a36Sopenharmony_ci } 61162306a36Sopenharmony_ci 61262306a36Sopenharmony_ci return tb_drom_parse_entries(sw, USB4_DROM_HEADER_SIZE); 61362306a36Sopenharmony_ci} 61462306a36Sopenharmony_ci 61562306a36Sopenharmony_cistatic int tb_drom_parse(struct tb_switch *sw, u16 size) 61662306a36Sopenharmony_ci{ 61762306a36Sopenharmony_ci const struct tb_drom_header *header = (const void *)sw->drom; 61862306a36Sopenharmony_ci int ret; 61962306a36Sopenharmony_ci 62062306a36Sopenharmony_ci if (header->data_len + TB_DROM_DATA_START != size) { 62162306a36Sopenharmony_ci tb_sw_warn(sw, "DROM size mismatch\n"); 62262306a36Sopenharmony_ci ret = -EIO; 62362306a36Sopenharmony_ci goto err; 62462306a36Sopenharmony_ci } 62562306a36Sopenharmony_ci 62662306a36Sopenharmony_ci tb_sw_dbg(sw, "DROM version: %d\n", header->device_rom_revision); 62762306a36Sopenharmony_ci 62862306a36Sopenharmony_ci switch (header->device_rom_revision) { 62962306a36Sopenharmony_ci case 3: 63062306a36Sopenharmony_ci ret = usb4_drom_parse(sw); 63162306a36Sopenharmony_ci break; 63262306a36Sopenharmony_ci default: 63362306a36Sopenharmony_ci tb_sw_warn(sw, "DROM device_rom_revision %#x unknown\n", 63462306a36Sopenharmony_ci header->device_rom_revision); 63562306a36Sopenharmony_ci fallthrough; 63662306a36Sopenharmony_ci case 1: 63762306a36Sopenharmony_ci ret = tb_drom_parse_v1(sw); 63862306a36Sopenharmony_ci break; 63962306a36Sopenharmony_ci } 64062306a36Sopenharmony_ci 64162306a36Sopenharmony_ci if (ret) { 64262306a36Sopenharmony_ci tb_sw_warn(sw, "parsing DROM failed\n"); 64362306a36Sopenharmony_ci goto err; 64462306a36Sopenharmony_ci } 64562306a36Sopenharmony_ci 64662306a36Sopenharmony_ci return 0; 64762306a36Sopenharmony_ci 64862306a36Sopenharmony_cierr: 64962306a36Sopenharmony_ci kfree(sw->drom); 65062306a36Sopenharmony_ci sw->drom = NULL; 65162306a36Sopenharmony_ci 65262306a36Sopenharmony_ci return ret; 65362306a36Sopenharmony_ci} 65462306a36Sopenharmony_ci 65562306a36Sopenharmony_cistatic int tb_drom_host_read(struct tb_switch *sw) 65662306a36Sopenharmony_ci{ 65762306a36Sopenharmony_ci u16 size; 65862306a36Sopenharmony_ci 65962306a36Sopenharmony_ci if (tb_switch_is_usb4(sw)) { 66062306a36Sopenharmony_ci usb4_switch_read_uid(sw, &sw->uid); 66162306a36Sopenharmony_ci if (!usb4_copy_drom(sw, &size)) 66262306a36Sopenharmony_ci return tb_drom_parse(sw, size); 66362306a36Sopenharmony_ci } else { 66462306a36Sopenharmony_ci if (!tb_drom_copy_efi(sw, &size)) 66562306a36Sopenharmony_ci return tb_drom_parse(sw, size); 66662306a36Sopenharmony_ci 66762306a36Sopenharmony_ci if (!tb_drom_copy_nvm(sw, &size)) 66862306a36Sopenharmony_ci return tb_drom_parse(sw, size); 66962306a36Sopenharmony_ci 67062306a36Sopenharmony_ci tb_drom_read_uid_only(sw, &sw->uid); 67162306a36Sopenharmony_ci } 67262306a36Sopenharmony_ci 67362306a36Sopenharmony_ci return 0; 67462306a36Sopenharmony_ci} 67562306a36Sopenharmony_ci 67662306a36Sopenharmony_cistatic int tb_drom_device_read(struct tb_switch *sw) 67762306a36Sopenharmony_ci{ 67862306a36Sopenharmony_ci u16 size; 67962306a36Sopenharmony_ci int ret; 68062306a36Sopenharmony_ci 68162306a36Sopenharmony_ci if (tb_switch_is_usb4(sw)) { 68262306a36Sopenharmony_ci usb4_switch_read_uid(sw, &sw->uid); 68362306a36Sopenharmony_ci ret = usb4_copy_drom(sw, &size); 68462306a36Sopenharmony_ci } else { 68562306a36Sopenharmony_ci ret = tb_drom_bit_bang(sw, &size); 68662306a36Sopenharmony_ci } 68762306a36Sopenharmony_ci 68862306a36Sopenharmony_ci if (ret) 68962306a36Sopenharmony_ci return ret; 69062306a36Sopenharmony_ci 69162306a36Sopenharmony_ci return tb_drom_parse(sw, size); 69262306a36Sopenharmony_ci} 69362306a36Sopenharmony_ci 69462306a36Sopenharmony_ci/** 69562306a36Sopenharmony_ci * tb_drom_read() - Copy DROM to sw->drom and parse it 69662306a36Sopenharmony_ci * @sw: Router whose DROM to read and parse 69762306a36Sopenharmony_ci * 69862306a36Sopenharmony_ci * This function reads router DROM and if successful parses the entries and 69962306a36Sopenharmony_ci * populates the fields in @sw accordingly. Can be called for any router 70062306a36Sopenharmony_ci * generation. 70162306a36Sopenharmony_ci * 70262306a36Sopenharmony_ci * Returns %0 in case of success and negative errno otherwise. 70362306a36Sopenharmony_ci */ 70462306a36Sopenharmony_ciint tb_drom_read(struct tb_switch *sw) 70562306a36Sopenharmony_ci{ 70662306a36Sopenharmony_ci if (sw->drom) 70762306a36Sopenharmony_ci return 0; 70862306a36Sopenharmony_ci 70962306a36Sopenharmony_ci if (!tb_route(sw)) 71062306a36Sopenharmony_ci return tb_drom_host_read(sw); 71162306a36Sopenharmony_ci return tb_drom_device_read(sw); 71262306a36Sopenharmony_ci} 713