[PATCH 12/16] Bluetooth: add hci_conn_user sub-modules

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Several sub-modules like HIDP, rfcomm, ... need to track HCI connections.
The hci_conn->dev object is used as parent for sysfs devices so the
sub-modules need to be notified when the hci_conn object is removed from
sysfs.

This patch introduces hci_conn_user objects which contain a "probe" and
"remove" callback. You can register these on any hci_conn object and if it
is active, the "probe" callback will get called. Otherwise, an error is
returned.

The hci_conn object will call your "remove" callback directly before it is
removed from user-space. This allows you to remove your submodules
_before_ the hci_conn object is removed.

At any time you can asynchronously unregister your hci_conn_user object if
your submodule vanishes before the hci_conn object does.

There is no way around hci_conn_user. If we want wire-protocols in the
kernel, we always want the hci_conn object as parent in the sysfs tree. We
cannot use a channel here since we might need multiple channels for a
single protocol.
But the problem is, we _must_ get notified when an hci_conn object is
removed. We cannot use reference-counting for object-removal! This is not
how it works. If a hardware is removed, we should immediately remove the
object from sysfs. Any other behavior would be inconsistent with the rest
of the system. Also note that device_del() might sleep, but it doesn't
wait for user-space or block very long. It only _unlinks_ the object from
sysfs and the whole device-tree. Everything else is handled by ref-counts!
This is exactly what the other sub-modules must do: unlink their devices
when the "remove" hci_conn_user callback is called. They should not do any
cleanup or synchronous shutdowns.

Signed-off-by: David Herrmann <dh.herrmann@xxxxxxxxx>
---
 include/net/bluetooth/hci_core.h | 11 +++++++
 net/bluetooth/hci_conn.c         | 66 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 77 insertions(+)

diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
index fa8fe72..3779568 100644
--- a/include/net/bluetooth/hci_core.h
+++ b/include/net/bluetooth/hci_core.h
@@ -296,6 +296,7 @@ struct hci_conn {
 	struct list_head list;
 
 	atomic_t	refcnt;
+	struct list_head users;
 
 	bdaddr_t	dst;
 	__u8		dst_type;
@@ -352,6 +353,12 @@ struct hci_conn {
 	void (*disconn_cfm_cb)	(struct hci_conn *conn, u8 reason);
 };
 
+struct hci_conn_user {
+	struct list_head list;
+	int (*probe) (struct hci_conn *conn, struct hci_conn_user *user);
+	void (*remove) (struct hci_conn *conn, struct hci_conn_user *user);
+};
+
 struct hci_chan {
 	struct list_head list;
 	__u16 handle;
@@ -593,6 +600,10 @@ int hci_conn_switch_role(struct hci_conn *conn, __u8 role);
 
 void hci_conn_enter_active_mode(struct hci_conn *conn, __u8 force_active);
 
+int hci_conn_register_user(struct hci_conn *conn, struct hci_conn_user *user);
+void hci_conn_unregister_user(struct hci_conn *conn,
+			      struct hci_conn_user *user);
+
 /*
  * 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,
diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c
index 75b75a8..5ba5e29 100644
--- a/net/bluetooth/hci_conn.c
+++ b/net/bluetooth/hci_conn.c
@@ -342,6 +342,70 @@ static void hci_conn_auto_accept(unsigned long arg)
 		     &conn->dst);
 }
 
+int hci_conn_register_user(struct hci_conn *conn, struct hci_conn_user *user)
+{
+	struct hci_dev *hdev = conn->hdev;
+	int ret;
+
+	hci_dev_lock(hdev);
+
+	if (user->list.next || user->list.prev) {
+		ret = -EINVAL;
+		goto out_unlock;
+	}
+
+	if (!device_is_registered(&conn->dev)) {
+		ret = -ENODEV;
+		goto out_unlock;
+	}
+
+	ret = user->probe(conn, user);
+	if (ret)
+		goto out_unlock;
+
+	list_add(&user->list, &conn->users);
+	ret = 0;
+
+out_unlock:
+	hci_dev_unlock(hdev);
+	return ret;
+}
+EXPORT_SYMBOL(hci_conn_register_user);
+
+void hci_conn_unregister_user(struct hci_conn *conn,
+			      struct hci_conn_user *user)
+{
+	struct hci_dev *hdev = conn->hdev;
+
+	hci_dev_lock(hdev);
+
+	if (!user->list.next || !user->list.prev)
+		goto out_unlock;
+
+	list_del(&user->list);
+	user->list.next = NULL;
+	user->list.prev = NULL;
+	user->remove(conn, user);
+
+out_unlock:
+	hci_dev_unlock(hdev);
+}
+EXPORT_SYMBOL(hci_conn_unregister_user);
+
+static void hci_conn_unregister_all_users(struct hci_conn *conn)
+{
+	struct hci_conn_user *user;
+
+	while (!list_empty(&conn->users)) {
+		user = list_first_entry(&conn->users, struct hci_conn_user,
+					list);
+		list_del(&user->list);
+		user->list.next = NULL;
+		user->list.prev = NULL;
+		user->remove(conn, user);
+	}
+}
+
 struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, bdaddr_t *dst)
 {
 	struct hci_conn *conn;
@@ -384,6 +448,7 @@ struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, bdaddr_t *dst)
 	skb_queue_head_init(&conn->data_q);
 
 	INIT_LIST_HEAD(&conn->chan_list);
+	INIT_LIST_HEAD(&conn->users);
 
 	INIT_DELAYED_WORK(&conn->disc_work, hci_conn_timeout);
 	setup_timer(&conn->idle_timer, hci_conn_idle, (unsigned long)conn);
@@ -446,6 +511,7 @@ int hci_conn_del(struct hci_conn *conn)
 
 	skb_queue_purge(&conn->data_q);
 
+	hci_conn_unregister_all_users(conn);
 	hci_conn_del_sysfs(conn);
 
 	hci_dev_put(hdev);
-- 
1.8.1.4

--
To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Bluez Devel]     [Linux Wireless Networking]     [Linux Wireless Personal Area Networking]     [Linux ATH6KL]     [Linux USB Devel]     [Linux Media Drivers]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Big List of Linux Books]

  Powered by Linux