thunderbolt: Add support for retimer NVM upgrade when there is no link

With help from platform firmware (ACPI) it is possible to power on
retimers even when there is no USB4 link (e.g nothing is connected to
the USB4 ports). This allows us to bring the USB4 sideband up so that we
can access retimers and upgrade their NVM firmware.

If the platform has support for this, we expose two additional
attributes under USB4 ports: offline and rescan. These can be used to
bring the port offline, rescan for the retimers and put the port online
again. The retimer NVM upgrade itself works the same way than with cable
connected.

Signed-off-by: Rajmohan Mani <rajmohan.mani@intel.com>
Co-developed-by: Mika Westerberg <mika.westerberg@linux.intel.com>
Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Rajmohan Mani 2021-04-01 18:42:38 +03:00 committed by Mika Westerberg
parent 3406de7cc2
commit 3fb10ea4ce
7 changed files with 277 additions and 28 deletions

View File

@ -297,6 +297,32 @@ Contact: Mika Westerberg <mika.westerberg@linux.intel.com>
Description: Returns the current link mode. Possible values are Description: Returns the current link mode. Possible values are
"usb4", "tbt" and "none". "usb4", "tbt" and "none".
What: /sys/bus/thunderbolt/devices/usb4_portX/offline
Date: Sep 2021
KernelVersion: v5.14
Contact: Rajmohan Mani <rajmohan.mani@intel.com>
Description: Writing 1 to this attribute puts the USB4 port into
offline mode. Only allowed when there is nothing
connected to the port (link attribute returns "none").
Once the port is in offline mode it does not receive any
hotplug events. This is used to update NVM firmware of
on-board retimers. Writing 0 puts the port back to
online mode.
This attribute is only visible if the platform supports
powering on retimers when there is no cable connected.
What: /sys/bus/thunderbolt/devices/usb4_portX/rescan
Date: Sep 2021
KernelVersion: v5.14
Contact: Rajmohan Mani <rajmohan.mani@intel.com>
Description: When the USB4 port is in offline mode writing 1 to this
attribute forces rescan of the sideband for on-board
retimers. Each retimer appear under the USB4 port as if
the USB4 link was up. These retimers act in the same way
as if the cable was connected so upgrading their NVM
firmware can be done the usual way.
What: /sys/bus/thunderbolt/devices/<device>:<port>.<index>/device What: /sys/bus/thunderbolt/devices/<device>:<port>.<index>/device
Date: Oct 2020 Date: Oct 2020
KernelVersion: v5.9 KernelVersion: v5.9

View File

@ -256,6 +256,35 @@ Note names of the NVMem devices ``nvm_activeN`` and ``nvm_non_activeN``
depend on the order they are registered in the NVMem subsystem. N in depend on the order they are registered in the NVMem subsystem. N in
the name is the identifier added by the NVMem subsystem. the name is the identifier added by the NVMem subsystem.
Upgrading on-board retimer NVM when there is no cable connected
---------------------------------------------------------------
If the platform supports, it may be possible to upgrade the retimer NVM
firmware even when there is nothing connected to the USB4
ports. When this is the case the ``usb4_portX`` devices have two special
attributes: ``offline`` and ``rescan``. The way to upgrade the firmware
is to first put the USB4 port into offline mode::
# echo 1 > /sys/bus/thunderbolt/devices/0-0/usb4_port1/offline
This step makes sure the port does not respond to any hotplug events,
and also ensures the retimers are powered on. The next step is to scan
for the retimers::
# echo 1 > /sys/bus/thunderbolt/devices/0-0/usb4_port1/rescan
This enumerates and adds the on-board retimers. Now retimer NVM can be
upgraded in the same way than with cable connected (see previous
section). However, the retimer is not disconnected as we are offline
mode) so after writing ``1`` to ``nvm_authenticate`` one should wait for
5 or more seconds before running rescan again::
# echo 1 > /sys/bus/thunderbolt/devices/0-0/usb4_port1/rescan
This point if everything went fine, the port can be put back to
functional state again::
# echo 0 > /sys/bus/thunderbolt/devices/0-0/usb4_port1/offline
Upgrading NVM when host controller is in safe mode Upgrading NVM when host controller is in safe mode
-------------------------------------------------- --------------------------------------------------
If the existing NVM is not properly authenticated (or is missing) the If the existing NVM is not properly authenticated (or is missing) the

View File

@ -401,19 +401,18 @@ static struct tb_retimer *tb_port_find_retimer(struct tb_port *port, u8 index)
/** /**
* tb_retimer_scan() - Scan for on-board retimers under port * tb_retimer_scan() - Scan for on-board retimers under port
* @port: USB4 port to scan * @port: USB4 port to scan
* @add: If true also registers found retimers
* *
* Tries to enumerate on-board retimers connected to @port. Found * Brings the sideband into a state where retimers can be accessed.
* retimers are registered as children of @port. Does not scan for cable * Then Tries to enumerate on-board retimers connected to @port. Found
* retimers for now. * retimers are registered as children of @port if @add is set. Does
* not scan for cable retimers for now.
*/ */
int tb_retimer_scan(struct tb_port *port) int tb_retimer_scan(struct tb_port *port, bool add)
{ {
u32 status[TB_MAX_RETIMER_INDEX + 1] = {}; u32 status[TB_MAX_RETIMER_INDEX + 1] = {};
int ret, i, last_idx = 0; int ret, i, last_idx = 0;
if (!port->cap_usb4)
return 0;
/* /*
* Send broadcast RT to make sure retimer indices facing this * Send broadcast RT to make sure retimer indices facing this
* port are set. * port are set.
@ -422,6 +421,13 @@ int tb_retimer_scan(struct tb_port *port)
if (ret) if (ret)
return ret; return ret;
/*
* Enable sideband channel for each retimer. We can do this
* regardless whether there is device connected or not.
*/
for (i = 1; i <= TB_MAX_RETIMER_INDEX; i++)
usb4_port_retimer_set_inbound_sbtx(port, i);
/* /*
* Before doing anything else, read the authentication status. * Before doing anything else, read the authentication status.
* If the retimer has it set, store it for the new retimer * If the retimer has it set, store it for the new retimer
@ -453,10 +459,10 @@ int tb_retimer_scan(struct tb_port *port)
rt = tb_port_find_retimer(port, i); rt = tb_port_find_retimer(port, i);
if (rt) { if (rt) {
put_device(&rt->dev); put_device(&rt->dev);
} else { } else if (add) {
ret = tb_retimer_add(port, i, status[i]); ret = tb_retimer_add(port, i, status[i]);
if (ret && ret != -EOPNOTSUPP) if (ret && ret != -EOPNOTSUPP)
return ret; break;
} }
} }

View File

@ -1153,6 +1153,33 @@ static int tb_port_start_lane_initialization(struct tb_port *port)
return ret == -EINVAL ? 0 : ret; return ret == -EINVAL ? 0 : ret;
} }
/*
* Returns true if the port had something (router, XDomain) connected
* before suspend.
*/
static bool tb_port_resume(struct tb_port *port)
{
bool has_remote = tb_port_has_remote(port);
if (port->usb4) {
usb4_port_device_resume(port->usb4);
} else if (!has_remote) {
/*
* For disconnected downstream lane adapters start lane
* initialization now so we detect future connects.
*
* For XDomain start the lane initialzation now so the
* link gets re-established.
*
* This is only needed for non-USB4 ports.
*/
if (!tb_is_upstream_port(port) || port->xdomain)
tb_port_start_lane_initialization(port);
}
return has_remote || port->xdomain;
}
/** /**
* tb_port_is_enabled() - Is the adapter port enabled * tb_port_is_enabled() - Is the adapter port enabled
* @port: Port to check * @port: Port to check
@ -2915,22 +2942,11 @@ int tb_switch_resume(struct tb_switch *sw)
/* check for surviving downstream switches */ /* check for surviving downstream switches */
tb_switch_for_each_port(sw, port) { tb_switch_for_each_port(sw, port) {
if (!tb_port_has_remote(port) && !port->xdomain) { if (!tb_port_is_null(port))
/* continue;
* For disconnected downstream lane adapters
* start lane initialization now so we detect if (!tb_port_resume(port))
* future connects.
*/
if (!tb_is_upstream_port(port) && tb_port_is_null(port))
tb_port_start_lane_initialization(port);
continue; continue;
} else if (port->xdomain) {
/*
* Start lane initialization for XDomain so the
* link gets re-established.
*/
tb_port_start_lane_initialization(port);
}
if (tb_wait_for_port(port, true) <= 0) { if (tb_wait_for_port(port, true) <= 0) {
tb_port_warn(port, tb_port_warn(port,
@ -2939,7 +2955,7 @@ int tb_switch_resume(struct tb_switch *sw)
tb_sw_set_unplugged(port->remote->sw); tb_sw_set_unplugged(port->remote->sw);
else if (port->xdomain) else if (port->xdomain)
port->xdomain->is_unplugged = true; port->xdomain->is_unplugged = true;
} else if (tb_port_has_remote(port) || port->xdomain) { } else {
/* /*
* Always unlock the port so the downstream * Always unlock the port so the downstream
* switch/domain is accessible. * switch/domain is accessible.

View File

@ -595,7 +595,7 @@ static void tb_scan_port(struct tb_port *port)
return; return;
} }
tb_retimer_scan(port); tb_retimer_scan(port, true);
sw = tb_switch_alloc(port->sw->tb, &port->sw->dev, sw = tb_switch_alloc(port->sw->tb, &port->sw->dev,
tb_downstream_route(port)); tb_downstream_route(port));
@ -662,7 +662,7 @@ static void tb_scan_port(struct tb_port *port)
tb_sw_warn(sw, "failed to enable TMU\n"); tb_sw_warn(sw, "failed to enable TMU\n");
/* Scan upstream retimers */ /* Scan upstream retimers */
tb_retimer_scan(upstream_port); tb_retimer_scan(upstream_port, true);
/* /*
* Create USB 3.x tunnels only when the switch is plugged to the * Create USB 3.x tunnels only when the switch is plugged to the

View File

@ -249,11 +249,13 @@ struct tb_port {
* @port: Pointer to the lane 0 adapter * @port: Pointer to the lane 0 adapter
* @can_offline: Does the port have necessary platform support to moved * @can_offline: Does the port have necessary platform support to moved
* it into offline mode and back * it into offline mode and back
* @offline: The port is currently in offline mode
*/ */
struct usb4_port { struct usb4_port {
struct device dev; struct device dev;
struct tb_port *port; struct tb_port *port;
bool can_offline; bool can_offline;
bool offline;
}; };
/** /**
@ -1017,7 +1019,7 @@ void tb_xdomain_remove(struct tb_xdomain *xd);
struct tb_xdomain *tb_xdomain_find_by_link_depth(struct tb *tb, u8 link, struct tb_xdomain *tb_xdomain_find_by_link_depth(struct tb *tb, u8 link,
u8 depth); u8 depth);
int tb_retimer_scan(struct tb_port *port); int tb_retimer_scan(struct tb_port *port, bool add);
void tb_retimer_remove_all(struct tb_port *port); void tb_retimer_remove_all(struct tb_port *port);
static inline bool tb_is_retimer(const struct device *dev) static inline bool tb_is_retimer(const struct device *dev)
@ -1105,6 +1107,7 @@ static inline struct usb4_port *tb_to_usb4_port_device(struct device *dev)
struct usb4_port *usb4_port_device_add(struct tb_port *port); struct usb4_port *usb4_port_device_add(struct tb_port *port);
void usb4_port_device_remove(struct usb4_port *usb4); void usb4_port_device_remove(struct usb4_port *usb4);
int usb4_port_device_resume(struct usb4_port *usb4);
/* Keep link controller awake during update */ /* Keep link controller awake during update */
#define QUIRK_FORCE_POWER_LINK_CONTROLLER BIT(0) #define QUIRK_FORCE_POWER_LINK_CONTROLLER BIT(0)

View File

@ -44,8 +44,166 @@ static const struct attribute_group common_group = {
.attrs = common_attrs, .attrs = common_attrs,
}; };
static int usb4_port_offline(struct usb4_port *usb4)
{
struct tb_port *port = usb4->port;
int ret;
ret = tb_acpi_power_on_retimers(port);
if (ret)
return ret;
ret = usb4_port_router_offline(port);
if (ret) {
tb_acpi_power_off_retimers(port);
return ret;
}
ret = tb_retimer_scan(port, false);
if (ret) {
usb4_port_router_online(port);
tb_acpi_power_off_retimers(port);
}
return ret;
}
static void usb4_port_online(struct usb4_port *usb4)
{
struct tb_port *port = usb4->port;
usb4_port_router_online(port);
tb_acpi_power_off_retimers(port);
}
static ssize_t offline_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct usb4_port *usb4 = tb_to_usb4_port_device(dev);
return sysfs_emit(buf, "%d\n", usb4->offline);
}
static ssize_t offline_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct usb4_port *usb4 = tb_to_usb4_port_device(dev);
struct tb_port *port = usb4->port;
struct tb *tb = port->sw->tb;
bool val;
int ret;
ret = kstrtobool(buf, &val);
if (ret)
return ret;
pm_runtime_get_sync(&usb4->dev);
if (mutex_lock_interruptible(&tb->lock)) {
ret = -ERESTARTSYS;
goto out_rpm;
}
if (val == usb4->offline)
goto out_unlock;
/* Offline mode works only for ports that are not connected */
if (tb_port_has_remote(port)) {
ret = -EBUSY;
goto out_unlock;
}
if (val) {
ret = usb4_port_offline(usb4);
if (ret)
goto out_unlock;
} else {
usb4_port_online(usb4);
tb_retimer_remove_all(port);
}
usb4->offline = val;
tb_port_dbg(port, "%s offline mode\n", val ? "enter" : "exit");
out_unlock:
mutex_unlock(&tb->lock);
out_rpm:
pm_runtime_mark_last_busy(&usb4->dev);
pm_runtime_put_autosuspend(&usb4->dev);
return ret ? ret : count;
}
static DEVICE_ATTR_RW(offline);
static ssize_t rescan_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct usb4_port *usb4 = tb_to_usb4_port_device(dev);
struct tb_port *port = usb4->port;
struct tb *tb = port->sw->tb;
bool val;
int ret;
ret = kstrtobool(buf, &val);
if (ret)
return ret;
if (!val)
return count;
pm_runtime_get_sync(&usb4->dev);
if (mutex_lock_interruptible(&tb->lock)) {
ret = -ERESTARTSYS;
goto out_rpm;
}
/* Must be in offline mode already */
if (!usb4->offline) {
ret = -EINVAL;
goto out_unlock;
}
tb_retimer_remove_all(port);
ret = tb_retimer_scan(port, true);
out_unlock:
mutex_unlock(&tb->lock);
out_rpm:
pm_runtime_mark_last_busy(&usb4->dev);
pm_runtime_put_autosuspend(&usb4->dev);
return ret ? ret : count;
}
static DEVICE_ATTR_WO(rescan);
static struct attribute *service_attrs[] = {
&dev_attr_offline.attr,
&dev_attr_rescan.attr,
NULL
};
static umode_t service_attr_is_visible(struct kobject *kobj,
struct attribute *attr, int n)
{
struct device *dev = kobj_to_dev(kobj);
struct usb4_port *usb4 = tb_to_usb4_port_device(dev);
/*
* Always need some platform help to cycle the modes so that
* retimers can be accessed through the sideband.
*/
return usb4->can_offline ? attr->mode : 0;
}
static const struct attribute_group service_group = {
.attrs = service_attrs,
.is_visible = service_attr_is_visible,
};
static const struct attribute_group *usb4_port_device_groups[] = { static const struct attribute_group *usb4_port_device_groups[] = {
&common_group, &common_group,
&service_group,
NULL NULL
}; };
@ -110,3 +268,14 @@ void usb4_port_device_remove(struct usb4_port *usb4)
{ {
device_unregister(&usb4->dev); device_unregister(&usb4->dev);
} }
/**
* usb4_port_device_resume() - Resumes USB4 port device
* @usb4: USB4 port device
*
* Used to resume USB4 port device after sleep state.
*/
int usb4_port_device_resume(struct usb4_port *usb4)
{
return usb4->offline ? usb4_port_offline(usb4) : 0;
}