Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e6b245cc authored by Mika Westerberg's avatar Mika Westerberg Committed by Greg Kroah-Hartman
Browse files

thunderbolt: Add support for host and device NVM firmware upgrade



Starting from Intel Falcon Ridge the NVM firmware can be upgraded by
using DMA configuration based mailbox commands. If we detect that the
host or device (device support starts from Intel Alpine Ridge) has the
DMA configuration based mailbox we expose NVM information to the
userspace as two separate Linux NVMem devices: nvm_active and
nvm_non_active. The former is read-only portion of the active NVM which
firmware upgrade tools can be use to find out suitable NVM image if the
device identification strings are not enough.

The latter is write-only portion where the new NVM image is to be
written by the userspace. It is up to the userspace to find out right
NVM image (the kernel does very minimal validation). The ICM firmware
itself authenticates the new NVM firmware and fails the operation if it
is not what is expected.

We also expose two new sysfs files per each switch: nvm_version and
nvm_authenticate which can be used to read the active NVM version and
start the upgrade process.

We also introduce safe mode which is the mode a switch goes when it does
not have properly authenticated firmware. In this mode the switch only
accepts a couple of commands including flashing a new NVM firmware image
and triggering power cycle.

This code is based on the work done by Amir Levy and Michael Jamet.

Signed-off-by: default avatarMichael Jamet <michael.jamet@intel.com>
Signed-off-by: default avatarMika Westerberg <mika.westerberg@linux.intel.com>
Reviewed-by: default avatarYehezkel Bernat <yehezkel.bernat@intel.com>
Reviewed-by: default avatarAndy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: default avatarAndreas Noever <andreas.noever@gmail.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent f67cf491
Loading
Loading
Loading
Loading
+26 −0
Original line number Diff line number Diff line
@@ -82,3 +82,29 @@ Description: This attribute contains unique_id string of this device.
		This is either read from hardware registers (UUID on
		newer hardware) or based on UID from the device DROM.
		Can be used to uniquely identify particular device.

What:		/sys/bus/thunderbolt/devices/.../nvm_version
Date:		Sep 2017
KernelVersion:	4.13
Contact:	thunderbolt-software@lists.01.org
Description:	If the device has upgradeable firmware the version
		number is available here. Format: %x.%x, major.minor.
		If the device is in safe mode reading the file returns
		-ENODATA instead as the NVM version is not available.

What:		/sys/bus/thunderbolt/devices/.../nvm_authenticate
Date:		Sep 2017
KernelVersion:	4.13
Contact:	thunderbolt-software@lists.01.org
Description:	When new NVM image is written to the non-active NVM
		area (through non_activeX NVMem device), the
		authentication procedure is started by writing 1 to
		this file. If everything goes well, the device is
		restarted with the new NVM firmware. If the image
		verification fails an error code is returned instead.

		When read holds status of the last authentication
		operation if an error occurred during the process. This
		is directly the status value from the DMA configuration
		based mailbox before the device is power cycled. Writing
		0 here clears the status.
+1 −0
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@ menuconfig THUNDERBOLT
	select CRC32
	select CRYPTO
	select CRYPTO_HASH
	select NVMEM
	help
	  Thunderbolt Controller driver. This driver is required if you
	  want to hotplug Thunderbolt devices on Apple hardware or on PCs
+18 −0
Original line number Diff line number Diff line
@@ -426,6 +426,23 @@ int tb_domain_challenge_switch_key(struct tb *tb, struct tb_switch *sw)
	return ret;
}

/**
 * tb_domain_disconnect_pcie_paths() - Disconnect all PCIe paths
 * @tb: Domain whose PCIe paths to disconnect
 *
 * This needs to be called in preparation for NVM upgrade of the host
 * controller. Makes sure all PCIe paths are disconnected.
 *
 * Return %0 on success and negative errno in case of error.
 */
int tb_domain_disconnect_pcie_paths(struct tb *tb)
{
	if (!tb->cm_ops->disconnect_pcie_paths)
		return -EPERM;

	return tb->cm_ops->disconnect_pcie_paths(tb);
}

int tb_domain_init(void)
{
	return bus_register(&tb_bus_type);
@@ -435,4 +452,5 @@ void tb_domain_exit(void)
{
	bus_unregister(&tb_bus_type);
	ida_destroy(&tb_domain_ida);
	tb_switch_exit();
}
+32 −1
Original line number Diff line number Diff line
@@ -54,6 +54,7 @@
 *		   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
 * @is_supported: Checks if we can support ICM on this controller
 * @get_mode: Read and return the ICM firmware mode (optional)
 * @get_route: Find a route string for given switch
@@ -65,6 +66,7 @@ struct icm {
	struct delayed_work rescan_work;
	struct pci_dev *upstream_port;
	int vnd_cap;
	bool safe_mode;
	bool (*is_supported)(struct tb *tb);
	int (*get_mode)(struct tb *tb);
	int (*get_route)(struct tb *tb, u8 link, u8 depth, u64 *route);
@@ -852,6 +854,10 @@ static int icm_firmware_init(struct tb *tb)
		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);
@@ -879,12 +885,20 @@ static int icm_firmware_init(struct tb *tb)

static int icm_driver_ready(struct tb *tb)
{
	struct icm *icm = tb_priv(tb);
	int ret;

	ret = icm_firmware_init(tb);
	if (ret)
		return ret;

	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;
	}

	return __icm_driver_ready(tb, &tb->security_level);
}

@@ -975,12 +989,23 @@ static void icm_complete(struct tb *tb)

static int icm_start(struct tb *tb)
{
	struct icm *icm = tb_priv(tb);
	int ret;

	if (icm->safe_mode)
		tb->root_switch = tb_switch_alloc_safe_mode(tb, &tb->dev, 0);
	else
		tb->root_switch = tb_switch_alloc(tb, &tb->dev, 0);
	if (!tb->root_switch)
		return -ENODEV;

	/*
	 * 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.
	 */
	tb->root_switch->no_nvm_upgrade = is_apple();

	ret = tb_switch_add(tb->root_switch);
	if (ret)
		tb_switch_put(tb->root_switch);
@@ -998,6 +1023,11 @@ static void icm_stop(struct tb *tb)
	nhi_mailbox_cmd(tb->nhi, NHI_MAILBOX_DRV_UNLOADS, 0);
}

static int icm_disconnect_pcie_paths(struct tb *tb)
{
	return nhi_mailbox_cmd(tb->nhi, NHI_MAILBOX_DISCONNECT_PCIE_PATHS, 0);
}

/* Falcon Ridge and Alpine Ridge */
static const struct tb_cm_ops icm_fr_ops = {
	.driver_ready = icm_driver_ready,
@@ -1009,6 +1039,7 @@ static const struct tb_cm_ops icm_fr_ops = {
	.approve_switch = icm_fr_approve_switch,
	.add_switch_key = icm_fr_add_switch_key,
	.challenge_switch_key = icm_fr_challenge_switch_key,
	.disconnect_pcie_paths = icm_disconnect_pcie_paths,
};

struct tb *icm_probe(struct tb_nhi *nhi)
+1 −0
Original line number Diff line number Diff line
@@ -155,6 +155,7 @@ enum nhi_fw_mode {

enum nhi_mailbox_cmd {
	NHI_MAILBOX_SAVE_DEVS = 0x05,
	NHI_MAILBOX_DISCONNECT_PCIE_PATHS = 0x06,
	NHI_MAILBOX_DRV_UNLOADS = 0x07,
	NHI_MAILBOX_ALLOW_ALL_DEVS = 0x23,
};
Loading