#define _GNU_SOURCE
#include <sys/socket.h>

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define LKM_DEVICE "/dev/lkm"

// where does this come from?
#define OBJS_PER_SLAB 42
#define PIPE_BUFFER   (OBJS_PER_SLAB * 10)
#define SPRAY	      (OBJS_PER_SLAB * 100)

int pipes[PIPE_BUFFER][2];
char buffer[0x100];

extern inline size_t
rdtsc_begin(void)
{
	size_t a, d;
	asm volatile("mfence");
	asm volatile("rdtsc" : "=a"(a), "=d"(d));
	a = (d << 32) | a;
	asm volatile("lfence");
	return a;
}

extern inline size_t
rdtsc_end(void)
{
	size_t a, d;
	asm volatile("lfence");
	asm volatile("rdtsc" : "=a"(a), "=d"(d));
	a = (d << 32) | a;
	asm volatile("mfence");
	return a;
}

int
main(void)
{
	printf("main: start\n");
	FILE *device = fopen(LKM_DEVICE, "r+");

	printf("main: spray to reduce TLB noise part 1: %u objects\n",
	    PIPE_BUFFER);
	for (size_t i = 0; i < SPRAY; i++) {
		pipe2(pipes[i], O_NONBLOCK);
		fcntl(pipes[i][0], F_SETPIPE_SZ, 2 << 12);
		write(pipes[i][1], buffer, 8);
	}

	// May need socket and two parts instead
	printf("main: fopen: %p\n", device);
	if (!device) {
		printf(
		    "main: fopen: Error opening %s. Make sure the kernel module is loaded\n",
		    LKM_DEVICE);
		exit(1);
	}

	printf("main: spray to reduce TLB noise part 2: %u objects\n",
	    PIPE_BUFFER);
	size_t t_delta;
	size_t t_prev = -1;
	size_t last_slab = -1;
	for (size_t i = 0; i < PIPE_BUFFER; i++) {
		pipe2(pipes[i], O_NONBLOCK);

		size_t t0 = rdtsc_begin();
		fcntl(pipes[i][0], F_SETPIPE_SZ, 8192);
		size_t t1 = rdtsc_end();

		write(pipes[i][1], buffer, 8);

		t_delta = t0 - t1;

		if (t_delta > (t_prev + 1000)) {
			if (last_slab == (size_t)-1) {
				last_slab = i;
			} else if (i - last_slab == OBJS_PER_SLAB) {
				break;
			} else {
				last_slab = -1;
			}
		}

		t_prev = t_delta;
	}

	for (size_t i = 0; i < PIPE_BUFFER; i++) {
		// Pipe buffer allocation primitive
		pipe2(pipes[i], O_NONBLOCK);
		fcntl(pipes[i][0], F_SETPIPE_SZ, 8192);
		write(pipes[i][1], buffer, 8);
	}

	fclose(device);
	printf("main: done\n");
}