// SPDX-License-Identifier: GPL-2.0 /* * Internal Thunderbolt Connection Manager. This is a firmware running on * the Thunderbolt host controller performing most of the low-level * handling. * * Copyright (C) 2017, Intel Corporation * Authors: Michael Jamet <michael.jamet@intel.com> * Mika Westerberg <mika.westerberg@linux.intel.com>
*/
#define ICM_TIMEOUT 5000 /* ms */ #define ICM_RETRIES 3 #define ICM_APPROVE_TIMEOUT 10000 /* ms */ #define ICM_MAX_LINK 4
staticbool start_icm;
module_param(start_icm, bool, 0444);
MODULE_PARM_DESC(start_icm, "start ICM firmware if it is not running (default: false)");
/** * struct usb4_switch_nvm_auth - Holds USB4 NVM_AUTH status * @reply: Reply from ICM firmware is placed here * @request: Request that is sent to ICM firmware * @icm: Pointer to ICM private data
*/ struct usb4_switch_nvm_auth { struct icm_usb4_switch_op_response reply; struct icm_usb4_switch_op request; struct icm *icm;
};
/** * struct icm - Internal connection manager private data * @request_lock: Makes sure only one message is send to ICM at time * @rescan_work: Work used to rescan the surviving switches after resume * @upstream_port: Pointer to the PCIe upstream port this host * controller is connected. This is only set for systems * where ICM needs to be started manually * @vnd_cap: Vendor defined capability where PCIe2CIO mailbox resides * (only set when @upstream_port is not %NULL) * @safe_mode: ICM is in safe mode * @max_boot_acl: Maximum number of preboot ACL entries (%0 if not supported) * @rpm: Does the controller support runtime PM (RTD3) * @can_upgrade_nvm: Can the NVM firmware be upgrade on this controller * @proto_version: Firmware protocol version * @last_nvm_auth: Last USB4 router NVM_AUTH result (or %NULL if not set) * @veto: Is RTD3 veto in effect * @is_supported: Checks if we can support ICM on this controller * @cio_reset: Trigger CIO reset * @get_mode: Read and return the ICM firmware mode (optional) * @get_route: Find a route string for given switch * @save_devices: Ask ICM to save devices to ACL when suspending (optional) * @driver_ready: Send driver ready message to ICM * @set_uuid: Set UUID for the root switch (optional) * @device_connected: Handle device connected ICM message * @device_disconnected: Handle device disconnected ICM message * @xdomain_connected: Handle XDomain connected ICM message * @xdomain_disconnected: Handle XDomain disconnected ICM message * @rtd3_veto: Handle RTD3 veto notification ICM message
*/ struct icm { struct mutex request_lock; struct delayed_work rescan_work; struct pci_dev *upstream_port; int vnd_cap; bool safe_mode;
size_t max_boot_acl; bool rpm; bool can_upgrade_nvm;
u8 proto_version; struct usb4_switch_nvm_auth *last_nvm_auth; bool veto; bool (*is_supported)(struct tb *tb); int (*cio_reset)(struct tb *tb); int (*get_mode)(struct tb *tb); int (*get_route)(struct tb *tb, u8 link, u8 depth, u64 *route); void (*save_devices)(struct tb *tb); int (*driver_ready)(struct tb *tb, enum tb_security_level *security_level,
u8 *proto_version, size_t *nboot_acl, bool *rpm); void (*set_uuid)(struct tb *tb); void (*device_connected)(struct tb *tb, conststruct icm_pkg_header *hdr); void (*device_disconnected)(struct tb *tb, conststruct icm_pkg_header *hdr); void (*xdomain_connected)(struct tb *tb, conststruct icm_pkg_header *hdr); void (*xdomain_disconnected)(struct tb *tb, conststruct icm_pkg_header *hdr); void (*rtd3_veto)(struct tb *tb, conststruct icm_pkg_header *hdr);
};
/* * If rescan is queued to run (we are resuming), postpone it to give the * firmware some more time to send device connected notifications for next * devices in the chain.
*/ staticvoid icm_postpone_rescan(struct tb *tb)
{ struct icm *icm = tb_priv(tb);
if (delayed_work_pending(&icm->rescan_work))
mod_delayed_work(tb->wq, &icm->rescan_work,
msecs_to_jiffies(500));
}
memset(&reply, 0, sizeof(reply)); /* Use larger timeout as establishing tunnels can take some time */
ret = icm_request(tb, &request, sizeof(request), &reply, sizeof(reply),
1, ICM_RETRIES, ICM_APPROVE_TIMEOUT); if (ret) return ret;
staticint icm_fr_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, int transmit_path, int transmit_ring, int receive_path, int receive_ring)
{ struct icm_fr_pkg_approve_xdomain_response reply; struct icm_fr_pkg_approve_xdomain request; int ret;
memset(&reply, 0, sizeof(reply));
ret = icm_request(tb, &request, sizeof(request), &reply, sizeof(reply),
1, ICM_RETRIES, ICM_TIMEOUT); if (ret) return ret;
if (reply.hdr.flags & ICM_FLAGS_ERROR) return -EIO;
icm_xdomain_activated(xd, true); return 0;
}
staticint icm_fr_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, int transmit_path, int transmit_ring, int receive_path, int receive_ring)
{
u8 phy_port;
u8 cmd;
/* Link the two switches now */
tb_port_at(route, parent_sw)->remote = tb_upstream_port(sw);
tb_upstream_port(sw)->remote = tb_port_at(route, parent_sw);
ret = tb_switch_add(sw); if (ret)
tb_port_at(tb_route(sw), parent_sw)->remote = NULL;
if (pkg->link_info & ICM_LINK_INFO_REJECTED) {
tb_info(tb, "switch at %u.%u was rejected by ICM firmware because topology limit exceeded\n",
link, depth); return;
}
sw = tb_switch_find_by_uuid(tb, &pkg->ep_uuid); if (sw) {
u8 phy_port, sw_phy_port;
/* * On resume ICM will send us connected events for the * devices that still are present. However, that * information might have changed for example by the * fact that a switch on a dual-link connection might * have been enumerated using the other link now. Make * sure our book keeping matches that.
*/ if (sw->depth == depth && sw_phy_port == phy_port &&
!!sw->authorized == authorized) { /* * It was enumerated through another link so update * route string accordingly.
*/ if (sw->link != link) {
ret = icm->get_route(tb, link, depth, &route); if (ret) {
tb_err(tb, "failed to update route string for switch at %u.%u\n",
link, depth);
tb_switch_put(sw); return;
}
} else {
route = tb_route(sw);
}
/* * User connected the same switch to another physical * port or to another part of the topology. Remove the * existing switch now before adding the new one.
*/
remove_switch(sw);
tb_switch_put(sw);
}
/* * If the switch was not found by UUID, look for a switch on * same physical port (taking possible link aggregation into * account) and depth. If we found one it is definitely a stale * one so remove it first.
*/
sw = tb_switch_find_by_link_depth(tb, link, depth); if (!sw) {
u8 dual_link;
dual_link = dual_link_from_link(link); if (dual_link)
sw = tb_switch_find_by_link_depth(tb, dual_link, depth);
} if (sw) {
remove_switch(sw);
tb_switch_put(sw);
}
/* Remove existing XDomain connection if found */
xd = tb_xdomain_find_by_link_depth(tb, link, depth); if (xd) {
remove_xdomain(xd);
tb_xdomain_put(xd);
}
parent_sw = tb_switch_find_by_link_depth(tb, link, depth - 1); if (!parent_sw) {
tb_err(tb, "failed to find parent switch for %u.%u\n",
link, depth); return;
}
ret = icm->get_route(tb, link, depth, &route); if (ret) {
tb_err(tb, "failed to find route string for switch at %u.%u\n",
link, depth);
tb_switch_put(parent_sw); return;
}
/* * If we find an existing XDomain connection remove it * now. We need to go through login handshake and * everything anyway to be able to re-establish the * connection.
*/
remove_xdomain(xd);
tb_xdomain_put(xd);
}
/* * Look if there already exists an XDomain in the same place * than the new one and in that case remove it because it is * most likely another host that got disconnected.
*/
xd = tb_xdomain_find_by_link_depth(tb, link, depth); if (!xd) {
u8 dual_link;
dual_link = dual_link_from_link(link); if (dual_link)
xd = tb_xdomain_find_by_link_depth(tb, dual_link,
depth);
} if (xd) {
remove_xdomain(xd);
tb_xdomain_put(xd);
}
/* * If the user disconnected a switch during suspend and * connected another host to the same port, remove the switch * first.
*/
sw = tb_switch_find_by_route(tb, route); if (sw) {
remove_switch(sw);
tb_switch_put(sw);
}
sw = tb_switch_find_by_link_depth(tb, link, depth); if (!sw) {
tb_warn(tb, "no switch exists at %u.%u, ignoring\n", link,
depth); return;
}
/* * If the connection is through one or multiple devices, the * XDomain device is removed along with them so it is fine if we * cannot find it here.
*/
xd = tb_xdomain_find_by_uuid(tb, &pkg->remote_uuid); if (xd) {
remove_xdomain(xd);
tb_xdomain_put(xd);
}
}
staticint icm_tr_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, int transmit_path, int transmit_ring, int receive_path, int receive_ring)
{ struct icm_tr_pkg_approve_xdomain_response reply; struct icm_tr_pkg_approve_xdomain request; int ret;
memset(&reply, 0, sizeof(reply));
ret = icm_request(tb, &request, sizeof(request), &reply, sizeof(reply),
1, ICM_RETRIES, ICM_TIMEOUT); if (ret) return ret;
if (reply.hdr.flags & ICM_FLAGS_ERROR) return -EIO;
return 0;
}
staticint icm_tr_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, int transmit_path, int transmit_ring, int receive_path, int receive_ring)
{ int ret;
ret = icm_tr_xdomain_tear_down(tb, xd, 1); if (ret) return ret;
usleep_range(10, 50);
ret = icm_tr_xdomain_tear_down(tb, xd, 2); if (ret) return ret;
/* * Currently we don't use the QoS information coming with the * device connected message so simply just ignore that extra * packet for now.
*/ if (pkg->hdr.packet_id) return;
if (pkg->link_info & ICM_LINK_INFO_REJECTED) {
tb_info(tb, "switch at %llx was rejected by ICM firmware because topology limit exceeded\n",
route); return;
}
sw = tb_switch_find_by_uuid(tb, &pkg->ep_uuid); if (sw) { /* Update the switch if it is still in the same place */ if (tb_route(sw) == route && !!sw->authorized == authorized) {
update_switch(sw, route, pkg->connection_id, 0, 0, 0,
boot);
tb_switch_put(sw); return;
}
remove_switch(sw);
tb_switch_put(sw);
}
/* Another switch with the same address */
sw = tb_switch_find_by_route(tb, route); if (sw) {
remove_switch(sw);
tb_switch_put(sw);
}
/* XDomain connection with the same address */
xd = tb_xdomain_find_by_route(tb, route); if (xd) {
remove_xdomain(xd);
tb_xdomain_put(xd);
}
parent_sw = tb_switch_find_by_route(tb, get_parent_route(route)); if (!parent_sw) {
tb_err(tb, "failed to find parent switch for %llx\n", route); return;
}
xd = tb_xdomain_find_by_uuid(tb, &pkg->remote_uuid); if (xd) { if (xd->route == route) {
update_xdomain(xd, route, 0);
tb_xdomain_put(xd); return;
}
remove_xdomain(xd);
tb_xdomain_put(xd);
}
/* An existing xdomain with the same address */
xd = tb_xdomain_find_by_route(tb, route); if (xd) {
remove_xdomain(xd);
tb_xdomain_put(xd);
}
/* * If the user disconnected a switch during suspend and * connected another host to the same port, remove the switch * first.
*/
sw = tb_switch_find_by_route(tb, route); if (sw) {
remove_switch(sw);
tb_switch_put(sw);
}
sw = tb_switch_find_by_route(tb, get_parent_route(route)); if (!sw) {
tb_warn(tb, "no switch exists at %llx, ignoring\n", route); return;
}
parent = pci_upstream_bridge(pdev); while (parent) { if (!pci_is_pcie(parent)) return NULL; if (pci_pcie_type(parent) == PCI_EXP_TYPE_UPSTREAM) break;
parent = pci_upstream_bridge(parent);
}
if (!parent) return NULL;
switch (parent->device) { case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_2C_BRIDGE: case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_4C_BRIDGE: case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_BRIDGE: case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_4C_BRIDGE: case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_2C_BRIDGE: case PCI_DEVICE_ID_INTEL_TITAN_RIDGE_2C_BRIDGE: case PCI_DEVICE_ID_INTEL_TITAN_RIDGE_4C_BRIDGE: return parent;
}
/* * Starting from Alpine Ridge we can use ICM on Apple machines * as well. We just need to reset and re-enable it first. * However, only start it if explicitly asked by the user.
*/ if (icm_firmware_running(tb->nhi)) returntrue; if (!start_icm) returnfalse;
/* * Find the upstream PCIe port in case we need to do reset * through its vendor specific registers.
*/
upstream_port = get_upstream_port(tb->nhi->pdev); if (upstream_port) { int cap;
cap = pci_find_ext_capability(upstream_port,
PCI_EXT_CAP_ID_VNDR); if (cap > 0) {
icm->upstream_port = upstream_port;
icm->vnd_cap = cap;
for (i = 0; i < nuuids; i++) { const u32 *uuid = (const u32 *)&uuids[i];
if (uuid_is_null(&uuids[i])) { /* * Map null UUID to the empty (all one) entries * for ICM.
*/
request.acl[i].uuid_lo = 0xffffffff;
request.acl[i].uuid_hi = 0xffffffff;
} else { /* Two high DWs need to be set to all one */ if (uuid[2] != 0xffffffff || uuid[3] != 0xffffffff) return -EINVAL;
/* * When the domain is stopped we flush its workqueue but before * that the root switch is removed. In that case we should treat * the queued events as being canceled.
*/ if (tb->root_switch) { switch (n->pkg->code) { case ICM_EVENT_DEVICE_CONNECTED:
icm->device_connected(tb, n->pkg); break; case ICM_EVENT_DEVICE_DISCONNECTED:
icm->device_disconnected(tb, n->pkg); break; case ICM_EVENT_XDOMAIN_CONNECTED: if (tb_is_xdomain_enabled())
icm->xdomain_connected(tb, n->pkg); break; case ICM_EVENT_XDOMAIN_DISCONNECTED: if (tb_is_xdomain_enabled())
icm->xdomain_disconnected(tb, n->pkg); break; case ICM_EVENT_DP_CONFIG_CHANGED:
icm_dp_event(tb); break; case ICM_EVENT_RTD3_VETO:
icm->rtd3_veto(tb, n->pkg); break;
}
}
ret = icm->driver_ready(tb, security_level, proto_version, nboot_acl,
rpm); if (ret) {
tb_err(tb, "failed to send driver ready to ICM\n"); return ret;
}
/* * Hold on here until the switch config space is accessible so * that we can read root switch config successfully.
*/ do { struct tb_cfg_result res;
u32 tmp;
res = tb_cfg_read_raw(tb->ctl, &tmp, 0, 0, TB_CFG_SWITCH,
0, 1, 100); if (!res.err) return 0;
/* Put ARC to wait for CIO reset event to happen */
val = ioread32(nhi->iobase + REG_FW_STS);
val |= REG_FW_STS_CIO_RESET_REQ;
iowrite32(val, nhi->iobase + REG_FW_STS);
/* Re-start ARC */
val = ioread32(nhi->iobase + REG_FW_STS);
val |= REG_FW_STS_ICM_EN_INVERT;
val |= REG_FW_STS_ICM_EN_CPU;
iowrite32(val, nhi->iobase + REG_FW_STS);
/* Trigger CIO reset now */ return icm->cio_reset(tb);
}
ret = icm_firmware_reset(tb, nhi); if (ret) return ret;
/* Wait until the ICM firmware tells us it is up and running */ do { /* Check that the ICM firmware is running */
val = ioread32(nhi->iobase + REG_FW_STS); if (val & REG_FW_STS_NVM_AUTH_DONE) return 0;
msleep(300);
} while (--retries);
return -ETIMEDOUT;
}
staticint icm_reset_phy_port(struct tb *tb, int phy_port)
{ struct icm *icm = tb_priv(tb);
u32 state0, state1; int port0, port1;
u32 val0, val1; int ret;
/* * Read link status of both null ports belonging to a single * physical port.
*/
ret = pcie2cio_read(icm, TB_CFG_PORT, port0, PHY_PORT_CS1, &val0); if (ret) return ret;
ret = pcie2cio_read(icm, TB_CFG_PORT, port1, PHY_PORT_CS1, &val1); if (ret) return ret;
ret = icm_firmware_start(tb, nhi); if (ret) {
dev_err(&nhi->pdev->dev, "could not start ICM firmware\n"); return ret;
}
if (icm->get_mode) {
ret = icm->get_mode(tb);
switch (ret) { case NHI_FW_SAFE_MODE:
icm->safe_mode = true; break;
case NHI_FW_CM_MODE: /* Ask ICM to accept all Thunderbolt devices */
nhi_mailbox_cmd(nhi, NHI_MAILBOX_ALLOW_ALL_DEVS, 0); break;
default: if (ret < 0) return ret;
tb_err(tb, "ICM firmware is in wrong mode: %u\n", ret); return -ENODEV;
}
}
/* * Reset both physical ports if there is anything connected to * them already.
*/
ret = icm_reset_phy_port(tb, 0); if (ret)
dev_warn(&nhi->pdev->dev, "failed to reset links on port0\n");
ret = icm_reset_phy_port(tb, 1); if (ret)
dev_warn(&nhi->pdev->dev, "failed to reset links on port1\n");
if (icm->safe_mode) {
tb_info(tb, "Thunderbolt host controller is in safe mode.\n");
tb_info(tb, "You need to update NVM firmware of the controller before it can be used.\n");
tb_info(tb, "For latest updates check https://thunderbolttechnology.net/updates.\n"); return 0;
}
ret = __icm_driver_ready(tb, &tb->security_level, &icm->proto_version,
&tb->nboot_acl, &icm->rpm); if (ret) return ret;
/* * Make sure the number of supported preboot ACL matches what we * expect or disable the whole feature.
*/ if (tb->nboot_acl > icm->max_boot_acl)
tb->nboot_acl = 0;
if (icm->proto_version >= 3)
tb_dbg(tb, "USB4 proxy operations supported\n");
/* * Mark all switches (except root switch) below this one unplugged. ICM * firmware will send us an updated list of switches after we have send * it driver ready command. If a switch is not in that list it will be * removed when we perform rescan.
*/ staticvoid icm_unplug_children(struct tb_switch *sw)
{ struct tb_port *port;
/* * Signal this and switches below for rpm_complete because * tb_switch_remove() calls pm_runtime_get_sync() that then waits * for it.
*/
complete_rpm(&sw->dev, NULL);
bus_for_each_dev(&tb_bus_type, &sw->dev, NULL, complete_rpm);
tb_switch_remove(sw);
/* * If RTD3 was vetoed before we entered system suspend allow it * again now before driver ready is sent. Firmware sends a new RTD3 * veto if it is still the case after we have sent it driver ready * command.
*/
icm_veto_end(tb);
icm_unplug_children(tb->root_switch);
/* * Now all existing children should be resumed, start events * from ICM to get updated status.
*/
__icm_driver_ready(tb, NULL, NULL, NULL, NULL);
/* * We do not get notifications of devices that have been * unplugged during suspend so schedule rescan to clean them up * if any.
*/
queue_delayed_work(tb->wq, &icm->rescan_work, msecs_to_jiffies(500));
}
staticint icm_runtime_resume(struct tb *tb)
{ /* * We can reuse the same resume functionality than with system * suspend.
*/
icm_complete(tb); return 0;
}
/* * USB4 router operation proxy is supported in firmware if the * protocol version is 3 or higher.
*/ if (icm->proto_version < 3) return -EOPNOTSUPP;
/* * NVM_AUTH is a special USB4 proxy operation that does not * return immediately so handle it separately.
*/ if (opcode == USB4_SWITCH_OP_NVM_AUTH) return icm_usb4_switch_nvm_authenticate(tb, route);
case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_2C_NHI: case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_4C_NHI: case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_NHI: case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_4C_NHI: case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_2C_NHI:
icm->max_boot_acl = ICM_AR_PREBOOT_ACL_ENTRIES; /* * NVM upgrade has not been tested on Apple systems and * they don't provide images publicly either. To be on * the safe side prevent root switch NVM upgrade on Macs * for now.
*/
icm->can_upgrade_nvm = !x86_apple_machine;
icm->is_supported = icm_ar_is_supported;
icm->cio_reset = icm_ar_cio_reset;
icm->get_mode = icm_ar_get_mode;
icm->get_route = icm_ar_get_route;
icm->save_devices = icm_fr_save_devices;
icm->driver_ready = icm_ar_driver_ready;
icm->device_connected = icm_fr_device_connected;
icm->device_disconnected = icm_fr_device_disconnected;
icm->xdomain_connected = icm_fr_xdomain_connected;
icm->xdomain_disconnected = icm_fr_xdomain_disconnected;
tb->cm_ops = &icm_ar_ops; break;
case PCI_DEVICE_ID_INTEL_TGL_NHI0: case PCI_DEVICE_ID_INTEL_TGL_NHI1: case PCI_DEVICE_ID_INTEL_TGL_H_NHI0: case PCI_DEVICE_ID_INTEL_TGL_H_NHI1: case PCI_DEVICE_ID_INTEL_ADL_NHI0: case PCI_DEVICE_ID_INTEL_ADL_NHI1: case PCI_DEVICE_ID_INTEL_RPL_NHI0: case PCI_DEVICE_ID_INTEL_RPL_NHI1: case PCI_DEVICE_ID_INTEL_MTL_M_NHI0: case PCI_DEVICE_ID_INTEL_MTL_P_NHI0: case PCI_DEVICE_ID_INTEL_MTL_P_NHI1:
icm->is_supported = icm_tgl_is_supported;
icm->driver_ready = icm_icl_driver_ready;
icm->set_uuid = icm_icl_set_uuid;
icm->device_connected = icm_icl_device_connected;
icm->device_disconnected = icm_tr_device_disconnected;
icm->xdomain_connected = icm_tr_xdomain_connected;
icm->xdomain_disconnected = icm_tr_xdomain_disconnected;
icm->rtd3_veto = icm_icl_rtd3_veto;
tb->cm_ops = &icm_icl_ops; break;
if (!icm->is_supported || !icm->is_supported(tb)) {
dev_dbg(&nhi->pdev->dev, "ICM not supported on this controller\n");
tb_domain_put(tb); return NULL;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.