| /* |
| * |
| * Wireless daemon for Linux |
| * |
| * Copyright (C) 2016-2019 Intel Corporation. All rights reserved. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library 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 |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| #include <errno.h> |
| #include <linux/rfkill.h> |
| #include <stdio.h> |
| #include <unistd.h> |
| |
| #include <ell/ell.h> |
| |
| #include "src/iwd.h" |
| #include "src/module.h" |
| #include "src/common.h" |
| #include "src/rfkill.h" |
| |
| struct rfkill_map_entry { |
| unsigned int wiphy_id; |
| unsigned int rfkill_id; |
| bool soft_state : 1; |
| bool hard_state : 1; |
| }; |
| |
| struct rfkill_watch { |
| uint32_t id; |
| rfkill_state_cb_t callback; |
| void *user_data; |
| }; |
| |
| static struct l_queue *rfkill_map; |
| static struct l_io *rfkill_io; |
| |
| static struct l_queue *rfkill_watches; |
| static uint32_t next_watch_id; |
| |
| static bool rfkill_id_match(const void *a, const void *b) |
| { |
| const struct rfkill_map_entry *entry = a; |
| uint32_t id = L_PTR_TO_UINT(b); |
| |
| return entry->rfkill_id == id; |
| } |
| |
| static bool wiphy_id_match(const void *a, const void *b) |
| { |
| const struct rfkill_map_entry *entry = a; |
| uint32_t id = L_PTR_TO_UINT(b); |
| |
| return entry->wiphy_id == id; |
| } |
| |
| static struct rfkill_map_entry *map_wiphy(unsigned int rfkill_id) |
| { |
| int fd, bytes, consumed; |
| char *path; |
| char buf[32]; |
| unsigned int wiphy_id; |
| struct rfkill_map_entry *entry; |
| |
| path = l_strdup_printf("/sys/class/rfkill/rfkill%u/device/index", rfkill_id); |
| |
| fd = L_TFR(open(path, O_RDONLY)); |
| |
| l_free(path); |
| |
| if (fd < 0) |
| return NULL; |
| |
| bytes = L_TFR(read(fd, buf, sizeof(buf) - 1)); |
| |
| close(fd); |
| |
| if (bytes <= 0) |
| return NULL; |
| |
| buf[bytes] = '\0'; |
| if (sscanf(buf, "%u %n", &wiphy_id, &consumed) != 1 || |
| consumed != bytes) |
| return NULL; |
| |
| entry = l_new(struct rfkill_map_entry, 1); |
| entry->rfkill_id = rfkill_id; |
| entry->wiphy_id = wiphy_id; |
| |
| return entry; |
| } |
| |
| static void rfkill_watch_notify(void *data, void *user_data) |
| { |
| struct rfkill_watch *watch = data; |
| struct rfkill_map_entry *entry = user_data; |
| |
| watch->callback(entry->wiphy_id, entry->soft_state, entry->hard_state, |
| watch->user_data); |
| } |
| |
| static bool rfkill_read(struct l_io *io, void *user_data) |
| { |
| int fd = l_io_get_fd(rfkill_io); |
| struct rfkill_event e; |
| int bytes; |
| struct rfkill_map_entry *entry; |
| |
| bytes = L_TFR(read(fd, &e, sizeof(e))); |
| if (bytes < (int) sizeof(e)) { |
| if (bytes <= 0) |
| l_error("rfkill read: %s", strerror(errno)); |
| else |
| l_error("rfkill read of %i bytes", bytes); |
| |
| return false; |
| } |
| |
| if (e.type != RFKILL_TYPE_WLAN) |
| return true; |
| |
| entry = l_queue_find(rfkill_map, rfkill_id_match, L_UINT_TO_PTR(e.idx)); |
| |
| if (e.op == RFKILL_OP_ADD) { |
| if (entry) { |
| l_error("rfkill id %u already known", e.idx); |
| |
| return true; |
| } |
| |
| entry = map_wiphy(e.idx); |
| if (!entry) { |
| l_error("rfkill id %u can't be matched to a wiphy", |
| e.idx); |
| |
| return true; |
| } |
| |
| l_queue_push_tail(rfkill_map, entry); |
| } else if (e.op != RFKILL_OP_DEL && e.op != RFKILL_OP_CHANGE) |
| return true; |
| |
| if (!entry) { |
| l_error("rfkill id %u not found in a %s event", e.idx, |
| e.op == RFKILL_OP_DEL ? "RFKILL_OP_DEL" : |
| "RFKILL_OP_CHANGE"); |
| |
| return true; |
| } |
| |
| if (e.op == RFKILL_OP_DEL) { |
| l_queue_remove(rfkill_map, entry); |
| |
| l_free(entry); |
| |
| return true; |
| } |
| |
| entry->soft_state = e.soft != 0; |
| entry->hard_state = e.hard != 0; |
| |
| l_queue_foreach(rfkill_watches, rfkill_watch_notify, entry); |
| |
| return true; |
| } |
| |
| bool rfkill_set_soft_state(unsigned int wiphy_id, bool state) |
| { |
| int fd = l_io_get_fd(rfkill_io); |
| struct rfkill_event e; |
| struct rfkill_map_entry *entry; |
| int bytes; |
| |
| entry = l_queue_find(rfkill_map, wiphy_id_match, |
| L_UINT_TO_PTR(wiphy_id)); |
| if (!entry) |
| return false; |
| |
| memset(&e, 0, sizeof(e)); |
| |
| e.idx = entry->rfkill_id; |
| e.type = RFKILL_TYPE_WLAN; |
| e.op = RFKILL_OP_CHANGE; |
| e.soft = state ? 1 : 0; |
| |
| bytes = L_TFR(write(fd, &e, sizeof(e))); |
| if (bytes < (int) sizeof(e)) { |
| if (bytes <= 0) |
| l_error("rfkill write: %s", strerror(errno)); |
| else |
| l_error("rfkill write of %i bytes", bytes); |
| |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static bool rfkill_watch_match(const void *a, const void *b) |
| { |
| const struct rfkill_watch *item = a; |
| uint32_t id = L_PTR_TO_UINT(b); |
| |
| return item->id == id; |
| } |
| |
| uint32_t rfkill_watch_add(rfkill_state_cb_t func, void *user_data) |
| { |
| struct rfkill_watch *item; |
| |
| item = l_new(struct rfkill_watch, 1); |
| item->id = ++next_watch_id; |
| item->callback = func; |
| item->user_data = user_data; |
| |
| if (!rfkill_watches) |
| rfkill_watches = l_queue_new(); |
| |
| l_queue_push_tail(rfkill_watches, item); |
| |
| return item->id; |
| } |
| |
| bool rfkill_watch_remove(uint32_t watch_id) |
| { |
| struct rfkill_watch *item; |
| |
| item = l_queue_remove_if(rfkill_watches, rfkill_watch_match, |
| L_UINT_TO_PTR(watch_id)); |
| if (!item) |
| return false; |
| |
| l_free(item); |
| |
| return true; |
| } |
| |
| bool rfkill_get_soft_state(unsigned int wiphy_id) |
| { |
| struct rfkill_map_entry *entry; |
| |
| entry = l_queue_find(rfkill_map, wiphy_id_match, |
| L_UINT_TO_PTR(wiphy_id)); |
| |
| return entry ? entry->soft_state : false; |
| } |
| |
| bool rfkill_get_hard_state(unsigned int wiphy_id) |
| { |
| struct rfkill_map_entry *entry; |
| |
| entry = l_queue_find(rfkill_map, wiphy_id_match, |
| L_UINT_TO_PTR(wiphy_id)); |
| |
| return entry ? entry->hard_state : false; |
| } |
| |
| static int rfkill_init(void) |
| { |
| int fd; |
| |
| fd = L_TFR(open("/dev/rfkill", O_RDWR | O_CLOEXEC)); |
| if (fd < 0) |
| return -errno; |
| |
| rfkill_io = l_io_new(fd); |
| if (!rfkill_io) { |
| close(fd); |
| return -EIO; |
| } |
| |
| l_io_set_close_on_destroy(rfkill_io, true); |
| l_io_set_read_handler(rfkill_io, rfkill_read, NULL, NULL); |
| |
| rfkill_map = l_queue_new(); |
| |
| return 0; |
| } |
| |
| static void rfkill_exit(void) |
| { |
| l_io_destroy(rfkill_io); |
| l_queue_destroy(rfkill_map, l_free); |
| l_queue_destroy(rfkill_watches, l_free); |
| } |
| |
| IWD_MODULE(rfkill, rfkill_init, rfkill_exit) |