Merge tag 'mfd-next-5.2' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd
[sfrench/cifs-2.6.git] / drivers / thunderbolt / xdomain.c
index e27dd8beb94be4a9690b4fcaab00e93996a3c82f..5118d46702d52d86d022ccdff44d64cda524afc4 100644 (file)
@@ -18,6 +18,7 @@
 #include "tb.h"
 
 #define XDOMAIN_DEFAULT_TIMEOUT                        5000 /* ms */
+#define XDOMAIN_UUID_RETRIES                   10
 #define XDOMAIN_PROPERTIES_RETRIES             60
 #define XDOMAIN_PROPERTIES_CHANGED_RETRIES     10
 
@@ -222,6 +223,50 @@ static int tb_xdp_handle_error(const struct tb_xdp_header *hdr)
        return 0;
 }
 
+static int tb_xdp_uuid_request(struct tb_ctl *ctl, u64 route, int retry,
+                              uuid_t *uuid)
+{
+       struct tb_xdp_uuid_response res;
+       struct tb_xdp_uuid req;
+       int ret;
+
+       memset(&req, 0, sizeof(req));
+       tb_xdp_fill_header(&req.hdr, route, retry % 4, UUID_REQUEST,
+                          sizeof(req));
+
+       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;
+
+       ret = tb_xdp_handle_error(&res.hdr);
+       if (ret)
+               return ret;
+
+       uuid_copy(uuid, &res.src_uuid);
+       return 0;
+}
+
+static int tb_xdp_uuid_response(struct tb_ctl *ctl, u64 route, u8 sequence,
+                               const uuid_t *uuid)
+{
+       struct tb_xdp_uuid_response res;
+
+       memset(&res, 0, sizeof(res));
+       tb_xdp_fill_header(&res.hdr, route, sequence, UUID_RESPONSE,
+                          sizeof(res));
+
+       uuid_copy(&res.src_uuid, uuid);
+       res.src_route_hi = upper_32_bits(route);
+       res.src_route_lo = lower_32_bits(route);
+
+       return __tb_xdomain_response(ctl, &res, sizeof(res),
+                                    TB_CFG_PKG_XDOMAIN_RESP);
+}
+
 static int tb_xdp_error_response(struct tb_ctl *ctl, u64 route, u8 sequence,
                                 enum tb_xdp_error error)
 {
@@ -512,7 +557,14 @@ static void tb_xdp_handle_request(struct work_struct *work)
                break;
        }
 
+       case UUID_REQUEST_OLD:
+       case UUID_REQUEST:
+               ret = tb_xdp_uuid_response(ctl, route, sequence, uuid);
+               break;
+
        default:
+               tb_xdp_error_response(ctl, route, sequence,
+                                     ERROR_NOT_SUPPORTED);
                break;
        }
 
@@ -524,9 +576,11 @@ static void tb_xdp_handle_request(struct work_struct *work)
 out:
        kfree(xw->pkg);
        kfree(xw);
+
+       tb_domain_put(tb);
 }
 
-static void
+static bool
 tb_xdp_schedule_request(struct tb *tb, const struct tb_xdp_header *hdr,
                        size_t size)
 {
@@ -534,13 +588,18 @@ tb_xdp_schedule_request(struct tb *tb, const struct tb_xdp_header *hdr,
 
        xw = kmalloc(sizeof(*xw), GFP_KERNEL);
        if (!xw)
-               return;
+               return false;
 
        INIT_WORK(&xw->work, tb_xdp_handle_request);
        xw->pkg = kmemdup(hdr, size, GFP_KERNEL);
-       xw->tb = tb;
+       if (!xw->pkg) {
+               kfree(xw);
+               return false;
+       }
+       xw->tb = tb_domain_get(tb);
 
-       queue_work(tb->wq, &xw->work);
+       schedule_work(&xw->work);
+       return true;
 }
 
 /**
@@ -740,6 +799,7 @@ static void enumerate_services(struct tb_xdomain *xd)
        struct tb_service *svc;
        struct tb_property *p;
        struct device *dev;
+       int id;
 
        /*
         * First remove all services that are not available anymore in
@@ -768,7 +828,12 @@ static void enumerate_services(struct tb_xdomain *xd)
                        break;
                }
 
-               svc->id = ida_simple_get(&xd->service_ids, 0, 0, GFP_KERNEL);
+               id = ida_simple_get(&xd->service_ids, 0, 0, GFP_KERNEL);
+               if (id < 0) {
+                       kfree(svc);
+                       break;
+               }
+               svc->id = id;
                svc->dev.bus = &tb_bus_type;
                svc->dev.type = &tb_service_type;
                svc->dev.parent = &xd->dev;
@@ -826,6 +891,55 @@ static void tb_xdomain_restore_paths(struct tb_xdomain *xd)
        }
 }
 
+static void tb_xdomain_get_uuid(struct work_struct *work)
+{
+       struct tb_xdomain *xd = container_of(work, typeof(*xd),
+                                            get_uuid_work.work);
+       struct tb *tb = xd->tb;
+       uuid_t uuid;
+       int ret;
+
+       ret = tb_xdp_uuid_request(tb->ctl, xd->route, xd->uuid_retries, &uuid);
+       if (ret < 0) {
+               if (xd->uuid_retries-- > 0) {
+                       queue_delayed_work(xd->tb->wq, &xd->get_uuid_work,
+                                          msecs_to_jiffies(100));
+               } else {
+                       dev_dbg(&xd->dev, "failed to read remote UUID\n");
+               }
+               return;
+       }
+
+       if (uuid_equal(&uuid, xd->local_uuid)) {
+               dev_dbg(&xd->dev, "intra-domain loop detected\n");
+               return;
+       }
+
+       /*
+        * If the UUID is different, there is another domain connected
+        * so mark this one unplugged and wait for the connection
+        * manager to replace it.
+        */
+       if (xd->remote_uuid && !uuid_equal(&uuid, xd->remote_uuid)) {
+               dev_dbg(&xd->dev, "remote UUID is different, unplugging\n");
+               xd->is_unplugged = true;
+               return;
+       }
+
+       /* First time fill in the missing UUID */
+       if (!xd->remote_uuid) {
+               xd->remote_uuid = kmemdup(&uuid, sizeof(uuid_t), GFP_KERNEL);
+               if (!xd->remote_uuid)
+                       return;
+       }
+
+       /* Now we can start the normal properties exchange */
+       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 tb_xdomain_get_properties(struct work_struct *work)
 {
        struct tb_xdomain *xd = container_of(work, typeof(*xd),
@@ -1032,21 +1146,29 @@ static void tb_xdomain_release(struct device *dev)
 
 static void start_handshake(struct tb_xdomain *xd)
 {
+       xd->uuid_retries = XDOMAIN_UUID_RETRIES;
        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));
+       if (xd->needs_uuid) {
+               queue_delayed_work(xd->tb->wq, &xd->get_uuid_work,
+                                  msecs_to_jiffies(100));
+       } else {
+               /* 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->uuid_retries = 0;
        xd->properties_retries = 0;
        xd->properties_changed_retries = 0;
 
+       cancel_delayed_work_sync(&xd->get_uuid_work);
        cancel_delayed_work_sync(&xd->get_properties_work);
        cancel_delayed_work_sync(&xd->properties_changed_work);
 }
@@ -1089,7 +1211,7 @@ EXPORT_SYMBOL_GPL(tb_xdomain_type);
  *         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
+ * @remote_uuid: UUID of the other domain (optional)
  *
  * Allocates new XDomain structure and returns pointer to that. The
  * object must be released by calling tb_xdomain_put().
@@ -1108,6 +1230,7 @@ struct tb_xdomain *tb_xdomain_alloc(struct tb *tb, struct device *parent,
        xd->route = route;
        ida_init(&xd->service_ids);
        mutex_init(&xd->lock);
+       INIT_DELAYED_WORK(&xd->get_uuid_work, tb_xdomain_get_uuid);
        INIT_DELAYED_WORK(&xd->get_properties_work, tb_xdomain_get_properties);
        INIT_DELAYED_WORK(&xd->properties_changed_work,
                          tb_xdomain_properties_changed);
@@ -1116,9 +1239,14 @@ struct tb_xdomain *tb_xdomain_alloc(struct tb *tb, struct device *parent,
        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;
+       if (remote_uuid) {
+               xd->remote_uuid = kmemdup(remote_uuid, sizeof(uuid_t),
+                                         GFP_KERNEL);
+               if (!xd->remote_uuid)
+                       goto err_free_local_uuid;
+       } else {
+               xd->needs_uuid = true;
+       }
 
        device_initialize(&xd->dev);
        xd->dev.parent = get_device(parent);
@@ -1282,14 +1410,12 @@ static struct tb_xdomain *switch_find_xdomain(struct tb_switch *sw,
                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))
+                               if (xd->remote_uuid &&
+                                   uuid_equal(xd->remote_uuid, lookup->uuid))
                                        return xd;
                        } else if (lookup->link &&
                                   lookup->link == xd->link &&
@@ -1299,7 +1425,7 @@ static struct tb_xdomain *switch_find_xdomain(struct tb_switch *sw,
                                   lookup->route == xd->route) {
                                return xd;
                        }
-               } else if (port->remote) {
+               } else if (tb_port_has_remote(port)) {
                        xd = switch_find_xdomain(port->remote->sw, lookup);
                        if (xd)
                                return xd;
@@ -1416,10 +1542,8 @@ bool tb_xdomain_handle_request(struct tb *tb, enum tb_cfg_pkg_type type,
         * 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;
-               }
+               if (type == TB_CFG_PKG_XDOMAIN_REQ)
+                       return tb_xdp_schedule_request(tb, hdr, size);
                return false;
        }