diff options
Diffstat (limited to 'drivers/thunderbolt/icm.c')
-rw-r--r-- | drivers/thunderbolt/icm.c | 174 |
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; |