forked from mirrors/linux
		
	This removes hci_request.{c,h} since it shall no longer be used.
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
		
	
			
		
			
				
	
	
		
			1395 lines
		
	
	
	
		
			33 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1395 lines
		
	
	
	
		
			33 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
   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->quirks) ? '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->quirks))			      \
 | 
						|
		return -EALREADY;					      \
 | 
						|
									      \
 | 
						|
	change_bit(__quirk, &hdev->quirks);				      \
 | 
						|
									      \
 | 
						|
	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 < 0x0006 || val > 0x0c80 || 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 < 0x0006 || val > 0x0c80 || 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 > 0x01f3)
 | 
						|
		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 < 0x000a || val > 0x0c80)
 | 
						|
		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 < 0x01 || val > 0x07)
 | 
						|
		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 < 0x0020 || val > 0x4000 || 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 < 0x0020 || val > 0x4000 || 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 < 0x0001 || val > 0xffff)
 | 
						|
		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 (test_bit(HCI_QUIRK_NON_PERSISTENT_DIAG, &hdev->quirks) &&
 | 
						|
	    (!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);
 | 
						|
}
 |