Netdev List
 help / color / mirror / Atom feed
* Re: [PATCH 3/3] selftests: silence test output by default
From: Josef Bacik @ 2017-09-18 17:52 UTC (permalink / raw)
  To: Shuah Khan
  Cc: josef, Josef Bacik, David S. Miller, linux-kernel,
	linux-kselftest, netdev, shuah Khan
In-Reply-To: <e8b067d2-9cef-2f3c-78a5-d6630aa765c1@kernel.org>

On Mon, Sep 18, 2017 at 11:46:18AM -0600, Shuah Khan wrote:
> On 09/18/2017 11:37 AM, josef@toxicpanda.com wrote:
> > From: Josef Bacik <jbacik@fb.com>
> > 
> > Some of the networking tests are very noisy and make it impossible to
> > see if we actually passed the tests as they run.  Default to suppressing
> > the output from any tests run in order to make it easier to track what
> > failed.
> > 
> > Signed-off-by: Josef Bacik <jbacik@fb.com>
> > --
> 
> This change suppresses pass/fail wrapper output for all tests, not just the
> networking tests.
> 
> Could you please send me before and after results for what you are trying
> to fix.
> 

Yeah I wanted to suppress extraneous output from everybody, I just happened to
notice it because I was testing net.  The default thing already spits out what
it's running and pass/fail, there's no need to include all of the random output
unless the user wants to go and run the test manually.  As it is now it's
_impossible_ to tell what ran and what passed/failed because of all the random
output.

Ideally kselftests would work like xfstests does and simply capture the output
to a log so you could go check afterwards, but that's a lot more work.  Making
it easier to tell which tests passed/failed is a good enough first step.
Thanks,

Josef

^ permalink raw reply

* Re: [REGRESSION] Warning in tcp_fastretrans_alert() of net/ipv4/tcp_input.c
From: Yuchung Cheng @ 2017-09-18 17:51 UTC (permalink / raw)
  To: Oleksandr Natalenko
  Cc: Neal Cardwell, David S. Miller, Alexey Kuznetsov,
	Hideaki YOSHIFUJI, Netdev
In-Reply-To: <CAK6E8=fF0hMmQ+m4JZPWFFJixYa3Vp5WrQapYu4zq9Zc5eMe_w@mail.gmail.com>

On Mon, Sep 18, 2017 at 10:18 AM, Yuchung Cheng <ycheng@google.com> wrote:
> On Sun, Sep 17, 2017 at 11:43 AM, Oleksandr Natalenko
> <oleksandr@natalenko.name> wrote:
>> Hi.
>>
>> Just to note that it looks like disabling RACK and re-enabling FACK prevents
>> warning from happening:
>>
>> net.ipv4.tcp_fack = 1
>> net.ipv4.tcp_recovery = 0
>>
>> Hope I get semantics of these tunables right.
> Thanks.
>
> One difference between RACK and FACK is that RACK can detect lost
> retransmission in CA_Recovery (fast recovery) and CA_Loss  (post RTO)
> mode, while the current FACK can not. A previous FACK version can also
> detect lost retransmission in CA_recovery with limited-transmit. I
> suspect it is RACK's special ability that triggers this warning.
>
> IMO, however, this warning itself is questionably valid: with undo
> (TCP Eifel), the sender can detect and revert a false CA_Recovery /
> CA_Loss to CA_Open, with spurious retransmission in-flight
> (tp->retrans_out > 0). Then another SACK after undo triggers this
> warning. Neal and I are not sure if this is causing the panics you're
> seeing, but personally I'd argue this warning is false, or at least
> should be revised to skip undo case.
Can you try this patch to verify my theory with tcp_recovery=0 and 1? thanks

diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c
index 5af2f04f8859..9253d9ee7d0e 100644
--- a/net/ipv4/tcp_input.c
+++ b/net/ipv4/tcp_input.c
@@ -2381,6 +2381,7 @@ static void tcp_undo_cwnd_reduction(struct sock
*sk, bool unmark_loss)
        }
        tp->snd_cwnd_stamp = tcp_time_stamp;
        tp->undo_marker = 0;
+       WARN_ON(tp->retrans_out);
 }




>
>
>>
>> On pátek 15. září 2017 21:04:36 CEST Oleksandr Natalenko wrote:
>>> Hello.
>>>
>>> With net.ipv4.tcp_fack set to 0 the warning still appears:
>>>
>>> ===
>>> » sysctl net.ipv4.tcp_fack
>>> net.ipv4.tcp_fack = 0
>>>
>>> » LC_TIME=C dmesg -T | grep WARNING
>>> [Fri Sep 15 20:40:30 2017] WARNING: CPU: 1 PID: 711 at net/ipv4/tcp_input.c:
>>> 2826 tcp_fastretrans_alert+0x7c8/0x990
>>> [Fri Sep 15 20:40:30 2017] WARNING: CPU: 0 PID: 711 at net/ipv4/tcp_input.c:
>>> 2826 tcp_fastretrans_alert+0x7c8/0x990
>>> [Fri Sep 15 20:48:37 2017] WARNING: CPU: 1 PID: 711 at net/ipv4/tcp_input.c:
>>> 2826 tcp_fastretrans_alert+0x7c8/0x990
>>> [Fri Sep 15 20:48:55 2017] WARNING: CPU: 0 PID: 711 at net/ipv4/tcp_input.c:
>>> 2826 tcp_fastretrans_alert+0x7c8/0x990
>>>
>>> » ps -up 711
>>> USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
>>> root       711  4.3  0.0      0     0 ?        S    18:12   7:23 [irq/123-
>>> enp3s0]
>>> ===
>>>
>>> Any suggestions?
>>>
>>> On pátek 15. září 2017 16:03:00 CEST Neal Cardwell wrote:
>>> > Thanks for testing that. That is a very useful data point.
>>> >
>>> > I was able to cook up a packetdrill test that could put the connection
>>> > in CA_Disorder with retransmitted packets out, but not in CA_Open. So
>>> > we do not yet have a test case to reproduce this.
>>> >
>>> > We do not see this warning on our fleet at Google. One significant
>>> > difference I see between our environment and yours is that it seems
>>> >
>>> > you run with FACK enabled:
>>> >   net.ipv4.tcp_fack = 1
>>> >
>>> > Note that FACK was disabled by default (since it was replaced by RACK)
>>> > between kernel v4.10 and v4.11. And this is exactly the time when this
>>> > bug started manifesting itself for you and some others, but not our
>>> > fleet. So my new working hypothesis would be that this warning is due
>>> > to a behavior that only shows up in kernels >=4.11 when FACK is
>>> > enabled.
>>> >
>>> > Would you be able to disable FACK ("sysctl net.ipv4.tcp_fack=0" at
>>> > boot, or net.ipv4.tcp_fack=0 in /etc/sysctl.conf, or equivalent),
>>> > reboot, and test the kernel for a few days to see if the warning still
>>> > pops up?
>>> >
>>> > thanks,
>>> > neal
>>> >
>>> > [ps: apologies for the previous, mis-formatted post...]
>>
>>

^ permalink raw reply related

* [RFC] endianness issues in drivers/net/ethernet/qlogic/qed
From: Al Viro @ 2017-09-18 17:49 UTC (permalink / raw)
  To: netdev; +Cc: Yuval Mintz, David Miller

	"qed: Utilize FW 8.10.3.0" has attempted some endianness
annotations in that driver; unfortunately, either annotations are
BS or the driver is genuinely broken on big-endian hosts.

	For example, struct init_qm_vport_params is claimed to have
->vport_wfq little-endian 16bit.  However, *all* uses are host-endian -
the things like
                vport_params[i].vport_wfq = (wfq_speed * QED_WFQ_UNIT) /
                                                min_pf_rate;
and it's not even "... and at some point we convert it to little-endian
in place, or when copying to another instance".  It is consistently
host-endian.  If that's how it should be, that __le16 is BS; otherwise,
the driver's broken on big-endian.

	Another example:
struct qm_rf_pq_map {
        __le32 reg;
#define QM_RF_PQ_MAP_PQ_VALID_MASK              0x1
#define QM_RF_PQ_MAP_PQ_VALID_SHIFT             0
#define QM_RF_PQ_MAP_RL_ID_MASK                 0xFF
#define QM_RF_PQ_MAP_RL_ID_SHIFT                1
#define QM_RF_PQ_MAP_VP_PQ_ID_MASK              0x1FF
#define QM_RF_PQ_MAP_VP_PQ_ID_SHIFT             9
#define QM_RF_PQ_MAP_VOQ_MASK                   0x1F
#define QM_RF_PQ_MAP_VOQ_SHIFT                  18
#define QM_RF_PQ_MAP_WRR_WEIGHT_GROUP_MASK      0x3
#define QM_RF_PQ_MAP_WRR_WEIGHT_GROUP_SHIFT     23
#define QM_RF_PQ_MAP_RL_VALID_MASK              0x1
#define QM_RF_PQ_MAP_RL_VALID_SHIFT             25
#define QM_RF_PQ_MAP_RESERVED_MASK              0x3F
#define QM_RF_PQ_MAP_RESERVED_SHIFT             26
};
with instances of that manipulated with
                memset(&tx_pq_map, 0, sizeof(tx_pq_map));
                SET_FIELD(tx_pq_map.reg, QM_RF_PQ_MAP_PQ_VALID, 1);
                SET_FIELD(tx_pq_map.reg,
                          QM_RF_PQ_MAP_RL_VALID, rl_valid ? 1 : 0);
                SET_FIELD(tx_pq_map.reg, QM_RF_PQ_MAP_VP_PQ_ID, first_tx_pq_id);
                SET_FIELD(tx_pq_map.reg, QM_RF_PQ_MAP_RL_ID,
                          rl_valid ?
                          p_params->pq_params[i].vport_id : 0);
                SET_FIELD(tx_pq_map.reg, QM_RF_PQ_MAP_VOQ, voq);
                SET_FIELD(tx_pq_map.reg, QM_RF_PQ_MAP_WRR_WEIGHT_GROUP,
                          p_params->pq_params[i].wrr_group);
                /* Write PQ map entry to CAM */
                STORE_RT_REG(p_hwfn, QM_REG_TXPQMAP_RT_OFFSET + pq_id,
                             *((u32 *)&tx_pq_map));
SET_FIELD manipulates the damn thing as *host-endian* - e.g.
                SET_FIELD(v, QM_RF_PQ_MAP_RL_ID, n)
would expand to v = (v & ~0x1fe) | ((n & 0xff) << 1).  Then we proceed to
pass tx_pq_map.reg (fetched in a bloody convoluted way) to
qed_init_store_rt_reg(), which stores it into p_hwfn->rt_data.init_val[...],
which is apparently host-endian.

So what is this __le32 about?

The really worrying case is this:

/* Binary buffer header */
struct bin_buffer_hdr {
        __le32 offset;
        __le32 length;
};

int qed_init_fw_data(struct qed_dev *cdev, const u8 *data)
{
        struct qed_fw_data *fw = cdev->fw_data;
        struct bin_buffer_hdr *buf_hdr;
        u32 offset, len;

        if (!data) {
                DP_NOTICE(cdev, "Invalid fw data\n");
                return -EINVAL;
        }

        /* First Dword contains metadata and should be skipped */
        buf_hdr = (struct bin_buffer_hdr *)data;

        offset = buf_hdr[BIN_BUF_INIT_FW_VER_INFO].offset;
        fw->fw_ver_info = (struct fw_ver_info *)(data + offset);

        offset = buf_hdr[BIN_BUF_INIT_CMD].offset;
        fw->init_ops = (union init_op *)(data + offset);

We are clearly using those as host-endian here.  Yet in _that_
case fixed-endian would appear to make sense, unless we want
separate firmware images for e.g. amd64 and sparc64 hosts...

Another fun one is struct regpair:
        p_ramrod->qp_handle_for_cqe.hi = cpu_to_le32(qp->qp_handle.hi);
        p_ramrod->qp_handle_for_cqe.lo = cpu_to_le32(qp->qp_handle.lo);
Both sides are struct regpair and by the look of helper macros it *is*
meant to be little-endian.  Here, however, one of those (and not
necessary p_ramrod->qp_handle_for_cqe) is host-endian.

Is that driver intended to be used on big-endian hosts at all?

^ permalink raw reply

* Re: [PATCH 3/3] selftests: silence test output by default
From: Shuah Khan @ 2017-09-18 17:46 UTC (permalink / raw)
  To: josef
  Cc: Josef Bacik, David S. Miller, linux-kernel, linux-kselftest,
	netdev, shuah Khan
In-Reply-To: <1505756224-8187-1-git-send-email-jbacik@fb.com>

On 09/18/2017 11:37 AM, josef@toxicpanda.com wrote:
> From: Josef Bacik <jbacik@fb.com>
> 
> Some of the networking tests are very noisy and make it impossible to
> see if we actually passed the tests as they run.  Default to suppressing
> the output from any tests run in order to make it easier to track what
> failed.
> 
> Signed-off-by: Josef Bacik <jbacik@fb.com>
> --

This change suppresses pass/fail wrapper output for all tests, not just the
networking tests.

Could you please send me before and after results for what you are trying
to fix.

>  tools/testing/selftests/lib.mk | 4 ++--
>  1 file changed, 2 insertions(+), 2 deletions(-)
> 
> diff --git a/tools/testing/selftests/lib.mk b/tools/testing/selftests/lib.mk
> index 693616651da5..223234cd98e9 100644
> --- a/tools/testing/selftests/lib.mk
> +++ b/tools/testing/selftests/lib.mk
> @@ -24,7 +24,7 @@ define RUN_TESTS
>  			echo "selftests: Warning: file $$BASENAME_TEST is not executable, correct this.";\
>  			echo "not ok 1..$$test_num selftests: $$BASENAME_TEST [FAIL]"; \
>  		else					\
> -			cd `dirname $$TEST` > /dev/null; (./$$BASENAME_TEST && echo "ok 1..$$test_num selftests: $$BASENAME_TEST [PASS]") || echo "not ok 1..$$test_num selftests:  $$BASENAME_TEST [FAIL]"; cd - > /dev/null;\
> +			cd `dirname $$TEST` > /dev/null; (./$$BASENAME_TEST > /dev/null 2>&1 && echo "ok 1..$$test_num selftests: $$BASENAME_TEST [PASS]") || echo "not ok 1..$$test_num selftests:  $$BASENAME_TEST [FAIL]"; cd - > /dev/null;\
>  		fi;					\
>  	done;
>  endef
> @@ -55,7 +55,7 @@ endif
>  define EMIT_TESTS
>  	@for TEST in $(TEST_GEN_PROGS) $(TEST_PROGS); do \
>  		BASENAME_TEST=`basename $$TEST`;	\
> -		echo "(./$$BASENAME_TEST && echo \"selftests: $$BASENAME_TEST [PASS]\") || echo \"selftests: $$BASENAME_TEST [FAIL]\""; \
> +		echo "(./$$BASENAME_TEST > /dev/null 2>&1 && echo \"selftests: $$BASENAME_TEST [PASS]\") || echo \"selftests: $$BASENAME_TEST [FAIL]\""; \
>  	done;
>  endef
>  
> 

thanks,
-- Shuah

^ permalink raw reply

* Re: [PATCH 0/3] fix reuseaddr regression
From: Cole Robinson @ 2017-09-18 17:44 UTC (permalink / raw)
  To: josef, davem, netdev, linux-kernel, labbott, kernel-team
In-Reply-To: <1505752137-15522-1-git-send-email-jbacik@fb.com>

On 09/18/2017 12:28 PM, josef@toxicpanda.com wrote:
> I introduced a regression when reworking the fastreuse port stuff that allows
> bind conflicts to occur once a reuseaddr socket successfully opens on an
> existing tb.  The root cause is I reversed an if statement which caused us to
> set the tb as if there were no owners on the socket if there were, which
> obviously is not correct.
> 
> Dave I have follow up patches that will add a selftest for this case and I ran
> the other reuseport related tests as well.  These need to go in pretty quickly
> as it breaks kvm, I've marked them for stable.  Sorry for the regression,
> 

To clarify, it doesn't really break KVM specifically, but it breaks a
port collision detection idiom that libvirt depends on to successfully
launch qemu/xen/... VMs in certain cases.

Thanks,
Cole

^ permalink raw reply

* [PATCH 3/3] selftests: silence test output by default
From: josef @ 2017-09-18 17:37 UTC (permalink / raw)
  Cc: Josef Bacik, Shuah Khan, David S. Miller, linux-kernel,
	linux-kselftest, netdev
In-Reply-To: <1505755982-7855-1-git-send-email-jbacik@fb.com>

From: Josef Bacik <jbacik@fb.com>

Some of the networking tests are very noisy and make it impossible to
see if we actually passed the tests as they run.  Default to suppressing
the output from any tests run in order to make it easier to track what
failed.

Signed-off-by: Josef Bacik <jbacik@fb.com>
---
 tools/testing/selftests/lib.mk | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/tools/testing/selftests/lib.mk b/tools/testing/selftests/lib.mk
index 693616651da5..223234cd98e9 100644
--- a/tools/testing/selftests/lib.mk
+++ b/tools/testing/selftests/lib.mk
@@ -24,7 +24,7 @@ define RUN_TESTS
 			echo "selftests: Warning: file $$BASENAME_TEST is not executable, correct this.";\
 			echo "not ok 1..$$test_num selftests: $$BASENAME_TEST [FAIL]"; \
 		else					\
-			cd `dirname $$TEST` > /dev/null; (./$$BASENAME_TEST && echo "ok 1..$$test_num selftests: $$BASENAME_TEST [PASS]") || echo "not ok 1..$$test_num selftests:  $$BASENAME_TEST [FAIL]"; cd - > /dev/null;\
+			cd `dirname $$TEST` > /dev/null; (./$$BASENAME_TEST > /dev/null 2>&1 && echo "ok 1..$$test_num selftests: $$BASENAME_TEST [PASS]") || echo "not ok 1..$$test_num selftests:  $$BASENAME_TEST [FAIL]"; cd - > /dev/null;\
 		fi;					\
 	done;
 endef
@@ -55,7 +55,7 @@ endif
 define EMIT_TESTS
 	@for TEST in $(TEST_GEN_PROGS) $(TEST_PROGS); do \
 		BASENAME_TEST=`basename $$TEST`;	\
-		echo "(./$$BASENAME_TEST && echo \"selftests: $$BASENAME_TEST [PASS]\") || echo \"selftests: $$BASENAME_TEST [FAIL]\""; \
+		echo "(./$$BASENAME_TEST > /dev/null 2>&1 && echo \"selftests: $$BASENAME_TEST [PASS]\") || echo \"selftests: $$BASENAME_TEST [FAIL]\""; \
 	done;
 endef
 
-- 
2.7.4

^ permalink raw reply related

* [PATCH 2/3] selftests: actually run the various net selftests
From: josef @ 2017-09-18 17:32 UTC (permalink / raw)
  Cc: Josef Bacik, Shuah Khan, David S. Miller, linux-kernel,
	linux-kselftest, netdev
In-Reply-To: <1505755982-7855-1-git-send-email-jbacik@fb.com>

From: Josef Bacik <jbacik@fb.com>

These self tests are just self contained binaries, they are not run by
any of the scripts in the directory.  This means they need to be marked
with TEST_GEN_PROGS to actually be run, not TEST_GEN_FILES.

Signed-off-by: Josef Bacik <jbacik@fb.com>
---
 tools/testing/selftests/net/Makefile | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
index 3df542c84610..45a4e77a47c4 100644
--- a/tools/testing/selftests/net/Makefile
+++ b/tools/testing/selftests/net/Makefile
@@ -6,8 +6,8 @@ CFLAGS += -I../../../../usr/include/
 TEST_PROGS := run_netsocktests run_afpackettests test_bpf.sh netdevice.sh rtnetlink.sh
 TEST_GEN_FILES =  socket
 TEST_GEN_FILES += psock_fanout psock_tpacket
-TEST_GEN_FILES += reuseport_bpf reuseport_bpf_cpu reuseport_bpf_numa
-TEST_GEN_FILES += reuseport_dualstack msg_zerocopy reuseaddr_conflict
+TEST_GEN_PROGS += reuseport_bpf reuseport_bpf_cpu reuseport_bpf_numa
+TEST_GEN_PROGS += reuseport_dualstack msg_zerocopy reuseaddr_conflict
 
 include ../lib.mk
 
-- 
2.7.4

^ permalink raw reply related

* [PATCH 1/3] selftest: add a reuseaddr test
From: josef @ 2017-09-18 17:32 UTC (permalink / raw)
  Cc: Josef Bacik, Shuah Khan, David S. Miller, linux-kernel,
	linux-kselftest, netdev

From: Josef Bacik <jbacik@fb.com>

This is to test for a regression introduced by

b9470c27607b ("inet: kill smallest_size and smallest_port")

which introduced a problem with reuseaddr and bind conflicts.

Signed-off-by: Josef Bacik <jbacik@fb.com>
---
 tools/testing/selftests/net/.gitignore           |   1 +
 tools/testing/selftests/net/Makefile             |   2 +-
 tools/testing/selftests/net/reuseaddr_conflict.c | 114 +++++++++++++++++++++++
 3 files changed, 116 insertions(+), 1 deletion(-)
 create mode 100644 tools/testing/selftests/net/reuseaddr_conflict.c

diff --git a/tools/testing/selftests/net/.gitignore b/tools/testing/selftests/net/.gitignore
index 9801253e4802..c612d6e38c62 100644
--- a/tools/testing/selftests/net/.gitignore
+++ b/tools/testing/selftests/net/.gitignore
@@ -6,3 +6,4 @@ reuseport_bpf
 reuseport_bpf_cpu
 reuseport_bpf_numa
 reuseport_dualstack
+reuseaddr_conflict
diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
index de1f5772b878..3df542c84610 100644
--- a/tools/testing/selftests/net/Makefile
+++ b/tools/testing/selftests/net/Makefile
@@ -7,7 +7,7 @@ TEST_PROGS := run_netsocktests run_afpackettests test_bpf.sh netdevice.sh rtnetl
 TEST_GEN_FILES =  socket
 TEST_GEN_FILES += psock_fanout psock_tpacket
 TEST_GEN_FILES += reuseport_bpf reuseport_bpf_cpu reuseport_bpf_numa
-TEST_GEN_FILES += reuseport_dualstack msg_zerocopy
+TEST_GEN_FILES += reuseport_dualstack msg_zerocopy reuseaddr_conflict
 
 include ../lib.mk
 
diff --git a/tools/testing/selftests/net/reuseaddr_conflict.c b/tools/testing/selftests/net/reuseaddr_conflict.c
new file mode 100644
index 000000000000..7c5b12664b03
--- /dev/null
+++ b/tools/testing/selftests/net/reuseaddr_conflict.c
@@ -0,0 +1,114 @@
+/*
+ * Test for the regression introduced by
+ *
+ * b9470c27607b ("inet: kill smallest_size and smallest_port")
+ *
+ * If we open an ipv4 socket on a port with reuseaddr we shouldn't reset the tb
+ * when we open the ipv6 conterpart, which is what was happening previously.
+ */
+#include <errno.h>
+#include <error.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#define PORT 9999
+
+int open_port(int ipv6, int any)
+{
+	int fd = -1;
+	int reuseaddr = 1;
+	int v6only = 1;
+	int addrlen;
+	int ret = -1;
+	struct sockaddr *addr;
+	int family = ipv6 ? AF_INET6 : AF_INET;
+
+	struct sockaddr_in6 addr6 = {
+		.sin6_family = AF_INET6,
+		.sin6_port = htons(PORT),
+		.sin6_addr = in6addr_any
+	};
+	struct sockaddr_in addr4 = {
+		.sin_family = AF_INET,
+		.sin_port = htons(PORT),
+		.sin_addr.s_addr = any ? htonl(INADDR_ANY) : inet_addr("127.0.0.1"),
+	};
+
+
+	if (ipv6) {
+		addr = (struct sockaddr*)&addr6;
+		addrlen = sizeof(addr6);
+	} else {
+		addr = (struct sockaddr*)&addr4;
+		addrlen = sizeof(addr4);
+	}
+
+	if ((fd = socket(family, SOCK_STREAM, IPPROTO_TCP)) < 0) {
+		perror("socket");
+		goto out;
+	}
+
+	if (ipv6 && setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (void*)&v6only,
+			       sizeof(v6only)) < 0) {
+		perror("setsockopt IPV6_V6ONLY");
+		goto out;
+	}
+
+	if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr,
+		       sizeof(reuseaddr)) < 0) {
+		perror("setsockopt SO_REUSEADDR");
+		goto out;
+	}
+
+	if (bind(fd, addr, addrlen) < 0) {
+		perror("bind");
+		goto out;
+	}
+
+	if (any)
+		return fd;
+
+	if (listen(fd, 1) < 0) {
+		perror("listen");
+		goto out;
+	}
+	return fd;
+out:
+	close(fd);
+	return ret;
+}
+
+int main(void)
+{
+	int listenfd;
+	int fd1, fd2;
+
+	fprintf(stderr, "Opening 127.0.0.1:%d\n", PORT);
+	listenfd = open_port(0, 0);
+	if (listenfd < 0)
+		error(1, errno, "Couldn't open listen socket");
+	fprintf(stderr, "Opening INADDR_ANY:%d\n", PORT);
+	fd1 = open_port(0, 1);
+	if (fd1 >= 0)
+		error(1, 0, "Was allowed to create an ipv4 reuseport on a already bound non-reuseport socket");
+	fprintf(stderr, "Opening in6addr_any:%d\n", PORT);
+	fd1 = open_port(1, 1);
+	if (fd1 < 0)
+		error(1, errno, "Couldn't open ipv6 reuseport");
+	fprintf(stderr, "Opening INADDR_ANY:%d\n", PORT);
+	fd2 = open_port(0, 1);
+	if (fd2 >= 0)
+		error(1, 0, "Was allowed to create an ipv4 reuseport on a already bound non-reuseport socket");
+	close(fd1);
+	fprintf(stderr, "Opening INADDR_ANY:%d after closing ipv6 socket\n", PORT);
+	fd1 = open_port(0, 1);
+	if (fd1 >= 0)
+		error(1, 0, "Was allowed to create an ipv4 reuseport on an already bound non-reuseport socket with no ipv6");
+	fprintf(stderr, "Success");
+	return 0;
+}
-- 
2.7.4

^ permalink raw reply related

* RE: [Intel-wired-lan] [PATCH] fm10k: Fix misuse of net_ratelimit()
From: Singh, Krishneil K @ 2017-09-18 17:22 UTC (permalink / raw)
  To: Joe Perches, Kirsher, Jeffrey T
  Cc: netdev@vger.kernel.org, intel-wired-lan@lists.osuosl.org,
	linux-kernel@vger.kernel.org
In-Reply-To: <83d6fcab6a180d7006c8eb396c9aa3169dc6a84d.1502468221.git.joe@perches.com>



> -----Original Message-----
> From: Intel-wired-lan [mailto:intel-wired-lan-bounces@osuosl.org] On Behalf
> Of Joe Perches
> Sent: Friday, August 11, 2017 9:17 AM
> To: Kirsher, Jeffrey T <jeffrey.t.kirsher@intel.com>
> Cc: netdev@vger.kernel.org; intel-wired-lan@lists.osuosl.org; linux-
> kernel@vger.kernel.org
> Subject: [Intel-wired-lan] [PATCH] fm10k: Fix misuse of net_ratelimit()
> 
> Correct the backward logic using !net_ratelimit()
> 
> Miscellanea:
> 
> o Add a blank line before the error return label
> 
> Signed-off-by: Joe Perches <joe@perches.com>
> ---

Tested-by: Krishneil Singh  <krishneil.k.singh@intel.com>

^ permalink raw reply

* Re: [REGRESSION] Warning in tcp_fastretrans_alert() of net/ipv4/tcp_input.c
From: Yuchung Cheng @ 2017-09-18 17:18 UTC (permalink / raw)
  To: Oleksandr Natalenko
  Cc: Neal Cardwell, David S. Miller, Alexey Kuznetsov,
	Hideaki YOSHIFUJI, Netdev
In-Reply-To: <22474097.Jky8MxLkJU@natalenko.name>

On Sun, Sep 17, 2017 at 11:43 AM, Oleksandr Natalenko
<oleksandr@natalenko.name> wrote:
> Hi.
>
> Just to note that it looks like disabling RACK and re-enabling FACK prevents
> warning from happening:
>
> net.ipv4.tcp_fack = 1
> net.ipv4.tcp_recovery = 0
>
> Hope I get semantics of these tunables right.
Thanks.

One difference between RACK and FACK is that RACK can detect lost
retransmission in CA_Recovery (fast recovery) and CA_Loss  (post RTO)
mode, while the current FACK can not. A previous FACK version can also
detect lost retransmission in CA_recovery with limited-transmit. I
suspect it is RACK's special ability that triggers this warning.

IMO, however, this warning itself is questionably valid: with undo
(TCP Eifel), the sender can detect and revert a false CA_Recovery /
CA_Loss to CA_Open, with spurious retransmission in-flight
(tp->retrans_out > 0). Then another SACK after undo triggers this
warning. Neal and I are not sure if this is causing the panics you're
seeing, but personally I'd argue this warning is false, or at least
should be revised to skip undo case.


>
> On pátek 15. září 2017 21:04:36 CEST Oleksandr Natalenko wrote:
>> Hello.
>>
>> With net.ipv4.tcp_fack set to 0 the warning still appears:
>>
>> ===
>> » sysctl net.ipv4.tcp_fack
>> net.ipv4.tcp_fack = 0
>>
>> » LC_TIME=C dmesg -T | grep WARNING
>> [Fri Sep 15 20:40:30 2017] WARNING: CPU: 1 PID: 711 at net/ipv4/tcp_input.c:
>> 2826 tcp_fastretrans_alert+0x7c8/0x990
>> [Fri Sep 15 20:40:30 2017] WARNING: CPU: 0 PID: 711 at net/ipv4/tcp_input.c:
>> 2826 tcp_fastretrans_alert+0x7c8/0x990
>> [Fri Sep 15 20:48:37 2017] WARNING: CPU: 1 PID: 711 at net/ipv4/tcp_input.c:
>> 2826 tcp_fastretrans_alert+0x7c8/0x990
>> [Fri Sep 15 20:48:55 2017] WARNING: CPU: 0 PID: 711 at net/ipv4/tcp_input.c:
>> 2826 tcp_fastretrans_alert+0x7c8/0x990
>>
>> » ps -up 711
>> USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
>> root       711  4.3  0.0      0     0 ?        S    18:12   7:23 [irq/123-
>> enp3s0]
>> ===
>>
>> Any suggestions?
>>
>> On pátek 15. září 2017 16:03:00 CEST Neal Cardwell wrote:
>> > Thanks for testing that. That is a very useful data point.
>> >
>> > I was able to cook up a packetdrill test that could put the connection
>> > in CA_Disorder with retransmitted packets out, but not in CA_Open. So
>> > we do not yet have a test case to reproduce this.
>> >
>> > We do not see this warning on our fleet at Google. One significant
>> > difference I see between our environment and yours is that it seems
>> >
>> > you run with FACK enabled:
>> >   net.ipv4.tcp_fack = 1
>> >
>> > Note that FACK was disabled by default (since it was replaced by RACK)
>> > between kernel v4.10 and v4.11. And this is exactly the time when this
>> > bug started manifesting itself for you and some others, but not our
>> > fleet. So my new working hypothesis would be that this warning is due
>> > to a behavior that only shows up in kernels >=4.11 when FACK is
>> > enabled.
>> >
>> > Would you be able to disable FACK ("sysctl net.ipv4.tcp_fack=0" at
>> > boot, or net.ipv4.tcp_fack=0 in /etc/sysctl.conf, or equivalent),
>> > reboot, and test the kernel for a few days to see if the warning still
>> > pops up?
>> >
>> > thanks,
>> > neal
>> >
>> > [ps: apologies for the previous, mis-formatted post...]
>
>

^ permalink raw reply

* RE: [Intel-wired-lan] [PATCH] fm10k: Use seq_putc() in fm10k_dbg_desc_break()
From: Singh, Krishneil K @ 2017-09-18 16:59 UTC (permalink / raw)
  To: SF Markus Elfring, intel-wired-lan@lists.osuosl.org,
	netdev@vger.kernel.org, Kirsher, Jeffrey T
  Cc: kernel-janitors@vger.kernel.org, LKML
In-Reply-To: <4f8686c6-76cb-3d12-934e-6af67b6f084b@users.sourceforge.net>


> -----Original Message-----
> From: Intel-wired-lan [mailto:intel-wired-lan-bounces@osuosl.org] On Behalf
> Of SF Markus Elfring
> Sent: Monday, May 8, 2017 9:18 AM
> To: intel-wired-lan@lists.osuosl.org; netdev@vger.kernel.org; Kirsher, Jeffrey T
> <jeffrey.t.kirsher@intel.com>
> Cc: kernel-janitors@vger.kernel.org; LKML <linux-kernel@vger.kernel.org>
> Subject: [Intel-wired-lan] [PATCH] fm10k: Use seq_putc() in
> fm10k_dbg_desc_break()
> 
> From: Markus Elfring <elfring@users.sourceforge.net>
> Date: Mon, 8 May 2017 18:10:39 +0200
> 
> Two single characters should be put into a sequence.
> Thus use the corresponding function "seq_putc".
> 
> This issue was detected by using the Coccinelle software.
> 
> Signed-off-by: Markus Elfring <elfring@users.sourceforge.net>
> ---

Tested-by: Krishneil Singh  <krishneil.k.singh@intel.com>

^ permalink raw reply

* [PATCH] vhost: remove unneeded linux/miscdevice.h include
From: Corentin Labbe @ 2017-09-18 16:58 UTC (permalink / raw)
  To: mst, jasowang; +Cc: kvm, virtualization, netdev, linux-kernel, Corentin Labbe

drivers/vhost/vhost.c does not use any miscdevice, so this patch
remove this unnecessary inclusion.

Signed-off-by: Corentin Labbe <clabbe.montjoie@gmail.com>
---
 drivers/vhost/vhost.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/drivers/vhost/vhost.c b/drivers/vhost/vhost.c
index 33ac2b186b85..33ab839696f9 100644
--- a/drivers/vhost/vhost.c
+++ b/drivers/vhost/vhost.c
@@ -16,7 +16,6 @@
 #include <linux/uio.h>
 #include <linux/mm.h>
 #include <linux/mmu_context.h>
-#include <linux/miscdevice.h>
 #include <linux/mutex.h>
 #include <linux/poll.h>
 #include <linux/file.h>
-- 
2.13.5

^ permalink raw reply related

* [PATCH v4 net 3/3] lan78xx: Use default values loaded from EEPROM/OTP after reset
From: Nisar Sayed @ 2017-09-18 22:02 UTC (permalink / raw)
  To: davem; +Cc: UNGLinuxDriver, netdev
In-Reply-To: <20170918220225.3767-1-Nisar.Sayed@microchip.com>

Use default value of auto duplex and auto speed values loaded
from EEPROM/OTP after reset. The LAN78xx allows platform
configurations to be loaded from EEPROM/OTP.
Ex: When external phy is connected, the MAC can be configured to
have correct auto speed, auto duplex, auto polarity configured
from the EEPROM/OTP.

Fixes: 55d7de9de6c3 ("Microchip's LAN7800 family USB 2/3 to 10/100/1000 Ethernet device driver")
Signed-off-by: Nisar Sayed <Nisar.Sayed@microchip.com>
---
 drivers/net/usb/lan78xx.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/drivers/net/usb/lan78xx.c b/drivers/net/usb/lan78xx.c
index 3292f56ffe02..0fc3c19d5aef 100644
--- a/drivers/net/usb/lan78xx.c
+++ b/drivers/net/usb/lan78xx.c
@@ -2449,7 +2449,6 @@ static int lan78xx_reset(struct lan78xx_net *dev)
 	/* LAN7801 only has RGMII mode */
 	if (dev->chipid == ID_REV_CHIP_ID_7801_)
 		buf &= ~MAC_CR_GMII_EN_;
-	buf |= MAC_CR_AUTO_DUPLEX_ | MAC_CR_AUTO_SPEED_;
 	ret = lan78xx_write_reg(dev, MAC_CR, buf);
 
 	ret = lan78xx_read_reg(dev, MAC_TX, &buf);
-- 
2.14.1

^ permalink raw reply related

* [PATCH v4 net 2/3] lan78xx: Allow EEPROM write for less than MAX_EEPROM_SIZE
From: Nisar Sayed @ 2017-09-18 22:02 UTC (permalink / raw)
  To: davem; +Cc: UNGLinuxDriver, netdev
In-Reply-To: <20170918220225.3767-1-Nisar.Sayed@microchip.com>

Allow EEPROM write for less than MAX_EEPROM_SIZE

Fixes: 55d7de9de6c3 ("Microchip's LAN7800 family USB 2/3 to 10/100/1000 Ethernet device driver")
Signed-off-by: Nisar Sayed <Nisar.Sayed@microchip.com>
---
 drivers/net/usb/lan78xx.c | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/drivers/net/usb/lan78xx.c b/drivers/net/usb/lan78xx.c
index fcf85ae37435..3292f56ffe02 100644
--- a/drivers/net/usb/lan78xx.c
+++ b/drivers/net/usb/lan78xx.c
@@ -1290,11 +1290,10 @@ static int lan78xx_ethtool_set_eeprom(struct net_device *netdev,
 	if (ret)
 		return ret;
 
-	/* Allow entire eeprom update only */
-	if ((ee->magic == LAN78XX_EEPROM_MAGIC) &&
-	    (ee->offset == 0) &&
-	    (ee->len == 512) &&
-	    (data[0] == EEPROM_INDICATOR))
+	/* Invalid EEPROM_INDICATOR at offset zero will result in fail to
+	 * load data from EEPROM
+	 */
+	if (ee->magic == LAN78XX_EEPROM_MAGIC)
 		ret = lan78xx_write_raw_eeprom(dev, ee->offset, ee->len, data);
 	else if ((ee->magic == LAN78XX_OTP_MAGIC) &&
 		 (ee->offset == 0) &&
-- 
2.14.1

^ permalink raw reply related

* [PATCH v4 net 1/3] lan78xx: Fix for eeprom read/write when device auto suspend
From: Nisar Sayed @ 2017-09-18 22:02 UTC (permalink / raw)
  To: davem; +Cc: UNGLinuxDriver, netdev
In-Reply-To: <20170918220225.3767-1-Nisar.Sayed@microchip.com>

Fix for eeprom read/write when device auto suspend

Fixes: 55d7de9de6c3 ("Microchip's LAN7800 family USB 2/3 to 10/100/1000 Ethernet device driver")
Signed-off-by: Nisar Sayed <Nisar.Sayed@microchip.com>
---
 drivers/net/usb/lan78xx.c | 24 ++++++++++++++++++++----
 1 file changed, 20 insertions(+), 4 deletions(-)

diff --git a/drivers/net/usb/lan78xx.c b/drivers/net/usb/lan78xx.c
index b99a7fb09f8e..fcf85ae37435 100644
--- a/drivers/net/usb/lan78xx.c
+++ b/drivers/net/usb/lan78xx.c
@@ -1265,30 +1265,46 @@ static int lan78xx_ethtool_get_eeprom(struct net_device *netdev,
 				      struct ethtool_eeprom *ee, u8 *data)
 {
 	struct lan78xx_net *dev = netdev_priv(netdev);
+	int ret;
+
+	ret = usb_autopm_get_interface(dev->intf);
+	if (ret)
+		return ret;
 
 	ee->magic = LAN78XX_EEPROM_MAGIC;
 
-	return lan78xx_read_raw_eeprom(dev, ee->offset, ee->len, data);
+	ret = lan78xx_read_raw_eeprom(dev, ee->offset, ee->len, data);
+
+	usb_autopm_put_interface(dev->intf);
+
+	return ret;
 }
 
 static int lan78xx_ethtool_set_eeprom(struct net_device *netdev,
 				      struct ethtool_eeprom *ee, u8 *data)
 {
 	struct lan78xx_net *dev = netdev_priv(netdev);
+	int ret;
+
+	ret = usb_autopm_get_interface(dev->intf);
+	if (ret)
+		return ret;
 
 	/* Allow entire eeprom update only */
 	if ((ee->magic == LAN78XX_EEPROM_MAGIC) &&
 	    (ee->offset == 0) &&
 	    (ee->len == 512) &&
 	    (data[0] == EEPROM_INDICATOR))
-		return lan78xx_write_raw_eeprom(dev, ee->offset, ee->len, data);
+		ret = lan78xx_write_raw_eeprom(dev, ee->offset, ee->len, data);
 	else if ((ee->magic == LAN78XX_OTP_MAGIC) &&
 		 (ee->offset == 0) &&
 		 (ee->len == 512) &&
 		 (data[0] == OTP_INDICATOR_1))
-		return lan78xx_write_raw_otp(dev, ee->offset, ee->len, data);
+		ret = lan78xx_write_raw_otp(dev, ee->offset, ee->len, data);
 
-	return -EINVAL;
+	usb_autopm_put_interface(dev->intf);
+
+	return ret;
 }
 
 static void lan78xx_get_strings(struct net_device *netdev, u32 stringset,
-- 
2.14.1

^ permalink raw reply related

* [PATCH v4 net 0/3] lan78xx: This series of patches are for lan78xx driver.
From: Nisar Sayed @ 2017-09-18 22:02 UTC (permalink / raw)
  To: davem; +Cc: UNGLinuxDriver, netdev

This series of patches are for lan78xx driver.

These patches fixes potential issues associated with lan78xx driver.

v4
- Updated changes to handle return values as per comments
- Updated EEPROM write handling as per comments

v3
- Updated chagnes as per comments

v2
- Added patch version information
- Added fixes tag
- Updated patch description
- Updated chagnes as per comments

v1
- Splitted patches as per comments
- Dropped "fixed_phy device support" and "Fix for system suspend" changes

Nisar Sayed (3):
  Fix for eeprom read/write when device auto suspend
  Allow EEPROM write for less than MAX_EEPROM_SIZE
  Use default values loaded from EEPROM/OTP after reset

 drivers/net/usb/lan78xx.c | 34 ++++++++++++++++++++++++----------
 1 file changed, 24 insertions(+), 10 deletions(-)

-- 
2.14.1

^ permalink raw reply

* Re: [PATCH RFC V1 net-next 0/6] Time based packet transmission
From: David Miller @ 2017-09-18 16:34 UTC (permalink / raw)
  To: rcochran
  Cc: netdev, linux-kernel, intel-wired-lan, andre.guedes, anna-maria,
	henrik, jesus.sanchez-palencia, john.stultz, tglx, vinicius.gomes
In-Reply-To: <cover.1505719061.git.rcochran@linutronix.de>

From: Richard Cochran <rcochran@linutronix.de>
Date: Mon, 18 Sep 2017 09:41:15 +0200

>   - The driver does not handle out of order packets.  If user space
>     sends a packet with an earlier Tx time, then the code should stop
>     the queue, reshuffle the descriptors accordingly, and then
>     restart the queue.

The user should simply be not allowed to do this.

Once the packet is in the device queue, that's it.  You cannot insert
a new packet to be transmitted before an already hw queued packet,
period.

Any out of order request should be rejected with an error.

I'd say the same is true for requests to send packets timed
in the past.

^ permalink raw reply

* [PATCH 3/3] inet: fix improper empty comparison
From: josef @ 2017-09-18 16:28 UTC (permalink / raw)
  To: davem, netdev, linux-kernel, crobinso, labbott, kernel-team
  Cc: Josef Bacik, stable
In-Reply-To: <1505752137-15522-1-git-send-email-jbacik@fb.com>

From: Josef Bacik <jbacik@fb.com>

When doing my reuseport rework I screwed up and changed a

if (hlist_empty(&tb->owners))

to

if (!hlist_empty(&tb->owners))

This is obviously bad as all of the reuseport/reuse logic was reversed,
which caused weird problems like allowing an ipv4 bind conflict if we
opened an ipv4 only socket on a port followed by an ipv6 only socket on
the same port.

Cc: stable@vger.kernel.org
Fixes: b9470c27607b ("inet: kill smallest_size and smallest_port")
Reported-by: Cole Robinson <crobinso@redhat.com>
Signed-off-by: Josef Bacik <jbacik@fb.com>
---
 net/ipv4/inet_connection_sock.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/net/ipv4/inet_connection_sock.c b/net/ipv4/inet_connection_sock.c
index a1bf30438bc5..c039c937ba90 100644
--- a/net/ipv4/inet_connection_sock.c
+++ b/net/ipv4/inet_connection_sock.c
@@ -321,7 +321,7 @@ int inet_csk_get_port(struct sock *sk, unsigned short snum)
 			goto fail_unlock;
 	}
 success:
-	if (!hlist_empty(&tb->owners)) {
+	if (hlist_empty(&tb->owners)) {
 		tb->fastreuse = reuse;
 		if (sk->sk_reuseport) {
 			tb->fastreuseport = FASTREUSEPORT_ANY;
-- 
2.7.4

^ permalink raw reply related

* [PATCH 2/3] net: use inet6_rcv_saddr to compare sockets
From: josef @ 2017-09-18 16:28 UTC (permalink / raw)
  To: davem, netdev, linux-kernel, crobinso, labbott, kernel-team
  Cc: Josef Bacik, stable
In-Reply-To: <1505752137-15522-1-git-send-email-jbacik@fb.com>

From: Josef Bacik <jbacik@fb.com>

In ipv6_rcv_saddr_equal() we need to use inet6_rcv_saddr(sk) for the
ipv6 compare with the fast socket information to make sure we're doing
the proper comparisons.

Cc: stable@vger.kernel.org
Fixes: 637bc8bbe6c0 ("inet: reset tb->fastreuseport when adding a reuseport sk")
Reported-and-tested-by: Cole Robinson <crobinso@redhat.com>
Signed-off-by: Josef Bacik <jbacik@fb.com>
---
 net/ipv4/inet_connection_sock.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/net/ipv4/inet_connection_sock.c b/net/ipv4/inet_connection_sock.c
index f87f4805e244..a1bf30438bc5 100644
--- a/net/ipv4/inet_connection_sock.c
+++ b/net/ipv4/inet_connection_sock.c
@@ -266,7 +266,7 @@ static inline int sk_reuseport_match(struct inet_bind_bucket *tb,
 #if IS_ENABLED(CONFIG_IPV6)
 	if (tb->fast_sk_family == AF_INET6)
 		return ipv6_rcv_saddr_equal(&tb->fast_v6_rcv_saddr,
-					    &sk->sk_v6_rcv_saddr,
+					    inet6_rcv_saddr(sk),
 					    tb->fast_rcv_saddr,
 					    sk->sk_rcv_saddr,
 					    tb->fast_ipv6_only,
-- 
2.7.4

^ permalink raw reply related

* [PATCH 1/3] net: set tb->fast_sk_family
From: josef @ 2017-09-18 16:28 UTC (permalink / raw)
  To: davem, netdev, linux-kernel, crobinso, labbott, kernel-team
  Cc: Josef Bacik, stable
In-Reply-To: <1505752137-15522-1-git-send-email-jbacik@fb.com>

From: Josef Bacik <jbacik@fb.com>

We need to set the tb->fast_sk_family properly so we can use the proper
comparison function for all subsequent reuseport bind requests.

Cc: stable@vger.kernel.org
Fixes: 637bc8bbe6c0 ("inet: reset tb->fastreuseport when adding a reuseport sk")
Reported-and-tested-by: Cole Robinson <crobinso@redhat.com>
Signed-off-by: Josef Bacik <jbacik@fb.com>
---
 net/ipv4/inet_connection_sock.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/net/ipv4/inet_connection_sock.c b/net/ipv4/inet_connection_sock.c
index b9c64b40a83a..f87f4805e244 100644
--- a/net/ipv4/inet_connection_sock.c
+++ b/net/ipv4/inet_connection_sock.c
@@ -328,6 +328,7 @@ int inet_csk_get_port(struct sock *sk, unsigned short snum)
 			tb->fastuid = uid;
 			tb->fast_rcv_saddr = sk->sk_rcv_saddr;
 			tb->fast_ipv6_only = ipv6_only_sock(sk);
+			tb->fast_sk_family = sk->sk_family;
 #if IS_ENABLED(CONFIG_IPV6)
 			tb->fast_v6_rcv_saddr = sk->sk_v6_rcv_saddr;
 #endif
@@ -354,6 +355,7 @@ int inet_csk_get_port(struct sock *sk, unsigned short snum)
 				tb->fastuid = uid;
 				tb->fast_rcv_saddr = sk->sk_rcv_saddr;
 				tb->fast_ipv6_only = ipv6_only_sock(sk);
+				tb->fast_sk_family = sk->sk_family;
 #if IS_ENABLED(CONFIG_IPV6)
 				tb->fast_v6_rcv_saddr = sk->sk_v6_rcv_saddr;
 #endif
-- 
2.7.4

^ permalink raw reply related

* [PATCH 0/3] fix reuseaddr regression
From: josef @ 2017-09-18 16:28 UTC (permalink / raw)
  To: davem, netdev, linux-kernel, crobinso, labbott, kernel-team

I introduced a regression when reworking the fastreuse port stuff that allows
bind conflicts to occur once a reuseaddr socket successfully opens on an
existing tb.  The root cause is I reversed an if statement which caused us to
set the tb as if there were no owners on the socket if there were, which
obviously is not correct.

Dave I have follow up patches that will add a selftest for this case and I ran
the other reuseport related tests as well.  These need to go in pretty quickly
as it breaks kvm, I've marked them for stable.  Sorry for the regression,

Josef

^ permalink raw reply

* Re: [PATCH 06/16] thunderbolt: Add support for XDomain discovery protocol
From: Dan Williams @ 2017-09-18 16:15 UTC (permalink / raw)
  To: Mika Westerberg, Greg Kroah-Hartman, David S . Miller
  Cc: Andreas Noever, Michael Jamet, Yehezkel Bernat, Amir Levy,
	Mario.Limonciello, Lukas Wunner, Andy Shevchenko, linux-kernel,
	netdev
In-Reply-To: <1505751137.11871.2.camel@redhat.com>

On Mon, 2017-09-18 at 11:12 -0500, Dan Williams wrote:
> On Mon, 2017-09-18 at 18:30 +0300, Mika Westerberg wrote:
> > When two hosts are connected over a Thunderbolt cable, there is a
> > protocol they can use to communicate capabilities supported by the
> > host.
> > The discovery protocol uses automatically configured control
> > channel
> > (ring 0) and is build on top of request/response transactions using
> > special XDomain primitives provided by the Thunderbolt base
> > protocol.
> > 
> > The capabilities consists of a root directory block of basic
> > properties
> > used for identification of the host, and then there can be zero or
> > more
> > directories each describing a Thunderbolt service and its
> > capabilities.
> > 
> > Once both sides have discovered what is supported the two hosts can
> > setup high-speed DMA paths and transfer data to the other side
> > using
> > whatever protocol was agreed based on the properties. The software
> > protocol used to communicate which DMA paths to enable is service
> > specific.
> > 
> > This patch adds support for the XDomain discovery protocol to the
> > Thunderbolt bus. We model each remote host connection as a Linux
> > XDomain
> > device. For each Thunderbolt service found supported on the XDomain
> > device, we create Linux Thunderbolt service device which
> > Thunderbolt
> > service drivers can then bind to based on the protocol
> > identification
> > information retrieved from the property directory describing the
> > service.
> > 
> > This code is based on the work done by Amir Levy and Michael Jamet.
> > 
> > Signed-off-by: Michael Jamet <michael.jamet@intel.com>
> > Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
> > Reviewed-by: Yehezkel Bernat <yehezkel.bernat@intel.com>
> > ---
> >  Documentation/ABI/testing/sysfs-bus-thunderbolt |   48 +
> >  drivers/thunderbolt/Makefile                    |    2 +-
> >  drivers/thunderbolt/ctl.c                       |   11 +-
> >  drivers/thunderbolt/ctl.h                       |    2 +-
> >  drivers/thunderbolt/domain.c                    |  197 ++-
> >  drivers/thunderbolt/icm.c                       |  218 +++-
> >  drivers/thunderbolt/nhi.h                       |    2 +
> >  drivers/thunderbolt/switch.c                    |    7 +-
> >  drivers/thunderbolt/tb.h                        |   39 +-
> >  drivers/thunderbolt/tb_msgs.h                   |  123 ++
> >  drivers/thunderbolt/xdomain.c                   | 1576
> > +++++++++++++++++++++++
> >  include/linux/mod_devicetable.h                 |   26 +
> >  include/linux/thunderbolt.h                     |  242 ++++
> >  scripts/mod/devicetable-offsets.c               |    7 +
> >  scripts/mod/file2alias.c                        |   25 +
> >  15 files changed, 2507 insertions(+), 18 deletions(-)
> >  create mode 100644 drivers/thunderbolt/xdomain.c
> > 
> > diff --git a/Documentation/ABI/testing/sysfs-bus-thunderbolt
> > b/Documentation/ABI/testing/sysfs-bus-thunderbolt
> > index 392bef5bd399..cb48850bd79b 100644
> > --- a/Documentation/ABI/testing/sysfs-bus-thunderbolt
> > +++ b/Documentation/ABI/testing/sysfs-bus-thunderbolt
> > @@ -110,3 +110,51 @@ Description:	When new NVM image is
> > written to the non-active NVM
> >  		is directly the status value from the DMA
> > configuration
> >  		based mailbox before the device is power cycled.
> > Writing
> >  		0 here clears the status.
> > +
> > +What:		/sys/bus/thunderbolt/devices/<xdomain>.<servi
> > ce
> > > /key
> > 
> > +Date:		Dec 2017
> > +KernelVersion:	4.14
> > +Contact:	thunderbolt-software@lists.01.org
> > +Description:	This contains name of the property directory
> > the
> > XDomain
> > +		service exposes. This entry describes the protocol
> > in
> > +		question. Following directories are already
> > reserved
> > by
> > +		the Apple XDomain specification:
> > +
> > +		network:  IP/ethernet over Thunderbolt
> > +		targetdm: Target disk mode protocol over
> > Thunderbolt
> > +		extdisp:  External display mode protocol over
> > Thunderbolt
> > +
> > +What:		/sys/bus/thunderbolt/devices/<xdomain>.<servi
> > ce
> > > /modalias
> > 
> > +Date:		Dec 2017
> > +KernelVersion:	4.14
> > +Contact:	thunderbolt-software@lists.01.org
> > +Description:	Stores the same MODALIAS value emitted by
> > uevent
> > for
> > +		the XDomain service. Format: tbtsvc:kSpNvNrN
> > +
> > +What:		/sys/bus/thunderbolt/devices/<xdomain>.<servi
> > ce
> > > /prtcid
> > 
> > +Date:		Dec 2017
> > +KernelVersion:	4.14
> > +Contact:	thunderbolt-software@lists.01.org
> > +Description:	This contains XDomain protocol identifier the
> > XDomain
> > +		service supports.
> > +
> > +What:		/sys/bus/thunderbolt/devices/<xdomain>.<servi
> > ce
> > > /prtcvers
> > 
> > +Date:		Dec 2017
> > +KernelVersion:	4.14
> > +Contact:	thunderbolt-software@lists.01.org
> > +Description:	This contains XDomain protocol version the
> > XDomain
> > +		service supports.
> > +
> > +What:		/sys/bus/thunderbolt/devices/<xdomain>.<servi
> > ce
> > > /prtcrevs
> > 
> > +Date:		Dec 2017
> > +KernelVersion:	4.14
> > +Contact:	thunderbolt-software@lists.01.org
> > +Description:	This contains XDomain software version the
> > XDomain
> > +		service supports.
> > +
> > +What:		/sys/bus/thunderbolt/devices/<xdomain>.<servi
> > ce
> > > /prtcstns
> > 
> > +Date:		Dec 2017
> > +KernelVersion:	4.14
> > +Contact:	thunderbolt-software@lists.01.org
> > +Description:	This contains XDomain service specific
> > settings
> > as
> > +		bitmask. Format: %x
> > diff --git a/drivers/thunderbolt/Makefile
> > b/drivers/thunderbolt/Makefile
> > index 7afd21f5383a..f2f0de27252b 100644
> > --- a/drivers/thunderbolt/Makefile
> > +++ b/drivers/thunderbolt/Makefile
> > @@ -1,3 +1,3 @@
> >  obj-${CONFIG_THUNDERBOLT} := thunderbolt.o
> >  thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o path.o
> > tunnel_pci.o eeprom.o
> > -thunderbolt-objs += domain.o dma_port.o icm.o property.o
> > +thunderbolt-objs += domain.o dma_port.o icm.o property.o xdomain.o
> > diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c
> > index e6a4c9458c76..46e393c5fd1d 100644
> > --- a/drivers/thunderbolt/ctl.c
> > +++ b/drivers/thunderbolt/ctl.c
> > @@ -368,10 +368,10 @@ static int tb_ctl_tx(struct tb_ctl *ctl,
> > const
> > void *data, size_t len,
> >  /**
> >   * tb_ctl_handle_event() - acknowledge a plug event, invoke ctl-
> > > callback
> > 
> >   */
> > -static void tb_ctl_handle_event(struct tb_ctl *ctl, enum
> > tb_cfg_pkg_type type,
> > +static bool tb_ctl_handle_event(struct tb_ctl *ctl, enum
> > tb_cfg_pkg_type type,
> >  				struct ctl_pkg *pkg, size_t size)
> >  {
> > -	ctl->callback(ctl->callback_data, type, pkg->buffer,
> > size);
> > +	return ctl->callback(ctl->callback_data, type, pkg-
> > >buffer,
> > size);
> >  }
> >  
> >  static void tb_ctl_rx_submit(struct ctl_pkg *pkg)
> > @@ -444,6 +444,8 @@ static void tb_ctl_rx_callback(struct tb_ring
> > *ring, struct ring_frame *frame,
> >  		break;
> >  
> >  	case TB_CFG_PKG_EVENT:
> > +	case TB_CFG_PKG_XDOMAIN_RESP:
> > +	case TB_CFG_PKG_XDOMAIN_REQ:
> >  		if (*(__be32 *)(pkg->buffer + frame->size) !=
> > crc32)
> > {
> >  			tb_ctl_err(pkg->ctl,
> >  				   "RX: checksum mismatch,
> > dropping
> > packet\n");
> > @@ -451,8 +453,9 @@ static void tb_ctl_rx_callback(struct tb_ring
> > *ring, struct ring_frame *frame,
> >  		}
> >  		/* Fall through */
> >  	case TB_CFG_PKG_ICM_EVENT:
> > -		tb_ctl_handle_event(pkg->ctl, frame->eof, pkg,
> > frame->size);
> > -		goto rx;
> > +		if (tb_ctl_handle_event(pkg->ctl, frame->eof, pkg,
> > frame->size))
> > +			goto rx;
> > +		break;
> >  
> >  	default:
> >  		break;
> > diff --git a/drivers/thunderbolt/ctl.h b/drivers/thunderbolt/ctl.h
> > index d0f21e1e0b8b..85c49dd301ea 100644
> > --- a/drivers/thunderbolt/ctl.h
> > +++ b/drivers/thunderbolt/ctl.h
> > @@ -16,7 +16,7 @@
> >  /* control channel */
> >  struct tb_ctl;
> >  
> > -typedef void (*event_cb)(void *data, enum tb_cfg_pkg_type type,
> > +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);
> > diff --git a/drivers/thunderbolt/domain.c
> > b/drivers/thunderbolt/domain.c
> > index 9f2dcd48974d..29d6436ec8ce 100644
> > --- a/drivers/thunderbolt/domain.c
> > +++ b/drivers/thunderbolt/domain.c
> > @@ -20,6 +20,98 @@
> >  
> >  static DEFINE_IDA(tb_domain_ida);
> >  
> > +static bool match_service_id(const struct tb_service_id *id,
> > +			     const struct tb_service *svc)
> > +{
> > +	if (id->match_flags & TBSVC_MATCH_PROTOCOL_KEY) {
> > +		if (strcmp(id->protocol_key, svc->key))
> > +			return false;
> > +	}
> > +
> > +	if (id->match_flags & TBSVC_MATCH_PROTOCOL_ID) {
> > +		if (id->protocol_id != svc->prtcid)
> > +			return false;
> > +	}
> > +
> > +	if (id->match_flags & TBSVC_MATCH_PROTOCOL_VERSION) {
> > +		if (id->protocol_version != svc->prtcvers)
> > +			return false;
> > +	}
> > +
> > +	if (id->match_flags & TBSVC_MATCH_PROTOCOL_VERSION) {
> > +		if (id->protocol_revision != svc->prtcrevs)
> > +			return false;
> > +	}
> > +
> > +	return true;
> > +}
> > +
> > +static const struct tb_service_id *__tb_service_match(struct
> > device
> > *dev,
> > +						      struct
> > device_driver *drv)
> > +{
> > +	struct tb_service_driver *driver;
> > +	const struct tb_service_id *ids;
> > +	struct tb_service *svc;
> > +
> > +	svc = tb_to_service(dev);
> > +	if (!svc)
> > +		return NULL;
> > +
> > +	driver = container_of(drv, struct tb_service_driver,
> > driver);
> > +	if (!driver->id_table)
> > +		return NULL;
> > +
> > +	for (ids = driver->id_table; ids->match_flags != 0; ids++)
> > {
> > +		if (match_service_id(ids, svc))
> > +			return ids;
> > +	}
> > +
> > +	return NULL;
> > +}
> > +
> > +static int tb_service_match(struct device *dev, struct
> > device_driver
> > *drv)
> > +{
> > +	return !!__tb_service_match(dev, drv);
> > +}
> > +
> > +static int tb_service_probe(struct device *dev)
> > +{
> > +	struct tb_service *svc = tb_to_service(dev);
> > +	struct tb_service_driver *driver;
> > +	const struct tb_service_id *id;
> > +
> > +	driver = container_of(dev->driver, struct
> > tb_service_driver,
> > driver);
> > +	id = __tb_service_match(dev, &driver->driver);
> > +
> > +	return driver->probe(svc, id);
> 
> Could you pass 'dev' to the probe function so that things like the
> network sub-driver can sensibly link the netdev to the parent
> hardware
> in sysfs with SET_NETDEV_DEV()?

Nevermind, I'm blind, you've handled that already in patch #16.  Ignore
me.

Dan


> Dan
> 
> > +}
> > +
> > +static int tb_service_remove(struct device *dev)
> > +{
> > +	struct tb_service *svc = tb_to_service(dev);
> > +	struct tb_service_driver *driver;
> > +
> > +	driver = container_of(dev->driver, struct
> > tb_service_driver,
> > driver);
> > +	if (driver->remove)
> > +		driver->remove(svc);
> > +
> > +	return 0;
> > +}
> > +
> > +static void tb_service_shutdown(struct device *dev)
> > +{
> > +	struct tb_service_driver *driver;
> > +	struct tb_service *svc;
> > +
> > +	svc = tb_to_service(dev);
> > +	if (!svc || !dev->driver)
> > +		return;
> > +
> > +	driver = container_of(dev->driver, struct
> > tb_service_driver,
> > driver);
> > +	if (driver->shutdown)
> > +		driver->shutdown(svc);
> > +}
> > +
> >  static const char * const tb_security_names[] = {
> >  	[TB_SECURITY_NONE] = "none",
> >  	[TB_SECURITY_USER] = "user",
> > @@ -52,6 +144,10 @@ static const struct attribute_group
> > *domain_attr_groups[] = {
> >  
> >  struct bus_type tb_bus_type = {
> >  	.name = "thunderbolt",
> > +	.match = tb_service_match,
> > +	.probe = tb_service_probe,
> > +	.remove = tb_service_remove,
> > +	.shutdown = tb_service_shutdown,
> >  };
> >  
> >  static void tb_domain_release(struct device *dev)
> > @@ -128,17 +224,26 @@ struct tb *tb_domain_alloc(struct tb_nhi
> > *nhi,
> > size_t privsize)
> >  	return NULL;
> >  }
> >  
> > -static void tb_domain_event_cb(void *data, enum tb_cfg_pkg_type
> > type,
> > +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;
> > +		return true;
> >  	}
> >  
> > -	tb->cm_ops->handle_event(tb, type, buf, size);
> > +	switch (type) {
> > +	case TB_CFG_PKG_XDOMAIN_REQ:
> > +	case TB_CFG_PKG_XDOMAIN_RESP:
> > +		return tb_xdomain_handle_request(tb, type, buf,
> > size);
> > +
> > +	default:
> > +		tb->cm_ops->handle_event(tb, type, buf, size);
> > +	}
> > +
> > +	return true;
> >  }
> >  
> >  /**
> > @@ -443,9 +548,92 @@ int tb_domain_disconnect_pcie_paths(struct tb
> > *tb)
> >  	return tb->cm_ops->disconnect_pcie_paths(tb);
> >  }
> >  
> > +/**
> > + * tb_domain_approve_xdomain_paths() - Enable DMA paths for
> > XDomain
> > + * @tb: Domain enabling the DMA paths
> > + * @xd: XDomain DMA paths are created to
> > + *
> > + * Calls connection manager specific method to enable DMA paths to
> > the
> > + * XDomain in question.
> > + *
> > + * Return: 0% in case of success and negative errno otherwise. In
> > + * 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)
> > +{
> > +	if (!tb->cm_ops->approve_xdomain_paths)
> > +		return -ENOTSUPP;
> > +
> > +	return tb->cm_ops->approve_xdomain_paths(tb, xd);
> > +}
> > +
> > +/**
> > + * tb_domain_disconnect_xdomain_paths() - Disable DMA paths for
> > XDomain
> > + * @tb: Domain disabling the DMA paths
> > + * @xd: XDomain whose DMA paths are disconnected
> > + *
> > + * Calls connection manager specific method to disconnect DMA
> > paths
> > to
> > + * the XDomain in question.
> > + *
> > + * Return: 0% in case of success and negative errno otherwise. In
> > + * 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)
> > +{
> > +	if (!tb->cm_ops->disconnect_xdomain_paths)
> > +		return -ENOTSUPP;
> > +
> > +	return tb->cm_ops->disconnect_xdomain_paths(tb, xd);
> > +}
> > +
> > +static int disconnect_xdomain(struct device *dev, void *data)
> > +{
> > +	struct tb_xdomain *xd;
> > +	struct tb *tb = data;
> > +	int ret = 0;
> > +
> > +	xd = tb_to_xdomain(dev);
> > +	if (xd && xd->tb == tb)
> > +		ret = tb_xdomain_disable_paths(xd);
> > +
> > +	return ret;
> > +}
> > +
> > +/**
> > + * tb_domain_disconnect_all_paths() - Disconnect all paths for the
> > domain
> > + * @tb: Domain whose paths are disconnected
> > + *
> > + * This function can be used to disconnect all paths (PCIe,
> > XDomain)
> > for
> > + * example in preparation for host NVM firmware upgrade. After
> > this
> > is
> > + * called the paths cannot be established without reseting the
> > switch.
> > + *
> > + * Return: %0 in case of success and negative errno otherwise.
> > + */
> > +int tb_domain_disconnect_all_paths(struct tb *tb)
> > +{
> > +	int ret;
> > +
> > +	ret = tb_domain_disconnect_pcie_paths(tb);
> > +	if (ret)
> > +		return ret;
> > +
> > +	return bus_for_each_dev(&tb_bus_type, NULL, tb,
> > disconnect_xdomain);
> > +}
> > +
> >  int tb_domain_init(void)
> >  {
> > -	return bus_register(&tb_bus_type);
> > +	int ret;
> > +
> > +	ret = tb_xdomain_init();
> > +	if (ret)
> > +		return ret;
> > +	ret = bus_register(&tb_bus_type);
> > +	if (ret)
> > +		tb_xdomain_exit();
> > +
> > +	return ret;
> >  }
> >  
> >  void tb_domain_exit(void)
> > @@ -453,4 +641,5 @@ void tb_domain_exit(void)
> >  	bus_unregister(&tb_bus_type);
> >  	ida_destroy(&tb_domain_ida);
> >  	tb_switch_exit();
> > +	tb_xdomain_exit();
> >  }
> > diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c
> > index 8c22b91ed040..ab02d13f40b7 100644
> > --- a/drivers/thunderbolt/icm.c
> > +++ b/drivers/thunderbolt/icm.c
> > @@ -60,6 +60,8 @@
> >   * @get_route: Find a route string for given switch
> >   * @device_connected: Handle device connected ICM message
> >   * @device_disconnected: Handle device disconnected ICM message
> > + * @xdomain_connected - Handle XDomain connected ICM message
> > + * @xdomain_disconnected - Handle XDomain disconnected ICM message
> >   */
> >  struct icm {
> >  	struct mutex request_lock;
> > @@ -74,6 +76,10 @@ struct icm {
> >  				 const struct icm_pkg_header
> > *hdr);
> >  	void (*device_disconnected)(struct tb *tb,
> >  				    const struct icm_pkg_header
> > *hdr);
> > +	void (*xdomain_connected)(struct tb *tb,
> > +				  const struct icm_pkg_header
> > *hdr);
> > +	void (*xdomain_disconnected)(struct tb *tb,
> > +				     const struct icm_pkg_header
> > *hdr);
> >  };
> >  
> >  struct icm_notification {
> > @@ -89,7 +95,10 @@ static inline struct tb *icm_to_tb(struct icm
> > *icm)
> >  
> >  static inline u8 phy_port_from_route(u64 route, u8 depth)
> >  {
> > -	return tb_phy_port_from_link(route >> ((depth - 1) * 8));
> > +	u8 link;
> > +
> > +	link = depth ? route >> ((depth - 1) * 8) : route;
> > +	return tb_phy_port_from_link(link);
> >  }
> >  
> >  static inline u8 dual_link_from_link(u8 link)
> > @@ -320,6 +329,51 @@ 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)
> > +{
> > +	struct icm_fr_pkg_approve_xdomain_response reply;
> > +	struct icm_fr_pkg_approve_xdomain request;
> > +	int ret;
> > +
> > +	memset(&request, 0, sizeof(request));
> > +	request.hdr.code = ICM_APPROVE_XDOMAIN;
> > +	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;
> > +
> > +	memset(&reply, 0, sizeof(reply));
> > +	ret = icm_request(tb, &request, sizeof(request), &reply,
> > sizeof(reply),
> > +			  1, ICM_TIMEOUT);
> > +	if (ret)
> > +		return ret;
> > +
> > +	if (reply.hdr.flags & ICM_FLAGS_ERROR)
> > +		return -EIO;
> > +
> > +	return 0;
> > +}
> > +
> > +static int icm_fr_disconnect_xdomain_paths(struct tb *tb, struct
> > tb_xdomain *xd)
> > +{
> > +	u8 phy_port;
> > +	u8 cmd;
> > +
> > +	phy_port = tb_phy_port_from_link(xd->link);
> > +	if (phy_port == 0)
> > +		cmd = NHI_MAILBOX_DISCONNECT_PA;
> > +	else
> > +		cmd = NHI_MAILBOX_DISCONNECT_PB;
> > +
> > +	nhi_mailbox_cmd(tb->nhi, cmd, 1);
> > +	usleep_range(10, 50);
> > +	nhi_mailbox_cmd(tb->nhi, cmd, 2);
> > +	return 0;
> > +}
> > +
> >  static void remove_switch(struct tb_switch *sw)
> >  {
> >  	struct tb_switch *parent_sw;
> > @@ -475,6 +529,141 @@ icm_fr_device_disconnected(struct tb *tb,
> > const
> > struct icm_pkg_header *hdr)
> >  	tb_switch_put(sw);
> >  }
> >  
> > +static void remove_xdomain(struct tb_xdomain *xd)
> > +{
> > +	struct tb_switch *sw;
> > +
> > +	sw = tb_to_switch(xd->dev.parent);
> > +	tb_port_at(xd->route, sw)->xdomain = NULL;
> > +	tb_xdomain_remove(xd);
> > +}
> > +
> > +static void
> > +icm_fr_xdomain_connected(struct tb *tb, const struct
> > icm_pkg_header
> > *hdr)
> > +{
> > +	const struct icm_fr_event_xdomain_connected *pkg =
> > +		(const struct icm_fr_event_xdomain_connected
> > *)hdr;
> > +	struct tb_xdomain *xd;
> > +	struct tb_switch *sw;
> > +	u8 link, depth;
> > +	bool approved;
> > +	u64 route;
> > +
> > +	/*
> > +	 * After NVM upgrade adding root switch device fails
> > because
> > we
> > +	 * initiated reset. During that time ICM might still send
> > +	 * XDomain connected message which we ignore here.
> > +	 */
> > +	if (!tb->root_switch)
> > +		return;
> > +
> > +	link = pkg->link_info & ICM_LINK_INFO_LINK_MASK;
> > +	depth = (pkg->link_info & ICM_LINK_INFO_DEPTH_MASK) >>
> > +		ICM_LINK_INFO_DEPTH_SHIFT;
> > +	approved = pkg->link_info & ICM_LINK_INFO_APPROVED;
> > +
> > +	if (link > ICM_MAX_LINK || depth > ICM_MAX_DEPTH) {
> > +		tb_warn(tb, "invalid topology %u.%u, ignoring\n",
> > link, depth);
> > +		return;
> > +	}
> > +
> > +	route = get_route(pkg->local_route_hi, pkg-
> > >local_route_lo);
> > +
> > +	xd = tb_xdomain_find_by_uuid(tb, &pkg->remote_uuid);
> > +	if (xd) {
> > +		u8 xd_phy_port, phy_port;
> > +
> > +		xd_phy_port = phy_port_from_route(xd->route, xd-
> > > depth);
> > 
> > +		phy_port = phy_port_from_route(route, depth);
> > +
> > +		if (xd->depth == depth && xd_phy_port == phy_port)
> > {
> > +			xd->link = link;
> > +			xd->route = route;
> > +			xd->is_unplugged = false;
> > +			tb_xdomain_put(xd);
> > +			return;
> > +		}
> > +
> > +		/*
> > +		 * If we find an existing XDomain connection
> > remove
> > it
> > +		 * now. We need to go through login handshake and
> > +		 * everything anyway to be able to re-establish
> > the
> > +		 * connection.
> > +		 */
> > +		remove_xdomain(xd);
> > +		tb_xdomain_put(xd);
> > +	}
> > +
> > +	/*
> > +	 * Look if there already exists an XDomain in the same
> > place
> > +	 * than the new one and in that case remove it because it
> > is
> > +	 * most likely another host that got disconnected.
> > +	 */
> > +	xd = tb_xdomain_find_by_link_depth(tb, link, depth);
> > +	if (!xd) {
> > +		u8 dual_link;
> > +
> > +		dual_link = dual_link_from_link(link);
> > +		if (dual_link)
> > +			xd = tb_xdomain_find_by_link_depth(tb,
> > dual_link,
> > +							   depth);
> > +	}
> > +	if (xd) {
> > +		remove_xdomain(xd);
> > +		tb_xdomain_put(xd);
> > +	}
> > +
> > +	/*
> > +	 * If the user disconnected a switch during suspend and
> > +	 * connected another host to the same port, remove the
> > switch
> > +	 * first.
> > +	 */
> > +	sw = get_switch_at_route(tb->root_switch, route);
> > +	if (sw)
> > +		remove_switch(sw);
> > +
> > +	sw = tb_switch_find_by_link_depth(tb, link, depth);
> > +	if (!sw) {
> > +		tb_warn(tb, "no switch exists at %u.%u,
> > ignoring\n",
> > link,
> > +			depth);
> > +		return;
> > +	}
> > +
> > +	xd = tb_xdomain_alloc(sw->tb, &sw->dev, route,
> > +			      &pkg->local_uuid, &pkg-
> > >remote_uuid);
> > +	if (!xd) {
> > +		tb_switch_put(sw);
> > +		return;
> > +	}
> > +
> > +	xd->link = link;
> > +	xd->depth = depth;
> > +
> > +	tb_port_at(route, sw)->xdomain = xd;
> > +
> > +	tb_xdomain_add(xd);
> > +	tb_switch_put(sw);
> > +}
> > +
> > +static void
> > +icm_fr_xdomain_disconnected(struct tb *tb, const struct
> > icm_pkg_header *hdr)
> > +{
> > +	const struct icm_fr_event_xdomain_disconnected *pkg =
> > +		(const struct icm_fr_event_xdomain_disconnected
> > *)hdr;
> > +	struct tb_xdomain *xd;
> > +
> > +	/*
> > +	 * If the connection is through one or multiple devices,
> > the
> > +	 * XDomain device is removed along with them so it is fine
> > if we
> > +	 * cannot find it here.
> > +	 */
> > +	xd = tb_xdomain_find_by_uuid(tb, &pkg->remote_uuid);
> > +	if (xd) {
> > +		remove_xdomain(xd);
> > +		tb_xdomain_put(xd);
> > +	}
> > +}
> > +
> >  static struct pci_dev *get_upstream_port(struct pci_dev *pdev)
> >  {
> >  	struct pci_dev *parent;
> > @@ -594,6 +783,12 @@ static void icm_handle_notification(struct
> > work_struct *work)
> >  	case ICM_EVENT_DEVICE_DISCONNECTED:
> >  		icm->device_disconnected(tb, n->pkg);
> >  		break;
> > +	case ICM_EVENT_XDOMAIN_CONNECTED:
> > +		icm->xdomain_connected(tb, n->pkg);
> > +		break;
> > +	case ICM_EVENT_XDOMAIN_DISCONNECTED:
> > +		icm->xdomain_disconnected(tb, n->pkg);
> > +		break;
> >  	}
> >  
> >  	mutex_unlock(&tb->lock);
> > @@ -927,6 +1122,10 @@ static void icm_unplug_children(struct
> > tb_switch *sw)
> >  
> >  		if (tb_is_upstream_port(port))
> >  			continue;
> > +		if (port->xdomain) {
> > +			port->xdomain->is_unplugged = true;
> > +			continue;
> > +		}
> >  		if (!port->remote)
> >  			continue;
> >  
> > @@ -943,6 +1142,13 @@ static void
> > icm_free_unplugged_children(struct
> > tb_switch *sw)
> >  
> >  		if (tb_is_upstream_port(port))
> >  			continue;
> > +
> > +		if (port->xdomain && port->xdomain->is_unplugged)
> > {
> > +			tb_xdomain_remove(port->xdomain);
> > +			port->xdomain = NULL;
> > +			continue;
> > +		}
> > +
> >  		if (!port->remote)
> >  			continue;
> >  
> > @@ -1009,8 +1215,10 @@ static int icm_start(struct tb *tb)
> >  	tb->root_switch->no_nvm_upgrade = x86_apple_machine;
> >  
> >  	ret = tb_switch_add(tb->root_switch);
> > -	if (ret)
> > +	if (ret) {
> >  		tb_switch_put(tb->root_switch);
> > +		tb->root_switch = NULL;
> > +	}
> >  
> >  	return ret;
> >  }
> > @@ -1042,6 +1250,8 @@ static const struct tb_cm_ops icm_fr_ops = {
> >  	.add_switch_key = icm_fr_add_switch_key,
> >  	.challenge_switch_key = icm_fr_challenge_switch_key,
> >  	.disconnect_pcie_paths = icm_disconnect_pcie_paths,
> > +	.approve_xdomain_paths = icm_fr_approve_xdomain_paths,
> > +	.disconnect_xdomain_paths =
> > icm_fr_disconnect_xdomain_paths,
> >  };
> >  
> >  struct tb *icm_probe(struct tb_nhi *nhi)
> > @@ -1064,6 +1274,8 @@ struct tb *icm_probe(struct tb_nhi *nhi)
> >  		icm->get_route = icm_fr_get_route;
> >  		icm->device_connected = icm_fr_device_connected;
> >  		icm->device_disconnected =
> > icm_fr_device_disconnected;
> > +		icm->xdomain_connected = icm_fr_xdomain_connected;
> > +		icm->xdomain_disconnected =
> > icm_fr_xdomain_disconnected;
> >  		tb->cm_ops = &icm_fr_ops;
> >  		break;
> >  
> > @@ -1077,6 +1289,8 @@ struct tb *icm_probe(struct tb_nhi *nhi)
> >  		icm->get_route = icm_ar_get_route;
> >  		icm->device_connected = icm_fr_device_connected;
> >  		icm->device_disconnected =
> > icm_fr_device_disconnected;
> > +		icm->xdomain_connected = icm_fr_xdomain_connected;
> > +		icm->xdomain_disconnected =
> > icm_fr_xdomain_disconnected;
> >  		tb->cm_ops = &icm_fr_ops;
> >  		break;
> >  	}
> > diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h
> > index 5b5bb2c436be..0e05828983db 100644
> > --- a/drivers/thunderbolt/nhi.h
> > +++ b/drivers/thunderbolt/nhi.h
> > @@ -157,6 +157,8 @@ enum nhi_mailbox_cmd {
> >  	NHI_MAILBOX_SAVE_DEVS = 0x05,
> >  	NHI_MAILBOX_DISCONNECT_PCIE_PATHS = 0x06,
> >  	NHI_MAILBOX_DRV_UNLOADS = 0x07,
> > +	NHI_MAILBOX_DISCONNECT_PA = 0x10,
> > +	NHI_MAILBOX_DISCONNECT_PB = 0x11,
> >  	NHI_MAILBOX_ALLOW_ALL_DEVS = 0x23,
> >  };
> >  
> > diff --git a/drivers/thunderbolt/switch.c
> > b/drivers/thunderbolt/switch.c
> > index 53f40c57df59..dfc357d33e1e 100644
> > --- a/drivers/thunderbolt/switch.c
> > +++ b/drivers/thunderbolt/switch.c
> > @@ -171,11 +171,11 @@ static int nvm_authenticate_host(struct
> > tb_switch *sw)
> >  
> >  	/*
> >  	 * Root switch NVM upgrade requires that we disconnect the
> > -	 * existing PCIe paths first (in case it is not in safe
> > mode
> > +	 * existing paths first (in case it is not in safe mode
> >  	 * already).
> >  	 */
> >  	if (!sw->safe_mode) {
> > -		ret = tb_domain_disconnect_pcie_paths(sw->tb);
> > +		ret = tb_domain_disconnect_all_paths(sw->tb);
> >  		if (ret)
> >  			return ret;
> >  		/*
> > @@ -1363,6 +1363,9 @@ void tb_switch_remove(struct tb_switch *sw)
> >  		if (sw->ports[i].remote)
> >  			tb_switch_remove(sw->ports[i].remote->sw);
> >  		sw->ports[i].remote = NULL;
> > +		if (sw->ports[i].xdomain)
> > +			tb_xdomain_remove(sw->ports[i].xdomain);
> > +		sw->ports[i].xdomain = NULL;
> >  	}
> >  
> >  	if (!sw->is_unplugged)
> > diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
> > index ea21d927bd09..74af9d4929ab 100644
> > --- a/drivers/thunderbolt/tb.h
> > +++ b/drivers/thunderbolt/tb.h
> > @@ -9,6 +9,7 @@
> >  
> >  #include <linux/nvmem-provider.h>
> >  #include <linux/pci.h>
> > +#include <linux/thunderbolt.h>
> >  #include <linux/uuid.h>
> >  
> >  #include "tb_regs.h"
> > @@ -109,14 +110,25 @@ struct tb_switch {
> >  
> >  /**
> >   * struct tb_port - a thunderbolt port, part of a tb_switch
> > + * @config: Cached port configuration read from registers
> > + * @sw: Switch the port belongs to
> > + * @remote: Remote port (%NULL if not connected)
> > + * @xdomain: Remote host (%NULL if not connected)
> > + * @cap_phy: Offset, zero if not found
> > + * @port: Port number on switch
> > + * @disabled: Disabled by eeprom
> > + * @dual_link_port: If the switch is connected using two ports,
> > points
> > + *		    to the other port.
> > + * @link_nr: Is this primary or secondary port on the dual_link.
> >   */
> >  struct tb_port {
> >  	struct tb_regs_port_header config;
> >  	struct tb_switch *sw;
> > -	struct tb_port *remote; /* remote port, NULL if not
> > connected */
> > -	int cap_phy; /* offset, zero if not found */
> > -	u8 port; /* port number on switch */
> > -	bool disabled; /* disabled by eeprom */
> > +	struct tb_port *remote;
> > +	struct tb_xdomain *xdomain;
> > +	int cap_phy;
> > +	u8 port;
> > +	bool disabled;
> >  	struct tb_port *dual_link_port;
> >  	u8 link_nr:1;
> >  };
> > @@ -189,6 +201,8 @@ struct tb_path {
> >   * @add_switch_key: Add key to switch
> >   * @challenge_switch_key: Challenge switch using key
> >   * @disconnect_pcie_paths: Disconnects PCIe paths before NVM
> > update
> > + * @approve_xdomain_paths: Approve (establish) XDomain DMA paths
> > + * @disconnect_xdomain_paths: Disconnect XDomain DMA paths
> >   */
> >  struct tb_cm_ops {
> >  	int (*driver_ready)(struct tb *tb);
> > @@ -205,6 +219,8 @@ 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);
> >  };
> >  
> >  static inline void *tb_priv(struct tb *tb)
> > @@ -331,6 +347,8 @@ extern struct device_type tb_switch_type;
> >  int tb_domain_init(void);
> >  void tb_domain_exit(void);
> >  void tb_switch_exit(void);
> > +int tb_xdomain_init(void);
> > +void tb_xdomain_exit(void);
> >  
> >  struct tb *tb_domain_alloc(struct tb_nhi *nhi, size_t privsize);
> >  int tb_domain_add(struct tb *tb);
> > @@ -343,6 +361,9 @@ 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_disconnect_all_paths(struct tb *tb);
> >  
> >  static inline void tb_domain_put(struct tb *tb)
> >  {
> > @@ -422,4 +443,14 @@ static inline u64 tb_downstream_route(struct
> > tb_port *port)
> >  	       | ((u64) port->port << (port->sw->config.depth *
> > 8));
> >  }
> >  
> > +bool tb_xdomain_handle_request(struct tb *tb, enum tb_cfg_pkg_type
> > type,
> > +			       const void *buf, size_t size);
> > +struct tb_xdomain *tb_xdomain_alloc(struct tb *tb, struct device
> > *parent,
> > +				    u64 route, const uuid_t
> > *local_uuid,
> > +				    const uuid_t *remote_uuid);
> > +void tb_xdomain_add(struct tb_xdomain *xd);
> > +void tb_xdomain_remove(struct tb_xdomain *xd);
> > +struct tb_xdomain *tb_xdomain_find_by_link_depth(struct tb *tb, u8
> > link,
> > +						 u8 depth);
> > +
> >  #endif
> > diff --git a/drivers/thunderbolt/tb_msgs.h
> > b/drivers/thunderbolt/tb_msgs.h
> > index fe3039b05da6..2a76908537a6 100644
> > --- a/drivers/thunderbolt/tb_msgs.h
> > +++ b/drivers/thunderbolt/tb_msgs.h
> > @@ -101,11 +101,14 @@ enum icm_pkg_code {
> >  	ICM_CHALLENGE_DEVICE = 0x5,
> >  	ICM_ADD_DEVICE_KEY = 0x6,
> >  	ICM_GET_ROUTE = 0xa,
> > +	ICM_APPROVE_XDOMAIN = 0x10,
> >  };
> >  
> >  enum icm_event_code {
> >  	ICM_EVENT_DEVICE_CONNECTED = 3,
> >  	ICM_EVENT_DEVICE_DISCONNECTED = 4,
> > +	ICM_EVENT_XDOMAIN_CONNECTED = 6,
> > +	ICM_EVENT_XDOMAIN_DISCONNECTED = 7,
> >  };
> >  
> >  struct icm_pkg_header {
> > @@ -188,6 +191,25 @@ struct icm_fr_event_device_disconnected {
> >  	u16 link_info;
> >  } __packed;
> >  
> > +struct icm_fr_event_xdomain_connected {
> > +	struct icm_pkg_header hdr;
> > +	u16 reserved;
> > +	u16 link_info;
> > +	uuid_t remote_uuid;
> > +	uuid_t local_uuid;
> > +	u32 local_route_hi;
> > +	u32 local_route_lo;
> > +	u32 remote_route_hi;
> > +	u32 remote_route_lo;
> > +} __packed;
> > +
> > +struct icm_fr_event_xdomain_disconnected {
> > +	struct icm_pkg_header hdr;
> > +	u16 reserved;
> > +	u16 link_info;
> > +	uuid_t remote_uuid;
> > +} __packed;
> > +
> >  struct icm_fr_pkg_add_device_key {
> >  	struct icm_pkg_header hdr;
> >  	uuid_t ep_uuid;
> > @@ -224,6 +246,28 @@ struct icm_fr_pkg_challenge_device_response {
> >  	u32 response[8];
> >  } __packed;
> >  
> > +struct icm_fr_pkg_approve_xdomain {
> > +	struct icm_pkg_header hdr;
> > +	u16 reserved;
> > +	u16 link_info;
> > +	uuid_t remote_uuid;
> > +	u16 transmit_path;
> > +	u16 transmit_ring;
> > +	u16 receive_path;
> > +	u16 receive_ring;
> > +} __packed;
> > +
> > +struct icm_fr_pkg_approve_xdomain_response {
> > +	struct icm_pkg_header hdr;
> > +	u16 reserved;
> > +	u16 link_info;
> > +	uuid_t remote_uuid;
> > +	u16 transmit_path;
> > +	u16 transmit_ring;
> > +	u16 receive_path;
> > +	u16 receive_ring;
> > +} __packed;
> > +
> >  /* Alpine Ridge only messages */
> >  
> >  struct icm_ar_pkg_get_route {
> > @@ -240,4 +284,83 @@ struct icm_ar_pkg_get_route_response {
> >  	u32 route_lo;
> >  } __packed;
> >  
> > +/* XDomain messages */
> > +
> > +struct tb_xdomain_header {
> > +	u32 route_hi;
> > +	u32 route_lo;
> > +	u32 length_sn;
> > +} __packed;
> > +
> > +#define TB_XDOMAIN_LENGTH_MASK	GENMASK(5, 0)
> > +#define TB_XDOMAIN_SN_MASK	GENMASK(28, 27)
> > +#define TB_XDOMAIN_SN_SHIFT	27
> > +
> > +enum tb_xdp_type {
> > +	UUID_REQUEST_OLD = 1,
> > +	UUID_RESPONSE = 2,
> > +	PROPERTIES_REQUEST,
> > +	PROPERTIES_RESPONSE,
> > +	PROPERTIES_CHANGED_REQUEST,
> > +	PROPERTIES_CHANGED_RESPONSE,
> > +	ERROR_RESPONSE,
> > +	UUID_REQUEST = 12,
> > +};
> > +
> > +struct tb_xdp_header {
> > +	struct tb_xdomain_header xd_hdr;
> > +	uuid_t uuid;
> > +	u32 type;
> > +} __packed;
> > +
> > +struct tb_xdp_properties {
> > +	struct tb_xdp_header hdr;
> > +	uuid_t src_uuid;
> > +	uuid_t dst_uuid;
> > +	u16 offset;
> > +	u16 reserved;
> > +} __packed;
> > +
> > +struct tb_xdp_properties_response {
> > +	struct tb_xdp_header hdr;
> > +	uuid_t src_uuid;
> > +	uuid_t dst_uuid;
> > +	u16 offset;
> > +	u16 data_length;
> > +	u32 generation;
> > +	u32 data[0];
> > +} __packed;
> > +
> > +/*
> > + * Max length of data array single XDomain property response is
> > allowed
> > + * to carry.
> > + */
> > +#define TB_XDP_PROPERTIES_MAX_DATA_LENGTH	\
> > +	(((256 - 4 - sizeof(struct tb_xdp_properties_response))) /
> > 4)
> > +
> > +/* Maximum size of the total property block in dwords we allow */
> > +#define TB_XDP_PROPERTIES_MAX_LENGTH		500
> > +
> > +struct tb_xdp_properties_changed {
> > +	struct tb_xdp_header hdr;
> > +	uuid_t src_uuid;
> > +} __packed;
> > +
> > +struct tb_xdp_properties_changed_response {
> > +	struct tb_xdp_header hdr;
> > +} __packed;
> > +
> > +enum tb_xdp_error {
> > +	ERROR_SUCCESS,
> > +	ERROR_UNKNOWN_PACKET,
> > +	ERROR_UNKNOWN_DOMAIN,
> > +	ERROR_NOT_SUPPORTED,
> > +	ERROR_NOT_READY,
> > +};
> > +
> > +struct tb_xdp_error_response {
> > +	struct tb_xdp_header hdr;
> > +	u32 error;
> > +} __packed;
> > +
> >  #endif
> > diff --git a/drivers/thunderbolt/xdomain.c
> > b/drivers/thunderbolt/xdomain.c
> > new file mode 100644
> > index 000000000000..1b929be8fdd6
> > --- /dev/null
> > +++ b/drivers/thunderbolt/xdomain.c
> > @@ -0,0 +1,1576 @@
> > +/*
> > + * Thunderbolt XDomain discovery protocol support
> > + *
> > + * Copyright (C) 2017, Intel Corporation
> > + * Authors: Michael Jamet <michael.jamet@intel.com>
> > + *          Mika Westerberg <mika.westerberg@linux.intel.com>
> > + *
> > + * This program is free software; you can redistribute it and/or
> > modify
> > + * it under the terms of the GNU General Public License version 2
> > as
> > + * published by the Free Software Foundation.
> > + */
> > +
> > +#include <linux/device.h>
> > +#include <linux/kmod.h>
> > +#include <linux/module.h>
> > +#include <linux/utsname.h>
> > +#include <linux/uuid.h>
> > +#include <linux/workqueue.h>
> > +
> > +#include "tb.h"
> > +
> > +#define XDOMAIN_DEFAULT_TIMEOUT			5000 /* ms
> > */
> > +#define XDOMAIN_PROPERTIES_RETRIES		60
> > +#define XDOMAIN_PROPERTIES_CHANGED_RETRIES	10
> > +
> > +struct xdomain_request_work {
> > +	struct work_struct work;
> > +	struct tb_xdp_header *pkg;
> > +	struct tb *tb;
> > +};
> > +
> > +/* Serializes access to the properties and protocol handlers below
> > */
> > +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 */
> > +static LIST_HEAD(protocol_handlers);
> > +
> > +/* UUID for XDomain discovery protocol */
> > +static const uuid_t tb_xdp_uuid =
> > +	UUID_INIT(0xb638d70e, 0x42ff, 0x40bb,
> > +		  0x97, 0xc2, 0x90, 0xe2, 0xc0, 0xb2, 0xff, 0x07);
> > +
> > +static bool tb_xdomain_match(const struct tb_cfg_request *req,
> > +			     const struct ctl_pkg *pkg)
> > +{
> > +	switch (pkg->frame.eof) {
> > +	case TB_CFG_PKG_ERROR:
> > +		return true;
> > +
> > +	case TB_CFG_PKG_XDOMAIN_RESP: {
> > +		const struct tb_xdp_header *res_hdr = pkg->buffer;
> > +		const struct tb_xdp_header *req_hdr = req-
> > >request;
> > +		u8 req_seq, res_seq;
> > +
> > +		if (pkg->frame.size < req->response_size / 4)
> > +			return false;
> > +
> > +		/* Make sure route matches */
> > +		if ((res_hdr->xd_hdr.route_hi & ~BIT(31)) !=
> > +		     req_hdr->xd_hdr.route_hi)
> > +			return false;
> > +		if ((res_hdr->xd_hdr.route_lo) != req_hdr-
> > > xd_hdr.route_lo)
> > 
> > +			return false;
> > +
> > +		/* Then check that the sequence number matches */
> > +		res_seq = res_hdr->xd_hdr.length_sn &
> > TB_XDOMAIN_SN_MASK;
> > +		res_seq >>= TB_XDOMAIN_SN_SHIFT;
> > +		req_seq = req_hdr->xd_hdr.length_sn &
> > TB_XDOMAIN_SN_MASK;
> > +		req_seq >>= TB_XDOMAIN_SN_SHIFT;
> > +		if (res_seq != req_seq)
> > +			return false;
> > +
> > +		/* Check that the XDomain protocol matches */
> > +		if (!uuid_equal(&res_hdr->uuid, &req_hdr->uuid))
> > +			return false;
> > +
> > +		return true;
> > +	}
> > +
> > +	default:
> > +		return false;
> > +	}
> > +}
> > +
> > +static bool tb_xdomain_copy(struct tb_cfg_request *req,
> > +			    const struct ctl_pkg *pkg)
> > +{
> > +	memcpy(req->response, pkg->buffer, req->response_size);
> > +	req->result.err = 0;
> > +	return true;
> > +}
> > +
> > +static void response_ready(void *data)
> > +{
> > +	tb_cfg_request_put(data);
> > +}
> > +
> > +static int __tb_xdomain_response(struct tb_ctl *ctl, const void
> > *response,
> > +				 size_t size, enum tb_cfg_pkg_type
> > type)
> > +{
> > +	struct tb_cfg_request *req;
> > +
> > +	req = tb_cfg_request_alloc();
> > +	if (!req)
> > +		return -ENOMEM;
> > +
> > +	req->match = tb_xdomain_match;
> > +	req->copy = tb_xdomain_copy;
> > +	req->request = response;
> > +	req->request_size = size;
> > +	req->request_type = type;
> > +
> > +	return tb_cfg_request(ctl, req, response_ready, req);
> > +}
> > +
> > +/**
> > + * tb_xdomain_response() - Send a XDomain response message
> > + * @xd: XDomain to send the message
> > + * @response: Response to send
> > + * @size: Size of the response
> > + * @type: PDF type of the response
> > + *
> > + * This can be used to send a XDomain response message to the
> > other
> > + * domain. No response for the message is expected.
> > + *
> > + * Return: %0 in case of success and negative errno in case of
> > failure
> > + */
> > +int tb_xdomain_response(struct tb_xdomain *xd, const void
> > *response,
> > +			size_t size, enum tb_cfg_pkg_type type)
> > +{
> > +	return __tb_xdomain_response(xd->tb->ctl, response, size,
> > type);
> > +}
> > +EXPORT_SYMBOL_GPL(tb_xdomain_response);
> > +
> > +static int __tb_xdomain_request(struct tb_ctl *ctl, const void
> > *request,
> > +	size_t request_size, enum tb_cfg_pkg_type request_type,
> > void
> > *response,
> > +	size_t response_size, enum tb_cfg_pkg_type response_type,
> > +	unsigned int timeout_msec)
> > +{
> > +	struct tb_cfg_request *req;
> > +	struct tb_cfg_result res;
> > +
> > +	req = tb_cfg_request_alloc();
> > +	if (!req)
> > +		return -ENOMEM;
> > +
> > +	req->match = tb_xdomain_match;
> > +	req->copy = tb_xdomain_copy;
> > +	req->request = request;
> > +	req->request_size = request_size;
> > +	req->request_type = request_type;
> > +	req->response = response;
> > +	req->response_size = response_size;
> > +	req->response_type = response_type;
> > +
> > +	res = tb_cfg_request_sync(ctl, req, timeout_msec);
> > +
> > +	tb_cfg_request_put(req);
> > +
> > +	return res.err == 1 ? -EIO : res.err;
> > +}
> > +
> > +/**
> > + * tb_xdomain_request() - Send a XDomain request
> > + * @xd: XDomain to send the request
> > + * @request: Request to send
> > + * @request_size: Size of the request in bytes
> > + * @request_type: PDF type of the request
> > + * @response: Response is copied here
> > + * @response_size: Expected size of the response in bytes
> > + * @response_type: Expected PDF type of the response
> > + * @timeout_msec: Timeout in milliseconds to wait for the response
> > + *
> > + * This function can be used to send XDomain control channel
> > messages to
> > + * the other domain. The function waits until the response is
> > received
> > + * or when timeout triggers. Whichever comes first.
> > + *
> > + * Return: %0 in case of success and negative errno in case of
> > failure
> > + */
> > +int tb_xdomain_request(struct tb_xdomain *xd, const void *request,
> > +	size_t request_size, enum tb_cfg_pkg_type request_type,
> > +	void *response, size_t response_size,
> > +	enum tb_cfg_pkg_type response_type, unsigned int
> > timeout_msec)
> > +{
> > +	return __tb_xdomain_request(xd->tb->ctl, request,
> > request_size,
> > +				    request_type, response,
> > response_size,
> > +				    response_type, timeout_msec);
> > +}
> > +EXPORT_SYMBOL_GPL(tb_xdomain_request);
> > +
> > +static inline void tb_xdp_fill_header(struct tb_xdp_header *hdr,
> > u64
> > route,
> > +	u8 sequence, enum tb_xdp_type type, size_t size)
> > +{
> > +	u32 length_sn;
> > +
> > +	length_sn = (size - sizeof(hdr->xd_hdr)) / 4;
> > +	length_sn |= (sequence << TB_XDOMAIN_SN_SHIFT) &
> > TB_XDOMAIN_SN_MASK;
> > +
> > +	hdr->xd_hdr.route_hi = upper_32_bits(route);
> > +	hdr->xd_hdr.route_lo = lower_32_bits(route);
> > +	hdr->xd_hdr.length_sn = length_sn;
> > +	hdr->type = type;
> > +	memcpy(&hdr->uuid, &tb_xdp_uuid, sizeof(tb_xdp_uuid));
> > +}
> > +
> > +static int tb_xdp_handle_error(const struct tb_xdp_header *hdr)
> > +{
> > +	const struct tb_xdp_error_response *error;
> > +
> > +	if (hdr->type != ERROR_RESPONSE)
> > +		return 0;
> > +
> > +	error = (const struct tb_xdp_error_response *)hdr;
> > +
> > +	switch (error->error) {
> > +	case ERROR_UNKNOWN_PACKET:
> > +	case ERROR_UNKNOWN_DOMAIN:
> > +		return -EIO;
> > +	case ERROR_NOT_SUPPORTED:
> > +		return -ENOTSUPP;
> > +	case ERROR_NOT_READY:
> > +		return -EAGAIN;
> > +	default:
> > +		break;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int tb_xdp_error_response(struct tb_ctl *ctl, u64 route, u8
> > sequence,
> > +				 enum tb_xdp_error error)
> > +{
> > +	struct tb_xdp_error_response res;
> > +
> > +	memset(&res, 0, sizeof(res));
> > +	tb_xdp_fill_header(&res.hdr, route, sequence,
> > ERROR_RESPONSE,
> > +			   sizeof(res));
> > +	res.error = error;
> > +
> > +	return __tb_xdomain_response(ctl, &res, sizeof(res),
> > +				     TB_CFG_PKG_XDOMAIN_RESP);
> > +}
> > +
> > +static int tb_xdp_properties_request(struct tb_ctl *ctl, u64
> > route,
> > +	const uuid_t *src_uuid, const uuid_t *dst_uuid, int retry,
> > +	u32 **block, u32 *generation)
> > +{
> > +	struct tb_xdp_properties_response *res;
> > +	struct tb_xdp_properties req;
> > +	u16 data_len, len;
> > +	size_t total_size;
> > +	u32 *data = NULL;
> > +	int ret;
> > +
> > +	total_size = sizeof(*res) +
> > TB_XDP_PROPERTIES_MAX_DATA_LENGTH * 4;
> > +	res = kzalloc(total_size, GFP_KERNEL);
> > +	if (!res)
> > +		return -ENOMEM;
> > +
> > +	memset(&req, 0, sizeof(req));
> > +	tb_xdp_fill_header(&req.hdr, route, retry % 4,
> > PROPERTIES_REQUEST,
> > +			   sizeof(req));
> > +	memcpy(&req.src_uuid, src_uuid, sizeof(*src_uuid));
> > +	memcpy(&req.dst_uuid, dst_uuid, sizeof(*dst_uuid));
> > +
> > +	len = 0;
> > +	data_len = 0;
> > +
> > +	do {
> > +		ret = __tb_xdomain_request(ctl, &req, sizeof(req),
> > +					   TB_CFG_PKG_XDOMAIN_REQ,
> > res,
> > +					   total_size,
> > TB_CFG_PKG_XDOMAIN_RESP,
> > +					   XDOMAIN_DEFAULT_TIMEOUT
> > );
> > +		if (ret)
> > +			goto err;
> > +
> > +		ret = tb_xdp_handle_error(&res->hdr);
> > +		if (ret)
> > +			goto err;
> > +
> > +		/*
> > +		 * Package length includes the whole payload
> > without
> > the
> > +		 * XDomain header. Validate first that the package
> > is at
> > +		 * least size of the response structure.
> > +		 */
> > +		len = res->hdr.xd_hdr.length_sn &
> > TB_XDOMAIN_LENGTH_MASK;
> > +		if (len < sizeof(*res) / 4) {
> > +			ret = -EINVAL;
> > +			goto err;
> > +		}
> > +
> > +		len += sizeof(res->hdr.xd_hdr) / 4;
> > +		len -= sizeof(*res) / 4;
> > +
> > +		if (res->offset != req.offset) {
> > +			ret = -EINVAL;
> > +			goto err;
> > +		}
> > +
> > +		/*
> > +		 * First time allocate block that has enough space
> > for
> > +		 * the whole properties block.
> > +		 */
> > +		if (!data) {
> > +			data_len = res->data_length;
> > +			if (data_len >
> > TB_XDP_PROPERTIES_MAX_LENGTH)
> > {
> > +				ret = -E2BIG;
> > +				goto err;
> > +			}
> > +
> > +			data = kcalloc(data_len, sizeof(u32),
> > GFP_KERNEL);
> > +			if (!data) {
> > +				ret = -ENOMEM;
> > +				goto err;
> > +			}
> > +		}
> > +
> > +		memcpy(data + req.offset, res->data, len * 4);
> > +		req.offset += len;
> > +	} while (!data_len || req.offset < data_len);
> > +
> > +	*block = data;
> > +	*generation = res->generation;
> > +
> > +	kfree(res);
> > +
> > +	return data_len;
> > +
> > +err:
> > +	kfree(data);
> > +	kfree(res);
> > +
> > +	return ret;
> > +}
> > +
> > +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_xdp_properties_response *res;
> > +	size_t total_size;
> > +	u16 len;
> > +	int ret;
> > +
> > +	/*
> > +	 * Currently we expect all requests to be directed to us.
> > The
> > +	 * 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,
> > +				      ERROR_UNKNOWN_DOMAIN);
> > +		return 0;
> > +	}
> > +
> > +	mutex_lock(&xdomain_lock);
> > +
> > +	if (req->offset >= xdomain_property_block_len) {
> > +		mutex_unlock(&xdomain_lock);
> > +		return -EINVAL;
> > +	}
> > +
> > +	len = xdomain_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);
> > +		return -ENOMEM;
> > +	}
> > +
> > +	tb_xdp_fill_header(&res->hdr, route, sequence,
> > PROPERTIES_RESPONSE,
> > +			   total_size);
> > +	res->generation = xdomain_property_block_gen;
> > +	res->data_length = xdomain_property_block_len;
> > +	res->offset = req->offset;
> > +	uuid_copy(&res->src_uuid, src_uuid);
> > +	uuid_copy(&res->dst_uuid, &req->src_uuid);
> > +	memcpy(res->data, &xdomain_property_block[req->offset],
> > len
> > * 4);
> > +
> > +	mutex_unlock(&xdomain_lock);
> > +
> > +	ret = __tb_xdomain_response(ctl, res, total_size,
> > +				    TB_CFG_PKG_XDOMAIN_RESP);
> > +
> > +	kfree(res);
> > +	return ret;
> > +}
> > +
> > +static int tb_xdp_properties_changed_request(struct tb_ctl *ctl,
> > u64
> > route,
> > +					     int retry, const
> > uuid_t
> > *uuid)
> > +{
> > +	struct tb_xdp_properties_changed_response res;
> > +	struct tb_xdp_properties_changed req;
> > +	int ret;
> > +
> > +	memset(&req, 0, sizeof(req));
> > +	tb_xdp_fill_header(&req.hdr, route, retry % 4,
> > +			   PROPERTIES_CHANGED_REQUEST,
> > sizeof(req));
> > +	uuid_copy(&req.src_uuid, uuid);
> > +
> > +	memset(&res, 0, sizeof(res));
> > +	ret = __tb_xdomain_request(ctl, &req, sizeof(req),
> > +				   TB_CFG_PKG_XDOMAIN_REQ, &res,
> > sizeof(res),
> > +				   TB_CFG_PKG_XDOMAIN_RESP,
> > +				   XDOMAIN_DEFAULT_TIMEOUT);
> > +	if (ret)
> > +		return ret;
> > +
> > +	return tb_xdp_handle_error(&res.hdr);
> > +}
> > +
> > +static int
> > +tb_xdp_properties_changed_response(struct tb_ctl *ctl, u64 route,
> > u8
> > sequence)
> > +{
> > +	struct tb_xdp_properties_changed_response res;
> > +
> > +	memset(&res, 0, sizeof(res));
> > +	tb_xdp_fill_header(&res.hdr, route, sequence,
> > +			   PROPERTIES_CHANGED_RESPONSE,
> > sizeof(res));
> > +	return __tb_xdomain_response(ctl, &res, sizeof(res),
> > +				     TB_CFG_PKG_XDOMAIN_RESP);
> > +}
> > +
> > +/**
> > + * tb_register_protocol_handler() - Register protocol handler
> > + * @handler: Handler to register
> > + *
> > + * This allows XDomain service drivers to hook into incoming
> > XDomain
> > + * messages. After this function is called the service driver
> > needs
> > to
> > + * be able to handle calls to callback whenever a package with the
> > + * registered protocol is received.
> > + */
> > +int tb_register_protocol_handler(struct tb_protocol_handler
> > *handler)
> > +{
> > +	if (!handler->uuid || !handler->callback)
> > +		return -EINVAL;
> > +	if (uuid_equal(handler->uuid, &tb_xdp_uuid))
> > +		return -EINVAL;
> > +
> > +	mutex_lock(&xdomain_lock);
> > +	list_add_tail(&handler->list, &protocol_handlers);
> > +	mutex_unlock(&xdomain_lock);
> > +
> > +	return 0;
> > +}
> > +EXPORT_SYMBOL_GPL(tb_register_protocol_handler);
> > +
> > +/**
> > + * tb_unregister_protocol_handler() - Unregister protocol handler
> > + * @handler: Handler to unregister
> > + *
> > + * Removes the previously registered protocol handler.
> > + */
> > +void tb_unregister_protocol_handler(struct tb_protocol_handler
> > *handler)
> > +{
> > +	mutex_lock(&xdomain_lock);
> > +	list_del_init(&handler->list);
> > +	mutex_unlock(&xdomain_lock);
> > +}
> > +EXPORT_SYMBOL_GPL(tb_unregister_protocol_handler);
> > +
> > +static void tb_xdp_handle_request(struct work_struct *work)
> > +{
> > +	struct xdomain_request_work *xw = container_of(work,
> > typeof(*xw), work);
> > +	const struct tb_xdp_header *pkg = xw->pkg;
> > +	const struct tb_xdomain_header *xhdr = &pkg->xd_hdr;
> > +	struct tb *tb = xw->tb;
> > +	struct tb_ctl *ctl = tb->ctl;
> > +	const uuid_t *uuid;
> > +	int ret = 0;
> > +	u8 sequence;
> > +	u64 route;
> > +
> > +	route = ((u64)xhdr->route_hi << 32 | xhdr->route_lo) &
> > ~BIT_ULL(63);
> > +	sequence = xhdr->length_sn & TB_XDOMAIN_SN_MASK;
> > +	sequence >>= TB_XDOMAIN_SN_SHIFT;
> > +
> > +	mutex_lock(&tb->lock);
> > +	if (tb->root_switch)
> > +		uuid = tb->root_switch->uuid;
> > +	else
> > +		uuid = NULL;
> > +	mutex_unlock(&tb->lock);
> > +
> > +	if (!uuid) {
> > +		tb_xdp_error_response(ctl, route, sequence,
> > ERROR_NOT_READY);
> > +		goto out;
> > +	}
> > +
> > +	switch (pkg->type) {
> > +	case PROPERTIES_REQUEST:
> > +		ret = tb_xdp_properties_response(tb, ctl, route,
> > sequence, uuid,
> > +			(const struct tb_xdp_properties *)pkg);
> > +		break;
> > +
> > +	case PROPERTIES_CHANGED_REQUEST: {
> > +		const struct tb_xdp_properties_changed *xchg =
> > +			(const struct tb_xdp_properties_changed
> > *)pkg;
> > +		struct tb_xdomain *xd;
> > +
> > +		ret = tb_xdp_properties_changed_response(ctl,
> > route,
> > sequence);
> > +
> > +		/*
> > +		 * Since the properties have been changed, let's
> > update
> > +		 * the xdomain related to this connection as well
> > in
> > +		 * case there is a change in services it offers.
> > +		 */
> > +		xd = tb_xdomain_find_by_uuid_locked(tb, &xchg-
> > > src_uuid);
> > 
> > +		if (xd) {
> > +			queue_delayed_work(tb->wq, &xd-
> > > get_properties_work,
> > 
> > +					   msecs_to_jiffies(50));
> > +			tb_xdomain_put(xd);
> > +		}
> > +
> > +		break;
> > +	}
> > +
> > +	default:
> > +		break;
> > +	}
> > +
> > +	if (ret) {
> > +		tb_warn(tb, "failed to send XDomain response for
> > %#x\n",
> > +			pkg->type);
> > +	}
> > +
> > +out:
> > +	kfree(xw->pkg);
> > +	kfree(xw);
> > +}
> > +
> > +static void
> > +tb_xdp_schedule_request(struct tb *tb, const struct tb_xdp_header
> > *hdr,
> > +			size_t size)
> > +{
> > +	struct xdomain_request_work *xw;
> > +
> > +	xw = kmalloc(sizeof(*xw), GFP_KERNEL);
> > +	if (!xw)
> > +		return;
> > +
> > +	INIT_WORK(&xw->work, tb_xdp_handle_request);
> > +	xw->pkg = kmemdup(hdr, size, GFP_KERNEL);
> > +	xw->tb = tb;
> > +
> > +	queue_work(tb->wq, &xw->work);
> > +}
> > +
> > +/**
> > + * tb_register_service_driver() - Register XDomain service driver
> > + * @drv: Driver to register
> > + *
> > + * Registers new service driver from @drv to the bus.
> > + */
> > +int tb_register_service_driver(struct tb_service_driver *drv)
> > +{
> > +	drv->driver.bus = &tb_bus_type;
> > +	return driver_register(&drv->driver);
> > +}
> > +EXPORT_SYMBOL_GPL(tb_register_service_driver);
> > +
> > +/**
> > + * tb_unregister_service_driver() - Unregister XDomain service
> > driver
> > + * @xdrv: Driver to unregister
> > + *
> > + * Unregisters XDomain service driver from the bus.
> > + */
> > +void tb_unregister_service_driver(struct tb_service_driver *drv)
> > +{
> > +	driver_unregister(&drv->driver);
> > +}
> > +EXPORT_SYMBOL_GPL(tb_unregister_service_driver);
> > +
> > +static ssize_t key_show(struct device *dev, struct
> > device_attribute
> > *attr,
> > +			char *buf)
> > +{
> > +	struct tb_service *svc = container_of(dev, struct
> > tb_service, dev);
> > +
> > +	/*
> > +	 * It should be null terminated but anything else is
> > pretty
> > much
> > +	 * allowed.
> > +	 */
> > +	return sprintf(buf, "%*pEp\n", (int)strlen(svc->key), svc-
> > > key);
> > 
> > +}
> > +static DEVICE_ATTR_RO(key);
> > +
> > +static int get_modalias(struct tb_service *svc, char *buf, size_t
> > size)
> > +{
> > +	return snprintf(buf, size, "tbsvc:k%sp%08Xv%08Xr%08X",
> > svc-
> > > key,
> > 
> > +			svc->prtcid, svc->prtcvers, svc-
> > >prtcrevs);
> > +}
> > +
> > +static ssize_t modalias_show(struct device *dev, struct
> > device_attribute *attr,
> > +			     char *buf)
> > +{
> > +	struct tb_service *svc = container_of(dev, struct
> > tb_service, dev);
> > +
> > +	/* Full buffer size except new line and null termination
> > */
> > +	get_modalias(svc, buf, PAGE_SIZE - 2);
> > +	return sprintf(buf, "%s\n", buf);
> > +}
> > +static DEVICE_ATTR_RO(modalias);
> > +
> > +static ssize_t prtcid_show(struct device *dev, struct
> > device_attribute *attr,
> > +			   char *buf)
> > +{
> > +	struct tb_service *svc = container_of(dev, struct
> > tb_service, dev);
> > +
> > +	return sprintf(buf, "%u\n", svc->prtcid);
> > +}
> > +static DEVICE_ATTR_RO(prtcid);
> > +
> > +static ssize_t prtcvers_show(struct device *dev, struct
> > device_attribute *attr,
> > +			     char *buf)
> > +{
> > +	struct tb_service *svc = container_of(dev, struct
> > tb_service, dev);
> > +
> > +	return sprintf(buf, "%u\n", svc->prtcvers);
> > +}
> > +static DEVICE_ATTR_RO(prtcvers);
> > +
> > +static ssize_t prtcrevs_show(struct device *dev, struct
> > device_attribute *attr,
> > +			     char *buf)
> > +{
> > +	struct tb_service *svc = container_of(dev, struct
> > tb_service, dev);
> > +
> > +	return sprintf(buf, "%u\n", svc->prtcrevs);
> > +}
> > +static DEVICE_ATTR_RO(prtcrevs);
> > +
> > +static ssize_t prtcstns_show(struct device *dev, struct
> > device_attribute *attr,
> > +			     char *buf)
> > +{
> > +	struct tb_service *svc = container_of(dev, struct
> > tb_service, dev);
> > +
> > +	return sprintf(buf, "0x%08x\n", svc->prtcstns);
> > +}
> > +static DEVICE_ATTR_RO(prtcstns);
> > +
> > +static struct attribute *tb_service_attrs[] = {
> > +	&dev_attr_key.attr,
> > +	&dev_attr_modalias.attr,
> > +	&dev_attr_prtcid.attr,
> > +	&dev_attr_prtcvers.attr,
> > +	&dev_attr_prtcrevs.attr,
> > +	&dev_attr_prtcstns.attr,
> > +	NULL,
> > +};
> > +
> > +static struct attribute_group tb_service_attr_group = {
> > +	.attrs = tb_service_attrs,
> > +};
> > +
> > +static const struct attribute_group *tb_service_attr_groups[] = {
> > +	&tb_service_attr_group,
> > +	NULL,
> > +};
> > +
> > +static int tb_service_uevent(struct device *dev, struct
> > kobj_uevent_env *env)
> > +{
> > +	struct tb_service *svc = container_of(dev, struct
> > tb_service, dev);
> > +	char modalias[64];
> > +
> > +	get_modalias(svc, modalias, sizeof(modalias));
> > +	return add_uevent_var(env, "MODALIAS=%s", modalias);
> > +}
> > +
> > +static void tb_service_release(struct device *dev)
> > +{
> > +	struct tb_service *svc = container_of(dev, struct
> > tb_service, dev);
> > +	struct tb_xdomain *xd = tb_service_parent(svc);
> > +
> > +	ida_simple_remove(&xd->service_ids, svc->id);
> > +	kfree(svc->key);
> > +	kfree(svc);
> > +}
> > +
> > +struct device_type tb_service_type = {
> > +	.name = "thunderbolt_service",
> > +	.groups = tb_service_attr_groups,
> > +	.uevent = tb_service_uevent,
> > +	.release = tb_service_release,
> > +};
> > +EXPORT_SYMBOL_GPL(tb_service_type);
> > +
> > +static int remove_missing_service(struct device *dev, void *data)
> > +{
> > +	struct tb_xdomain *xd = data;
> > +	struct tb_service *svc;
> > +
> > +	svc = tb_to_service(dev);
> > +	if (!svc)
> > +		return 0;
> > +
> > +	if (!tb_property_find(xd->properties, svc->key,
> > +			      TB_PROPERTY_TYPE_DIRECTORY))
> > +		device_unregister(dev);
> > +
> > +	return 0;
> > +}
> > +
> > +static int find_service(struct device *dev, void *data)
> > +{
> > +	const struct tb_property *p = data;
> > +	struct tb_service *svc;
> > +
> > +	svc = tb_to_service(dev);
> > +	if (!svc)
> > +		return 0;
> > +
> > +	return !strcmp(svc->key, p->key);
> > +}
> > +
> > +static int populate_service(struct tb_service *svc,
> > +			    struct tb_property *property)
> > +{
> > +	struct tb_property_dir *dir = property->value.dir;
> > +	struct tb_property *p;
> > +
> > +	/* Fill in standard properties */
> > +	p = tb_property_find(dir, "prtcid",
> > TB_PROPERTY_TYPE_VALUE);
> > +	if (p)
> > +		svc->prtcid = p->value.immediate;
> > +	p = tb_property_find(dir, "prtcvers",
> > TB_PROPERTY_TYPE_VALUE);
> > +	if (p)
> > +		svc->prtcvers = p->value.immediate;
> > +	p = tb_property_find(dir, "prtcrevs",
> > TB_PROPERTY_TYPE_VALUE);
> > +	if (p)
> > +		svc->prtcrevs = p->value.immediate;
> > +	p = tb_property_find(dir, "prtcstns",
> > TB_PROPERTY_TYPE_VALUE);
> > +	if (p)
> > +		svc->prtcstns = p->value.immediate;
> > +
> > +	svc->key = kstrdup(property->key, GFP_KERNEL);
> > +	if (!svc->key)
> > +		return -ENOMEM;
> > +
> > +	return 0;
> > +}
> > +
> > +static void enumerate_services(struct tb_xdomain *xd)
> > +{
> > +	struct tb_service *svc;
> > +	struct tb_property *p;
> > +	struct device *dev;
> > +
> > +	/*
> > +	 * First remove all services that are not available
> > anymore
> > in
> > +	 * the updated property block.
> > +	 */
> > +	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) {
> > +		if (p->type != TB_PROPERTY_TYPE_DIRECTORY)
> > +			continue;
> > +
> > +		/* If the service exists already we are fine */
> > +		dev = device_find_child(&xd->dev, p,
> > find_service);
> > +		if (dev) {
> > +			put_device(dev);
> > +			continue;
> > +		}
> > +
> > +		svc = kzalloc(sizeof(*svc), GFP_KERNEL);
> > +		if (!svc)
> > +			break;
> > +
> > +		if (populate_service(svc, p)) {
> > +			kfree(svc);
> > +			break;
> > +		}
> > +
> > +		svc->id = ida_simple_get(&xd->service_ids, 0, 0,
> > GFP_KERNEL);
> > +		svc->dev.bus = &tb_bus_type;
> > +		svc->dev.type = &tb_service_type;
> > +		svc->dev.parent = &xd->dev;
> > +		dev_set_name(&svc->dev, "%s.%d", dev_name(&xd-
> > >dev), 
> > svc->id);
> > +
> > +		if (device_register(&svc->dev)) {
> > +			put_device(&svc->dev);
> > +			break;
> > +		}
> > +	}
> > +}
> > +
> > +static int populate_properties(struct tb_xdomain *xd,
> > +			       struct tb_property_dir *dir)
> > +{
> > +	const struct tb_property *p;
> > +
> > +	/* Required properties */
> > +	p = tb_property_find(dir, "deviceid",
> > TB_PROPERTY_TYPE_VALUE);
> > +	if (!p)
> > +		return -EINVAL;
> > +	xd->device = p->value.immediate;
> > +
> > +	p = tb_property_find(dir, "vendorid",
> > TB_PROPERTY_TYPE_VALUE);
> > +	if (!p)
> > +		return -EINVAL;
> > +	xd->vendor = p->value.immediate;
> > +
> > +	kfree(xd->device_name);
> > +	xd->device_name = NULL;
> > +	kfree(xd->vendor_name);
> > +	xd->vendor_name = NULL;
> > +
> > +	/* Optional properties */
> > +	p = tb_property_find(dir, "deviceid",
> > TB_PROPERTY_TYPE_TEXT);
> > +	if (p)
> > +		xd->device_name = kstrdup(p->value.text,
> > GFP_KERNEL);
> > +	p = tb_property_find(dir, "vendorid",
> > TB_PROPERTY_TYPE_TEXT);
> > +	if (p)
> > +		xd->vendor_name = kstrdup(p->value.text,
> > GFP_KERNEL);
> > +
> > +	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 void tb_xdomain_get_properties(struct work_struct *work)
> > +{
> > +	struct tb_xdomain *xd = container_of(work, typeof(*xd),
> > +					     get_properties_work.w
> > or
> > k);
> > +	struct tb_property_dir *dir;
> > +	struct tb *tb = xd->tb;
> > +	bool update = false;
> > +	u32 *block = NULL;
> > +	u32 gen = 0;
> > +	int ret;
> > +
> > +	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) {
> > +			queue_delayed_work(xd->tb->wq, &xd-
> > > get_properties_work,
> > 
> > +					   msecs_to_jiffies(1000))
> > ;
> > +		} else {
> > +			/* Give up now */
> > +			dev_err(&xd->dev,
> > +				"failed read XDomain properties
> > from
> > %pUb\n",
> > +				xd->remote_uuid);
> > +		}
> > +		return;
> > +	}
> > +
> > +	xd->properties_retries = XDOMAIN_PROPERTIES_RETRIES;
> > +
> > +	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);
> > +		goto err_free_block;
> > +	}
> > +
> > +	dir = tb_property_parse_dir(block, ret);
> > +	if (!dir) {
> > +		dev_err(&xd->dev, "failed to parse XDomain
> > properties\n");
> > +		goto err_free_block;
> > +	}
> > +
> > +	ret = populate_properties(xd, dir);
> > +	if (ret) {
> > +		dev_err(&xd->dev, "missing XDomain properties in
> > response\n");
> > +		goto err_free_dir;
> > +	}
> > +
> > +	/* Release the existing one */
> > +	if (xd->properties) {
> > +		tb_property_free_dir(xd->properties);
> > +		update = true;
> > +	}
> > +
> > +	xd->properties = dir;
> > +	xd->property_block_gen = gen;
> > +
> > +	tb_xdomain_restore_paths(xd);
> > +
> > +	mutex_unlock(&xd->lock);
> > +
> > +	kfree(block);
> > +
> > +	/*
> > +	 * Now the device should be ready enough so we can add it
> > to
> > the
> > +	 * bus and let userspace know about it. If the device is
> > already
> > +	 * registered, we notify the userspace that it has
> > changed.
> > +	 */
> > +	if (!update) {
> > +		if (device_add(&xd->dev)) {
> > +			dev_err(&xd->dev, "failed to add XDomain
> > device\n");
> > +			return;
> > +		}
> > +	} else {
> > +		kobject_uevent(&xd->dev.kobj, KOBJ_CHANGE);
> > +	}
> > +
> > +	enumerate_services(xd);
> > +	return;
> > +
> > +err_free_dir:
> > +	tb_property_free_dir(dir);
> > +err_free_block:
> > +	kfree(block);
> > +	mutex_unlock(&xd->lock);
> > +}
> > +
> > +static void tb_xdomain_properties_changed(struct work_struct
> > *work)
> > +{
> > +	struct tb_xdomain *xd = container_of(work, typeof(*xd),
> > +					     properties_changed_wo
> > rk
> > .work);
> > +	int ret;
> > +
> > +	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)
> > +			queue_delayed_work(xd->tb->wq,
> > +					   &xd-
> > > properties_changed_work,
> > 
> > +					   msecs_to_jiffies(1000))
> > ;
> > +		return;
> > +	}
> > +
> > +	xd->properties_changed_retries =
> > XDOMAIN_PROPERTIES_CHANGED_RETRIES;
> > +}
> > +
> > +static ssize_t device_show(struct device *dev, struct
> > device_attribute *attr,
> > +			   char *buf)
> > +{
> > +	struct tb_xdomain *xd = container_of(dev, struct
> > tb_xdomain,
> > dev);
> > +
> > +	return sprintf(buf, "%#x\n", xd->device);
> > +}
> > +static DEVICE_ATTR_RO(device);
> > +
> > +static ssize_t
> > +device_name_show(struct device *dev, struct device_attribute
> > *attr,
> > char *buf)
> > +{
> > +	struct tb_xdomain *xd = container_of(dev, struct
> > tb_xdomain,
> > dev);
> > +	int ret;
> > +
> > +	if (mutex_lock_interruptible(&xd->lock))
> > +		return -ERESTARTSYS;
> > +	ret = sprintf(buf, "%s\n", xd->device_name ? xd-
> > >device_name 
> > : "");
> > +	mutex_unlock(&xd->lock);
> > +
> > +	return ret;
> > +}
> > +static DEVICE_ATTR_RO(device_name);
> > +
> > +static ssize_t vendor_show(struct device *dev, struct
> > device_attribute *attr,
> > +			   char *buf)
> > +{
> > +	struct tb_xdomain *xd = container_of(dev, struct
> > tb_xdomain,
> > dev);
> > +
> > +	return sprintf(buf, "%#x\n", xd->vendor);
> > +}
> > +static DEVICE_ATTR_RO(vendor);
> > +
> > +static ssize_t
> > +vendor_name_show(struct device *dev, struct device_attribute
> > *attr,
> > char *buf)
> > +{
> > +	struct tb_xdomain *xd = container_of(dev, struct
> > tb_xdomain,
> > dev);
> > +	int ret;
> > +
> > +	if (mutex_lock_interruptible(&xd->lock))
> > +		return -ERESTARTSYS;
> > +	ret = sprintf(buf, "%s\n", xd->vendor_name ? xd-
> > >vendor_name 
> > : "");
> > +	mutex_unlock(&xd->lock);
> > +
> > +	return ret;
> > +}
> > +static DEVICE_ATTR_RO(vendor_name);
> > +
> > +static ssize_t unique_id_show(struct device *dev, struct
> > device_attribute *attr,
> > +			      char *buf)
> > +{
> > +	struct tb_xdomain *xd = container_of(dev, struct
> > tb_xdomain,
> > dev);
> > +
> > +	return sprintf(buf, "%pUb\n", xd->remote_uuid);
> > +}
> > +static DEVICE_ATTR_RO(unique_id);
> > +
> > +static struct attribute *xdomain_attrs[] = {
> > +	&dev_attr_device.attr,
> > +	&dev_attr_device_name.attr,
> > +	&dev_attr_unique_id.attr,
> > +	&dev_attr_vendor.attr,
> > +	&dev_attr_vendor_name.attr,
> > +	NULL,
> > +};
> > +
> > +static struct attribute_group xdomain_attr_group = {
> > +	.attrs = xdomain_attrs,
> > +};
> > +
> > +static const struct attribute_group *xdomain_attr_groups[] = {
> > +	&xdomain_attr_group,
> > +	NULL,
> > +};
> > +
> > +static void tb_xdomain_release(struct device *dev)
> > +{
> > +	struct tb_xdomain *xd = container_of(dev, struct
> > tb_xdomain,
> > dev);
> > +
> > +	put_device(xd->dev.parent);
> > +
> > +	tb_property_free_dir(xd->properties);
> > +	ida_destroy(&xd->service_ids);
> > +
> > +	kfree(xd->local_uuid);
> > +	kfree(xd->remote_uuid);
> > +	kfree(xd->device_name);
> > +	kfree(xd->vendor_name);
> > +	kfree(xd);
> > +}
> > +
> > +static void start_handshake(struct tb_xdomain *xd)
> > +{
> > +	xd->properties_retries = XDOMAIN_PROPERTIES_RETRIES;
> > +	xd->properties_changed_retries =
> > XDOMAIN_PROPERTIES_CHANGED_RETRIES;
> > +
> > +	/* Start exchanging properties with the other host */
> > +	queue_delayed_work(xd->tb->wq, &xd-
> > >properties_changed_work,
> > +			   msecs_to_jiffies(100));
> > +	queue_delayed_work(xd->tb->wq, &xd->get_properties_work,
> > +			   msecs_to_jiffies(1000));
> > +}
> > +
> > +static void stop_handshake(struct tb_xdomain *xd)
> > +{
> > +	xd->properties_retries = 0;
> > +	xd->properties_changed_retries = 0;
> > +
> > +	cancel_delayed_work_sync(&xd->get_properties_work);
> > +	cancel_delayed_work_sync(&xd->properties_changed_work);
> > +}
> > +
> > +static int __maybe_unused tb_xdomain_suspend(struct device *dev)
> > +{
> > +	stop_handshake(tb_to_xdomain(dev));
> > +	return 0;
> > +}
> > +
> > +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);
> > +
> > +	return 0;
> > +}
> > +
> > +static const struct dev_pm_ops tb_xdomain_pm_ops = {
> > +	SET_SYSTEM_SLEEP_PM_OPS(tb_xdomain_suspend,
> > tb_xdomain_resume)
> > +};
> > +
> > +struct device_type tb_xdomain_type = {
> > +	.name = "thunderbolt_xdomain",
> > +	.release = tb_xdomain_release,
> > +	.pm = &tb_xdomain_pm_ops,
> > +};
> > +EXPORT_SYMBOL_GPL(tb_xdomain_type);
> > +
> > +/**
> > + * tb_xdomain_alloc() - Allocate new XDomain object
> > + * @tb: Domain where the XDomain belongs
> > + * @parent: Parent device (the switch through the connection to
> > the
> > + *	    other domain is reached).
> > + * @route: Route string used to reach the other domain
> > + * @local_uuid: Our local domain UUID
> > + * @remote_uuid: UUID of the other domain
> > + *
> > + * Allocates new XDomain structure and returns pointer to that.
> > The
> > + * object must be released by calling tb_xdomain_put().
> > + */
> > +struct tb_xdomain *tb_xdomain_alloc(struct tb *tb, struct device
> > *parent,
> > +				    u64 route, const uuid_t
> > *local_uuid,
> > +				    const uuid_t *remote_uuid)
> > +{
> > +	struct tb_xdomain *xd;
> > +
> > +	xd = kzalloc(sizeof(*xd), GFP_KERNEL);
> > +	if (!xd)
> > +		return NULL;
> > +
> > +	xd->tb = tb;
> > +	xd->route = route;
> > +	ida_init(&xd->service_ids);
> > +	mutex_init(&xd->lock);
> > +	INIT_DELAYED_WORK(&xd->get_properties_work,
> > tb_xdomain_get_properties);
> > +	INIT_DELAYED_WORK(&xd->properties_changed_work,
> > +			  tb_xdomain_properties_changed);
> > +
> > +	xd->local_uuid = kmemdup(local_uuid, sizeof(uuid_t),
> > GFP_KERNEL);
> > +	if (!xd->local_uuid)
> > +		goto err_free;
> > +
> > +	xd->remote_uuid = kmemdup(remote_uuid, sizeof(uuid_t),
> > GFP_KERNEL);
> > +	if (!xd->remote_uuid)
> > +		goto err_free_local_uuid;
> > +
> > +	device_initialize(&xd->dev);
> > +	xd->dev.parent = get_device(parent);
> > +	xd->dev.bus = &tb_bus_type;
> > +	xd->dev.type = &tb_xdomain_type;
> > +	xd->dev.groups = xdomain_attr_groups;
> > +	dev_set_name(&xd->dev, "%u-%llx", tb->index, route);
> > +
> > +	return xd;
> > +
> > +err_free_local_uuid:
> > +	kfree(xd->local_uuid);
> > +err_free:
> > +	kfree(xd);
> > +
> > +	return NULL;
> > +}
> > +
> > +/**
> > + * tb_xdomain_add() - Add XDomain to the bus
> > + * @xd: XDomain to add
> > + *
> > + * This function starts XDomain discovery protocol handshake and
> > + * eventually adds the XDomain to the bus. After calling this
> > function
> > + * the caller needs to call tb_xdomain_remove() in order to remove
> > and
> > + * release the object regardless whether the handshake succeeded
> > or
> > not.
> > + */
> > +void tb_xdomain_add(struct tb_xdomain *xd)
> > +{
> > +	/* Start exchanging properties with the other host */
> > +	start_handshake(xd);
> > +}
> > +
> > +static int unregister_service(struct device *dev, void *data)
> > +{
> > +	device_unregister(dev);
> > +	return 0;
> > +}
> > +
> > +/**
> > + * tb_xdomain_remove() - Remove XDomain from the bus
> > + * @xd: XDomain to remove
> > + *
> > + * This will stop all ongoing configuration work and remove the
> > XDomain
> > + * along with any services from the bus. When the last reference
> > to
> > @xd
> > + * is released the object will be released as well.
> > + */
> > +void tb_xdomain_remove(struct tb_xdomain *xd)
> > +{
> > +	stop_handshake(xd);
> > +
> > +	device_for_each_child_reverse(&xd->dev, xd,
> > unregister_service);
> > +
> > +	if (!device_is_registered(&xd->dev))
> > +		put_device(&xd->dev);
> > +	else
> > +		device_unregister(&xd->dev);
> > +}
> > +
> > +/**
> > + * 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
> > + *
> > + * The function enables DMA paths accordingly so that after
> > successful
> > + * return the caller can send and receive packets using high-speed
> > DMA
> > + * path.
> > + *
> > + * Return: %0 in case of success and negative errno in case of
> > error
> > + */
> > +int tb_xdomain_enable_paths(struct tb_xdomain *xd, u16
> > transmit_path,
> > +			    u16 transmit_ring, u16 receive_path,
> > +			    u16 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;
> > +}
> > +EXPORT_SYMBOL_GPL(tb_xdomain_enable_paths);
> > +
> > +/**
> > + * tb_xdomain_disable_paths() - Disable DMA paths for XDomain
> > connection
> > + * @xd: XDomain connection
> > + *
> > + * This does the opposite of tb_xdomain_enable_paths(). After call
> > to
> > + * this the caller is not expected to use the rings anymore.
> > + *
> > + * Return: %0 in case of success and negative errno in case of
> > error
> > + */
> > +int tb_xdomain_disable_paths(struct tb_xdomain *xd)
> > +{
> > +	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;
> > +}
> > +EXPORT_SYMBOL_GPL(tb_xdomain_disable_paths);
> > +
> > +struct tb_xdomain_lookup {
> > +	const uuid_t *uuid;
> > +	u8 link;
> > +	u8 depth;
> > +};
> > +
> > +static struct tb_xdomain *switch_find_xdomain(struct tb_switch
> > *sw,
> > +	const struct tb_xdomain_lookup *lookup)
> > +{
> > +	int i;
> > +
> > +	for (i = 1; i <= sw->config.max_port_number; i++) {
> > +		struct tb_port *port = &sw->ports[i];
> > +		struct tb_xdomain *xd;
> > +
> > +		if (tb_is_upstream_port(port))
> > +			continue;
> > +
> > +		if (port->xdomain) {
> > +			xd = port->xdomain;
> > +
> > +			if (lookup->uuid) {
> > +				if (uuid_equal(xd->remote_uuid,
> > lookup->uuid))
> > +					return xd;
> > +			} else if (lookup->link == xd->link &&
> > +				   lookup->depth == xd->depth) {
> > +				return xd;
> > +			}
> > +		} else if (port->remote) {
> > +			xd = switch_find_xdomain(port->remote->sw,
> > lookup);
> > +			if (xd)
> > +				return xd;
> > +		}
> > +	}
> > +
> > +	return NULL;
> > +}
> > +
> > +/**
> > + * tb_xdomain_find_by_uuid() - Find an XDomain by UUID
> > + * @tb: Domain where the XDomain belongs to
> > + * @uuid: UUID to look for
> > + *
> > + * Finds XDomain by walking through the Thunderbolt topology below
> > @tb.
> > + * The returned XDomain will have its reference count increased so
> > the
> > + * caller needs to call tb_xdomain_put() when it is done with the
> > + * object.
> > + *
> > + * This will find all XDomains including the ones that are not yet
> > added
> > + * to the bus (handshake is still in progress).
> > + *
> > + * The caller needs to hold @tb->lock.
> > + */
> > +struct tb_xdomain *tb_xdomain_find_by_uuid(struct tb *tb, const
> > uuid_t *uuid)
> > +{
> > +	struct tb_xdomain_lookup lookup;
> > +	struct tb_xdomain *xd;
> > +
> > +	memset(&lookup, 0, sizeof(lookup));
> > +	lookup.uuid = uuid;
> > +
> > +	xd = switch_find_xdomain(tb->root_switch, &lookup);
> > +	if (xd) {
> > +		get_device(&xd->dev);
> > +		return xd;
> > +	}
> > +
> > +	return NULL;
> > +}
> > +EXPORT_SYMBOL_GPL(tb_xdomain_find_by_uuid);
> > +
> > +/**
> > + * tb_xdomain_find_by_link_depth() - Find an XDomain by link and
> > depth
> > + * @tb: Domain where the XDomain belongs to
> > + * @link: Root switch link number
> > + * @depth: Depth in the link
> > + *
> > + * Finds XDomain by walking through the Thunderbolt topology below
> > @tb.
> > + * The returned XDomain will have its reference count increased so
> > the
> > + * caller needs to call tb_xdomain_put() when it is done with the
> > + * object.
> > + *
> > + * This will find all XDomains including the ones that are not yet
> > added
> > + * to the bus (handshake is still in progress).
> > + *
> > + * The caller needs to hold @tb->lock.
> > + */
> > +struct tb_xdomain *tb_xdomain_find_by_link_depth(struct tb *tb, u8
> > link,
> > +						 u8 depth)
> > +{
> > +	struct tb_xdomain_lookup lookup;
> > +	struct tb_xdomain *xd;
> > +
> > +	memset(&lookup, 0, sizeof(lookup));
> > +	lookup.link = link;
> > +	lookup.depth = depth;
> > +
> > +	xd = switch_find_xdomain(tb->root_switch, &lookup);
> > +	if (xd) {
> > +		get_device(&xd->dev);
> > +		return xd;
> > +	}
> > +
> > +	return NULL;
> > +}
> > +
> > +bool tb_xdomain_handle_request(struct tb *tb, enum tb_cfg_pkg_type
> > type,
> > +			       const void *buf, size_t size)
> > +{
> > +	const struct tb_protocol_handler *handler, *tmp;
> > +	const struct tb_xdp_header *hdr = buf;
> > +	unsigned int length;
> > +	int ret = 0;
> > +
> > +	/* We expect the packet is at least size of the header */
> > +	length = hdr->xd_hdr.length_sn & TB_XDOMAIN_LENGTH_MASK;
> > +	if (length != size / 4 - sizeof(hdr->xd_hdr) / 4)
> > +		return true;
> > +	if (length < sizeof(*hdr) / 4 - sizeof(hdr->xd_hdr) / 4)
> > +		return true;
> > +
> > +	/*
> > +	 * Handle XDomain discovery protocol packets directly
> > here.
> > For
> > +	 * other protocols (based on their UUID) we call
> > registered
> > +	 * handlers in turn.
> > +	 */
> > +	if (uuid_equal(&hdr->uuid, &tb_xdp_uuid)) {
> > +		if (type == TB_CFG_PKG_XDOMAIN_REQ) {
> > +			tb_xdp_schedule_request(tb, hdr, size);
> > +			return true;
> > +		}
> > +		return false;
> > +	}
> > +
> > +	mutex_lock(&xdomain_lock);
> > +	list_for_each_entry_safe(handler, tmp, &protocol_handlers,
> > list) {
> > +		if (!uuid_equal(&hdr->uuid, handler->uuid))
> > +			continue;
> > +
> > +		mutex_unlock(&xdomain_lock);
> > +		ret = handler->callback(buf, size, handler->data);
> > +		mutex_lock(&xdomain_lock);
> > +
> > +		if (ret)
> > +			break;
> > +	}
> > +	mutex_unlock(&xdomain_lock);
> > +
> > +	return ret > 0;
> > +}
> > +
> > +static int rebuild_property_block(void)
> > +{
> > +	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 int update_xdomain(struct device *dev, void *data)
> > +{
> > +	struct tb_xdomain *xd;
> > +
> > +	xd = tb_to_xdomain(dev);
> > +	if (xd) {
> > +		queue_delayed_work(xd->tb->wq, &xd-
> > > properties_changed_work,
> > 
> > +				   msecs_to_jiffies(50));
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static void update_all_xdomains(void)
> > +{
> > +	bus_for_each_dev(&tb_bus_type, NULL, NULL,
> > update_xdomain);
> > +}
> > +
> > +static bool remove_directory(const char *key, const struct
> > tb_property_dir *dir)
> > +{
> > +	struct tb_property *p;
> > +
> > +	p = tb_property_find(xdomain_property_dir, key,
> > +			     TB_PROPERTY_TYPE_DIRECTORY);
> > +	if (p && p->value.dir == dir) {
> > +		tb_property_remove(p);
> > +		return true;
> > +	}
> > +	return false;
> > +}
> > +
> > +/**
> > + * tb_register_property_dir() - Register property directory to the
> > host
> > + * @key: Key (name) of the directory to add
> > + * @dir: Directory to add
> > + *
> > + * Service drivers can use this function to add new property
> > directory
> > + * to the host available properties. The other connected hosts are
> > + * notified so they can re-read properties of this host if they
> > are
> > + * interested.
> > + *
> > + * Return: %0 on success and negative errno on failure
> > + */
> > +int tb_register_property_dir(const char *key, struct
> > tb_property_dir
> > *dir)
> > +{
> > +	int ret;
> > +
> > +	if (!key || strlen(key) > 8)
> > +		return -EINVAL;
> > +
> > +	mutex_lock(&xdomain_lock);
> > +	if (tb_property_find(xdomain_property_dir, key,
> > +			     TB_PROPERTY_TYPE_DIRECTORY)) {
> > +		ret = -EEXIST;
> > +		goto err_unlock;
> > +	}
> > +
> > +	ret = tb_property_add_dir(xdomain_property_dir, key, dir);
> > +	if (ret)
> > +		goto err_unlock;
> > +
> > +	ret = rebuild_property_block();
> > +	if (ret) {
> > +		remove_directory(key, dir);
> > +		goto err_unlock;
> > +	}
> > +
> > +	mutex_unlock(&xdomain_lock);
> > +	update_all_xdomains();
> > +	return 0;
> > +
> > +err_unlock:
> > +	mutex_unlock(&xdomain_lock);
> > +	return ret;
> > +}
> > +EXPORT_SYMBOL_GPL(tb_register_property_dir);
> > +
> > +/**
> > + * tb_unregister_property_dir() - Removes property directory from
> > host
> > + * @key: Key (name) of the directory
> > + * @dir: Directory to remove
> > + *
> > + * This will remove the existing directory from this host and
> > notify
> > the
> > + * connected hosts about the change.
> > + */
> > +void tb_unregister_property_dir(const char *key, struct
> > tb_property_dir *dir)
> > +{
> > +	int ret = 0;
> > +
> > +	mutex_lock(&xdomain_lock);
> > +	if (remove_directory(key, dir))
> > +		ret = rebuild_property_block();
> > +	mutex_unlock(&xdomain_lock);
> > +
> > +	if (!ret)
> > +		update_all_xdomains();
> > +}
> > +EXPORT_SYMBOL_GPL(tb_unregister_property_dir);
> > +
> > +int tb_xdomain_init(void)
> > +{
> > +	int ret;
> > +
> > +	xdomain_property_dir = tb_property_create_dir(NULL);
> > +	if (!xdomain_property_dir)
> > +		return -ENOMEM;
> > +
> > +	/*
> > +	 * Initialize standard set of properties without any
> > service
> > +	 * directories. Those will be added by service drivers
> > +	 * themselves when they are loaded.
> > +	 */
> > +	tb_property_add_immediate(xdomain_property_dir,
> > "vendorid",
> > +				  PCI_VENDOR_ID_INTEL);
> > +	tb_property_add_text(xdomain_property_dir, "vendorid",
> > "Intel Corp.");
> > +	tb_property_add_immediate(xdomain_property_dir,
> > "deviceid",
> > 0x1);
> > +	tb_property_add_text(xdomain_property_dir, "deviceid",
> > +			     utsname()->nodename);
> > +	tb_property_add_immediate(xdomain_property_dir,
> > "devicerv",
> > 0x80000100);
> > +
> > +	ret = rebuild_property_block();
> > +	if (ret) {
> > +		tb_property_free_dir(xdomain_property_dir);
> > +		xdomain_property_dir = NULL;
> > +	}
> > +
> > +	return ret;
> > +}
> > +
> > +void tb_xdomain_exit(void)
> > +{
> > +	kfree(xdomain_property_block);
> > +	tb_property_free_dir(xdomain_property_dir);
> > +}
> > diff --git a/include/linux/mod_devicetable.h
> > b/include/linux/mod_devicetable.h
> > index 694cebb50f72..7625c3b81f84 100644
> > --- a/include/linux/mod_devicetable.h
> > +++ b/include/linux/mod_devicetable.h
> > @@ -683,5 +683,31 @@ struct fsl_mc_device_id {
> >  	const char obj_type[16];
> >  };
> >  
> > +/**
> > + * struct tb_service_id - Thunderbolt service identifiers
> > + * @match_flags: Flags used to match the structure
> > + * @protocol_key: Protocol key the service supports
> > + * @protocol_id: Protocol id the service supports
> > + * @protocol_version: Version of the protocol
> > + * @protocol_revision: Revision of the protocol software
> > + * @driver_data: Driver specific data
> > + *
> > + * Thunderbolt XDomain services are exposed as devices where each
> > device
> > + * carries the protocol information the service supports.
> > Thunderbolt
> > + * XDomain service drivers match against that information.
> > + */
> > +struct tb_service_id {
> > +	__u32 match_flags;
> > +	char protocol_key[8 + 1];
> > +	__u32 protocol_id;
> > +	__u32 protocol_version;
> > +	__u32 protocol_revision;
> > +	kernel_ulong_t driver_data;
> > +};
> > +
> > +#define TBSVC_MATCH_PROTOCOL_KEY	0x0001
> > +#define TBSVC_MATCH_PROTOCOL_ID		0x0002
> > +#define TBSVC_MATCH_PROTOCOL_VERSION	0x0004
> > +#define TBSVC_MATCH_PROTOCOL_REVISION	0x0008
> >  
> >  #endif /* LINUX_MOD_DEVICETABLE_H */
> > diff --git a/include/linux/thunderbolt.h
> > b/include/linux/thunderbolt.h
> > index 4011d6537a8c..79abdaf1c296 100644
> > --- a/include/linux/thunderbolt.h
> > +++ b/include/linux/thunderbolt.h
> > @@ -17,6 +17,7 @@
> >  #include <linux/device.h>
> >  #include <linux/list.h>
> >  #include <linux/mutex.h>
> > +#include <linux/mod_devicetable.h>
> >  #include <linux/uuid.h>
> >  
> >  enum tb_cfg_pkg_type {
> > @@ -77,6 +78,8 @@ struct tb {
> >  };
> >  
> >  extern struct bus_type tb_bus_type;
> > +extern struct device_type tb_service_type;
> > +extern struct device_type tb_xdomain_type;
> >  
> >  #define TB_LINKS_PER_PHY_PORT	2
> >  
> > @@ -155,4 +158,243 @@ struct tb_property
> > *tb_property_get_next(struct
> > tb_property_dir *dir,
> >  	     property;						
> > \
> >  	     property = tb_property_get_next(dir, property))
> >  
> > +int tb_register_property_dir(const char *key, struct
> > tb_property_dir
> > *dir);
> > +void tb_unregister_property_dir(const char *key, struct
> > tb_property_dir *dir);
> > +
> > +/**
> > + * struct tb_xdomain - Cross-domain (XDomain) connection
> > + * @dev: XDomain device
> > + * @tb: Pointer to the domain
> > + * @remote_uuid: UUID of the remote domain (host)
> > + * @local_uuid: Cached local UUID
> > + * @route: Route string the other domain can be reached
> > + * @vendor: Vendor ID of the remote domain
> > + * @device: Device ID of the demote domain
> > + * @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)
> > + * @is_unplugged: The XDomain is unplugged
> > + * @resume: The XDomain is being resumed
> > + * @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.
> > + * @get_properties_work: Work used to get remote domain properties
> > + * @properties_retries: Number of times left to read properties
> > + * @properties_changed_work: Work used to notify the remote domain
> > that
> > + *			     our properties have changed
> > + * @properties_changed_retries: Number of times left to send
> > properties
> > + *				changed notification
> > + * @link: Root switch link the remote domain is connected (ICM
> > only)
> > + * @depth: Depth in the chain the remote domain is connected (ICM
> > only)
> > + *
> > + * This structure represents connection across two domains
> > (hosts).
> > + * Each XDomain contains zero or more services which are exposed
> > as
> > + * &struct tb_service objects.
> > + *
> > + * Service drivers may access this structure if they need to
> > enumerate
> > + * non-standard properties but they need hold @lock when doing so
> > + * because properties can be changed asynchronously in response to
> > + * changes in the remote domain.
> > + */
> > +struct tb_xdomain {
> > +	struct device dev;
> > +	struct tb *tb;
> > +	uuid_t *remote_uuid;
> > +	const uuid_t *local_uuid;
> > +	u64 route;
> > +	u16 vendor;
> > +	u16 device;
> > +	struct mutex lock;
> > +	const char *vendor_name;
> > +	const char *device_name;
> > +	bool is_unplugged;
> > +	bool resume;
> > +	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 delayed_work get_properties_work;
> > +	int properties_retries;
> > +	struct delayed_work properties_changed_work;
> > +	int properties_changed_retries;
> > +	u8 link;
> > +	u8 depth;
> > +};
> > +
> > +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);
> > +struct tb_xdomain *tb_xdomain_find_by_uuid(struct tb *tb, const
> > uuid_t *uuid);
> > +
> > +static inline struct tb_xdomain *
> > +tb_xdomain_find_by_uuid_locked(struct tb *tb, const uuid_t *uuid)
> > +{
> > +	struct tb_xdomain *xd;
> > +
> > +	mutex_lock(&tb->lock);
> > +	xd = tb_xdomain_find_by_uuid(tb, uuid);
> > +	mutex_unlock(&tb->lock);
> > +
> > +	return xd;
> > +}
> > +
> > +static inline struct tb_xdomain *tb_xdomain_get(struct tb_xdomain
> > *xd)
> > +{
> > +	if (xd)
> > +		get_device(&xd->dev);
> > +	return xd;
> > +}
> > +
> > +static inline void tb_xdomain_put(struct tb_xdomain *xd)
> > +{
> > +	if (xd)
> > +		put_device(&xd->dev);
> > +}
> > +
> > +static inline bool tb_is_xdomain(const struct device *dev)
> > +{
> > +	return dev->type == &tb_xdomain_type;
> > +}
> > +
> > +static inline struct tb_xdomain *tb_to_xdomain(struct device *dev)
> > +{
> > +	if (tb_is_xdomain(dev))
> > +		return container_of(dev, struct tb_xdomain, dev);
> > +	return NULL;
> > +}
> > +
> > +int tb_xdomain_response(struct tb_xdomain *xd, const void
> > *response,
> > +			size_t size, enum tb_cfg_pkg_type type);
> > +int tb_xdomain_request(struct tb_xdomain *xd, const void *request,
> > +		       size_t request_size, enum tb_cfg_pkg_type
> > request_type,
> > +		       void *response, size_t response_size,
> > +		       enum tb_cfg_pkg_type response_type,
> > +		       unsigned int timeout_msec);
> > +
> > +/**
> > + * tb_protocol_handler - Protocol specific handler
> > + * @uuid: XDomain messages with this UUID are dispatched to this
> > handler
> > + * @callback: Callback called with the XDomain message. Returning
> > %1
> > + *	      here tells the XDomain core that the message was
> > handled
> > + *	      by this handler and should not be forwared to
> > other
> > + *	      handlers.
> > + * @data: Data passed with the callback
> > + * @list: Handlers are linked using this
> > + *
> > + * Thunderbolt services can hook into incoming XDomain requests by
> > + * registering protocol handler. Only limitation is that the
> > XDomain
> > + * discovery protocol UUID cannot be registered since it is
> > handled
> > by
> > + * the core XDomain code.
> > + *
> > + * The @callback must check that the message is really directed to
> > the
> > + * service the driver implements.
> > + */
> > +struct tb_protocol_handler {
> > +	const uuid_t *uuid;
> > +	int (*callback)(const void *buf, size_t size, void *data);
> > +	void *data;
> > +	struct list_head list;
> > +};
> > +
> > +int tb_register_protocol_handler(struct tb_protocol_handler
> > *handler);
> > +void tb_unregister_protocol_handler(struct tb_protocol_handler
> > *handler);
> > +
> > +/**
> > + * struct tb_service - Thunderbolt service
> > + * @dev: XDomain device
> > + * @id: ID of the service (shown in sysfs)
> > + * @key: Protocol key from the properties directory
> > + * @prtcid: Protocol ID from the properties directory
> > + * @prtcvers: Protocol version from the properties directory
> > + * @prtcrevs: Protocol software revision from the properties
> > directory
> > + * @prtcstns: Protocol settings mask from the properties directory
> > + *
> > + * Each domain exposes set of services it supports as collection
> > of
> > + * properties. For each service there will be one corresponding
> > + * &struct tb_service. Service drivers are bound to these.
> > + */
> > +struct tb_service {
> > +	struct device dev;
> > +	int id;
> > +	const char *key;
> > +	u32 prtcid;
> > +	u32 prtcvers;
> > +	u32 prtcrevs;
> > +	u32 prtcstns;
> > +};
> > +
> > +static inline struct tb_service *tb_service_get(struct tb_service
> > *svc)
> > +{
> > +	if (svc)
> > +		get_device(&svc->dev);
> > +	return svc;
> > +}
> > +
> > +static inline void tb_service_put(struct tb_service *svc)
> > +{
> > +	if (svc)
> > +		put_device(&svc->dev);
> > +}
> > +
> > +static inline bool tb_is_service(const struct device *dev)
> > +{
> > +	return dev->type == &tb_service_type;
> > +}
> > +
> > +static inline struct tb_service *tb_to_service(struct device *dev)
> > +{
> > +	if (tb_is_service(dev))
> > +		return container_of(dev, struct tb_service, dev);
> > +	return NULL;
> > +}
> > +
> > +/**
> > + * tb_service_driver - Thunderbolt service driver
> > + * @driver: Driver structure
> > + * @probe: Called when the driver is probed
> > + * @remove: Called when the driver is removed (optional)
> > + * @shutdown: Called at shutdown time to stop the service
> > (optional)
> > + * @id_table: Table of service identifiers the driver supports
> > + */
> > +struct tb_service_driver {
> > +	struct device_driver driver;
> > +	int (*probe)(struct tb_service *svc, const struct
> > tb_service_id *id);
> > +	void (*remove)(struct tb_service *svc);
> > +	void (*shutdown)(struct tb_service *svc);
> > +	const struct tb_service_id *id_table;
> > +};
> > +
> > +#define TB_SERVICE(key, id)				\
> > +	.match_flags = TBSVC_MATCH_PROTOCOL_KEY |	\
> > +		       TBSVC_MATCH_PROTOCOL_ID,		\
> > +	.protocol_key = (key),				\
> > +	.protocol_id = (id)
> > +
> > +int tb_register_service_driver(struct tb_service_driver *drv);
> > +void tb_unregister_service_driver(struct tb_service_driver *drv);
> > +
> > +static inline void *tb_service_get_drvdata(const struct tb_service
> > *svc)
> > +{
> > +	return dev_get_drvdata(&svc->dev);
> > +}
> > +
> > +static inline void tb_service_set_drvdata(struct tb_service *svc,
> > void *data)
> > +{
> > +	dev_set_drvdata(&svc->dev, data);
> > +}
> > +
> > +static inline struct tb_xdomain *tb_service_parent(struct
> > tb_service
> > *svc)
> > +{
> > +	return tb_to_xdomain(svc->dev.parent);
> > +}
> > +
> >  #endif /* THUNDERBOLT_H_ */
> > diff --git a/scripts/mod/devicetable-offsets.c
> > b/scripts/mod/devicetable-offsets.c
> > index e4d90e50f6fe..57263f2f8f2f 100644
> > --- a/scripts/mod/devicetable-offsets.c
> > +++ b/scripts/mod/devicetable-offsets.c
> > @@ -206,5 +206,12 @@ int main(void)
> >  	DEVID_FIELD(fsl_mc_device_id, vendor);
> >  	DEVID_FIELD(fsl_mc_device_id, obj_type);
> >  
> > +	DEVID(tb_service_id);
> > +	DEVID_FIELD(tb_service_id, match_flags);
> > +	DEVID_FIELD(tb_service_id, protocol_key);
> > +	DEVID_FIELD(tb_service_id, protocol_id);
> > +	DEVID_FIELD(tb_service_id, protocol_version);
> > +	DEVID_FIELD(tb_service_id, protocol_revision);
> > +
> >  	return 0;
> >  }
> > diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c
> > index 29d6699d5a06..6ef6e63f96fd 100644
> > --- a/scripts/mod/file2alias.c
> > +++ b/scripts/mod/file2alias.c
> > @@ -1301,6 +1301,31 @@ static int do_fsl_mc_entry(const char
> > *filename, void *symval,
> >  }
> >  ADD_TO_DEVTABLE("fslmc", fsl_mc_device_id, do_fsl_mc_entry);
> >  
> > +/* Looks like: tbsvc:kSpNvNrN */
> > +static int do_tbsvc_entry(const char *filename, void *symval, char
> > *alias)
> > +{
> > +	DEF_FIELD(symval, tb_service_id, match_flags);
> > +	DEF_FIELD_ADDR(symval, tb_service_id, protocol_key);
> > +	DEF_FIELD(symval, tb_service_id, protocol_id);
> > +	DEF_FIELD(symval, tb_service_id, protocol_version);
> > +	DEF_FIELD(symval, tb_service_id, protocol_revision);
> > +
> > +	strcpy(alias, "tbsvc:");
> > +	if (match_flags & TBSVC_MATCH_PROTOCOL_KEY)
> > +		sprintf(alias + strlen(alias), "k%s",
> > *protocol_key);
> > +	else
> > +		strcat(alias + strlen(alias), "k*");
> > +	ADD(alias, "p", match_flags & TBSVC_MATCH_PROTOCOL_ID,
> > protocol_id);
> > +	ADD(alias, "v", match_flags &
> > TBSVC_MATCH_PROTOCOL_VERSION,
> > +	    protocol_version);
> > +	ADD(alias, "r", match_flags &
> > TBSVC_MATCH_PROTOCOL_REVISION,
> > +	    protocol_revision);
> > +
> > +	add_wildcard(alias);
> > +	return 1;
> > +}
> > +ADD_TO_DEVTABLE("tbsvc", tb_service_id, do_tbsvc_entry);
> > +
> >  /* Does namelen bytes of name exactly match the symbol? */
> >  static bool sym_is(const char *name, unsigned namelen, const char
> > *symbol)
> >  {

^ permalink raw reply

* Re: [PATCH 06/16] thunderbolt: Add support for XDomain discovery protocol
From: Dan Williams @ 2017-09-18 16:12 UTC (permalink / raw)
  To: Mika Westerberg, Greg Kroah-Hartman, David S . Miller
  Cc: Andreas Noever, Michael Jamet, Yehezkel Bernat, Amir Levy,
	Mario.Limonciello, Lukas Wunner, Andy Shevchenko, linux-kernel,
	netdev
In-Reply-To: <20170918153049.44185-7-mika.westerberg@linux.intel.com>

On Mon, 2017-09-18 at 18:30 +0300, Mika Westerberg wrote:
> When two hosts are connected over a Thunderbolt cable, there is a
> protocol they can use to communicate capabilities supported by the
> host.
> The discovery protocol uses automatically configured control channel
> (ring 0) and is build on top of request/response transactions using
> special XDomain primitives provided by the Thunderbolt base protocol.
> 
> The capabilities consists of a root directory block of basic
> properties
> used for identification of the host, and then there can be zero or
> more
> directories each describing a Thunderbolt service and its
> capabilities.
> 
> Once both sides have discovered what is supported the two hosts can
> setup high-speed DMA paths and transfer data to the other side using
> whatever protocol was agreed based on the properties. The software
> protocol used to communicate which DMA paths to enable is service
> specific.
> 
> This patch adds support for the XDomain discovery protocol to the
> Thunderbolt bus. We model each remote host connection as a Linux
> XDomain
> device. For each Thunderbolt service found supported on the XDomain
> device, we create Linux Thunderbolt service device which Thunderbolt
> service drivers can then bind to based on the protocol identification
> information retrieved from the property directory describing the
> service.
> 
> This code is based on the work done by Amir Levy and Michael Jamet.
> 
> Signed-off-by: Michael Jamet <michael.jamet@intel.com>
> Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
> Reviewed-by: Yehezkel Bernat <yehezkel.bernat@intel.com>
> ---
>  Documentation/ABI/testing/sysfs-bus-thunderbolt |   48 +
>  drivers/thunderbolt/Makefile                    |    2 +-
>  drivers/thunderbolt/ctl.c                       |   11 +-
>  drivers/thunderbolt/ctl.h                       |    2 +-
>  drivers/thunderbolt/domain.c                    |  197 ++-
>  drivers/thunderbolt/icm.c                       |  218 +++-
>  drivers/thunderbolt/nhi.h                       |    2 +
>  drivers/thunderbolt/switch.c                    |    7 +-
>  drivers/thunderbolt/tb.h                        |   39 +-
>  drivers/thunderbolt/tb_msgs.h                   |  123 ++
>  drivers/thunderbolt/xdomain.c                   | 1576
> +++++++++++++++++++++++
>  include/linux/mod_devicetable.h                 |   26 +
>  include/linux/thunderbolt.h                     |  242 ++++
>  scripts/mod/devicetable-offsets.c               |    7 +
>  scripts/mod/file2alias.c                        |   25 +
>  15 files changed, 2507 insertions(+), 18 deletions(-)
>  create mode 100644 drivers/thunderbolt/xdomain.c
> 
> diff --git a/Documentation/ABI/testing/sysfs-bus-thunderbolt
> b/Documentation/ABI/testing/sysfs-bus-thunderbolt
> index 392bef5bd399..cb48850bd79b 100644
> --- a/Documentation/ABI/testing/sysfs-bus-thunderbolt
> +++ b/Documentation/ABI/testing/sysfs-bus-thunderbolt
> @@ -110,3 +110,51 @@ Description:	When new NVM image is
> written to the non-active NVM
>  		is directly the status value from the DMA
> configuration
>  		based mailbox before the device is power cycled.
> Writing
>  		0 here clears the status.
> +
> +What:		/sys/bus/thunderbolt/devices/<xdomain>.<service
> >/key
> +Date:		Dec 2017
> +KernelVersion:	4.14
> +Contact:	thunderbolt-software@lists.01.org
> +Description:	This contains name of the property directory the
> XDomain
> +		service exposes. This entry describes the protocol
> in
> +		question. Following directories are already reserved
> by
> +		the Apple XDomain specification:
> +
> +		network:  IP/ethernet over Thunderbolt
> +		targetdm: Target disk mode protocol over Thunderbolt
> +		extdisp:  External display mode protocol over
> Thunderbolt
> +
> +What:		/sys/bus/thunderbolt/devices/<xdomain>.<service
> >/modalias
> +Date:		Dec 2017
> +KernelVersion:	4.14
> +Contact:	thunderbolt-software@lists.01.org
> +Description:	Stores the same MODALIAS value emitted by uevent
> for
> +		the XDomain service. Format: tbtsvc:kSpNvNrN
> +
> +What:		/sys/bus/thunderbolt/devices/<xdomain>.<service
> >/prtcid
> +Date:		Dec 2017
> +KernelVersion:	4.14
> +Contact:	thunderbolt-software@lists.01.org
> +Description:	This contains XDomain protocol identifier the
> XDomain
> +		service supports.
> +
> +What:		/sys/bus/thunderbolt/devices/<xdomain>.<service
> >/prtcvers
> +Date:		Dec 2017
> +KernelVersion:	4.14
> +Contact:	thunderbolt-software@lists.01.org
> +Description:	This contains XDomain protocol version the
> XDomain
> +		service supports.
> +
> +What:		/sys/bus/thunderbolt/devices/<xdomain>.<service
> >/prtcrevs
> +Date:		Dec 2017
> +KernelVersion:	4.14
> +Contact:	thunderbolt-software@lists.01.org
> +Description:	This contains XDomain software version the
> XDomain
> +		service supports.
> +
> +What:		/sys/bus/thunderbolt/devices/<xdomain>.<service
> >/prtcstns
> +Date:		Dec 2017
> +KernelVersion:	4.14
> +Contact:	thunderbolt-software@lists.01.org
> +Description:	This contains XDomain service specific settings
> as
> +		bitmask. Format: %x
> diff --git a/drivers/thunderbolt/Makefile
> b/drivers/thunderbolt/Makefile
> index 7afd21f5383a..f2f0de27252b 100644
> --- a/drivers/thunderbolt/Makefile
> +++ b/drivers/thunderbolt/Makefile
> @@ -1,3 +1,3 @@
>  obj-${CONFIG_THUNDERBOLT} := thunderbolt.o
>  thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o path.o
> tunnel_pci.o eeprom.o
> -thunderbolt-objs += domain.o dma_port.o icm.o property.o
> +thunderbolt-objs += domain.o dma_port.o icm.o property.o xdomain.o
> diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c
> index e6a4c9458c76..46e393c5fd1d 100644
> --- a/drivers/thunderbolt/ctl.c
> +++ b/drivers/thunderbolt/ctl.c
> @@ -368,10 +368,10 @@ static int tb_ctl_tx(struct tb_ctl *ctl, const
> void *data, size_t len,
>  /**
>   * tb_ctl_handle_event() - acknowledge a plug event, invoke ctl-
> >callback
>   */
> -static void tb_ctl_handle_event(struct tb_ctl *ctl, enum
> tb_cfg_pkg_type type,
> +static bool tb_ctl_handle_event(struct tb_ctl *ctl, enum
> tb_cfg_pkg_type type,
>  				struct ctl_pkg *pkg, size_t size)
>  {
> -	ctl->callback(ctl->callback_data, type, pkg->buffer, size);
> +	return ctl->callback(ctl->callback_data, type, pkg->buffer,
> size);
>  }
>  
>  static void tb_ctl_rx_submit(struct ctl_pkg *pkg)
> @@ -444,6 +444,8 @@ static void tb_ctl_rx_callback(struct tb_ring
> *ring, struct ring_frame *frame,
>  		break;
>  
>  	case TB_CFG_PKG_EVENT:
> +	case TB_CFG_PKG_XDOMAIN_RESP:
> +	case TB_CFG_PKG_XDOMAIN_REQ:
>  		if (*(__be32 *)(pkg->buffer + frame->size) != crc32)
> {
>  			tb_ctl_err(pkg->ctl,
>  				   "RX: checksum mismatch, dropping
> packet\n");
> @@ -451,8 +453,9 @@ static void tb_ctl_rx_callback(struct tb_ring
> *ring, struct ring_frame *frame,
>  		}
>  		/* Fall through */
>  	case TB_CFG_PKG_ICM_EVENT:
> -		tb_ctl_handle_event(pkg->ctl, frame->eof, pkg,
> frame->size);
> -		goto rx;
> +		if (tb_ctl_handle_event(pkg->ctl, frame->eof, pkg,
> frame->size))
> +			goto rx;
> +		break;
>  
>  	default:
>  		break;
> diff --git a/drivers/thunderbolt/ctl.h b/drivers/thunderbolt/ctl.h
> index d0f21e1e0b8b..85c49dd301ea 100644
> --- a/drivers/thunderbolt/ctl.h
> +++ b/drivers/thunderbolt/ctl.h
> @@ -16,7 +16,7 @@
>  /* control channel */
>  struct tb_ctl;
>  
> -typedef void (*event_cb)(void *data, enum tb_cfg_pkg_type type,
> +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);
> diff --git a/drivers/thunderbolt/domain.c
> b/drivers/thunderbolt/domain.c
> index 9f2dcd48974d..29d6436ec8ce 100644
> --- a/drivers/thunderbolt/domain.c
> +++ b/drivers/thunderbolt/domain.c
> @@ -20,6 +20,98 @@
>  
>  static DEFINE_IDA(tb_domain_ida);
>  
> +static bool match_service_id(const struct tb_service_id *id,
> +			     const struct tb_service *svc)
> +{
> +	if (id->match_flags & TBSVC_MATCH_PROTOCOL_KEY) {
> +		if (strcmp(id->protocol_key, svc->key))
> +			return false;
> +	}
> +
> +	if (id->match_flags & TBSVC_MATCH_PROTOCOL_ID) {
> +		if (id->protocol_id != svc->prtcid)
> +			return false;
> +	}
> +
> +	if (id->match_flags & TBSVC_MATCH_PROTOCOL_VERSION) {
> +		if (id->protocol_version != svc->prtcvers)
> +			return false;
> +	}
> +
> +	if (id->match_flags & TBSVC_MATCH_PROTOCOL_VERSION) {
> +		if (id->protocol_revision != svc->prtcrevs)
> +			return false;
> +	}
> +
> +	return true;
> +}
> +
> +static const struct tb_service_id *__tb_service_match(struct device
> *dev,
> +						      struct
> device_driver *drv)
> +{
> +	struct tb_service_driver *driver;
> +	const struct tb_service_id *ids;
> +	struct tb_service *svc;
> +
> +	svc = tb_to_service(dev);
> +	if (!svc)
> +		return NULL;
> +
> +	driver = container_of(drv, struct tb_service_driver,
> driver);
> +	if (!driver->id_table)
> +		return NULL;
> +
> +	for (ids = driver->id_table; ids->match_flags != 0; ids++) {
> +		if (match_service_id(ids, svc))
> +			return ids;
> +	}
> +
> +	return NULL;
> +}
> +
> +static int tb_service_match(struct device *dev, struct device_driver
> *drv)
> +{
> +	return !!__tb_service_match(dev, drv);
> +}
> +
> +static int tb_service_probe(struct device *dev)
> +{
> +	struct tb_service *svc = tb_to_service(dev);
> +	struct tb_service_driver *driver;
> +	const struct tb_service_id *id;
> +
> +	driver = container_of(dev->driver, struct tb_service_driver,
> driver);
> +	id = __tb_service_match(dev, &driver->driver);
> +
> +	return driver->probe(svc, id);

Could you pass 'dev' to the probe function so that things like the
network sub-driver can sensibly link the netdev to the parent hardware
in sysfs with SET_NETDEV_DEV()?

Dan

> +}
> +
> +static int tb_service_remove(struct device *dev)
> +{
> +	struct tb_service *svc = tb_to_service(dev);
> +	struct tb_service_driver *driver;
> +
> +	driver = container_of(dev->driver, struct tb_service_driver,
> driver);
> +	if (driver->remove)
> +		driver->remove(svc);
> +
> +	return 0;
> +}
> +
> +static void tb_service_shutdown(struct device *dev)
> +{
> +	struct tb_service_driver *driver;
> +	struct tb_service *svc;
> +
> +	svc = tb_to_service(dev);
> +	if (!svc || !dev->driver)
> +		return;
> +
> +	driver = container_of(dev->driver, struct tb_service_driver,
> driver);
> +	if (driver->shutdown)
> +		driver->shutdown(svc);
> +}
> +
>  static const char * const tb_security_names[] = {
>  	[TB_SECURITY_NONE] = "none",
>  	[TB_SECURITY_USER] = "user",
> @@ -52,6 +144,10 @@ static const struct attribute_group
> *domain_attr_groups[] = {
>  
>  struct bus_type tb_bus_type = {
>  	.name = "thunderbolt",
> +	.match = tb_service_match,
> +	.probe = tb_service_probe,
> +	.remove = tb_service_remove,
> +	.shutdown = tb_service_shutdown,
>  };
>  
>  static void tb_domain_release(struct device *dev)
> @@ -128,17 +224,26 @@ struct tb *tb_domain_alloc(struct tb_nhi *nhi,
> size_t privsize)
>  	return NULL;
>  }
>  
> -static void tb_domain_event_cb(void *data, enum tb_cfg_pkg_type
> type,
> +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;
> +		return true;
>  	}
>  
> -	tb->cm_ops->handle_event(tb, type, buf, size);
> +	switch (type) {
> +	case TB_CFG_PKG_XDOMAIN_REQ:
> +	case TB_CFG_PKG_XDOMAIN_RESP:
> +		return tb_xdomain_handle_request(tb, type, buf,
> size);
> +
> +	default:
> +		tb->cm_ops->handle_event(tb, type, buf, size);
> +	}
> +
> +	return true;
>  }
>  
>  /**
> @@ -443,9 +548,92 @@ int tb_domain_disconnect_pcie_paths(struct tb
> *tb)
>  	return tb->cm_ops->disconnect_pcie_paths(tb);
>  }
>  
> +/**
> + * tb_domain_approve_xdomain_paths() - Enable DMA paths for XDomain
> + * @tb: Domain enabling the DMA paths
> + * @xd: XDomain DMA paths are created to
> + *
> + * Calls connection manager specific method to enable DMA paths to
> the
> + * XDomain in question.
> + *
> + * Return: 0% in case of success and negative errno otherwise. In
> + * 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)
> +{
> +	if (!tb->cm_ops->approve_xdomain_paths)
> +		return -ENOTSUPP;
> +
> +	return tb->cm_ops->approve_xdomain_paths(tb, xd);
> +}
> +
> +/**
> + * tb_domain_disconnect_xdomain_paths() - Disable DMA paths for
> XDomain
> + * @tb: Domain disabling the DMA paths
> + * @xd: XDomain whose DMA paths are disconnected
> + *
> + * Calls connection manager specific method to disconnect DMA paths
> to
> + * the XDomain in question.
> + *
> + * Return: 0% in case of success and negative errno otherwise. In
> + * 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)
> +{
> +	if (!tb->cm_ops->disconnect_xdomain_paths)
> +		return -ENOTSUPP;
> +
> +	return tb->cm_ops->disconnect_xdomain_paths(tb, xd);
> +}
> +
> +static int disconnect_xdomain(struct device *dev, void *data)
> +{
> +	struct tb_xdomain *xd;
> +	struct tb *tb = data;
> +	int ret = 0;
> +
> +	xd = tb_to_xdomain(dev);
> +	if (xd && xd->tb == tb)
> +		ret = tb_xdomain_disable_paths(xd);
> +
> +	return ret;
> +}
> +
> +/**
> + * tb_domain_disconnect_all_paths() - Disconnect all paths for the
> domain
> + * @tb: Domain whose paths are disconnected
> + *
> + * This function can be used to disconnect all paths (PCIe, XDomain)
> for
> + * example in preparation for host NVM firmware upgrade. After this
> is
> + * called the paths cannot be established without reseting the
> switch.
> + *
> + * Return: %0 in case of success and negative errno otherwise.
> + */
> +int tb_domain_disconnect_all_paths(struct tb *tb)
> +{
> +	int ret;
> +
> +	ret = tb_domain_disconnect_pcie_paths(tb);
> +	if (ret)
> +		return ret;
> +
> +	return bus_for_each_dev(&tb_bus_type, NULL, tb,
> disconnect_xdomain);
> +}
> +
>  int tb_domain_init(void)
>  {
> -	return bus_register(&tb_bus_type);
> +	int ret;
> +
> +	ret = tb_xdomain_init();
> +	if (ret)
> +		return ret;
> +	ret = bus_register(&tb_bus_type);
> +	if (ret)
> +		tb_xdomain_exit();
> +
> +	return ret;
>  }
>  
>  void tb_domain_exit(void)
> @@ -453,4 +641,5 @@ void tb_domain_exit(void)
>  	bus_unregister(&tb_bus_type);
>  	ida_destroy(&tb_domain_ida);
>  	tb_switch_exit();
> +	tb_xdomain_exit();
>  }
> diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c
> index 8c22b91ed040..ab02d13f40b7 100644
> --- a/drivers/thunderbolt/icm.c
> +++ b/drivers/thunderbolt/icm.c
> @@ -60,6 +60,8 @@
>   * @get_route: Find a route string for given switch
>   * @device_connected: Handle device connected ICM message
>   * @device_disconnected: Handle device disconnected ICM message
> + * @xdomain_connected - Handle XDomain connected ICM message
> + * @xdomain_disconnected - Handle XDomain disconnected ICM message
>   */
>  struct icm {
>  	struct mutex request_lock;
> @@ -74,6 +76,10 @@ struct icm {
>  				 const struct icm_pkg_header *hdr);
>  	void (*device_disconnected)(struct tb *tb,
>  				    const struct icm_pkg_header
> *hdr);
> +	void (*xdomain_connected)(struct tb *tb,
> +				  const struct icm_pkg_header *hdr);
> +	void (*xdomain_disconnected)(struct tb *tb,
> +				     const struct icm_pkg_header
> *hdr);
>  };
>  
>  struct icm_notification {
> @@ -89,7 +95,10 @@ static inline struct tb *icm_to_tb(struct icm
> *icm)
>  
>  static inline u8 phy_port_from_route(u64 route, u8 depth)
>  {
> -	return tb_phy_port_from_link(route >> ((depth - 1) * 8));
> +	u8 link;
> +
> +	link = depth ? route >> ((depth - 1) * 8) : route;
> +	return tb_phy_port_from_link(link);
>  }
>  
>  static inline u8 dual_link_from_link(u8 link)
> @@ -320,6 +329,51 @@ 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)
> +{
> +	struct icm_fr_pkg_approve_xdomain_response reply;
> +	struct icm_fr_pkg_approve_xdomain request;
> +	int ret;
> +
> +	memset(&request, 0, sizeof(request));
> +	request.hdr.code = ICM_APPROVE_XDOMAIN;
> +	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;
> +
> +	memset(&reply, 0, sizeof(reply));
> +	ret = icm_request(tb, &request, sizeof(request), &reply,
> sizeof(reply),
> +			  1, ICM_TIMEOUT);
> +	if (ret)
> +		return ret;
> +
> +	if (reply.hdr.flags & ICM_FLAGS_ERROR)
> +		return -EIO;
> +
> +	return 0;
> +}
> +
> +static int icm_fr_disconnect_xdomain_paths(struct tb *tb, struct
> tb_xdomain *xd)
> +{
> +	u8 phy_port;
> +	u8 cmd;
> +
> +	phy_port = tb_phy_port_from_link(xd->link);
> +	if (phy_port == 0)
> +		cmd = NHI_MAILBOX_DISCONNECT_PA;
> +	else
> +		cmd = NHI_MAILBOX_DISCONNECT_PB;
> +
> +	nhi_mailbox_cmd(tb->nhi, cmd, 1);
> +	usleep_range(10, 50);
> +	nhi_mailbox_cmd(tb->nhi, cmd, 2);
> +	return 0;
> +}
> +
>  static void remove_switch(struct tb_switch *sw)
>  {
>  	struct tb_switch *parent_sw;
> @@ -475,6 +529,141 @@ icm_fr_device_disconnected(struct tb *tb, const
> struct icm_pkg_header *hdr)
>  	tb_switch_put(sw);
>  }
>  
> +static void remove_xdomain(struct tb_xdomain *xd)
> +{
> +	struct tb_switch *sw;
> +
> +	sw = tb_to_switch(xd->dev.parent);
> +	tb_port_at(xd->route, sw)->xdomain = NULL;
> +	tb_xdomain_remove(xd);
> +}
> +
> +static void
> +icm_fr_xdomain_connected(struct tb *tb, const struct icm_pkg_header
> *hdr)
> +{
> +	const struct icm_fr_event_xdomain_connected *pkg =
> +		(const struct icm_fr_event_xdomain_connected *)hdr;
> +	struct tb_xdomain *xd;
> +	struct tb_switch *sw;
> +	u8 link, depth;
> +	bool approved;
> +	u64 route;
> +
> +	/*
> +	 * After NVM upgrade adding root switch device fails because
> we
> +	 * initiated reset. During that time ICM might still send
> +	 * XDomain connected message which we ignore here.
> +	 */
> +	if (!tb->root_switch)
> +		return;
> +
> +	link = pkg->link_info & ICM_LINK_INFO_LINK_MASK;
> +	depth = (pkg->link_info & ICM_LINK_INFO_DEPTH_MASK) >>
> +		ICM_LINK_INFO_DEPTH_SHIFT;
> +	approved = pkg->link_info & ICM_LINK_INFO_APPROVED;
> +
> +	if (link > ICM_MAX_LINK || depth > ICM_MAX_DEPTH) {
> +		tb_warn(tb, "invalid topology %u.%u, ignoring\n",
> link, depth);
> +		return;
> +	}
> +
> +	route = get_route(pkg->local_route_hi, pkg->local_route_lo);
> +
> +	xd = tb_xdomain_find_by_uuid(tb, &pkg->remote_uuid);
> +	if (xd) {
> +		u8 xd_phy_port, phy_port;
> +
> +		xd_phy_port = phy_port_from_route(xd->route, xd-
> >depth);
> +		phy_port = phy_port_from_route(route, depth);
> +
> +		if (xd->depth == depth && xd_phy_port == phy_port) {
> +			xd->link = link;
> +			xd->route = route;
> +			xd->is_unplugged = false;
> +			tb_xdomain_put(xd);
> +			return;
> +		}
> +
> +		/*
> +		 * If we find an existing XDomain connection remove
> it
> +		 * now. We need to go through login handshake and
> +		 * everything anyway to be able to re-establish the
> +		 * connection.
> +		 */
> +		remove_xdomain(xd);
> +		tb_xdomain_put(xd);
> +	}
> +
> +	/*
> +	 * Look if there already exists an XDomain in the same place
> +	 * than the new one and in that case remove it because it is
> +	 * most likely another host that got disconnected.
> +	 */
> +	xd = tb_xdomain_find_by_link_depth(tb, link, depth);
> +	if (!xd) {
> +		u8 dual_link;
> +
> +		dual_link = dual_link_from_link(link);
> +		if (dual_link)
> +			xd = tb_xdomain_find_by_link_depth(tb,
> dual_link,
> +							   depth);
> +	}
> +	if (xd) {
> +		remove_xdomain(xd);
> +		tb_xdomain_put(xd);
> +	}
> +
> +	/*
> +	 * If the user disconnected a switch during suspend and
> +	 * connected another host to the same port, remove the
> switch
> +	 * first.
> +	 */
> +	sw = get_switch_at_route(tb->root_switch, route);
> +	if (sw)
> +		remove_switch(sw);
> +
> +	sw = tb_switch_find_by_link_depth(tb, link, depth);
> +	if (!sw) {
> +		tb_warn(tb, "no switch exists at %u.%u, ignoring\n",
> link,
> +			depth);
> +		return;
> +	}
> +
> +	xd = tb_xdomain_alloc(sw->tb, &sw->dev, route,
> +			      &pkg->local_uuid, &pkg->remote_uuid);
> +	if (!xd) {
> +		tb_switch_put(sw);
> +		return;
> +	}
> +
> +	xd->link = link;
> +	xd->depth = depth;
> +
> +	tb_port_at(route, sw)->xdomain = xd;
> +
> +	tb_xdomain_add(xd);
> +	tb_switch_put(sw);
> +}
> +
> +static void
> +icm_fr_xdomain_disconnected(struct tb *tb, const struct
> icm_pkg_header *hdr)
> +{
> +	const struct icm_fr_event_xdomain_disconnected *pkg =
> +		(const struct icm_fr_event_xdomain_disconnected
> *)hdr;
> +	struct tb_xdomain *xd;
> +
> +	/*
> +	 * If the connection is through one or multiple devices, the
> +	 * XDomain device is removed along with them so it is fine
> if we
> +	 * cannot find it here.
> +	 */
> +	xd = tb_xdomain_find_by_uuid(tb, &pkg->remote_uuid);
> +	if (xd) {
> +		remove_xdomain(xd);
> +		tb_xdomain_put(xd);
> +	}
> +}
> +
>  static struct pci_dev *get_upstream_port(struct pci_dev *pdev)
>  {
>  	struct pci_dev *parent;
> @@ -594,6 +783,12 @@ static void icm_handle_notification(struct
> work_struct *work)
>  	case ICM_EVENT_DEVICE_DISCONNECTED:
>  		icm->device_disconnected(tb, n->pkg);
>  		break;
> +	case ICM_EVENT_XDOMAIN_CONNECTED:
> +		icm->xdomain_connected(tb, n->pkg);
> +		break;
> +	case ICM_EVENT_XDOMAIN_DISCONNECTED:
> +		icm->xdomain_disconnected(tb, n->pkg);
> +		break;
>  	}
>  
>  	mutex_unlock(&tb->lock);
> @@ -927,6 +1122,10 @@ static void icm_unplug_children(struct
> tb_switch *sw)
>  
>  		if (tb_is_upstream_port(port))
>  			continue;
> +		if (port->xdomain) {
> +			port->xdomain->is_unplugged = true;
> +			continue;
> +		}
>  		if (!port->remote)
>  			continue;
>  
> @@ -943,6 +1142,13 @@ static void icm_free_unplugged_children(struct
> tb_switch *sw)
>  
>  		if (tb_is_upstream_port(port))
>  			continue;
> +
> +		if (port->xdomain && port->xdomain->is_unplugged) {
> +			tb_xdomain_remove(port->xdomain);
> +			port->xdomain = NULL;
> +			continue;
> +		}
> +
>  		if (!port->remote)
>  			continue;
>  
> @@ -1009,8 +1215,10 @@ static int icm_start(struct tb *tb)
>  	tb->root_switch->no_nvm_upgrade = x86_apple_machine;
>  
>  	ret = tb_switch_add(tb->root_switch);
> -	if (ret)
> +	if (ret) {
>  		tb_switch_put(tb->root_switch);
> +		tb->root_switch = NULL;
> +	}
>  
>  	return ret;
>  }
> @@ -1042,6 +1250,8 @@ static const struct tb_cm_ops icm_fr_ops = {
>  	.add_switch_key = icm_fr_add_switch_key,
>  	.challenge_switch_key = icm_fr_challenge_switch_key,
>  	.disconnect_pcie_paths = icm_disconnect_pcie_paths,
> +	.approve_xdomain_paths = icm_fr_approve_xdomain_paths,
> +	.disconnect_xdomain_paths = icm_fr_disconnect_xdomain_paths,
>  };
>  
>  struct tb *icm_probe(struct tb_nhi *nhi)
> @@ -1064,6 +1274,8 @@ struct tb *icm_probe(struct tb_nhi *nhi)
>  		icm->get_route = icm_fr_get_route;
>  		icm->device_connected = icm_fr_device_connected;
>  		icm->device_disconnected =
> icm_fr_device_disconnected;
> +		icm->xdomain_connected = icm_fr_xdomain_connected;
> +		icm->xdomain_disconnected =
> icm_fr_xdomain_disconnected;
>  		tb->cm_ops = &icm_fr_ops;
>  		break;
>  
> @@ -1077,6 +1289,8 @@ struct tb *icm_probe(struct tb_nhi *nhi)
>  		icm->get_route = icm_ar_get_route;
>  		icm->device_connected = icm_fr_device_connected;
>  		icm->device_disconnected =
> icm_fr_device_disconnected;
> +		icm->xdomain_connected = icm_fr_xdomain_connected;
> +		icm->xdomain_disconnected =
> icm_fr_xdomain_disconnected;
>  		tb->cm_ops = &icm_fr_ops;
>  		break;
>  	}
> diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h
> index 5b5bb2c436be..0e05828983db 100644
> --- a/drivers/thunderbolt/nhi.h
> +++ b/drivers/thunderbolt/nhi.h
> @@ -157,6 +157,8 @@ enum nhi_mailbox_cmd {
>  	NHI_MAILBOX_SAVE_DEVS = 0x05,
>  	NHI_MAILBOX_DISCONNECT_PCIE_PATHS = 0x06,
>  	NHI_MAILBOX_DRV_UNLOADS = 0x07,
> +	NHI_MAILBOX_DISCONNECT_PA = 0x10,
> +	NHI_MAILBOX_DISCONNECT_PB = 0x11,
>  	NHI_MAILBOX_ALLOW_ALL_DEVS = 0x23,
>  };
>  
> diff --git a/drivers/thunderbolt/switch.c
> b/drivers/thunderbolt/switch.c
> index 53f40c57df59..dfc357d33e1e 100644
> --- a/drivers/thunderbolt/switch.c
> +++ b/drivers/thunderbolt/switch.c
> @@ -171,11 +171,11 @@ static int nvm_authenticate_host(struct
> tb_switch *sw)
>  
>  	/*
>  	 * Root switch NVM upgrade requires that we disconnect the
> -	 * existing PCIe paths first (in case it is not in safe mode
> +	 * existing paths first (in case it is not in safe mode
>  	 * already).
>  	 */
>  	if (!sw->safe_mode) {
> -		ret = tb_domain_disconnect_pcie_paths(sw->tb);
> +		ret = tb_domain_disconnect_all_paths(sw->tb);
>  		if (ret)
>  			return ret;
>  		/*
> @@ -1363,6 +1363,9 @@ void tb_switch_remove(struct tb_switch *sw)
>  		if (sw->ports[i].remote)
>  			tb_switch_remove(sw->ports[i].remote->sw);
>  		sw->ports[i].remote = NULL;
> +		if (sw->ports[i].xdomain)
> +			tb_xdomain_remove(sw->ports[i].xdomain);
> +		sw->ports[i].xdomain = NULL;
>  	}
>  
>  	if (!sw->is_unplugged)
> diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
> index ea21d927bd09..74af9d4929ab 100644
> --- a/drivers/thunderbolt/tb.h
> +++ b/drivers/thunderbolt/tb.h
> @@ -9,6 +9,7 @@
>  
>  #include <linux/nvmem-provider.h>
>  #include <linux/pci.h>
> +#include <linux/thunderbolt.h>
>  #include <linux/uuid.h>
>  
>  #include "tb_regs.h"
> @@ -109,14 +110,25 @@ struct tb_switch {
>  
>  /**
>   * struct tb_port - a thunderbolt port, part of a tb_switch
> + * @config: Cached port configuration read from registers
> + * @sw: Switch the port belongs to
> + * @remote: Remote port (%NULL if not connected)
> + * @xdomain: Remote host (%NULL if not connected)
> + * @cap_phy: Offset, zero if not found
> + * @port: Port number on switch
> + * @disabled: Disabled by eeprom
> + * @dual_link_port: If the switch is connected using two ports,
> points
> + *		    to the other port.
> + * @link_nr: Is this primary or secondary port on the dual_link.
>   */
>  struct tb_port {
>  	struct tb_regs_port_header config;
>  	struct tb_switch *sw;
> -	struct tb_port *remote; /* remote port, NULL if not
> connected */
> -	int cap_phy; /* offset, zero if not found */
> -	u8 port; /* port number on switch */
> -	bool disabled; /* disabled by eeprom */
> +	struct tb_port *remote;
> +	struct tb_xdomain *xdomain;
> +	int cap_phy;
> +	u8 port;
> +	bool disabled;
>  	struct tb_port *dual_link_port;
>  	u8 link_nr:1;
>  };
> @@ -189,6 +201,8 @@ struct tb_path {
>   * @add_switch_key: Add key to switch
>   * @challenge_switch_key: Challenge switch using key
>   * @disconnect_pcie_paths: Disconnects PCIe paths before NVM update
> + * @approve_xdomain_paths: Approve (establish) XDomain DMA paths
> + * @disconnect_xdomain_paths: Disconnect XDomain DMA paths
>   */
>  struct tb_cm_ops {
>  	int (*driver_ready)(struct tb *tb);
> @@ -205,6 +219,8 @@ 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);
>  };
>  
>  static inline void *tb_priv(struct tb *tb)
> @@ -331,6 +347,8 @@ extern struct device_type tb_switch_type;
>  int tb_domain_init(void);
>  void tb_domain_exit(void);
>  void tb_switch_exit(void);
> +int tb_xdomain_init(void);
> +void tb_xdomain_exit(void);
>  
>  struct tb *tb_domain_alloc(struct tb_nhi *nhi, size_t privsize);
>  int tb_domain_add(struct tb *tb);
> @@ -343,6 +361,9 @@ 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_disconnect_all_paths(struct tb *tb);
>  
>  static inline void tb_domain_put(struct tb *tb)
>  {
> @@ -422,4 +443,14 @@ static inline u64 tb_downstream_route(struct
> tb_port *port)
>  	       | ((u64) port->port << (port->sw->config.depth * 8));
>  }
>  
> +bool tb_xdomain_handle_request(struct tb *tb, enum tb_cfg_pkg_type
> type,
> +			       const void *buf, size_t size);
> +struct tb_xdomain *tb_xdomain_alloc(struct tb *tb, struct device
> *parent,
> +				    u64 route, const uuid_t
> *local_uuid,
> +				    const uuid_t *remote_uuid);
> +void tb_xdomain_add(struct tb_xdomain *xd);
> +void tb_xdomain_remove(struct tb_xdomain *xd);
> +struct tb_xdomain *tb_xdomain_find_by_link_depth(struct tb *tb, u8
> link,
> +						 u8 depth);
> +
>  #endif
> diff --git a/drivers/thunderbolt/tb_msgs.h
> b/drivers/thunderbolt/tb_msgs.h
> index fe3039b05da6..2a76908537a6 100644
> --- a/drivers/thunderbolt/tb_msgs.h
> +++ b/drivers/thunderbolt/tb_msgs.h
> @@ -101,11 +101,14 @@ enum icm_pkg_code {
>  	ICM_CHALLENGE_DEVICE = 0x5,
>  	ICM_ADD_DEVICE_KEY = 0x6,
>  	ICM_GET_ROUTE = 0xa,
> +	ICM_APPROVE_XDOMAIN = 0x10,
>  };
>  
>  enum icm_event_code {
>  	ICM_EVENT_DEVICE_CONNECTED = 3,
>  	ICM_EVENT_DEVICE_DISCONNECTED = 4,
> +	ICM_EVENT_XDOMAIN_CONNECTED = 6,
> +	ICM_EVENT_XDOMAIN_DISCONNECTED = 7,
>  };
>  
>  struct icm_pkg_header {
> @@ -188,6 +191,25 @@ struct icm_fr_event_device_disconnected {
>  	u16 link_info;
>  } __packed;
>  
> +struct icm_fr_event_xdomain_connected {
> +	struct icm_pkg_header hdr;
> +	u16 reserved;
> +	u16 link_info;
> +	uuid_t remote_uuid;
> +	uuid_t local_uuid;
> +	u32 local_route_hi;
> +	u32 local_route_lo;
> +	u32 remote_route_hi;
> +	u32 remote_route_lo;
> +} __packed;
> +
> +struct icm_fr_event_xdomain_disconnected {
> +	struct icm_pkg_header hdr;
> +	u16 reserved;
> +	u16 link_info;
> +	uuid_t remote_uuid;
> +} __packed;
> +
>  struct icm_fr_pkg_add_device_key {
>  	struct icm_pkg_header hdr;
>  	uuid_t ep_uuid;
> @@ -224,6 +246,28 @@ struct icm_fr_pkg_challenge_device_response {
>  	u32 response[8];
>  } __packed;
>  
> +struct icm_fr_pkg_approve_xdomain {
> +	struct icm_pkg_header hdr;
> +	u16 reserved;
> +	u16 link_info;
> +	uuid_t remote_uuid;
> +	u16 transmit_path;
> +	u16 transmit_ring;
> +	u16 receive_path;
> +	u16 receive_ring;
> +} __packed;
> +
> +struct icm_fr_pkg_approve_xdomain_response {
> +	struct icm_pkg_header hdr;
> +	u16 reserved;
> +	u16 link_info;
> +	uuid_t remote_uuid;
> +	u16 transmit_path;
> +	u16 transmit_ring;
> +	u16 receive_path;
> +	u16 receive_ring;
> +} __packed;
> +
>  /* Alpine Ridge only messages */
>  
>  struct icm_ar_pkg_get_route {
> @@ -240,4 +284,83 @@ struct icm_ar_pkg_get_route_response {
>  	u32 route_lo;
>  } __packed;
>  
> +/* XDomain messages */
> +
> +struct tb_xdomain_header {
> +	u32 route_hi;
> +	u32 route_lo;
> +	u32 length_sn;
> +} __packed;
> +
> +#define TB_XDOMAIN_LENGTH_MASK	GENMASK(5, 0)
> +#define TB_XDOMAIN_SN_MASK	GENMASK(28, 27)
> +#define TB_XDOMAIN_SN_SHIFT	27
> +
> +enum tb_xdp_type {
> +	UUID_REQUEST_OLD = 1,
> +	UUID_RESPONSE = 2,
> +	PROPERTIES_REQUEST,
> +	PROPERTIES_RESPONSE,
> +	PROPERTIES_CHANGED_REQUEST,
> +	PROPERTIES_CHANGED_RESPONSE,
> +	ERROR_RESPONSE,
> +	UUID_REQUEST = 12,
> +};
> +
> +struct tb_xdp_header {
> +	struct tb_xdomain_header xd_hdr;
> +	uuid_t uuid;
> +	u32 type;
> +} __packed;
> +
> +struct tb_xdp_properties {
> +	struct tb_xdp_header hdr;
> +	uuid_t src_uuid;
> +	uuid_t dst_uuid;
> +	u16 offset;
> +	u16 reserved;
> +} __packed;
> +
> +struct tb_xdp_properties_response {
> +	struct tb_xdp_header hdr;
> +	uuid_t src_uuid;
> +	uuid_t dst_uuid;
> +	u16 offset;
> +	u16 data_length;
> +	u32 generation;
> +	u32 data[0];
> +} __packed;
> +
> +/*
> + * Max length of data array single XDomain property response is
> allowed
> + * to carry.
> + */
> +#define TB_XDP_PROPERTIES_MAX_DATA_LENGTH	\
> +	(((256 - 4 - sizeof(struct tb_xdp_properties_response))) /
> 4)
> +
> +/* Maximum size of the total property block in dwords we allow */
> +#define TB_XDP_PROPERTIES_MAX_LENGTH		500
> +
> +struct tb_xdp_properties_changed {
> +	struct tb_xdp_header hdr;
> +	uuid_t src_uuid;
> +} __packed;
> +
> +struct tb_xdp_properties_changed_response {
> +	struct tb_xdp_header hdr;
> +} __packed;
> +
> +enum tb_xdp_error {
> +	ERROR_SUCCESS,
> +	ERROR_UNKNOWN_PACKET,
> +	ERROR_UNKNOWN_DOMAIN,
> +	ERROR_NOT_SUPPORTED,
> +	ERROR_NOT_READY,
> +};
> +
> +struct tb_xdp_error_response {
> +	struct tb_xdp_header hdr;
> +	u32 error;
> +} __packed;
> +
>  #endif
> diff --git a/drivers/thunderbolt/xdomain.c
> b/drivers/thunderbolt/xdomain.c
> new file mode 100644
> index 000000000000..1b929be8fdd6
> --- /dev/null
> +++ b/drivers/thunderbolt/xdomain.c
> @@ -0,0 +1,1576 @@
> +/*
> + * Thunderbolt XDomain discovery protocol support
> + *
> + * Copyright (C) 2017, Intel Corporation
> + * Authors: Michael Jamet <michael.jamet@intel.com>
> + *          Mika Westerberg <mika.westerberg@linux.intel.com>
> + *
> + * This program is free software; you can redistribute it and/or
> modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include <linux/device.h>
> +#include <linux/kmod.h>
> +#include <linux/module.h>
> +#include <linux/utsname.h>
> +#include <linux/uuid.h>
> +#include <linux/workqueue.h>
> +
> +#include "tb.h"
> +
> +#define XDOMAIN_DEFAULT_TIMEOUT			5000 /* ms */
> +#define XDOMAIN_PROPERTIES_RETRIES		60
> +#define XDOMAIN_PROPERTIES_CHANGED_RETRIES	10
> +
> +struct xdomain_request_work {
> +	struct work_struct work;
> +	struct tb_xdp_header *pkg;
> +	struct tb *tb;
> +};
> +
> +/* Serializes access to the properties and protocol handlers below
> */
> +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 */
> +static LIST_HEAD(protocol_handlers);
> +
> +/* UUID for XDomain discovery protocol */
> +static const uuid_t tb_xdp_uuid =
> +	UUID_INIT(0xb638d70e, 0x42ff, 0x40bb,
> +		  0x97, 0xc2, 0x90, 0xe2, 0xc0, 0xb2, 0xff, 0x07);
> +
> +static bool tb_xdomain_match(const struct tb_cfg_request *req,
> +			     const struct ctl_pkg *pkg)
> +{
> +	switch (pkg->frame.eof) {
> +	case TB_CFG_PKG_ERROR:
> +		return true;
> +
> +	case TB_CFG_PKG_XDOMAIN_RESP: {
> +		const struct tb_xdp_header *res_hdr = pkg->buffer;
> +		const struct tb_xdp_header *req_hdr = req->request;
> +		u8 req_seq, res_seq;
> +
> +		if (pkg->frame.size < req->response_size / 4)
> +			return false;
> +
> +		/* Make sure route matches */
> +		if ((res_hdr->xd_hdr.route_hi & ~BIT(31)) !=
> +		     req_hdr->xd_hdr.route_hi)
> +			return false;
> +		if ((res_hdr->xd_hdr.route_lo) != req_hdr-
> >xd_hdr.route_lo)
> +			return false;
> +
> +		/* Then check that the sequence number matches */
> +		res_seq = res_hdr->xd_hdr.length_sn &
> TB_XDOMAIN_SN_MASK;
> +		res_seq >>= TB_XDOMAIN_SN_SHIFT;
> +		req_seq = req_hdr->xd_hdr.length_sn &
> TB_XDOMAIN_SN_MASK;
> +		req_seq >>= TB_XDOMAIN_SN_SHIFT;
> +		if (res_seq != req_seq)
> +			return false;
> +
> +		/* Check that the XDomain protocol matches */
> +		if (!uuid_equal(&res_hdr->uuid, &req_hdr->uuid))
> +			return false;
> +
> +		return true;
> +	}
> +
> +	default:
> +		return false;
> +	}
> +}
> +
> +static bool tb_xdomain_copy(struct tb_cfg_request *req,
> +			    const struct ctl_pkg *pkg)
> +{
> +	memcpy(req->response, pkg->buffer, req->response_size);
> +	req->result.err = 0;
> +	return true;
> +}
> +
> +static void response_ready(void *data)
> +{
> +	tb_cfg_request_put(data);
> +}
> +
> +static int __tb_xdomain_response(struct tb_ctl *ctl, const void
> *response,
> +				 size_t size, enum tb_cfg_pkg_type
> type)
> +{
> +	struct tb_cfg_request *req;
> +
> +	req = tb_cfg_request_alloc();
> +	if (!req)
> +		return -ENOMEM;
> +
> +	req->match = tb_xdomain_match;
> +	req->copy = tb_xdomain_copy;
> +	req->request = response;
> +	req->request_size = size;
> +	req->request_type = type;
> +
> +	return tb_cfg_request(ctl, req, response_ready, req);
> +}
> +
> +/**
> + * tb_xdomain_response() - Send a XDomain response message
> + * @xd: XDomain to send the message
> + * @response: Response to send
> + * @size: Size of the response
> + * @type: PDF type of the response
> + *
> + * This can be used to send a XDomain response message to the other
> + * domain. No response for the message is expected.
> + *
> + * Return: %0 in case of success and negative errno in case of
> failure
> + */
> +int tb_xdomain_response(struct tb_xdomain *xd, const void *response,
> +			size_t size, enum tb_cfg_pkg_type type)
> +{
> +	return __tb_xdomain_response(xd->tb->ctl, response, size,
> type);
> +}
> +EXPORT_SYMBOL_GPL(tb_xdomain_response);
> +
> +static int __tb_xdomain_request(struct tb_ctl *ctl, const void
> *request,
> +	size_t request_size, enum tb_cfg_pkg_type request_type, void
> *response,
> +	size_t response_size, enum tb_cfg_pkg_type response_type,
> +	unsigned int timeout_msec)
> +{
> +	struct tb_cfg_request *req;
> +	struct tb_cfg_result res;
> +
> +	req = tb_cfg_request_alloc();
> +	if (!req)
> +		return -ENOMEM;
> +
> +	req->match = tb_xdomain_match;
> +	req->copy = tb_xdomain_copy;
> +	req->request = request;
> +	req->request_size = request_size;
> +	req->request_type = request_type;
> +	req->response = response;
> +	req->response_size = response_size;
> +	req->response_type = response_type;
> +
> +	res = tb_cfg_request_sync(ctl, req, timeout_msec);
> +
> +	tb_cfg_request_put(req);
> +
> +	return res.err == 1 ? -EIO : res.err;
> +}
> +
> +/**
> + * tb_xdomain_request() - Send a XDomain request
> + * @xd: XDomain to send the request
> + * @request: Request to send
> + * @request_size: Size of the request in bytes
> + * @request_type: PDF type of the request
> + * @response: Response is copied here
> + * @response_size: Expected size of the response in bytes
> + * @response_type: Expected PDF type of the response
> + * @timeout_msec: Timeout in milliseconds to wait for the response
> + *
> + * This function can be used to send XDomain control channel
> messages to
> + * the other domain. The function waits until the response is
> received
> + * or when timeout triggers. Whichever comes first.
> + *
> + * Return: %0 in case of success and negative errno in case of
> failure
> + */
> +int tb_xdomain_request(struct tb_xdomain *xd, const void *request,
> +	size_t request_size, enum tb_cfg_pkg_type request_type,
> +	void *response, size_t response_size,
> +	enum tb_cfg_pkg_type response_type, unsigned int
> timeout_msec)
> +{
> +	return __tb_xdomain_request(xd->tb->ctl, request,
> request_size,
> +				    request_type, response,
> response_size,
> +				    response_type, timeout_msec);
> +}
> +EXPORT_SYMBOL_GPL(tb_xdomain_request);
> +
> +static inline void tb_xdp_fill_header(struct tb_xdp_header *hdr, u64
> route,
> +	u8 sequence, enum tb_xdp_type type, size_t size)
> +{
> +	u32 length_sn;
> +
> +	length_sn = (size - sizeof(hdr->xd_hdr)) / 4;
> +	length_sn |= (sequence << TB_XDOMAIN_SN_SHIFT) &
> TB_XDOMAIN_SN_MASK;
> +
> +	hdr->xd_hdr.route_hi = upper_32_bits(route);
> +	hdr->xd_hdr.route_lo = lower_32_bits(route);
> +	hdr->xd_hdr.length_sn = length_sn;
> +	hdr->type = type;
> +	memcpy(&hdr->uuid, &tb_xdp_uuid, sizeof(tb_xdp_uuid));
> +}
> +
> +static int tb_xdp_handle_error(const struct tb_xdp_header *hdr)
> +{
> +	const struct tb_xdp_error_response *error;
> +
> +	if (hdr->type != ERROR_RESPONSE)
> +		return 0;
> +
> +	error = (const struct tb_xdp_error_response *)hdr;
> +
> +	switch (error->error) {
> +	case ERROR_UNKNOWN_PACKET:
> +	case ERROR_UNKNOWN_DOMAIN:
> +		return -EIO;
> +	case ERROR_NOT_SUPPORTED:
> +		return -ENOTSUPP;
> +	case ERROR_NOT_READY:
> +		return -EAGAIN;
> +	default:
> +		break;
> +	}
> +
> +	return 0;
> +}
> +
> +static int tb_xdp_error_response(struct tb_ctl *ctl, u64 route, u8
> sequence,
> +				 enum tb_xdp_error error)
> +{
> +	struct tb_xdp_error_response res;
> +
> +	memset(&res, 0, sizeof(res));
> +	tb_xdp_fill_header(&res.hdr, route, sequence,
> ERROR_RESPONSE,
> +			   sizeof(res));
> +	res.error = error;
> +
> +	return __tb_xdomain_response(ctl, &res, sizeof(res),
> +				     TB_CFG_PKG_XDOMAIN_RESP);
> +}
> +
> +static int tb_xdp_properties_request(struct tb_ctl *ctl, u64 route,
> +	const uuid_t *src_uuid, const uuid_t *dst_uuid, int retry,
> +	u32 **block, u32 *generation)
> +{
> +	struct tb_xdp_properties_response *res;
> +	struct tb_xdp_properties req;
> +	u16 data_len, len;
> +	size_t total_size;
> +	u32 *data = NULL;
> +	int ret;
> +
> +	total_size = sizeof(*res) +
> TB_XDP_PROPERTIES_MAX_DATA_LENGTH * 4;
> +	res = kzalloc(total_size, GFP_KERNEL);
> +	if (!res)
> +		return -ENOMEM;
> +
> +	memset(&req, 0, sizeof(req));
> +	tb_xdp_fill_header(&req.hdr, route, retry % 4,
> PROPERTIES_REQUEST,
> +			   sizeof(req));
> +	memcpy(&req.src_uuid, src_uuid, sizeof(*src_uuid));
> +	memcpy(&req.dst_uuid, dst_uuid, sizeof(*dst_uuid));
> +
> +	len = 0;
> +	data_len = 0;
> +
> +	do {
> +		ret = __tb_xdomain_request(ctl, &req, sizeof(req),
> +					   TB_CFG_PKG_XDOMAIN_REQ,
> res,
> +					   total_size,
> TB_CFG_PKG_XDOMAIN_RESP,
> +					   XDOMAIN_DEFAULT_TIMEOUT);
> +		if (ret)
> +			goto err;
> +
> +		ret = tb_xdp_handle_error(&res->hdr);
> +		if (ret)
> +			goto err;
> +
> +		/*
> +		 * Package length includes the whole payload without
> the
> +		 * XDomain header. Validate first that the package
> is at
> +		 * least size of the response structure.
> +		 */
> +		len = res->hdr.xd_hdr.length_sn &
> TB_XDOMAIN_LENGTH_MASK;
> +		if (len < sizeof(*res) / 4) {
> +			ret = -EINVAL;
> +			goto err;
> +		}
> +
> +		len += sizeof(res->hdr.xd_hdr) / 4;
> +		len -= sizeof(*res) / 4;
> +
> +		if (res->offset != req.offset) {
> +			ret = -EINVAL;
> +			goto err;
> +		}
> +
> +		/*
> +		 * First time allocate block that has enough space
> for
> +		 * the whole properties block.
> +		 */
> +		if (!data) {
> +			data_len = res->data_length;
> +			if (data_len > TB_XDP_PROPERTIES_MAX_LENGTH)
> {
> +				ret = -E2BIG;
> +				goto err;
> +			}
> +
> +			data = kcalloc(data_len, sizeof(u32),
> GFP_KERNEL);
> +			if (!data) {
> +				ret = -ENOMEM;
> +				goto err;
> +			}
> +		}
> +
> +		memcpy(data + req.offset, res->data, len * 4);
> +		req.offset += len;
> +	} while (!data_len || req.offset < data_len);
> +
> +	*block = data;
> +	*generation = res->generation;
> +
> +	kfree(res);
> +
> +	return data_len;
> +
> +err:
> +	kfree(data);
> +	kfree(res);
> +
> +	return ret;
> +}
> +
> +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_xdp_properties_response *res;
> +	size_t total_size;
> +	u16 len;
> +	int ret;
> +
> +	/*
> +	 * Currently we expect all requests to be directed to us.
> The
> +	 * 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,
> +				      ERROR_UNKNOWN_DOMAIN);
> +		return 0;
> +	}
> +
> +	mutex_lock(&xdomain_lock);
> +
> +	if (req->offset >= xdomain_property_block_len) {
> +		mutex_unlock(&xdomain_lock);
> +		return -EINVAL;
> +	}
> +
> +	len = xdomain_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);
> +		return -ENOMEM;
> +	}
> +
> +	tb_xdp_fill_header(&res->hdr, route, sequence,
> PROPERTIES_RESPONSE,
> +			   total_size);
> +	res->generation = xdomain_property_block_gen;
> +	res->data_length = xdomain_property_block_len;
> +	res->offset = req->offset;
> +	uuid_copy(&res->src_uuid, src_uuid);
> +	uuid_copy(&res->dst_uuid, &req->src_uuid);
> +	memcpy(res->data, &xdomain_property_block[req->offset], len
> * 4);
> +
> +	mutex_unlock(&xdomain_lock);
> +
> +	ret = __tb_xdomain_response(ctl, res, total_size,
> +				    TB_CFG_PKG_XDOMAIN_RESP);
> +
> +	kfree(res);
> +	return ret;
> +}
> +
> +static int tb_xdp_properties_changed_request(struct tb_ctl *ctl, u64
> route,
> +					     int retry, const uuid_t
> *uuid)
> +{
> +	struct tb_xdp_properties_changed_response res;
> +	struct tb_xdp_properties_changed req;
> +	int ret;
> +
> +	memset(&req, 0, sizeof(req));
> +	tb_xdp_fill_header(&req.hdr, route, retry % 4,
> +			   PROPERTIES_CHANGED_REQUEST, sizeof(req));
> +	uuid_copy(&req.src_uuid, uuid);
> +
> +	memset(&res, 0, sizeof(res));
> +	ret = __tb_xdomain_request(ctl, &req, sizeof(req),
> +				   TB_CFG_PKG_XDOMAIN_REQ, &res,
> sizeof(res),
> +				   TB_CFG_PKG_XDOMAIN_RESP,
> +				   XDOMAIN_DEFAULT_TIMEOUT);
> +	if (ret)
> +		return ret;
> +
> +	return tb_xdp_handle_error(&res.hdr);
> +}
> +
> +static int
> +tb_xdp_properties_changed_response(struct tb_ctl *ctl, u64 route, u8
> sequence)
> +{
> +	struct tb_xdp_properties_changed_response res;
> +
> +	memset(&res, 0, sizeof(res));
> +	tb_xdp_fill_header(&res.hdr, route, sequence,
> +			   PROPERTIES_CHANGED_RESPONSE,
> sizeof(res));
> +	return __tb_xdomain_response(ctl, &res, sizeof(res),
> +				     TB_CFG_PKG_XDOMAIN_RESP);
> +}
> +
> +/**
> + * tb_register_protocol_handler() - Register protocol handler
> + * @handler: Handler to register
> + *
> + * This allows XDomain service drivers to hook into incoming XDomain
> + * messages. After this function is called the service driver needs
> to
> + * be able to handle calls to callback whenever a package with the
> + * registered protocol is received.
> + */
> +int tb_register_protocol_handler(struct tb_protocol_handler
> *handler)
> +{
> +	if (!handler->uuid || !handler->callback)
> +		return -EINVAL;
> +	if (uuid_equal(handler->uuid, &tb_xdp_uuid))
> +		return -EINVAL;
> +
> +	mutex_lock(&xdomain_lock);
> +	list_add_tail(&handler->list, &protocol_handlers);
> +	mutex_unlock(&xdomain_lock);
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL_GPL(tb_register_protocol_handler);
> +
> +/**
> + * tb_unregister_protocol_handler() - Unregister protocol handler
> + * @handler: Handler to unregister
> + *
> + * Removes the previously registered protocol handler.
> + */
> +void tb_unregister_protocol_handler(struct tb_protocol_handler
> *handler)
> +{
> +	mutex_lock(&xdomain_lock);
> +	list_del_init(&handler->list);
> +	mutex_unlock(&xdomain_lock);
> +}
> +EXPORT_SYMBOL_GPL(tb_unregister_protocol_handler);
> +
> +static void tb_xdp_handle_request(struct work_struct *work)
> +{
> +	struct xdomain_request_work *xw = container_of(work,
> typeof(*xw), work);
> +	const struct tb_xdp_header *pkg = xw->pkg;
> +	const struct tb_xdomain_header *xhdr = &pkg->xd_hdr;
> +	struct tb *tb = xw->tb;
> +	struct tb_ctl *ctl = tb->ctl;
> +	const uuid_t *uuid;
> +	int ret = 0;
> +	u8 sequence;
> +	u64 route;
> +
> +	route = ((u64)xhdr->route_hi << 32 | xhdr->route_lo) &
> ~BIT_ULL(63);
> +	sequence = xhdr->length_sn & TB_XDOMAIN_SN_MASK;
> +	sequence >>= TB_XDOMAIN_SN_SHIFT;
> +
> +	mutex_lock(&tb->lock);
> +	if (tb->root_switch)
> +		uuid = tb->root_switch->uuid;
> +	else
> +		uuid = NULL;
> +	mutex_unlock(&tb->lock);
> +
> +	if (!uuid) {
> +		tb_xdp_error_response(ctl, route, sequence,
> ERROR_NOT_READY);
> +		goto out;
> +	}
> +
> +	switch (pkg->type) {
> +	case PROPERTIES_REQUEST:
> +		ret = tb_xdp_properties_response(tb, ctl, route,
> sequence, uuid,
> +			(const struct tb_xdp_properties *)pkg);
> +		break;
> +
> +	case PROPERTIES_CHANGED_REQUEST: {
> +		const struct tb_xdp_properties_changed *xchg =
> +			(const struct tb_xdp_properties_changed
> *)pkg;
> +		struct tb_xdomain *xd;
> +
> +		ret = tb_xdp_properties_changed_response(ctl, route,
> sequence);
> +
> +		/*
> +		 * Since the properties have been changed, let's
> update
> +		 * the xdomain related to this connection as well in
> +		 * case there is a change in services it offers.
> +		 */
> +		xd = tb_xdomain_find_by_uuid_locked(tb, &xchg-
> >src_uuid);
> +		if (xd) {
> +			queue_delayed_work(tb->wq, &xd-
> >get_properties_work,
> +					   msecs_to_jiffies(50));
> +			tb_xdomain_put(xd);
> +		}
> +
> +		break;
> +	}
> +
> +	default:
> +		break;
> +	}
> +
> +	if (ret) {
> +		tb_warn(tb, "failed to send XDomain response for
> %#x\n",
> +			pkg->type);
> +	}
> +
> +out:
> +	kfree(xw->pkg);
> +	kfree(xw);
> +}
> +
> +static void
> +tb_xdp_schedule_request(struct tb *tb, const struct tb_xdp_header
> *hdr,
> +			size_t size)
> +{
> +	struct xdomain_request_work *xw;
> +
> +	xw = kmalloc(sizeof(*xw), GFP_KERNEL);
> +	if (!xw)
> +		return;
> +
> +	INIT_WORK(&xw->work, tb_xdp_handle_request);
> +	xw->pkg = kmemdup(hdr, size, GFP_KERNEL);
> +	xw->tb = tb;
> +
> +	queue_work(tb->wq, &xw->work);
> +}
> +
> +/**
> + * tb_register_service_driver() - Register XDomain service driver
> + * @drv: Driver to register
> + *
> + * Registers new service driver from @drv to the bus.
> + */
> +int tb_register_service_driver(struct tb_service_driver *drv)
> +{
> +	drv->driver.bus = &tb_bus_type;
> +	return driver_register(&drv->driver);
> +}
> +EXPORT_SYMBOL_GPL(tb_register_service_driver);
> +
> +/**
> + * tb_unregister_service_driver() - Unregister XDomain service
> driver
> + * @xdrv: Driver to unregister
> + *
> + * Unregisters XDomain service driver from the bus.
> + */
> +void tb_unregister_service_driver(struct tb_service_driver *drv)
> +{
> +	driver_unregister(&drv->driver);
> +}
> +EXPORT_SYMBOL_GPL(tb_unregister_service_driver);
> +
> +static ssize_t key_show(struct device *dev, struct device_attribute
> *attr,
> +			char *buf)
> +{
> +	struct tb_service *svc = container_of(dev, struct
> tb_service, dev);
> +
> +	/*
> +	 * It should be null terminated but anything else is pretty
> much
> +	 * allowed.
> +	 */
> +	return sprintf(buf, "%*pEp\n", (int)strlen(svc->key), svc-
> >key);
> +}
> +static DEVICE_ATTR_RO(key);
> +
> +static int get_modalias(struct tb_service *svc, char *buf, size_t
> size)
> +{
> +	return snprintf(buf, size, "tbsvc:k%sp%08Xv%08Xr%08X", svc-
> >key,
> +			svc->prtcid, svc->prtcvers, svc->prtcrevs);
> +}
> +
> +static ssize_t modalias_show(struct device *dev, struct
> device_attribute *attr,
> +			     char *buf)
> +{
> +	struct tb_service *svc = container_of(dev, struct
> tb_service, dev);
> +
> +	/* Full buffer size except new line and null termination */
> +	get_modalias(svc, buf, PAGE_SIZE - 2);
> +	return sprintf(buf, "%s\n", buf);
> +}
> +static DEVICE_ATTR_RO(modalias);
> +
> +static ssize_t prtcid_show(struct device *dev, struct
> device_attribute *attr,
> +			   char *buf)
> +{
> +	struct tb_service *svc = container_of(dev, struct
> tb_service, dev);
> +
> +	return sprintf(buf, "%u\n", svc->prtcid);
> +}
> +static DEVICE_ATTR_RO(prtcid);
> +
> +static ssize_t prtcvers_show(struct device *dev, struct
> device_attribute *attr,
> +			     char *buf)
> +{
> +	struct tb_service *svc = container_of(dev, struct
> tb_service, dev);
> +
> +	return sprintf(buf, "%u\n", svc->prtcvers);
> +}
> +static DEVICE_ATTR_RO(prtcvers);
> +
> +static ssize_t prtcrevs_show(struct device *dev, struct
> device_attribute *attr,
> +			     char *buf)
> +{
> +	struct tb_service *svc = container_of(dev, struct
> tb_service, dev);
> +
> +	return sprintf(buf, "%u\n", svc->prtcrevs);
> +}
> +static DEVICE_ATTR_RO(prtcrevs);
> +
> +static ssize_t prtcstns_show(struct device *dev, struct
> device_attribute *attr,
> +			     char *buf)
> +{
> +	struct tb_service *svc = container_of(dev, struct
> tb_service, dev);
> +
> +	return sprintf(buf, "0x%08x\n", svc->prtcstns);
> +}
> +static DEVICE_ATTR_RO(prtcstns);
> +
> +static struct attribute *tb_service_attrs[] = {
> +	&dev_attr_key.attr,
> +	&dev_attr_modalias.attr,
> +	&dev_attr_prtcid.attr,
> +	&dev_attr_prtcvers.attr,
> +	&dev_attr_prtcrevs.attr,
> +	&dev_attr_prtcstns.attr,
> +	NULL,
> +};
> +
> +static struct attribute_group tb_service_attr_group = {
> +	.attrs = tb_service_attrs,
> +};
> +
> +static const struct attribute_group *tb_service_attr_groups[] = {
> +	&tb_service_attr_group,
> +	NULL,
> +};
> +
> +static int tb_service_uevent(struct device *dev, struct
> kobj_uevent_env *env)
> +{
> +	struct tb_service *svc = container_of(dev, struct
> tb_service, dev);
> +	char modalias[64];
> +
> +	get_modalias(svc, modalias, sizeof(modalias));
> +	return add_uevent_var(env, "MODALIAS=%s", modalias);
> +}
> +
> +static void tb_service_release(struct device *dev)
> +{
> +	struct tb_service *svc = container_of(dev, struct
> tb_service, dev);
> +	struct tb_xdomain *xd = tb_service_parent(svc);
> +
> +	ida_simple_remove(&xd->service_ids, svc->id);
> +	kfree(svc->key);
> +	kfree(svc);
> +}
> +
> +struct device_type tb_service_type = {
> +	.name = "thunderbolt_service",
> +	.groups = tb_service_attr_groups,
> +	.uevent = tb_service_uevent,
> +	.release = tb_service_release,
> +};
> +EXPORT_SYMBOL_GPL(tb_service_type);
> +
> +static int remove_missing_service(struct device *dev, void *data)
> +{
> +	struct tb_xdomain *xd = data;
> +	struct tb_service *svc;
> +
> +	svc = tb_to_service(dev);
> +	if (!svc)
> +		return 0;
> +
> +	if (!tb_property_find(xd->properties, svc->key,
> +			      TB_PROPERTY_TYPE_DIRECTORY))
> +		device_unregister(dev);
> +
> +	return 0;
> +}
> +
> +static int find_service(struct device *dev, void *data)
> +{
> +	const struct tb_property *p = data;
> +	struct tb_service *svc;
> +
> +	svc = tb_to_service(dev);
> +	if (!svc)
> +		return 0;
> +
> +	return !strcmp(svc->key, p->key);
> +}
> +
> +static int populate_service(struct tb_service *svc,
> +			    struct tb_property *property)
> +{
> +	struct tb_property_dir *dir = property->value.dir;
> +	struct tb_property *p;
> +
> +	/* Fill in standard properties */
> +	p = tb_property_find(dir, "prtcid", TB_PROPERTY_TYPE_VALUE);
> +	if (p)
> +		svc->prtcid = p->value.immediate;
> +	p = tb_property_find(dir, "prtcvers",
> TB_PROPERTY_TYPE_VALUE);
> +	if (p)
> +		svc->prtcvers = p->value.immediate;
> +	p = tb_property_find(dir, "prtcrevs",
> TB_PROPERTY_TYPE_VALUE);
> +	if (p)
> +		svc->prtcrevs = p->value.immediate;
> +	p = tb_property_find(dir, "prtcstns",
> TB_PROPERTY_TYPE_VALUE);
> +	if (p)
> +		svc->prtcstns = p->value.immediate;
> +
> +	svc->key = kstrdup(property->key, GFP_KERNEL);
> +	if (!svc->key)
> +		return -ENOMEM;
> +
> +	return 0;
> +}
> +
> +static void enumerate_services(struct tb_xdomain *xd)
> +{
> +	struct tb_service *svc;
> +	struct tb_property *p;
> +	struct device *dev;
> +
> +	/*
> +	 * First remove all services that are not available anymore
> in
> +	 * the updated property block.
> +	 */
> +	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) {
> +		if (p->type != TB_PROPERTY_TYPE_DIRECTORY)
> +			continue;
> +
> +		/* If the service exists already we are fine */
> +		dev = device_find_child(&xd->dev, p, find_service);
> +		if (dev) {
> +			put_device(dev);
> +			continue;
> +		}
> +
> +		svc = kzalloc(sizeof(*svc), GFP_KERNEL);
> +		if (!svc)
> +			break;
> +
> +		if (populate_service(svc, p)) {
> +			kfree(svc);
> +			break;
> +		}
> +
> +		svc->id = ida_simple_get(&xd->service_ids, 0, 0,
> GFP_KERNEL);
> +		svc->dev.bus = &tb_bus_type;
> +		svc->dev.type = &tb_service_type;
> +		svc->dev.parent = &xd->dev;
> +		dev_set_name(&svc->dev, "%s.%d", dev_name(&xd->dev), 
> svc->id);
> +
> +		if (device_register(&svc->dev)) {
> +			put_device(&svc->dev);
> +			break;
> +		}
> +	}
> +}
> +
> +static int populate_properties(struct tb_xdomain *xd,
> +			       struct tb_property_dir *dir)
> +{
> +	const struct tb_property *p;
> +
> +	/* Required properties */
> +	p = tb_property_find(dir, "deviceid",
> TB_PROPERTY_TYPE_VALUE);
> +	if (!p)
> +		return -EINVAL;
> +	xd->device = p->value.immediate;
> +
> +	p = tb_property_find(dir, "vendorid",
> TB_PROPERTY_TYPE_VALUE);
> +	if (!p)
> +		return -EINVAL;
> +	xd->vendor = p->value.immediate;
> +
> +	kfree(xd->device_name);
> +	xd->device_name = NULL;
> +	kfree(xd->vendor_name);
> +	xd->vendor_name = NULL;
> +
> +	/* Optional properties */
> +	p = tb_property_find(dir, "deviceid",
> TB_PROPERTY_TYPE_TEXT);
> +	if (p)
> +		xd->device_name = kstrdup(p->value.text,
> GFP_KERNEL);
> +	p = tb_property_find(dir, "vendorid",
> TB_PROPERTY_TYPE_TEXT);
> +	if (p)
> +		xd->vendor_name = kstrdup(p->value.text,
> GFP_KERNEL);
> +
> +	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 void tb_xdomain_get_properties(struct work_struct *work)
> +{
> +	struct tb_xdomain *xd = container_of(work, typeof(*xd),
> +					     get_properties_work.wor
> k);
> +	struct tb_property_dir *dir;
> +	struct tb *tb = xd->tb;
> +	bool update = false;
> +	u32 *block = NULL;
> +	u32 gen = 0;
> +	int ret;
> +
> +	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) {
> +			queue_delayed_work(xd->tb->wq, &xd-
> >get_properties_work,
> +					   msecs_to_jiffies(1000));
> +		} else {
> +			/* Give up now */
> +			dev_err(&xd->dev,
> +				"failed read XDomain properties from
> %pUb\n",
> +				xd->remote_uuid);
> +		}
> +		return;
> +	}
> +
> +	xd->properties_retries = XDOMAIN_PROPERTIES_RETRIES;
> +
> +	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);
> +		goto err_free_block;
> +	}
> +
> +	dir = tb_property_parse_dir(block, ret);
> +	if (!dir) {
> +		dev_err(&xd->dev, "failed to parse XDomain
> properties\n");
> +		goto err_free_block;
> +	}
> +
> +	ret = populate_properties(xd, dir);
> +	if (ret) {
> +		dev_err(&xd->dev, "missing XDomain properties in
> response\n");
> +		goto err_free_dir;
> +	}
> +
> +	/* Release the existing one */
> +	if (xd->properties) {
> +		tb_property_free_dir(xd->properties);
> +		update = true;
> +	}
> +
> +	xd->properties = dir;
> +	xd->property_block_gen = gen;
> +
> +	tb_xdomain_restore_paths(xd);
> +
> +	mutex_unlock(&xd->lock);
> +
> +	kfree(block);
> +
> +	/*
> +	 * Now the device should be ready enough so we can add it to
> the
> +	 * bus and let userspace know about it. If the device is
> already
> +	 * registered, we notify the userspace that it has changed.
> +	 */
> +	if (!update) {
> +		if (device_add(&xd->dev)) {
> +			dev_err(&xd->dev, "failed to add XDomain
> device\n");
> +			return;
> +		}
> +	} else {
> +		kobject_uevent(&xd->dev.kobj, KOBJ_CHANGE);
> +	}
> +
> +	enumerate_services(xd);
> +	return;
> +
> +err_free_dir:
> +	tb_property_free_dir(dir);
> +err_free_block:
> +	kfree(block);
> +	mutex_unlock(&xd->lock);
> +}
> +
> +static void tb_xdomain_properties_changed(struct work_struct *work)
> +{
> +	struct tb_xdomain *xd = container_of(work, typeof(*xd),
> +					     properties_changed_work
> .work);
> +	int ret;
> +
> +	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)
> +			queue_delayed_work(xd->tb->wq,
> +					   &xd-
> >properties_changed_work,
> +					   msecs_to_jiffies(1000));
> +		return;
> +	}
> +
> +	xd->properties_changed_retries =
> XDOMAIN_PROPERTIES_CHANGED_RETRIES;
> +}
> +
> +static ssize_t device_show(struct device *dev, struct
> device_attribute *attr,
> +			   char *buf)
> +{
> +	struct tb_xdomain *xd = container_of(dev, struct tb_xdomain,
> dev);
> +
> +	return sprintf(buf, "%#x\n", xd->device);
> +}
> +static DEVICE_ATTR_RO(device);
> +
> +static ssize_t
> +device_name_show(struct device *dev, struct device_attribute *attr,
> char *buf)
> +{
> +	struct tb_xdomain *xd = container_of(dev, struct tb_xdomain,
> dev);
> +	int ret;
> +
> +	if (mutex_lock_interruptible(&xd->lock))
> +		return -ERESTARTSYS;
> +	ret = sprintf(buf, "%s\n", xd->device_name ? xd->device_name 
> : "");
> +	mutex_unlock(&xd->lock);
> +
> +	return ret;
> +}
> +static DEVICE_ATTR_RO(device_name);
> +
> +static ssize_t vendor_show(struct device *dev, struct
> device_attribute *attr,
> +			   char *buf)
> +{
> +	struct tb_xdomain *xd = container_of(dev, struct tb_xdomain,
> dev);
> +
> +	return sprintf(buf, "%#x\n", xd->vendor);
> +}
> +static DEVICE_ATTR_RO(vendor);
> +
> +static ssize_t
> +vendor_name_show(struct device *dev, struct device_attribute *attr,
> char *buf)
> +{
> +	struct tb_xdomain *xd = container_of(dev, struct tb_xdomain,
> dev);
> +	int ret;
> +
> +	if (mutex_lock_interruptible(&xd->lock))
> +		return -ERESTARTSYS;
> +	ret = sprintf(buf, "%s\n", xd->vendor_name ? xd->vendor_name 
> : "");
> +	mutex_unlock(&xd->lock);
> +
> +	return ret;
> +}
> +static DEVICE_ATTR_RO(vendor_name);
> +
> +static ssize_t unique_id_show(struct device *dev, struct
> device_attribute *attr,
> +			      char *buf)
> +{
> +	struct tb_xdomain *xd = container_of(dev, struct tb_xdomain,
> dev);
> +
> +	return sprintf(buf, "%pUb\n", xd->remote_uuid);
> +}
> +static DEVICE_ATTR_RO(unique_id);
> +
> +static struct attribute *xdomain_attrs[] = {
> +	&dev_attr_device.attr,
> +	&dev_attr_device_name.attr,
> +	&dev_attr_unique_id.attr,
> +	&dev_attr_vendor.attr,
> +	&dev_attr_vendor_name.attr,
> +	NULL,
> +};
> +
> +static struct attribute_group xdomain_attr_group = {
> +	.attrs = xdomain_attrs,
> +};
> +
> +static const struct attribute_group *xdomain_attr_groups[] = {
> +	&xdomain_attr_group,
> +	NULL,
> +};
> +
> +static void tb_xdomain_release(struct device *dev)
> +{
> +	struct tb_xdomain *xd = container_of(dev, struct tb_xdomain,
> dev);
> +
> +	put_device(xd->dev.parent);
> +
> +	tb_property_free_dir(xd->properties);
> +	ida_destroy(&xd->service_ids);
> +
> +	kfree(xd->local_uuid);
> +	kfree(xd->remote_uuid);
> +	kfree(xd->device_name);
> +	kfree(xd->vendor_name);
> +	kfree(xd);
> +}
> +
> +static void start_handshake(struct tb_xdomain *xd)
> +{
> +	xd->properties_retries = XDOMAIN_PROPERTIES_RETRIES;
> +	xd->properties_changed_retries =
> XDOMAIN_PROPERTIES_CHANGED_RETRIES;
> +
> +	/* Start exchanging properties with the other host */
> +	queue_delayed_work(xd->tb->wq, &xd->properties_changed_work,
> +			   msecs_to_jiffies(100));
> +	queue_delayed_work(xd->tb->wq, &xd->get_properties_work,
> +			   msecs_to_jiffies(1000));
> +}
> +
> +static void stop_handshake(struct tb_xdomain *xd)
> +{
> +	xd->properties_retries = 0;
> +	xd->properties_changed_retries = 0;
> +
> +	cancel_delayed_work_sync(&xd->get_properties_work);
> +	cancel_delayed_work_sync(&xd->properties_changed_work);
> +}
> +
> +static int __maybe_unused tb_xdomain_suspend(struct device *dev)
> +{
> +	stop_handshake(tb_to_xdomain(dev));
> +	return 0;
> +}
> +
> +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);
> +
> +	return 0;
> +}
> +
> +static const struct dev_pm_ops tb_xdomain_pm_ops = {
> +	SET_SYSTEM_SLEEP_PM_OPS(tb_xdomain_suspend,
> tb_xdomain_resume)
> +};
> +
> +struct device_type tb_xdomain_type = {
> +	.name = "thunderbolt_xdomain",
> +	.release = tb_xdomain_release,
> +	.pm = &tb_xdomain_pm_ops,
> +};
> +EXPORT_SYMBOL_GPL(tb_xdomain_type);
> +
> +/**
> + * tb_xdomain_alloc() - Allocate new XDomain object
> + * @tb: Domain where the XDomain belongs
> + * @parent: Parent device (the switch through the connection to the
> + *	    other domain is reached).
> + * @route: Route string used to reach the other domain
> + * @local_uuid: Our local domain UUID
> + * @remote_uuid: UUID of the other domain
> + *
> + * Allocates new XDomain structure and returns pointer to that. The
> + * object must be released by calling tb_xdomain_put().
> + */
> +struct tb_xdomain *tb_xdomain_alloc(struct tb *tb, struct device
> *parent,
> +				    u64 route, const uuid_t
> *local_uuid,
> +				    const uuid_t *remote_uuid)
> +{
> +	struct tb_xdomain *xd;
> +
> +	xd = kzalloc(sizeof(*xd), GFP_KERNEL);
> +	if (!xd)
> +		return NULL;
> +
> +	xd->tb = tb;
> +	xd->route = route;
> +	ida_init(&xd->service_ids);
> +	mutex_init(&xd->lock);
> +	INIT_DELAYED_WORK(&xd->get_properties_work,
> tb_xdomain_get_properties);
> +	INIT_DELAYED_WORK(&xd->properties_changed_work,
> +			  tb_xdomain_properties_changed);
> +
> +	xd->local_uuid = kmemdup(local_uuid, sizeof(uuid_t),
> GFP_KERNEL);
> +	if (!xd->local_uuid)
> +		goto err_free;
> +
> +	xd->remote_uuid = kmemdup(remote_uuid, sizeof(uuid_t),
> GFP_KERNEL);
> +	if (!xd->remote_uuid)
> +		goto err_free_local_uuid;
> +
> +	device_initialize(&xd->dev);
> +	xd->dev.parent = get_device(parent);
> +	xd->dev.bus = &tb_bus_type;
> +	xd->dev.type = &tb_xdomain_type;
> +	xd->dev.groups = xdomain_attr_groups;
> +	dev_set_name(&xd->dev, "%u-%llx", tb->index, route);
> +
> +	return xd;
> +
> +err_free_local_uuid:
> +	kfree(xd->local_uuid);
> +err_free:
> +	kfree(xd);
> +
> +	return NULL;
> +}
> +
> +/**
> + * tb_xdomain_add() - Add XDomain to the bus
> + * @xd: XDomain to add
> + *
> + * This function starts XDomain discovery protocol handshake and
> + * eventually adds the XDomain to the bus. After calling this
> function
> + * the caller needs to call tb_xdomain_remove() in order to remove
> and
> + * release the object regardless whether the handshake succeeded or
> not.
> + */
> +void tb_xdomain_add(struct tb_xdomain *xd)
> +{
> +	/* Start exchanging properties with the other host */
> +	start_handshake(xd);
> +}
> +
> +static int unregister_service(struct device *dev, void *data)
> +{
> +	device_unregister(dev);
> +	return 0;
> +}
> +
> +/**
> + * tb_xdomain_remove() - Remove XDomain from the bus
> + * @xd: XDomain to remove
> + *
> + * This will stop all ongoing configuration work and remove the
> XDomain
> + * along with any services from the bus. When the last reference to
> @xd
> + * is released the object will be released as well.
> + */
> +void tb_xdomain_remove(struct tb_xdomain *xd)
> +{
> +	stop_handshake(xd);
> +
> +	device_for_each_child_reverse(&xd->dev, xd,
> unregister_service);
> +
> +	if (!device_is_registered(&xd->dev))
> +		put_device(&xd->dev);
> +	else
> +		device_unregister(&xd->dev);
> +}
> +
> +/**
> + * 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
> + *
> + * The function enables DMA paths accordingly so that after
> successful
> + * return the caller can send and receive packets using high-speed
> DMA
> + * path.
> + *
> + * Return: %0 in case of success and negative errno in case of error
> + */
> +int tb_xdomain_enable_paths(struct tb_xdomain *xd, u16
> transmit_path,
> +			    u16 transmit_ring, u16 receive_path,
> +			    u16 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;
> +}
> +EXPORT_SYMBOL_GPL(tb_xdomain_enable_paths);
> +
> +/**
> + * tb_xdomain_disable_paths() - Disable DMA paths for XDomain
> connection
> + * @xd: XDomain connection
> + *
> + * This does the opposite of tb_xdomain_enable_paths(). After call
> to
> + * this the caller is not expected to use the rings anymore.
> + *
> + * Return: %0 in case of success and negative errno in case of error
> + */
> +int tb_xdomain_disable_paths(struct tb_xdomain *xd)
> +{
> +	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;
> +}
> +EXPORT_SYMBOL_GPL(tb_xdomain_disable_paths);
> +
> +struct tb_xdomain_lookup {
> +	const uuid_t *uuid;
> +	u8 link;
> +	u8 depth;
> +};
> +
> +static struct tb_xdomain *switch_find_xdomain(struct tb_switch *sw,
> +	const struct tb_xdomain_lookup *lookup)
> +{
> +	int i;
> +
> +	for (i = 1; i <= sw->config.max_port_number; i++) {
> +		struct tb_port *port = &sw->ports[i];
> +		struct tb_xdomain *xd;
> +
> +		if (tb_is_upstream_port(port))
> +			continue;
> +
> +		if (port->xdomain) {
> +			xd = port->xdomain;
> +
> +			if (lookup->uuid) {
> +				if (uuid_equal(xd->remote_uuid,
> lookup->uuid))
> +					return xd;
> +			} else if (lookup->link == xd->link &&
> +				   lookup->depth == xd->depth) {
> +				return xd;
> +			}
> +		} else if (port->remote) {
> +			xd = switch_find_xdomain(port->remote->sw,
> lookup);
> +			if (xd)
> +				return xd;
> +		}
> +	}
> +
> +	return NULL;
> +}
> +
> +/**
> + * tb_xdomain_find_by_uuid() - Find an XDomain by UUID
> + * @tb: Domain where the XDomain belongs to
> + * @uuid: UUID to look for
> + *
> + * Finds XDomain by walking through the Thunderbolt topology below
> @tb.
> + * The returned XDomain will have its reference count increased so
> the
> + * caller needs to call tb_xdomain_put() when it is done with the
> + * object.
> + *
> + * This will find all XDomains including the ones that are not yet
> added
> + * to the bus (handshake is still in progress).
> + *
> + * The caller needs to hold @tb->lock.
> + */
> +struct tb_xdomain *tb_xdomain_find_by_uuid(struct tb *tb, const
> uuid_t *uuid)
> +{
> +	struct tb_xdomain_lookup lookup;
> +	struct tb_xdomain *xd;
> +
> +	memset(&lookup, 0, sizeof(lookup));
> +	lookup.uuid = uuid;
> +
> +	xd = switch_find_xdomain(tb->root_switch, &lookup);
> +	if (xd) {
> +		get_device(&xd->dev);
> +		return xd;
> +	}
> +
> +	return NULL;
> +}
> +EXPORT_SYMBOL_GPL(tb_xdomain_find_by_uuid);
> +
> +/**
> + * tb_xdomain_find_by_link_depth() - Find an XDomain by link and
> depth
> + * @tb: Domain where the XDomain belongs to
> + * @link: Root switch link number
> + * @depth: Depth in the link
> + *
> + * Finds XDomain by walking through the Thunderbolt topology below
> @tb.
> + * The returned XDomain will have its reference count increased so
> the
> + * caller needs to call tb_xdomain_put() when it is done with the
> + * object.
> + *
> + * This will find all XDomains including the ones that are not yet
> added
> + * to the bus (handshake is still in progress).
> + *
> + * The caller needs to hold @tb->lock.
> + */
> +struct tb_xdomain *tb_xdomain_find_by_link_depth(struct tb *tb, u8
> link,
> +						 u8 depth)
> +{
> +	struct tb_xdomain_lookup lookup;
> +	struct tb_xdomain *xd;
> +
> +	memset(&lookup, 0, sizeof(lookup));
> +	lookup.link = link;
> +	lookup.depth = depth;
> +
> +	xd = switch_find_xdomain(tb->root_switch, &lookup);
> +	if (xd) {
> +		get_device(&xd->dev);
> +		return xd;
> +	}
> +
> +	return NULL;
> +}
> +
> +bool tb_xdomain_handle_request(struct tb *tb, enum tb_cfg_pkg_type
> type,
> +			       const void *buf, size_t size)
> +{
> +	const struct tb_protocol_handler *handler, *tmp;
> +	const struct tb_xdp_header *hdr = buf;
> +	unsigned int length;
> +	int ret = 0;
> +
> +	/* We expect the packet is at least size of the header */
> +	length = hdr->xd_hdr.length_sn & TB_XDOMAIN_LENGTH_MASK;
> +	if (length != size / 4 - sizeof(hdr->xd_hdr) / 4)
> +		return true;
> +	if (length < sizeof(*hdr) / 4 - sizeof(hdr->xd_hdr) / 4)
> +		return true;
> +
> +	/*
> +	 * Handle XDomain discovery protocol packets directly here.
> For
> +	 * other protocols (based on their UUID) we call registered
> +	 * handlers in turn.
> +	 */
> +	if (uuid_equal(&hdr->uuid, &tb_xdp_uuid)) {
> +		if (type == TB_CFG_PKG_XDOMAIN_REQ) {
> +			tb_xdp_schedule_request(tb, hdr, size);
> +			return true;
> +		}
> +		return false;
> +	}
> +
> +	mutex_lock(&xdomain_lock);
> +	list_for_each_entry_safe(handler, tmp, &protocol_handlers,
> list) {
> +		if (!uuid_equal(&hdr->uuid, handler->uuid))
> +			continue;
> +
> +		mutex_unlock(&xdomain_lock);
> +		ret = handler->callback(buf, size, handler->data);
> +		mutex_lock(&xdomain_lock);
> +
> +		if (ret)
> +			break;
> +	}
> +	mutex_unlock(&xdomain_lock);
> +
> +	return ret > 0;
> +}
> +
> +static int rebuild_property_block(void)
> +{
> +	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 int update_xdomain(struct device *dev, void *data)
> +{
> +	struct tb_xdomain *xd;
> +
> +	xd = tb_to_xdomain(dev);
> +	if (xd) {
> +		queue_delayed_work(xd->tb->wq, &xd-
> >properties_changed_work,
> +				   msecs_to_jiffies(50));
> +	}
> +
> +	return 0;
> +}
> +
> +static void update_all_xdomains(void)
> +{
> +	bus_for_each_dev(&tb_bus_type, NULL, NULL, update_xdomain);
> +}
> +
> +static bool remove_directory(const char *key, const struct
> tb_property_dir *dir)
> +{
> +	struct tb_property *p;
> +
> +	p = tb_property_find(xdomain_property_dir, key,
> +			     TB_PROPERTY_TYPE_DIRECTORY);
> +	if (p && p->value.dir == dir) {
> +		tb_property_remove(p);
> +		return true;
> +	}
> +	return false;
> +}
> +
> +/**
> + * tb_register_property_dir() - Register property directory to the
> host
> + * @key: Key (name) of the directory to add
> + * @dir: Directory to add
> + *
> + * Service drivers can use this function to add new property
> directory
> + * to the host available properties. The other connected hosts are
> + * notified so they can re-read properties of this host if they are
> + * interested.
> + *
> + * Return: %0 on success and negative errno on failure
> + */
> +int tb_register_property_dir(const char *key, struct tb_property_dir
> *dir)
> +{
> +	int ret;
> +
> +	if (!key || strlen(key) > 8)
> +		return -EINVAL;
> +
> +	mutex_lock(&xdomain_lock);
> +	if (tb_property_find(xdomain_property_dir, key,
> +			     TB_PROPERTY_TYPE_DIRECTORY)) {
> +		ret = -EEXIST;
> +		goto err_unlock;
> +	}
> +
> +	ret = tb_property_add_dir(xdomain_property_dir, key, dir);
> +	if (ret)
> +		goto err_unlock;
> +
> +	ret = rebuild_property_block();
> +	if (ret) {
> +		remove_directory(key, dir);
> +		goto err_unlock;
> +	}
> +
> +	mutex_unlock(&xdomain_lock);
> +	update_all_xdomains();
> +	return 0;
> +
> +err_unlock:
> +	mutex_unlock(&xdomain_lock);
> +	return ret;
> +}
> +EXPORT_SYMBOL_GPL(tb_register_property_dir);
> +
> +/**
> + * tb_unregister_property_dir() - Removes property directory from
> host
> + * @key: Key (name) of the directory
> + * @dir: Directory to remove
> + *
> + * This will remove the existing directory from this host and notify
> the
> + * connected hosts about the change.
> + */
> +void tb_unregister_property_dir(const char *key, struct
> tb_property_dir *dir)
> +{
> +	int ret = 0;
> +
> +	mutex_lock(&xdomain_lock);
> +	if (remove_directory(key, dir))
> +		ret = rebuild_property_block();
> +	mutex_unlock(&xdomain_lock);
> +
> +	if (!ret)
> +		update_all_xdomains();
> +}
> +EXPORT_SYMBOL_GPL(tb_unregister_property_dir);
> +
> +int tb_xdomain_init(void)
> +{
> +	int ret;
> +
> +	xdomain_property_dir = tb_property_create_dir(NULL);
> +	if (!xdomain_property_dir)
> +		return -ENOMEM;
> +
> +	/*
> +	 * Initialize standard set of properties without any service
> +	 * directories. Those will be added by service drivers
> +	 * themselves when they are loaded.
> +	 */
> +	tb_property_add_immediate(xdomain_property_dir, "vendorid",
> +				  PCI_VENDOR_ID_INTEL);
> +	tb_property_add_text(xdomain_property_dir, "vendorid",
> "Intel Corp.");
> +	tb_property_add_immediate(xdomain_property_dir, "deviceid",
> 0x1);
> +	tb_property_add_text(xdomain_property_dir, "deviceid",
> +			     utsname()->nodename);
> +	tb_property_add_immediate(xdomain_property_dir, "devicerv",
> 0x80000100);
> +
> +	ret = rebuild_property_block();
> +	if (ret) {
> +		tb_property_free_dir(xdomain_property_dir);
> +		xdomain_property_dir = NULL;
> +	}
> +
> +	return ret;
> +}
> +
> +void tb_xdomain_exit(void)
> +{
> +	kfree(xdomain_property_block);
> +	tb_property_free_dir(xdomain_property_dir);
> +}
> diff --git a/include/linux/mod_devicetable.h
> b/include/linux/mod_devicetable.h
> index 694cebb50f72..7625c3b81f84 100644
> --- a/include/linux/mod_devicetable.h
> +++ b/include/linux/mod_devicetable.h
> @@ -683,5 +683,31 @@ struct fsl_mc_device_id {
>  	const char obj_type[16];
>  };
>  
> +/**
> + * struct tb_service_id - Thunderbolt service identifiers
> + * @match_flags: Flags used to match the structure
> + * @protocol_key: Protocol key the service supports
> + * @protocol_id: Protocol id the service supports
> + * @protocol_version: Version of the protocol
> + * @protocol_revision: Revision of the protocol software
> + * @driver_data: Driver specific data
> + *
> + * Thunderbolt XDomain services are exposed as devices where each
> device
> + * carries the protocol information the service supports.
> Thunderbolt
> + * XDomain service drivers match against that information.
> + */
> +struct tb_service_id {
> +	__u32 match_flags;
> +	char protocol_key[8 + 1];
> +	__u32 protocol_id;
> +	__u32 protocol_version;
> +	__u32 protocol_revision;
> +	kernel_ulong_t driver_data;
> +};
> +
> +#define TBSVC_MATCH_PROTOCOL_KEY	0x0001
> +#define TBSVC_MATCH_PROTOCOL_ID		0x0002
> +#define TBSVC_MATCH_PROTOCOL_VERSION	0x0004
> +#define TBSVC_MATCH_PROTOCOL_REVISION	0x0008
>  
>  #endif /* LINUX_MOD_DEVICETABLE_H */
> diff --git a/include/linux/thunderbolt.h
> b/include/linux/thunderbolt.h
> index 4011d6537a8c..79abdaf1c296 100644
> --- a/include/linux/thunderbolt.h
> +++ b/include/linux/thunderbolt.h
> @@ -17,6 +17,7 @@
>  #include <linux/device.h>
>  #include <linux/list.h>
>  #include <linux/mutex.h>
> +#include <linux/mod_devicetable.h>
>  #include <linux/uuid.h>
>  
>  enum tb_cfg_pkg_type {
> @@ -77,6 +78,8 @@ struct tb {
>  };
>  
>  extern struct bus_type tb_bus_type;
> +extern struct device_type tb_service_type;
> +extern struct device_type tb_xdomain_type;
>  
>  #define TB_LINKS_PER_PHY_PORT	2
>  
> @@ -155,4 +158,243 @@ struct tb_property *tb_property_get_next(struct
> tb_property_dir *dir,
>  	     property;						
> \
>  	     property = tb_property_get_next(dir, property))
>  
> +int tb_register_property_dir(const char *key, struct tb_property_dir
> *dir);
> +void tb_unregister_property_dir(const char *key, struct
> tb_property_dir *dir);
> +
> +/**
> + * struct tb_xdomain - Cross-domain (XDomain) connection
> + * @dev: XDomain device
> + * @tb: Pointer to the domain
> + * @remote_uuid: UUID of the remote domain (host)
> + * @local_uuid: Cached local UUID
> + * @route: Route string the other domain can be reached
> + * @vendor: Vendor ID of the remote domain
> + * @device: Device ID of the demote domain
> + * @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)
> + * @is_unplugged: The XDomain is unplugged
> + * @resume: The XDomain is being resumed
> + * @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.
> + * @get_properties_work: Work used to get remote domain properties
> + * @properties_retries: Number of times left to read properties
> + * @properties_changed_work: Work used to notify the remote domain
> that
> + *			     our properties have changed
> + * @properties_changed_retries: Number of times left to send
> properties
> + *				changed notification
> + * @link: Root switch link the remote domain is connected (ICM only)
> + * @depth: Depth in the chain the remote domain is connected (ICM
> only)
> + *
> + * This structure represents connection across two domains (hosts).
> + * Each XDomain contains zero or more services which are exposed as
> + * &struct tb_service objects.
> + *
> + * Service drivers may access this structure if they need to
> enumerate
> + * non-standard properties but they need hold @lock when doing so
> + * because properties can be changed asynchronously in response to
> + * changes in the remote domain.
> + */
> +struct tb_xdomain {
> +	struct device dev;
> +	struct tb *tb;
> +	uuid_t *remote_uuid;
> +	const uuid_t *local_uuid;
> +	u64 route;
> +	u16 vendor;
> +	u16 device;
> +	struct mutex lock;
> +	const char *vendor_name;
> +	const char *device_name;
> +	bool is_unplugged;
> +	bool resume;
> +	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 delayed_work get_properties_work;
> +	int properties_retries;
> +	struct delayed_work properties_changed_work;
> +	int properties_changed_retries;
> +	u8 link;
> +	u8 depth;
> +};
> +
> +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);
> +struct tb_xdomain *tb_xdomain_find_by_uuid(struct tb *tb, const
> uuid_t *uuid);
> +
> +static inline struct tb_xdomain *
> +tb_xdomain_find_by_uuid_locked(struct tb *tb, const uuid_t *uuid)
> +{
> +	struct tb_xdomain *xd;
> +
> +	mutex_lock(&tb->lock);
> +	xd = tb_xdomain_find_by_uuid(tb, uuid);
> +	mutex_unlock(&tb->lock);
> +
> +	return xd;
> +}
> +
> +static inline struct tb_xdomain *tb_xdomain_get(struct tb_xdomain
> *xd)
> +{
> +	if (xd)
> +		get_device(&xd->dev);
> +	return xd;
> +}
> +
> +static inline void tb_xdomain_put(struct tb_xdomain *xd)
> +{
> +	if (xd)
> +		put_device(&xd->dev);
> +}
> +
> +static inline bool tb_is_xdomain(const struct device *dev)
> +{
> +	return dev->type == &tb_xdomain_type;
> +}
> +
> +static inline struct tb_xdomain *tb_to_xdomain(struct device *dev)
> +{
> +	if (tb_is_xdomain(dev))
> +		return container_of(dev, struct tb_xdomain, dev);
> +	return NULL;
> +}
> +
> +int tb_xdomain_response(struct tb_xdomain *xd, const void *response,
> +			size_t size, enum tb_cfg_pkg_type type);
> +int tb_xdomain_request(struct tb_xdomain *xd, const void *request,
> +		       size_t request_size, enum tb_cfg_pkg_type
> request_type,
> +		       void *response, size_t response_size,
> +		       enum tb_cfg_pkg_type response_type,
> +		       unsigned int timeout_msec);
> +
> +/**
> + * tb_protocol_handler - Protocol specific handler
> + * @uuid: XDomain messages with this UUID are dispatched to this
> handler
> + * @callback: Callback called with the XDomain message. Returning %1
> + *	      here tells the XDomain core that the message was
> handled
> + *	      by this handler and should not be forwared to other
> + *	      handlers.
> + * @data: Data passed with the callback
> + * @list: Handlers are linked using this
> + *
> + * Thunderbolt services can hook into incoming XDomain requests by
> + * registering protocol handler. Only limitation is that the XDomain
> + * discovery protocol UUID cannot be registered since it is handled
> by
> + * the core XDomain code.
> + *
> + * The @callback must check that the message is really directed to
> the
> + * service the driver implements.
> + */
> +struct tb_protocol_handler {
> +	const uuid_t *uuid;
> +	int (*callback)(const void *buf, size_t size, void *data);
> +	void *data;
> +	struct list_head list;
> +};
> +
> +int tb_register_protocol_handler(struct tb_protocol_handler
> *handler);
> +void tb_unregister_protocol_handler(struct tb_protocol_handler
> *handler);
> +
> +/**
> + * struct tb_service - Thunderbolt service
> + * @dev: XDomain device
> + * @id: ID of the service (shown in sysfs)
> + * @key: Protocol key from the properties directory
> + * @prtcid: Protocol ID from the properties directory
> + * @prtcvers: Protocol version from the properties directory
> + * @prtcrevs: Protocol software revision from the properties
> directory
> + * @prtcstns: Protocol settings mask from the properties directory
> + *
> + * Each domain exposes set of services it supports as collection of
> + * properties. For each service there will be one corresponding
> + * &struct tb_service. Service drivers are bound to these.
> + */
> +struct tb_service {
> +	struct device dev;
> +	int id;
> +	const char *key;
> +	u32 prtcid;
> +	u32 prtcvers;
> +	u32 prtcrevs;
> +	u32 prtcstns;
> +};
> +
> +static inline struct tb_service *tb_service_get(struct tb_service
> *svc)
> +{
> +	if (svc)
> +		get_device(&svc->dev);
> +	return svc;
> +}
> +
> +static inline void tb_service_put(struct tb_service *svc)
> +{
> +	if (svc)
> +		put_device(&svc->dev);
> +}
> +
> +static inline bool tb_is_service(const struct device *dev)
> +{
> +	return dev->type == &tb_service_type;
> +}
> +
> +static inline struct tb_service *tb_to_service(struct device *dev)
> +{
> +	if (tb_is_service(dev))
> +		return container_of(dev, struct tb_service, dev);
> +	return NULL;
> +}
> +
> +/**
> + * tb_service_driver - Thunderbolt service driver
> + * @driver: Driver structure
> + * @probe: Called when the driver is probed
> + * @remove: Called when the driver is removed (optional)
> + * @shutdown: Called at shutdown time to stop the service (optional)
> + * @id_table: Table of service identifiers the driver supports
> + */
> +struct tb_service_driver {
> +	struct device_driver driver;
> +	int (*probe)(struct tb_service *svc, const struct
> tb_service_id *id);
> +	void (*remove)(struct tb_service *svc);
> +	void (*shutdown)(struct tb_service *svc);
> +	const struct tb_service_id *id_table;
> +};
> +
> +#define TB_SERVICE(key, id)				\
> +	.match_flags = TBSVC_MATCH_PROTOCOL_KEY |	\
> +		       TBSVC_MATCH_PROTOCOL_ID,		\
> +	.protocol_key = (key),				\
> +	.protocol_id = (id)
> +
> +int tb_register_service_driver(struct tb_service_driver *drv);
> +void tb_unregister_service_driver(struct tb_service_driver *drv);
> +
> +static inline void *tb_service_get_drvdata(const struct tb_service
> *svc)
> +{
> +	return dev_get_drvdata(&svc->dev);
> +}
> +
> +static inline void tb_service_set_drvdata(struct tb_service *svc,
> void *data)
> +{
> +	dev_set_drvdata(&svc->dev, data);
> +}
> +
> +static inline struct tb_xdomain *tb_service_parent(struct tb_service
> *svc)
> +{
> +	return tb_to_xdomain(svc->dev.parent);
> +}
> +
>  #endif /* THUNDERBOLT_H_ */
> diff --git a/scripts/mod/devicetable-offsets.c
> b/scripts/mod/devicetable-offsets.c
> index e4d90e50f6fe..57263f2f8f2f 100644
> --- a/scripts/mod/devicetable-offsets.c
> +++ b/scripts/mod/devicetable-offsets.c
> @@ -206,5 +206,12 @@ int main(void)
>  	DEVID_FIELD(fsl_mc_device_id, vendor);
>  	DEVID_FIELD(fsl_mc_device_id, obj_type);
>  
> +	DEVID(tb_service_id);
> +	DEVID_FIELD(tb_service_id, match_flags);
> +	DEVID_FIELD(tb_service_id, protocol_key);
> +	DEVID_FIELD(tb_service_id, protocol_id);
> +	DEVID_FIELD(tb_service_id, protocol_version);
> +	DEVID_FIELD(tb_service_id, protocol_revision);
> +
>  	return 0;
>  }
> diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c
> index 29d6699d5a06..6ef6e63f96fd 100644
> --- a/scripts/mod/file2alias.c
> +++ b/scripts/mod/file2alias.c
> @@ -1301,6 +1301,31 @@ static int do_fsl_mc_entry(const char
> *filename, void *symval,
>  }
>  ADD_TO_DEVTABLE("fslmc", fsl_mc_device_id, do_fsl_mc_entry);
>  
> +/* Looks like: tbsvc:kSpNvNrN */
> +static int do_tbsvc_entry(const char *filename, void *symval, char
> *alias)
> +{
> +	DEF_FIELD(symval, tb_service_id, match_flags);
> +	DEF_FIELD_ADDR(symval, tb_service_id, protocol_key);
> +	DEF_FIELD(symval, tb_service_id, protocol_id);
> +	DEF_FIELD(symval, tb_service_id, protocol_version);
> +	DEF_FIELD(symval, tb_service_id, protocol_revision);
> +
> +	strcpy(alias, "tbsvc:");
> +	if (match_flags & TBSVC_MATCH_PROTOCOL_KEY)
> +		sprintf(alias + strlen(alias), "k%s",
> *protocol_key);
> +	else
> +		strcat(alias + strlen(alias), "k*");
> +	ADD(alias, "p", match_flags & TBSVC_MATCH_PROTOCOL_ID,
> protocol_id);
> +	ADD(alias, "v", match_flags & TBSVC_MATCH_PROTOCOL_VERSION,
> +	    protocol_version);
> +	ADD(alias, "r", match_flags & TBSVC_MATCH_PROTOCOL_REVISION,
> +	    protocol_revision);
> +
> +	add_wildcard(alias);
> +	return 1;
> +}
> +ADD_TO_DEVTABLE("tbsvc", tb_service_id, do_tbsvc_entry);
> +
>  /* Does namelen bytes of name exactly match the symbol? */
>  static bool sym_is(const char *name, unsigned namelen, const char
> *symbol)
>  {

^ permalink raw reply

* Re: [PATCH] bpf: devmap: pass on return value of bpf_map_precharge_memlock
From: Alexei Starovoitov @ 2017-09-18 15:57 UTC (permalink / raw)
  To: Tobias Klauser
  Cc: Alexei Starovoitov, Daniel Borkmann, John Fastabend, netdev
In-Reply-To: <20170918130346.10833-1-tklauser@distanz.ch>

On Mon, Sep 18, 2017 at 03:03:46PM +0200, Tobias Klauser wrote:
> If bpf_map_precharge_memlock in dev_map_alloc, -ENOMEM is returned
> regardless of the actual error produced by bpf_map_precharge_memlock.
> Fix it by passing on the error returned by bpf_map_precharge_memlock.
> 
> Also return -EINVAL instead of -ENOMEM if the page count overflow check
> fails.
> 
> This makes dev_map_alloc match the behavior of other bpf maps' alloc
> functions wrt. return values.
> 
> Signed-off-by: Tobias Klauser <tklauser@distanz.ch>

good catch.
Acked-by: Alexei Starovoitov <ast@kernel.org>

^ permalink raw reply

* Re: [PATCH] netfilter: nat: Do not use ARRAY_SIZE() on spinlocks to fix zero div
From: Pablo Neira Ayuso @ 2017-09-18 15:33 UTC (permalink / raw)
  To: Geert Uytterhoeven
  Cc: Jozsef Kadlecsik, Florian Westphal, David S . Miller,
	Arnd Bergmann, netfilter-devel, coreteam, netdev, linux-kernel
In-Reply-To: <1505043701-13255-1-git-send-email-geert@linux-m68k.org>

On Sun, Sep 10, 2017 at 01:41:41PM +0200, Geert Uytterhoeven wrote:
> If no spinlock debugging options (CONFIG_GENERIC_LOCKBREAK,
> CONFIG_DEBUG_SPINLOCK, CONFIG_DEBUG_LOCK_ALLOC) are enabled on a UP
> platform (e.g. m68k defconfig), arch_spinlock_t is an empty struct,
> hence using ARRAY_SIZE(nf_nat_locks) causes a division by zero:
> 
>     net/netfilter/nf_nat_core.c: In function ‘nf_nat_setup_info’:
>     net/netfilter/nf_nat_core.c:432: warning: division by zero
>     net/netfilter/nf_nat_core.c: In function ‘__nf_nat_cleanup_conntrack’:
>     net/netfilter/nf_nat_core.c:535: warning: division by zero
>     net/netfilter/nf_nat_core.c:537: warning: division by zero
>     net/netfilter/nf_nat_core.c: In function ‘nf_nat_init’:
>     net/netfilter/nf_nat_core.c:810: warning: division by zero
>     net/netfilter/nf_nat_core.c:811: warning: division by zero
>     net/netfilter/nf_nat_core.c:824: warning: division by zero
> 
> Fix this by using the CONNTRACK_LOCKS definition instead.

Applied.

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox