diff options
author | Marcel Holtmann <marcel@holtmann.org> | 2009-05-02 21:24:06 -0400 |
---|---|---|
committer | Marcel Holtmann <marcel@holtmann.org> | 2009-05-04 17:29:02 -0400 |
commit | a67e899cf38ae542d1a028ccd021f9189f76fb74 (patch) | |
tree | d0bb79ccbdd5737745e99acbc569605bc74bc446 | |
parent | 9f722c0978b04acba209f8ca1896ad05814bc3a3 (diff) |
Bluetooth: Fix issue with sysfs handling for connections
Due to a semantic changes in flush_workqueue() the current approach of
synchronizing the sysfs handling for connections doesn't work anymore. The
whole approach is actually fully broken and based on assumptions that are
no longer valid.
With the introduction of Simple Pairing support, the creation of low-level
ACL links got changed. This change invalidates the reason why in the past
two independent work queues have been used for adding/removing sysfs
devices. The adding of the actual sysfs device is now postponed until the
host controller successfully assigns an unique handle to that link. So
the real synchronization happens inside the controller and not the host.
The only left-over problem is that some internals of the sysfs device
handling are not initialized ahead of time. This leaves potential access
to invalid data and can cause various NULL pointer dereferences. To fix
this a new function makes sure that all sysfs details are initialized
when an connection attempt is made. The actual sysfs device is only
registered when the connection has been successfully established. To
avoid a race condition with the registration, the check if a device is
registered has been moved into the removal work.
As an extra protection two flush_work() calls are left in place to
make sure a previous add/del work has been completed first.
Based on a report by Marc Pignat <marc.pignat@hevs.ch>
Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
Tested-by: Justin P. Mattock <justinmattock@gmail.com>
Tested-by: Roger Quadros <ext-roger.quadros@nokia.com>
Tested-by: Marc Pignat <marc.pignat@hevs.ch>
-rw-r--r-- | include/net/bluetooth/hci_core.h | 1 | ||||
-rw-r--r-- | net/bluetooth/hci_conn.c | 2 | ||||
-rw-r--r-- | net/bluetooth/hci_sysfs.c | 74 |
3 files changed, 43 insertions, 34 deletions
diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index be5bd713d2c9..73aead222b32 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h | |||
@@ -457,6 +457,7 @@ int hci_recv_fragment(struct hci_dev *hdev, int type, void *data, int count); | |||
457 | 457 | ||
458 | int hci_register_sysfs(struct hci_dev *hdev); | 458 | int hci_register_sysfs(struct hci_dev *hdev); |
459 | void hci_unregister_sysfs(struct hci_dev *hdev); | 459 | void hci_unregister_sysfs(struct hci_dev *hdev); |
460 | void hci_conn_init_sysfs(struct hci_conn *conn); | ||
460 | void hci_conn_add_sysfs(struct hci_conn *conn); | 461 | void hci_conn_add_sysfs(struct hci_conn *conn); |
461 | void hci_conn_del_sysfs(struct hci_conn *conn); | 462 | void hci_conn_del_sysfs(struct hci_conn *conn); |
462 | 463 | ||
diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index 375f4b4f7f79..61309b26f271 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c | |||
@@ -248,6 +248,8 @@ struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, bdaddr_t *dst) | |||
248 | if (hdev->notify) | 248 | if (hdev->notify) |
249 | hdev->notify(hdev, HCI_NOTIFY_CONN_ADD); | 249 | hdev->notify(hdev, HCI_NOTIFY_CONN_ADD); |
250 | 250 | ||
251 | hci_conn_init_sysfs(conn); | ||
252 | |||
251 | tasklet_enable(&hdev->tx_task); | 253 | tasklet_enable(&hdev->tx_task); |
252 | 254 | ||
253 | return conn; | 255 | return conn; |
diff --git a/net/bluetooth/hci_sysfs.c b/net/bluetooth/hci_sysfs.c index b7c51082ddeb..582d8877078c 100644 --- a/net/bluetooth/hci_sysfs.c +++ b/net/bluetooth/hci_sysfs.c | |||
@@ -9,7 +9,7 @@ | |||
9 | struct class *bt_class = NULL; | 9 | struct class *bt_class = NULL; |
10 | EXPORT_SYMBOL_GPL(bt_class); | 10 | EXPORT_SYMBOL_GPL(bt_class); |
11 | 11 | ||
12 | static struct workqueue_struct *bluetooth; | 12 | static struct workqueue_struct *bt_workq; |
13 | 13 | ||
14 | static inline char *link_typetostr(int type) | 14 | static inline char *link_typetostr(int type) |
15 | { | 15 | { |
@@ -89,8 +89,8 @@ static void add_conn(struct work_struct *work) | |||
89 | { | 89 | { |
90 | struct hci_conn *conn = container_of(work, struct hci_conn, work_add); | 90 | struct hci_conn *conn = container_of(work, struct hci_conn, work_add); |
91 | 91 | ||
92 | /* ensure previous add/del is complete */ | 92 | /* ensure previous del is complete */ |
93 | flush_workqueue(bluetooth); | 93 | flush_work(&conn->work_del); |
94 | 94 | ||
95 | if (device_add(&conn->dev) < 0) { | 95 | if (device_add(&conn->dev) < 0) { |
96 | BT_ERR("Failed to register connection device"); | 96 | BT_ERR("Failed to register connection device"); |
@@ -98,27 +98,6 @@ static void add_conn(struct work_struct *work) | |||
98 | } | 98 | } |
99 | } | 99 | } |
100 | 100 | ||
101 | void hci_conn_add_sysfs(struct hci_conn *conn) | ||
102 | { | ||
103 | struct hci_dev *hdev = conn->hdev; | ||
104 | |||
105 | BT_DBG("conn %p", conn); | ||
106 | |||
107 | conn->dev.type = &bt_link; | ||
108 | conn->dev.class = bt_class; | ||
109 | conn->dev.parent = &hdev->dev; | ||
110 | |||
111 | dev_set_name(&conn->dev, "%s:%d", hdev->name, conn->handle); | ||
112 | |||
113 | dev_set_drvdata(&conn->dev, conn); | ||
114 | |||
115 | device_initialize(&conn->dev); | ||
116 | |||
117 | INIT_WORK(&conn->work_add, add_conn); | ||
118 | |||
119 | queue_work(bluetooth, &conn->work_add); | ||
120 | } | ||
121 | |||
122 | /* | 101 | /* |
123 | * The rfcomm tty device will possibly retain even when conn | 102 | * The rfcomm tty device will possibly retain even when conn |
124 | * is down, and sysfs doesn't support move zombie device, | 103 | * is down, and sysfs doesn't support move zombie device, |
@@ -134,8 +113,11 @@ static void del_conn(struct work_struct *work) | |||
134 | struct hci_conn *conn = container_of(work, struct hci_conn, work_del); | 113 | struct hci_conn *conn = container_of(work, struct hci_conn, work_del); |
135 | struct hci_dev *hdev = conn->hdev; | 114 | struct hci_dev *hdev = conn->hdev; |
136 | 115 | ||
137 | /* ensure previous add/del is complete */ | 116 | /* ensure previous add is complete */ |
138 | flush_workqueue(bluetooth); | 117 | flush_work(&conn->work_add); |
118 | |||
119 | if (!device_is_registered(&conn->dev)) | ||
120 | return; | ||
139 | 121 | ||
140 | while (1) { | 122 | while (1) { |
141 | struct device *dev; | 123 | struct device *dev; |
@@ -152,16 +134,40 @@ static void del_conn(struct work_struct *work) | |||
152 | hci_dev_put(hdev); | 134 | hci_dev_put(hdev); |
153 | } | 135 | } |
154 | 136 | ||
155 | void hci_conn_del_sysfs(struct hci_conn *conn) | 137 | void hci_conn_init_sysfs(struct hci_conn *conn) |
156 | { | 138 | { |
139 | struct hci_dev *hdev = conn->hdev; | ||
140 | |||
157 | BT_DBG("conn %p", conn); | 141 | BT_DBG("conn %p", conn); |
158 | 142 | ||
159 | if (!device_is_registered(&conn->dev)) | 143 | conn->dev.type = &bt_link; |
160 | return; | 144 | conn->dev.class = bt_class; |
145 | conn->dev.parent = &hdev->dev; | ||
146 | |||
147 | dev_set_drvdata(&conn->dev, conn); | ||
161 | 148 | ||
149 | device_initialize(&conn->dev); | ||
150 | |||
151 | INIT_WORK(&conn->work_add, add_conn); | ||
162 | INIT_WORK(&conn->work_del, del_conn); | 152 | INIT_WORK(&conn->work_del, del_conn); |
153 | } | ||
154 | |||
155 | void hci_conn_add_sysfs(struct hci_conn *conn) | ||
156 | { | ||
157 | struct hci_dev *hdev = conn->hdev; | ||
158 | |||
159 | BT_DBG("conn %p", conn); | ||
160 | |||
161 | dev_set_name(&conn->dev, "%s:%d", hdev->name, conn->handle); | ||
162 | |||
163 | queue_work(bt_workq, &conn->work_add); | ||
164 | } | ||
165 | |||
166 | void hci_conn_del_sysfs(struct hci_conn *conn) | ||
167 | { | ||
168 | BT_DBG("conn %p", conn); | ||
163 | 169 | ||
164 | queue_work(bluetooth, &conn->work_del); | 170 | queue_work(bt_workq, &conn->work_del); |
165 | } | 171 | } |
166 | 172 | ||
167 | static inline char *host_typetostr(int type) | 173 | static inline char *host_typetostr(int type) |
@@ -438,13 +444,13 @@ void hci_unregister_sysfs(struct hci_dev *hdev) | |||
438 | 444 | ||
439 | int __init bt_sysfs_init(void) | 445 | int __init bt_sysfs_init(void) |
440 | { | 446 | { |
441 | bluetooth = create_singlethread_workqueue("bluetooth"); | 447 | bt_workq = create_singlethread_workqueue("bluetooth"); |
442 | if (!bluetooth) | 448 | if (!bt_workq) |
443 | return -ENOMEM; | 449 | return -ENOMEM; |
444 | 450 | ||
445 | bt_class = class_create(THIS_MODULE, "bluetooth"); | 451 | bt_class = class_create(THIS_MODULE, "bluetooth"); |
446 | if (IS_ERR(bt_class)) { | 452 | if (IS_ERR(bt_class)) { |
447 | destroy_workqueue(bluetooth); | 453 | destroy_workqueue(bt_workq); |
448 | return PTR_ERR(bt_class); | 454 | return PTR_ERR(bt_class); |
449 | } | 455 | } |
450 | 456 | ||
@@ -453,7 +459,7 @@ int __init bt_sysfs_init(void) | |||
453 | 459 | ||
454 | void bt_sysfs_cleanup(void) | 460 | void bt_sysfs_cleanup(void) |
455 | { | 461 | { |
456 | destroy_workqueue(bluetooth); | 462 | destroy_workqueue(bt_workq); |
457 | 463 | ||
458 | class_destroy(bt_class); | 464 | class_destroy(bt_class); |
459 | } | 465 | } |