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

thunderbolt: Convert switch to a device

Thunderbolt domain consists of switches that are connected to each
other, forming a bus. This will convert each switch into a real Linux
device structure and adds them to the domain. The advantage here is
that we get all the goodies from the driver core, like reference
counting and sysfs hierarchy for free.

Also expose device identification information to the userspace via new
sysfs attributes.

In order to support internal connection manager (ICM) we separate switch
configuration into its own function (tb_switch_configure()) which is
only called by the existing native connection manager implementation
used on Macs.
Signed-off-by: default avatarMika Westerberg <mika.westerberg@linux.intel.com>
Reviewed-by: default avatarYehezkel Bernat <yehezkel.bernat@intel.com>
Reviewed-by: default avatarMichael Jamet <michael.jamet@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 9d3cce0b
What: /sys/bus/thunderbolt/devices/.../device
Date: Sep 2017
KernelVersion: 4.13
Contact: thunderbolt-software@lists.01.org
Description: This attribute contains id of this device extracted from
the device DROM.
What: /sys/bus/thunderbolt/devices/.../vendor
Date: Sep 2017
KernelVersion: 4.13
Contact: thunderbolt-software@lists.01.org
Description: This attribute contains vendor id of this device extracted
from the device DROM.
What: /sys/bus/thunderbolt/devices/.../unique_id
Date: Sep 2017
KernelVersion: 4.13
Contact: thunderbolt-software@lists.01.org
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.
...@@ -479,6 +479,8 @@ int tb_drom_read(struct tb_switch *sw) ...@@ -479,6 +479,8 @@ int tb_drom_read(struct tb_switch *sw)
goto err; goto err;
} }
sw->uid = header->uid; 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); crc = tb_crc32(sw->drom + TB_DROM_DATA_START, header->data_len);
if (crc != header->data_crc32) { if (crc != header->data_crc32) {
......
...@@ -281,6 +281,9 @@ static int tb_plug_events_active(struct tb_switch *sw, bool active) ...@@ -281,6 +281,9 @@ static int tb_plug_events_active(struct tb_switch *sw, bool active)
u32 data; u32 data;
int res; int res;
if (!sw->config.enabled)
return 0;
sw->config.plug_events_delay = 0xff; sw->config.plug_events_delay = 0xff;
res = tb_sw_write(sw, ((u32 *) &sw->config) + 4, TB_CFG_SWITCH, 4, 1); res = tb_sw_write(sw, ((u32 *) &sw->config) + 4, TB_CFG_SWITCH, 4, 1);
if (res) if (res)
...@@ -307,36 +310,79 @@ static int tb_plug_events_active(struct tb_switch *sw, bool active) ...@@ -307,36 +310,79 @@ static int tb_plug_events_active(struct tb_switch *sw, bool active)
sw->cap_plug_events + 1, 1); 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);
/** return sprintf(buf, "%#x\n", sw->device);
* tb_switch_free() - free a tb_switch and all downstream switches }
*/ static DEVICE_ATTR_RO(device);
void tb_switch_free(struct tb_switch *sw)
static ssize_t vendor_show(struct device *dev, struct device_attribute *attr,
char *buf)
{ {
int i; struct tb_switch *sw = tb_to_switch(dev);
/* 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;
}
if (!sw->is_unplugged) return sprintf(buf, "%#x\n", sw->vendor);
tb_plug_events_active(sw, false); }
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->ports);
kfree(sw->drom); kfree(sw->drom);
kfree(sw); 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 i;
int cap; int cap;
...@@ -351,11 +397,9 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route) ...@@ -351,11 +397,9 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route)
sw->tb = tb; sw->tb = tb;
if (tb_cfg_read(tb->ctl, &sw->config, route, 0, TB_CFG_SWITCH, 0, 5)) if (tb_cfg_read(tb->ctl, &sw->config, route, 0, TB_CFG_SWITCH, 0, 5))
goto err; goto err_free_sw_ports;
tb_info(tb,
"initializing Switch at %#llx (depth: %d, up port: %d)\n", tb_info(tb, "current switch config:\n");
route, tb_route_length(route), upstream_port);
tb_info(tb, "old switch config:\n");
tb_dump_switch(tb, &sw->config); tb_dump_switch(tb, &sw->config);
/* configure switch */ /* configure switch */
...@@ -363,30 +407,13 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route) ...@@ -363,30 +407,13 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route)
sw->config.depth = tb_route_length(route); sw->config.depth = tb_route_length(route);
sw->config.route_lo = route; sw->config.route_lo = route;
sw->config.route_hi = route >> 32; sw->config.route_hi = route >> 32;
sw->config.enabled = 1; sw->config.enabled = 0;
/* 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;
/* initialize ports */ /* initialize ports */
sw->ports = kcalloc(sw->config.max_port_number + 1, sizeof(*sw->ports), sw->ports = kcalloc(sw->config.max_port_number + 1, sizeof(*sw->ports),
GFP_KERNEL); GFP_KERNEL);
if (!sw->ports) if (!sw->ports)
goto err; goto err_free_sw_ports;
for (i = 0; i <= sw->config.max_port_number; i++) { for (i = 0; i <= sw->config.max_port_number; i++) {
/* minimum setup for tb_find_cap and tb_drom_read to work */ /* 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) ...@@ -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); cap = tb_switch_find_vse_cap(sw, TB_VSE_CAP_PLUG_EVENTS);
if (cap < 0) { if (cap < 0) {
tb_sw_warn(sw, "cannot find TB_VSE_CAP_PLUG_EVENTS aborting\n"); 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; 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 */ /* read drom */
if (tb_drom_read(sw)) if (tb_drom_read(sw))
tb_sw_warn(sw, "tb_eeprom_read_rom failed, continuing\n"); tb_sw_warn(sw, "tb_eeprom_read_rom failed, continuing\n");
tb_sw_info(sw, "uid: %#llx\n", sw->uid); tb_sw_info(sw, "uid: %#llx\n", sw->uid);
tb_switch_set_uuid(sw);
for (i = 0; i <= sw->config.max_port_number; i++) { for (i = 0; i <= sw->config.max_port_number; i++) {
if (sw->ports[i].disabled) { if (sw->ports[i].disabled) {
tb_port_info(&sw->ports[i], "disabled by eeprom\n"); tb_port_info(&sw->ports[i], "disabled by eeprom\n");
continue; continue;
} }
if (tb_init_port(&sw->ports[i])) ret = tb_init_port(&sw->ports[i]);
goto err; 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; /* port 0 is the switch itself and never has a remote */
err: for (i = 1; i <= sw->config.max_port_number; i++) {
kfree(sw->ports); if (tb_is_upstream_port(&sw->ports[i]))
kfree(sw->drom); continue;
kfree(sw); if (sw->ports[i].remote)
return NULL; 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);
} }
/** /**
......
...@@ -61,9 +61,21 @@ static void tb_scan_port(struct tb_port *port) ...@@ -61,9 +61,21 @@ static void tb_scan_port(struct tb_port *port)
tb_port_WARN(port, "port already has a remote!\n"); tb_port_WARN(port, "port already has a remote!\n");
return; 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) if (!sw)
return; 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); port->remote = tb_upstream_port(sw);
tb_upstream_port(sw)->remote = port; tb_upstream_port(sw)->remote = port;
tb_scan_switch(sw); tb_scan_switch(sw);
...@@ -100,7 +112,7 @@ static void tb_free_unplugged_children(struct tb_switch *sw) ...@@ -100,7 +112,7 @@ static void tb_free_unplugged_children(struct tb_switch *sw)
if (!port->remote) if (!port->remote)
continue; continue;
if (port->remote->sw->is_unplugged) { if (port->remote->sw->is_unplugged) {
tb_switch_free(port->remote->sw); tb_switch_remove(port->remote->sw);
port->remote = NULL; port->remote = NULL;
} else { } else {
tb_free_unplugged_children(port->remote->sw); tb_free_unplugged_children(port->remote->sw);
...@@ -266,7 +278,7 @@ static void tb_handle_hotplug(struct work_struct *work) ...@@ -266,7 +278,7 @@ static void tb_handle_hotplug(struct work_struct *work)
tb_port_info(port, "unplugged\n"); tb_port_info(port, "unplugged\n");
tb_sw_set_unplugged(port->remote->sw); tb_sw_set_unplugged(port->remote->sw);
tb_free_invalid_tunnels(tb); tb_free_invalid_tunnels(tb);
tb_switch_free(port->remote->sw); tb_switch_remove(port->remote->sw);
port->remote = NULL; port->remote = NULL;
} else { } else {
tb_port_info(port, tb_port_info(port,
...@@ -325,22 +337,32 @@ static void tb_stop(struct tb *tb) ...@@ -325,22 +337,32 @@ static void tb_stop(struct tb *tb)
tb_pci_deactivate(tunnel); tb_pci_deactivate(tunnel);
tb_pci_free(tunnel); tb_pci_free(tunnel);
} }
tb_switch_remove(tb->root_switch);
if (tb->root_switch)
tb_switch_free(tb->root_switch);
tb->root_switch = NULL;
tcm->hotplug_active = false; /* signal tb_handle_hotplug to quit */ tcm->hotplug_active = false; /* signal tb_handle_hotplug to quit */
} }
static int tb_start(struct tb *tb) static int tb_start(struct tb *tb)
{ {
struct tb_cm *tcm = tb_priv(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) if (!tb->root_switch)
return -ENOMEM; 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. */ /* Full scan to discover devices added before the driver was loaded. */
tb_scan_switch(tb->root_switch); tb_scan_switch(tb->root_switch);
tb_activate_pcie_devices(tb); tb_activate_pcie_devices(tb);
......
...@@ -8,20 +8,36 @@ ...@@ -8,20 +8,36 @@
#define TB_H_ #define TB_H_
#include <linux/pci.h> #include <linux/pci.h>
#include <linux/uuid.h>
#include "tb_regs.h" #include "tb_regs.h"
#include "ctl.h" #include "ctl.h"
/** /**
* struct tb_switch - a thunderbolt switch * 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 tb_switch {
struct device dev;
struct tb_regs_switch_header config; struct tb_regs_switch_header config;
struct tb_port *ports; struct tb_port *ports;
struct tb *tb; struct tb *tb;
u64 uid; u64 uid;
int cap_plug_events; /* offset, zero if not found */ uuid_be *uuid;
bool is_unplugged; /* unplugged, will go away */ u16 vendor;
u16 device;
int cap_plug_events;
bool is_unplugged;
u8 *drom; u8 *drom;
}; };
...@@ -242,6 +258,7 @@ struct tb *tb_probe(struct tb_nhi *nhi); ...@@ -242,6 +258,7 @@ struct tb *tb_probe(struct tb_nhi *nhi);
extern struct bus_type tb_bus_type; extern struct bus_type tb_bus_type;
extern struct device_type tb_domain_type; extern struct device_type tb_domain_type;
extern struct device_type tb_switch_type;
int tb_domain_init(void); int tb_domain_init(void);
void tb_domain_exit(void); void tb_domain_exit(void);
...@@ -257,14 +274,34 @@ static inline void tb_domain_put(struct tb *tb) ...@@ -257,14 +274,34 @@ static inline void tb_domain_put(struct tb *tb)
put_device(&tb->dev); put_device(&tb->dev);
} }
struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route); struct tb_switch *tb_switch_alloc(struct tb *tb, struct device *parent,
void tb_switch_free(struct tb_switch *sw); 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); void tb_switch_suspend(struct tb_switch *sw);
int tb_switch_resume(struct tb_switch *sw); int tb_switch_resume(struct tb_switch *sw);
int tb_switch_reset(struct tb *tb, u64 route); int tb_switch_reset(struct tb *tb, u64 route);
void tb_sw_set_unplugged(struct tb_switch *sw); void tb_sw_set_unplugged(struct tb_switch *sw);
struct tb_switch *get_switch_at_route(struct tb_switch *sw, u64 route); 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_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_add_nfc_credits(struct tb_port *port, int credits);
int tb_port_clear_counter(struct tb_port *port, int counter); int tb_port_clear_counter(struct tb_port *port, int counter);
......
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