Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

thunderbolt: Add support for XDomain connections

Two domains (hosts) can be connected through a Thunderbolt cable and in
that case they can start software services such as networking over the
high-speed DMA paths. Now that we have all the basic building blocks in
place to create DMA tunnels over the Thunderbolt fabric we can add this
support to the software connection manager as well.

Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>

+188 -8
+25 -4
drivers/thunderbolt/switch.c
··· 1845 1845 for (i = 0; i <= sw->config.max_port_number; i++) { 1846 1846 if (tb_port_has_remote(&sw->ports[i])) 1847 1847 tb_sw_set_unplugged(sw->ports[i].remote->sw); 1848 + else if (sw->ports[i].xdomain) 1849 + sw->ports[i].xdomain->is_unplugged = true; 1848 1850 } 1849 1851 } 1850 1852 ··· 1861 1859 */ 1862 1860 if (tb_route(sw)) { 1863 1861 u64 uid; 1862 + 1863 + /* 1864 + * Check first that we can still read the switch config 1865 + * space. It may be that there is now another domain 1866 + * connected. 1867 + */ 1868 + err = tb_cfg_get_upstream_port(sw->tb->ctl, tb_route(sw)); 1869 + if (err < 0) { 1870 + tb_sw_info(sw, "switch not present anymore\n"); 1871 + return err; 1872 + } 1864 1873 1865 1874 err = tb_drom_read_uid_only(sw, &uid); 1866 1875 if (err) { ··· 1903 1890 for (i = 1; i <= sw->config.max_port_number; i++) { 1904 1891 struct tb_port *port = &sw->ports[i]; 1905 1892 1906 - if (!tb_port_has_remote(port)) 1893 + if (!tb_port_has_remote(port) && !port->xdomain) 1907 1894 continue; 1908 1895 1909 - if (tb_wait_for_port(port, true) <= 0 1910 - || tb_switch_resume(port->remote->sw)) { 1896 + if (tb_wait_for_port(port, true) <= 0) { 1911 1897 tb_port_warn(port, 1912 1898 "lost during suspend, disconnecting\n"); 1913 - tb_sw_set_unplugged(port->remote->sw); 1899 + if (tb_port_has_remote(port)) 1900 + tb_sw_set_unplugged(port->remote->sw); 1901 + else if (port->xdomain) 1902 + port->xdomain->is_unplugged = true; 1903 + } else if (tb_port_has_remote(port)) { 1904 + if (tb_switch_resume(port->remote->sw)) { 1905 + tb_port_warn(port, 1906 + "lost during suspend, disconnecting\n"); 1907 + tb_sw_set_unplugged(port->remote->sw); 1908 + } 1914 1909 } 1915 1910 } 1916 1911 return 0;
+163 -4
drivers/thunderbolt/tb.c
··· 101 101 } 102 102 } 103 103 104 + static void tb_scan_xdomain(struct tb_port *port) 105 + { 106 + struct tb_switch *sw = port->sw; 107 + struct tb *tb = sw->tb; 108 + struct tb_xdomain *xd; 109 + u64 route; 110 + 111 + route = tb_downstream_route(port); 112 + xd = tb_xdomain_find_by_route(tb, route); 113 + if (xd) { 114 + tb_xdomain_put(xd); 115 + return; 116 + } 117 + 118 + xd = tb_xdomain_alloc(tb, &sw->dev, route, tb->root_switch->uuid, 119 + NULL); 120 + if (xd) { 121 + tb_port_at(route, sw)->xdomain = xd; 122 + tb_xdomain_add(xd); 123 + } 124 + } 125 + 104 126 static void tb_scan_port(struct tb_port *port); 105 127 106 128 /** ··· 165 143 if (tb_wait_for_port(port, false) <= 0) 166 144 return; 167 145 if (port->remote) { 168 - tb_port_WARN(port, "port already has a remote!\n"); 146 + tb_port_dbg(port, "port already has a remote\n"); 169 147 return; 170 148 } 171 149 sw = tb_switch_alloc(port->sw->tb, &port->sw->dev, 172 150 tb_downstream_route(port)); 173 - if (IS_ERR(sw)) 151 + if (IS_ERR(sw)) { 152 + /* 153 + * If there is an error accessing the connected switch 154 + * it may be connected to another domain. Also we allow 155 + * the other domain to be connected to a max depth switch. 156 + */ 157 + if (PTR_ERR(sw) == -EIO || PTR_ERR(sw) == -EADDRNOTAVAIL) 158 + tb_scan_xdomain(port); 174 159 return; 160 + } 175 161 176 162 if (tb_switch_configure(sw)) { 177 163 tb_switch_put(sw); 178 164 return; 165 + } 166 + 167 + /* 168 + * If there was previously another domain connected remove it 169 + * first. 170 + */ 171 + if (port->xdomain) { 172 + tb_xdomain_remove(port->xdomain); 173 + port->xdomain = NULL; 179 174 } 180 175 181 176 /* ··· 432 393 return 0; 433 394 } 434 395 396 + static int tb_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd) 397 + { 398 + struct tb_cm *tcm = tb_priv(tb); 399 + struct tb_port *nhi_port, *dst_port; 400 + struct tb_tunnel *tunnel; 401 + struct tb_switch *sw; 402 + 403 + sw = tb_to_switch(xd->dev.parent); 404 + dst_port = tb_port_at(xd->route, sw); 405 + nhi_port = tb_find_port(tb->root_switch, TB_TYPE_NHI); 406 + 407 + mutex_lock(&tb->lock); 408 + tunnel = tb_tunnel_alloc_dma(tb, nhi_port, dst_port, xd->transmit_ring, 409 + xd->transmit_path, xd->receive_ring, 410 + xd->receive_path); 411 + if (!tunnel) { 412 + mutex_unlock(&tb->lock); 413 + return -ENOMEM; 414 + } 415 + 416 + if (tb_tunnel_activate(tunnel)) { 417 + tb_port_info(nhi_port, 418 + "DMA tunnel activation failed, aborting\n"); 419 + tb_tunnel_free(tunnel); 420 + mutex_unlock(&tb->lock); 421 + return -EIO; 422 + } 423 + 424 + list_add_tail(&tunnel->list, &tcm->tunnel_list); 425 + mutex_unlock(&tb->lock); 426 + return 0; 427 + } 428 + 429 + static void __tb_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd) 430 + { 431 + struct tb_port *dst_port; 432 + struct tb_switch *sw; 433 + 434 + sw = tb_to_switch(xd->dev.parent); 435 + dst_port = tb_port_at(xd->route, sw); 436 + 437 + /* 438 + * It is possible that the tunnel was already teared down (in 439 + * case of cable disconnect) so it is fine if we cannot find it 440 + * here anymore. 441 + */ 442 + tb_free_tunnel(tb, TB_TUNNEL_DMA, NULL, dst_port); 443 + } 444 + 445 + static int tb_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd) 446 + { 447 + if (!xd->is_unplugged) { 448 + mutex_lock(&tb->lock); 449 + __tb_disconnect_xdomain_paths(tb, xd); 450 + mutex_unlock(&tb->lock); 451 + } 452 + return 0; 453 + } 454 + 435 455 /* hotplug handling */ 436 456 437 457 /** ··· 530 432 } 531 433 if (ev->unplug) { 532 434 if (tb_port_has_remote(port)) { 533 - tb_port_info(port, "unplugged\n"); 435 + tb_port_dbg(port, "switch unplugged\n"); 534 436 tb_sw_set_unplugged(port->remote->sw); 535 437 tb_free_invalid_tunnels(tb); 536 438 tb_switch_remove(port->remote->sw); 537 439 port->remote = NULL; 538 440 if (port->dual_link_port) 539 441 port->dual_link_port->remote = NULL; 442 + } else if (port->xdomain) { 443 + struct tb_xdomain *xd = tb_xdomain_get(port->xdomain); 444 + 445 + tb_port_dbg(port, "xdomain unplugged\n"); 446 + /* 447 + * Service drivers are unbound during 448 + * tb_xdomain_remove() so setting XDomain as 449 + * unplugged here prevents deadlock if they call 450 + * tb_xdomain_disable_paths(). We will tear down 451 + * the path below. 452 + */ 453 + xd->is_unplugged = true; 454 + tb_xdomain_remove(xd); 455 + port->xdomain = NULL; 456 + __tb_disconnect_xdomain_paths(tb, xd); 457 + tb_xdomain_put(xd); 540 458 } else if (tb_port_is_dpout(port)) { 541 459 tb_teardown_dp(tb, port); 542 460 } else { ··· 614 500 struct tb_tunnel *n; 615 501 616 502 /* tunnels are only present after everything has been initialized */ 617 - list_for_each_entry_safe(tunnel, n, &tcm->tunnel_list, list) 503 + list_for_each_entry_safe(tunnel, n, &tcm->tunnel_list, list) { 504 + /* 505 + * DMA tunnels require the driver to be functional so we 506 + * tear them down. Other protocol tunnels can be left 507 + * intact. 508 + */ 509 + if (tb_tunnel_is_dma(tunnel)) 510 + tb_tunnel_deactivate(tunnel); 618 511 tb_tunnel_free(tunnel); 512 + } 619 513 tb_switch_remove(tb->root_switch); 620 514 tcm->hotplug_active = false; /* signal tb_handle_hotplug to quit */ 621 515 } ··· 733 611 return 0; 734 612 } 735 613 614 + static int tb_free_unplugged_xdomains(struct tb_switch *sw) 615 + { 616 + int i, ret = 0; 617 + 618 + for (i = 1; i <= sw->config.max_port_number; i++) { 619 + struct tb_port *port = &sw->ports[i]; 620 + 621 + if (tb_is_upstream_port(port)) 622 + continue; 623 + if (port->xdomain && port->xdomain->is_unplugged) { 624 + tb_xdomain_remove(port->xdomain); 625 + port->xdomain = NULL; 626 + ret++; 627 + } else if (port->remote) { 628 + ret += tb_free_unplugged_xdomains(port->remote->sw); 629 + } 630 + } 631 + 632 + return ret; 633 + } 634 + 635 + static void tb_complete(struct tb *tb) 636 + { 637 + /* 638 + * Release any unplugged XDomains and if there is a case where 639 + * another domain is swapped in place of unplugged XDomain we 640 + * need to run another rescan. 641 + */ 642 + mutex_lock(&tb->lock); 643 + if (tb_free_unplugged_xdomains(tb->root_switch)) 644 + tb_scan_switch(tb->root_switch); 645 + mutex_unlock(&tb->lock); 646 + } 647 + 736 648 static const struct tb_cm_ops tb_cm_ops = { 737 649 .start = tb_start, 738 650 .stop = tb_stop, 739 651 .suspend_noirq = tb_suspend_noirq, 740 652 .resume_noirq = tb_resume_noirq, 653 + .complete = tb_complete, 741 654 .handle_event = tb_handle_event, 742 655 .approve_switch = tb_tunnel_pci, 656 + .approve_xdomain_paths = tb_approve_xdomain_paths, 657 + .disconnect_xdomain_paths = tb_disconnect_xdomain_paths, 743 658 }; 744 659 745 660 struct tb *tb_probe(struct tb_nhi *nhi)