summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohan Hovold <johan@hovoldconsulting.com>2016-01-19 12:51:25 +0100
committerGreg Kroah-Hartman <gregkh@google.com>2016-01-19 12:17:13 -0800
commit0e46fab7dd5afbb4db97d9520812c57e9301bfc2 (patch)
treed165efb0b613ace84f2ad6e3c36a0ee6ab445717
parentc6d64a19cdb39680460ab9f153e33843aa8c825c (diff)
greybus: connection: fix lookup race
Fix longstanding race that could lead to a connection being destroyed while processing an incoming message due to missing reference counting when looking up the connection. Add helpers to manipulate the connection kref. Reviewed-by: Viresh Kumar <viresh.kumar@linaro.org> Signed-off-by: Johan Hovold <johan@hovoldconsulting.com> Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
-rw-r--r--drivers/staging/greybus/connection.c23
1 files changed, 21 insertions, 2 deletions
diff --git a/drivers/staging/greybus/connection.c b/drivers/staging/greybus/connection.c
index bfd2c0dee056..d215c5c6331b 100644
--- a/drivers/staging/greybus/connection.c
+++ b/drivers/staging/greybus/connection.c
@@ -12,6 +12,9 @@
#include "greybus.h"
+static void gb_connection_kref_release(struct kref *kref);
+
+
static DEFINE_SPINLOCK(gb_connections_lock);
/* This is only used at initialization time; no locking is required. */
@@ -30,6 +33,19 @@ gb_connection_intf_find(struct gb_interface *intf, u16 cport_id)
return NULL;
}
+static void gb_connection_get(struct gb_connection *connection)
+{
+ kref_get(&connection->kref);
+}
+
+static void gb_connection_put(struct gb_connection *connection)
+{
+ kref_put(&connection->kref, gb_connection_kref_release);
+}
+
+/*
+ * Returns a reference-counted pointer to the connection if found.
+ */
static struct gb_connection *
gb_connection_hd_find(struct gb_host_device *hd, u16 cport_id)
{
@@ -38,8 +54,10 @@ gb_connection_hd_find(struct gb_host_device *hd, u16 cport_id)
spin_lock_irqsave(&gb_connections_lock, flags);
list_for_each_entry(connection, &hd->connections, hd_links)
- if (connection->hd_cport_id == cport_id)
+ if (connection->hd_cport_id == cport_id) {
+ gb_connection_get(connection);
goto found;
+ }
connection = NULL;
found:
spin_unlock_irqrestore(&gb_connections_lock, flags);
@@ -63,6 +81,7 @@ void greybus_data_rcvd(struct gb_host_device *hd, u16 cport_id,
return;
}
gb_connection_recv(connection, data, length);
+ gb_connection_put(connection);
}
EXPORT_SYMBOL_GPL(greybus_data_rcvd);
@@ -516,7 +535,7 @@ void gb_connection_destroy(struct gb_connection *connection)
ida_simple_remove(id_map, connection->hd_cport_id);
connection->hd_cport_id = CPORT_ID_BAD;
- kref_put(&connection->kref, gb_connection_kref_release);
+ gb_connection_put(connection);
}
void gb_connection_latency_tag_enable(struct gb_connection *connection)