Skip to content

Commit

Permalink
thunderbolt: Add support for host and device NVM firmware upgrade
Browse files Browse the repository at this point in the history
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: Michael Jamet <[email protected]>
Signed-off-by: Mika Westerberg <[email protected]>
Reviewed-by: Yehezkel Bernat <[email protected]>
Reviewed-by: Andy Shevchenko <[email protected]>
Signed-off-by: Andreas Noever <[email protected]>
Signed-off-by: Greg Kroah-Hartman <[email protected]>
  • Loading branch information
westeri authored and gregkh committed Jun 9, 2017
1 parent f67cf49 commit e6b245c
Show file tree
Hide file tree
Showing 8 changed files with 706 additions and 23 deletions.
26 changes: 26 additions & 0 deletions Documentation/ABI/testing/sysfs-bus-thunderbolt
Original file line number Diff line number Diff line change
Expand Up @@ -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: [email protected]
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: [email protected]
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 change: 1 addition & 0 deletions drivers/thunderbolt/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 18 additions & 0 deletions drivers/thunderbolt/domain.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -435,4 +452,5 @@ void tb_domain_exit(void)
{
bus_unregister(&tb_bus_type);
ida_destroy(&tb_domain_ida);
tb_switch_exit();
}
33 changes: 32 additions & 1 deletion drivers/thunderbolt/icm.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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;

tb->root_switch = tb_switch_alloc(tb, &tb->dev, 0);
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);
Expand All @@ -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,
Expand All @@ -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)
Expand Down
1 change: 1 addition & 0 deletions drivers/thunderbolt/nhi.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down
Loading

0 comments on commit e6b245c

Please sign in to comment.