| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org> |
| * |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #define _GNU_SOURCE |
| #include <errno.h> |
| #include <stdio.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <limits.h> |
| |
| #include <glib.h> |
| |
| #include "bluetooth/bluetooth.h" |
| #include "bluetooth/sdp.h" |
| |
| #include "log.h" |
| #include "adapter.h" |
| #include "btd.h" |
| |
| enum rfkill_type { |
| RFKILL_TYPE_ALL = 0, |
| RFKILL_TYPE_WLAN, |
| RFKILL_TYPE_BLUETOOTH, |
| RFKILL_TYPE_UWB, |
| RFKILL_TYPE_WIMAX, |
| RFKILL_TYPE_WWAN, |
| }; |
| |
| enum rfkill_operation { |
| RFKILL_OP_ADD = 0, |
| RFKILL_OP_DEL, |
| RFKILL_OP_CHANGE, |
| RFKILL_OP_CHANGE_ALL, |
| }; |
| |
| struct rfkill_event { |
| uint32_t idx; |
| uint8_t type; |
| uint8_t op; |
| uint8_t soft; |
| uint8_t hard; |
| }; |
| #define RFKILL_EVENT_SIZE_V1 8 |
| #define RFKILL_DEVICE_PATH "/dev/rfkill" |
| |
| static int get_adapter_id_for_rfkill(uint32_t rfkill_id) |
| { |
| char sysname[PATH_MAX]; |
| int namefd; |
| |
| snprintf(sysname, sizeof(sysname) - 1, |
| "/sys/class/rfkill/rfkill%u/name", rfkill_id); |
| |
| namefd = open(sysname, O_RDONLY); |
| if (namefd < 0) |
| return -1; |
| |
| memset(sysname, 0, sizeof(sysname)); |
| |
| if (read(namefd, sysname, sizeof(sysname) - 1) < 4) { |
| close(namefd); |
| return -1; |
| } |
| |
| close(namefd); |
| |
| if (g_str_has_prefix(sysname, "hci") == FALSE) |
| return -1; |
| |
| return atoi(sysname + 3); |
| } |
| |
| int rfkill_get_blocked(uint16_t index) |
| { |
| int fd; |
| int blocked = -1; |
| |
| fd = open(RFKILL_DEVICE_PATH, O_RDWR); |
| if (fd < 0) { |
| DBG("Failed to open RFKILL control device"); |
| return -1; |
| } |
| |
| while (1) { |
| struct rfkill_event event = { 0 }; |
| int id; |
| ssize_t len; |
| |
| len = read(fd, &event, sizeof(event)); |
| if (len < RFKILL_EVENT_SIZE_V1) |
| break; |
| |
| id = get_adapter_id_for_rfkill(event.idx); |
| |
| if (index == id) { |
| blocked = event.soft || event.hard; |
| break; |
| } |
| } |
| close(fd); |
| |
| return blocked; |
| } |
| |
| static gboolean rfkill_event(GIOChannel *chan, |
| GIOCondition cond, gpointer data) |
| { |
| struct rfkill_event event = { 0 }; |
| struct btd_adapter *adapter; |
| bool blocked = false; |
| ssize_t len; |
| int fd, id; |
| |
| if (cond & (G_IO_NVAL | G_IO_HUP | G_IO_ERR)) |
| return FALSE; |
| |
| fd = g_io_channel_unix_get_fd(chan); |
| |
| len = read(fd, &event, sizeof(event)); |
| if (len < 0) { |
| if (errno == EAGAIN) |
| return TRUE; |
| return FALSE; |
| } |
| |
| if (len < RFKILL_EVENT_SIZE_V1) |
| return TRUE; |
| |
| DBG("RFKILL event idx %u type %u op %u soft %u hard %u", |
| event.idx, event.type, event.op, |
| event.soft, event.hard); |
| |
| if (event.soft || event.hard) |
| blocked = true; |
| |
| if (event.op != RFKILL_OP_CHANGE) |
| return TRUE; |
| |
| if (event.type != RFKILL_TYPE_BLUETOOTH && |
| event.type != RFKILL_TYPE_ALL) |
| return TRUE; |
| |
| id = get_adapter_id_for_rfkill(event.idx); |
| if (id < 0) |
| return TRUE; |
| |
| adapter = adapter_find_by_id(id); |
| if (!adapter) |
| return TRUE; |
| |
| DBG("RFKILL unblock for hci%d", id); |
| |
| if (blocked) |
| btd_adapter_set_blocked(adapter); |
| else |
| btd_adapter_restore_powered(adapter); |
| |
| return TRUE; |
| } |
| |
| static guint watch = 0; |
| |
| void rfkill_init(void) |
| { |
| int fd; |
| GIOChannel *channel; |
| |
| errno = 0; |
| fd = open(RFKILL_DEVICE_PATH, O_RDWR); |
| if (fd < 0) { |
| if (errno == ENOENT) { |
| DBG("No RFKILL device available at '%s'", |
| RFKILL_DEVICE_PATH); |
| } else { |
| error("Failed to open RFKILL control device: %s", |
| strerror(errno)); |
| } |
| return; |
| } |
| |
| channel = g_io_channel_unix_new(fd); |
| g_io_channel_set_close_on_unref(channel, TRUE); |
| |
| watch = g_io_add_watch(channel, |
| G_IO_IN | G_IO_NVAL | G_IO_HUP | G_IO_ERR, |
| rfkill_event, NULL); |
| |
| g_io_channel_unref(channel); |
| } |
| |
| void rfkill_exit(void) |
| { |
| if (watch == 0) |
| return; |
| |
| g_source_remove(watch); |
| watch = 0; |
| } |