summaryrefslogtreecommitdiffstats
path: root/drivers/thunderbolt
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/thunderbolt')
-rw-r--r--drivers/thunderbolt/ctl.c21
-rw-r--r--drivers/thunderbolt/ctl.h8
-rw-r--r--drivers/thunderbolt/debugfs.c37
-rw-r--r--drivers/thunderbolt/dma_test.c35
-rw-r--r--drivers/thunderbolt/domain.c89
-rw-r--r--drivers/thunderbolt/eeprom.c105
-rw-r--r--drivers/thunderbolt/icm.c34
-rw-r--r--drivers/thunderbolt/property.c71
-rw-r--r--drivers/thunderbolt/switch.c75
-rw-r--r--drivers/thunderbolt/tb.c52
-rw-r--r--drivers/thunderbolt/tb.h45
-rw-r--r--drivers/thunderbolt/test.c492
-rw-r--r--drivers/thunderbolt/tunnel.c102
-rw-r--r--drivers/thunderbolt/tunnel.h8
-rw-r--r--drivers/thunderbolt/xdomain.c416
15 files changed, 1208 insertions, 382 deletions
diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c
index f1aeaff9f368..0fb5e04191e2 100644
--- a/drivers/thunderbolt/ctl.c
+++ b/drivers/thunderbolt/ctl.c
@@ -17,7 +17,7 @@
#define TB_CTL_RX_PKG_COUNT 10
-#define TB_CTL_RETRIES 4
+#define TB_CTL_RETRIES 1
/**
* struct tb_ctl - Thunderbolt control channel
@@ -29,6 +29,7 @@
* @request_queue_lock: Lock protecting @request_queue
* @request_queue: List of outstanding requests
* @running: Is the control channel running at the moment
+ * @timeout_msec: Default timeout for non-raw control messages
* @callback: Callback called when hotplug message is received
* @callback_data: Data passed to @callback
*/
@@ -43,6 +44,7 @@ struct tb_ctl {
struct list_head request_queue;
bool running;
+ int timeout_msec;
event_cb callback;
void *callback_data;
};
@@ -613,6 +615,7 @@ struct tb_cfg_result tb_cfg_request_sync(struct tb_ctl *ctl,
/**
* tb_ctl_alloc() - allocate a control channel
* @nhi: Pointer to NHI
+ * @timeout_msec: Default timeout used with non-raw control messages
* @cb: Callback called for plug events
* @cb_data: Data passed to @cb
*
@@ -620,13 +623,15 @@ struct tb_cfg_result tb_cfg_request_sync(struct tb_ctl *ctl,
*
* Return: Returns a pointer on success or NULL on failure.
*/
-struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, event_cb cb, void *cb_data)
+struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, int timeout_msec, event_cb cb,
+ void *cb_data)
{
int i;
struct tb_ctl *ctl = kzalloc(sizeof(*ctl), GFP_KERNEL);
if (!ctl)
return NULL;
ctl->nhi = nhi;
+ ctl->timeout_msec = timeout_msec;
ctl->callback = cb;
ctl->callback_data = cb_data;
@@ -802,14 +807,12 @@ static bool tb_cfg_copy(struct tb_cfg_request *req, const struct ctl_pkg *pkg)
* tb_cfg_reset() - send a reset packet and wait for a response
* @ctl: Control channel pointer
* @route: Router string for the router to send reset
- * @timeout_msec: Timeout in ms how long to wait for the response
*
* If the switch at route is incorrectly configured then we will not receive a
* reply (even though the switch will reset). The caller should check for
* -ETIMEDOUT and attempt to reconfigure the switch.
*/
-struct tb_cfg_result tb_cfg_reset(struct tb_ctl *ctl, u64 route,
- int timeout_msec)
+struct tb_cfg_result tb_cfg_reset(struct tb_ctl *ctl, u64 route)
{
struct cfg_reset_pkg request = { .header = tb_cfg_make_header(route) };
struct tb_cfg_result res = { 0 };
@@ -831,7 +834,7 @@ struct tb_cfg_result tb_cfg_reset(struct tb_ctl *ctl, u64 route,
req->response_size = sizeof(reply);
req->response_type = TB_CFG_PKG_RESET;
- res = tb_cfg_request_sync(ctl, req, timeout_msec);
+ res = tb_cfg_request_sync(ctl, req, ctl->timeout_msec);
tb_cfg_request_put(req);
@@ -1007,7 +1010,7 @@ int tb_cfg_read(struct tb_ctl *ctl, void *buffer, u64 route, u32 port,
enum tb_cfg_space space, u32 offset, u32 length)
{
struct tb_cfg_result res = tb_cfg_read_raw(ctl, buffer, route, port,
- space, offset, length, TB_CFG_DEFAULT_TIMEOUT);
+ space, offset, length, ctl->timeout_msec);
switch (res.err) {
case 0:
/* Success */
@@ -1033,7 +1036,7 @@ int tb_cfg_write(struct tb_ctl *ctl, const void *buffer, u64 route, u32 port,
enum tb_cfg_space space, u32 offset, u32 length)
{
struct tb_cfg_result res = tb_cfg_write_raw(ctl, buffer, route, port,
- space, offset, length, TB_CFG_DEFAULT_TIMEOUT);
+ space, offset, length, ctl->timeout_msec);
switch (res.err) {
case 0:
/* Success */
@@ -1071,7 +1074,7 @@ int tb_cfg_get_upstream_port(struct tb_ctl *ctl, u64 route)
u32 dummy;
struct tb_cfg_result res = tb_cfg_read_raw(ctl, &dummy, route, 0,
TB_CFG_SWITCH, 0, 1,
- TB_CFG_DEFAULT_TIMEOUT);
+ ctl->timeout_msec);
if (res.err == 1)
return -EIO;
if (res.err)
diff --git a/drivers/thunderbolt/ctl.h b/drivers/thunderbolt/ctl.h
index 97cb03b38953..e8c64898dfce 100644
--- a/drivers/thunderbolt/ctl.h
+++ b/drivers/thunderbolt/ctl.h
@@ -21,15 +21,14 @@ struct tb_ctl;
typedef bool (*event_cb)(void *data, enum tb_cfg_pkg_type type,
const void *buf, size_t size);
-struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, event_cb cb, void *cb_data);
+struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, int timeout_msec, event_cb cb,
+ void *cb_data);
void tb_ctl_start(struct tb_ctl *ctl);
void tb_ctl_stop(struct tb_ctl *ctl);
void tb_ctl_free(struct tb_ctl *ctl);
/* configuration commands */
-#define TB_CFG_DEFAULT_TIMEOUT 5000 /* msec */
-
struct tb_cfg_result {
u64 response_route;
u32 response_port; /*
@@ -124,8 +123,7 @@ static inline struct tb_cfg_header tb_cfg_make_header(u64 route)
}
int tb_cfg_ack_plug(struct tb_ctl *ctl, u64 route, u32 port, bool unplug);
-struct tb_cfg_result tb_cfg_reset(struct tb_ctl *ctl, u64 route,
- int timeout_msec);
+struct tb_cfg_result tb_cfg_reset(struct tb_ctl *ctl, u64 route);
struct tb_cfg_result tb_cfg_read_raw(struct tb_ctl *ctl, void *buffer,
u64 route, u32 port,
enum tb_cfg_space space, u32 offset,
diff --git a/drivers/thunderbolt/debugfs.c b/drivers/thunderbolt/debugfs.c
index 9541d7409ab1..c850b0ac098c 100644
--- a/drivers/thunderbolt/debugfs.c
+++ b/drivers/thunderbolt/debugfs.c
@@ -251,6 +251,29 @@ out:
return ret < 0 ? ret : count;
}
+static void cap_show_by_dw(struct seq_file *s, struct tb_switch *sw,
+ struct tb_port *port, unsigned int cap,
+ unsigned int offset, u8 cap_id, u8 vsec_id,
+ int dwords)
+{
+ int i, ret;
+ u32 data;
+
+ for (i = 0; i < dwords; i++) {
+ if (port)
+ ret = tb_port_read(port, &data, TB_CFG_PORT, cap + offset + i, 1);
+ else
+ ret = tb_sw_read(sw, &data, TB_CFG_SWITCH, cap + offset + i, 1);
+ if (ret) {
+ seq_printf(s, "0x%04x <not accessible>\n", cap + offset + i);
+ continue;
+ }
+
+ seq_printf(s, "0x%04x %4d 0x%02x 0x%02x 0x%08x\n", cap + offset + i,
+ offset + i, cap_id, vsec_id, data);
+ }
+}
+
static void cap_show(struct seq_file *s, struct tb_switch *sw,
struct tb_port *port, unsigned int cap, u8 cap_id,
u8 vsec_id, int length)
@@ -267,10 +290,7 @@ static void cap_show(struct seq_file *s, struct tb_switch *sw,
else
ret = tb_sw_read(sw, data, TB_CFG_SWITCH, cap + offset, dwords);
if (ret) {
- seq_printf(s, "0x%04x <not accessible>\n",
- cap + offset);
- if (dwords > 1)
- seq_printf(s, "0x%04x ...\n", cap + offset + 1);
+ cap_show_by_dw(s, sw, port, cap, offset, cap_id, vsec_id, length);
return;
}
@@ -341,15 +361,6 @@ static void port_cap_show(struct tb_port *port, struct seq_file *s,
} else {
length = header.extended_short.length;
vsec_id = header.extended_short.vsec_id;
- /*
- * Ice Lake and Tiger Lake do not implement the
- * full length of the capability, only first 32
- * dwords so hard-code it here.
- */
- if (!vsec_id &&
- (tb_switch_is_ice_lake(port->sw) ||
- tb_switch_is_tiger_lake(port->sw)))
- length = 32;
}
break;
diff --git a/drivers/thunderbolt/dma_test.c b/drivers/thunderbolt/dma_test.c
index 6debaf5a6604..3bedecb236e0 100644
--- a/drivers/thunderbolt/dma_test.c
+++ b/drivers/thunderbolt/dma_test.c
@@ -13,7 +13,6 @@
#include <linux/sizes.h>
#include <linux/thunderbolt.h>
-#define DMA_TEST_HOPID 8
#define DMA_TEST_TX_RING_SIZE 64
#define DMA_TEST_RX_RING_SIZE 256
#define DMA_TEST_FRAME_SIZE SZ_4K
@@ -72,7 +71,9 @@ static const char * const dma_test_result_names[] = {
* @svc: XDomain service the driver is bound to
* @xd: XDomain the service belongs to
* @rx_ring: Software ring holding RX frames
+ * @rx_hopid: HopID used for receiving frames
* @tx_ring: Software ring holding TX frames
+ * @tx_hopid: HopID used for sending fames
* @packets_to_send: Number of packets to send
* @packets_to_receive: Number of packets to receive
* @packets_sent: Actual number of packets sent
@@ -92,7 +93,9 @@ struct dma_test {
const struct tb_service *svc;
struct tb_xdomain *xd;
struct tb_ring *rx_ring;
+ int rx_hopid;
struct tb_ring *tx_ring;
+ int tx_hopid;
unsigned int packets_to_send;
unsigned int packets_to_receive;
unsigned int packets_sent;
@@ -119,10 +122,12 @@ static void *dma_test_pattern;
static void dma_test_free_rings(struct dma_test *dt)
{
if (dt->rx_ring) {
+ tb_xdomain_release_in_hopid(dt->xd, dt->rx_hopid);
tb_ring_free(dt->rx_ring);
dt->rx_ring = NULL;
}
if (dt->tx_ring) {
+ tb_xdomain_release_out_hopid(dt->xd, dt->tx_hopid);
tb_ring_free(dt->tx_ring);
dt->tx_ring = NULL;
}
@@ -151,6 +156,14 @@ static int dma_test_start_rings(struct dma_test *dt)
dt->tx_ring = ring;
e2e_tx_hop = ring->hop;
+
+ ret = tb_xdomain_alloc_out_hopid(xd, -1);
+ if (ret < 0) {
+ dma_test_free_rings(dt);
+ return ret;
+ }
+
+ dt->tx_hopid = ret;
}
if (dt->packets_to_receive) {
@@ -168,11 +181,19 @@ static int dma_test_start_rings(struct dma_test *dt)
}
dt->rx_ring = ring;
+
+ ret = tb_xdomain_alloc_in_hopid(xd, -1);
+ if (ret < 0) {
+ dma_test_free_rings(dt);
+ return ret;
+ }
+
+ dt->rx_hopid = ret;
}
- ret = tb_xdomain_enable_paths(dt->xd, DMA_TEST_HOPID,
+ ret = tb_xdomain_enable_paths(dt->xd, dt->tx_hopid,
dt->tx_ring ? dt->tx_ring->hop : 0,
- DMA_TEST_HOPID,
+ dt->rx_hopid,
dt->rx_ring ? dt->rx_ring->hop : 0);
if (ret) {
dma_test_free_rings(dt);
@@ -189,12 +210,18 @@ static int dma_test_start_rings(struct dma_test *dt)
static void dma_test_stop_rings(struct dma_test *dt)
{
+ int ret;
+
if (dt->rx_ring)
tb_ring_stop(dt->rx_ring);
if (dt->tx_ring)
tb_ring_stop(dt->tx_ring);
- if (tb_xdomain_disable_paths(dt->xd))
+ ret = tb_xdomain_disable_paths(dt->xd, dt->tx_hopid,
+ dt->tx_ring ? dt->tx_ring->hop : 0,
+ dt->rx_hopid,
+ dt->rx_ring ? dt->rx_ring->hop : 0);
+ if (ret)
dev_warn(&dt->svc->dev, "failed to disable DMA paths\n");
dma_test_free_rings(dt);
diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c
index 89ae614eaba2..98f4056f89ff 100644
--- a/drivers/thunderbolt/domain.c
+++ b/drivers/thunderbolt/domain.c
@@ -341,9 +341,34 @@ struct device_type tb_domain_type = {
.release = tb_domain_release,
};
+static bool tb_domain_event_cb(void *data, enum tb_cfg_pkg_type type,
+ const void *buf, size_t size)
+{
+ struct tb *tb = data;
+
+ if (!tb->cm_ops->handle_event) {
+ tb_warn(tb, "domain does not have event handler\n");
+ return true;
+ }
+
+ switch (type) {
+ case TB_CFG_PKG_XDOMAIN_REQ:
+ case TB_CFG_PKG_XDOMAIN_RESP:
+ if (tb_is_xdomain_enabled())
+ return tb_xdomain_handle_request(tb, type, buf, size);
+ break;
+
+ default:
+ tb->cm_ops->handle_event(tb, type, buf, size);
+ }
+
+ return true;
+}
+
/**
* tb_domain_alloc() - Allocate a domain
* @nhi: Pointer to the host controller
+ * @timeout_msec: Control channel timeout for non-raw messages
* @privsize: Size of the connection manager private data
*
* Allocates and initializes a new Thunderbolt domain. Connection
@@ -355,7 +380,7 @@ struct device_type tb_domain_type = {
*
* Return: allocated domain structure on %NULL in case of error
*/
-struct tb *tb_domain_alloc(struct tb_nhi *nhi, size_t privsize)
+struct tb *tb_domain_alloc(struct tb_nhi *nhi, int timeout_msec, size_t privsize)
{
struct tb *tb;
@@ -382,6 +407,10 @@ struct tb *tb_domain_alloc(struct tb_nhi *nhi, size_t privsize)
if (!tb->wq)
goto err_remove_ida;
+ tb->ctl = tb_ctl_alloc(nhi, timeout_msec, tb_domain_event_cb, tb);
+ if (!tb->ctl)
+ goto err_destroy_wq;
+
tb->dev.parent = &nhi->pdev->dev;
tb->dev.bus = &tb_bus_type;
tb->dev.type = &tb_domain_type;
@@ -391,6 +420,8 @@ struct tb *tb_domain_alloc(struct tb_nhi *nhi, size_t privsize)
return tb;
+err_destroy_wq:
+ destroy_workqueue(tb->wq);
err_remove_ida:
ida_simple_remove(&tb_domain_ida, tb->index);
err_free:
@@ -399,30 +430,6 @@ err_free:
return NULL;
}
-static bool tb_domain_event_cb(void *data, enum tb_cfg_pkg_type type,
- const void *buf, size_t size)
-{
- struct tb *tb = data;
-
- if (!tb->cm_ops->handle_event) {
- tb_warn(tb, "domain does not have event handler\n");
- return true;
- }
-
- switch (type) {
- case TB_CFG_PKG_XDOMAIN_REQ:
- case TB_CFG_PKG_XDOMAIN_RESP:
- if (tb_is_xdomain_enabled())
- return tb_xdomain_handle_request(tb, type, buf, size);
- break;
-
- default:
- tb->cm_ops->handle_event(tb, type, buf, size);
- }
-
- return true;
-}
-
/**
* tb_domain_add() - Add domain to the system
* @tb: Domain to add
@@ -442,13 +449,6 @@ int tb_domain_add(struct tb *tb)
return -EINVAL;
mutex_lock(&tb->lock);
-
- tb->ctl = tb_ctl_alloc(tb->nhi, tb_domain_event_cb, tb);
- if (!tb->ctl) {
- ret = -ENOMEM;
- goto err_unlock;
- }
-
/*
* tb_schedule_hotplug_handler may be called as soon as the config
* channel is started. Thats why we have to hold the lock here.
@@ -493,7 +493,6 @@ err_domain_del:
device_del(&tb->dev);
err_ctl_stop:
tb_ctl_stop(tb->ctl);
-err_unlock:
mutex_unlock(&tb->lock);
return ret;
@@ -793,6 +792,10 @@ int tb_domain_disconnect_pcie_paths(struct tb *tb)
* tb_domain_approve_xdomain_paths() - Enable DMA paths for XDomain
* @tb: Domain enabling the DMA paths
* @xd: XDomain DMA paths are created to
+ * @transmit_path: HopID we are using to send out packets
+ * @transmit_ring: DMA ring used to send out packets
+ * @receive_path: HopID the other end is using to send packets to us
+ * @receive_ring: DMA ring used to receive packets from @receive_path
*
* Calls connection manager specific method to enable DMA paths to the
* XDomain in question.
@@ -801,18 +804,25 @@ int tb_domain_disconnect_pcie_paths(struct tb *tb)
* particular returns %-ENOTSUPP if the connection manager
* implementation does not support XDomains.
*/
-int tb_domain_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd)
+int tb_domain_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
+ int transmit_path, int transmit_ring,
+ int receive_path, int receive_ring)
{
if (!tb->cm_ops->approve_xdomain_paths)
return -ENOTSUPP;
- return tb->cm_ops->approve_xdomain_paths(tb, xd);
+ return tb->cm_ops->approve_xdomain_paths(tb, xd, transmit_path,
+ transmit_ring, receive_path, receive_ring);
}
/**
* tb_domain_disconnect_xdomain_paths() - Disable DMA paths for XDomain
* @tb: Domain disabling the DMA paths
* @xd: XDomain whose DMA paths are disconnected
+ * @transmit_path: HopID we are using to send out packets
+ * @transmit_ring: DMA ring used to send out packets
+ * @receive_path: HopID the other end is using to send packets to us
+ * @receive_ring: DMA ring used to receive packets from @receive_path
*
* Calls connection manager specific method to disconnect DMA paths to
* the XDomain in question.
@@ -821,12 +831,15 @@ int tb_domain_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd)
* particular returns %-ENOTSUPP if the connection manager
* implementation does not support XDomains.
*/
-int tb_domain_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd)
+int tb_domain_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
+ int transmit_path, int transmit_ring,
+ int receive_path, int receive_ring)
{
if (!tb->cm_ops->disconnect_xdomain_paths)
return -ENOTSUPP;
- return tb->cm_ops->disconnect_xdomain_paths(tb, xd);
+ return tb->cm_ops->disconnect_xdomain_paths(tb, xd, transmit_path,
+ transmit_ring, receive_path, receive_ring);
}
static int disconnect_xdomain(struct device *dev, void *data)
@@ -837,7 +850,7 @@ static int disconnect_xdomain(struct device *dev, void *data)
xd = tb_to_xdomain(dev);
if (xd && xd->tb == tb)
- ret = tb_xdomain_disable_paths(xd);
+ ret = tb_xdomain_disable_all_paths(xd);
return ret;
}
diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c
index dd03d3096653..46d0906a3070 100644
--- a/drivers/thunderbolt/eeprom.c
+++ b/drivers/thunderbolt/eeprom.c
@@ -277,6 +277,16 @@ struct tb_drom_entry_port {
u8 unknown4:2;
} __packed;
+/* USB4 product descriptor */
+struct tb_drom_entry_desc {
+ struct tb_drom_entry_header header;
+ u16 bcdUSBSpec;
+ u16 idVendor;
+ u16 idProduct;
+ u16 bcdProductFWRevision;
+ u32 TID;
+ u8 productHWRevision;
+};
/**
* tb_drom_read_uid_only() - Read UID directly from DROM
@@ -329,6 +339,16 @@ static int tb_drom_parse_entry_generic(struct tb_switch *sw,
if (!sw->device_name)
return -ENOMEM;
break;
+ case 9: {
+ const struct tb_drom_entry_desc *desc =
+ (const struct tb_drom_entry_desc *)entry;
+
+ if (!sw->vendor && !sw->device) {
+ sw->vendor = desc->idVendor;
+ sw->device = desc->idProduct;
+ }
+ break;
+ }
}
return 0;
@@ -521,6 +541,51 @@ static int tb_drom_read_n(struct tb_switch *sw, u16 offset, u8 *val,
return tb_eeprom_read_n(sw, offset, val, count);
}
+static int tb_drom_parse(struct tb_switch *sw)
+{
+ const struct tb_drom_header *header =
+ (const struct tb_drom_header *)sw->drom;
+ u32 crc;
+
+ crc = tb_crc8((u8 *) &header->uid, 8);
+ if (crc != header->uid_crc8) {
+ tb_sw_warn(sw,
+ "DROM UID CRC8 mismatch (expected: %#x, got: %#x), aborting\n",
+ header->uid_crc8, crc);
+ return -EINVAL;
+ }
+ if (!sw->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);
+ if (crc != header->data_crc32) {
+ tb_sw_warn(sw,
+ "DROM data CRC32 mismatch (expected: %#x, got: %#x), continuing\n",
+ header->data_crc32, crc);
+ }
+
+ return tb_drom_parse_entries(sw);
+}
+
+static int usb4_drom_parse(struct tb_switch *sw)
+{
+ const struct tb_drom_header *header =
+ (const struct tb_drom_header *)sw->drom;
+ u32 crc;
+
+ crc = tb_crc32(sw->drom + TB_DROM_DATA_START, header->data_len);
+ if (crc != header->data_crc32) {
+ tb_sw_warn(sw,
+ "DROM data CRC32 mismatch (expected: %#x, got: %#x), aborting\n",
+ header->data_crc32, crc);
+ return -EINVAL;
+ }
+
+ return tb_drom_parse_entries(sw);
+}
+
/**
* tb_drom_read() - Copy DROM to sw->drom and parse it
* @sw: Router whose DROM to read and parse
@@ -534,7 +599,6 @@ static int tb_drom_read_n(struct tb_switch *sw, u16 offset, u8 *val,
int tb_drom_read(struct tb_switch *sw)
{
u16 size;
- u32 crc;
struct tb_drom_header *header;
int res, retries = 1;
@@ -599,31 +663,21 @@ parse:
goto err;
}
- crc = tb_crc8((u8 *) &header->uid, 8);
- if (crc != header->uid_crc8) {
- tb_sw_warn(sw,
- "drom uid crc8 mismatch (expected: %#x, got: %#x), aborting\n",
- header->uid_crc8, crc);
- goto err;
- }
- if (!sw->uid)
- sw->uid = header->uid;
- sw->vendor = header->vendor_id;
- sw->device = header->model_id;
- tb_check_quirks(sw);
+ tb_sw_dbg(sw, "DROM version: %d\n", header->device_rom_revision);
- crc = tb_crc32(sw->drom + TB_DROM_DATA_START, header->data_len);
- if (crc != header->data_crc32) {
- tb_sw_warn(sw,
- "drom data crc32 mismatch (expected: %#x, got: %#x), continuing\n",
- header->data_crc32, crc);
+ switch (header->device_rom_revision) {
+ case 3:
+ res = usb4_drom_parse(sw);
+ break;
+ default:
+ tb_sw_warn(sw, "DROM device_rom_revision %#x unknown\n",
+ header->device_rom_revision);
+ fallthrough;
+ case 1:
+ res = tb_drom_parse(sw);
+ break;
}
- if (header->device_rom_revision > 2)
- tb_sw_warn(sw, "drom device_rom_revision %#x unknown\n",
- header->device_rom_revision);
-
- res = tb_drom_parse_entries(sw);
/* If the DROM parsing fails, wait a moment and retry once */
if (res == -EILSEQ && retries--) {
tb_sw_warn(sw, "parsing DROM failed, retrying\n");
@@ -633,10 +687,11 @@ parse:
goto parse;
}
- return res;
+ if (!res)
+ return 0;
+
err:
kfree(sw->drom);
sw->drom = NULL;
return -EIO;
-
}
diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c
index f6f605d48371..2f30b816705a 100644
--- a/drivers/thunderbolt/icm.c
+++ b/drivers/thunderbolt/icm.c
@@ -557,7 +557,9 @@ static int icm_fr_challenge_switch_key(struct tb *tb, struct tb_switch *sw,
return 0;
}
-static int icm_fr_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd)
+static int icm_fr_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
+ int transmit_path, int transmit_ring,
+ int receive_path, int receive_ring)
{
struct icm_fr_pkg_approve_xdomain_response reply;
struct icm_fr_pkg_approve_xdomain request;
@@ -568,10 +570,10 @@ static int icm_fr_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd)
request.link_info = xd->depth << ICM_LINK_INFO_DEPTH_SHIFT | xd->link;
memcpy(&request.remote_uuid, xd->remote_uuid, sizeof(*xd->remote_uuid));
- request.transmit_path = xd->transmit_path;
- request.transmit_ring = xd->transmit_ring;
- request.receive_path = xd->receive_path;
- request.receive_ring = xd->receive_ring;
+ request.transmit_path = transmit_path;
+ request.transmit_ring = transmit_ring;
+ request.receive_path = receive_path;
+ request.receive_ring = receive_ring;
memset(&reply, 0, sizeof(reply));
ret = icm_request(tb, &request, sizeof(request), &reply, sizeof(reply),
@@ -585,7 +587,9 @@ static int icm_fr_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd)
return 0;
}
-static int icm_fr_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd)
+static int icm_fr_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
+ int transmit_path, int transmit_ring,
+ int receive_path, int receive_ring)
{
u8 phy_port;
u8 cmd;
@@ -1122,7 +1126,9 @@ static int icm_tr_challenge_switch_key(struct tb *tb, struct tb_switch *sw,
return 0;
}
-static int icm_tr_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd)
+static int icm_tr_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
+ int transmit_path, int transmit_ring,
+ int receive_path, int receive_ring)
{
struct icm_tr_pkg_approve_xdomain_response reply;
struct icm_tr_pkg_approve_xdomain request;
@@ -1132,10 +1138,10 @@ static int icm_tr_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd)
request.hdr.code = ICM_APPROVE_XDOMAIN;
request.route_hi = upper_32_bits(xd->route);
request.route_lo = lower_32_bits(xd->route);
- request.transmit_path = xd->transmit_path;
- request.transmit_ring = xd->transmit_ring;
- request.receive_path = xd->receive_path;
- request.receive_ring = xd->receive_ring;
+ request.transmit_path = transmit_path;
+ request.transmit_ring = transmit_ring;
+ request.receive_path = receive_path;
+ request.receive_ring = receive_ring;
memcpy(&request.remote_uuid, xd->remote_uuid, sizeof(*xd->remote_uuid));
memset(&reply, 0, sizeof(reply));
@@ -1176,7 +1182,9 @@ static int icm_tr_xdomain_tear_down(struct tb *tb, struct tb_xdomain *xd,
return 0;
}
-static int icm_tr_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd)
+static int icm_tr_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
+ int transmit_path, int transmit_ring,
+ int receive_path, int receive_ring)
{
int ret;
@@ -2416,7 +2424,7 @@ struct tb *icm_probe(struct tb_nhi *nhi)
struct icm *icm;
struct tb *tb;
- tb = tb_domain_alloc(nhi, sizeof(struct icm));
+ tb = tb_domain_alloc(nhi, ICM_TIMEOUT, sizeof(struct icm));
if (!tb)
return NULL;
diff --git a/drivers/thunderbolt/property.c b/drivers/thunderbolt/property.c
index d5b0cdb8f0b1..dc555cda98e6 100644
--- a/drivers/thunderbolt/property.c
+++ b/drivers/thunderbolt/property.c
@@ -502,6 +502,77 @@ ssize_t tb_property_format_dir(const struct tb_property_dir *dir, u32 *block,
}
/**
+ * tb_property_copy_dir() - Take a deep copy of directory
+ * @dir: Directory to copy
+ *
+ * This function takes a deep copy of @dir and returns back the copy. In
+ * case of error returns %NULL. The resulting directory needs to be
+ * released by calling tb_property_free_dir().
+ */
+struct tb_property_dir *tb_property_copy_dir(const struct tb_property_dir *dir)
+{
+ struct tb_property *property, *p = NULL;
+ struct tb_property_dir *d;
+
+ if (!dir)
+ return NULL;
+
+ d = tb_property_create_dir(dir->uuid);
+ if (!d)
+ return NULL;
+
+ list_for_each_entry(property, &dir->properties, list) {
+ struct tb_property *p;
+
+ p = tb_property_alloc(property->key, property->type);
+ if (!p)
+ goto err_free;
+
+ p->length = property->length;
+
+ switch (property->type) {
+ case TB_PROPERTY_TYPE_DIRECTORY:
+ p->value.dir = tb_property_copy_dir(property->value.dir);
+ if (!p->value.dir)
+ goto err_free;
+ break;
+
+ case TB_PROPERTY_TYPE_DATA:
+ p->value.data = kmemdup(property->value.data,
+ property->length * 4,
+ GFP_KERNEL);
+ if (!p->value.data)
+ goto err_free;
+ break;
+
+ case TB_PROPERTY_TYPE_TEXT:
+ p->value.text = kzalloc(p->length * 4, GFP_KERNEL);
+ if (!p->value.text)
+ goto err_free;
+ strcpy(p->value.text, property->value.text);
+ break;
+
+ case TB_PROPERTY_TYPE_VALUE:
+ p->value.immediate = property->value.immediate;
+ break;
+
+ default:
+ break;
+ }
+
+ list_add_tail(&p->list, &d->properties);
+ }
+
+ return d;
+
+err_free:
+ kfree(p);
+ tb_property_free_dir(d);
+
+ return NULL;
+}
+
+/**
* tb_property_add_immediate() - Add immediate property to directory
* @parent: Directory to add the property
* @key: Key for the property
diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c
index 2a95b4ce06c0..e73cd296db7e 100644
--- a/drivers/thunderbolt/switch.c
+++ b/drivers/thunderbolt/switch.c
@@ -627,28 +627,6 @@ int tb_port_add_nfc_credits(struct tb_port *port, int credits)
}
/**
- * tb_port_set_initial_credits() - Set initial port link credits allocated
- * @port: Port to set the initial credits
- * @credits: Number of credits to to allocate
- *
- * Set initial credits value to be used for ingress shared buffering.
- */
-int tb_port_set_initial_credits(struct tb_port *port, u32 credits)
-{
- u32 data;
- int ret;
-
- ret = tb_port_read(port, &data, TB_CFG_PORT, ADP_CS_5, 1);
- if (ret)
- return ret;
-
- data &= ~ADP_CS_5_LCA_MASK;
- data |= (credits << ADP_CS_5_LCA_SHIFT) & ADP_CS_5_LCA_MASK;
-
- return tb_port_write(port, &data, TB_CFG_PORT, ADP_CS_5, 1);
-}
-
-/**
* tb_port_clear_counter() - clear a counter in TB_CFG_COUNTER
* @port: Port whose counters to clear
* @counter: Counter index to clear
@@ -1331,7 +1309,7 @@ int tb_switch_reset(struct tb_switch *sw)
TB_CFG_SWITCH, 2, 2);
if (res.err)
return res.err;
- res = tb_cfg_reset(sw->tb->ctl, tb_route(sw), TB_CFG_DEFAULT_TIMEOUT);
+ res = tb_cfg_reset(sw->tb->ctl, tb_route(sw));
if (res.err > 0)
return -EIO;
return res.err;
@@ -1762,6 +1740,18 @@ static struct attribute *switch_attrs[] = {
NULL,
};
+static bool has_port(const struct tb_switch *sw, enum tb_port_type type)
+{
+ const struct tb_port *port;
+
+ tb_switch_for_each_port(sw, port) {
+ if (!port->disabled && port->config.type == type)
+ return true;
+ }
+
+ return false;
+}
+
static umode_t switch_attr_is_visible(struct kobject *kobj,
struct attribute *attr, int n)
{
@@ -1770,7 +1760,8 @@ static umode_t switch_attr_is_visible(struct kobject *kobj,
if (attr == &dev_attr_authorized.attr) {
if (sw->tb->security_level == TB_SECURITY_NOPCIE ||
- sw->tb->security_level == TB_SECURITY_DPONLY)
+ sw->tb->security_level == TB_SECURITY_DPONLY ||
+ !has_port(sw, TB_TYPE_PCIE_UP))
return 0;
} else if (attr == &dev_attr_device.attr) {
if (!sw->device)
@@ -1849,6 +1840,39 @@ static void tb_switch_release(struct device *dev)
kfree(sw);
}
+static int tb_switch_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+ struct tb_switch *sw = tb_to_switch(dev);
+ const char *type;
+
+ if (sw->config.thunderbolt_version == USB4_VERSION_1_0) {
+ if (add_uevent_var(env, "USB4_VERSION=1.0"))
+ return -ENOMEM;
+ }
+
+ if (!tb_route(sw)) {
+ type = "host";
+ } else {
+ const struct tb_port *port;
+ bool hub = false;
+
+ /* Device is hub if it has any downstream ports */
+ tb_switch_for_each_port(sw, port) {
+ if (!port->disabled && !tb_is_upstream_port(port) &&
+ tb_port_is_null(port)) {
+ hub = true;
+ break;
+ }
+ }
+
+ type = hub ? "hub" : "device";
+ }
+
+ if (add_uevent_var(env, "USB4_TYPE=%s", type))
+ return -ENOMEM;
+ return 0;
+}
+
/*
* Currently only need to provide the callbacks. Everything else is handled
* in the connection manager.
@@ -1882,6 +1906,7 @@ static const struct dev_pm_ops tb_switch_pm_ops = {
struct device_type tb_switch_type = {
.name = "thunderbolt_device",
.release = tb_switch_release,
+ .uevent = tb_switch_uevent,
.pm = &tb_switch_pm_ops,
};
@@ -2542,6 +2567,8 @@ int tb_switch_add(struct tb_switch *sw)
}
tb_sw_dbg(sw, "uid: %#llx\n", sw->uid);
+ tb_check_quirks(sw);
+
ret = tb_switch_set_uuid(sw);
if (ret) {
dev_err(&sw->dev, "failed to set UUID\n");
diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
index c348b1fc0efc..7e6dc2b03bed 100644
--- a/drivers/thunderbolt/tb.c
+++ b/drivers/thunderbolt/tb.c
@@ -15,6 +15,8 @@
#include "tb_regs.h"
#include "tunnel.h"
+#define TB_TIMEOUT 100 /* ms */
+
/**
* struct tb_cm - Simple Thunderbolt connection manager
* @tunnel_list: List of active tunnels
@@ -1077,7 +1079,9 @@ static int tb_tunnel_pci(struct tb *tb, struct tb_switch *sw)
return 0;
}
-static int tb_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd)
+static int tb_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
+ int transmit_path, int transmit_ring,
+ int receive_path, int receive_ring)
{
struct tb_cm *tcm = tb_priv(tb);
struct tb_port *nhi_port, *dst_port;
@@ -1089,9 +1093,8 @@ static int tb_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd)
nhi_port = tb_switch_find_port(tb->root_switch, TB_TYPE_NHI);
mutex_lock(&tb->lock);
- tunnel = tb_tunnel_alloc_dma(tb, nhi_port, dst_port, xd->transmit_ring,
- xd->transmit_path, xd->receive_ring,
- xd->receive_path);
+ tunnel = tb_tunnel_alloc_dma(tb, nhi_port, dst_port, transmit_path,
+ transmit_ring, receive_path, receive_ring);
if (!tunnel) {
mutex_unlock(&tb->lock);
return -ENOMEM;
@@ -1110,29 +1113,40 @@ static int tb_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd)
return 0;
}
-static void __tb_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd)
+static void __tb_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
+ int transmit_path, int transmit_ring,
+ int receive_path, int receive_ring)
{
- struct tb_port *dst_port;
- struct tb_tunnel *tunnel;
+ struct tb_cm *tcm = tb_priv(tb);
+ struct tb_port *nhi_port, *dst_port;
+ struct tb_tunnel *tunnel, *n;
struct tb_switch *sw;
sw = tb_to_switch(xd->dev.parent);
dst_port = tb_port_at(xd->route, sw);
+ nhi_port = tb_switch_find_port(tb->root_switch, TB_TYPE_NHI);
- /*
- * It is possible that the tunnel was already teared down (in
- * case of cable disconnect) so it is fine if we cannot find it
- * here anymore.
- */
- tunnel = tb_find_tunnel(tb, TB_TUNNEL_DMA, NULL, dst_port);
- tb_deactivate_and_free_tunnel(tunnel);
+ list_for_each_entry_safe(tunnel, n, &tcm->tunnel_list, list) {
+ if (!tb_tunnel_is_dma(tunnel))
+ continue;
+ if (tunnel->src_port != nhi_port || tunnel->dst_port != dst_port)
+ continue;
+
+ if (tb_tunnel_match_dma(tunnel, transmit_path, transmit_ring,
+ receive_path, receive_ring))
+ tb_deactivate_and_free_tunnel(tunnel);
+ }
}
-static int tb_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd)
+static int tb_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
+ int transmit_path, int transmit_ring,
+ int receive_path, int receive_ring)
{
if (!xd->is_unplugged) {
mutex_lock(&tb->lock);
- __tb_disconnect_xdomain_paths(tb, xd);
+ __tb_disconnect_xdomain_paths(tb, xd, transmit_path,
+ transmit_ring, receive_path,
+ receive_ring);
mutex_unlock(&tb->lock);
}
return 0;
@@ -1208,12 +1222,12 @@ static void tb_handle_hotplug(struct work_struct *work)
* tb_xdomain_remove() so setting XDomain as
* unplugged here prevents deadlock if they call
* tb_xdomain_disable_paths(). We will tear down
- * the path below.
+ * all the tunnels below.
*/
xd->is_unplugged = true;
tb_xdomain_remove(xd);
port->xdomain = NULL;
- __tb_disconnect_xdomain_paths(tb, xd);
+ __tb_disconnect_xdomain_paths(tb, xd, -1, -1, -1, -1);
tb_xdomain_put(xd);
tb_port_unconfigure_xdomain(port);
} else if (tb_port_is_dpout(port) || tb_port_is_dpin(port)) {
@@ -1562,7 +1576,7 @@ struct tb *tb_probe(struct tb_nhi *nhi)
struct tb_cm *tcm;
struct tb *tb;
- tb = tb_domain_alloc(nhi, sizeof(*tcm));
+ tb = tb_domain_alloc(nhi, TB_TIMEOUT, sizeof(*tcm));
if (!tb)
return NULL;
diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
index beea88c34c0f..9790e9f13d2b 100644
--- a/drivers/thunderbolt/tb.h
+++ b/drivers/thunderbolt/tb.h
@@ -406,8 +406,12 @@ struct tb_cm_ops {
int (*challenge_switch_key)(struct tb *tb, struct tb_switch *sw,
const u8 *challenge, u8 *response);
int (*disconnect_pcie_paths)(struct tb *tb);
- int (*approve_xdomain_paths)(struct tb *tb, struct tb_xdomain *xd);
- int (*disconnect_xdomain_paths)(struct tb *tb, struct tb_xdomain *xd);
+ int (*approve_xdomain_paths)(struct tb *tb, struct tb_xdomain *xd,
+ int transmit_path, int transmit_ring,
+ int receive_path, int receive_ring);
+ int (*disconnect_xdomain_paths)(struct tb *tb, struct tb_xdomain *xd,
+ int transmit_path, int transmit_ring,
+ int receive_path, int receive_ring);
int (*usb4_switch_op)(struct tb_switch *sw, u16 opcode, u32 *metadata,
u8 *status, const void *tx_data, size_t tx_data_len,
void *rx_data, size_t rx_data_len);
@@ -625,7 +629,7 @@ void tb_domain_exit(void);
int tb_xdomain_init(void);
void tb_xdomain_exit(void);
-struct tb *tb_domain_alloc(struct tb_nhi *nhi, size_t privsize);
+struct tb *tb_domain_alloc(struct tb_nhi *nhi, int timeout_msec, size_t privsize);
int tb_domain_add(struct tb *tb);
void tb_domain_remove(struct tb *tb);
int tb_domain_suspend_noirq(struct tb *tb);
@@ -641,8 +645,12 @@ 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);
-int tb_domain_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd);
-int tb_domain_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd);
+int tb_domain_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
+ int transmit_path, int transmit_ring,
+ int receive_path, int receive_ring);
+int tb_domain_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd,
+ int transmit_path, int transmit_ring,
+ int receive_path, int receive_ring);
int tb_domain_disconnect_all_paths(struct tb *tb);
static inline struct tb *tb_domain_get(struct tb *tb)
@@ -787,32 +795,6 @@ static inline bool tb_switch_is_titan_ridge(const struct tb_switch *sw)
return false;
}
-static inline bool tb_switch_is_ice_lake(const struct tb_switch *sw)
-{
- if (sw->config.vendor_id == PCI_VENDOR_ID_INTEL) {
- switch (sw->config.device_id) {
- case PCI_DEVICE_ID_INTEL_ICL_NHI0:
- case PCI_DEVICE_ID_INTEL_ICL_NHI1:
- return true;
- }
- }
- return false;
-}
-
-static inline bool tb_switch_is_tiger_lake(const struct tb_switch *sw)
-{
- if (sw->config.vendor_id == PCI_VENDOR_ID_INTEL) {
- switch (sw->config.device_id) {
- case PCI_DEVICE_ID_INTEL_TGL_NHI0:
- case PCI_DEVICE_ID_INTEL_TGL_NHI1:
- case PCI_DEVICE_ID_INTEL_TGL_H_NHI0:
- case PCI_DEVICE_ID_INTEL_TGL_H_NHI1:
- return true;
- }
- }
- return false;
-}
-
/**
* tb_switch_is_usb4() - Is the switch USB4 compliant
* @sw: Switch to check
@@ -860,7 +842,6 @@ static inline bool tb_switch_tmu_is_enabled(const struct tb_switch *sw)
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_set_initial_credits(struct tb_port *port, u32 credits);
int tb_port_clear_counter(struct tb_port *port, int counter);
int tb_port_unlock(struct tb_port *port);
int tb_port_enable(struct tb_port *port);
diff --git a/drivers/thunderbolt/test.c b/drivers/thunderbolt/test.c
index 464c2d37b992..5ff5a03bc9ce 100644
--- a/drivers/thunderbolt/test.c
+++ b/drivers/thunderbolt/test.c
@@ -119,6 +119,7 @@ static struct tb_switch *alloc_host(struct kunit *test)
sw->ports[7].config.type = TB_TYPE_NHI;
sw->ports[7].config.max_in_hop_id = 11;
sw->ports[7].config.max_out_hop_id = 11;
+ sw->ports[7].config.nfc_credits = 0x41800000;
sw->ports[8].config.type = TB_TYPE_PCIE_DOWN;
sw->ports[8].config.max_in_hop_id = 8;
@@ -1594,6 +1595,489 @@ static void tb_test_tunnel_port_on_path(struct kunit *test)
tb_tunnel_free(dp_tunnel);
}
+static void tb_test_tunnel_dma(struct kunit *test)
+{
+ struct tb_port *nhi, *port;
+ struct tb_tunnel *tunnel;
+ struct tb_switch *host;
+
+ /*
+ * Create DMA tunnel from NHI to port 1 and back.
+ *
+ * [Host 1]
+ * 1 ^ In HopID 1 -> Out HopID 8
+ * |
+ * v In HopID 8 -> Out HopID 1
+ * ............ Domain border
+ * |
+ * [Host 2]
+ */
+ host = alloc_host(test);
+ nhi = &host->ports[7];
+ port = &host->ports[1];
+
+ tunnel = tb_tunnel_alloc_dma(NULL, nhi, port, 8, 1, 8, 1);
+ KUNIT_ASSERT_TRUE(test, tunnel != NULL);
+ KUNIT_EXPECT_EQ(test, tunnel->type, (enum tb_tunnel_type)TB_TUNNEL_DMA);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->src_port, nhi);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->dst_port, port);
+ KUNIT_ASSERT_EQ(test, tunnel->npaths, (size_t)2);
+ /* RX path */
+ KUNIT_ASSERT_EQ(test, tunnel->paths[0]->path_length, 1);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->paths[0]->hops[0].in_port, port);
+ KUNIT_EXPECT_EQ(test, tunnel->paths[0]->hops[0].in_hop_index, 8);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->paths[0]->hops[0].out_port, nhi);
+ KUNIT_EXPECT_EQ(test, tunnel->paths[0]->hops[0].next_hop_index, 1);
+ /* TX path */
+ KUNIT_ASSERT_EQ(test, tunnel->paths[1]->path_length, 1);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->paths[1]->hops[0].in_port, nhi);
+ KUNIT_EXPECT_EQ(test, tunnel->paths[1]->hops[0].in_hop_index, 1);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->paths[1]->hops[0].out_port, port);
+ KUNIT_EXPECT_EQ(test, tunnel->paths[1]->hops[0].next_hop_index, 8);
+
+ tb_tunnel_free(tunnel);
+}
+
+static void tb_test_tunnel_dma_rx(struct kunit *test)
+{
+ struct tb_port *nhi, *port;
+ struct tb_tunnel *tunnel;
+ struct tb_switch *host;
+
+ /*
+ * Create DMA RX tunnel from port 1 to NHI.
+ *
+ * [Host 1]
+ * 1 ^
+ * |
+ * | In HopID 15 -> Out HopID 2
+ * ............ Domain border
+ * |
+ * [Host 2]
+ */
+ host = alloc_host(test);
+ nhi = &host->ports[7];
+ port = &host->ports[1];
+
+ tunnel = tb_tunnel_alloc_dma(NULL, nhi, port, -1, -1, 15, 2);
+ KUNIT_ASSERT_TRUE(test, tunnel != NULL);
+ KUNIT_EXPECT_EQ(test, tunnel->type, (enum tb_tunnel_type)TB_TUNNEL_DMA);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->src_port, nhi);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->dst_port, port);
+ KUNIT_ASSERT_EQ(test, tunnel->npaths, (size_t)1);
+ /* RX path */
+ KUNIT_ASSERT_EQ(test, tunnel->paths[0]->path_length, 1);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->paths[0]->hops[0].in_port, port);
+ KUNIT_EXPECT_EQ(test, tunnel->paths[0]->hops[0].in_hop_index, 15);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->paths[0]->hops[0].out_port, nhi);
+ KUNIT_EXPECT_EQ(test, tunnel->paths[0]->hops[0].next_hop_index, 2);
+
+ tb_tunnel_free(tunnel);
+}
+
+static void tb_test_tunnel_dma_tx(struct kunit *test)
+{
+ struct tb_port *nhi, *port;
+ struct tb_tunnel *tunnel;
+ struct tb_switch *host;
+
+ /*
+ * Create DMA TX tunnel from NHI to port 1.
+ *
+ * [Host 1]
+ * 1 | In HopID 2 -> Out HopID 15
+ * |
+ * v
+ * ............ Domain border
+ * |
+ * [Host 2]
+ */
+ host = alloc_host(test);
+ nhi = &host->ports[7];
+ port = &host->ports[1];
+
+ tunnel = tb_tunnel_alloc_dma(NULL, nhi, port, 15, 2, -1, -1);
+ KUNIT_ASSERT_TRUE(test, tunnel != NULL);
+ KUNIT_EXPECT_EQ(test, tunnel->type, (enum tb_tunnel_type)TB_TUNNEL_DMA);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->src_port, nhi);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->dst_port, port);
+ KUNIT_ASSERT_EQ(test, tunnel->npaths, (size_t)1);
+ /* TX path */
+ KUNIT_ASSERT_EQ(test, tunnel->paths[0]->path_length, 1);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->paths[0]->hops[0].in_port, nhi);
+ KUNIT_EXPECT_EQ(test, tunnel->paths[0]->hops[0].in_hop_index, 2);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->paths[0]->hops[0].out_port, port);
+ KUNIT_EXPECT_EQ(test, tunnel->paths[0]->hops[0].next_hop_index, 15);
+
+ tb_tunnel_free(tunnel);
+}
+
+static void tb_test_tunnel_dma_chain(struct kunit *test)
+{
+ struct tb_switch *host, *dev1, *dev2;
+ struct tb_port *nhi, *port;
+ struct tb_tunnel *tunnel;
+
+ /*
+ * Create DMA tunnel from NHI to Device #2 port 3 and back.
+ *
+ * [Host 1]
+ * 1 ^ In HopID 1 -> Out HopID x
+ * |
+ * 1 | In HopID x -> Out HopID 1
+ * [Device #1]
+ * 7 \
+ * 1 \
+ * [Device #2]
+ * 3 | In HopID x -> Out HopID 8
+ * |
+ * v In HopID 8 -> Out HopID x
+ * ............ Domain border
+ * |
+ * [Host 2]
+ */
+ host = alloc_host(test);
+ dev1 = alloc_dev_default(test, host, 0x1, true);
+ dev2 = alloc_dev_default(test, dev1, 0x701, true);
+
+ nhi = &host->ports[7];
+ port = &dev2->ports[3];
+ tunnel = tb_tunnel_alloc_dma(NULL, nhi, port, 8, 1, 8, 1);
+ KUNIT_ASSERT_TRUE(test, tunnel != NULL);
+ KUNIT_EXPECT_EQ(test, tunnel->type, (enum tb_tunnel_type)TB_TUNNEL_DMA);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->src_port, nhi);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->dst_port, port);
+ KUNIT_ASSERT_EQ(test, tunnel->npaths, (size_t)2);
+ /* RX path */
+ KUNIT_ASSERT_EQ(test, tunnel->paths[0]->path_length, 3);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->paths[0]->hops[0].in_port, port);
+ KUNIT_EXPECT_EQ(test, tunnel->paths[0]->hops[0].in_hop_index, 8);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->paths[0]->hops[0].out_port,
+ &dev2->ports[1]);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->paths[0]->hops[1].in_port,
+ &dev1->ports[7]);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->paths[0]->hops[1].out_port,
+ &dev1->ports[1]);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->paths[0]->hops[2].in_port,
+ &host->ports[1]);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->paths[0]->hops[2].out_port, nhi);
+ KUNIT_EXPECT_EQ(test, tunnel->paths[0]->hops[2].next_hop_index, 1);
+ /* TX path */
+ KUNIT_ASSERT_EQ(test, tunnel->paths[1]->path_length, 3);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->paths[1]->hops[0].in_port, nhi);
+ KUNIT_EXPECT_EQ(test, tunnel->paths[1]->hops[0].in_hop_index, 1);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->paths[1]->hops[1].in_port,
+ &dev1->ports[1]);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->paths[1]->hops[1].out_port,
+ &dev1->ports[7]);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->paths[1]->hops[2].in_port,
+ &dev2->ports[1]);
+ KUNIT_EXPECT_PTR_EQ(test, tunnel->paths[1]->hops[2].out_port, port);
+ KUNIT_EXPECT_EQ(test, tunnel->paths[1]->hops[2].next_hop_index, 8);
+
+ tb_tunnel_free(tunnel);
+}
+
+static void tb_test_tunnel_dma_match(struct kunit *test)
+{
+ struct tb_port *nhi, *port;
+ struct tb_tunnel *tunnel;
+ struct tb_switch *host;
+
+ host = alloc_host(test);
+ nhi = &host->ports[7];
+ port = &host->ports[1];
+
+ tunnel = tb_tunnel_alloc_dma(NULL, nhi, port, 15, 1, 15, 1);
+ KUNIT_ASSERT_TRUE(test, tunnel != NULL);
+
+ KUNIT_ASSERT_TRUE(test, tb_tunnel_match_dma(tunnel, 15, 1, 15, 1));
+ KUNIT_ASSERT_FALSE(test, tb_tunnel_match_dma(tunnel, 8, 1, 15, 1));
+ KUNIT_ASSERT_TRUE(test, tb_tunnel_match_dma(tunnel, -1, -1, 15, 1));
+ KUNIT_ASSERT_TRUE(test, tb_tunnel_match_dma(tunnel, 15, 1, -1, -1));
+ KUNIT_ASSERT_TRUE(test, tb_tunnel_match_dma(tunnel, 15, -1, -1, -1));
+ KUNIT_ASSERT_TRUE(test, tb_tunnel_match_dma(tunnel, -1, 1, -1, -1));
+ KUNIT_ASSERT_TRUE(test, tb_tunnel_match_dma(tunnel, -1, -1, 15, -1));
+ KUNIT_ASSERT_TRUE(test, tb_tunnel_match_dma(tunnel, -1, -1, -1, 1));
+ KUNIT_ASSERT_TRUE(test, tb_tunnel_match_dma(tunnel, -1, -1, -1, -1));
+ KUNIT_ASSERT_FALSE(test, tb_tunnel_match_dma(tunnel, 8, -1, 8, -1));
+
+ tb_tunnel_free(tunnel);
+
+ tunnel = tb_tunnel_alloc_dma(NULL, nhi, port, 15, 1, -1, -1);
+ KUNIT_ASSERT_TRUE(test, tunnel != NULL);
+ KUNIT_ASSERT_TRUE(test, tb_tunnel_match_dma(tunnel, 15, 1, -1, -1));
+ KUNIT_ASSERT_TRUE(test, tb_tunnel_match_dma(tunnel, 15, -1, -1, -1));
+ KUNIT_ASSERT_TRUE(test, tb_tunnel_match_dma(tunnel, -1, 1, -1, -1));
+ KUNIT_ASSERT_TRUE(test, tb_tunnel_match_dma(tunnel, -1, -1, -1, -1));
+ KUNIT_ASSERT_FALSE(test, tb_tunnel_match_dma(tunnel, 15, 1, 15, 1));
+ KUNIT_ASSERT_FALSE(test, tb_tunnel_match_dma(tunnel, -1, -1, 15, 1));
+ KUNIT_ASSERT_FALSE(test, tb_tunnel_match_dma(tunnel, 15, 11, -1, -1));
+
+ tb_tunnel_free(tunnel);
+
+ tunnel = tb_tunnel_alloc_dma(NULL, nhi, port, -1, -1, 15, 11);
+ KUNIT_ASSERT_TRUE(test, tunnel != NULL);
+ KUNIT_ASSERT_TRUE(test, tb_tunnel_match_dma(tunnel, -1, -1, 15, 11));
+ KUNIT_ASSERT_TRUE(test, tb_tunnel_match_dma(tunnel, -1, -1, 15, -1));
+ KUNIT_ASSERT_TRUE(test, tb_tunnel_match_dma(tunnel, -1, -1, -1, 11));
+ KUNIT_ASSERT_TRUE(test, tb_tunnel_match_dma(tunnel, -1, -1, -1, -1));
+ KUNIT_ASSERT_FALSE(test, tb_tunnel_match_dma(tunnel, -1, -1, 15, 1));
+ KUNIT_ASSERT_FALSE(test, tb_tunnel_match_dma(tunnel, -1, -1, 10, 11));
+ KUNIT_ASSERT_FALSE(test, tb_tunnel_match_dma(tunnel, 15, 11, -1, -1));
+
+ tb_tunnel_free(tunnel);
+}
+
+static const u32 root_directory[] = {
+ 0x55584401, /* "UXD" v1 */
+ 0x00000018, /* Root directory length */
+ 0x76656e64, /* "vend" */
+ 0x6f726964, /* "orid" */
+ 0x76000001, /* "v" R 1 */
+ 0x00000a27, /* Immediate value, ! Vendor ID */
+ 0x76656e64, /* "vend" */
+ 0x6f726964, /* "orid" */
+ 0x74000003, /* "t" R 3 */
+ 0x0000001a, /* Text leaf offset, (“Apple Inc.”) */
+ 0x64657669, /* "devi" */
+ 0x63656964, /* "ceid" */
+ 0x76000001, /* "v" R 1 */
+ 0x0000000a, /* Immediate value, ! Device ID */
+ 0x64657669, /* "devi" */
+ 0x63656964, /* "ceid" */
+ 0x74000003, /* "t" R 3 */
+ 0x0000001d, /* Text leaf offset, (“Macintosh”) */
+ 0x64657669, /* "devi" */
+ 0x63657276, /* "cerv" */
+ 0x76000001, /* "v" R 1 */
+ 0x80000100, /* Immediate value, Device Revision */
+ 0x6e657477, /* "netw" */
+ 0x6f726b00, /* "ork" */
+ 0x44000014, /* "D" R 20 */
+ 0x00000021, /* Directory data offset, (Network Directory) */
+ 0x4170706c, /* "Appl" */
+ 0x6520496e, /* "e In" */
+ 0x632e0000, /* "c." ! */
+ 0x4d616369, /* "Maci" */
+ 0x6e746f73, /* "ntos" */
+ 0x68000000, /* "h" */
+ 0x00000000, /* padding */
+ 0xca8961c6, /* Directory UUID, Network Directory */
+ 0x9541ce1c, /* Directory UUID, Network Directory */
+ 0x5949b8bd, /* Directory UUID, Network Directory */
+ 0x4f5a5f2e, /* Directory UUID, Network Directory */
+ 0x70727463, /* "prtc" */
+ 0x69640000, /* "id" */
+ 0x76000001, /* "v" R 1 */
+ 0x00000001, /* Immediate value, Network Protocol ID */
+ 0x70727463, /* "prtc" */
+ 0x76657273, /* "vers" */
+ 0x76000001, /* "v" R 1 */
+ 0x00000001, /* Immediate value, Network Protocol Version */
+ 0x70727463, /* "prtc" */
+ 0x72657673, /* "revs" */
+ 0x76000001, /* "v" R 1 */
+ 0x00000001, /* Immediate value, Network Protocol Revision */
+ 0x70727463, /* "prtc" */
+ 0x73746e73, /* "stns" */
+ 0x76000001, /* "v" R 1 */
+ 0x00000000, /* Immediate value, Network Protocol Settings */
+};
+
+static const uuid_t network_dir_uuid =
+ UUID_INIT(0xc66189ca, 0x1cce, 0x4195,
+ 0xbd, 0xb8, 0x49, 0x59, 0x2e, 0x5f, 0x5a, 0x4f);
+
+static void tb_test_property_parse(struct kunit *test)
+{
+ struct tb_property_dir *dir, *network_dir;
+ struct tb_property *p;
+
+ dir = tb_property_parse_dir(root_directory, ARRAY_SIZE(root_directory));
+ KUNIT_ASSERT_TRUE(test, dir != NULL);
+
+ p = tb_property_find(dir, "foo", TB_PROPERTY_TYPE_TEXT);
+ KUNIT_ASSERT_TRUE(test, !p);
+
+ p = tb_property_find(dir, "vendorid", TB_PROPERTY_TYPE_TEXT);
+ KUNIT_ASSERT_TRUE(test, p != NULL);
+ KUNIT_EXPECT_STREQ(test, p->value.text, "Apple Inc.");
+
+ p = tb_property_find(dir, "vendorid", TB_PROPERTY_TYPE_VALUE);
+ KUNIT_ASSERT_TRUE(test, p != NULL);
+ KUNIT_EXPECT_EQ(test, p->value.immediate, (u32)0xa27);
+
+ p = tb_property_find(dir, "deviceid", TB_PROPERTY_TYPE_TEXT);
+ KUNIT_ASSERT_TRUE(test, p != NULL);
+ KUNIT_EXPECT_STREQ(test, p->value.text, "Macintosh");
+
+ p = tb_property_find(dir, "deviceid", TB_PROPERTY_TYPE_VALUE);
+ KUNIT_ASSERT_TRUE(test, p != NULL);
+ KUNIT_EXPECT_EQ(test, p->value.immediate, (u32)0xa);
+
+ p = tb_property_find(dir, "missing", TB_PROPERTY_TYPE_DIRECTORY);
+ KUNIT_ASSERT_TRUE(test, !p);
+
+ p = tb_property_find(dir, "network", TB_PROPERTY_TYPE_DIRECTORY);
+ KUNIT_ASSERT_TRUE(test, p != NULL);
+
+ network_dir = p->value.dir;
+ KUNIT_EXPECT_TRUE(test, uuid_equal(network_dir->uuid, &network_dir_uuid));
+
+ p = tb_property_find(network_dir, "prtcid", TB_PROPERTY_TYPE_VALUE);
+ KUNIT_ASSERT_TRUE(test, p != NULL);
+ KUNIT_EXPECT_EQ(test, p->value.immediate, (u32)0x1);
+
+ p = tb_property_find(network_dir, "prtcvers", TB_PROPERTY_TYPE_VALUE);
+ KUNIT_ASSERT_TRUE(test, p != NULL);
+ KUNIT_EXPECT_EQ(test, p->value.immediate, (u32)0x1);
+
+ p = tb_property_find(network_dir, "prtcrevs", TB_PROPERTY_TYPE_VALUE);
+ KUNIT_ASSERT_TRUE(test, p != NULL);
+ KUNIT_EXPECT_EQ(test, p->value.immediate, (u32)0x1);
+
+ p = tb_property_find(network_dir, "prtcstns", TB_PROPERTY_TYPE_VALUE);
+ KUNIT_ASSERT_TRUE(test, p != NULL);
+ KUNIT_EXPECT_EQ(test, p->value.immediate, (u32)0x0);
+
+ p = tb_property_find(network_dir, "deviceid", TB_PROPERTY_TYPE_VALUE);
+ KUNIT_EXPECT_TRUE(test, !p);
+ p = tb_property_find(network_dir, "deviceid", TB_PROPERTY_TYPE_TEXT);
+ KUNIT_EXPECT_TRUE(test, !p);
+
+ tb_property_free_dir(dir);
+}
+
+static void tb_test_property_format(struct kunit *test)
+{
+ struct tb_property_dir *dir;
+ ssize_t block_len;
+ u32 *block;
+ int ret, i;
+
+ dir = tb_property_parse_dir(root_directory, ARRAY_SIZE(root_directory));
+ KUNIT_ASSERT_TRUE(test, dir != NULL);
+
+ ret = tb_property_format_dir(dir, NULL, 0);
+ KUNIT_ASSERT_EQ(test, ret, (int)ARRAY_SIZE(root_directory));
+
+ block_len = ret;
+
+ block = kunit_kzalloc(test, block_len * sizeof(u32), GFP_KERNEL);
+ KUNIT_ASSERT_TRUE(test, block != NULL);
+
+ ret = tb_property_format_dir(dir, block, block_len);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+
+ for (i = 0; i < ARRAY_SIZE(root_directory); i++)
+ KUNIT_EXPECT_EQ(test, root_directory[i], block[i]);
+
+ tb_property_free_dir(dir);
+}
+
+static void compare_dirs(struct kunit *test, struct tb_property_dir *d1,
+ struct tb_property_dir *d2)
+{
+ struct tb_property *p1, *p2, *tmp;
+ int n1, n2, i;
+
+ if (d1->uuid) {
+ KUNIT_ASSERT_TRUE(test, d2->uuid != NULL);
+ KUNIT_ASSERT_TRUE(test, uuid_equal(d1->uuid, d2->uuid));
+ } else {
+ KUNIT_ASSERT_TRUE(test, d2->uuid == NULL);
+ }
+
+ n1 = 0;
+ tb_property_for_each(d1, tmp)
+ n1++;
+ KUNIT_ASSERT_NE(test, n1, 0);
+
+ n2 = 0;
+ tb_property_for_each(d2, tmp)
+ n2++;
+ KUNIT_ASSERT_NE(test, n2, 0);
+
+ KUNIT_ASSERT_EQ(test, n1, n2);
+
+ p1 = NULL;
+ p2 = NULL;
+ for (i = 0; i < n1; i++) {
+ p1 = tb_property_get_next(d1, p1);
+ KUNIT_ASSERT_TRUE(test, p1 != NULL);
+ p2 = tb_property_get_next(d2, p2);
+ KUNIT_ASSERT_TRUE(test, p2 != NULL);
+
+ KUNIT_ASSERT_STREQ(test, &p1->key[0], &p2->key[0]);
+ KUNIT_ASSERT_EQ(test, p1->type, p2->type);
+ KUNIT_ASSERT_EQ(test, p1->length, p2->length);
+
+ switch (p1->type) {
+ case TB_PROPERTY_TYPE_DIRECTORY:
+ KUNIT_ASSERT_TRUE(test, p1->value.dir != NULL);
+ KUNIT_ASSERT_TRUE(test, p2->value.dir != NULL);
+ compare_dirs(test, p1->value.dir, p2->value.dir);
+ break;
+
+ case TB_PROPERTY_TYPE_DATA:
+ KUNIT_ASSERT_TRUE(test, p1->value.data != NULL);
+ KUNIT_ASSERT_TRUE(test, p2->value.data != NULL);
+ KUNIT_ASSERT_TRUE(test,
+ !memcmp(p1->value.data, p2->value.data,
+ p1->length * 4)
+ );
+ break;
+
+ case TB_PROPERTY_TYPE_TEXT:
+ KUNIT_ASSERT_TRUE(test, p1->value.text != NULL);
+ KUNIT_ASSERT_TRUE(test, p2->value.text != NULL);
+ KUNIT_ASSERT_STREQ(test, p1->value.text, p2->value.text);
+ break;
+
+ case TB_PROPERTY_TYPE_VALUE:
+ KUNIT_ASSERT_EQ(test, p1->value.immediate,
+ p2->value.immediate);
+ break;
+ default:
+ KUNIT_FAIL(test, "unexpected property type");
+ break;
+ }
+ }
+}
+
+static void tb_test_property_copy(struct kunit *test)
+{
+ struct tb_property_dir *src, *dst;
+ u32 *block;
+ int ret, i;
+
+ src = tb_property_parse_dir(root_directory, ARRAY_SIZE(root_directory));
+ KUNIT_ASSERT_TRUE(test, src != NULL);
+
+ dst = tb_property_copy_dir(src);
+ KUNIT_ASSERT_TRUE(test, dst != NULL);
+
+ /* Compare the structures */
+ compare_dirs(test, src, dst);
+
+ /* Compare the resulting property block */
+ ret = tb_property_format_dir(dst, NULL, 0);
+ KUNIT_ASSERT_EQ(test, ret, (int)ARRAY_SIZE(root_directory));
+
+ block = kunit_kzalloc(test, sizeof(root_directory), GFP_KERNEL);
+ KUNIT_ASSERT_TRUE(test, block != NULL);
+
+ ret = tb_property_format_dir(dst, block, ARRAY_SIZE(root_directory));
+ KUNIT_EXPECT_TRUE(test, !ret);
+
+ for (i = 0; i < ARRAY_SIZE(root_directory); i++)
+ KUNIT_EXPECT_EQ(test, root_directory[i], block[i]);
+
+ tb_property_free_dir(dst);
+ tb_property_free_dir(src);
+}
+
static struct kunit_case tb_test_cases[] = {
KUNIT_CASE(tb_test_path_basic),
KUNIT_CASE(tb_test_path_not_connected_walk),
@@ -1616,6 +2100,14 @@ static struct kunit_case tb_test_cases[] = {
KUNIT_CASE(tb_test_tunnel_dp_max_length),
KUNIT_CASE(tb_test_tunnel_port_on_path),
KUNIT_CASE(tb_test_tunnel_usb3),
+ KUNIT_CASE(tb_test_tunnel_dma),
+ KUNIT_CASE(tb_test_tunnel_dma_rx),
+ KUNIT_CASE(tb_test_tunnel_dma_tx),
+ KUNIT_CASE(tb_test_tunnel_dma_chain),
+ KUNIT_CASE(tb_test_tunnel_dma_match),
+ KUNIT_CASE(tb_test_property_parse),
+ KUNIT_CASE(tb_test_property_format),
+ KUNIT_CASE(tb_test_property_copy),
{ }
};
diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c
index 6557b6e07009..e1979bed7146 100644
--- a/drivers/thunderbolt/tunnel.c
+++ b/drivers/thunderbolt/tunnel.c
@@ -794,24 +794,14 @@ static u32 tb_dma_credits(struct tb_port *nhi)
return min(max_credits, 13U);
}
-static int tb_dma_activate(struct tb_tunnel *tunnel, bool active)
-{
- struct tb_port *nhi = tunnel->src_port;
- u32 credits;
-
- credits = active ? tb_dma_credits(nhi) : 0;
- return tb_port_set_initial_credits(nhi, credits);
-}
-
-static void tb_dma_init_path(struct tb_path *path, unsigned int isb,
- unsigned int efc, u32 credits)
+static void tb_dma_init_path(struct tb_path *path, unsigned int efc, u32 credits)
{
int i;
path->egress_fc_enable = efc;
path->ingress_fc_enable = TB_PATH_ALL;
path->egress_shared_buffer = TB_PATH_NONE;
- path->ingress_shared_buffer = isb;
+ path->ingress_shared_buffer = TB_PATH_NONE;
path->priority = 5;
path->weight = 1;
path->clear_fc = true;
@@ -825,28 +815,28 @@ static void tb_dma_init_path(struct tb_path *path, unsigned int isb,
* @tb: Pointer to the domain structure
* @nhi: Host controller port
* @dst: Destination null port which the other domain is connected to
- * @transmit_ring: NHI ring number used to send packets towards the
- * other domain. Set to %0 if TX path is not needed.
* @transmit_path: HopID used for transmitting packets
- * @receive_ring: NHI ring number used to receive packets from the
- * other domain. Set to %0 if RX path is not needed.
+ * @transmit_ring: NHI ring number used to send packets towards the
+ * other domain. Set to %-1 if TX path is not needed.
* @receive_path: HopID used for receiving packets
+ * @receive_ring: NHI ring number used to receive packets from the
+ * other domain. Set to %-1 if RX path is not needed.
*
* Return: Returns a tb_tunnel on success or NULL on failure.
*/
struct tb_tunnel *tb_tunnel_alloc_dma(struct tb *tb, struct tb_port *nhi,
- struct tb_port *dst, int transmit_ring,
- int transmit_path, int receive_ring,
- int receive_path)
+ struct tb_port *dst, int transmit_path,
+ int transmit_ring, int receive_path,
+ int receive_ring)
{
struct tb_tunnel *tunnel;
size_t npaths = 0, i = 0;
struct tb_path *path;
u32 credits;
- if (receive_ring)
+ if (receive_ring > 0)
npaths++;
- if (transmit_ring)
+ if (transmit_ring > 0)
npaths++;
if (WARN_ON(!npaths))
@@ -856,38 +846,96 @@ struct tb_tunnel *tb_tunnel_alloc_dma(struct tb *tb, struct tb_port *nhi,
if (!tunnel)
return NULL;
- tunnel->activate = tb_dma_activate;
tunnel->src_port = nhi;
tunnel->dst_port = dst;
credits = tb_dma_credits(nhi);
- if (receive_ring) {
+ if (receive_ring > 0) {
path = tb_path_alloc(tb, dst, receive_path, nhi, receive_ring, 0,
"DMA RX");
if (!path) {
tb_tunnel_free(tunnel);
return NULL;
}
- tb_dma_init_path(path, TB_PATH_NONE, TB_PATH_SOURCE | TB_PATH_INTERNAL,
- credits);
+ tb_dma_init_path(path, TB_PATH_SOURCE | TB_PATH_INTERNAL, credits);
tunnel->paths[i++] = path;
}
- if (transmit_ring) {
+ if (transmit_ring > 0) {
path = tb_path_alloc(tb, nhi, transmit_ring, dst, transmit_path, 0,
"DMA TX");
if (!path) {
tb_tunnel_free(tunnel);
return NULL;
}
- tb_dma_init_path(path, TB_PATH_SOURCE, TB_PATH_ALL, credits);
+ tb_dma_init_path(path, TB_PATH_ALL, credits);
tunnel->paths[i++] = path;
}
return tunnel;
}
+/**
+ * tb_tunnel_match_dma() - Match DMA tunnel
+ * @tunnel: Tunnel to match
+ * @transmit_path: HopID used for transmitting packets. Pass %-1 to ignore.
+ * @transmit_ring: NHI ring number used to send packets towards the
+ * other domain. Pass %-1 to ignore.
+ * @receive_path: HopID used for receiving packets. Pass %-1 to ignore.
+ * @receive_ring: NHI ring number used to receive packets from the
+ * other domain. Pass %-1 to ignore.
+ *
+ * This function can be used to match specific DMA tunnel, if there are
+ * multiple DMA tunnels going through the same XDomain connection.
+ * Returns true if there is match and false otherwise.
+ */
+bool tb_tunnel_match_dma(const struct tb_tunnel *tunnel, int transmit_path,
+ int transmit_ring, int receive_path, int receive_ring)
+{
+ const struct tb_path *tx_path = NULL, *rx_path = NULL;
+ int i;
+
+ if (!receive_ring || !transmit_ring)
+ return false;
+
+ for (i = 0; i < tunnel->npaths; i++) {
+ const struct tb_path *path = tunnel->paths[i];
+
+ if (!path)
+ continue;
+
+ if (tb_port_is_nhi(path->hops[0].in_port))
+ tx_path = path;
+ else if (tb_port_is_nhi(path->hops[path->path_length - 1].out_port))
+ rx_path = path;
+ }
+
+ if (transmit_ring > 0 || transmit_path > 0) {
+ if (!tx_path)
+ return false;
+ if (transmit_ring > 0 &&
+ (tx_path->hops[0].in_hop_index != transmit_ring))
+ return false;
+ if (transmit_path > 0 &&
+ (tx_path->hops[tx_path->path_length - 1].next_hop_index != transmit_path))
+ return false;
+ }
+
+ if (receive_ring > 0 || receive_path > 0) {
+ if (!rx_path)
+ return false;
+ if (receive_path > 0 &&
+ (rx_path->hops[0].in_hop_index != receive_path))
+ return false;
+ if (receive_ring > 0 &&
+ (rx_path->hops[rx_path->path_length - 1].next_hop_index != receive_ring))
+ return false;
+ }
+
+ return true;
+}
+
static int tb_usb3_max_link_rate(struct tb_port *up, struct tb_port *down)
{
int ret, up_max_rate, down_max_rate;
diff --git a/drivers/thunderbolt/tunnel.h b/drivers/thunderbolt/tunnel.h
index 1d2a64eb060d..a66994fb4e60 100644
--- a/drivers/thunderbolt/tunnel.h
+++ b/drivers/thunderbolt/tunnel.h
@@ -70,9 +70,11 @@ struct tb_tunnel *tb_tunnel_alloc_dp(struct tb *tb, struct tb_port *in,
struct tb_port *out, int max_up,
int max_down);
struct tb_tunnel *tb_tunnel_alloc_dma(struct tb *tb, struct tb_port *nhi,
- struct tb_port *dst, int transmit_ring,
- int transmit_path, int receive_ring,
- int receive_path);
+ struct tb_port *dst, int transmit_path,
+ int transmit_ring, int receive_path,
+ int receive_ring);
+bool tb_tunnel_match_dma(const struct tb_tunnel *tunnel, int transmit_path,
+ int transmit_ring, int receive_path, int receive_ring);
struct tb_tunnel *tb_tunnel_discover_usb3(struct tb *tb, struct tb_port *down);
struct tb_tunnel *tb_tunnel_alloc_usb3(struct tb *tb, struct tb_port *up,
struct tb_port *down, int max_up,
diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c
index 7cf8b9c85ab7..b21d99d59412 100644
--- a/drivers/thunderbolt/xdomain.c
+++ b/drivers/thunderbolt/xdomain.c
@@ -12,17 +12,19 @@
#include <linux/kmod.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
+#include <linux/prandom.h>
#include <linux/utsname.h>
#include <linux/uuid.h>
#include <linux/workqueue.h>
#include "tb.h"
-#define XDOMAIN_DEFAULT_TIMEOUT 5000 /* ms */
+#define XDOMAIN_DEFAULT_TIMEOUT 1000 /* ms */
#define XDOMAIN_UUID_RETRIES 10
-#define XDOMAIN_PROPERTIES_RETRIES 60
+#define XDOMAIN_PROPERTIES_RETRIES 10
#define XDOMAIN_PROPERTIES_CHANGED_RETRIES 10
#define XDOMAIN_BONDING_WAIT 100 /* ms */
+#define XDOMAIN_DEFAULT_MAX_HOPID 15
struct xdomain_request_work {
struct work_struct work;
@@ -34,13 +36,15 @@ static bool tb_xdomain_enabled = true;
module_param_named(xdomain, tb_xdomain_enabled, bool, 0444);
MODULE_PARM_DESC(xdomain, "allow XDomain protocol (default: true)");
-/* Serializes access to the properties and protocol handlers below */
+/*
+ * Serializes access to the properties and protocol handlers below. If
+ * you need to take both this lock and the struct tb_xdomain lock, take
+ * this one first.
+ */
static DEFINE_MUTEX(xdomain_lock);
/* Properties exposed to the remote domains */
static struct tb_property_dir *xdomain_property_dir;
-static u32 *xdomain_property_block;
-static u32 xdomain_property_block_len;
static u32 xdomain_property_block_gen;
/* Additional protocol handlers */
@@ -385,8 +389,7 @@ err:
}
static int tb_xdp_properties_response(struct tb *tb, struct tb_ctl *ctl,
- u64 route, u8 sequence, const uuid_t *src_uuid,
- const struct tb_xdp_properties *req)
+ struct tb_xdomain *xd, u8 sequence, const struct tb_xdp_properties *req)
{
struct tb_xdp_properties_response *res;
size_t total_size;
@@ -398,39 +401,39 @@ static int tb_xdp_properties_response(struct tb *tb, struct tb_ctl *ctl,
* protocol supports forwarding, though which we might add
* support later on.
*/
- if (!uuid_equal(src_uuid, &req->dst_uuid)) {
- tb_xdp_error_response(ctl, route, sequence,
+ if (!uuid_equal(xd->local_uuid, &req->dst_uuid)) {
+ tb_xdp_error_response(ctl, xd->route, sequence,
ERROR_UNKNOWN_DOMAIN);
return 0;
}
- mutex_lock(&xdomain_lock);
+ mutex_lock(&xd->lock);
- if (req->offset >= xdomain_property_block_len) {
- mutex_unlock(&xdomain_lock);
+ if (req->offset >= xd->local_property_block_len) {
+ mutex_unlock(&xd->lock);
return -EINVAL;
}
- len = xdomain_property_block_len - req->offset;
+ len = xd->local_property_block_len - req->offset;
len = min_t(u16, len, TB_XDP_PROPERTIES_MAX_DATA_LENGTH);
total_size = sizeof(*res) + len * 4;
res = kzalloc(total_size, GFP_KERNEL);
if (!res) {
- mutex_unlock(&xdomain_lock);
+ mutex_unlock(&xd->lock);
return -ENOMEM;
}
- tb_xdp_fill_header(&res->hdr, route, sequence, PROPERTIES_RESPONSE,
+ tb_xdp_fill_header(&res->hdr, xd->route, sequence, PROPERTIES_RESPONSE,
total_size);
- res->generation = xdomain_property_block_gen;
- res->data_length = xdomain_property_block_len;
+ res->generation = xd->local_property_block_gen;
+ res->data_length = xd->local_property_block_len;
res->offset = req->offset;
- uuid_copy(&res->src_uuid, src_uuid);
+ uuid_copy(&res->src_uuid, xd->local_uuid);
uuid_copy(&res->dst_uuid, &req->src_uuid);
- memcpy(res->data, &xdomain_property_block[req->offset], len * 4);
+ memcpy(res->data, &xd->local_property_block[req->offset], len * 4);
- mutex_unlock(&xdomain_lock);
+ mutex_unlock(&xd->lock);
ret = __tb_xdomain_response(ctl, res, total_size,
TB_CFG_PKG_XDOMAIN_RESP);
@@ -512,52 +515,63 @@ void tb_unregister_protocol_handler(struct tb_protocol_handler *handler)
}
EXPORT_SYMBOL_GPL(tb_unregister_protocol_handler);
-static int rebuild_property_block(void)
+static void update_property_block(struct tb_xdomain *xd)
{
- u32 *block, len;
- int ret;
-
- ret = tb_property_format_dir(xdomain_property_dir, NULL, 0);
- if (ret < 0)
- return ret;
-
- len = ret;
-
- block = kcalloc(len, sizeof(u32), GFP_KERNEL);
- if (!block)
- return -ENOMEM;
+ mutex_lock(&xdomain_lock);
+ mutex_lock(&xd->lock);
+ /*
+ * If the local property block is not up-to-date, rebuild it now
+ * based on the global property template.
+ */
+ if (!xd->local_property_block ||
+ xd->local_property_block_gen < xdomain_property_block_gen) {
+ struct tb_property_dir *dir;
+ int ret, block_len;
+ u32 *block;
+
+ dir = tb_property_copy_dir(xdomain_property_dir);
+ if (!dir) {
+ dev_warn(&xd->dev, "failed to copy properties\n");
+ goto out_unlock;
+ }
- ret = tb_property_format_dir(xdomain_property_dir, block, len);
- if (ret) {
- kfree(block);
- return ret;
- }
+ /* Fill in non-static properties now */
+ tb_property_add_text(dir, "deviceid", utsname()->nodename);
+ tb_property_add_immediate(dir, "maxhopid", xd->local_max_hopid);
- kfree(xdomain_property_block);
- xdomain_property_block = block;
- xdomain_property_block_len = len;
- xdomain_property_block_gen++;
+ ret = tb_property_format_dir(dir, NULL, 0);
+ if (ret < 0) {
+ dev_warn(&xd->dev, "local property block creation failed\n");
+ tb_property_free_dir(dir);
+ goto out_unlock;
+ }
- return 0;
-}
+ block_len = ret;
+ block = kcalloc(block_len, sizeof(*block), GFP_KERNEL);
+ if (!block) {
+ tb_property_free_dir(dir);
+ goto out_unlock;
+ }
-static void finalize_property_block(void)
-{
- const struct tb_property *nodename;
+ ret = tb_property_format_dir(dir, block, block_len);
+ if (ret) {
+ dev_warn(&xd->dev, "property block generation failed\n");
+ tb_property_free_dir(dir);
+ kfree(block);
+ goto out_unlock;
+ }
- /*
- * On first XDomain connection we set up the the system
- * nodename. This delayed here because userspace may not have it
- * set when the driver is first probed.
- */
- mutex_lock(&xdomain_lock);
- nodename = tb_property_find(xdomain_property_dir, "deviceid",
- TB_PROPERTY_TYPE_TEXT);
- if (!nodename) {
- tb_property_add_text(xdomain_property_dir, "deviceid",
- utsname()->nodename);
- rebuild_property_block();
+ tb_property_free_dir(dir);
+ /* Release the previous block */
+ kfree(xd->local_property_block);
+ /* Assign new one */
+ xd->local_property_block = block;
+ xd->local_property_block_len = block_len;
+ xd->local_property_block_gen = xdomain_property_block_gen;
}
+
+out_unlock:
+ mutex_unlock(&xd->lock);
mutex_unlock(&xdomain_lock);
}
@@ -568,6 +582,7 @@ static void tb_xdp_handle_request(struct work_struct *work)
const struct tb_xdomain_header *xhdr = &pkg->xd_hdr;
struct tb *tb = xw->tb;
struct tb_ctl *ctl = tb->ctl;
+ struct tb_xdomain *xd;
const uuid_t *uuid;
int ret = 0;
u32 sequence;
@@ -589,17 +604,21 @@ static void tb_xdp_handle_request(struct work_struct *work)
goto out;
}
- finalize_property_block();
+ tb_dbg(tb, "%llx: received XDomain request %#x\n", route, pkg->type);
+
+ xd = tb_xdomain_find_by_route_locked(tb, route);
+ if (xd)
+ update_property_block(xd);
switch (pkg->type) {
case PROPERTIES_REQUEST:
- ret = tb_xdp_properties_response(tb, ctl, route, sequence, uuid,
- (const struct tb_xdp_properties *)pkg);
+ if (xd) {
+ ret = tb_xdp_properties_response(tb, ctl, xd, sequence,
+ (const struct tb_xdp_properties *)pkg);
+ }
break;
- case PROPERTIES_CHANGED_REQUEST: {
- struct tb_xdomain *xd;
-
+ case PROPERTIES_CHANGED_REQUEST:
ret = tb_xdp_properties_changed_response(ctl, route, sequence);
/*
@@ -607,17 +626,11 @@ static void tb_xdp_handle_request(struct work_struct *work)
* the xdomain related to this connection as well in
* case there is a change in services it offers.
*/
- xd = tb_xdomain_find_by_route_locked(tb, route);
- if (xd) {
- if (device_is_registered(&xd->dev)) {
- queue_delayed_work(tb->wq, &xd->get_properties_work,
- msecs_to_jiffies(50));
- }
- tb_xdomain_put(xd);
+ if (xd && device_is_registered(&xd->dev)) {
+ queue_delayed_work(tb->wq, &xd->get_properties_work,
+ msecs_to_jiffies(50));
}
-
break;
- }
case UUID_REQUEST_OLD:
case UUID_REQUEST:
@@ -630,6 +643,8 @@ static void tb_xdp_handle_request(struct work_struct *work)
break;
}
+ tb_xdomain_put(xd);
+
if (ret) {
tb_warn(tb, "failed to send XDomain response for %#x\n",
pkg->type);
@@ -811,7 +826,7 @@ static int remove_missing_service(struct device *dev, void *data)
if (!svc)
return 0;
- if (!tb_property_find(xd->properties, svc->key,
+ if (!tb_property_find(xd->remote_properties, svc->key,
TB_PROPERTY_TYPE_DIRECTORY))
device_unregister(dev);
@@ -871,7 +886,7 @@ static void enumerate_services(struct tb_xdomain *xd)
device_for_each_child_reverse(&xd->dev, xd, remove_missing_service);
/* Then re-enumerate properties creating new services as we go */
- tb_property_for_each(xd->properties, p) {
+ tb_property_for_each(xd->remote_properties, p) {
if (p->type != TB_PROPERTY_TYPE_DIRECTORY)
continue;
@@ -928,6 +943,14 @@ static int populate_properties(struct tb_xdomain *xd,
return -EINVAL;
xd->vendor = p->value.immediate;
+ p = tb_property_find(dir, "maxhopid", TB_PROPERTY_TYPE_VALUE);
+ /*
+ * USB4 inter-domain spec suggests using 15 as HopID if the
+ * other end does not announce it in a property. This is for
+ * TBT3 compatibility.
+ */
+ xd->remote_max_hopid = p ? p->value.immediate : XDOMAIN_DEFAULT_MAX_HOPID;
+
kfree(xd->device_name);
xd->device_name = NULL;
kfree(xd->vendor_name);
@@ -944,19 +967,6 @@ static int populate_properties(struct tb_xdomain *xd,
return 0;
}
-/* Called with @xd->lock held */
-static void tb_xdomain_restore_paths(struct tb_xdomain *xd)
-{
- if (!xd->resume)
- return;
-
- xd->resume = false;
- if (xd->transmit_path) {
- dev_dbg(&xd->dev, "re-establishing DMA path\n");
- tb_domain_approve_xdomain_paths(xd->tb, xd);
- }
-}
-
static inline struct tb_switch *tb_xdomain_parent(struct tb_xdomain *xd)
{
return tb_to_switch(xd->dev.parent);
@@ -1002,9 +1012,12 @@ static void tb_xdomain_get_uuid(struct work_struct *work)
uuid_t uuid;
int ret;
+ dev_dbg(&xd->dev, "requesting remote UUID\n");
+
ret = tb_xdp_uuid_request(tb->ctl, xd->route, xd->uuid_retries, &uuid);
if (ret < 0) {
if (xd->uuid_retries-- > 0) {
+ dev_dbg(&xd->dev, "failed to request UUID, retrying\n");
queue_delayed_work(xd->tb->wq, &xd->get_uuid_work,
msecs_to_jiffies(100));
} else {
@@ -1013,6 +1026,8 @@ static void tb_xdomain_get_uuid(struct work_struct *work)
return;
}
+ dev_dbg(&xd->dev, "got remote UUID %pUb\n", &uuid);
+
if (uuid_equal(&uuid, xd->local_uuid))
dev_dbg(&xd->dev, "intra-domain loop detected\n");
@@ -1052,11 +1067,15 @@ static void tb_xdomain_get_properties(struct work_struct *work)
u32 gen = 0;
int ret;
+ dev_dbg(&xd->dev, "requesting remote properties\n");
+
ret = tb_xdp_properties_request(tb->ctl, xd->route, xd->local_uuid,
xd->remote_uuid, xd->properties_retries,
&block, &gen);
if (ret < 0) {
if (xd->properties_retries-- > 0) {
+ dev_dbg(&xd->dev,
+ "failed to request remote properties, retrying\n");
queue_delayed_work(xd->tb->wq, &xd->get_properties_work,
msecs_to_jiffies(1000));
} else {
@@ -1073,16 +1092,8 @@ static void tb_xdomain_get_properties(struct work_struct *work)
mutex_lock(&xd->lock);
/* Only accept newer generation properties */
- if (xd->properties && gen <= xd->property_block_gen) {
- /*
- * On resume it is likely that the properties block is
- * not changed (unless the other end added or removed
- * services). However, we need to make sure the existing
- * DMA paths are restored properly.
- */
- tb_xdomain_restore_paths(xd);
+ if (xd->remote_properties && gen <= xd->remote_property_block_gen)
goto err_free_block;
- }
dir = tb_property_parse_dir(block, ret);
if (!dir) {
@@ -1097,18 +1108,16 @@ static void tb_xdomain_get_properties(struct work_struct *work)
}
/* Release the existing one */
- if (xd->properties) {
- tb_property_free_dir(xd->properties);
+ if (xd->remote_properties) {
+ tb_property_free_dir(xd->remote_properties);
update = true;
}
- xd->properties = dir;
- xd->property_block_gen = gen;
+ xd->remote_properties = dir;
+ xd->remote_property_block_gen = gen;
tb_xdomain_update_link_attributes(xd);
- tb_xdomain_restore_paths(xd);
-
mutex_unlock(&xd->lock);
kfree(block);
@@ -1123,6 +1132,11 @@ static void tb_xdomain_get_properties(struct work_struct *work)
dev_err(&xd->dev, "failed to add XDomain device\n");
return;
}
+ dev_info(&xd->dev, "new host found, vendor=%#x device=%#x\n",
+ xd->vendor, xd->device);
+ if (xd->vendor_name && xd->device_name)
+ dev_info(&xd->dev, "%s %s\n", xd->vendor_name,
+ xd->device_name);
} else {
kobject_uevent(&xd->dev.kobj, KOBJ_CHANGE);
}
@@ -1143,13 +1157,19 @@ static void tb_xdomain_properties_changed(struct work_struct *work)
properties_changed_work.work);
int ret;
+ dev_dbg(&xd->dev, "sending properties changed notification\n");
+
ret = tb_xdp_properties_changed_request(xd->tb->ctl, xd->route,
xd->properties_changed_retries, xd->local_uuid);
if (ret) {
- if (xd->properties_changed_retries-- > 0)
+ if (xd->properties_changed_retries-- > 0) {
+ dev_dbg(&xd->dev,
+ "failed to send properties changed notification, retrying\n");
queue_delayed_work(xd->tb->wq,
&xd->properties_changed_work,
msecs_to_jiffies(1000));
+ }
+ dev_err(&xd->dev, "failed to send properties changed notification\n");
return;
}
@@ -1180,6 +1200,15 @@ device_name_show(struct device *dev, struct device_attribute *attr, char *buf)
}
static DEVICE_ATTR_RO(device_name);
+static ssize_t maxhopid_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct tb_xdomain *xd = container_of(dev, struct tb_xdomain, dev);
+
+ return sprintf(buf, "%d\n", xd->remote_max_hopid);
+}
+static DEVICE_ATTR_RO(maxhopid);
+
static ssize_t vendor_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
@@ -1238,6 +1267,7 @@ static DEVICE_ATTR(tx_lanes, 0444, lanes_show, NULL);
static struct attribute *xdomain_attrs[] = {
&dev_attr_device.attr,
&dev_attr_device_name.attr,
+ &dev_attr_maxhopid.attr,
&dev_attr_rx_lanes.attr,
&dev_attr_rx_speed.attr,
&dev_attr_tx_lanes.attr,
@@ -1263,7 +1293,10 @@ static void tb_xdomain_release(struct device *dev)
put_device(xd->dev.parent);
- tb_property_free_dir(xd->properties);
+ kfree(xd->local_property_block);
+ tb_property_free_dir(xd->remote_properties);
+ ida_destroy(&xd->out_hopids);
+ ida_destroy(&xd->in_hopids);
ida_destroy(&xd->service_ids);
kfree(xd->local_uuid);
@@ -1310,15 +1343,7 @@ static int __maybe_unused tb_xdomain_suspend(struct device *dev)
static int __maybe_unused tb_xdomain_resume(struct device *dev)
{
- struct tb_xdomain *xd = tb_to_xdomain(dev);
-
- /*
- * Ask tb_xdomain_get_properties() restore any existing DMA
- * paths after properties are re-read.
- */
- xd->resume = true;
- start_handshake(xd);
-
+ start_handshake(tb_to_xdomain(dev));
return 0;
}
@@ -1363,7 +1388,10 @@ struct tb_xdomain *tb_xdomain_alloc(struct tb *tb, struct device *parent,
xd->tb = tb;
xd->route = route;
+ xd->local_max_hopid = down->config.max_in_hop_id;
ida_init(&xd->service_ids);
+ ida_init(&xd->in_hopids);
+ ida_init(&xd->out_hopids);
mutex_init(&xd->lock);
INIT_DELAYED_WORK(&xd->get_uuid_work, tb_xdomain_get_uuid);
INIT_DELAYED_WORK(&xd->get_properties_work, tb_xdomain_get_properties);
@@ -1390,6 +1418,10 @@ struct tb_xdomain *tb_xdomain_alloc(struct tb *tb, struct device *parent,
xd->dev.groups = xdomain_attr_groups;
dev_set_name(&xd->dev, "%u-%llx", tb->index, route);
+ dev_dbg(&xd->dev, "local UUID %pUb\n", local_uuid);
+ if (remote_uuid)
+ dev_dbg(&xd->dev, "remote UUID %pUb\n", remote_uuid);
+
/*
* This keeps the DMA powered on as long as we have active
* connection to another host.
@@ -1452,10 +1484,12 @@ void tb_xdomain_remove(struct tb_xdomain *xd)
pm_runtime_put_noidle(&xd->dev);
pm_runtime_set_suspended(&xd->dev);
- if (!device_is_registered(&xd->dev))
+ if (!device_is_registered(&xd->dev)) {
put_device(&xd->dev);
- else
+ } else {
+ dev_info(&xd->dev, "host disconnected\n");
device_unregister(&xd->dev);
+ }
}
/**
@@ -1523,73 +1557,118 @@ void tb_xdomain_lane_bonding_disable(struct tb_xdomain *xd)
EXPORT_SYMBOL_GPL(tb_xdomain_lane_bonding_disable);
/**
- * tb_xdomain_enable_paths() - Enable DMA paths for XDomain connection
+ * tb_xdomain_alloc_in_hopid() - Allocate input HopID for tunneling
* @xd: XDomain connection
- * @transmit_path: HopID of the transmit path the other end is using to
- * send packets
- * @transmit_ring: DMA ring used to receive packets from the other end
- * @receive_path: HopID of the receive path the other end is using to
- * receive packets
- * @receive_ring: DMA ring used to send packets to the other end
+ * @hopid: Preferred HopID or %-1 for next available
*
- * The function enables DMA paths accordingly so that after successful
- * return the caller can send and receive packets using high-speed DMA
- * path.
- *
- * Return: %0 in case of success and negative errno in case of error
+ * Returns allocated HopID or negative errno. Specifically returns
+ * %-ENOSPC if there are no more available HopIDs. Returned HopID is
+ * guaranteed to be within range supported by the input lane adapter.
+ * Call tb_xdomain_release_in_hopid() to release the allocated HopID.
*/
-int tb_xdomain_enable_paths(struct tb_xdomain *xd, u16 transmit_path,
- u16 transmit_ring, u16 receive_path,
- u16 receive_ring)
+int tb_xdomain_alloc_in_hopid(struct tb_xdomain *xd, int hopid)
{
- int ret;
+ if (hopid < 0)
+ hopid = TB_PATH_MIN_HOPID;
+ if (hopid < TB_PATH_MIN_HOPID || hopid > xd->local_max_hopid)
+ return -EINVAL;
- mutex_lock(&xd->lock);
+ return ida_alloc_range(&xd->in_hopids, hopid, xd->local_max_hopid,
+ GFP_KERNEL);
+}
+EXPORT_SYMBOL_GPL(tb_xdomain_alloc_in_hopid);
- if (xd->transmit_path) {
- ret = xd->transmit_path == transmit_path ? 0 : -EBUSY;
- goto exit_unlock;
- }
+/**
+ * tb_xdomain_alloc_out_hopid() - Allocate output HopID for tunneling
+ * @xd: XDomain connection
+ * @hopid: Preferred HopID or %-1 for next available
+ *
+ * Returns allocated HopID or negative errno. Specifically returns
+ * %-ENOSPC if there are no more available HopIDs. Returned HopID is
+ * guaranteed to be within range supported by the output lane adapter.
+ * Call tb_xdomain_release_in_hopid() to release the allocated HopID.
+ */
+int tb_xdomain_alloc_out_hopid(struct tb_xdomain *xd, int hopid)
+{
+ if (hopid < 0)
+ hopid = TB_PATH_MIN_HOPID;
+ if (hopid < TB_PATH_MIN_HOPID || hopid > xd->remote_max_hopid)
+ return -EINVAL;
- xd->transmit_path = transmit_path;
- xd->transmit_ring = transmit_ring;
- xd->receive_path = receive_path;
- xd->receive_ring = receive_ring;
+ return ida_alloc_range(&xd->out_hopids, hopid, xd->remote_max_hopid,
+ GFP_KERNEL);
+}
+EXPORT_SYMBOL_GPL(tb_xdomain_alloc_out_hopid);
- ret = tb_domain_approve_xdomain_paths(xd->tb, xd);
+/**
+ * tb_xdomain_release_in_hopid() - Release input HopID
+ * @xd: XDomain connection
+ * @hopid: HopID to release
+ */
+void tb_xdomain_release_in_hopid(struct tb_xdomain *xd, int hopid)
+{
+ ida_free(&xd->in_hopids, hopid);
+}
+EXPORT_SYMBOL_GPL(tb_xdomain_release_in_hopid);
-exit_unlock:
- mutex_unlock(&xd->lock);
+/**
+ * tb_xdomain_release_out_hopid() - Release output HopID
+ * @xd: XDomain connection
+ * @hopid: HopID to release
+ */
+void tb_xdomain_release_out_hopid(struct tb_xdomain *xd, int hopid)
+{
+ ida_free(&xd->out_hopids, hopid);
+}
+EXPORT_SYMBOL_GPL(tb_xdomain_release_out_hopid);
- return ret;
+/**
+ * tb_xdomain_enable_paths() - Enable DMA paths for XDomain connection
+ * @xd: XDomain connection
+ * @transmit_path: HopID we are using to send out packets
+ * @transmit_ring: DMA ring used to send out packets
+ * @receive_path: HopID the other end is using to send packets to us
+ * @receive_ring: DMA ring used to receive packets from @receive_path
+ *
+ * The function enables DMA paths accordingly so that after successful
+ * return the caller can send and receive packets using high-speed DMA
+ * path. If a transmit or receive path is not needed, pass %-1 for those
+ * parameters.
+ *
+ * Return: %0 in case of success and negative errno in case of error
+ */
+int tb_xdomain_enable_paths(struct tb_xdomain *xd, int transmit_path,
+ int transmit_ring, int receive_path,
+ int receive_ring)
+{
+ return tb_domain_approve_xdomain_paths(xd->tb, xd, transmit_path,
+ transmit_ring, receive_path,
+ receive_ring);
}
EXPORT_SYMBOL_GPL(tb_xdomain_enable_paths);
/**
* tb_xdomain_disable_paths() - Disable DMA paths for XDomain connection
* @xd: XDomain connection
+ * @transmit_path: HopID we are using to send out packets
+ * @transmit_ring: DMA ring used to send out packets
+ * @receive_path: HopID the other end is using to send packets to us
+ * @receive_ring: DMA ring used to receive packets from @receive_path
*
* This does the opposite of tb_xdomain_enable_paths(). After call to
- * this the caller is not expected to use the rings anymore.
+ * this the caller is not expected to use the rings anymore. Passing %-1
+ * as path/ring parameter means don't care. Normally the callers should
+ * pass the same values here as they do when paths are enabled.
*
* Return: %0 in case of success and negative errno in case of error
*/
-int tb_xdomain_disable_paths(struct tb_xdomain *xd)
+int tb_xdomain_disable_paths(struct tb_xdomain *xd, int transmit_path,
+ int transmit_ring, int receive_path,
+ int receive_ring)
{
- int ret = 0;
-
- mutex_lock(&xd->lock);
- if (xd->transmit_path) {
- xd->transmit_path = 0;
- xd->transmit_ring = 0;
- xd->receive_path = 0;
- xd->receive_ring = 0;
-
- ret = tb_domain_disconnect_xdomain_paths(xd->tb, xd);
- }
- mutex_unlock(&xd->lock);
-
- return ret;
+ return tb_domain_disconnect_xdomain_paths(xd->tb, xd, transmit_path,
+ transmit_ring, receive_path,
+ receive_ring);
}
EXPORT_SYMBOL_GPL(tb_xdomain_disable_paths);
@@ -1826,11 +1905,7 @@ int tb_register_property_dir(const char *key, struct tb_property_dir *dir)
if (ret)
goto err_unlock;
- ret = rebuild_property_block();
- if (ret) {
- remove_directory(key, dir);
- goto err_unlock;
- }
+ xdomain_property_block_gen++;
mutex_unlock(&xdomain_lock);
update_all_xdomains();
@@ -1856,7 +1931,7 @@ void tb_unregister_property_dir(const char *key, struct tb_property_dir *dir)
mutex_lock(&xdomain_lock);
if (remove_directory(key, dir))
- ret = rebuild_property_block();
+ xdomain_property_block_gen++;
mutex_unlock(&xdomain_lock);
if (!ret)
@@ -1875,7 +1950,8 @@ int tb_xdomain_init(void)
* directories. Those will be added by service drivers
* themselves when they are loaded.
*
- * We also add node name later when first connection is made.
+ * Rest of the properties are filled dynamically based on these
+ * when the P2P connection is made.
*/
tb_property_add_immediate(xdomain_property_dir, "vendorid",
PCI_VENDOR_ID_INTEL);
@@ -1883,11 +1959,11 @@ int tb_xdomain_init(void)
tb_property_add_immediate(xdomain_property_dir, "deviceid", 0x1);
tb_property_add_immediate(xdomain_property_dir, "devicerv", 0x80000100);
+ xdomain_property_block_gen = prandom_u32();
return 0;
}
void tb_xdomain_exit(void)
{
- kfree(xdomain_property_block);
tb_property_free_dir(xdomain_property_dir);
}