summaryrefslogtreecommitdiff
path: root/drivers/thunderbolt/icm.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/thunderbolt/icm.c')
-rw-r--r--drivers/thunderbolt/icm.c174
1 files changed, 144 insertions, 30 deletions
diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c
index 500911f16498..e1e264a9a4c7 100644
--- a/drivers/thunderbolt/icm.c
+++ b/drivers/thunderbolt/icm.c
@@ -15,6 +15,7 @@
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/pci.h>
+#include <linux/pm_runtime.h>
#include <linux/platform_data/x86/apple.h>
#include <linux/sizes.h>
#include <linux/slab.h>
@@ -57,9 +58,11 @@
* (only set when @upstream_port is not %NULL)
* @safe_mode: ICM is in safe mode
* @max_boot_acl: Maximum number of preboot ACL entries (%0 if not supported)
+ * @rpm: Does the controller support runtime PM (RTD3)
* @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
+ * @save_devices: Ask ICM to save devices to ACL when suspending (optional)
* @driver_ready: Send driver ready message to ICM
* @device_connected: Handle device connected ICM message
* @device_disconnected: Handle device disconnected ICM message
@@ -73,12 +76,14 @@ struct icm {
size_t max_boot_acl;
int vnd_cap;
bool safe_mode;
+ bool rpm;
bool (*is_supported)(struct tb *tb);
int (*get_mode)(struct tb *tb);
int (*get_route)(struct tb *tb, u8 link, u8 depth, u64 *route);
+ void (*save_devices)(struct tb *tb);
int (*driver_ready)(struct tb *tb,
enum tb_security_level *security_level,
- size_t *nboot_acl);
+ size_t *nboot_acl, bool *rpm);
void (*device_connected)(struct tb *tb,
const struct icm_pkg_header *hdr);
void (*device_disconnected)(struct tb *tb,
@@ -95,6 +100,47 @@ struct icm_notification {
struct tb *tb;
};
+struct ep_name_entry {
+ u8 len;
+ u8 type;
+ u8 data[0];
+};
+
+#define EP_NAME_INTEL_VSS 0x10
+
+/* Intel Vendor specific structure */
+struct intel_vss {
+ u16 vendor;
+ u16 model;
+ u8 mc;
+ u8 flags;
+ u16 pci_devid;
+ u32 nvm_version;
+};
+
+#define INTEL_VSS_FLAGS_RTD3 BIT(0)
+
+static const struct intel_vss *parse_intel_vss(const void *ep_name, size_t size)
+{
+ const void *end = ep_name + size;
+
+ while (ep_name < end) {
+ const struct ep_name_entry *ep = ep_name;
+
+ if (!ep->len)
+ break;
+ if (ep_name + ep->len > end)
+ break;
+
+ if (ep->type == EP_NAME_INTEL_VSS)
+ return (const struct intel_vss *)ep->data;
+
+ ep_name += ep->len;
+ }
+
+ return NULL;
+}
+
static inline struct tb *icm_to_tb(struct icm *icm)
{
return ((void *)icm - sizeof(struct tb));
@@ -258,9 +304,14 @@ err_free:
return ret;
}
+static void icm_fr_save_devices(struct tb *tb)
+{
+ nhi_mailbox_cmd(tb->nhi, NHI_MAILBOX_SAVE_DEVS, 0);
+}
+
static int
icm_fr_driver_ready(struct tb *tb, enum tb_security_level *security_level,
- size_t *nboot_acl)
+ size_t *nboot_acl, bool *rpm)
{
struct icm_fr_pkg_driver_ready_response reply;
struct icm_pkg_driver_ready request = {
@@ -410,15 +461,19 @@ static int icm_fr_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd)
}
static void add_switch(struct tb_switch *parent_sw, u64 route,
- const uuid_t *uuid, u8 connection_id, u8 connection_key,
+ const uuid_t *uuid, const u8 *ep_name,
+ size_t ep_name_size, u8 connection_id, u8 connection_key,
u8 link, u8 depth, enum tb_security_level security_level,
bool authorized, bool boot)
{
+ const struct intel_vss *vss;
struct tb_switch *sw;
+ pm_runtime_get_sync(&parent_sw->dev);
+
sw = tb_switch_alloc(parent_sw->tb, &parent_sw->dev, route);
if (!sw)
- return;
+ goto out;
sw->uuid = kmemdup(uuid, sizeof(*uuid), GFP_KERNEL);
sw->connection_id = connection_id;
@@ -429,6 +484,10 @@ static void add_switch(struct tb_switch *parent_sw, u64 route,
sw->security_level = security_level;
sw->boot = boot;
+ vss = parse_intel_vss(ep_name, ep_name_size);
+ if (vss)
+ sw->rpm = !!(vss->flags & INTEL_VSS_FLAGS_RTD3);
+
/* Link the two switches now */
tb_port_at(route, parent_sw)->remote = tb_upstream_port(sw);
tb_upstream_port(sw)->remote = tb_port_at(route, parent_sw);
@@ -436,8 +495,11 @@ static void add_switch(struct tb_switch *parent_sw, u64 route,
if (tb_switch_add(sw)) {
tb_port_at(tb_route(sw), parent_sw)->remote = NULL;
tb_switch_put(sw);
- return;
}
+
+out:
+ pm_runtime_mark_last_busy(&parent_sw->dev);
+ pm_runtime_put_autosuspend(&parent_sw->dev);
}
static void update_switch(struct tb_switch *parent_sw, struct tb_switch *sw,
@@ -477,9 +539,11 @@ static void add_xdomain(struct tb_switch *sw, u64 route,
{
struct tb_xdomain *xd;
+ pm_runtime_get_sync(&sw->dev);
+
xd = tb_xdomain_alloc(sw->tb, &sw->dev, route, local_uuid, remote_uuid);
if (!xd)
- return;
+ goto out;
xd->link = link;
xd->depth = depth;
@@ -487,6 +551,10 @@ static void add_xdomain(struct tb_switch *sw, u64 route,
tb_port_at(route, sw)->xdomain = xd;
tb_xdomain_add(xd);
+
+out:
+ pm_runtime_mark_last_busy(&sw->dev);
+ pm_runtime_put_autosuspend(&sw->dev);
}
static void update_xdomain(struct tb_xdomain *xd, u64 route, u8 link)
@@ -534,20 +602,13 @@ icm_fr_device_connected(struct tb *tb, const struct icm_pkg_header *hdr)
return;
}
- ret = icm->get_route(tb, link, depth, &route);
- if (ret) {
- tb_err(tb, "failed to find route string for switch at %u.%u\n",
- link, depth);
- return;
- }
-
sw = tb_switch_find_by_uuid(tb, &pkg->ep_uuid);
if (sw) {
u8 phy_port, sw_phy_port;
parent_sw = tb_to_switch(sw->dev.parent);
- sw_phy_port = phy_port_from_route(tb_route(sw), sw->depth);
- phy_port = phy_port_from_route(route, depth);
+ sw_phy_port = tb_phy_port_from_link(sw->link);
+ phy_port = tb_phy_port_from_link(link);
/*
* On resume ICM will send us connected events for the
@@ -559,6 +620,22 @@ icm_fr_device_connected(struct tb *tb, const struct icm_pkg_header *hdr)
*/
if (sw->depth == depth && sw_phy_port == phy_port &&
!!sw->authorized == authorized) {
+ /*
+ * It was enumerated through another link so update
+ * route string accordingly.
+ */
+ if (sw->link != link) {
+ ret = icm->get_route(tb, link, depth, &route);
+ if (ret) {
+ tb_err(tb, "failed to update route string for switch at %u.%u\n",
+ link, depth);
+ tb_switch_put(sw);
+ return;
+ }
+ } else {
+ route = tb_route(sw);
+ }
+
update_switch(parent_sw, sw, route, pkg->connection_id,
pkg->connection_key, link, depth, boot);
tb_switch_put(sw);
@@ -607,7 +684,16 @@ icm_fr_device_connected(struct tb *tb, const struct icm_pkg_header *hdr)
return;
}
- add_switch(parent_sw, route, &pkg->ep_uuid, pkg->connection_id,
+ ret = icm->get_route(tb, link, depth, &route);
+ if (ret) {
+ tb_err(tb, "failed to find route string for switch at %u.%u\n",
+ link, depth);
+ tb_switch_put(parent_sw);
+ return;
+ }
+
+ add_switch(parent_sw, route, &pkg->ep_uuid, (const u8 *)pkg->ep_name,
+ sizeof(pkg->ep_name), pkg->connection_id,
pkg->connection_key, link, depth, security_level,
authorized, boot);
@@ -650,7 +736,6 @@ icm_fr_xdomain_connected(struct tb *tb, const struct icm_pkg_header *hdr)
struct tb_xdomain *xd;
struct tb_switch *sw;
u8 link, depth;
- bool approved;
u64 route;
/*
@@ -664,7 +749,6 @@ icm_fr_xdomain_connected(struct tb *tb, const struct icm_pkg_header *hdr)
link = pkg->link_info & ICM_LINK_INFO_LINK_MASK;
depth = (pkg->link_info & ICM_LINK_INFO_DEPTH_MASK) >>
ICM_LINK_INFO_DEPTH_SHIFT;
- approved = pkg->link_info & ICM_LINK_INFO_APPROVED;
if (link > ICM_MAX_LINK || depth > ICM_MAX_DEPTH) {
tb_warn(tb, "invalid topology %u.%u, ignoring\n", link, depth);
@@ -757,7 +841,7 @@ icm_fr_xdomain_disconnected(struct tb *tb, const struct icm_pkg_header *hdr)
static int
icm_tr_driver_ready(struct tb *tb, enum tb_security_level *security_level,
- size_t *nboot_acl)
+ size_t *nboot_acl, bool *rpm)
{
struct icm_tr_pkg_driver_ready_response reply;
struct icm_pkg_driver_ready request = {
@@ -776,6 +860,9 @@ icm_tr_driver_ready(struct tb *tb, enum tb_security_level *security_level,
if (nboot_acl)
*nboot_acl = (reply.info & ICM_TR_INFO_BOOT_ACL_MASK) >>
ICM_TR_INFO_BOOT_ACL_SHIFT;
+ if (rpm)
+ *rpm = !!(reply.hdr.flags & ICM_TR_FLAGS_RTD3);
+
return 0;
}
@@ -1005,7 +1092,8 @@ icm_tr_device_connected(struct tb *tb, const struct icm_pkg_header *hdr)
return;
}
- add_switch(parent_sw, route, &pkg->ep_uuid, pkg->connection_id,
+ add_switch(parent_sw, route, &pkg->ep_uuid, (const u8 *)pkg->ep_name,
+ sizeof(pkg->ep_name), pkg->connection_id,
0, 0, 0, security_level, authorized, boot);
tb_switch_put(parent_sw);
@@ -1184,7 +1272,7 @@ static int icm_ar_get_mode(struct tb *tb)
static int
icm_ar_driver_ready(struct tb *tb, enum tb_security_level *security_level,
- size_t *nboot_acl)
+ size_t *nboot_acl, bool *rpm)
{
struct icm_ar_pkg_driver_ready_response reply;
struct icm_pkg_driver_ready request = {
@@ -1203,6 +1291,9 @@ icm_ar_driver_ready(struct tb *tb, enum tb_security_level *security_level,
if (nboot_acl && (reply.info & ICM_AR_INFO_BOOT_ACL_SUPPORTED))
*nboot_acl = (reply.info & ICM_AR_INFO_BOOT_ACL_MASK) >>
ICM_AR_INFO_BOOT_ACL_SHIFT;
+ if (rpm)
+ *rpm = !!(reply.hdr.flags & ICM_AR_FLAGS_RTD3);
+
return 0;
}
@@ -1356,13 +1447,13 @@ static void icm_handle_event(struct tb *tb, enum tb_cfg_pkg_type type,
static int
__icm_driver_ready(struct tb *tb, enum tb_security_level *security_level,
- size_t *nboot_acl)
+ size_t *nboot_acl, bool *rpm)
{
struct icm *icm = tb_priv(tb);
unsigned int retries = 50;
int ret;
- ret = icm->driver_ready(tb, security_level, nboot_acl);
+ ret = icm->driver_ready(tb, security_level, nboot_acl, rpm);
if (ret) {
tb_err(tb, "failed to send driver ready to ICM\n");
return ret;
@@ -1632,7 +1723,8 @@ static int icm_driver_ready(struct tb *tb)
return 0;
}
- ret = __icm_driver_ready(tb, &tb->security_level, &tb->nboot_acl);
+ ret = __icm_driver_ready(tb, &tb->security_level, &tb->nboot_acl,
+ &icm->rpm);
if (ret)
return ret;
@@ -1648,13 +1740,12 @@ static int icm_driver_ready(struct tb *tb)
static int icm_suspend(struct tb *tb)
{
- int ret;
+ struct icm *icm = tb_priv(tb);
- ret = nhi_mailbox_cmd(tb->nhi, NHI_MAILBOX_SAVE_DEVS, 0);
- if (ret)
- tb_info(tb, "Ignoring mailbox command error (%d) in %s\n",
- ret, __func__);
+ if (icm->save_devices)
+ icm->save_devices(tb);
+ nhi_mailbox_cmd(tb->nhi, NHI_MAILBOX_DRV_UNLOADS, 0);
return 0;
}
@@ -1739,7 +1830,7 @@ static void icm_complete(struct tb *tb)
* Now all existing children should be resumed, start events
* from ICM to get updated status.
*/
- __icm_driver_ready(tb, NULL, NULL);
+ __icm_driver_ready(tb, NULL, NULL, NULL);
/*
* We do not get notifications of devices that have been
@@ -1749,6 +1840,22 @@ static void icm_complete(struct tb *tb)
queue_delayed_work(tb->wq, &icm->rescan_work, msecs_to_jiffies(500));
}
+static int icm_runtime_suspend(struct tb *tb)
+{
+ nhi_mailbox_cmd(tb->nhi, NHI_MAILBOX_DRV_UNLOADS, 0);
+ return 0;
+}
+
+static int icm_runtime_resume(struct tb *tb)
+{
+ /*
+ * We can reuse the same resume functionality than with system
+ * suspend.
+ */
+ icm_complete(tb);
+ return 0;
+}
+
static int icm_start(struct tb *tb)
{
struct icm *icm = tb_priv(tb);
@@ -1767,6 +1874,7 @@ static int icm_start(struct tb *tb)
* prevent root switch NVM upgrade on Macs for now.
*/
tb->root_switch->no_nvm_upgrade = x86_apple_machine;
+ tb->root_switch->rpm = icm->rpm;
ret = tb_switch_add(tb->root_switch);
if (ret) {
@@ -1815,6 +1923,8 @@ static const struct tb_cm_ops icm_ar_ops = {
.stop = icm_stop,
.suspend = icm_suspend,
.complete = icm_complete,
+ .runtime_suspend = icm_runtime_suspend,
+ .runtime_resume = icm_runtime_resume,
.handle_event = icm_handle_event,
.get_boot_acl = icm_ar_get_boot_acl,
.set_boot_acl = icm_ar_set_boot_acl,
@@ -1833,6 +1943,8 @@ static const struct tb_cm_ops icm_tr_ops = {
.stop = icm_stop,
.suspend = icm_suspend,
.complete = icm_complete,
+ .runtime_suspend = icm_runtime_suspend,
+ .runtime_resume = icm_runtime_resume,
.handle_event = icm_handle_event,
.get_boot_acl = icm_ar_get_boot_acl,
.set_boot_acl = icm_ar_set_boot_acl,
@@ -1862,6 +1974,7 @@ struct tb *icm_probe(struct tb_nhi *nhi)
case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI:
icm->is_supported = icm_fr_is_supported;
icm->get_route = icm_fr_get_route;
+ icm->save_devices = icm_fr_save_devices;
icm->driver_ready = icm_fr_driver_ready;
icm->device_connected = icm_fr_device_connected;
icm->device_disconnected = icm_fr_device_disconnected;
@@ -1879,6 +1992,7 @@ struct tb *icm_probe(struct tb_nhi *nhi)
icm->is_supported = icm_ar_is_supported;
icm->get_mode = icm_ar_get_mode;
icm->get_route = icm_ar_get_route;
+ icm->save_devices = icm_fr_save_devices;
icm->driver_ready = icm_ar_driver_ready;
icm->device_connected = icm_fr_device_connected;
icm->device_disconnected = icm_fr_device_disconnected;