mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-12-29 17:25:38 +00:00
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:
parent
3406de7cc2
commit
3fb10ea4ce
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user