Merge branch 'odp_fixes' into rdma.git for-next
[linux-2.6-block.git] / drivers / infiniband / core / device.c
index 8892862fb7591b00d850b8495b46862002b7753e..99c4a55545cfba00812824c87ac56c4b0a2acd20 100644 (file)
@@ -93,11 +93,17 @@ static DEFINE_XARRAY_FLAGS(devices, XA_FLAGS_ALLOC);
 static DECLARE_RWSEM(devices_rwsem);
 #define DEVICE_REGISTERED XA_MARK_1
 
-static LIST_HEAD(client_list);
+static u32 highest_client_id;
 #define CLIENT_REGISTERED XA_MARK_1
 static DEFINE_XARRAY_FLAGS(clients, XA_FLAGS_ALLOC);
 static DECLARE_RWSEM(clients_rwsem);
 
+static void ib_client_put(struct ib_client *client)
+{
+       if (refcount_dec_and_test(&client->uses))
+               complete(&client->uses_zero);
+}
+
 /*
  * If client_data is registered then the corresponding client must also still
  * be registered.
@@ -652,6 +658,14 @@ static int add_client_context(struct ib_device *device,
                return 0;
 
        down_write(&device->client_data_rwsem);
+       /*
+        * So long as the client is registered hold both the client and device
+        * unregistration locks.
+        */
+       if (!refcount_inc_not_zero(&client->uses))
+               goto out_unlock;
+       refcount_inc(&device->refcount);
+
        /*
         * Another caller to add_client_context got here first and has already
         * completely initialized context.
@@ -675,6 +689,9 @@ static int add_client_context(struct ib_device *device,
        return 0;
 
 out:
+       ib_device_put(device);
+       ib_client_put(client);
+out_unlock:
        up_write(&device->client_data_rwsem);
        return ret;
 }
@@ -694,7 +711,7 @@ static void remove_client_context(struct ib_device *device,
        client_data = xa_load(&device->client_data, client_id);
        xa_clear_mark(&device->client_data, client_id, CLIENT_DATA_REGISTERED);
        client = xa_load(&clients, client_id);
-       downgrade_write(&device->client_data_rwsem);
+       up_write(&device->client_data_rwsem);
 
        /*
         * Notice we cannot be holding any exclusive locks when calling the
@@ -704,17 +721,13 @@ static void remove_client_context(struct ib_device *device,
         *
         * For this reason clients and drivers should not call the
         * unregistration functions will holdling any locks.
-        *
-        * It tempting to drop the client_data_rwsem too, but this is required
-        * to ensure that unregister_client does not return until all clients
-        * are completely unregistered, which is required to avoid module
-        * unloading races.
         */
        if (client->remove)
                client->remove(device, client_data);
 
        xa_erase(&device->client_data, client_id);
-       up_read(&device->client_data_rwsem);
+       ib_device_put(device);
+       ib_client_put(client);
 }
 
 static int alloc_port_data(struct ib_device *device)
@@ -1223,7 +1236,7 @@ static int setup_device(struct ib_device *device)
 
 static void disable_device(struct ib_device *device)
 {
-       struct ib_client *client;
+       u32 cid;
 
        WARN_ON(!refcount_read(&device->refcount));
 
@@ -1231,10 +1244,19 @@ static void disable_device(struct ib_device *device)
        xa_clear_mark(&devices, device->index, DEVICE_REGISTERED);
        up_write(&devices_rwsem);
 
+       /*
+        * Remove clients in LIFO order, see assign_client_id. This could be
+        * more efficient if xarray learns to reverse iterate. Since no new
+        * clients can be added to this ib_device past this point we only need
+        * the maximum possible client_id value here.
+        */
        down_read(&clients_rwsem);
-       list_for_each_entry_reverse(client, &client_list, list)
-               remove_client_context(device, client->client_id);
+       cid = highest_client_id;
        up_read(&clients_rwsem);
+       while (cid) {
+               cid--;
+               remove_client_context(device, cid);
+       }
 
        /* Pairs with refcount_set in enable_device */
        ib_device_put(device);
@@ -1661,30 +1683,31 @@ static int assign_client_id(struct ib_client *client)
        /*
         * The add/remove callbacks must be called in FIFO/LIFO order. To
         * achieve this we assign client_ids so they are sorted in
-        * registration order, and retain a linked list we can reverse iterate
-        * to get the LIFO order. The extra linked list can go away if xarray
-        * learns to reverse iterate.
+        * registration order.
         */
-       if (list_empty(&client_list)) {
-               client->client_id = 0;
-       } else {
-               struct ib_client *last;
-
-               last = list_last_entry(&client_list, struct ib_client, list);
-               client->client_id = last->client_id + 1;
-       }
+       client->client_id = highest_client_id;
        ret = xa_insert(&clients, client->client_id, client, GFP_KERNEL);
        if (ret)
                goto out;
 
+       highest_client_id++;
        xa_set_mark(&clients, client->client_id, CLIENT_REGISTERED);
-       list_add_tail(&client->list, &client_list);
 
 out:
        up_write(&clients_rwsem);
        return ret;
 }
 
+static void remove_client_id(struct ib_client *client)
+{
+       down_write(&clients_rwsem);
+       xa_erase(&clients, client->client_id);
+       for (; highest_client_id; highest_client_id--)
+               if (xa_load(&clients, highest_client_id - 1))
+                       break;
+       up_write(&clients_rwsem);
+}
+
 /**
  * ib_register_client - Register an IB client
  * @client:Client to register
@@ -1704,6 +1727,8 @@ int ib_register_client(struct ib_client *client)
        unsigned long index;
        int ret;
 
+       refcount_set(&client->uses, 1);
+       init_completion(&client->uses_zero);
        ret = assign_client_id(client);
        if (ret)
                return ret;
@@ -1739,21 +1764,30 @@ void ib_unregister_client(struct ib_client *client)
        unsigned long index;
 
        down_write(&clients_rwsem);
+       ib_client_put(client);
        xa_clear_mark(&clients, client->client_id, CLIENT_REGISTERED);
        up_write(&clients_rwsem);
-       /*
-        * Every device still known must be serialized to make sure we are
-        * done with the client callbacks before we return.
-        */
-       down_read(&devices_rwsem);
-       xa_for_each (&devices, index, device)
+
+       /* We do not want to have locks while calling client->remove() */
+       rcu_read_lock();
+       xa_for_each (&devices, index, device) {
+               if (!ib_device_try_get(device))
+                       continue;
+               rcu_read_unlock();
+
                remove_client_context(device, client->client_id);
-       up_read(&devices_rwsem);
 
-       down_write(&clients_rwsem);
-       list_del(&client->list);
-       xa_erase(&clients, client->client_id);
-       up_write(&clients_rwsem);
+               ib_device_put(device);
+               rcu_read_lock();
+       }
+       rcu_read_unlock();
+
+       /*
+        * remove_client_context() is not a fence, it can return even though a
+        * removal is ongoing. Wait until all removals are completed.
+        */
+       wait_for_completion(&client->uses_zero);
+       remove_client_id(client);
 }
 EXPORT_SYMBOL(ib_unregister_client);
 
@@ -2582,6 +2616,7 @@ void ib_set_device_ops(struct ib_device *dev, const struct ib_device_ops *ops)
        SET_DEVICE_OP(dev_ops, get_vf_config);
        SET_DEVICE_OP(dev_ops, get_vf_stats);
        SET_DEVICE_OP(dev_ops, init_port);
+       SET_DEVICE_OP(dev_ops, invalidate_range);
        SET_DEVICE_OP(dev_ops, iw_accept);
        SET_DEVICE_OP(dev_ops, iw_add_ref);
        SET_DEVICE_OP(dev_ops, iw_connect);