// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2022 Benjamin Tissoires
 */

#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include "hid_bpf_helpers.h"

#define HID_UP_BUTTON		0x0009
#define HID_GD_WHEEL		0x0038

SEC("struct_ops/hid_device_event")
int BPF_PROG(hid_event, struct hid_bpf_ctx *hctx)
{
	__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 9 /* size */);

	if (!data)
		return 0; /* EPERM check */

	/* Touch */
	data[1] &= 0xfd;

	/* X */
	data[4] = 0;
	data[5] = 0;

	/* Y */
	data[6] = 0;
	data[7] = 0;

	return 0;
}

/* 72 == 360 / 5 -> 1 report every 5 degrees */
int resolution = 72;
int physical = 5;

struct haptic_syscall_args {
	unsigned int hid;
	int retval;
};

static __u8 haptic_data[8];

SEC("syscall")
int set_haptic(struct haptic_syscall_args *args)
{
	struct hid_bpf_ctx *ctx;
	const size_t size = sizeof(haptic_data);
	u16 *res;
	int ret;

	if (size > sizeof(haptic_data))
		return -7; /* -E2BIG */

	ctx = hid_bpf_allocate_context(args->hid);
	if (!ctx)
		return -1; /* EPERM check */

	haptic_data[0] = 1;  /* report ID */

	ret = hid_bpf_hw_request(ctx, haptic_data, size, HID_FEATURE_REPORT, HID_REQ_GET_REPORT);

	bpf_printk("probed/remove event ret value: %d", ret);
	bpf_printk("buf: %02x %02x %02x",
		   haptic_data[0],
		   haptic_data[1],
		   haptic_data[2]);
	bpf_printk("     %02x %02x %02x",
		   haptic_data[3],
		   haptic_data[4],
		   haptic_data[5]);
	bpf_printk("     %02x %02x",
		   haptic_data[6],
		   haptic_data[7]);

	/* whenever resolution multiplier is not 3600, we have the fixed report descriptor */
	res = (u16 *)&haptic_data[1];
	if (*res != 3600) {
//		haptic_data[1] = 72; /* resolution multiplier */
//		haptic_data[2] = 0;  /* resolution multiplier */
//		haptic_data[3] = 0;  /* Repeat Count */
		haptic_data[4] = 3;  /* haptic Auto Trigger */
//		haptic_data[5] = 5;  /* Waveform Cutoff Time */
//		haptic_data[6] = 80; /* Retrigger Period */
//		haptic_data[7] = 0;  /* Retrigger Period */
	} else {
		haptic_data[4] = 0;
	}

	ret = hid_bpf_hw_request(ctx, haptic_data, size, HID_FEATURE_REPORT, HID_REQ_SET_REPORT);

	bpf_printk("set haptic ret value: %d -> %d", ret, haptic_data[4]);

	args->retval = ret;

	hid_bpf_release_context(ctx);

	return 0;
}

/* Convert REL_DIAL into REL_WHEEL */
SEC("struct_ops/hid_rdesc_fixup")
int BPF_PROG(hid_rdesc_fixup, struct hid_bpf_ctx *hctx)
{
	__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
	__u16 *res, *phys;

	if (!data)
		return 0; /* EPERM check */

	/* Convert TOUCH into a button */
	data[31] = HID_UP_BUTTON;
	data[33] = 2;

	/* Convert REL_DIAL into REL_WHEEL */
	data[45] = HID_GD_WHEEL;

	/* Change Resolution Multiplier */
	phys = (__u16 *)&data[61];
	*phys = physical;
	res = (__u16 *)&data[66];
	*res = resolution;

	/* Convert X,Y from Abs to Rel */
	data[88] = 0x06;
	data[98] = 0x06;

	return 0;
}

SEC(".struct_ops.link")
struct hid_bpf_ops surface_dial = {
	.hid_rdesc_fixup = (void *)hid_rdesc_fixup,
	.hid_device_event = (void *)hid_event,
};

char _license[] SEC("license") = "GPL";
u32 _version SEC("version") = 1;
