diff options
author | Johan Hovold <johan@hovoldconsulting.com> | 2016-01-19 12:51:25 +0100 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@google.com> | 2016-01-19 12:17:13 -0800 |
commit | 0e46fab7dd5afbb4db97d9520812c57e9301bfc2 (patch) | |
tree | d165efb0b613ace84f2ad6e3c36a0ee6ab445717 | |
parent | c6d64a19cdb39680460ab9f153e33843aa8c825c (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.c | 23 |
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) |