thunderbolt: Changes for v5.13 merge window

This includes following Thunderbolt/USB4 changes for v5.13 merge window:
 
   * Debugfs improvements
 
   * Align the inter-domain (peer-to-peer) support with the USB4
     inter-domain spec for better interoperability
 
   * Add support for USB4 DROM and the new product descriptor
 
   * More KUnit tests
 
   * Detailed uevent for routers
 
   * Few miscellaneous improvements
 
 All these have been in linux-next without reported issues.
 -----BEGIN PGP SIGNATURE-----
 
 iQJUBAABCgA+FiEEVTdhRGBbNzLrSUBaAP2fSd+ZWKAFAmB1aWcgHG1pa2Eud2Vz
 dGVyYmVyZ0BsaW51eC5pbnRlbC5jb20ACgkQAP2fSd+ZWKB01g/+N8mjL2V4Bvfi
 uKQJGsuK+5Bbg1x51Hx8tjxG2I75QMzImoikYJlq99ttPC/+/sBJeXbfPp4yZZQ8
 9dxKoXlAu7Ij43/5VNwb9IhMsCktzTsLEsOS0brp4xS5zLAnTKhps4sM8h8pcSgF
 IYcNs0thBXoGL9dOOKfRjst4Zs+ksH4wE/FSS581y2dVu3jzqEYRM0O+hSWt6Ekz
 UJrt1DJ63coFKqHdHBG2Vnxj1q3+f5S+crGgjVvoDqVLBeur9JGuenllxKfgtJ9k
 jKW9pm5P4F5k6l4t7SPk7y10GHmif8JnCJ4zZX4mb29CrgdtG3+P92stkwCWaP1b
 yxeLuWv3lOWJrDKdt29cC4tKGCMM3HTsVGOdFHI8IaLOodazMHFXonKSrlaFBRBX
 eP94i4OrgR6lmuBD2w5u15LosNJSr7TeCmvDakKW90S3jVohDSS4AfVw4euk1cdu
 k0AIJYzmoADdfxY4bHVJ8ZdmUQoRHOMMCpUIAc+nQiP8bGHh9sVx+SRqbEA2BZu1
 BrJ5x/zi72We10SySNzMW9l2CL9PTDUIKtebLov0F5kiCFGTGQOqLU8ps3A+r6sR
 GB0Dk9mjyozNE88EhjxquzbStm1Mxf2ahvlGSntEk0+5trdbHHK7axPb6ormcQVX
 W66XT9bGQ0wyb+qvxY6D/X7lrk3sXXc=
 =RzJl
 -----END PGP SIGNATURE-----

Merge tag 'thunderbolt-for-v5.13-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/westeri/thunderbolt into usb-next

Mika writes:

thunderbolt: Changes for v5.13 merge window

This includes following Thunderbolt/USB4 changes for v5.13 merge window:

  * Debugfs improvements

  * Align the inter-domain (peer-to-peer) support with the USB4
    inter-domain spec for better interoperability

  * Add support for USB4 DROM and the new product descriptor

  * More KUnit tests

  * Detailed uevent for routers

  * Few miscellaneous improvements

All these have been in linux-next without reported issues.

* tag 'thunderbolt-for-v5.13-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/westeri/thunderbolt: (24 commits)
  thunderbolt: Hide authorized attribute if router does not support PCIe tunnels
  thunderbolt: Add details to router uevent
  thunderbolt: Unlock on error path in tb_domain_add()
  thunderbolt: Add support for USB4 DROM
  thunderbolt: Check quirks in tb_switch_add()
  thunderbolt: Add KUnit tests for DMA tunnels
  thunderbolt: Add KUnit tests for XDomain properties
  net: thunderbolt: Align the driver to the USB4 networking spec
  thunderbolt: Allow multiple DMA tunnels over a single XDomain connection
  thunderbolt: Drop unused tb_port_set_initial_credits()
  thunderbolt: Use dedicated flow control for DMA tunnels
  thunderbolt: Add support for maxhopid XDomain property
  thunderbolt: Add tb_property_copy_dir()
  thunderbolt: Align XDomain protocol timeouts with the spec
  thunderbolt: Use pseudo-random number as initial property block generation
  thunderbolt: Do not re-establish XDomain DMA paths automatically
  thunderbolt: Add more logging to XDomain connections
  Documentation / thunderbolt: Drop speed/lanes entries for XDomain
  thunderbolt: Decrease control channel timeout for software connection manager
  thunderbolt: Do not pass timeout for tb_cfg_reset()
  ...
This commit is contained in:
Greg Kroah-Hartman 2021-04-13 12:17:14 +02:00
commit 5367f82a21
18 changed files with 1297 additions and 448 deletions

View File

@ -1,31 +1,3 @@
What: /sys/bus/thunderbolt/devices/<xdomain>/rx_speed
Date: Feb 2021
KernelVersion: 5.11
Contact: Isaac Hazan <isaac.hazan@intel.com>
Description: This attribute reports the XDomain RX speed per lane.
All RX lanes run at the same speed.
What: /sys/bus/thunderbolt/devices/<xdomain>/rx_lanes
Date: Feb 2021
KernelVersion: 5.11
Contact: Isaac Hazan <isaac.hazan@intel.com>
Description: This attribute reports the number of RX lanes the XDomain
is using simultaneously through its upstream port.
What: /sys/bus/thunderbolt/devices/<xdomain>/tx_speed
Date: Feb 2021
KernelVersion: 5.11
Contact: Isaac Hazan <isaac.hazan@intel.com>
Description: This attribute reports the XDomain TX speed per lane.
All TX lanes run at the same speed.
What: /sys/bus/thunderbolt/devices/<xdomain>/tx_lanes
Date: Feb 2021
KernelVersion: 5.11
Contact: Isaac Hazan <isaac.hazan@intel.com>
Description: This attribute reports number of TX lanes the XDomain
is using simultaneously through its upstream port.
What: /sys/bus/thunderbolt/devices/.../domainX/boot_acl
Date: Jun 2018
KernelVersion: 4.17
@ -162,6 +134,13 @@ Contact: thunderbolt-software@lists.01.org
Description: This attribute contains name of this device extracted from
the device DROM.
What: /sys/bus/thunderbolt/devices/.../maxhopid
Date: Jul 2021
KernelVersion: 5.13
Contact: Mika Westerberg <mika.westerberg@linux.intel.com>
Description: Only set for XDomains. The maximum HopID the other host
supports as its input HopID.
What: /sys/bus/thunderbolt/devices/.../rx_speed
Date: Jan 2020
KernelVersion: 5.5

View File

@ -25,13 +25,13 @@
/* Protocol timeouts in ms */
#define TBNET_LOGIN_DELAY 4500
#define TBNET_LOGIN_TIMEOUT 500
#define TBNET_LOGOUT_TIMEOUT 100
#define TBNET_LOGOUT_TIMEOUT 1000
#define TBNET_RING_SIZE 256
#define TBNET_LOCAL_PATH 0xf
#define TBNET_LOGIN_RETRIES 60
#define TBNET_LOGOUT_RETRIES 5
#define TBNET_LOGOUT_RETRIES 10
#define TBNET_MATCH_FRAGS_ID BIT(1)
#define TBNET_64K_FRAMES BIT(2)
#define TBNET_MAX_MTU SZ_64K
#define TBNET_FRAME_SIZE SZ_4K
#define TBNET_MAX_PAYLOAD_SIZE \
@ -154,8 +154,8 @@ struct tbnet_ring {
* @login_sent: ThunderboltIP login message successfully sent
* @login_received: ThunderboltIP login message received from the remote
* host
* @transmit_path: HopID the other end needs to use building the
* opposite side path.
* @local_transmit_path: HopID we are using to send out packets
* @remote_transmit_path: HopID the other end is using to send packets to us
* @connection_lock: Lock serializing access to @login_sent,
* @login_received and @transmit_path.
* @login_retries: Number of login retries currently done
@ -184,7 +184,8 @@ struct tbnet {
atomic_t command_id;
bool login_sent;
bool login_received;
u32 transmit_path;
int local_transmit_path;
int remote_transmit_path;
struct mutex connection_lock;
int login_retries;
struct delayed_work login_work;
@ -257,7 +258,7 @@ static int tbnet_login_request(struct tbnet *net, u8 sequence)
atomic_inc_return(&net->command_id));
request.proto_version = TBIP_LOGIN_PROTO_VERSION;
request.transmit_path = TBNET_LOCAL_PATH;
request.transmit_path = net->local_transmit_path;
return tb_xdomain_request(xd, &request, sizeof(request),
TB_CFG_PKG_XDOMAIN_RESP, &reply,
@ -364,10 +365,10 @@ static void tbnet_tear_down(struct tbnet *net, bool send_logout)
mutex_lock(&net->connection_lock);
if (net->login_sent && net->login_received) {
int retries = TBNET_LOGOUT_RETRIES;
int ret, retries = TBNET_LOGOUT_RETRIES;
while (send_logout && retries-- > 0) {
int ret = tbnet_logout_request(net);
ret = tbnet_logout_request(net);
if (ret != -ETIMEDOUT)
break;
}
@ -377,8 +378,16 @@ static void tbnet_tear_down(struct tbnet *net, bool send_logout)
tbnet_free_buffers(&net->rx_ring);
tbnet_free_buffers(&net->tx_ring);
if (tb_xdomain_disable_paths(net->xd))
ret = tb_xdomain_disable_paths(net->xd,
net->local_transmit_path,
net->rx_ring.ring->hop,
net->remote_transmit_path,
net->tx_ring.ring->hop);
if (ret)
netdev_warn(net->dev, "failed to disable DMA paths\n");
tb_xdomain_release_in_hopid(net->xd, net->remote_transmit_path);
net->remote_transmit_path = 0;
}
net->login_retries = 0;
@ -424,7 +433,7 @@ static int tbnet_handle_packet(const void *buf, size_t size, void *data)
if (!ret) {
mutex_lock(&net->connection_lock);
net->login_received = true;
net->transmit_path = pkg->transmit_path;
net->remote_transmit_path = pkg->transmit_path;
/* If we reached the number of max retries or
* previous logout, schedule another round of
@ -597,12 +606,18 @@ static void tbnet_connected_work(struct work_struct *work)
if (!connected)
return;
ret = tb_xdomain_alloc_in_hopid(net->xd, net->remote_transmit_path);
if (ret != net->remote_transmit_path) {
netdev_err(net->dev, "failed to allocate Rx HopID\n");
return;
}
/* Both logins successful so enable the high-speed DMA paths and
* start the network device queue.
*/
ret = tb_xdomain_enable_paths(net->xd, TBNET_LOCAL_PATH,
ret = tb_xdomain_enable_paths(net->xd, net->local_transmit_path,
net->rx_ring.ring->hop,
net->transmit_path,
net->remote_transmit_path,
net->tx_ring.ring->hop);
if (ret) {
netdev_err(net->dev, "failed to enable DMA paths\n");
@ -629,6 +644,7 @@ err_free_rx_buffers:
err_stop_rings:
tb_ring_stop(net->rx_ring.ring);
tb_ring_stop(net->tx_ring.ring);
tb_xdomain_release_in_hopid(net->xd, net->remote_transmit_path);
}
static void tbnet_login_work(struct work_struct *work)
@ -851,6 +867,7 @@ static int tbnet_open(struct net_device *dev)
struct tb_xdomain *xd = net->xd;
u16 sof_mask, eof_mask;
struct tb_ring *ring;
int hopid;
netif_carrier_off(dev);
@ -862,6 +879,15 @@ static int tbnet_open(struct net_device *dev)
}
net->tx_ring.ring = ring;
hopid = tb_xdomain_alloc_out_hopid(xd, -1);
if (hopid < 0) {
netdev_err(dev, "failed to allocate Tx HopID\n");
tb_ring_free(net->tx_ring.ring);
net->tx_ring.ring = NULL;
return hopid;
}
net->local_transmit_path = hopid;
sof_mask = BIT(TBIP_PDF_FRAME_START);
eof_mask = BIT(TBIP_PDF_FRAME_END);
@ -893,6 +919,8 @@ static int tbnet_stop(struct net_device *dev)
tb_ring_free(net->rx_ring.ring);
net->rx_ring.ring = NULL;
tb_xdomain_release_out_hopid(net->xd, net->local_transmit_path);
tb_ring_free(net->tx_ring.ring);
net->tx_ring.ring = NULL;
@ -1340,7 +1368,7 @@ static int __init tbnet_init(void)
* the moment.
*/
tb_property_add_immediate(tbnet_dir, "prtcstns",
TBNET_MATCH_FRAGS_ID);
TBNET_MATCH_FRAGS_ID | TBNET_64K_FRAMES);
ret = tb_register_property_dir("network", tbnet_dir);
if (ret) {

View File

@ -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)

View File

@ -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,

View File

@ -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;

View File

@ -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);

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;

View File

@ -501,6 +501,77 @@ ssize_t tb_property_format_dir(const struct tb_property_dir *dir, u32 *block,
return ret < 0 ? ret : 0;
}
/**
* 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

View File

@ -626,28 +626,6 @@ int tb_port_add_nfc_credits(struct tb_port *port, int credits)
TB_CFG_PORT, ADP_CS_4, 1);
}
/**
* 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
@ -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");

View File

@ -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;

View File

@ -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);

View File

@ -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),
{ }
};

View File

@ -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;

View File

@ -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,

View File

@ -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;
ret = tb_property_format_dir(xdomain_property_dir, block, len);
if (ret) {
kfree(block);
return ret;
}
kfree(xdomain_property_block);
xdomain_property_block = block;
xdomain_property_block_len = len;
xdomain_property_block_gen++;
return 0;
}
static void finalize_property_block(void)
{
const struct tb_property *nodename;
/*
* 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();
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;
}
/* Fill in non-static properties now */
tb_property_add_text(dir, "deviceid", utsname()->nodename);
tb_property_add_immediate(dir, "maxhopid", xd->local_max_hopid);
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;
}
block_len = ret;
block = kcalloc(block_len, sizeof(*block), GFP_KERNEL);
if (!block) {
tb_property_free_dir(dir);
goto out_unlock;
}
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;
}
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);
}
}
/**
@ -1522,74 +1556,119 @@ void tb_xdomain_lane_bonding_disable(struct tb_xdomain *xd)
}
EXPORT_SYMBOL_GPL(tb_xdomain_lane_bonding_disable);
/**
* tb_xdomain_alloc_in_hopid() - Allocate input 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 input lane adapter.
* Call tb_xdomain_release_in_hopid() to release the allocated HopID.
*/
int tb_xdomain_alloc_in_hopid(struct tb_xdomain *xd, int hopid)
{
if (hopid < 0)
hopid = TB_PATH_MIN_HOPID;
if (hopid < TB_PATH_MIN_HOPID || hopid > xd->local_max_hopid)
return -EINVAL;
return ida_alloc_range(&xd->in_hopids, hopid, xd->local_max_hopid,
GFP_KERNEL);
}
EXPORT_SYMBOL_GPL(tb_xdomain_alloc_in_hopid);
/**
* 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;
return ida_alloc_range(&xd->out_hopids, hopid, xd->remote_max_hopid,
GFP_KERNEL);
}
EXPORT_SYMBOL_GPL(tb_xdomain_alloc_out_hopid);
/**
* 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);
/**
* 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);
/**
* tb_xdomain_enable_paths() - Enable DMA paths for XDomain connection
* @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
* @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.
* 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, u16 transmit_path,
u16 transmit_ring, u16 receive_path,
u16 receive_ring)
int tb_xdomain_enable_paths(struct tb_xdomain *xd, int transmit_path,
int transmit_ring, int receive_path,
int receive_ring)
{
int ret;
mutex_lock(&xd->lock);
if (xd->transmit_path) {
ret = xd->transmit_path == transmit_path ? 0 : -EBUSY;
goto exit_unlock;
}
xd->transmit_path = transmit_path;
xd->transmit_ring = transmit_ring;
xd->receive_path = receive_path;
xd->receive_ring = receive_ring;
ret = tb_domain_approve_xdomain_paths(xd->tb, xd);
exit_unlock:
mutex_unlock(&xd->lock);
return ret;
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);
}

View File

@ -146,6 +146,7 @@ struct tb_property_dir *tb_property_parse_dir(const u32 *block,
size_t block_len);
ssize_t tb_property_format_dir(const struct tb_property_dir *dir, u32 *block,
size_t block_len);
struct tb_property_dir *tb_property_copy_dir(const struct tb_property_dir *dir);
struct tb_property_dir *tb_property_create_dir(const uuid_t *uuid);
void tb_property_free_dir(struct tb_property_dir *dir);
int tb_property_add_immediate(struct tb_property_dir *parent, const char *key,
@ -179,23 +180,24 @@ void tb_unregister_property_dir(const char *key, struct tb_property_dir *dir);
* @route: Route string the other domain can be reached
* @vendor: Vendor ID of the remote domain
* @device: Device ID of the demote domain
* @local_max_hopid: Maximum input HopID of this host
* @remote_max_hopid: Maximum input HopID of the remote host
* @lock: Lock to serialize access to the following fields of this structure
* @vendor_name: Name of the vendor (or %NULL if not known)
* @device_name: Name of the device (or %NULL if not known)
* @link_speed: Speed of the link in Gb/s
* @link_width: Width of the link (1 or 2)
* @is_unplugged: The XDomain is unplugged
* @resume: The XDomain is being resumed
* @needs_uuid: If the XDomain does not have @remote_uuid it will be
* queried first
* @transmit_path: HopID which the remote end expects us to transmit
* @transmit_ring: Local ring (hop) where outgoing packets are pushed
* @receive_path: HopID which we expect the remote end to transmit
* @receive_ring: Local ring (hop) where incoming packets arrive
* @service_ids: Used to generate IDs for the services
* @properties: Properties exported by the remote domain
* @property_block_gen: Generation of @properties
* @properties_lock: Lock protecting @properties.
* @in_hopids: Input HopIDs for DMA tunneling
* @out_hopids; Output HopIDs for DMA tunneling
* @local_property_block: Local block of properties
* @local_property_block_gen: Generation of @local_property_block
* @local_property_block_len: Length of the @local_property_block in dwords
* @remote_properties: Properties exported by the remote domain
* @remote_property_block_gen: Generation of @remote_properties
* @get_uuid_work: Work used to retrieve @remote_uuid
* @uuid_retries: Number of times left @remote_uuid is requested before
* giving up
@ -225,21 +227,23 @@ struct tb_xdomain {
u64 route;
u16 vendor;
u16 device;
unsigned int local_max_hopid;
unsigned int remote_max_hopid;
struct mutex lock;
const char *vendor_name;
const char *device_name;
unsigned int link_speed;
unsigned int link_width;
bool is_unplugged;
bool resume;
bool needs_uuid;
u16 transmit_path;
u16 transmit_ring;
u16 receive_path;
u16 receive_ring;
struct ida service_ids;
struct tb_property_dir *properties;
u32 property_block_gen;
struct ida in_hopids;
struct ida out_hopids;
u32 *local_property_block;
u32 local_property_block_gen;
u32 local_property_block_len;
struct tb_property_dir *remote_properties;
u32 remote_property_block_gen;
struct delayed_work get_uuid_work;
int uuid_retries;
struct delayed_work get_properties_work;
@ -252,10 +256,22 @@ struct tb_xdomain {
int tb_xdomain_lane_bonding_enable(struct tb_xdomain *xd);
void tb_xdomain_lane_bonding_disable(struct tb_xdomain *xd);
int tb_xdomain_enable_paths(struct tb_xdomain *xd, u16 transmit_path,
u16 transmit_ring, u16 receive_path,
u16 receive_ring);
int tb_xdomain_disable_paths(struct tb_xdomain *xd);
int tb_xdomain_alloc_in_hopid(struct tb_xdomain *xd, int hopid);
void tb_xdomain_release_in_hopid(struct tb_xdomain *xd, int hopid);
int tb_xdomain_alloc_out_hopid(struct tb_xdomain *xd, int hopid);
void tb_xdomain_release_out_hopid(struct tb_xdomain *xd, int hopid);
int tb_xdomain_enable_paths(struct tb_xdomain *xd, int transmit_path,
int transmit_ring, int receive_path,
int receive_ring);
int tb_xdomain_disable_paths(struct tb_xdomain *xd, int transmit_path,
int transmit_ring, int receive_path,
int receive_ring);
static inline int tb_xdomain_disable_all_paths(struct tb_xdomain *xd)
{
return tb_xdomain_disable_paths(xd, -1, -1, -1, -1);
}
struct tb_xdomain *tb_xdomain_find_by_uuid(struct tb *tb, const uuid_t *uuid);
struct tb_xdomain *tb_xdomain_find_by_route(struct tb *tb, u64 route);