Skip to content

Commit

Permalink
drivers: hv: vmbus: Introduce latency testing
Browse files Browse the repository at this point in the history
Introduce user specified latency in the packet reception path
By exposing the test parameters as part of the debugfs channel
attributes. We will control the testing state via these attributes.

Signed-off-by: Branden Bonaby <[email protected]>
Reviewed-by: Michael Kelley <[email protected]>
Signed-off-by: Sasha Levin <[email protected]>
  • Loading branch information
brandenbonaby authored and Sasha Levin committed Nov 22, 2019
1 parent d21987d commit af9ca6f
Show file tree
Hide file tree
Showing 10 changed files with 269 additions and 0 deletions.
23 changes: 23 additions & 0 deletions Documentation/ABI/testing/debugfs-hyperv
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
What: /sys/kernel/debug/hyperv/<UUID>/fuzz_test_state
Date: October 2019
KernelVersion: 5.5
Contact: Branden Bonaby <[email protected]>
Description: Fuzz testing status of a vmbus device, whether its in an ON
state or a OFF state
Users: Debugging tools

What: /sys/kernel/debug/hyperv/<UUID>/delay/fuzz_test_buffer_interrupt_delay
Date: October 2019
KernelVersion: 5.5
Contact: Branden Bonaby <[email protected]>
Description: Fuzz testing buffer interrupt delay value between 0 - 1000
microseconds (inclusive).
Users: Debugging tools

What: /sys/kernel/debug/hyperv/<UUID>/delay/fuzz_test_message_delay
Date: October 2019
KernelVersion: 5.5
Contact: Branden Bonaby <[email protected]>
Description: Fuzz testing message delay value between 0 - 1000 microseconds
(inclusive).
Users: Debugging tools
1 change: 1 addition & 0 deletions MAINTAINERS
Original file line number Diff line number Diff line change
Expand Up @@ -7578,6 +7578,7 @@ F: include/uapi/linux/hyperv.h
F: include/asm-generic/mshyperv.h
F: tools/hv/
F: Documentation/ABI/stable/sysfs-bus-vmbus
F: Documentation/ABI/testing/debugfs-hyperv

HYPERBUS SUPPORT
M: Vignesh Raghavendra <[email protected]>
Expand Down
1 change: 1 addition & 0 deletions drivers/hv/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ CFLAGS_hv_balloon.o = -I$(src)
hv_vmbus-y := vmbus_drv.o \
hv.o connection.o channel.o \
channel_mgmt.o ring_buffer.o hv_trace.o
hv_vmbus-$(CONFIG_HYPERV_TESTING) += hv_debugfs.o
hv_utils-y := hv_util.o hv_kvp.o hv_snapshot.o hv_fcopy.o hv_utils_transport.o
1 change: 1 addition & 0 deletions drivers/hv/connection.c
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,7 @@ void vmbus_on_event(unsigned long data)

trace_vmbus_on_event(channel);

hv_debug_delay_test(channel, INTERRUPT_DELAY);
do {
void (*callback_fn)(void *);

Expand Down
178 changes: 178 additions & 0 deletions drivers/hv/hv_debugfs.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Authors:
* Branden Bonaby <[email protected]>
*/

#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;
}
31 changes: 31 additions & 0 deletions drivers/hv/hyperv_vmbus.h
Original file line number Diff line number Diff line change
Expand Up @@ -385,4 +385,35 @@ enum hvutil_device_state {
HVUTIL_DEVICE_DYING, /* driver unload is in progress */
};

enum delay {
INTERRUPT_DELAY = 0,
MESSAGE_DELAY = 1,
};

#ifdef CONFIG_HYPERV_TESTING

int hv_debug_add_dev_dir(struct hv_device *dev);
void hv_debug_rm_dev_dir(struct hv_device *dev);
void hv_debug_rm_all_dir(void);
int hv_debug_init(void);
void hv_debug_delay_test(struct vmbus_channel *channel, enum delay delay_type);

#else /* CONFIG_HYPERV_TESTING */

static inline void hv_debug_rm_dev_dir(struct hv_device *dev) {};
static inline void hv_debug_rm_all_dir(void) {};
static inline void hv_debug_delay_test(struct vmbus_channel *channel,
enum delay delay_type) {};
static inline int hv_debug_init(void)
{
return -1;
}

static inline int hv_debug_add_dev_dir(struct hv_device *dev)
{
return -1;
}

#endif /* CONFIG_HYPERV_TESTING */

#endif /* _HYPERV_VMBUS_H */
2 changes: 2 additions & 0 deletions drivers/hv/ring_buffer.c
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,7 @@ struct vmpacket_descriptor *hv_pkt_iter_first(struct vmbus_channel *channel)
struct hv_ring_buffer_info *rbi = &channel->inbound;
struct vmpacket_descriptor *desc;

hv_debug_delay_test(channel, MESSAGE_DELAY);
if (hv_pkt_iter_avail(rbi) < sizeof(struct vmpacket_descriptor))
return NULL;

Expand All @@ -421,6 +422,7 @@ __hv_pkt_iter_next(struct vmbus_channel *channel,
u32 packetlen = desc->len8 << 3;
u32 dsize = rbi->ring_datasize;

hv_debug_delay_test(channel, MESSAGE_DELAY);
/* bump offset to next potential packet */
rbi->priv_read_index += packetlen + VMBUS_PKT_TRAILER;
if (rbi->priv_read_index >= dsize)
Expand Down
6 changes: 6 additions & 0 deletions drivers/hv/vmbus_drv.c
Original file line number Diff line number Diff line change
Expand Up @@ -960,6 +960,8 @@ static void vmbus_device_release(struct device *device)
struct hv_device *hv_dev = device_to_hv_device(device);
struct vmbus_channel *channel = hv_dev->channel;

hv_debug_rm_dev_dir(hv_dev);

mutex_lock(&vmbus_connection.channel_mutex);
hv_process_channel_removal(channel);
mutex_unlock(&vmbus_connection.channel_mutex);
Expand Down Expand Up @@ -1814,6 +1816,7 @@ int vmbus_device_register(struct hv_device *child_device_obj)
pr_err("Unable to register primary channeln");
goto err_kset_unregister;
}
hv_debug_add_dev_dir(child_device_obj);

return 0;

Expand Down Expand Up @@ -2374,6 +2377,7 @@ static int __init hv_acpi_init(void)
ret = -ETIMEDOUT;
goto cleanup;
}
hv_debug_init();

ret = vmbus_bus_init();
if (ret)
Expand Down Expand Up @@ -2410,6 +2414,8 @@ static void __exit vmbus_exit(void)

tasklet_kill(&hv_cpu->msg_dpc);
}
hv_debug_rm_all_dir();

vmbus_free_channels();

if (ms_hyperv.misc_features & HV_FEATURE_GUEST_CRASH_MSR_AVAILABLE) {
Expand Down
19 changes: 19 additions & 0 deletions include/linux/hyperv.h
Original file line number Diff line number Diff line change
Expand Up @@ -934,6 +934,21 @@ struct vmbus_channel {
* full outbound ring buffer.
*/
u64 out_full_first;

/* enabling/disabling fuzz testing on the channel (default is false)*/
bool fuzz_testing_state;

/*
* Interrupt delay will delay the guest from emptying the ring buffer
* for a specific amount of time. The delay is in microseconds and will
* be between 1 to a maximum of 1000, its default is 0 (no delay).
* The Message delay will delay guest reading on a per message basis
* in microseconds between 1 to 1000 with the default being 0
* (no delay).
*/
u32 fuzz_testing_interrupt_delay;
u32 fuzz_testing_message_delay;

};

static inline bool is_hvsock_channel(const struct vmbus_channel *c)
Expand Down Expand Up @@ -1182,6 +1197,10 @@ struct hv_device {

struct vmbus_channel *channel;
struct kset *channels_kset;

/* place holder to keep track of the dir for hv device in debugfs */
struct dentry *debug_dir;

};


Expand Down
7 changes: 7 additions & 0 deletions lib/Kconfig.debug
Original file line number Diff line number Diff line change
Expand Up @@ -2127,4 +2127,11 @@ config IO_STRICT_DEVMEM

source "arch/$(SRCARCH)/Kconfig.debug"

config HYPERV_TESTING
bool "Microsoft Hyper-V driver testing"
default n
depends on HYPERV && DEBUG_FS
help
Select this option to enable Hyper-V vmbus testing.

endmenu # Kernel hacking

0 comments on commit af9ca6f

Please sign in to comment.