--- /dev/null
+#include <linux/module.h>
+
+/* validate @native and @pcp counter values match @expected */
+#define CHECK(native, pcp, expected)                                    \
+       do {                                                            \
+               WARN((native) != (expected),                            \
+                    "raw %ld (0x%lx) != expected %lld (0x%llx)",       \
+                    (native), (native),                                \
+                    (long long)(expected), (long long)(expected));     \
+               WARN(__this_cpu_read(pcp) != (expected),                \
+                    "pcp %ld (0x%lx) != expected %lld (0x%llx)",       \
+                    __this_cpu_read(pcp), __this_cpu_read(pcp),        \
+                    (long long)(expected), (long long)(expected));     \
+       } while (0)
+
+static DEFINE_PER_CPU(long, long_counter);
+static DEFINE_PER_CPU(unsigned long, ulong_counter);
+
+static int __init percpu_test_init(void)
+{
+       /*
+        * volatile prevents compiler from optimizing it uses, otherwise the
+        * +ul_one/-ul_one below would replace with inc/dec instructions.
+        */
+       volatile unsigned int ui_one = 1;
+       long l = 0;
+       unsigned long ul = 0;
+
+       pr_info("percpu test start\n");
+
+       preempt_disable();
+
+       l += -1;
+       __this_cpu_add(long_counter, -1);
+       CHECK(l, long_counter, -1);
+
+       l += 1;
+       __this_cpu_add(long_counter, 1);
+       CHECK(l, long_counter, 0);
+
+       ul = 0;
+       __this_cpu_write(ulong_counter, 0);
+
+       ul += 1UL;
+       __this_cpu_add(ulong_counter, 1UL);
+       CHECK(ul, ulong_counter, 1);
+
+       ul += -1UL;
+       __this_cpu_add(ulong_counter, -1UL);
+       CHECK(ul, ulong_counter, 0);
+
+       ul += -(unsigned long)1;
+       __this_cpu_add(ulong_counter, -(unsigned long)1);
+       CHECK(ul, ulong_counter, -1);
+
+       ul = 0;
+       __this_cpu_write(ulong_counter, 0);
+
+       ul -= 1;
+       __this_cpu_dec(ulong_counter);
+       CHECK(ul, ulong_counter, -1);
+       CHECK(ul, ulong_counter, ULONG_MAX);
+
+       l += -ui_one;
+       __this_cpu_add(long_counter, -ui_one);
+       CHECK(l, long_counter, 0xffffffff);
+
+       l += ui_one;
+       __this_cpu_add(long_counter, ui_one);
+       CHECK(l, long_counter, (long)0x100000000LL);
+
+
+       l = 0;
+       __this_cpu_write(long_counter, 0);
+
+       l -= ui_one;
+       __this_cpu_sub(long_counter, ui_one);
+       CHECK(l, long_counter, -1);
+
+       l = 0;
+       __this_cpu_write(long_counter, 0);
+
+       l += ui_one;
+       __this_cpu_add(long_counter, ui_one);
+       CHECK(l, long_counter, 1);
+
+       l += -ui_one;
+       __this_cpu_add(long_counter, -ui_one);
+       CHECK(l, long_counter, (long)0x100000000LL);
+
+       l = 0;
+       __this_cpu_write(long_counter, 0);
+
+       l -= ui_one;
+       this_cpu_sub(long_counter, ui_one);
+       CHECK(l, long_counter, -1);
+       CHECK(l, long_counter, ULONG_MAX);
+
+       ul = 0;
+       __this_cpu_write(ulong_counter, 0);
+
+       ul += ui_one;
+       __this_cpu_add(ulong_counter, ui_one);
+       CHECK(ul, ulong_counter, 1);
+
+       ul = 0;
+       __this_cpu_write(ulong_counter, 0);
+
+       ul -= ui_one;
+       __this_cpu_sub(ulong_counter, ui_one);
+       CHECK(ul, ulong_counter, -1);
+       CHECK(ul, ulong_counter, ULONG_MAX);
+
+       ul = 3;
+       __this_cpu_write(ulong_counter, 3);
+
+       ul = this_cpu_sub_return(ulong_counter, ui_one);
+       CHECK(ul, ulong_counter, 2);
+
+       ul = __this_cpu_sub_return(ulong_counter, ui_one);
+       CHECK(ul, ulong_counter, 1);
+
+       preempt_enable();
+
+       pr_info("percpu test done\n");
+       return -EAGAIN;  /* Fail will directly unload the module */
+}
+
+static void __exit percpu_test_exit(void)
+{
+}
+
+module_init(percpu_test_init)
+module_exit(percpu_test_exit)
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Greg Thelen");
+MODULE_DESCRIPTION("percpu operations test");