Commit 90855d7b authored by Marcel Holtmann's avatar Marcel Holtmann

[Bluetooth] Fix userspace breakage due missing class links

The Bluetooth adapters and connections are best presented via a class
in sysfs. The removal of the links inside the Bluetooth class broke
assumptions by userspace programs on how to find attached adapters.

This patch creates adapters and connections as part of the Bluetooth
class, but it uses different device types to distinguish them. The
userspace programs can now easily navigate in the sysfs device tree.

The unused platform device and bus have been removed to keep the
code simple and clean.
Signed-off-by: default avatarMarcel Holtmann <marcel@holtmann.org>
parent 9bfa35fe
...@@ -3,8 +3,6 @@ ...@@ -3,8 +3,6 @@
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/init.h> #include <linux/init.h>
#include <linux/platform_device.h>
#include <net/bluetooth/bluetooth.h> #include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/hci_core.h> #include <net/bluetooth/hci_core.h>
...@@ -12,10 +10,164 @@ ...@@ -12,10 +10,164 @@
#undef BT_DBG #undef BT_DBG
#define BT_DBG(D...) #define BT_DBG(D...)
#endif #endif
struct class *bt_class = NULL;
EXPORT_SYMBOL_GPL(bt_class);
static struct workqueue_struct *btaddconn; static struct workqueue_struct *btaddconn;
static struct workqueue_struct *btdelconn; static struct workqueue_struct *btdelconn;
static inline char *typetostr(int type) static inline char *link_typetostr(int type)
{
switch (type) {
case ACL_LINK:
return "ACL";
case SCO_LINK:
return "SCO";
case ESCO_LINK:
return "eSCO";
default:
return "UNKNOWN";
}
}
static ssize_t show_link_type(struct device *dev, struct device_attribute *attr, char *buf)
{
struct hci_conn *conn = dev_get_drvdata(dev);
return sprintf(buf, "%s\n", link_typetostr(conn->type));
}
static ssize_t show_link_address(struct device *dev, struct device_attribute *attr, char *buf)
{
struct hci_conn *conn = dev_get_drvdata(dev);
bdaddr_t bdaddr;
baswap(&bdaddr, &conn->dst);
return sprintf(buf, "%s\n", batostr(&bdaddr));
}
static ssize_t show_link_features(struct device *dev, struct device_attribute *attr, char *buf)
{
struct hci_conn *conn = dev_get_drvdata(dev);
return sprintf(buf, "0x%02x%02x%02x%02x%02x%02x%02x%02x\n",
conn->features[0], conn->features[1],
conn->features[2], conn->features[3],
conn->features[4], conn->features[5],
conn->features[6], conn->features[7]);
}
#define LINK_ATTR(_name,_mode,_show,_store) \
struct device_attribute link_attr_##_name = __ATTR(_name,_mode,_show,_store)
static LINK_ATTR(type, S_IRUGO, show_link_type, NULL);
static LINK_ATTR(address, S_IRUGO, show_link_address, NULL);
static LINK_ATTR(features, S_IRUGO, show_link_features, NULL);
static struct attribute *bt_link_attrs[] = {
&link_attr_type.attr,
&link_attr_address.attr,
&link_attr_features.attr,
NULL
};
static struct attribute_group bt_link_group = {
.attrs = bt_link_attrs,
};
static struct attribute_group *bt_link_groups[] = {
&bt_link_group,
NULL
};
static void bt_link_release(struct device *dev)
{
void *data = dev_get_drvdata(dev);
kfree(data);
}
static struct device_type bt_link = {
.name = "link",
.groups = bt_link_groups,
.release = bt_link_release,
};
static void add_conn(struct work_struct *work)
{
struct hci_conn *conn = container_of(work, struct hci_conn, work);
flush_workqueue(btdelconn);
if (device_add(&conn->dev) < 0) {
BT_ERR("Failed to register connection device");
return;
}
}
void hci_conn_add_sysfs(struct hci_conn *conn)
{
struct hci_dev *hdev = conn->hdev;
BT_DBG("conn %p", conn);
conn->dev.type = &bt_link;
conn->dev.class = bt_class;
conn->dev.parent = &hdev->dev;
snprintf(conn->dev.bus_id, BUS_ID_SIZE, "%s:%d",
hdev->name, conn->handle);
dev_set_drvdata(&conn->dev, conn);
device_initialize(&conn->dev);
INIT_WORK(&conn->work, add_conn);
queue_work(btaddconn, &conn->work);
}
/*
* The rfcomm tty device will possibly retain even when conn
* is down, and sysfs doesn't support move zombie device,
* so we should move the device before conn device is destroyed.
*/
static int __match_tty(struct device *dev, void *data)
{
return !strncmp(dev->bus_id, "rfcomm", 6);
}
static void del_conn(struct work_struct *work)
{
struct hci_conn *conn = container_of(work, struct hci_conn, work);
struct hci_dev *hdev = conn->hdev;
while (1) {
struct device *dev;
dev = device_find_child(&conn->dev, NULL, __match_tty);
if (!dev)
break;
device_move(dev, NULL);
put_device(dev);
}
device_del(&conn->dev);
put_device(&conn->dev);
hci_dev_put(hdev);
}
void hci_conn_del_sysfs(struct hci_conn *conn)
{
BT_DBG("conn %p", conn);
if (!device_is_registered(&conn->dev))
return;
INIT_WORK(&conn->work, del_conn);
queue_work(btdelconn, &conn->work);
}
static inline char *host_typetostr(int type)
{ {
switch (type) { switch (type) {
case HCI_VIRTUAL: case HCI_VIRTUAL:
...@@ -40,7 +192,7 @@ static inline char *typetostr(int type) ...@@ -40,7 +192,7 @@ static inline char *typetostr(int type)
static ssize_t show_type(struct device *dev, struct device_attribute *attr, char *buf) static ssize_t show_type(struct device *dev, struct device_attribute *attr, char *buf)
{ {
struct hci_dev *hdev = dev_get_drvdata(dev); struct hci_dev *hdev = dev_get_drvdata(dev);
return sprintf(buf, "%s\n", typetostr(hdev->type)); return sprintf(buf, "%s\n", host_typetostr(hdev->type));
} }
static ssize_t show_name(struct device *dev, struct device_attribute *attr, char *buf) static ssize_t show_name(struct device *dev, struct device_attribute *attr, char *buf)
...@@ -221,183 +373,62 @@ static DEVICE_ATTR(sniff_max_interval, S_IRUGO | S_IWUSR, ...@@ -221,183 +373,62 @@ static DEVICE_ATTR(sniff_max_interval, S_IRUGO | S_IWUSR,
static DEVICE_ATTR(sniff_min_interval, S_IRUGO | S_IWUSR, static DEVICE_ATTR(sniff_min_interval, S_IRUGO | S_IWUSR,
show_sniff_min_interval, store_sniff_min_interval); show_sniff_min_interval, store_sniff_min_interval);
static struct device_attribute *bt_attrs[] = { static struct attribute *bt_host_attrs[] = {
&dev_attr_type, &dev_attr_type.attr,
&dev_attr_name, &dev_attr_name.attr,
&dev_attr_class, &dev_attr_class.attr,
&dev_attr_address, &dev_attr_address.attr,
&dev_attr_features, &dev_attr_features.attr,
&dev_attr_manufacturer, &dev_attr_manufacturer.attr,
&dev_attr_hci_version, &dev_attr_hci_version.attr,
&dev_attr_hci_revision, &dev_attr_hci_revision.attr,
&dev_attr_inquiry_cache, &dev_attr_inquiry_cache.attr,
&dev_attr_idle_timeout, &dev_attr_idle_timeout.attr,
&dev_attr_sniff_max_interval, &dev_attr_sniff_max_interval.attr,
&dev_attr_sniff_min_interval, &dev_attr_sniff_min_interval.attr,
NULL NULL
}; };
static ssize_t show_conn_type(struct device *dev, struct device_attribute *attr, char *buf) static struct attribute_group bt_host_group = {
{ .attrs = bt_host_attrs,
struct hci_conn *conn = dev_get_drvdata(dev);
return sprintf(buf, "%s\n", conn->type == ACL_LINK ? "ACL" : "SCO");
}
static ssize_t show_conn_address(struct device *dev, struct device_attribute *attr, char *buf)
{
struct hci_conn *conn = dev_get_drvdata(dev);
bdaddr_t bdaddr;
baswap(&bdaddr, &conn->dst);
return sprintf(buf, "%s\n", batostr(&bdaddr));
}
static ssize_t show_conn_features(struct device *dev, struct device_attribute *attr, char *buf)
{
struct hci_conn *conn = dev_get_drvdata(dev);
return sprintf(buf, "0x%02x%02x%02x%02x%02x%02x%02x%02x\n",
conn->features[0], conn->features[1],
conn->features[2], conn->features[3],
conn->features[4], conn->features[5],
conn->features[6], conn->features[7]);
}
#define CONN_ATTR(_name,_mode,_show,_store) \
struct device_attribute conn_attr_##_name = __ATTR(_name,_mode,_show,_store)
static CONN_ATTR(type, S_IRUGO, show_conn_type, NULL);
static CONN_ATTR(address, S_IRUGO, show_conn_address, NULL);
static CONN_ATTR(features, S_IRUGO, show_conn_features, NULL);
static struct device_attribute *conn_attrs[] = {
&conn_attr_type,
&conn_attr_address,
&conn_attr_features,
NULL
}; };
struct class *bt_class = NULL; static struct attribute_group *bt_host_groups[] = {
EXPORT_SYMBOL_GPL(bt_class); &bt_host_group,
NULL
static struct bus_type bt_bus = {
.name = "bluetooth",
}; };
static struct platform_device *bt_platform; static void bt_host_release(struct device *dev)
static void bt_release(struct device *dev)
{ {
void *data = dev_get_drvdata(dev); void *data = dev_get_drvdata(dev);
kfree(data); kfree(data);
} }
static void add_conn(struct work_struct *work) static struct device_type bt_host = {
{ .name = "host",
struct hci_conn *conn = container_of(work, struct hci_conn, work); .groups = bt_host_groups,
int i; .release = bt_host_release,
};
flush_workqueue(btdelconn);
if (device_add(&conn->dev) < 0) {
BT_ERR("Failed to register connection device");
return;
}
for (i = 0; conn_attrs[i]; i++)
if (device_create_file(&conn->dev, conn_attrs[i]) < 0)
BT_ERR("Failed to create connection attribute");
}
void hci_conn_add_sysfs(struct hci_conn *conn)
{
struct hci_dev *hdev = conn->hdev;
BT_DBG("conn %p", conn);
conn->dev.bus = &bt_bus;
conn->dev.parent = &hdev->dev;
conn->dev.release = bt_release;
snprintf(conn->dev.bus_id, BUS_ID_SIZE, "%s:%d",
hdev->name, conn->handle);
dev_set_drvdata(&conn->dev, conn);
device_initialize(&conn->dev);
INIT_WORK(&conn->work, add_conn);
queue_work(btaddconn, &conn->work);
}
/*
* The rfcomm tty device will possibly retain even when conn
* is down, and sysfs doesn't support move zombie device,
* so we should move the device before conn device is destroyed.
*/
static int __match_tty(struct device *dev, void *data)
{
return !strncmp(dev->bus_id, "rfcomm", 6);
}
static void del_conn(struct work_struct *work)
{
struct hci_conn *conn = container_of(work, struct hci_conn, work);
struct hci_dev *hdev = conn->hdev;
while (1) {
struct device *dev;
dev = device_find_child(&conn->dev, NULL, __match_tty);
if (!dev)
break;
device_move(dev, NULL);
put_device(dev);
}
device_del(&conn->dev);
put_device(&conn->dev);
hci_dev_put(hdev);
}
void hci_conn_del_sysfs(struct hci_conn *conn)
{
BT_DBG("conn %p", conn);
if (!device_is_registered(&conn->dev))
return;
INIT_WORK(&conn->work, del_conn);
queue_work(btdelconn, &conn->work);
}
int hci_register_sysfs(struct hci_dev *hdev) int hci_register_sysfs(struct hci_dev *hdev)
{ {
struct device *dev = &hdev->dev; struct device *dev = &hdev->dev;
unsigned int i;
int err; int err;
BT_DBG("%p name %s type %d", hdev, hdev->name, hdev->type); BT_DBG("%p name %s type %d", hdev, hdev->name, hdev->type);
dev->bus = &bt_bus; dev->type = &bt_host;
dev->class = bt_class;
dev->parent = hdev->parent; dev->parent = hdev->parent;
strlcpy(dev->bus_id, hdev->name, BUS_ID_SIZE); strlcpy(dev->bus_id, hdev->name, BUS_ID_SIZE);
dev->release = bt_release;
dev_set_drvdata(dev, hdev); dev_set_drvdata(dev, hdev);
err = device_register(dev); err = device_register(dev);
if (err < 0) if (err < 0)
return err; return err;
for (i = 0; bt_attrs[i]; i++)
if (device_create_file(dev, bt_attrs[i]) < 0)
BT_ERR("Failed to create device attribute");
return 0; return 0;
} }
...@@ -410,59 +441,30 @@ void hci_unregister_sysfs(struct hci_dev *hdev) ...@@ -410,59 +441,30 @@ void hci_unregister_sysfs(struct hci_dev *hdev)
int __init bt_sysfs_init(void) int __init bt_sysfs_init(void)
{ {
int err;
btaddconn = create_singlethread_workqueue("btaddconn"); btaddconn = create_singlethread_workqueue("btaddconn");
if (!btaddconn) { if (!btaddconn)
err = -ENOMEM; return -ENOMEM;
goto out;
}
btdelconn = create_singlethread_workqueue("btdelconn"); btdelconn = create_singlethread_workqueue("btdelconn");
if (!btdelconn) { if (!btdelconn) {
err = -ENOMEM; destroy_workqueue(btaddconn);
goto out_del; return -ENOMEM;
}
bt_platform = platform_device_register_simple("bluetooth", -1, NULL, 0);
if (IS_ERR(bt_platform)) {
err = PTR_ERR(bt_platform);
goto out_platform;
} }
err = bus_register(&bt_bus);
if (err < 0)
goto out_bus;
bt_class = class_create(THIS_MODULE, "bluetooth"); bt_class = class_create(THIS_MODULE, "bluetooth");
if (IS_ERR(bt_class)) { if (IS_ERR(bt_class)) {
err = PTR_ERR(bt_class); destroy_workqueue(btdelconn);
goto out_class; destroy_workqueue(btaddconn);
return PTR_ERR(bt_class);
} }
return 0; return 0;
out_class:
bus_unregister(&bt_bus);
out_bus:
platform_device_unregister(bt_platform);
out_platform:
destroy_workqueue(btdelconn);
out_del:
destroy_workqueue(btaddconn);
out:
return err;
} }
void bt_sysfs_cleanup(void) void bt_sysfs_cleanup(void)
{ {
destroy_workqueue(btaddconn); destroy_workqueue(btaddconn);
destroy_workqueue(btdelconn); destroy_workqueue(btdelconn);
class_destroy(bt_class); class_destroy(bt_class);
bus_unregister(&bt_bus);
platform_device_unregister(bt_platform);
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment