[PATCH 2/5] thunderbolt: Obtain PCI slot number from DROM

From: Lukas Wunner
Date: Sun Sep 09 2018 - 17:44:33 EST


The port numbers of PCIe adapters on a CIO switch and the slot numbers
of their corresponding PCI devices don't necessarily match up in
ascending order.

E.g. Light Ridge in host mode (MacBookPro9,1) mixes them up like this:
switch port 7 == pci slot 4 (PCIe downstream port)
switch port 8 == pci slot 5 (PCIe downstream port)
switch port 9 == pci slot 6 (PCIe downstream port)
switch port 10 == pci slot 3 (PCIe downstream port)

Light Ridge in endpoint mode (Sonnet Echo Express):
switch port 7 == pci slot 4 (PCIe downstream port)
switch port 8 == pci slot 5 (PCIe downstream port)
switch port 9 (PCIe downstream port, disabled in DROM)
switch port 10 == pci slot 0 (PCIe upstream port, pci slot 3 in DROM)

Obtain the slot number used by a switch port from the DROM and store it
in struct tb_port. This will allow us to correlate PCI devices with
Thunderbolt ports. For space efficiency, use a union in struct tb_port
to store attributes that are specific to certain port types.
Attributes specific to TB_TYPE_PORT could be moved into a separate
struct within the union.

The slot number occupies 3 bits in the third byte of PCI DROM entries.
The purpose of the remaining 5 bits is unknown (function number?).

Downstream port entries have 3 bytes, upstream port entries 11 bytes.
The slot number in upstream port entries is not that of the port itself
(which is always zero), but that of the PCI endpoint device built into
the Thunderbolt product. In the case of the Sonnet Echo Express, the
upstream port specifies slot 3, which is used by a PLX switch.

The purpose of the additional 8 bytes present on upstream ports is
unknown (number of PCIe lanes, priority requirements, direct tunnel to
the host desired?). Examples:

01 00 64 00 00 00 00 00 (Sonnet Echo Express, PLX switch)
01 40 0a 00 00 00 00 00 (Apple Gigabit Ethernet Adapter, BCM957762)

Signed-off-by: Lukas Wunner <lukas@xxxxxxxxx>
---
drivers/thunderbolt/eeprom.c | 21 +++++++++++++++++++++
drivers/thunderbolt/switch.c | 14 +++++++++++++-
drivers/thunderbolt/tb.h | 9 +++++++++
3 files changed, 43 insertions(+), 1 deletion(-)

diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c
index 3e8caf22c294..ce6e037fdb39 100644
--- a/drivers/thunderbolt/eeprom.c
+++ b/drivers/thunderbolt/eeprom.c
@@ -236,6 +236,15 @@ struct tb_drom_entry_port {
u8 unknown4:2;
} __packed;

+struct tb_drom_entry_pci {
+ /* BYTES 0-1 */
+ struct tb_drom_entry_header header;
+ /* BYTE 2 */
+ u8 unknown:5;
+ u8 slot:3;
+ /* BYTES 3-10 are only present on PCIe upstream ports */
+} __packed;
+

/**
* tb_eeprom_get_drom_offset - get drom offset within eeprom
@@ -365,6 +374,18 @@ static int tb_drom_parse_entry_port(struct tb_switch *sw,
if (entry->has_dual_link_port)
port->dual_link_port =
&port->sw->ports[entry->dual_link_port_nr];
+ } else if (type == TB_TYPE_PCIE_UP || type == TB_TYPE_PCIE_DOWN) {
+ struct tb_drom_entry_pci *entry = (void *) header;
+ if (header->len < sizeof(*entry)) {
+ tb_sw_warn(sw,
+ "port entry has size %#x (expected %#zx+)\n",
+ header->len, sizeof(struct tb_drom_entry_pci));
+ return -EIO;
+ }
+ port->pci.devfn = PCI_DEVFN(entry->slot, 0);
+ memcpy(port->pci.unknown, header + 1,
+ min(header->len - sizeof(struct tb_drom_entry_header),
+ sizeof(port->pci.unknown)));
}
return 0;
}
diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c
index 7442bc4c6433..54fd42250935 100644
--- a/drivers/thunderbolt/switch.c
+++ b/drivers/thunderbolt/switch.c
@@ -577,6 +577,7 @@ int tb_port_clear_counter(struct tb_port *port, int counter)
*/
static int tb_init_port(struct tb_port *port)
{
+ struct tb *tb = port->sw->tb;
int res;
int cap;

@@ -594,9 +595,20 @@ static int tb_init_port(struct tb_port *port)
tb_port_WARN(port, "non switch port without a PHY\n");
}

- tb_dump_port(port->sw->tb, &port->config);
+ tb_dump_port(tb, &port->config);

/* TODO: Read dual link port, DP port and more from EEPROM. */
+ switch (port->config.type) {
+ case TB_TYPE_PCIE_UP:
+ case TB_TYPE_PCIE_DOWN:
+ tb_info(tb, " PCI slot: %02x.0\n", PCI_SLOT(port->pci.devfn));
+ tb_info(tb, " PCI unknown data: %*ph\n",
+ (int)sizeof(port->pci.unknown), port->pci.unknown);
+ break;
+ default:
+ break;
+ }
+
return 0;

}
diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
index 5067d69d0501..755183f0b257 100644
--- a/drivers/thunderbolt/tb.h
+++ b/drivers/thunderbolt/tb.h
@@ -125,6 +125,9 @@ struct tb_switch {
* @dual_link_port: If the switch is connected using two ports, points
* to the other port.
* @link_nr: Is this primary or secondary port on the dual_link.
+ * @pci: Data specific to PCIe adapters
+ * @pci.devfn: PCI slot/function according to DROM
+ * @pci.unknown: Unknown data in DROM (to be reverse-engineered)
*/
struct tb_port {
struct tb_regs_port_header config;
@@ -136,6 +139,12 @@ struct tb_port {
bool disabled;
struct tb_port *dual_link_port;
u8 link_nr:1;
+ union {
+ struct {
+ u8 devfn;
+ u8 unknown[9];
+ } pci;
+ };
};

/**
--
2.18.0