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

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
......@@ -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.
......@@ -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
......
......@@ -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();
}
......@@ -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)
......
......@@ -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,
};
......
This diff is collapsed.
......@@ -369,6 +369,13 @@ static int tb_start(struct tb *tb)
if (!tb->root_switch)
return -ENOMEM;
/*
* ICM firmware upgrade needs running firmware and in native
* mode that is not available so disable firmware upgrade of the
* root switch.
*/
tb->root_switch->no_nvm_upgrade = true;
ret = tb_switch_configure(tb->root_switch);
if (ret) {
tb_switch_put(tb->root_switch);
......
......@@ -7,6 +7,7 @@
#ifndef TB_H_
#define TB_H_
#include <linux/nvmem-provider.h>
#include <linux/pci.h>
#include <linux/uuid.h>
......@@ -14,6 +15,30 @@
#include "ctl.h"
#include "dma_port.h"
/**
* struct tb_switch_nvm - Structure holding switch NVM information
* @major: Major version number of the active NVM portion
* @minor: Minor version number of the active NVM portion
* @id: Identifier used with both NVM portions
* @active: Active portion NVMem device
* @non_active: Non-active portion NVMem device
* @buf: Buffer where the NVM image is stored before it is written to
* the actual NVM flash device
* @buf_data_size: Number of bytes actually consumed by the new NVM
* image
* @authenticating: The switch is authenticating the new NVM
*/
struct tb_switch_nvm {
u8 major;
u8 minor;
int id;
struct nvmem_device *active;
struct nvmem_device *non_active;
void *buf;
size_t buf_data_size;
bool authenticating;
};
/**
* enum tb_security_level - Thunderbolt security level
* @TB_SECURITY_NONE: No security, legacy mode
......@@ -39,7 +64,8 @@ enum tb_security_level {
* @ports: Ports in this switch
* @dma_port: If the switch has port supporting DMA configuration based
* mailbox this will hold the pointer to that (%NULL
* otherwise).
* otherwise). If set it also means the switch has
* upgradeable NVM.
* @tb: Pointer to the domain the switch belongs to
* @uid: Unique ID of the switch
* @uuid: UUID of the switch (or %NULL if not supported)
......@@ -51,6 +77,9 @@ enum tb_security_level {
* @cap_plug_events: Offset to the plug events capability (%0 if not found)
* @is_unplugged: The switch is going away
* @drom: DROM of the switch (%NULL if not found)
* @nvm: Pointer to the NVM if the switch has one (%NULL otherwise)
* @no_nvm_upgrade: Prevent NVM upgrade of this switch
* @safe_mode: The switch is in safe-mode
* @authorized: Whether the switch is authorized by user or policy
* @work: Work used to automatically authorize a switch
* @security_level: Switch supported security level
......@@ -81,6 +110,9 @@ struct tb_switch {
int cap_plug_events;
bool is_unplugged;
u8 *drom;
struct tb_switch_nvm *nvm;
bool no_nvm_upgrade;
bool safe_mode;
unsigned int authorized;
struct work_struct work;
enum tb_security_level security_level;
......@@ -172,6 +204,7 @@ struct tb_path {
* @approve_switch: Approve switch
* @add_switch_key: Add key to switch
* @challenge_switch_key: Challenge switch using key
* @disconnect_pcie_paths: Disconnects PCIe paths before NVM update
*/
struct tb_cm_ops {
int (*driver_ready)(struct tb *tb);
......@@ -187,6 +220,7 @@ struct tb_cm_ops {
int (*add_switch_key)(struct tb *tb, struct tb_switch *sw);
int (*challenge_switch_key)(struct tb *tb, struct tb_switch *sw,
const u8 *challenge, u8 *response);
int (*disconnect_pcie_paths)(struct tb *tb);
};
/**
......@@ -340,6 +374,7 @@ extern struct device_type tb_switch_type;
int tb_domain_init(void);
void tb_domain_exit(void);
void tb_switch_exit(void);
struct tb *tb_domain_alloc(struct tb_nhi *nhi, size_t privsize);
int tb_domain_add(struct tb *tb);
......@@ -351,6 +386,7 @@ void tb_domain_complete(struct tb *tb);
int tb_domain_approve_switch(struct tb *tb, struct tb_switch *sw);
int tb_domain_approve_switch_key(struct tb *tb, struct tb_switch *sw);
int tb_domain_challenge_switch_key(struct tb *tb, struct tb_switch *sw);
int tb_domain_disconnect_pcie_paths(struct tb *tb);
static inline void tb_domain_put(struct tb *tb)
{
......@@ -359,6 +395,8 @@ static inline void tb_domain_put(struct tb *tb)
struct tb_switch *tb_switch_alloc(struct tb *tb, struct device *parent,
u64 route);
struct tb_switch *tb_switch_alloc_safe_mode(struct tb *tb,
struct device *parent, u64 route);
int tb_switch_configure(struct tb_switch *sw);
int tb_switch_add(struct tb_switch *sw);
void tb_switch_remove(struct tb_switch *sw);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment