From 0878b6667f28772aa7d6b735abff53efc7bf6d91 Mon Sep 17 00:00:00 2001 From: Marcel Holtmann Date: Sat, 5 May 2007 00:35:59 +0200 Subject: [Bluetooth] Fix L2CAP and HCI setsockopt() information leaks The L2CAP and HCI setsockopt() implementations have a small information leak that makes it possible to leak kernel stack memory to userspace. If the optlen parameter is 0, no data will be copied by copy_from_user(), but the uninitialized stack buffer will be read and stored later. A call to getsockopt() can now retrieve the leaked information. To fix this problem the stack buffer given to copy_from_user() must be initialized with the current settings. Signed-off-by: Marcel Holtmann --- net/bluetooth/hci_sock.c | 9 +++++++++ net/bluetooth/l2cap.c | 6 ++++++ 2 files changed, 15 insertions(+) (limited to 'net') diff --git a/net/bluetooth/hci_sock.c b/net/bluetooth/hci_sock.c index 832b5f44be5c..bfc9a35bad33 100644 --- a/net/bluetooth/hci_sock.c +++ b/net/bluetooth/hci_sock.c @@ -499,6 +499,15 @@ static int hci_sock_setsockopt(struct socket *sock, int level, int optname, char break; case HCI_FILTER: + { + struct hci_filter *f = &hci_pi(sk)->filter; + + uf.type_mask = f->type_mask; + uf.opcode = f->opcode; + uf.event_mask[0] = *((u32 *) f->event_mask + 0); + uf.event_mask[1] = *((u32 *) f->event_mask + 1); + } + len = min_t(unsigned int, len, sizeof(uf)); if (copy_from_user(&uf, optval, len)) { err = -EFAULT; diff --git a/net/bluetooth/l2cap.c b/net/bluetooth/l2cap.c index a5867879b615..a59b1fb63b76 100644 --- a/net/bluetooth/l2cap.c +++ b/net/bluetooth/l2cap.c @@ -954,11 +954,17 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname, ch switch (optname) { case L2CAP_OPTIONS: + opts.imtu = l2cap_pi(sk)->imtu; + opts.omtu = l2cap_pi(sk)->omtu; + opts.flush_to = l2cap_pi(sk)->flush_to; + opts.mode = 0x00; + len = min_t(unsigned int, sizeof(opts), optlen); if (copy_from_user((char *) &opts, optval, len)) { err = -EFAULT; break; } + l2cap_pi(sk)->imtu = opts.imtu; l2cap_pi(sk)->omtu = opts.omtu; break; -- cgit v1.2.2 From 53c1d4b0b22243c093ded25aaa01c8ff8ab6e6b3 Mon Sep 17 00:00:00 2001 From: Marcel Holtmann Date: Sat, 5 May 2007 00:36:03 +0200 Subject: [Bluetooth] Attach host adapters to the Bluetooth bus The Bluetooth host adapters are attached to the Bluetooth class and the low-level connections are children of these class devices. Having class devices as parent of bus devices breaks a lot of reasonable assumptions about sysfs. The host adapters should be attached to the Bluetooth bus to simplify the dependency resolving. For compatibility an additional symlink from the Bluetooth class will be used. Signed-off-by: Marcel Holtmann --- net/bluetooth/hci_sysfs.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'net') diff --git a/net/bluetooth/hci_sysfs.c b/net/bluetooth/hci_sysfs.c index 801d687ea4ef..359e3440cf29 100644 --- a/net/bluetooth/hci_sysfs.c +++ b/net/bluetooth/hci_sysfs.c @@ -305,7 +305,7 @@ int hci_register_sysfs(struct hci_dev *hdev) BT_DBG("%p name %s type %d", hdev, hdev->name, hdev->type); - dev->class = bt_class; + dev->bus = &bt_bus; dev->parent = hdev->parent; strlcpy(dev->bus_id, hdev->name, BUS_ID_SIZE); @@ -322,6 +322,10 @@ int hci_register_sysfs(struct hci_dev *hdev) if (device_create_file(dev, bt_attrs[i]) < 0) BT_ERR("Failed to create device attribute"); + if (sysfs_create_link(&bt_class->subsys.kset.kobj, + &dev->kobj, kobject_name(&dev->kobj)) < 0) + BT_ERR("Failed to create class symlink"); + return 0; } @@ -329,6 +333,9 @@ void hci_unregister_sysfs(struct hci_dev *hdev) { BT_DBG("%p name %s type %d", hdev, hdev->name, hdev->type); + sysfs_remove_link(&bt_class->subsys.kset.kobj, + kobject_name(&hdev->dev.kobj)); + device_del(&hdev->dev); } -- cgit v1.2.2 From 48db9ca4f2ac9f39eb90ccb12ad3ca7b645a552c Mon Sep 17 00:00:00 2001 From: Marcel Holtmann Date: Sat, 5 May 2007 00:36:06 +0200 Subject: [Bluetooth] Use in-kernel sockets API The kernel provides a new convenient way to access the sockets API for in-kernel users. It is a good idea to actually use it. Signed-off-by: Marcel Holtmann --- net/bluetooth/rfcomm/core.c | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) (limited to 'net') diff --git a/net/bluetooth/rfcomm/core.c b/net/bluetooth/rfcomm/core.c index fe7df90eb707..71a72fcb2b54 100644 --- a/net/bluetooth/rfcomm/core.c +++ b/net/bluetooth/rfcomm/core.c @@ -622,7 +622,7 @@ static struct rfcomm_session *rfcomm_session_create(bdaddr_t *src, bdaddr_t *dst bacpy(&addr.l2_bdaddr, src); addr.l2_family = AF_BLUETOOTH; addr.l2_psm = 0; - *err = sock->ops->bind(sock, (struct sockaddr *) &addr, sizeof(addr)); + *err = kernel_bind(sock, (struct sockaddr *) &addr, sizeof(addr)); if (*err < 0) goto failed; @@ -643,7 +643,7 @@ static struct rfcomm_session *rfcomm_session_create(bdaddr_t *src, bdaddr_t *dst bacpy(&addr.l2_bdaddr, dst); addr.l2_family = AF_BLUETOOTH; addr.l2_psm = htobs(RFCOMM_PSM); - *err = sock->ops->connect(sock, (struct sockaddr *) &addr, sizeof(addr), O_NONBLOCK); + *err = kernel_connect(sock, (struct sockaddr *) &addr, sizeof(addr), O_NONBLOCK); if (*err == 0 || *err == -EINPROGRESS) return s; @@ -1757,19 +1757,12 @@ static inline void rfcomm_accept_connection(struct rfcomm_session *s) BT_DBG("session %p", s); - if (sock_create_lite(PF_BLUETOOTH, sock->type, BTPROTO_L2CAP, &nsock)) + err = kernel_accept(sock, &nsock, O_NONBLOCK); + if (err < 0) return; - nsock->ops = sock->ops; - __module_get(nsock->ops->owner); - err = sock->ops->accept(sock, nsock, O_NONBLOCK); - if (err < 0) { - sock_release(nsock); - return; - } - /* Set our callbacks */ nsock->sk->sk_data_ready = rfcomm_l2data_ready; nsock->sk->sk_state_change = rfcomm_l2state_change; @@ -1885,7 +1878,7 @@ static int rfcomm_add_listener(bdaddr_t *ba) bacpy(&addr.l2_bdaddr, ba); addr.l2_family = AF_BLUETOOTH; addr.l2_psm = htobs(RFCOMM_PSM); - err = sock->ops->bind(sock, (struct sockaddr *) &addr, sizeof(addr)); + err = kernel_bind(sock, (struct sockaddr *) &addr, sizeof(addr)); if (err < 0) { BT_ERR("Bind failed %d", err); goto failed; @@ -1898,7 +1891,7 @@ static int rfcomm_add_listener(bdaddr_t *ba) release_sock(sk); /* Start listening on the socket */ - err = sock->ops->listen(sock, 10); + err = kernel_listen(sock, 10); if (err) { BT_ERR("Listen failed %d", err); goto failed; -- cgit v1.2.2 From 77f2a45fa1ba33147fd6cc8ae546188504a822cd Mon Sep 17 00:00:00 2001 From: Marcel Holtmann Date: Sat, 5 May 2007 00:36:10 +0200 Subject: [Bluetooth] Check that device is in rfcomm_dev_list before deleting If RFCOMM_RELEASE_ONHUP flag is on and rfcomm_release_dev is called before connection is closed, rfcomm_dev is deleted twice from the rfcomm_dev_list and refcount is messed up. This patch adds a check before deleting device that the device actually is listed. Signed-off-by: Ville Tervo Signed-off-by: Marcel Holtmann --- net/bluetooth/rfcomm/tty.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'net') diff --git a/net/bluetooth/rfcomm/tty.c b/net/bluetooth/rfcomm/tty.c index 9a7a44fc721f..b2b1cceb102a 100644 --- a/net/bluetooth/rfcomm/tty.c +++ b/net/bluetooth/rfcomm/tty.c @@ -517,9 +517,10 @@ static void rfcomm_dev_state_change(struct rfcomm_dlc *dlc, int err) if (dlc->state == BT_CLOSED) { if (!dev->tty) { if (test_bit(RFCOMM_RELEASE_ONHUP, &dev->flags)) { - rfcomm_dev_hold(dev); - rfcomm_dev_del(dev); + if (rfcomm_dev_get(dev->id) == NULL) + return; + rfcomm_dev_del(dev); /* We have to drop DLC lock here, otherwise rfcomm_dev_put() will dead lock if it's the last reference. */ @@ -974,8 +975,12 @@ static void rfcomm_tty_hangup(struct tty_struct *tty) rfcomm_tty_flush_buffer(tty); - if (test_bit(RFCOMM_RELEASE_ONHUP, &dev->flags)) + if (test_bit(RFCOMM_RELEASE_ONHUP, &dev->flags)) { + if (rfcomm_dev_get(dev->id) == NULL) + return; rfcomm_dev_del(dev); + rfcomm_dev_put(dev); + } } static int rfcomm_tty_read_proc(char *buf, char **start, off_t offset, int len, int *eof, void *unused) -- cgit v1.2.2 From 9cf5b0ea3a7f1432c61029f7aaf4b8b338628884 Mon Sep 17 00:00:00 2001 From: Marcel Holtmann Date: Sat, 5 May 2007 00:36:13 +0200 Subject: [Bluetooth] Disconnect L2CAP connection after last RFCOMM DLC The RFCOMM specification says that the device closing the last DLC on a particular session is responsible for closing the multiplexer by closing the corresponding L2CAP channel. Signed-off-by: Ville Tervo Signed-off-by: Marcel Holtmann --- net/bluetooth/rfcomm/core.c | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'net') diff --git a/net/bluetooth/rfcomm/core.c b/net/bluetooth/rfcomm/core.c index 71a72fcb2b54..52e04df323ea 100644 --- a/net/bluetooth/rfcomm/core.c +++ b/net/bluetooth/rfcomm/core.c @@ -1058,6 +1058,12 @@ static int rfcomm_recv_ua(struct rfcomm_session *s, u8 dlci) case BT_DISCONN: d->state = BT_CLOSED; __rfcomm_dlc_close(d, 0); + + if (list_empty(&s->dlcs)) { + s->state = BT_DISCONN; + rfcomm_send_disc(s, 0); + } + break; } } else { @@ -1067,6 +1073,10 @@ static int rfcomm_recv_ua(struct rfcomm_session *s, u8 dlci) s->state = BT_CONNECTED; rfcomm_process_connect(s); break; + + case BT_DISCONN: + rfcomm_session_put(s); + break; } } return 0; -- cgit v1.2.2 From 2396a22e0989df6038996506bfbf7a57f116c299 Mon Sep 17 00:00:00 2001 From: Josef 'Jeff' Sipek Date: Mon, 7 May 2007 00:33:18 -0700 Subject: [NET] net/core: Fix error handling Upon failure to register "ptype" procfs entry, "softnet_stat" was not removed, and an incorrect attempt was made to remove the "ptype" entry. Signed-off-by: Josef 'Jeff' Sipek Signed-off-by: David S. Miller --- net/core/dev.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'net') diff --git a/net/core/dev.c b/net/core/dev.c index f27d4ab181e6..4317c1be4d3f 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -2377,9 +2377,9 @@ static int __init dev_proc_init(void) out: return rc; out_softnet: - proc_net_remove("softnet_stat"); -out_dev2: proc_net_remove("ptype"); +out_dev2: + proc_net_remove("softnet_stat"); out_dev: proc_net_remove("dev"); goto out; -- cgit v1.2.2 From cf4328cd949c2086091c62c5685f1580fe9b55e4 Mon Sep 17 00:00:00 2001 From: Ivo van Doorn Date: Mon, 7 May 2007 00:34:20 -0700 Subject: [NET]: rfkill: add support for input key to control wireless radio The RF kill patch that provides infrastructure for implementing switches controlling radio states on various network and other cards. [dtor@insightbb.com: address review comments] [akpm@linux-foundation.org: cleanups, build fixes] Signed-off-by: Ivo van Doorn Signed-off-by: Andrew Morton Signed-off-by: Dmitry Torokhov Signed-off-by: David S. Miller --- net/Kconfig | 2 + net/Makefile | 1 + net/rfkill/Kconfig | 24 +++ net/rfkill/Makefile | 6 + net/rfkill/rfkill-input.c | 174 ++++++++++++++++++++ net/rfkill/rfkill.c | 407 ++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 614 insertions(+) create mode 100644 net/rfkill/Kconfig create mode 100644 net/rfkill/Makefile create mode 100644 net/rfkill/rfkill-input.c create mode 100644 net/rfkill/rfkill.c (limited to 'net') diff --git a/net/Kconfig b/net/Kconfig index 5b2227813923..caeacd16656a 100644 --- a/net/Kconfig +++ b/net/Kconfig @@ -225,6 +225,8 @@ source "net/ieee80211/Kconfig" endmenu +source "net/rfkill/Kconfig" + endif # if NET endmenu # Networking diff --git a/net/Makefile b/net/Makefile index 9fdb60c2e4a1..34e5b2d7f877 100644 --- a/net/Makefile +++ b/net/Makefile @@ -51,6 +51,7 @@ obj-$(CONFIG_IEEE80211) += ieee80211/ obj-$(CONFIG_TIPC) += tipc/ obj-$(CONFIG_NETLABEL) += netlabel/ obj-$(CONFIG_IUCV) += iucv/ +obj-$(CONFIG_RFKILL) += rfkill/ ifeq ($(CONFIG_NET),y) obj-$(CONFIG_SYSCTL) += sysctl_net.o diff --git a/net/rfkill/Kconfig b/net/rfkill/Kconfig new file mode 100644 index 000000000000..8b31759ee8b0 --- /dev/null +++ b/net/rfkill/Kconfig @@ -0,0 +1,24 @@ +# +# RF switch subsystem configuration +# +menuconfig RFKILL + tristate "RF switch subsystem support" + help + Say Y here if you want to have control over RF switches + found on many WiFi, Bluetooth and IRDA cards. + + To compile this driver as a module, choose M here: the + module will be called rfkill. + +config RFKILL_INPUT + tristate "Input layer to RF switch connector" + depends on RFKILL && INPUT + help + Say Y here if you want kernel automatically toggle state + of RF switches on and off when user presses appropriate + button or a key on the keyboard. Without this module you + need a some kind of userspace application to control + state of the switches. + + To compile this driver as a module, choose M here: the + module will be called rfkill-input. diff --git a/net/rfkill/Makefile b/net/rfkill/Makefile new file mode 100644 index 000000000000..b38c430be057 --- /dev/null +++ b/net/rfkill/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for the RF switch subsystem. +# + +obj-$(CONFIG_RFKILL) += rfkill.o +obj-$(CONFIG_RFKILL_INPUT) += rfkill-input.o diff --git a/net/rfkill/rfkill-input.c b/net/rfkill/rfkill-input.c new file mode 100644 index 000000000000..e5c840c30284 --- /dev/null +++ b/net/rfkill/rfkill-input.c @@ -0,0 +1,174 @@ +/* + * Input layer to RF Kill interface connector + * + * Copyright (c) 2007 Dmitry Torokhov + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Dmitry Torokhov "); +MODULE_DESCRIPTION("Input layer to RF switch connector"); +MODULE_LICENSE("GPL"); + +struct rfkill_task { + struct work_struct work; + enum rfkill_type type; + struct mutex mutex; /* ensures that task is serialized */ + spinlock_t lock; /* for accessing last and desired state */ + unsigned long last; /* last schedule */ + enum rfkill_state desired_state; /* on/off */ + enum rfkill_state current_state; /* on/off */ +}; + +static void rfkill_task_handler(struct work_struct *work) +{ + struct rfkill_task *task = container_of(work, struct rfkill_task, work); + enum rfkill_state state; + + mutex_lock(&task->mutex); + + /* + * Use temp variable to fetch desired state to keep it + * consistent even if rfkill_schedule_toggle() runs in + * another thread or interrupts us. + */ + state = task->desired_state; + + if (state != task->current_state) { + rfkill_switch_all(task->type, state); + task->current_state = state; + } + + mutex_unlock(&task->mutex); +} + +static void rfkill_schedule_toggle(struct rfkill_task *task) +{ + unsigned int flags; + + spin_lock_irqsave(&task->lock, flags); + + if (time_after(jiffies, task->last + msecs_to_jiffies(200))) { + task->desired_state = !task->desired_state; + task->last = jiffies; + schedule_work(&task->work); + } + + spin_unlock_irqrestore(&task->lock, flags); +} + +#define DEFINE_RFKILL_TASK(n, t) \ + struct rfkill_task n = { \ + .work = __WORK_INITIALIZER(n.work, \ + rfkill_task_handler), \ + .type = t, \ + .mutex = __MUTEX_INITIALIZER(n.mutex), \ + .lock = __SPIN_LOCK_UNLOCKED(n.lock), \ + .desired_state = RFKILL_STATE_ON, \ + .current_state = RFKILL_STATE_ON, \ + } + +static DEFINE_RFKILL_TASK(rfkill_wlan, RFKILL_TYPE_WLAN); +static DEFINE_RFKILL_TASK(rfkill_bt, RFKILL_TYPE_BLUETOOTH); + +static void rfkill_event(struct input_handle *handle, unsigned int type, + unsigned int code, int down) +{ + if (type == EV_KEY && down == 1) { + switch (code) { + case KEY_WLAN: + rfkill_schedule_toggle(&rfkill_wlan); + break; + case KEY_BLUETOOTH: + rfkill_schedule_toggle(&rfkill_bt); + break; + default: + break; + } + } +} + +static int rfkill_connect(struct input_handler *handler, struct input_dev *dev, + const struct input_device_id *id) +{ + struct input_handle *handle; + int error; + + handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL); + if (!handle) + return -ENOMEM; + + handle->dev = dev; + handle->handler = handler; + handle->name = "rfkill"; + + error = input_register_handle(handle); + if (error) + goto err_free_handle; + + error = input_open_device(handle); + if (error) + goto err_unregister_handle; + + return 0; + + err_unregister_handle: + input_unregister_handle(handle); + err_free_handle: + kfree(handle); + return error; +} + +static void rfkill_disconnect(struct input_handle *handle) +{ + input_close_device(handle); + input_unregister_handle(handle); + kfree(handle); +} + +static const struct input_device_id rfkill_ids[] = { + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT, + .evbit = { BIT(EV_KEY) }, + .keybit = { [LONG(KEY_WLAN)] = BIT(KEY_WLAN) }, + }, + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT, + .evbit = { BIT(EV_KEY) }, + .keybit = { [LONG(KEY_BLUETOOTH)] = BIT(KEY_BLUETOOTH) }, + }, + { } +}; + +static struct input_handler rfkill_handler = { + .event = rfkill_event, + .connect = rfkill_connect, + .disconnect = rfkill_disconnect, + .name = "rfkill", + .id_table = rfkill_ids, +}; + +static int __init rfkill_handler_init(void) +{ + return input_register_handler(&rfkill_handler); +} + +static void __exit rfkill_handler_exit(void) +{ + input_unregister_handler(&rfkill_handler); + flush_scheduled_work(); +} + +module_init(rfkill_handler_init); +module_exit(rfkill_handler_exit); diff --git a/net/rfkill/rfkill.c b/net/rfkill/rfkill.c new file mode 100644 index 000000000000..a973603e3880 --- /dev/null +++ b/net/rfkill/rfkill.c @@ -0,0 +1,407 @@ +/* + * Copyright (C) 2006 Ivo van Doorn + * Copyright (C) 2007 Dmitry Torokhov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Ivo van Doorn "); +MODULE_VERSION("1.0"); +MODULE_DESCRIPTION("RF switch support"); +MODULE_LICENSE("GPL"); + +static LIST_HEAD(rfkill_list); /* list of registered rf switches */ +static DEFINE_MUTEX(rfkill_mutex); + +static enum rfkill_state rfkill_states[RFKILL_TYPE_MAX]; + +static int rfkill_toggle_radio(struct rfkill *rfkill, + enum rfkill_state state) +{ + int retval; + + retval = mutex_lock_interruptible(&rfkill->mutex); + if (retval) + return retval; + + if (state != rfkill->state) { + retval = rfkill->toggle_radio(rfkill->data, state); + if (!retval) + rfkill->state = state; + } + + mutex_unlock(&rfkill->mutex); + return retval; +} + +/** + * rfkill_switch_all - Toggle state of all switches of given type + * @type: type of interfaces to be affeceted + * @state: the new state + * + * This function toggles state of all switches of given type unless + * a specific switch is claimed by userspace in which case it is + * left alone. + */ + +void rfkill_switch_all(enum rfkill_type type, enum rfkill_state state) +{ + struct rfkill *rfkill; + + mutex_lock(&rfkill_mutex); + + rfkill_states[type] = state; + + list_for_each_entry(rfkill, &rfkill_list, node) { + if (!rfkill->user_claim) + rfkill_toggle_radio(rfkill, state); + } + + mutex_unlock(&rfkill_mutex); +} +EXPORT_SYMBOL(rfkill_switch_all); + +static ssize_t rfkill_name_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct rfkill *rfkill = to_rfkill(dev); + + return sprintf(buf, "%s\n", rfkill->name); +} + +static ssize_t rfkill_type_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct rfkill *rfkill = to_rfkill(dev); + const char *type; + + switch (rfkill->type) { + case RFKILL_TYPE_WLAN: + type = "wlan"; + break; + case RFKILL_TYPE_BLUETOOTH: + type = "bluetooth"; + break; + case RFKILL_TYPE_IRDA: + type = "irda"; + break; + default: + BUG(); + } + + return sprintf(buf, "%s\n", type); +} + +static ssize_t rfkill_state_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct rfkill *rfkill = to_rfkill(dev); + + return sprintf(buf, "%d\n", rfkill->state); +} + +static ssize_t rfkill_state_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct rfkill *rfkill = to_rfkill(dev); + unsigned int state = simple_strtoul(buf, NULL, 0); + int error; + + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + + error = rfkill_toggle_radio(rfkill, + state ? RFKILL_STATE_ON : RFKILL_STATE_OFF); + if (error) + return error; + + return count; +} + +static ssize_t rfkill_claim_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct rfkill *rfkill = to_rfkill(dev); + + return sprintf(buf, "%d", rfkill->user_claim); +} + +static ssize_t rfkill_claim_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct rfkill *rfkill = to_rfkill(dev); + bool claim = !!simple_strtoul(buf, NULL, 0); + int error; + + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + + /* + * Take the global lock to make sure the kernel is not in + * the middle of rfkill_switch_all + */ + error = mutex_lock_interruptible(&rfkill_mutex); + if (error) + return error; + + if (rfkill->user_claim != claim) { + if (!claim) + rfkill_toggle_radio(rfkill, + rfkill_states[rfkill->type]); + rfkill->user_claim = claim; + } + + mutex_unlock(&rfkill_mutex); + + return count; +} + +static struct device_attribute rfkill_dev_attrs[] = { + __ATTR(name, S_IRUGO, rfkill_name_show, NULL), + __ATTR(type, S_IRUGO, rfkill_type_show, NULL), + __ATTR(state, S_IRUGO, rfkill_state_show, rfkill_state_store), + __ATTR(claim, S_IRUGO|S_IWUSR, rfkill_claim_show, rfkill_claim_store), + __ATTR_NULL +}; + +static void rfkill_release(struct device *dev) +{ + struct rfkill *rfkill = to_rfkill(dev); + + kfree(rfkill); + module_put(THIS_MODULE); +} + +#ifdef CONFIG_PM +static int rfkill_suspend(struct device *dev, pm_message_t state) +{ + struct rfkill *rfkill = to_rfkill(dev); + + if (dev->power.power_state.event != state.event) { + if (state.event == PM_EVENT_SUSPEND) { + mutex_lock(&rfkill->mutex); + + if (rfkill->state == RFKILL_STATE_ON) + rfkill->toggle_radio(rfkill->data, + RFKILL_STATE_OFF); + + mutex_unlock(&rfkill->mutex); + } + + dev->power.power_state = state; + } + + return 0; +} + +static int rfkill_resume(struct device *dev) +{ + struct rfkill *rfkill = to_rfkill(dev); + + if (dev->power.power_state.event != PM_EVENT_ON) { + mutex_lock(&rfkill->mutex); + + if (rfkill->state == RFKILL_STATE_ON) + rfkill->toggle_radio(rfkill->data, RFKILL_STATE_ON); + + mutex_unlock(&rfkill->mutex); + } + + dev->power.power_state = PMSG_ON; + return 0; +} +#else +#define rfkill_suspend NULL +#define rfkill_resume NULL +#endif + +static struct class rfkill_class = { + .name = "rfkill", + .dev_release = rfkill_release, + .dev_attrs = rfkill_dev_attrs, + .suspend = rfkill_suspend, + .resume = rfkill_resume, +}; + +static int rfkill_add_switch(struct rfkill *rfkill) +{ + int retval; + + retval = mutex_lock_interruptible(&rfkill_mutex); + if (retval) + return retval; + + retval = rfkill_toggle_radio(rfkill, rfkill_states[rfkill->type]); + if (retval) + goto out; + + list_add_tail(&rfkill->node, &rfkill_list); + + out: + mutex_unlock(&rfkill_mutex); + return retval; +} + +static void rfkill_remove_switch(struct rfkill *rfkill) +{ + mutex_lock(&rfkill_mutex); + list_del_init(&rfkill->node); + rfkill_toggle_radio(rfkill, RFKILL_STATE_OFF); + mutex_unlock(&rfkill_mutex); +} + +/** + * rfkill_allocate - allocate memory for rfkill structure. + * @parent: device that has rf switch on it + * @type: type of the switch (wlan, bluetooth, irda) + * + * This function should be called by the network driver when it needs + * rfkill structure. Once the structure is allocated the driver shoud + * finish its initialization by setting name, private data, enable_radio + * and disable_radio methods and then register it with rfkill_register(). + * NOTE: If registration fails the structure shoudl be freed by calling + * rfkill_free() otherwise rfkill_unregister() should be used. + */ +struct rfkill *rfkill_allocate(struct device *parent, enum rfkill_type type) +{ + struct rfkill *rfkill; + struct device *dev; + + rfkill = kzalloc(sizeof(struct rfkill), GFP_KERNEL); + if (rfkill) + return NULL; + + mutex_init(&rfkill->mutex); + INIT_LIST_HEAD(&rfkill->node); + rfkill->type = type; + + dev = &rfkill->dev; + dev->class = &rfkill_class; + dev->parent = parent; + device_initialize(dev); + + __module_get(THIS_MODULE); + + return rfkill; +} +EXPORT_SYMBOL(rfkill_allocate); + +/** + * rfkill_free - Mark rfkill structure for deletion + * @rfkill: rfkill structure to be destroyed + * + * Decrements reference count of rfkill structure so it is destoryed. + * Note that rfkill_free() should _not_ be called after rfkill_unregister(). + */ +void rfkill_free(struct rfkill *rfkill) +{ + if (rfkill) + put_device(&rfkill->dev); +} +EXPORT_SYMBOL(rfkill_free); + +/** + * rfkill_register - Register a rfkill structure. + * @rfkill: rfkill structure to be registered + * + * This function should be called by the network driver when the rfkill + * structure needs to be registered. Immediately from registration the + * switch driver should be able to service calls to toggle_radio. + */ +int rfkill_register(struct rfkill *rfkill) +{ + static atomic_t rfkill_no = ATOMIC_INIT(0); + struct device *dev = &rfkill->dev; + int error; + + if (!rfkill->toggle_radio) + return -EINVAL; + + error = rfkill_add_switch(rfkill); + if (error) + return error; + + snprintf(dev->bus_id, sizeof(dev->bus_id), + "rfkill%ld", (long)atomic_inc_return(&rfkill_no) - 1); + + error = device_add(dev); + if (error) { + rfkill_remove_switch(rfkill); + return error; + } + + return 0; +} +EXPORT_SYMBOL(rfkill_register); + +/** + * rfkill_unregister - Uegister a rfkill structure. + * @rfkill: rfkill structure to be unregistered + * + * This function should be called by the network driver during device + * teardown to destroy rfkill structure. Note that rfkill_free() should + * _not_ be called after rfkill_unregister(). + */ +void rfkill_unregister(struct rfkill *rfkill) +{ + device_del(&rfkill->dev); + rfkill_remove_switch(rfkill); + put_device(&rfkill->dev); +} +EXPORT_SYMBOL(rfkill_unregister); + +/* + * Rfkill module initialization/deinitialization. + */ +static int __init rfkill_init(void) +{ + int error; + int i; + + for (i = 0; i < ARRAY_SIZE(rfkill_states); i++) + rfkill_states[i] = RFKILL_STATE_ON; + + error = class_register(&rfkill_class); + if (error) { + printk(KERN_ERR "rfkill: unable to register rfkill class\n"); + return error; + } + + return 0; +} + +static void __exit rfkill_exit(void) +{ + class_unregister(&rfkill_class); +} + +module_init(rfkill_init); +module_exit(rfkill_exit); -- cgit v1.2.2