summaryrefslogtreecommitdiff
path: root/drivers/hv/hv_debugfs.c
blob: 8a28785735820b61f6a85adca84f61022c133f3a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Authors:
 *   Branden Bonaby <brandonbonaby94@gmail.com>
 */

#include <linux/hyperv.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/err.h>

#include "hyperv_vmbus.h"

struct dentry *hv_debug_root;

static int hv_debugfs_delay_get(void *data, u64 *val)
{
	*val = *(u32 *)data;
	return 0;
}

static int hv_debugfs_delay_set(void *data, u64 val)
{
	if (val > 1000)
		return -EINVAL;
	*(u32 *)data = val;
	return 0;
}

DEFINE_DEBUGFS_ATTRIBUTE(hv_debugfs_delay_fops, hv_debugfs_delay_get,
			 hv_debugfs_delay_set, "%llu\n");

static int hv_debugfs_state_get(void *data, u64 *val)
{
	*val = *(bool *)data;
	return 0;
}

static int hv_debugfs_state_set(void *data, u64 val)
{
	if (val == 1)
		*(bool *)data = true;
	else if (val == 0)
		*(bool *)data = false;
	else
		return -EINVAL;
	return 0;
}

DEFINE_DEBUGFS_ATTRIBUTE(hv_debugfs_state_fops, hv_debugfs_state_get,
			 hv_debugfs_state_set, "%llu\n");

/* Setup delay files to store test values */
static int hv_debug_delay_files(struct hv_device *dev, struct dentry *root)
{
	struct vmbus_channel *channel = dev->channel;
	char *buffer = "fuzz_test_buffer_interrupt_delay";
	char *message = "fuzz_test_message_delay";
	int *buffer_val = &channel->fuzz_testing_interrupt_delay;
	int *message_val = &channel->fuzz_testing_message_delay;
	struct dentry *buffer_file, *message_file;

	buffer_file = debugfs_create_file(buffer, 0644, root,
					  buffer_val,
					  &hv_debugfs_delay_fops);
	if (IS_ERR(buffer_file)) {
		pr_debug("debugfs_hyperv: file %s not created\n", buffer);
		return PTR_ERR(buffer_file);
	}

	message_file = debugfs_create_file(message, 0644, root,
					   message_val,
					   &hv_debugfs_delay_fops);
	if (IS_ERR(message_file)) {
		pr_debug("debugfs_hyperv: file %s not created\n", message);
		return PTR_ERR(message_file);
	}

	return 0;
}

/* Setup test state value for vmbus device */
static int hv_debug_set_test_state(struct hv_device *dev, struct dentry *root)
{
	struct vmbus_channel *channel = dev->channel;
	bool *state = &channel->fuzz_testing_state;
	char *status = "fuzz_test_state";
	struct dentry *test_state;

	test_state = debugfs_create_file(status, 0644, root,
					 state,
					 &hv_debugfs_state_fops);
	if (IS_ERR(test_state)) {
		pr_debug("debugfs_hyperv: file %s not created\n", status);
		return PTR_ERR(test_state);
	}

	return 0;
}

/* Bind hv device to a dentry for debugfs */
static void hv_debug_set_dir_dentry(struct hv_device *dev, struct dentry *root)
{
	if (hv_debug_root)
		dev->debug_dir = root;
}

/* Create all test dentry's and names for fuzz testing */
int hv_debug_add_dev_dir(struct hv_device *dev)
{
	const char *device = dev_name(&dev->device);
	char *delay_name = "delay";
	struct dentry *delay, *dev_root;
	int ret;

	if (!IS_ERR(hv_debug_root)) {
		dev_root = debugfs_create_dir(device, hv_debug_root);
		if (IS_ERR(dev_root)) {
			pr_debug("debugfs_hyperv: hyperv/%s/ not created\n",
				 device);
			return PTR_ERR(dev_root);
		}
		hv_debug_set_test_state(dev, dev_root);
		hv_debug_set_dir_dentry(dev, dev_root);
		delay = debugfs_create_dir(delay_name, dev_root);

		if (IS_ERR(delay)) {
			pr_debug("debugfs_hyperv: hyperv/%s/%s/ not created\n",
				 device, delay_name);
			return PTR_ERR(delay);
		}
		ret = hv_debug_delay_files(dev, delay);

		return ret;
	}
	pr_debug("debugfs_hyperv: hyperv/ not in root debugfs path\n");
	return PTR_ERR(hv_debug_root);
}

/* Remove dentry associated with released hv device */
void hv_debug_rm_dev_dir(struct hv_device *dev)
{
	if (!IS_ERR(hv_debug_root))
		debugfs_remove_recursive(dev->debug_dir);
}

/* Remove all dentrys associated with vmbus testing */
void hv_debug_rm_all_dir(void)
{
	debugfs_remove_recursive(hv_debug_root);
}

/* Delay buffer/message reads on a vmbus channel */
void hv_debug_delay_test(struct vmbus_channel *channel, enum delay delay_type)
{
	struct vmbus_channel *test_channel =    channel->primary_channel ?
						channel->primary_channel :
						channel;
	bool state = test_channel->fuzz_testing_state;

	if (state) {
		if (delay_type == 0)
			udelay(test_channel->fuzz_testing_interrupt_delay);
		else
			udelay(test_channel->fuzz_testing_message_delay);
	}
}

/* Initialize top dentry for vmbus testing */
int hv_debug_init(void)
{
	hv_debug_root = debugfs_create_dir("hyperv", NULL);
	if (IS_ERR(hv_debug_root)) {
		pr_debug("debugfs_hyperv: hyperv/ not created\n");
		return PTR_ERR(hv_debug_root);
	}
	return 0;
}