#include <sys/ioctl.h>
 #include <sys/stat.h>
 #include <unistd.h>
+#include <glob.h>
+#include <string.h>
 #include <asm/unistd.h>
 
 #include "../kselftest_harness.h"
 
 const char *data_file = "/sys/kernel/tracing/user_events_data";
 const char *enable_file = "/sys/kernel/tracing/events/user_events/__abi_event/enable";
+const char *multi_dir_glob = "/sys/kernel/tracing/events/user_events_multi/__abi_event.*";
+
+static int wait_for_delete(char *dir)
+{
+       struct stat buf;
+       int i;
+
+       for (i = 0; i < 10000; ++i) {
+               if (stat(dir, &buf) == -1 && errno == ENOENT)
+                       return 0;
+
+               usleep(1000);
+       }
+
+       return -1;
+}
+
+static int find_multi_event_dir(char *unique_field, char *out_dir, int dir_len)
+{
+       char path[256];
+       glob_t buf;
+       int i, ret;
+
+       ret = glob(multi_dir_glob, GLOB_ONLYDIR, NULL, &buf);
+
+       if (ret)
+               return -1;
+
+       ret = -1;
+
+       for (i = 0; i < buf.gl_pathc; ++i) {
+               FILE *fp;
+
+               snprintf(path, sizeof(path), "%s/format", buf.gl_pathv[i]);
+               fp = fopen(path, "r");
+
+               if (!fp)
+                       continue;
+
+               while (fgets(path, sizeof(path), fp) != NULL) {
+                       if (strstr(path, unique_field)) {
+                               fclose(fp);
+                               /* strscpy is not available, use snprintf */
+                               snprintf(out_dir, dir_len, "%s", buf.gl_pathv[i]);
+                               ret = 0;
+                               goto out;
+                       }
+               }
+
+               fclose(fp);
+       }
+out:
+       globfree(&buf);
+
+       return ret;
+}
 
 static bool event_exists(void)
 {
        return ret;
 }
 
+static int reg_enable_multi(void *enable, int size, int bit, int flags,
+                           char *args)
+{
+       struct user_reg reg = {0};
+       char full_args[512] = {0};
+       int fd = open(data_file, O_RDWR);
+       int len;
+       int ret;
+
+       if (fd < 0)
+               return -1;
+
+       len = snprintf(full_args, sizeof(full_args), "__abi_event %s", args);
+
+       if (len > sizeof(full_args)) {
+               ret = -E2BIG;
+               goto out;
+       }
+
+       reg.size = sizeof(reg);
+       reg.name_args = (__u64)full_args;
+       reg.flags = USER_EVENT_REG_MULTI_FORMAT | flags;
+       reg.enable_bit = bit;
+       reg.enable_addr = (__u64)enable;
+       reg.enable_size = size;
+
+       ret = ioctl(fd, DIAG_IOCSREG, ®);
+out:
+       close(fd);
+
+       return ret;
+}
+
 static int reg_enable_flags(void *enable, int size, int bit, int flags)
 {
        struct user_reg reg = {0};
        ASSERT_NE(0, reg_enable(&self->check, 128, 0));
 }
 
+TEST_F(user, multi_format) {
+       char first_dir[256];
+       char second_dir[256];
+       struct stat buf;
+
+       /* Multiple formats for the same name should work */
+       ASSERT_EQ(0, reg_enable_multi(&self->check, sizeof(int), 0,
+                                     0, "u32 multi_first"));
+
+       ASSERT_EQ(0, reg_enable_multi(&self->check, sizeof(int), 1,
+                                     0, "u64 multi_second"));
+
+       /* Same name with same format should also work */
+       ASSERT_EQ(0, reg_enable_multi(&self->check, sizeof(int), 2,
+                                     0, "u64 multi_second"));
+
+       ASSERT_EQ(0, find_multi_event_dir("multi_first",
+                                         first_dir, sizeof(first_dir)));
+
+       ASSERT_EQ(0, find_multi_event_dir("multi_second",
+                                         second_dir, sizeof(second_dir)));
+
+       /* Should not be found in the same dir */
+       ASSERT_NE(0, strcmp(first_dir, second_dir));
+
+       /* First dir should still exist */
+       ASSERT_EQ(0, stat(first_dir, &buf));
+
+       /* Disabling first register should remove first dir */
+       ASSERT_EQ(0, reg_disable(&self->check, 0));
+       ASSERT_EQ(0, wait_for_delete(first_dir));
+
+       /* Second dir should still exist */
+       ASSERT_EQ(0, stat(second_dir, &buf));
+
+       /* Disabling second register should remove second dir */
+       ASSERT_EQ(0, reg_disable(&self->check, 1));
+       /* Ensure bit 1 and 2 are tied together, should not delete yet */
+       ASSERT_EQ(0, stat(second_dir, &buf));
+       ASSERT_EQ(0, reg_disable(&self->check, 2));
+       ASSERT_EQ(0, wait_for_delete(second_dir));
+}
+
 TEST_F(user, forks) {
        int i;