mirror of
https://github.com/torvalds/linux.git
synced 2024-11-27 14:41:39 +00:00
0414bec5f3
In Apple Macs the boot firmware (EFI) connects all devices automatically when the system is started, before it hands over to the OS. Instead of ignoring we discover all those PCIe tunnels and record them using our internal structures, just like we do when a device is connected after the OS is already up. By doing this we can properly tear down tunnels when devices are disconnected. Also this allows us to resume the existing tunnels after system suspend/resume cycle. Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
342 lines
7.9 KiB
C
342 lines
7.9 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Thunderbolt driver - Tunneling support
|
|
*
|
|
* Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
|
|
* Copyright (C) 2019, Intel Corporation
|
|
*/
|
|
|
|
#include <linux/slab.h>
|
|
#include <linux/list.h>
|
|
|
|
#include "tunnel.h"
|
|
#include "tb.h"
|
|
|
|
/* PCIe adapters use always HopID of 8 for both directions */
|
|
#define TB_PCI_HOPID 8
|
|
|
|
#define TB_PCI_PATH_DOWN 0
|
|
#define TB_PCI_PATH_UP 1
|
|
|
|
#define __TB_TUNNEL_PRINT(level, tunnel, fmt, arg...) \
|
|
do { \
|
|
struct tb_tunnel *__tunnel = (tunnel); \
|
|
level(__tunnel->tb, "%llx:%x <-> %llx:%x (PCI): " fmt, \
|
|
tb_route(__tunnel->src_port->sw), \
|
|
__tunnel->src_port->port, \
|
|
tb_route(__tunnel->dst_port->sw), \
|
|
__tunnel->dst_port->port, \
|
|
## arg); \
|
|
} while (0)
|
|
|
|
#define tb_tunnel_WARN(tunnel, fmt, arg...) \
|
|
__TB_TUNNEL_PRINT(tb_WARN, tunnel, fmt, ##arg)
|
|
#define tb_tunnel_warn(tunnel, fmt, arg...) \
|
|
__TB_TUNNEL_PRINT(tb_warn, tunnel, fmt, ##arg)
|
|
#define tb_tunnel_info(tunnel, fmt, arg...) \
|
|
__TB_TUNNEL_PRINT(tb_info, tunnel, fmt, ##arg)
|
|
#define tb_tunnel_dbg(tunnel, fmt, arg...) \
|
|
__TB_TUNNEL_PRINT(tb_dbg, tunnel, fmt, ##arg)
|
|
|
|
static struct tb_tunnel *tb_tunnel_alloc(struct tb *tb, size_t npaths)
|
|
{
|
|
struct tb_tunnel *tunnel;
|
|
|
|
tunnel = kzalloc(sizeof(*tunnel), GFP_KERNEL);
|
|
if (!tunnel)
|
|
return NULL;
|
|
|
|
tunnel->paths = kcalloc(npaths, sizeof(tunnel->paths[0]), GFP_KERNEL);
|
|
if (!tunnel->paths) {
|
|
tb_tunnel_free(tunnel);
|
|
return NULL;
|
|
}
|
|
|
|
INIT_LIST_HEAD(&tunnel->list);
|
|
tunnel->tb = tb;
|
|
tunnel->npaths = npaths;
|
|
|
|
return tunnel;
|
|
}
|
|
|
|
static int tb_pci_activate(struct tb_tunnel *tunnel, bool activate)
|
|
{
|
|
int res;
|
|
|
|
res = tb_pci_port_enable(tunnel->src_port, activate);
|
|
if (res)
|
|
return res;
|
|
|
|
if (tb_port_is_pcie_up(tunnel->dst_port))
|
|
return tb_pci_port_enable(tunnel->dst_port, activate);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void tb_pci_init_path(struct tb_path *path)
|
|
{
|
|
path->egress_fc_enable = TB_PATH_SOURCE | TB_PATH_INTERNAL;
|
|
path->egress_shared_buffer = TB_PATH_NONE;
|
|
path->ingress_fc_enable = TB_PATH_ALL;
|
|
path->ingress_shared_buffer = TB_PATH_NONE;
|
|
path->priority = 3;
|
|
path->weight = 1;
|
|
path->drop_packages = 0;
|
|
path->nfc_credits = 0;
|
|
path->hops[0].initial_credits = 7;
|
|
path->hops[1].initial_credits = 16;
|
|
}
|
|
|
|
/**
|
|
* tb_tunnel_discover_pci() - Discover existing PCIe tunnels
|
|
* @tb: Pointer to the domain structure
|
|
* @down: PCIe downstream adapter
|
|
*
|
|
* If @down adapter is active, follows the tunnel to the PCIe upstream
|
|
* adapter and back. Returns the discovered tunnel or %NULL if there was
|
|
* no tunnel.
|
|
*/
|
|
struct tb_tunnel *tb_tunnel_discover_pci(struct tb *tb, struct tb_port *down)
|
|
{
|
|
struct tb_tunnel *tunnel;
|
|
struct tb_path *path;
|
|
|
|
if (!tb_pci_port_is_enabled(down))
|
|
return NULL;
|
|
|
|
tunnel = tb_tunnel_alloc(tb, 2);
|
|
if (!tunnel)
|
|
return NULL;
|
|
|
|
tunnel->activate = tb_pci_activate;
|
|
tunnel->src_port = down;
|
|
|
|
/*
|
|
* Discover both paths even if they are not complete. We will
|
|
* clean them up by calling tb_tunnel_deactivate() below in that
|
|
* case.
|
|
*/
|
|
path = tb_path_discover(down, TB_PCI_HOPID, NULL, -1,
|
|
&tunnel->dst_port, "PCIe Up");
|
|
if (!path) {
|
|
/* Just disable the downstream port */
|
|
tb_pci_port_enable(down, false);
|
|
goto err_free;
|
|
}
|
|
tunnel->paths[TB_PCI_PATH_UP] = path;
|
|
tb_pci_init_path(tunnel->paths[TB_PCI_PATH_UP]);
|
|
|
|
path = tb_path_discover(tunnel->dst_port, -1, down, TB_PCI_HOPID, NULL,
|
|
"PCIe Down");
|
|
if (!path)
|
|
goto err_deactivate;
|
|
tunnel->paths[TB_PCI_PATH_DOWN] = path;
|
|
tb_pci_init_path(tunnel->paths[TB_PCI_PATH_DOWN]);
|
|
|
|
/* Validate that the tunnel is complete */
|
|
if (!tb_port_is_pcie_up(tunnel->dst_port)) {
|
|
tb_port_warn(tunnel->dst_port,
|
|
"path does not end on a PCIe adapter, cleaning up\n");
|
|
goto err_deactivate;
|
|
}
|
|
|
|
if (down != tunnel->src_port) {
|
|
tb_tunnel_warn(tunnel, "path is not complete, cleaning up\n");
|
|
goto err_deactivate;
|
|
}
|
|
|
|
if (!tb_pci_port_is_enabled(tunnel->dst_port)) {
|
|
tb_tunnel_warn(tunnel,
|
|
"tunnel is not fully activated, cleaning up\n");
|
|
goto err_deactivate;
|
|
}
|
|
|
|
tb_tunnel_dbg(tunnel, "discovered\n");
|
|
return tunnel;
|
|
|
|
err_deactivate:
|
|
tb_tunnel_deactivate(tunnel);
|
|
err_free:
|
|
tb_tunnel_free(tunnel);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* tb_tunnel_alloc_pci() - allocate a pci tunnel
|
|
* @tb: Pointer to the domain structure
|
|
* @up: PCIe upstream adapter port
|
|
* @down: PCIe downstream adapter port
|
|
*
|
|
* Allocate a PCI tunnel. The ports must be of type TB_TYPE_PCIE_UP and
|
|
* TB_TYPE_PCIE_DOWN.
|
|
*
|
|
* Return: Returns a tb_tunnel on success or NULL on failure.
|
|
*/
|
|
struct tb_tunnel *tb_tunnel_alloc_pci(struct tb *tb, struct tb_port *up,
|
|
struct tb_port *down)
|
|
{
|
|
struct tb_tunnel *tunnel;
|
|
struct tb_path *path;
|
|
|
|
tunnel = tb_tunnel_alloc(tb, 2);
|
|
if (!tunnel)
|
|
return NULL;
|
|
|
|
tunnel->activate = tb_pci_activate;
|
|
tunnel->src_port = down;
|
|
tunnel->dst_port = up;
|
|
|
|
path = tb_path_alloc(tb, down, TB_PCI_HOPID, up, TB_PCI_HOPID, 0,
|
|
"PCIe Down");
|
|
if (!path) {
|
|
tb_tunnel_free(tunnel);
|
|
return NULL;
|
|
}
|
|
tb_pci_init_path(path);
|
|
tunnel->paths[TB_PCI_PATH_UP] = path;
|
|
|
|
path = tb_path_alloc(tb, up, TB_PCI_HOPID, down, TB_PCI_HOPID, 0,
|
|
"PCIe Up");
|
|
if (!path) {
|
|
tb_tunnel_free(tunnel);
|
|
return NULL;
|
|
}
|
|
tb_pci_init_path(path);
|
|
tunnel->paths[TB_PCI_PATH_DOWN] = path;
|
|
|
|
return tunnel;
|
|
}
|
|
|
|
/**
|
|
* tb_tunnel_free() - free a tunnel
|
|
* @tunnel: Tunnel to be freed
|
|
*
|
|
* The tunnel must have been deactivated.
|
|
*/
|
|
void tb_tunnel_free(struct tb_tunnel *tunnel)
|
|
{
|
|
int i;
|
|
|
|
if (!tunnel)
|
|
return;
|
|
|
|
for (i = 0; i < tunnel->npaths; i++) {
|
|
if (tunnel->paths[i] && tunnel->paths[i]->activated) {
|
|
tb_tunnel_WARN(tunnel,
|
|
"trying to free an activated tunnel\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < tunnel->npaths; i++) {
|
|
if (tunnel->paths[i])
|
|
tb_path_free(tunnel->paths[i]);
|
|
}
|
|
|
|
kfree(tunnel->paths);
|
|
kfree(tunnel);
|
|
}
|
|
|
|
/**
|
|
* tb_tunnel_is_invalid - check whether an activated path is still valid
|
|
* @tunnel: Tunnel to check
|
|
*/
|
|
bool tb_tunnel_is_invalid(struct tb_tunnel *tunnel)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < tunnel->npaths; i++) {
|
|
WARN_ON(!tunnel->paths[i]->activated);
|
|
if (tb_path_is_invalid(tunnel->paths[i]))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* tb_tunnel_restart() - activate a tunnel after a hardware reset
|
|
* @tunnel: Tunnel to restart
|
|
*
|
|
* Return: 0 on success and negative errno in case if failure
|
|
*/
|
|
int tb_tunnel_restart(struct tb_tunnel *tunnel)
|
|
{
|
|
int res, i;
|
|
|
|
tb_tunnel_info(tunnel, "activating\n");
|
|
|
|
/*
|
|
* Make sure all paths are properly disabled before enabling
|
|
* them again.
|
|
*/
|
|
for (i = 0; i < tunnel->npaths; i++) {
|
|
if (tunnel->paths[i]->activated) {
|
|
tb_path_deactivate(tunnel->paths[i]);
|
|
tunnel->paths[i]->activated = false;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < tunnel->npaths; i++) {
|
|
res = tb_path_activate(tunnel->paths[i]);
|
|
if (res)
|
|
goto err;
|
|
}
|
|
|
|
if (tunnel->activate) {
|
|
res = tunnel->activate(tunnel, true);
|
|
if (res)
|
|
goto err;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err:
|
|
tb_tunnel_warn(tunnel, "activation failed\n");
|
|
tb_tunnel_deactivate(tunnel);
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* tb_tunnel_activate() - activate a tunnel
|
|
* @tunnel: Tunnel to activate
|
|
*
|
|
* Return: Returns 0 on success or an error code on failure.
|
|
*/
|
|
int tb_tunnel_activate(struct tb_tunnel *tunnel)
|
|
{
|
|
int i;
|
|
|
|
tb_tunnel_info(tunnel, "activating\n");
|
|
|
|
for (i = 0; i < tunnel->npaths; i++) {
|
|
if (tunnel->paths[i]->activated) {
|
|
tb_tunnel_WARN(tunnel,
|
|
"trying to activate an already activated tunnel\n");
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
return tb_tunnel_restart(tunnel);
|
|
}
|
|
|
|
/**
|
|
* tb_tunnel_deactivate() - deactivate a tunnel
|
|
* @tunnel: Tunnel to deactivate
|
|
*/
|
|
void tb_tunnel_deactivate(struct tb_tunnel *tunnel)
|
|
{
|
|
int i;
|
|
|
|
tb_tunnel_info(tunnel, "deactivating\n");
|
|
|
|
if (tunnel->activate)
|
|
tunnel->activate(tunnel, false);
|
|
|
|
for (i = 0; i < tunnel->npaths; i++) {
|
|
if (tunnel->paths[i] && tunnel->paths[i]->activated)
|
|
tb_path_deactivate(tunnel->paths[i]);
|
|
}
|
|
}
|