Skip to content

Commit d024c6e

Browse files
committed
thunderbolt: Correlate PCI devices with Thunderbolt ports
macOS correlates PCI devices with Thunderbolt ports and stores the device paths in their respective I/O Registry entries: IOThunderboltPort@7 { "PCI Device" = 4 "PCI Function" = 0 "PCI Path" = "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/PEG1@1,1/IOPP/UPSB@0/IOPP/DSB2@4" ... } DSB2@4 { "pcidebug" = "6:4:0(132:132)" "Thunderbolt Path" = "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/PEG1@1,1/IOPP/UPSB@0/IOPP/DSB0@0/IOPP/NHI0@0/AppleThunderboltHAL/AppleThunderboltNHIType1/IOThunderboltController/IOThunderboltPort@6/IOThunderboltSwitchType1/IOThunderboltPort@7" ... } Do the same by finding the Thunderbolt port corresponding to a PCI device from a bus notifier and storing a pointer to the PCI device in struct tb_port. On initial switch scan, fill in the pointers for already registered PCI devices. (If a reverse pointer from PCI device to Thunderbolt port is needed, it may be possible to abuse the sysdata pointer in struct pci_dev.) To find the Thunderbolt port, the PCI slot numbers specified in the root switch's DROM need to be available. On Macs with Thunderbolt 1 that's not the case unless the kernel is booted by the EFI stub. Moreover the driver needs to know which tunnels have been established, which is not the case for ICM-controlled tunnel management, so the bus notifier is only registered if tunnel management is under OS control. Perhaps it is possible to retrieve a list of established tunnels from the ICM firmware, or if all else fails, follow the hop entries in a downstream port's config space to discover established tunnels. Correlation would then also work for ICM-controlled tunnel management. Ideas what we can do with correlation: * Represent the relationship between PCI devices and Thunderbolt ports with a symlink in sysfs. * Thunderbolt controllers up to revision 1 of Cactus Ridge 4C have to use INTx because MSI signaling is broken. This results in hotplug ports sharing interrupts with other devices. On tunnel establishment we could prefer downstream ports which do not share interrupts based on the nr_actions field of the correlated PCI devices' irq_desc. * Alternatively, we could use non-working MSI signaling on affected controllers and synthesize an interrupt whenever a tunnel is established or goes down on unplug. The shared interrupts issue is grave. This is /proc/interrupts on a MacBookPro9,1 with a daisy-chain of two Light Ridge controllers and one Port Ridge (all with broken MSI signaling): 16: IO-APIC 16-fasteoi pciehp 17: IO-APIC 17-fasteoi pciehp, pciehp, pciehp, mmc0, snd_hda_intel, b43 18: IO-APIC 18-fasteoi pciehp, pciehp, i801_smbus 19: IO-APIC 19-fasteoi pciehp Cc: Andreas Noever <andreas.noever@gmail.com> Cc: Michael Jamet <michael.jamet@intel.com> Cc: Mika Westerberg <mika.westerberg@linux.intel.com> Cc: Yehezkel Bernat <yehezkel.bernat@intel.com> Signed-off-by: Lukas Wunner <lukas@wunner.de>
1 parent 5531487 commit d024c6e

File tree

5 files changed

+214
-14
lines changed

5 files changed

+214
-14
lines changed

drivers/thunderbolt/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
obj-${CONFIG_THUNDERBOLT} := thunderbolt.o
22
thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o path.o tunnel_pci.o eeprom.o
3-
thunderbolt-objs += domain.o dma_port.o icm.o property.o xdomain.o
3+
thunderbolt-objs += adapter_pci.o domain.o dma_port.o icm.o property.o xdomain.o

drivers/thunderbolt/adapter_pci.c

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* PCIe adapters on a Thunderbolt switch serve as endpoints for PCI tunnels.
4+
* Each may be attached to an upstream or downstream port of the PCIe switch
5+
* integrated into a Thunderbolt controller.
6+
*
7+
* Copyright (C) 2018 Lukas Wunner <lukas@wunner.de>
8+
*/
9+
10+
#include "tb.h"
11+
#include "tunnel_pci.h"
12+
#include "adapter_pci.h"
13+
14+
/**
15+
* tb_pci_adapter() - whether a given PCI device is a Thunderbolt PCIe adapter
16+
* @pdev: PCI device
17+
*
18+
* To keep it simple, this function will return a false positive in a few cases
19+
* and callers need to make sure they can handle that:
20+
* * Upstream port on a host controller
21+
* * Downstream port to the XHCI on a host controller
22+
* * Downstream port on non-chainable endpoint controllers such as Port Ridge
23+
*/
24+
static bool tb_pci_adapter(struct pci_dev *pdev)
25+
{
26+
/* downstream ports with devfn 0 are reserved for the NHI */
27+
return pdev->is_thunderbolt &&
28+
(pci_pcie_type(pdev) == PCI_EXP_TYPE_UPSTREAM ||
29+
(pci_pcie_type(pdev) == PCI_EXP_TYPE_DOWNSTREAM &&
30+
pdev->devfn));
31+
}
32+
33+
/**
34+
* tb_pci_find_port() - locate Thunderbolt port for a given PCI device
35+
* @pdev: PCI device
36+
*
37+
* Walk up the PCI hierarchy from @pdev to discover the sequence of
38+
* PCIe upstream and downstream ports leading to the host controller.
39+
* Then walk down the Thunderbolt daisy-chain following the previously
40+
* discovered sequence along the tunnels we've established.
41+
*
42+
* Return the port corresponding to @pdev, or %NULL if none was found.
43+
*
44+
* This function needs to be called under the global Thunderbolt lock
45+
* to prevent tb_switch and tb_pci_tunnel structs from going away.
46+
*/
47+
static struct tb_port *tb_pci_find_port(struct tb *tb, struct pci_dev *pdev)
48+
{
49+
struct tb_cm *tcm = tb_priv(tb);
50+
struct tb_pci_tunnel *tunnel;
51+
struct tb_port *parent_port;
52+
struct pci_dev *parent_pdev;
53+
int i;
54+
55+
if (!tb_pci_adapter(pdev))
56+
return NULL;
57+
58+
/* base of the recursion: we've reached the host controller */
59+
if (pdev->bus == tb->upstream->subordinate) {
60+
for (i = 1; i <= tb->root_switch->config.max_port_number; i++)
61+
if (tb->root_switch->ports[i].pci.devfn == pdev->devfn)
62+
return &tb->root_switch->ports[i];
63+
64+
return NULL;
65+
}
66+
67+
/* recurse up the PCI hierarchy */
68+
parent_pdev = pci_upstream_bridge(pdev);
69+
if (!parent_pdev)
70+
return NULL;
71+
72+
parent_port = tb_pci_find_port(tb, parent_pdev);
73+
if (!parent_port)
74+
return NULL;
75+
76+
switch (parent_port->config.type) {
77+
case TB_TYPE_PCIE_UP:
78+
/*
79+
* A PCIe upstream adapter is the parent of
80+
* a PCIe downstream adapter on the same switch.
81+
*/
82+
for (i = 1; i <= parent_port->sw->config.max_port_number; i++) {
83+
struct tb_port *port = &parent_port->sw->ports[i];
84+
if (port->config.type == TB_TYPE_PCIE_DOWN &&
85+
port->pci.devfn == pdev->devfn)
86+
return port;
87+
}
88+
return NULL;
89+
case TB_TYPE_PCIE_DOWN:
90+
/*
91+
* A PCIe downstream adapter is the parent of
92+
* a PCIe upstream adapter at the other end of a tunnel.
93+
*/
94+
list_for_each_entry(tunnel, &tcm->tunnel_list, list)
95+
if (tunnel->down_port == parent_port)
96+
return tunnel->up_port;
97+
return NULL;
98+
default:
99+
return NULL;
100+
}
101+
}
102+
103+
/**
104+
* tb_pci_notifier_call() - Thunderbolt PCI bus notifier
105+
* @nb: Notifier block embedded in struct tb_cm
106+
* @action: Notifier action
107+
* @data: PCI device
108+
*
109+
* On addition of PCI device @data, correlate it with a PCIe adapter on the
110+
* Thunderbolt bus and store a pointer to the PCI device in struct tb_port.
111+
* On deletion, reset the pointer to %NULL.
112+
*/
113+
int tb_pci_notifier_call(struct notifier_block *nb, unsigned long action,
114+
void *data)
115+
{
116+
struct tb_cm *tcm = container_of(nb, struct tb_cm, pci_notifier);
117+
struct tb *tb = (void *)(tcm) - offsetof(struct tb, privdata);
118+
struct device *dev = data;
119+
struct pci_dev *pdev = to_pci_dev(dev);
120+
struct tb_port *port;
121+
122+
if ((action != BUS_NOTIFY_ADD_DEVICE &&
123+
action != BUS_NOTIFY_DEL_DEVICE) || !tb_pci_adapter(pdev))
124+
return NOTIFY_DONE;
125+
126+
mutex_lock(&tb->lock);
127+
port = tb_pci_find_port(tb, pdev);
128+
if (!port)
129+
goto out;
130+
131+
switch (action) {
132+
case BUS_NOTIFY_ADD_DEVICE:
133+
port->pci.dev = pdev;
134+
tb_port_info(port, "correlates with %s\n", pci_name(pdev));
135+
break;
136+
case BUS_NOTIFY_DEL_DEVICE:
137+
port->pci.dev = NULL;
138+
tb_port_info(port, "no longer correlates with %s\n",
139+
pci_name(pdev));
140+
break;
141+
}
142+
out:
143+
mutex_unlock(&tb->lock);
144+
return NOTIFY_DONE;
145+
}
146+
147+
/**
148+
* tb_pci_correlate() - Correlate a PCI device with a Thunderbolt port
149+
* @pdev: PCI device
150+
* @data: Thunderbolt bus
151+
*
152+
* Correlate @pdev with a PCIe adapter on Thunderbolt bus @data and store a
153+
* pointer to the PCI device in struct tb_port. This function is intended
154+
* to be used as a pci_walk_bus() callback.
155+
*/
156+
int tb_pci_correlate(struct pci_dev *pdev, void *data)
157+
{
158+
struct tb *tb = data;
159+
struct tb_port *port;
160+
161+
port = tb_pci_find_port(tb, pdev);
162+
if (port) {
163+
port->pci.dev = pdev;
164+
tb_port_info(port, "correlates with %s\n", pci_name(pdev));
165+
}
166+
167+
return 0;
168+
}

drivers/thunderbolt/adapter_pci.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* PCIe adapters on a Thunderbolt switch serve as endpoints for PCI tunnels.
4+
* Each may be attached to an upstream or downstream port of the PCIe switch
5+
* integrated into a Thunderbolt controller.
6+
*
7+
* Copyright (C) 2018 Lukas Wunner <lukas@wunner.de>
8+
*/
9+
10+
#ifndef ADAPTER_PCI_H_
11+
#define ADAPTER_PCI_H_
12+
13+
#include <linux/notifier.h>
14+
15+
int tb_pci_notifier_call(struct notifier_block *nb, unsigned long action,
16+
void *data);
17+
int tb_pci_correlate(struct pci_dev *pdev, void *data);
18+
19+
#endif

drivers/thunderbolt/tb.c

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,7 @@
1313
#include "tb.h"
1414
#include "tb_regs.h"
1515
#include "tunnel_pci.h"
16-
17-
/**
18-
* struct tb_cm - Simple Thunderbolt connection manager
19-
* @tunnel_list: List of active tunnels
20-
* @hotplug_active: tb_handle_hotplug will stop progressing plug
21-
* events and exit if this is not set (it needs to
22-
* acquire the lock one more time). Used to drain wq
23-
* after cfg has been paused.
24-
*/
25-
struct tb_cm {
26-
struct list_head tunnel_list;
27-
bool hotplug_active;
28-
};
16+
#include "adapter_pci.h"
2917

3018
/* enumeration & hot plug handling */
3119

@@ -355,6 +343,7 @@ static void tb_stop(struct tb *tb)
355343
struct tb_pci_tunnel *tunnel;
356344
struct tb_pci_tunnel *n;
357345

346+
bus_unregister_notifier(&pci_bus_type, &tcm->pci_notifier);
358347
/* tunnels are only present after everything has been initialized */
359348
list_for_each_entry_safe(tunnel, n, &tcm->tunnel_list, list) {
360349
tb_pci_deactivate(tunnel);
@@ -395,6 +384,11 @@ static int tb_start(struct tb *tb)
395384

396385
/* Full scan to discover devices added before the driver was loaded. */
397386
tb_scan_switch(tb->root_switch);
387+
388+
/* Correlate PCI devices with Thunderbolt ports */
389+
bus_register_notifier(&pci_bus_type, &tcm->pci_notifier);
390+
pci_walk_bus(tb->upstream->subordinate, tb_pci_correlate, tb);
391+
398392
tb_activate_pcie_devices(tb);
399393

400394
/* Allow tb_handle_hotplug to progress events */
@@ -469,6 +463,7 @@ struct tb *tb_probe(struct tb_nhi *nhi)
469463

470464
tcm = tb_priv(tb);
471465
INIT_LIST_HEAD(&tcm->tunnel_list);
466+
tcm->pci_notifier.notifier_call = tb_pci_notifier_call;
472467

473468
return tb;
474469
}

drivers/thunderbolt/tb.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,20 @@
1717
#include "ctl.h"
1818
#include "dma_port.h"
1919

20+
/**
21+
* struct tb_cm - Native Thunderbolt connection manager
22+
* @pci_notifier: Notifier to correlate PCI devices with Thunderbolt ports
23+
* @tunnel_list: List of active tunnels
24+
* @hotplug_active: tb_handle_hotplug() will stop processing plug events and
25+
* exit if this is not set (it needs to acquire the lock one
26+
* more time). Used to drain wq after cfg has been paused.
27+
*/
28+
struct tb_cm {
29+
struct notifier_block pci_notifier;
30+
struct list_head tunnel_list;
31+
bool hotplug_active;
32+
};
33+
2034
/**
2135
* struct tb_switch_nvm - Structure holding switch NVM information
2236
* @major: Major version number of the active NVM portion
@@ -123,6 +137,9 @@ struct tb_switch {
123137
* @link_nr: Is this primary or secondary port on the dual_link.
124138
* @pci: Data specific to PCIe adapters
125139
* @pci.devfn: PCI slot/function according to DROM
140+
* @pci.dev: PCI device of corresponding PCIe upstream or downstream port
141+
* (%NULL if not found or if removed by PCI core). To access,
142+
* acquire Thunderbolt lock, call pci_dev_get(), release the lock.
126143
*/
127144
struct tb_port {
128145
struct tb_regs_port_header config;
@@ -137,6 +154,7 @@ struct tb_port {
137154
union {
138155
struct {
139156
u8 devfn;
157+
struct pci_dev *dev;
140158
} pci;
141159
};
142160
};

0 commit comments

Comments
 (0)