diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/thunderbolt/eeprom.c | 2 | ||||
-rw-r--r-- | drivers/thunderbolt/switch.c | 261 | ||||
-rw-r--r-- | drivers/thunderbolt/tb.c | 40 | ||||
-rw-r--r-- | drivers/thunderbolt/tb.h | 45 |
4 files changed, 281 insertions, 67 deletions
diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c index eb2179c98b09..7e485e3ef27e 100644 --- a/drivers/thunderbolt/eeprom.c +++ b/drivers/thunderbolt/eeprom.c @@ -479,6 +479,8 @@ parse: goto err; } sw->uid = header->uid; + sw->vendor = header->vendor_id; + sw->device = header->model_id; crc = tb_crc32(sw->drom + TB_DROM_DATA_START, header->data_len); if (crc != header->data_crc32) { diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index b379b4183bac..0475bfc6c208 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -281,6 +281,9 @@ static int tb_plug_events_active(struct tb_switch *sw, bool active) u32 data; int res; + if (!sw->config.enabled) + return 0; + sw->config.plug_events_delay = 0xff; res = tb_sw_write(sw, ((u32 *) &sw->config) + 4, TB_CFG_SWITCH, 4, 1); if (res) @@ -307,36 +310,79 @@ static int tb_plug_events_active(struct tb_switch *sw, bool active) sw->cap_plug_events + 1, 1); } +static ssize_t device_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct tb_switch *sw = tb_to_switch(dev); -/** - * tb_switch_free() - free a tb_switch and all downstream switches - */ -void tb_switch_free(struct tb_switch *sw) + return sprintf(buf, "%#x\n", sw->device); +} +static DEVICE_ATTR_RO(device); + +static ssize_t vendor_show(struct device *dev, struct device_attribute *attr, + char *buf) { - int i; - /* port 0 is the switch itself and never has a remote */ - for (i = 1; i <= sw->config.max_port_number; i++) { - if (tb_is_upstream_port(&sw->ports[i])) - continue; - if (sw->ports[i].remote) - tb_switch_free(sw->ports[i].remote->sw); - sw->ports[i].remote = NULL; - } + struct tb_switch *sw = tb_to_switch(dev); - if (!sw->is_unplugged) - tb_plug_events_active(sw, false); + return sprintf(buf, "%#x\n", sw->vendor); +} +static DEVICE_ATTR_RO(vendor); + +static ssize_t unique_id_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct tb_switch *sw = tb_to_switch(dev); + + return sprintf(buf, "%pUb\n", sw->uuid); +} +static DEVICE_ATTR_RO(unique_id); + +static struct attribute *switch_attrs[] = { + &dev_attr_device.attr, + &dev_attr_vendor.attr, + &dev_attr_unique_id.attr, + NULL, +}; + +static struct attribute_group switch_group = { + .attrs = switch_attrs, +}; +static const struct attribute_group *switch_groups[] = { + &switch_group, + NULL, +}; + +static void tb_switch_release(struct device *dev) +{ + struct tb_switch *sw = tb_to_switch(dev); + + kfree(sw->uuid); kfree(sw->ports); kfree(sw->drom); kfree(sw); } +struct device_type tb_switch_type = { + .name = "thunderbolt_device", + .release = tb_switch_release, +}; + /** - * tb_switch_alloc() - allocate and initialize a switch + * tb_switch_alloc() - allocate a switch + * @tb: Pointer to the owning domain + * @parent: Parent device for this switch + * @route: Route string for this switch * - * Return: Returns a NULL on failure. + * Allocates and initializes a switch. Will not upload configuration to + * the switch. For that you need to call tb_switch_configure() + * separately. The returned switch should be released by calling + * tb_switch_put(). + * + * Return: Pointer to the allocated switch or %NULL in case of failure */ -struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route) +struct tb_switch *tb_switch_alloc(struct tb *tb, struct device *parent, + u64 route) { int i; int cap; @@ -351,11 +397,9 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route) sw->tb = tb; if (tb_cfg_read(tb->ctl, &sw->config, route, 0, TB_CFG_SWITCH, 0, 5)) - goto err; - tb_info(tb, - "initializing Switch at %#llx (depth: %d, up port: %d)\n", - route, tb_route_length(route), upstream_port); - tb_info(tb, "old switch config:\n"); + goto err_free_sw_ports; + + tb_info(tb, "current switch config:\n"); tb_dump_switch(tb, &sw->config); /* configure switch */ @@ -363,30 +407,13 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route) sw->config.depth = tb_route_length(route); sw->config.route_lo = route; sw->config.route_hi = route >> 32; - sw->config.enabled = 1; - /* from here on we may use the tb_sw_* functions & macros */ - - if (sw->config.vendor_id != 0x8086) - tb_sw_warn(sw, "unknown switch vendor id %#x\n", - sw->config.vendor_id); - - if (sw->config.device_id != PCI_DEVICE_ID_INTEL_LIGHT_RIDGE && - sw->config.device_id != PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C && - sw->config.device_id != PCI_DEVICE_ID_INTEL_PORT_RIDGE && - sw->config.device_id != PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_BRIDGE && - sw->config.device_id != PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_BRIDGE) - tb_sw_warn(sw, "unsupported switch device id %#x\n", - sw->config.device_id); - - /* upload configuration */ - if (tb_sw_write(sw, 1 + (u32 *) &sw->config, TB_CFG_SWITCH, 1, 3)) - goto err; + sw->config.enabled = 0; /* initialize ports */ sw->ports = kcalloc(sw->config.max_port_number + 1, sizeof(*sw->ports), GFP_KERNEL); if (!sw->ports) - goto err; + goto err_free_sw_ports; for (i = 0; i <= sw->config.max_port_number; i++) { /* minimum setup for tb_find_cap and tb_drom_read to work */ @@ -397,35 +424,161 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route) cap = tb_switch_find_vse_cap(sw, TB_VSE_CAP_PLUG_EVENTS); if (cap < 0) { tb_sw_warn(sw, "cannot find TB_VSE_CAP_PLUG_EVENTS aborting\n"); - goto err; + goto err_free_sw_ports; } sw->cap_plug_events = cap; + device_initialize(&sw->dev); + sw->dev.parent = parent; + sw->dev.bus = &tb_bus_type; + sw->dev.type = &tb_switch_type; + sw->dev.groups = switch_groups; + dev_set_name(&sw->dev, "%u-%llx", tb->index, tb_route(sw)); + + return sw; + +err_free_sw_ports: + kfree(sw->ports); + kfree(sw); + + return NULL; +} + +/** + * tb_switch_configure() - Uploads configuration to the switch + * @sw: Switch to configure + * + * Call this function before the switch is added to the system. It will + * upload configuration to the switch and makes it available for the + * connection manager to use. + * + * Return: %0 in case of success and negative errno in case of failure + */ +int tb_switch_configure(struct tb_switch *sw) +{ + struct tb *tb = sw->tb; + u64 route; + int ret; + + route = tb_route(sw); + tb_info(tb, + "initializing Switch at %#llx (depth: %d, up port: %d)\n", + route, tb_route_length(route), sw->config.upstream_port_number); + + if (sw->config.vendor_id != PCI_VENDOR_ID_INTEL) + tb_sw_warn(sw, "unknown switch vendor id %#x\n", + sw->config.vendor_id); + + if (sw->config.device_id != PCI_DEVICE_ID_INTEL_LIGHT_RIDGE && + sw->config.device_id != PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C && + sw->config.device_id != PCI_DEVICE_ID_INTEL_PORT_RIDGE && + sw->config.device_id != PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_BRIDGE && + sw->config.device_id != PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_BRIDGE) + tb_sw_warn(sw, "unsupported switch device id %#x\n", + sw->config.device_id); + + sw->config.enabled = 1; + + /* upload configuration */ + ret = tb_sw_write(sw, 1 + (u32 *)&sw->config, TB_CFG_SWITCH, 1, 3); + if (ret) + return ret; + + return tb_plug_events_active(sw, true); +} + +static void tb_switch_set_uuid(struct tb_switch *sw) +{ + u32 uuid[4]; + int cap; + + if (sw->uuid) + return; + + /* + * The newer controllers include fused UUID as part of link + * controller specific registers + */ + cap = tb_switch_find_vse_cap(sw, TB_VSE_CAP_LINK_CONTROLLER); + if (cap > 0) { + tb_sw_read(sw, uuid, TB_CFG_SWITCH, cap + 3, 4); + } else { + /* + * ICM generates UUID based on UID and fills the upper + * two words with ones. This is not strictly following + * UUID format but we want to be compatible with it so + * we do the same here. + */ + uuid[0] = sw->uid & 0xffffffff; + uuid[1] = (sw->uid >> 32) & 0xffffffff; + uuid[2] = 0xffffffff; + uuid[3] = 0xffffffff; + } + + sw->uuid = kmemdup(uuid, sizeof(uuid), GFP_KERNEL); +} + +/** + * tb_switch_add() - Add a switch to the domain + * @sw: Switch to add + * + * This is the last step in adding switch to the domain. It will read + * identification information from DROM and initializes ports so that + * they can be used to connect other switches. The switch will be + * exposed to the userspace when this function successfully returns. To + * remove and release the switch, call tb_switch_remove(). + * + * Return: %0 in case of success and negative errno in case of failure + */ +int tb_switch_add(struct tb_switch *sw) +{ + int i, ret; + /* read drom */ if (tb_drom_read(sw)) tb_sw_warn(sw, "tb_eeprom_read_rom failed, continuing\n"); tb_sw_info(sw, "uid: %#llx\n", sw->uid); + tb_switch_set_uuid(sw); + for (i = 0; i <= sw->config.max_port_number; i++) { if (sw->ports[i].disabled) { tb_port_info(&sw->ports[i], "disabled by eeprom\n"); continue; } - if (tb_init_port(&sw->ports[i])) - goto err; + ret = tb_init_port(&sw->ports[i]); + if (ret) + return ret; } - /* TODO: I2C, IECS, link controller */ + return device_add(&sw->dev); +} - if (tb_plug_events_active(sw, true)) - goto err; +/** + * tb_switch_remove() - Remove and release a switch + * @sw: Switch to remove + * + * This will remove the switch from the domain and release it after last + * reference count drops to zero. If there are switches connected below + * this switch, they will be removed as well. + */ +void tb_switch_remove(struct tb_switch *sw) +{ + int i; - return sw; -err: - kfree(sw->ports); - kfree(sw->drom); - kfree(sw); - return NULL; + /* port 0 is the switch itself and never has a remote */ + for (i = 1; i <= sw->config.max_port_number; i++) { + if (tb_is_upstream_port(&sw->ports[i])) + continue; + if (sw->ports[i].remote) + tb_switch_remove(sw->ports[i].remote->sw); + sw->ports[i].remote = NULL; + } + + if (!sw->is_unplugged) + tb_plug_events_active(sw, false); + + device_unregister(&sw->dev); } /** diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 9f00a0f28d53..94ecac012428 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -61,9 +61,21 @@ static void tb_scan_port(struct tb_port *port) tb_port_WARN(port, "port already has a remote!\n"); return; } - sw = tb_switch_alloc(port->sw->tb, tb_downstream_route(port)); + sw = tb_switch_alloc(port->sw->tb, &port->sw->dev, + tb_downstream_route(port)); if (!sw) return; + + if (tb_switch_configure(sw)) { + tb_switch_put(sw); + return; + } + + if (tb_switch_add(sw)) { + tb_switch_put(sw); + return; + } + port->remote = tb_upstream_port(sw); tb_upstream_port(sw)->remote = port; tb_scan_switch(sw); @@ -100,7 +112,7 @@ static void tb_free_unplugged_children(struct tb_switch *sw) if (!port->remote) continue; if (port->remote->sw->is_unplugged) { - tb_switch_free(port->remote->sw); + tb_switch_remove(port->remote->sw); port->remote = NULL; } else { tb_free_unplugged_children(port->remote->sw); @@ -266,7 +278,7 @@ static void tb_handle_hotplug(struct work_struct *work) tb_port_info(port, "unplugged\n"); tb_sw_set_unplugged(port->remote->sw); tb_free_invalid_tunnels(tb); - tb_switch_free(port->remote->sw); + tb_switch_remove(port->remote->sw); port->remote = NULL; } else { tb_port_info(port, @@ -325,22 +337,32 @@ static void tb_stop(struct tb *tb) tb_pci_deactivate(tunnel); tb_pci_free(tunnel); } - - if (tb->root_switch) - tb_switch_free(tb->root_switch); - tb->root_switch = NULL; - + tb_switch_remove(tb->root_switch); tcm->hotplug_active = false; /* signal tb_handle_hotplug to quit */ } static int tb_start(struct tb *tb) { struct tb_cm *tcm = tb_priv(tb); + int ret; - tb->root_switch = tb_switch_alloc(tb, 0); + tb->root_switch = tb_switch_alloc(tb, &tb->dev, 0); if (!tb->root_switch) return -ENOMEM; + ret = tb_switch_configure(tb->root_switch); + if (ret) { + tb_switch_put(tb->root_switch); + return ret; + } + + /* Announce the switch to the world */ + ret = tb_switch_add(tb->root_switch); + if (ret) { + tb_switch_put(tb->root_switch); + return ret; + } + /* Full scan to discover devices added before the driver was loaded. */ tb_scan_switch(tb->root_switch); tb_activate_pcie_devices(tb); diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 5fab4c44f124..f7dfe733d71a 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -8,20 +8,36 @@ #define TB_H_ #include <linux/pci.h> +#include <linux/uuid.h> #include "tb_regs.h" #include "ctl.h" /** * struct tb_switch - a thunderbolt switch + * @dev: Device for the switch + * @config: Switch configuration + * @ports: Ports in this switch + * @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) + * @vendor: Vendor ID of the switch + * @device: Device ID of the switch + * @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) */ struct tb_switch { + struct device dev; struct tb_regs_switch_header config; struct tb_port *ports; struct tb *tb; u64 uid; - int cap_plug_events; /* offset, zero if not found */ - bool is_unplugged; /* unplugged, will go away */ + uuid_be *uuid; + u16 vendor; + u16 device; + int cap_plug_events; + bool is_unplugged; u8 *drom; }; @@ -242,6 +258,7 @@ struct tb *tb_probe(struct tb_nhi *nhi); extern struct bus_type tb_bus_type; extern struct device_type tb_domain_type; +extern struct device_type tb_switch_type; int tb_domain_init(void); void tb_domain_exit(void); @@ -257,14 +274,34 @@ static inline void tb_domain_put(struct tb *tb) put_device(&tb->dev); } -struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route); -void tb_switch_free(struct tb_switch *sw); +struct tb_switch *tb_switch_alloc(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); void tb_switch_suspend(struct tb_switch *sw); int tb_switch_resume(struct tb_switch *sw); int tb_switch_reset(struct tb *tb, u64 route); void tb_sw_set_unplugged(struct tb_switch *sw); struct tb_switch *get_switch_at_route(struct tb_switch *sw, u64 route); +static inline void tb_switch_put(struct tb_switch *sw) +{ + put_device(&sw->dev); +} + +static inline bool tb_is_switch(const struct device *dev) +{ + return dev->type == &tb_switch_type; +} + +static inline struct tb_switch *tb_to_switch(struct device *dev) +{ + if (tb_is_switch(dev)) + return container_of(dev, struct tb_switch, dev); + return NULL; +} + int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged); int tb_port_add_nfc_credits(struct tb_port *port, int credits); int tb_port_clear_counter(struct tb_port *port, int counter); |