Bluetooth: introduce hci_conn ref-counting
authorDavid Herrmann <dh.herrmann@gmail.com>
Sat, 6 Apr 2013 18:28:39 +0000 (20:28 +0200)
committerGustavo Padovan <gustavo.padovan@collabora.co.uk>
Wed, 17 Apr 2013 05:45:22 +0000 (02:45 -0300)
We currently do not allow using hci_conn from outside of HCI-core.
However, several other users could make great use of it. This includes
HIDP, rfcomm and all other sub-protocols that rely on an active
connection.

Hence, we now introduce hci_conn ref-counting. We currently never call
get_device(). put_device() is exclusively used in hci_conn_del_sysfs().
Hence, we currently never have a greater device-refcnt than 1.
Therefore, it is safe to move the put_device() call from
hci_conn_del_sysfs() to hci_conn_del() (it's the only caller). In fact,
this even fixes a "use-after-free" bug as we access hci_conn after calling
hci_conn_del_sysfs() in hci_conn_del().

From now on we can add references to hci_conn objects in other layers
(like l2cap_sock, HIDP, rfcomm, ...) and grab a reference via
hci_conn_get(). This does _not_ guarantee, that the connection is still
alive. But, this isn't what we want. We can simply lock the hci_conn
device and use "device_is_registered(hci_conn->dev)" to test that.
However, this is hardly necessary as outside users should never rely on
the HCI connection to be alive, anyway. Instead, they should solely rely
on the device-object to be available.
But if sub-devices want the hci_conn object as sysfs parent, they need to
be notified when the connection drops. This will be introduced in later
patches with l2cap_users.

Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
Acked-by: Marcel Holtmann <marcel@holtmann.org>
Signed-off-by: Gustavo Padovan <gustavo.padovan@collabora.co.uk>
include/net/bluetooth/hci_core.h
net/bluetooth/hci_conn.c
net/bluetooth/hci_sysfs.c

index 5590cc4412c68e52e20de67f20391a9510272edf..d324b11a0c8f3368f5cfa7c9d48740a8844abfa2 100644 (file)
@@ -600,6 +600,37 @@ int hci_conn_switch_role(struct hci_conn *conn, __u8 role);
 
 void hci_conn_enter_active_mode(struct hci_conn *conn, __u8 force_active);
 
+/*
+ * hci_conn_get() and hci_conn_put() are used to control the life-time of an
+ * "hci_conn" object. They do not guarantee that the hci_conn object is running,
+ * working or anything else. They just guarantee that the object is available
+ * and can be dereferenced. So you can use its locks, local variables and any
+ * other constant data.
+ * Before accessing runtime data, you _must_ lock the object and then check that
+ * it is still running. As soon as you release the locks, the connection might
+ * get dropped, though.
+ *
+ * On the other hand, hci_conn_hold() and hci_conn_drop() are used to control
+ * how long the underlying connection is held. So every channel that runs on the
+ * hci_conn object calls this to prevent the connection from disappearing. As
+ * long as you hold a device, you must also guarantee that you have a valid
+ * reference to the device via hci_conn_get() (or the initial reference from
+ * hci_conn_add()).
+ * The hold()/drop() ref-count is known to drop below 0 sometimes, which doesn't
+ * break because nobody cares for that. But this means, we cannot use
+ * _get()/_drop() in it, but require the caller to have a valid ref (FIXME).
+ */
+
+static inline void hci_conn_get(struct hci_conn *conn)
+{
+       get_device(&conn->dev);
+}
+
+static inline void hci_conn_put(struct hci_conn *conn)
+{
+       put_device(&conn->dev);
+}
+
 static inline void hci_conn_hold(struct hci_conn *conn)
 {
        BT_DBG("hcon %p orig refcnt %d", conn, atomic_read(&conn->refcnt));
index 6b5b8e77cf0b9e51d65b1038f23d06619a00e0c9..6c7f3637972254c9bbbe0b5a9c6f007ba1ec41e2 100644 (file)
@@ -462,8 +462,7 @@ int hci_conn_del(struct hci_conn *conn)
 
        hci_dev_put(hdev);
 
-       if (conn->handle == 0)
-               kfree(conn);
+       hci_conn_put(conn);
 
        return 0;
 }
index ff38561385def476441d4fb1c5037f5fb4e9a06a..6fe15c822847319c587e3780f96094cef18047df 100644 (file)
@@ -146,7 +146,6 @@ void hci_conn_del_sysfs(struct hci_conn *conn)
        }
 
        device_del(&conn->dev);
-       put_device(&conn->dev);
 
        hci_dev_put(hdev);
 }