/* * * 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 #endif #include #include #include #include #include #include #include #include #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)