/*
BlueZ - Bluetooth protocol stack for Linux
Copyright ( C ) 2014 Intel Corporation
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 ;
THE SOFTWARE IS PROVIDED " AS IS " , WITHOUT WARRANTY OF ANY KIND , EXPRESS
OR IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY ,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS .
IN NO EVENT SHALL THE COPYRIGHT HOLDER ( S ) AND AUTHOR ( S ) BE LIABLE FOR ANY
CLAIM , OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES , OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE , DATA OR PROFITS , WHETHER IN AN
ACTION OF CONTRACT , NEGLIGENCE OR OTHER TORTIOUS ACTION , ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE .
ALL LIABILITY , INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS ,
COPYRIGHTS , TRADEMARKS OR OTHER RIGHTS , RELATING TO USE OF THIS
SOFTWARE IS DISCLAIMED .
*/
#include <linux/debugfs.h>
#include <linux/kstrtox.h>
#include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/hci_core.h>
#include "smp.h"
#include "hci_debugfs.h"
#define DEFINE_QUIRK_ATTRIBUTE(__name, __quirk) \
static ssize_t __name ## _read(struct file *file, \
char __user *user_buf, \
size_t count, loff_t *ppos) \
{ \
struct hci_dev *hdev = file->private_data; \
char buf[3 ]; \
\
buf[0 ] = test_bit(__quirk, hdev->quirk_flags) ? 'Y' : 'N' ; \
buf[1 ] = '\n' ; \
buf[2 ] = '\0' ; \
return simple_read_from_buffer(user_buf, count, ppos, buf, 2 ); \
} \
\
static ssize_t __name ## _write(struct file *file, \
const char __user *user_buf, \
size_t count, loff_t *ppos) \
{ \
struct hci_dev *hdev = file->private_data; \
bool enable; \
int err; \
\
if (test_bit(HCI_UP, &hdev->flags)) \
return -EBUSY; \
\
err = kstrtobool_from_user(user_buf, count, &enable); \
if (err) \
return err; \
\
if (enable == test_bit(__quirk, hdev->quirk_flags)) \
return -EALREADY; \
\
change_bit(__quirk, hdev->quirk_flags); \
\
return count; \
} \
\
static const struct file_operations __name ## _fops = { \
.open = simple_open, \
.read = __name ## _read, \
.write = __name ## _write, \
.llseek = default_llseek, \
} \
#define DEFINE_INFO_ATTRIBUTE(__name, __field) \
static int __name ## _show(struct seq_file *f, void *ptr) \
{ \
struct hci_dev *hdev = f->private ; \
\
hci_dev_lock(hdev); \
seq_printf(f, "%s\n" , hdev->__field ? : "" ); \
hci_dev_unlock(hdev); \
\
return 0 ; \
} \
\
DEFINE_SHOW_ATTRIBUTE(__name)
static int features_show(struct seq_file *f, void *ptr)
{
struct hci_dev *hdev = f->private ;
u8 p;
hci_dev_lock(hdev);
for (p = 0 ; p < HCI_MAX_PAGES && p <= hdev->max_page; p++)
seq_printf(f, "%2u: %8ph\n" , p, hdev->features[p]);
if (lmp_le_capable(hdev))
seq_printf(f, "LE: %8ph\n" , hdev->le_features);
hci_dev_unlock(hdev);
return 0 ;
}
DEFINE_SHOW_ATTRIBUTE(features);
static int device_id_show(struct seq_file *f, void *ptr)
{
struct hci_dev *hdev = f->private ;
hci_dev_lock(hdev);
seq_printf(f, "%4.4x:%4.4x:%4.4x:%4.4x\n" , hdev->devid_source,
hdev->devid_vendor, hdev->devid_product, hdev->devid_version);
hci_dev_unlock(hdev);
return 0 ;
}
DEFINE_SHOW_ATTRIBUTE(device_id);
static int device_list_show(struct seq_file *f, void *ptr)
{
struct hci_dev *hdev = f->private ;
struct hci_conn_params *p;
struct bdaddr_list *b;
hci_dev_lock(hdev);
list_for_each_entry(b, &hdev->accept_list, list)
seq_printf(f, "%pMR (type %u)\n" , &b->bdaddr, b->bdaddr_type);
list_for_each_entry(p, &hdev->le_conn_params, list) {
seq_printf(f, "%pMR (type %u) %u\n" , &p->addr, p->addr_type,
p->auto_connect);
}
hci_dev_unlock(hdev);
return 0 ;
}
DEFINE_SHOW_ATTRIBUTE(device_list);
static int blacklist_show(struct seq_file *f, void *p)
{
struct hci_dev *hdev = f->private ;
struct bdaddr_list *b;
hci_dev_lock(hdev);
list_for_each_entry(b, &hdev->reject_list, list)
seq_printf(f, "%pMR (type %u)\n" , &b->bdaddr, b->bdaddr_type);
hci_dev_unlock(hdev);
return 0 ;
}
DEFINE_SHOW_ATTRIBUTE(blacklist);
static int blocked_keys_show(struct seq_file *f, void *p)
{
struct hci_dev *hdev = f->private ;
struct blocked_key *key;
rcu_read_lock();
list_for_each_entry_rcu(key, &hdev->blocked_keys, list)
seq_printf(f, "%u %*phN\n" , key->type, 16 , key->val);
rcu_read_unlock();
return 0 ;
}
DEFINE_SHOW_ATTRIBUTE(blocked_keys);
static int uuids_show(struct seq_file *f, void *p)
{
struct hci_dev *hdev = f->private ;
struct bt_uuid *uuid;
hci_dev_lock(hdev);
list_for_each_entry(uuid, &hdev->uuids, list) {
u8 i, val[16 ];
/* The Bluetooth UUID values are stored in big endian,
* but with reversed byte order . So convert them into
* the right order for the % pUb modifier .
*/
for (i = 0 ; i < 16 ; i++)
val[i] = uuid->uuid[15 - i];
seq_printf(f, "%pUb\n" , val);
}
hci_dev_unlock(hdev);
return 0 ;
}
DEFINE_SHOW_ATTRIBUTE(uuids);
static int remote_oob_show(struct seq_file *f, void *ptr)
{
struct hci_dev *hdev = f->private ;
struct oob_data *data;
hci_dev_lock(hdev);
list_for_each_entry(data, &hdev->remote_oob_data, list) {
seq_printf(f, "%pMR (type %u) %u %*phN %*phN %*phN %*phN\n" ,
&data->bdaddr, data->bdaddr_type, data->present,
16 , data->hash192, 16 , data->rand192,
16 , data->hash256, 16 , data->rand256);
}
hci_dev_unlock(hdev);
return 0 ;
}
DEFINE_SHOW_ATTRIBUTE(remote_oob);
static int conn_info_min_age_set(void *data, u64 val)
{
struct hci_dev *hdev = data;
hci_dev_lock(hdev);
if (val == 0 || val > hdev->conn_info_max_age) {
hci_dev_unlock(hdev);
return -EINVAL;
}
hdev->conn_info_min_age = val;
hci_dev_unlock(hdev);
return 0 ;
}
static int conn_info_min_age_get(void *data, u64 *val)
{
struct hci_dev *hdev = data;
hci_dev_lock(hdev);
*val = hdev->conn_info_min_age;
hci_dev_unlock(hdev);
return 0 ;
}
DEFINE_DEBUGFS_ATTRIBUTE(conn_info_min_age_fops, conn_info_min_age_get,
conn_info_min_age_set, "%llu\n" );
static int conn_info_max_age_set(void *data, u64 val)
{
struct hci_dev *hdev = data;
hci_dev_lock(hdev);
if (val == 0 || val < hdev->conn_info_min_age) {
hci_dev_unlock(hdev);
return -EINVAL;
}
hdev->conn_info_max_age = val;
hci_dev_unlock(hdev);
return 0 ;
}
static int conn_info_max_age_get(void *data, u64 *val)
{
struct hci_dev *hdev = data;
hci_dev_lock(hdev);
*val = hdev->conn_info_max_age;
hci_dev_unlock(hdev);
return 0 ;
}
DEFINE_DEBUGFS_ATTRIBUTE(conn_info_max_age_fops, conn_info_max_age_get,
conn_info_max_age_set, "%llu\n" );
static ssize_t use_debug_keys_read(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos)
{
struct hci_dev *hdev = file->private_data;
char buf[3 ];
buf[0 ] = hci_dev_test_flag(hdev, HCI_USE_DEBUG_KEYS) ? 'Y' : 'N' ;
buf[1 ] = '\n' ;
buf[2 ] = '\0' ;
return simple_read_from_buffer(user_buf, count, ppos, buf, 2 );
}
static const struct file_operations use_debug_keys_fops = {
.open = simple_open,
.read = use_debug_keys_read,
.llseek = default_llseek,
};
static ssize_t sc_only_mode_read(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos)
{
struct hci_dev *hdev = file->private_data;
char buf[3 ];
buf[0 ] = hci_dev_test_flag(hdev, HCI_SC_ONLY) ? 'Y' : 'N' ;
buf[1 ] = '\n' ;
buf[2 ] = '\0' ;
return simple_read_from_buffer(user_buf, count, ppos, buf, 2 );
}
static const struct file_operations sc_only_mode_fops = {
.open = simple_open,
.read = sc_only_mode_read,
.llseek = default_llseek,
};
DEFINE_INFO_ATTRIBUTE(hardware_info, hw_info);
DEFINE_INFO_ATTRIBUTE(firmware_info, fw_info);
void hci_debugfs_create_common(struct hci_dev *hdev)
{
debugfs_create_file("features" , 0444 , hdev->debugfs, hdev,
&features_fops);
debugfs_create_u16("manufacturer" , 0444 , hdev->debugfs,
&hdev->manufacturer);
debugfs_create_u8("hci_version" , 0444 , hdev->debugfs, &hdev->hci_ver);
debugfs_create_u16("hci_revision" , 0444 , hdev->debugfs, &hdev->hci_rev);
debugfs_create_u8("hardware_error" , 0444 , hdev->debugfs,
&hdev->hw_error_code);
debugfs_create_file("device_id" , 0444 , hdev->debugfs, hdev,
&device_id_fops);
debugfs_create_file("device_list" , 0444 , hdev->debugfs, hdev,
&device_list_fops);
debugfs_create_file("blacklist" , 0444 , hdev->debugfs, hdev,
&blacklist_fops);
debugfs_create_file("blocked_keys" , 0444 , hdev->debugfs, hdev,
&blocked_keys_fops);
debugfs_create_file("uuids" , 0444 , hdev->debugfs, hdev, &uuids_fops);
debugfs_create_file("remote_oob" , 0400 , hdev->debugfs, hdev,
&remote_oob_fops);
debugfs_create_file("conn_info_min_age" , 0644 , hdev->debugfs, hdev,
&conn_info_min_age_fops);
debugfs_create_file("conn_info_max_age" , 0644 , hdev->debugfs, hdev,
&conn_info_max_age_fops);
if (lmp_ssp_capable(hdev) || lmp_le_capable(hdev))
debugfs_create_file("use_debug_keys" , 0444 , hdev->debugfs,
hdev, &use_debug_keys_fops);
if (lmp_sc_capable(hdev) || lmp_le_capable(hdev))
debugfs_create_file("sc_only_mode" , 0444 , hdev->debugfs,
hdev, &sc_only_mode_fops);
if (hdev->hw_info)
debugfs_create_file("hardware_info" , 0444 , hdev->debugfs,
hdev, &hardware_info_fops);
if (hdev->fw_info)
debugfs_create_file("firmware_info" , 0444 , hdev->debugfs,
hdev, &firmware_info_fops);
}
static int inquiry_cache_show(struct seq_file *f, void *p)
{
struct hci_dev *hdev = f->private ;
struct discovery_state *cache = &hdev->discovery;
struct inquiry_entry *e;
hci_dev_lock(hdev);
list_for_each_entry(e, &cache->all, all) {
struct inquiry_data *data = &e->data;
seq_printf(f, "%pMR %d %d %d 0x%.2x%.2x%.2x 0x%.4x %d %d %u\n" ,
&data->bdaddr,
data->pscan_rep_mode, data->pscan_period_mode,
data->pscan_mode, data->dev_class[2 ],
data->dev_class[1 ], data->dev_class[0 ],
__le16_to_cpu(data->clock_offset),
data->rssi, data->ssp_mode, e->timestamp);
}
hci_dev_unlock(hdev);
return 0 ;
}
DEFINE_SHOW_ATTRIBUTE(inquiry_cache);
static int link_keys_show(struct seq_file *f, void *ptr)
{
struct hci_dev *hdev = f->private ;
struct link_key *key;
rcu_read_lock();
list_for_each_entry_rcu(key, &hdev->link_keys, list)
seq_printf(f, "%pMR %u %*phN %u\n" , &key->bdaddr, key->type,
HCI_LINK_KEY_SIZE, key->val, key->pin_len);
rcu_read_unlock();
return 0 ;
}
DEFINE_SHOW_ATTRIBUTE(link_keys);
static int dev_class_show(struct seq_file *f, void *ptr)
{
struct hci_dev *hdev = f->private ;
hci_dev_lock(hdev);
seq_printf(f, "0x%.2x%.2x%.2x\n" , hdev->dev_class[2 ],
hdev->dev_class[1 ], hdev->dev_class[0 ]);
hci_dev_unlock(hdev);
return 0 ;
}
DEFINE_SHOW_ATTRIBUTE(dev_class);
static int voice_setting_get(void *data, u64 *val)
{
struct hci_dev *hdev = data;
hci_dev_lock(hdev);
*val = hdev->voice_setting;
hci_dev_unlock(hdev);
return 0 ;
}
DEFINE_DEBUGFS_ATTRIBUTE(voice_setting_fops, voice_setting_get,
NULL, "0x%4.4llx\n" );
static ssize_t ssp_debug_mode_read(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos)
{
struct hci_dev *hdev = file->private_data;
char buf[3 ];
buf[0 ] = hdev->ssp_debug_mode ? 'Y' : 'N' ;
buf[1 ] = '\n' ;
buf[2 ] = '\0' ;
return simple_read_from_buffer(user_buf, count, ppos, buf, 2 );
}
static const struct file_operations ssp_debug_mode_fops = {
.open = simple_open,
.read = ssp_debug_mode_read,
.llseek = default_llseek,
};
static int auto_accept_delay_set(void *data, u64 val)
{
struct hci_dev *hdev = data;
hci_dev_lock(hdev);
hdev->auto_accept_delay = val;
hci_dev_unlock(hdev);
return 0 ;
}
static int min_encrypt_key_size_set(void *data, u64 val)
{
struct hci_dev *hdev = data;
if (val < 1 || val > 16 )
return -EINVAL;
hci_dev_lock(hdev);
hdev->min_enc_key_size = val;
hci_dev_unlock(hdev);
return 0 ;
}
static int min_encrypt_key_size_get(void *data, u64 *val)
{
struct hci_dev *hdev = data;
hci_dev_lock(hdev);
*val = hdev->min_enc_key_size;
hci_dev_unlock(hdev);
return 0 ;
}
DEFINE_DEBUGFS_ATTRIBUTE(min_encrypt_key_size_fops,
min_encrypt_key_size_get,
min_encrypt_key_size_set, "%llu\n" );
static int auto_accept_delay_get(void *data, u64 *val)
{
struct hci_dev *hdev = data;
hci_dev_lock(hdev);
*val = hdev->auto_accept_delay;
hci_dev_unlock(hdev);
return 0 ;
}
DEFINE_DEBUGFS_ATTRIBUTE(auto_accept_delay_fops, auto_accept_delay_get,
auto_accept_delay_set, "%llu\n" );
static ssize_t force_bredr_smp_read(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
struct hci_dev *hdev = file->private_data;
char buf[3 ];
buf[0 ] = hci_dev_test_flag(hdev, HCI_FORCE_BREDR_SMP) ? 'Y' : 'N' ;
buf[1 ] = '\n' ;
buf[2 ] = '\0' ;
return simple_read_from_buffer(user_buf, count, ppos, buf, 2 );
}
static ssize_t force_bredr_smp_write(struct file *file,
const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct hci_dev *hdev = file->private_data;
bool enable;
int err;
err = kstrtobool_from_user(user_buf, count, &enable);
if (err)
return err;
err = smp_force_bredr(hdev, enable);
if (err)
return err;
return count;
}
static const struct file_operations force_bredr_smp_fops = {
.open = simple_open,
.read = force_bredr_smp_read,
.write = force_bredr_smp_write,
.llseek = default_llseek,
};
static int idle_timeout_set(void *data, u64 val)
{
struct hci_dev *hdev = data;
if (val != 0 && (val < 500 || val > 3600000 ))
return -EINVAL;
hci_dev_lock(hdev);
hdev->idle_timeout = val;
hci_dev_unlock(hdev);
return 0 ;
}
static int idle_timeout_get(void *data, u64 *val)
{
struct hci_dev *hdev = data;
hci_dev_lock(hdev);
*val = hdev->idle_timeout;
hci_dev_unlock(hdev);
return 0 ;
}
DEFINE_DEBUGFS_ATTRIBUTE(idle_timeout_fops, idle_timeout_get,
idle_timeout_set, "%llu\n" );
static int sniff_min_interval_set(void *data, u64 val)
{
struct hci_dev *hdev = data;
hci_dev_lock(hdev);
if (val == 0 || val % 2 || val > hdev->sniff_max_interval) {
hci_dev_unlock(hdev);
return -EINVAL;
}
hdev->sniff_min_interval = val;
hci_dev_unlock(hdev);
return 0 ;
}
static int sniff_min_interval_get(void *data, u64 *val)
{
struct hci_dev *hdev = data;
hci_dev_lock(hdev);
*val = hdev->sniff_min_interval;
hci_dev_unlock(hdev);
return 0 ;
}
DEFINE_DEBUGFS_ATTRIBUTE(sniff_min_interval_fops, sniff_min_interval_get,
sniff_min_interval_set, "%llu\n" );
static int sniff_max_interval_set(void *data, u64 val)
{
struct hci_dev *hdev = data;
hci_dev_lock(hdev);
if (val == 0 || val % 2 || val < hdev->sniff_min_interval) {
hci_dev_unlock(hdev);
return -EINVAL;
}
hdev->sniff_max_interval = val;
hci_dev_unlock(hdev);
return 0 ;
}
static int sniff_max_interval_get(void *data, u64 *val)
{
struct hci_dev *hdev = data;
hci_dev_lock(hdev);
*val = hdev->sniff_max_interval;
hci_dev_unlock(hdev);
return 0 ;
}
DEFINE_DEBUGFS_ATTRIBUTE(sniff_max_interval_fops, sniff_max_interval_get,
sniff_max_interval_set, "%llu\n" );
void hci_debugfs_create_bredr(struct hci_dev *hdev)
{
debugfs_create_file("inquiry_cache" , 0444 , hdev->debugfs, hdev,
&inquiry_cache_fops);
debugfs_create_file("link_keys" , 0400 , hdev->debugfs, hdev,
&link_keys_fops);
debugfs_create_file("dev_class" , 0444 , hdev->debugfs, hdev,
&dev_class_fops);
debugfs_create_file("voice_setting" , 0444 , hdev->debugfs, hdev,
&voice_setting_fops);
/* If the controller does not support BR/EDR Secure Connections
* feature , then the BR / EDR SMP channel shall not be present .
*
* To test this with Bluetooth 4 . 0 controllers , create a debugfs
* switch that allows forcing BR / EDR SMP support and accepting
* cross - transport pairing on non - AES encrypted connections .
*/
if (!lmp_sc_capable(hdev))
debugfs_create_file("force_bredr_smp" , 0644 , hdev->debugfs,
hdev, &force_bredr_smp_fops);
if (lmp_ssp_capable(hdev)) {
debugfs_create_file("ssp_debug_mode" , 0444 , hdev->debugfs,
hdev, &ssp_debug_mode_fops);
debugfs_create_file("min_encrypt_key_size" , 0644 , hdev->debugfs,
hdev, &min_encrypt_key_size_fops);
debugfs_create_file("auto_accept_delay" , 0644 , hdev->debugfs,
hdev, &auto_accept_delay_fops);
}
if (lmp_sniff_capable(hdev)) {
debugfs_create_file("idle_timeout" , 0644 , hdev->debugfs,
hdev, &idle_timeout_fops);
debugfs_create_file("sniff_min_interval" , 0644 , hdev->debugfs,
hdev, &sniff_min_interval_fops);
debugfs_create_file("sniff_max_interval" , 0644 , hdev->debugfs,
hdev, &sniff_max_interval_fops);
}
}
static int identity_show(struct seq_file *f, void *p)
{
struct hci_dev *hdev = f->private ;
bdaddr_t addr;
u8 addr_type;
hci_dev_lock(hdev);
hci_copy_identity_address(hdev, &addr, &addr_type);
seq_printf(f, "%pMR (type %u) %*phN %pMR\n" , &addr, addr_type,
16 , hdev->irk, &hdev->rpa);
hci_dev_unlock(hdev);
return 0 ;
}
DEFINE_SHOW_ATTRIBUTE(identity);
static int rpa_timeout_set(void *data, u64 val)
{
struct hci_dev *hdev = data;
/* Require the RPA timeout to be at least 30 seconds and at most
* 24 hours .
*/
if (val < 30 || val > (60 * 60 * 24 ))
return -EINVAL;
hci_dev_lock(hdev);
hdev->rpa_timeout = val;
hci_dev_unlock(hdev);
return 0 ;
}
static int rpa_timeout_get(void *data, u64 *val)
{
struct hci_dev *hdev = data;
hci_dev_lock(hdev);
*val = hdev->rpa_timeout;
hci_dev_unlock(hdev);
return 0 ;
}
DEFINE_DEBUGFS_ATTRIBUTE(rpa_timeout_fops, rpa_timeout_get,
rpa_timeout_set, "%llu\n" );
static int random_address_show(struct seq_file *f, void *p)
{
struct hci_dev *hdev = f->private ;
hci_dev_lock(hdev);
seq_printf(f, "%pMR\n" , &hdev->random_addr);
hci_dev_unlock(hdev);
return 0 ;
}
DEFINE_SHOW_ATTRIBUTE(random_address);
static int static_address_show(struct seq_file *f, void *p)
{
struct hci_dev *hdev = f->private ;
hci_dev_lock(hdev);
seq_printf(f, "%pMR\n" , &hdev->static_addr);
hci_dev_unlock(hdev);
return 0 ;
}
DEFINE_SHOW_ATTRIBUTE(static_address);
static ssize_t force_static_address_read(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
struct hci_dev *hdev = file->private_data;
char buf[3 ];
buf[0 ] = hci_dev_test_flag(hdev, HCI_FORCE_STATIC_ADDR) ? 'Y' : 'N' ;
buf[1 ] = '\n' ;
buf[2 ] = '\0' ;
return simple_read_from_buffer(user_buf, count, ppos, buf, 2 );
}
static ssize_t force_static_address_write(struct file *file,
const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct hci_dev *hdev = file->private_data;
bool enable;
int err;
if (hdev_is_powered(hdev))
return -EBUSY;
err = kstrtobool_from_user(user_buf, count, &enable);
if (err)
return err;
if (enable == hci_dev_test_flag(hdev, HCI_FORCE_STATIC_ADDR))
return -EALREADY;
hci_dev_change_flag(hdev, HCI_FORCE_STATIC_ADDR);
return count;
}
static const struct file_operations force_static_address_fops = {
.open = simple_open,
.read = force_static_address_read,
.write = force_static_address_write,
.llseek = default_llseek,
};
static int white_list_show(struct seq_file *f, void *ptr)
{
struct hci_dev *hdev = f->private ;
struct bdaddr_list *b;
hci_dev_lock(hdev);
list_for_each_entry(b, &hdev->le_accept_list, list)
seq_printf(f, "%pMR (type %u)\n" , &b->bdaddr, b->bdaddr_type);
hci_dev_unlock(hdev);
return 0 ;
}
DEFINE_SHOW_ATTRIBUTE(white_list);
static int resolv_list_show(struct seq_file *f, void *ptr)
{
struct hci_dev *hdev = f->private ;
struct bdaddr_list *b;
hci_dev_lock(hdev);
list_for_each_entry(b, &hdev->le_resolv_list, list)
seq_printf(f, "%pMR (type %u)\n" , &b->bdaddr, b->bdaddr_type);
hci_dev_unlock(hdev);
return 0 ;
}
DEFINE_SHOW_ATTRIBUTE(resolv_list);
static int identity_resolving_keys_show(struct seq_file *f, void *ptr)
{
struct hci_dev *hdev = f->private ;
struct smp_irk *irk;
rcu_read_lock();
list_for_each_entry_rcu(irk, &hdev->identity_resolving_keys, list) {
seq_printf(f, "%pMR (type %u) %*phN %pMR\n" ,
&irk->bdaddr, irk->addr_type,
16 , irk->val, &irk->rpa);
}
rcu_read_unlock();
return 0 ;
}
DEFINE_SHOW_ATTRIBUTE(identity_resolving_keys);
static int long_term_keys_show(struct seq_file *f, void *ptr)
{
struct hci_dev *hdev = f->private ;
struct smp_ltk *ltk;
rcu_read_lock();
list_for_each_entry_rcu(ltk, &hdev->long_term_keys, list)
seq_printf(f, "%pMR (type %u) %u 0x%02x %u %.4x %.16llx %*phN\n" ,
<k->bdaddr, ltk->bdaddr_type, ltk->authenticated,
ltk->type, ltk->enc_size, __le16_to_cpu(ltk->ediv),
__le64_to_cpu(ltk->rand), 16 , ltk->val);
rcu_read_unlock();
return 0 ;
}
DEFINE_SHOW_ATTRIBUTE(long_term_keys);
static int conn_min_interval_set(void *data, u64 val)
{
struct hci_dev *hdev = data;
hci_dev_lock(hdev);
if (val < 0 x0006 || val > 0 x0c80 || val > hdev->le_conn_max_interval) {
hci_dev_unlock(hdev);
return -EINVAL;
}
hdev->le_conn_min_interval = val;
hci_dev_unlock(hdev);
return 0 ;
}
static int conn_min_interval_get(void *data, u64 *val)
{
struct hci_dev *hdev = data;
hci_dev_lock(hdev);
*val = hdev->le_conn_min_interval;
hci_dev_unlock(hdev);
return 0 ;
}
DEFINE_DEBUGFS_ATTRIBUTE(conn_min_interval_fops, conn_min_interval_get,
conn_min_interval_set, "%llu\n" );
static int conn_max_interval_set(void *data, u64 val)
{
struct hci_dev *hdev = data;
hci_dev_lock(hdev);
if (val < 0 x0006 || val > 0 x0c80 || val < hdev->le_conn_min_interval) {
hci_dev_unlock(hdev);
return -EINVAL;
}
hdev->le_conn_max_interval = val;
hci_dev_unlock(hdev);
return 0 ;
}
static int conn_max_interval_get(void *data, u64 *val)
{
struct hci_dev *hdev = data;
hci_dev_lock(hdev);
*val = hdev->le_conn_max_interval;
hci_dev_unlock(hdev);
return 0 ;
}
DEFINE_DEBUGFS_ATTRIBUTE(conn_max_interval_fops, conn_max_interval_get,
conn_max_interval_set, "%llu\n" );
static int conn_latency_set(void *data, u64 val)
{
struct hci_dev *hdev = data;
if (val > 0 x01f3)
return -EINVAL;
hci_dev_lock(hdev);
hdev->le_conn_latency = val;
hci_dev_unlock(hdev);
return 0 ;
}
static int conn_latency_get(void *data, u64 *val)
{
struct hci_dev *hdev = data;
hci_dev_lock(hdev);
*val = hdev->le_conn_latency;
hci_dev_unlock(hdev);
return 0 ;
}
DEFINE_DEBUGFS_ATTRIBUTE(conn_latency_fops, conn_latency_get,
conn_latency_set, "%llu\n" );
static int supervision_timeout_set(void *data, u64 val)
{
struct hci_dev *hdev = data;
if (val < 0 x000a || val > 0 x0c80)
return -EINVAL;
hci_dev_lock(hdev);
hdev->le_supv_timeout = val;
hci_dev_unlock(hdev);
return 0 ;
}
static int supervision_timeout_get(void *data, u64 *val)
{
struct hci_dev *hdev = data;
hci_dev_lock(hdev);
*val = hdev->le_supv_timeout;
hci_dev_unlock(hdev);
return 0 ;
}
DEFINE_DEBUGFS_ATTRIBUTE(supervision_timeout_fops, supervision_timeout_get,
supervision_timeout_set, "%llu\n" );
static int adv_channel_map_set(void *data, u64 val)
{
struct hci_dev *hdev = data;
if (val < 0 x01 || val > 0 x07)
return -EINVAL;
hci_dev_lock(hdev);
hdev->le_adv_channel_map = val;
hci_dev_unlock(hdev);
return 0 ;
}
static int adv_channel_map_get(void *data, u64 *val)
{
struct hci_dev *hdev = data;
hci_dev_lock(hdev);
*val = hdev->le_adv_channel_map;
hci_dev_unlock(hdev);
return 0 ;
}
DEFINE_DEBUGFS_ATTRIBUTE(adv_channel_map_fops, adv_channel_map_get,
adv_channel_map_set, "%llu\n" );
static int adv_min_interval_set(void *data, u64 val)
{
struct hci_dev *hdev = data;
hci_dev_lock(hdev);
if (val < 0 x0020 || val > 0 x4000 || val > hdev->le_adv_max_interval) {
hci_dev_unlock(hdev);
return -EINVAL;
}
hdev->le_adv_min_interval = val;
hci_dev_unlock(hdev);
return 0 ;
}
static int adv_min_interval_get(void *data, u64 *val)
{
struct hci_dev *hdev = data;
hci_dev_lock(hdev);
*val = hdev->le_adv_min_interval;
hci_dev_unlock(hdev);
return 0 ;
}
DEFINE_DEBUGFS_ATTRIBUTE(adv_min_interval_fops, adv_min_interval_get,
adv_min_interval_set, "%llu\n" );
static int adv_max_interval_set(void *data, u64 val)
{
struct hci_dev *hdev = data;
hci_dev_lock(hdev);
if (val < 0 x0020 || val > 0 x4000 || val < hdev->le_adv_min_interval) {
hci_dev_unlock(hdev);
return -EINVAL;
}
hdev->le_adv_max_interval = val;
hci_dev_unlock(hdev);
return 0 ;
}
static int adv_max_interval_get(void *data, u64 *val)
{
struct hci_dev *hdev = data;
hci_dev_lock(hdev);
*val = hdev->le_adv_max_interval;
hci_dev_unlock(hdev);
return 0 ;
}
DEFINE_DEBUGFS_ATTRIBUTE(adv_max_interval_fops, adv_max_interval_get,
adv_max_interval_set, "%llu\n" );
static int min_key_size_set(void *data, u64 val)
{
struct hci_dev *hdev = data;
hci_dev_lock(hdev);
if (val > hdev->le_max_key_size || val < SMP_MIN_ENC_KEY_SIZE) {
hci_dev_unlock(hdev);
return -EINVAL;
}
hdev->le_min_key_size = val;
hci_dev_unlock(hdev);
return 0 ;
}
static int min_key_size_get(void *data, u64 *val)
{
struct hci_dev *hdev = data;
hci_dev_lock(hdev);
*val = hdev->le_min_key_size;
hci_dev_unlock(hdev);
return 0 ;
}
DEFINE_DEBUGFS_ATTRIBUTE(min_key_size_fops, min_key_size_get,
min_key_size_set, "%llu\n" );
static int max_key_size_set(void *data, u64 val)
{
struct hci_dev *hdev = data;
hci_dev_lock(hdev);
if (val > SMP_MAX_ENC_KEY_SIZE || val < hdev->le_min_key_size) {
hci_dev_unlock(hdev);
return -EINVAL;
}
hdev->le_max_key_size = val;
hci_dev_unlock(hdev);
return 0 ;
}
static int max_key_size_get(void *data, u64 *val)
{
struct hci_dev *hdev = data;
hci_dev_lock(hdev);
*val = hdev->le_max_key_size;
hci_dev_unlock(hdev);
return 0 ;
}
DEFINE_DEBUGFS_ATTRIBUTE(max_key_size_fops, max_key_size_get,
max_key_size_set, "%llu\n" );
static int auth_payload_timeout_set(void *data, u64 val)
{
struct hci_dev *hdev = data;
if (val < 0 x0001 || val > 0 xffff)
return -EINVAL;
hci_dev_lock(hdev);
hdev->auth_payload_timeout = val;
hci_dev_unlock(hdev);
return 0 ;
}
static int auth_payload_timeout_get(void *data, u64 *val)
{
struct hci_dev *hdev = data;
hci_dev_lock(hdev);
*val = hdev->auth_payload_timeout;
hci_dev_unlock(hdev);
return 0 ;
}
DEFINE_DEBUGFS_ATTRIBUTE(auth_payload_timeout_fops,
auth_payload_timeout_get,
auth_payload_timeout_set, "%llu\n" );
static ssize_t force_no_mitm_read(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
struct hci_dev *hdev = file->private_data;
char buf[3 ];
buf[0 ] = hci_dev_test_flag(hdev, HCI_FORCE_NO_MITM) ? 'Y' : 'N' ;
buf[1 ] = '\n' ;
buf[2 ] = '\0' ;
return simple_read_from_buffer(user_buf, count, ppos, buf, 2 );
}
static ssize_t force_no_mitm_write(struct file *file,
const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct hci_dev *hdev = file->private_data;
char buf[32 ];
size_t buf_size = min(count, (sizeof (buf) - 1 ));
bool enable;
if (copy_from_user(buf, user_buf, buf_size))
return -EFAULT;
buf[buf_size] = '\0' ;
if (kstrtobool(buf, &enable))
return -EINVAL;
if (enable == hci_dev_test_flag(hdev, HCI_FORCE_NO_MITM))
return -EALREADY;
hci_dev_change_flag(hdev, HCI_FORCE_NO_MITM);
return count;
}
static const struct file_operations force_no_mitm_fops = {
.open = simple_open,
.read = force_no_mitm_read,
.write = force_no_mitm_write,
.llseek = default_llseek,
};
DEFINE_QUIRK_ATTRIBUTE(quirk_strict_duplicate_filter,
HCI_QUIRK_STRICT_DUPLICATE_FILTER);
DEFINE_QUIRK_ATTRIBUTE(quirk_simultaneous_discovery,
HCI_QUIRK_SIMULTANEOUS_DISCOVERY);
void hci_debugfs_create_le(struct hci_dev *hdev)
{
debugfs_create_file("identity" , 0400 , hdev->debugfs, hdev,
&identity_fops);
debugfs_create_file("rpa_timeout" , 0644 , hdev->debugfs, hdev,
&rpa_timeout_fops);
debugfs_create_file("random_address" , 0444 , hdev->debugfs, hdev,
&random_address_fops);
debugfs_create_file("static_address" , 0444 , hdev->debugfs, hdev,
&static_address_fops);
/* For controllers with a public address, provide a debug
* option to force the usage of the configured static
* address . By default the public address is used .
*/
if (bacmp(&hdev->bdaddr, BDADDR_ANY))
debugfs_create_file("force_static_address" , 0644 ,
hdev->debugfs, hdev,
&force_static_address_fops);
debugfs_create_u8("white_list_size" , 0444 , hdev->debugfs,
&hdev->le_accept_list_size);
debugfs_create_file("white_list" , 0444 , hdev->debugfs, hdev,
&white_list_fops);
debugfs_create_u8("resolv_list_size" , 0444 , hdev->debugfs,
&hdev->le_resolv_list_size);
debugfs_create_file("resolv_list" , 0444 , hdev->debugfs, hdev,
&resolv_list_fops);
debugfs_create_file("identity_resolving_keys" , 0400 , hdev->debugfs,
hdev, &identity_resolving_keys_fops);
debugfs_create_file("long_term_keys" , 0400 , hdev->debugfs, hdev,
&long_term_keys_fops);
debugfs_create_file("conn_min_interval" , 0644 , hdev->debugfs, hdev,
&conn_min_interval_fops);
debugfs_create_file("conn_max_interval" , 0644 , hdev->debugfs, hdev,
&conn_max_interval_fops);
debugfs_create_file("conn_latency" , 0644 , hdev->debugfs, hdev,
&conn_latency_fops);
debugfs_create_file("supervision_timeout" , 0644 , hdev->debugfs, hdev,
&supervision_timeout_fops);
debugfs_create_file("adv_channel_map" , 0644 , hdev->debugfs, hdev,
&adv_channel_map_fops);
debugfs_create_file("adv_min_interval" , 0644 , hdev->debugfs, hdev,
&adv_min_interval_fops);
debugfs_create_file("adv_max_interval" , 0644 , hdev->debugfs, hdev,
&adv_max_interval_fops);
debugfs_create_u16("discov_interleaved_timeout" , 0644 , hdev->debugfs,
&hdev->discov_interleaved_timeout);
debugfs_create_file("min_key_size" , 0644 , hdev->debugfs, hdev,
&min_key_size_fops);
debugfs_create_file("max_key_size" , 0644 , hdev->debugfs, hdev,
&max_key_size_fops);
debugfs_create_file("auth_payload_timeout" , 0644 , hdev->debugfs, hdev,
&auth_payload_timeout_fops);
debugfs_create_file("force_no_mitm" , 0644 , hdev->debugfs, hdev,
&force_no_mitm_fops);
debugfs_create_file("quirk_strict_duplicate_filter" , 0644 ,
hdev->debugfs, hdev,
&quirk_strict_duplicate_filter_fops);
debugfs_create_file("quirk_simultaneous_discovery" , 0644 ,
hdev->debugfs, hdev,
&quirk_simultaneous_discovery_fops);
}
void hci_debugfs_create_conn(struct hci_conn *conn)
{
struct hci_dev *hdev = conn->hdev;
char name[6 ];
if (IS_ERR_OR_NULL(hdev->debugfs) || conn->debugfs)
return ;
snprintf(name, sizeof (name), "%u" , conn->handle);
conn->debugfs = debugfs_create_dir(name, hdev->debugfs);
}
static ssize_t dut_mode_read(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos)
{
struct hci_dev *hdev = file->private_data;
char buf[3 ];
buf[0 ] = hci_dev_test_flag(hdev, HCI_DUT_MODE) ? 'Y' : 'N' ;
buf[1 ] = '\n' ;
buf[2 ] = '\0' ;
return simple_read_from_buffer(user_buf, count, ppos, buf, 2 );
}
static ssize_t dut_mode_write(struct file *file, const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct hci_dev *hdev = file->private_data;
struct sk_buff *skb;
bool enable;
int err;
if (!test_bit(HCI_UP, &hdev->flags))
return -ENETDOWN;
err = kstrtobool_from_user(user_buf, count, &enable);
if (err)
return err;
if (enable == hci_dev_test_flag(hdev, HCI_DUT_MODE))
return -EALREADY;
hci_req_sync_lock(hdev);
if (enable)
skb = __hci_cmd_sync(hdev, HCI_OP_ENABLE_DUT_MODE, 0 , NULL,
HCI_CMD_TIMEOUT);
else
skb = __hci_cmd_sync(hdev, HCI_OP_RESET, 0 , NULL,
HCI_CMD_TIMEOUT);
hci_req_sync_unlock(hdev);
if (IS_ERR(skb))
return PTR_ERR(skb);
kfree_skb(skb);
hci_dev_change_flag(hdev, HCI_DUT_MODE);
return count;
}
static const struct file_operations dut_mode_fops = {
.open = simple_open,
.read = dut_mode_read,
.write = dut_mode_write,
.llseek = default_llseek,
};
static ssize_t vendor_diag_read(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos)
{
struct hci_dev *hdev = file->private_data;
char buf[3 ];
buf[0 ] = hci_dev_test_flag(hdev, HCI_VENDOR_DIAG) ? 'Y' : 'N' ;
buf[1 ] = '\n' ;
buf[2 ] = '\0' ;
return simple_read_from_buffer(user_buf, count, ppos, buf, 2 );
}
static ssize_t vendor_diag_write(struct file *file, const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct hci_dev *hdev = file->private_data;
bool enable;
int err;
err = kstrtobool_from_user(user_buf, count, &enable);
if (err)
return err;
/* When the diagnostic flags are not persistent and the transport
* is not active or in user channel operation , then there is no need
* for the vendor callback . Instead just store the desired value and
* the setting will be programmed when the controller gets powered on .
*/
if (hci_test_quirk(hdev, HCI_QUIRK_NON_PERSISTENT_DIAG) &&
(!test_bit(HCI_RUNNING, &hdev->flags) ||
hci_dev_test_flag(hdev, HCI_USER_CHANNEL)))
goto done;
hci_req_sync_lock(hdev);
err = hdev->set_diag(hdev, enable);
hci_req_sync_unlock(hdev);
if (err < 0 )
return err;
done:
if (enable)
hci_dev_set_flag(hdev, HCI_VENDOR_DIAG);
else
hci_dev_clear_flag(hdev, HCI_VENDOR_DIAG);
return count;
}
static const struct file_operations vendor_diag_fops = {
.open = simple_open,
.read = vendor_diag_read,
.write = vendor_diag_write,
.llseek = default_llseek,
};
void hci_debugfs_create_basic(struct hci_dev *hdev)
{
debugfs_create_file("dut_mode" , 0644 , hdev->debugfs, hdev,
&dut_mode_fops);
if (hdev->set_diag)
debugfs_create_file("vendor_diag" , 0644 , hdev->debugfs, hdev,
&vendor_diag_fops);
}
Messung V0.5 in Prozent C=100 H=99 G=99
¤ Dauer der Verarbeitung: 0.28 Sekunden
(vorverarbeitet am 2026-09-29)
¤
*© Formatika GbR, Deutschland