drivers: hv: vmbus: Introduce latency testing
authorBranden Bonaby <brandonbonaby94@gmail.com>
Thu, 3 Oct 2019 21:01:49 +0000 (17:01 -0400)
committerSasha Levin <sashal@kernel.org>
Fri, 22 Nov 2019 01:10:44 +0000 (20:10 -0500)
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 <brandonbonaby94@gmail.com>
Reviewed-by: Michael Kelley <mikelley@microsoft.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
Documentation/ABI/testing/debugfs-hyperv [new file with mode: 0644]
MAINTAINERS
drivers/hv/Makefile
drivers/hv/connection.c
drivers/hv/hv_debugfs.c [new file with mode: 0644]
drivers/hv/hyperv_vmbus.h
drivers/hv/ring_buffer.c
drivers/hv/vmbus_drv.c
include/linux/hyperv.h
lib/Kconfig.debug

diff --git a/Documentation/ABI/testing/debugfs-hyperv b/Documentation/ABI/testing/debugfs-hyperv
new file mode 100644 (file)
index 0000000..9185e1b
--- /dev/null
@@ -0,0 +1,23 @@
+What:           /sys/kernel/debug/hyperv/<UUID>/fuzz_test_state
+Date:           October 2019
+KernelVersion:  5.5
+Contact:        Branden Bonaby <brandonbonaby94@gmail.com>
+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 <brandonbonaby94@gmail.com>
+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 <brandonbonaby94@gmail.com>
+Description:    Fuzz testing message delay value between 0 - 1000 microseconds
+                (inclusive).
+Users:          Debugging tools
index cba1095547fd720f7ca08b4d6fe0ec9c1a9a9807..e7febfbf44abd95b79758a18899a0b1a0736022a 100644 (file)
@@ -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 <vigneshr@ti.com>
index a1eec7177c2d7377d31e47a7474cfd9b814eb223..94daf8240c9591ddf69b1e470ebf15d0aa8c7f0c 100644 (file)
@@ -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
index 0475be4356dd700aa51c57efa9ae01fc13e033dc..e947c39d4cc7df3cb944e48a8071083e98b338cd 100644 (file)
@@ -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 *);
 
diff --git a/drivers/hv/hv_debugfs.c b/drivers/hv/hv_debugfs.c
new file mode 100644 (file)
index 0000000..8a28785
--- /dev/null
@@ -0,0 +1,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;
+}
index af9379a3bf8990fa5ee68f5f0fbddd9391eab3c2..20edcfd3b96c8ddbafc96befc3463859575cb011 100644 (file)
@@ -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 */
index 9a03b163cbbda3ebb4e724216e12d198f4604e73..356e22159e8348e9119e7f35e255d5a3aaa762b6 100644 (file)
@@ -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;
 
@@ -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)
index 0ac874faf7209e29a13aa12ca2a1587b5b29a8d5..125991820278cd567c2688622dcd7338acea5a41 100644 (file)
@@ -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);
@@ -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;
 
@@ -2374,6 +2377,7 @@ static int __init hv_acpi_init(void)
                ret = -ETIMEDOUT;
                goto cleanup;
        }
+       hv_debug_init();
 
        ret = vmbus_bus_init();
        if (ret)
@@ -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) {
index f17f2cd22e39fd2b5ce0af28e3c442a43bcf0031..26f3aeeae1cab6cd434e34dac9fc161e91566932 100644 (file)
@@ -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)
@@ -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;
+
 };
 
 
index 93d97f9b015714493715297fd49a5be1b457df69..55eebbc0b0fb0dbd0998451203f2fe3f06b832ec 100644 (file)
@@ -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