This commit is contained in:
@@ -0,0 +1 @@
|
||||
accel_test
|
||||
@@ -0,0 +1,75 @@
|
||||
#include <linux/types.h>
|
||||
|
||||
#ifndef __KERNEL__
|
||||
#include <stdint.h>
|
||||
#endif // !__KERNEL__
|
||||
|
||||
/**
|
||||
* [Yeetmouse](https://github.com/AndyFilter/YeetMouse/blob/master/driver/FixedMath/Fixed64.h#L1360)
|
||||
* has better string utils. FP64_ToString there doesn't require
|
||||
* `__udivmodti4` in 64-bit mode.
|
||||
*
|
||||
*/
|
||||
|
||||
typedef int32_t FP_INT;
|
||||
typedef int64_t FP_LONG;
|
||||
|
||||
#define FP64_Shift 32
|
||||
|
||||
static inline FP_LONG FP64_Mul(FP_LONG a, FP_LONG b) {
|
||||
return (FP_LONG)(((__int128_t)a * (__int128_t)b) >> FP64_Shift);
|
||||
}
|
||||
|
||||
static char *FP_64_itoa_loop(char *buf, uint64_t scale, uint64_t value,
|
||||
int skip) {
|
||||
while (scale) {
|
||||
unsigned digit = (value / scale);
|
||||
|
||||
if (!skip || digit || scale == 1) {
|
||||
skip = 0;
|
||||
*buf++ = '0' + digit;
|
||||
value %= scale;
|
||||
}
|
||||
|
||||
scale /= 10;
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
void FP64_ToString(FP_LONG value, char *buf);
|
||||
|
||||
void FP64_ToString(FP_LONG value, char *buf) {
|
||||
uint64_t uvalue = (value >= 0) ? value : -value;
|
||||
if (value < 0)
|
||||
*buf++ = '-';
|
||||
|
||||
#define SCALE 9
|
||||
|
||||
static const uint64_t FP_64_scales[10] = {
|
||||
/* 18 decimals is enough for full 64bit fixed point precision */
|
||||
1, 10, 100, 1000, 10000,
|
||||
100000, 1000000, 10000000, 100000000, 1000000000};
|
||||
|
||||
/* Separate the integer and decimal parts of the value */
|
||||
uint64_t intpart = uvalue >> 32;
|
||||
uint64_t fracpart = uvalue & 0xFFFFFFFF;
|
||||
uint64_t scale = FP_64_scales[SCALE];
|
||||
fracpart = FP64_Mul(fracpart, scale);
|
||||
|
||||
if (fracpart >= scale) {
|
||||
/* Handle carry from decimal part */
|
||||
intpart++;
|
||||
fracpart -= scale;
|
||||
}
|
||||
|
||||
/* Format integer part */
|
||||
buf = FP_64_itoa_loop(buf, 1000000000, intpart, 1);
|
||||
|
||||
/* Format decimal part (if any) */
|
||||
if (scale != 1) {
|
||||
*buf++ = '.';
|
||||
buf = FP_64_itoa_loop(buf, scale / 10, fracpart, 0);
|
||||
}
|
||||
|
||||
*buf = '\0';
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
ifneq ($(KERNELRELEASE),)
|
||||
obj-m := maccel.o
|
||||
ccflags-y += $(DRIVER_CFLAGS)
|
||||
endif
|
||||
|
||||
KVER ?= $(shell uname -r)
|
||||
KDIR ?= /lib/modules/$(KVER)/build
|
||||
|
||||
DRIVER_CFLAGS ?= -DFIXEDPT_BITS=$(shell getconf LONG_BIT)
|
||||
|
||||
ifneq ($(CC),clang)
|
||||
CC=gcc
|
||||
else
|
||||
export LLVM=1
|
||||
endif
|
||||
|
||||
build:
|
||||
$(MAKE) CC=$(CC) -C $(KDIR) M=$(CURDIR)
|
||||
|
||||
build_debug: DRIVER_CFLAGS += -g -DDEBUG
|
||||
build_debug: build
|
||||
|
||||
clean:
|
||||
$(MAKE) -C $(KDIR) M=$(CURDIR) clean
|
||||
|
||||
test_debug: DRIVER_CFLAGS += -g -DDEBUG
|
||||
test_debug: test
|
||||
|
||||
test: **/*.test.c
|
||||
@mkdir -p tests/snapshots
|
||||
DRIVER_CFLAGS="$(DRIVER_CFLAGS)" TEST_NAME=$(name) sh tests/run_tests.sh
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
#ifndef _ACCEL_H_
|
||||
#define _ACCEL_H_
|
||||
|
||||
#include "accel/linear.h"
|
||||
#include "accel/mode.h"
|
||||
#include "accel/natural.h"
|
||||
#include "accel/synchronous.h"
|
||||
#include "dbg.h"
|
||||
#include "fixedptc.h"
|
||||
#include "math.h"
|
||||
#include "speed.h"
|
||||
|
||||
struct no_accel_curve_args {};
|
||||
|
||||
union __accel_args {
|
||||
struct natural_curve_args natural;
|
||||
struct linear_curve_args linear;
|
||||
struct synchronous_curve_args synchronous;
|
||||
struct no_accel_curve_args no_accel;
|
||||
};
|
||||
|
||||
struct accel_args {
|
||||
fpt sens_mult;
|
||||
fpt yx_ratio;
|
||||
fpt input_dpi;
|
||||
fpt angle_rotation_deg;
|
||||
|
||||
enum accel_mode tag;
|
||||
union __accel_args args;
|
||||
};
|
||||
|
||||
const fpt NORMALIZED_DPI = fpt_fromint(1000);
|
||||
|
||||
/**
|
||||
* Calculate the factor by which to multiply the input vector
|
||||
* in order to get the desired output speed.
|
||||
*
|
||||
*/
|
||||
static inline struct vector sensitivity(fpt input_speed,
|
||||
struct accel_args args) {
|
||||
fpt sens;
|
||||
|
||||
switch (args.tag) {
|
||||
case synchronous:
|
||||
dbg("accel mode %d: synchronous", args.tag);
|
||||
sens = __synchronous_sens_fun(input_speed, args.args.synchronous);
|
||||
break;
|
||||
case natural:
|
||||
dbg("accel mode %d: natural", args.tag);
|
||||
sens = __natural_sens_fun(input_speed, args.args.natural);
|
||||
break;
|
||||
case linear:
|
||||
dbg("accel mode %d: linear", args.tag);
|
||||
sens = __linear_sens_fun(input_speed, args.args.linear);
|
||||
break;
|
||||
case no_accel:
|
||||
dbg("accel mode %d: no_accel", args.tag);
|
||||
sens = FIXEDPT_ONE;
|
||||
break;
|
||||
default:
|
||||
sens = FIXEDPT_ONE;
|
||||
}
|
||||
sens = fpt_mul(sens, args.sens_mult);
|
||||
return (struct vector){sens, fpt_mul(sens, args.yx_ratio)};
|
||||
}
|
||||
|
||||
const fpt DEG_TO_RAD_FACTOR = fpt_xdiv(FIXEDPT_PI, fpt_rconst(180));
|
||||
|
||||
static inline void f_accelerate(int *x, int *y, fpt time_interval_ms,
|
||||
struct accel_args args) {
|
||||
static fpt carry_x = 0;
|
||||
static fpt carry_y = 0;
|
||||
|
||||
fpt dx = fpt_fromint(*x);
|
||||
fpt dy = fpt_fromint(*y);
|
||||
|
||||
{
|
||||
if (args.angle_rotation_deg == 0) {
|
||||
goto accel_routine;
|
||||
}
|
||||
// Add rotation of input vector
|
||||
fpt degrees = args.angle_rotation_deg;
|
||||
fpt radians =
|
||||
fpt_mul(degrees, DEG_TO_RAD_FACTOR); // Convert degrees to radians
|
||||
|
||||
fpt cos_angle = fpt_cos(radians);
|
||||
fpt sin_angle = fpt_sin(radians);
|
||||
|
||||
dbg("rotation angle(deg): %s deg", fptoa(degrees));
|
||||
dbg("rotation angle(rad): %s rad", fptoa(radians));
|
||||
dbg("cosine of rotation: %s", fptoa(cos_angle));
|
||||
dbg("sine of rotation: %s", fptoa(sin_angle));
|
||||
|
||||
// Rotate input vector
|
||||
fpt dx_rot = fpt_mul(dx, cos_angle) - fpt_mul(dy, sin_angle);
|
||||
fpt dy_rot = fpt_mul(dx, sin_angle) + fpt_mul(dy, cos_angle);
|
||||
|
||||
dbg("rotated x: %s", fptoa(dx_rot));
|
||||
dbg("rotated y: %s", fptoa(dy_rot));
|
||||
|
||||
dx = dx_rot;
|
||||
dy = dy_rot;
|
||||
}
|
||||
accel_routine:
|
||||
|
||||
dbg("in (%d, %d)", *x, *y);
|
||||
dbg("in: x (fpt conversion) %s", fptoa(dx));
|
||||
dbg("in: y (fpt conversion) %s", fptoa(dy));
|
||||
|
||||
fpt dpi_factor = fpt_div(NORMALIZED_DPI, args.input_dpi);
|
||||
dbg("dpi adjustment factor: %s", fptoa(dpi_factor));
|
||||
dx = fpt_mul(dx, dpi_factor);
|
||||
dy = fpt_mul(dy, dpi_factor);
|
||||
|
||||
fpt speed_in = input_speed(dx, dy, time_interval_ms);
|
||||
struct vector sens = sensitivity(speed_in, args);
|
||||
dbg("scale x %s", fptoa(sens.x));
|
||||
dbg("scale y %s", fptoa(sens.y));
|
||||
|
||||
fpt dx_out = fpt_mul(dx, sens.x);
|
||||
fpt dy_out = fpt_mul(dy, sens.y);
|
||||
|
||||
dx_out = fpt_add(dx_out, carry_x);
|
||||
dy_out = fpt_add(dy_out, carry_y);
|
||||
|
||||
dbg("out: x %s", fptoa(dx_out));
|
||||
dbg("out: y %s", fptoa(dy_out));
|
||||
|
||||
*x = fpt_toint(dx_out);
|
||||
*y = fpt_toint(dy_out);
|
||||
|
||||
dbg("out (int conversion) (%d, %d)", *x, *y);
|
||||
|
||||
carry_x = fpt_sub(dx_out, fpt_fromint(*x));
|
||||
carry_y = fpt_sub(dy_out, fpt_fromint(*y));
|
||||
|
||||
dbg("carry (%s, %s)", fptoa(carry_x), fptoa(carry_x));
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,50 @@
|
||||
#ifndef __ACCEL_LINEAR_H_
|
||||
#define __ACCEL_LINEAR_H_
|
||||
|
||||
#include "../dbg.h"
|
||||
#include "../fixedptc.h"
|
||||
#include "../math.h"
|
||||
|
||||
struct linear_curve_args {
|
||||
fpt accel;
|
||||
fpt offset;
|
||||
fpt output_cap;
|
||||
};
|
||||
|
||||
static inline fpt linear_base_fn(fpt x, fpt accel,
|
||||
fpt input_offset) {
|
||||
fpt _x = x - input_offset;
|
||||
fpt _x_square = fpt_mul(
|
||||
_x, _x); // because linear in rawaccel is classic with exponent = 2
|
||||
return fpt_mul(accel, fpt_div(_x_square, x));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sensitivity Function for Linear Acceleration
|
||||
*/
|
||||
static inline fpt __linear_sens_fun(fpt input_speed,
|
||||
struct linear_curve_args args) {
|
||||
dbg("linear: accel %s", fptoa(args.accel));
|
||||
dbg("linear: offset %s", fptoa(args.offset));
|
||||
dbg("linear: output_cap %s", fptoa(args.output_cap));
|
||||
|
||||
if (input_speed <= args.offset) {
|
||||
return FIXEDPT_ONE;
|
||||
}
|
||||
|
||||
fpt sens = linear_base_fn(input_speed, args.accel, args.offset);
|
||||
dbg("linear: base_fn sens %s", fptoa(args.accel));
|
||||
|
||||
fpt sign = FIXEDPT_ONE;
|
||||
if (args.output_cap > 0) {
|
||||
fpt cap = fpt_sub(args.output_cap, FIXEDPT_ONE);
|
||||
if (cap < 0) {
|
||||
cap = -cap;
|
||||
sign = -sign;
|
||||
}
|
||||
sens = minsd(sens, cap);
|
||||
}
|
||||
|
||||
return fpt_add(FIXEDPT_ONE, fpt_mul(sign, sens));
|
||||
}
|
||||
#endif // !__ACCEL_LINEAR_H_
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef __ACCEL_MODE_H
|
||||
#define __ACCEL_MODE_H
|
||||
|
||||
enum accel_mode : unsigned char { linear, natural, synchronous, no_accel };
|
||||
|
||||
#endif // !__ACCEL_MODE_H
|
||||
@@ -0,0 +1,48 @@
|
||||
#ifndef __ACCEL_NATURAL_H_
|
||||
#define __ACCEL_NATURAL_H_
|
||||
|
||||
#include "../fixedptc.h"
|
||||
|
||||
struct natural_curve_args {
|
||||
fpt decay_rate;
|
||||
fpt offset;
|
||||
fpt limit;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gain Function for Natural Acceleration
|
||||
*/
|
||||
static inline fpt __natural_sens_fun(fpt input_speed,
|
||||
struct natural_curve_args args) {
|
||||
dbg("natural: decay_rate %s", fptoa(args.decay_rate));
|
||||
dbg("natural: offset %s", fptoa(args.offset));
|
||||
dbg("natural: limit %s", fptoa(args.limit));
|
||||
if (input_speed <= args.offset) {
|
||||
return FIXEDPT_ONE;
|
||||
}
|
||||
|
||||
if (args.limit <= FIXEDPT_ONE) {
|
||||
return FIXEDPT_ONE;
|
||||
}
|
||||
|
||||
if (args.decay_rate <= 0) {
|
||||
return FIXEDPT_ONE;
|
||||
}
|
||||
|
||||
fpt limit = args.limit - FIXEDPT_ONE;
|
||||
fpt accel = fpt_div(args.decay_rate, fpt_abs(limit));
|
||||
fpt constant = fpt_div(-limit, accel);
|
||||
|
||||
dbg("natural: constant %s", fptoa(constant));
|
||||
|
||||
fpt offset_x = args.offset - input_speed;
|
||||
fpt decay = fpt_exp(fpt_mul(accel, offset_x));
|
||||
|
||||
dbg("natural: decay %s", fptoa(decay));
|
||||
|
||||
fpt output_denom = fpt_div(decay, accel) - offset_x;
|
||||
fpt output = fpt_mul(limit, output_denom) + constant;
|
||||
|
||||
return fpt_div(output, input_speed) + FIXEDPT_ONE;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,64 @@
|
||||
#ifndef __ACCEL_SYNCHRONOUS_H_
|
||||
#define __ACCEL_SYNCHRONOUS_H_
|
||||
|
||||
#include "../fixedptc.h"
|
||||
|
||||
struct synchronous_curve_args {
|
||||
fpt gamma;
|
||||
fpt smooth;
|
||||
fpt motivity;
|
||||
fpt sync_speed;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sensitivity Function for `Synchronous` Acceleration
|
||||
*/
|
||||
static inline fpt __synchronous_sens_fun(fpt input_speed,
|
||||
struct synchronous_curve_args args) {
|
||||
fpt log_motivity = fpt_ln(args.motivity);
|
||||
fpt gamma_const = fpt_div(args.gamma, log_motivity);
|
||||
fpt log_syncspeed = fpt_ln(args.sync_speed);
|
||||
fpt syncspeed = args.sync_speed;
|
||||
fpt sharpness = args.smooth == 0 ? fpt_rconst(16.0)
|
||||
: fpt_div(fpt_rconst(0.5), args.smooth);
|
||||
int use_linear_clamp = sharpness >= fpt_rconst(16.0);
|
||||
fpt sharpness_recip = fpt_div(FIXEDPT_ONE, sharpness);
|
||||
fpt minimum_sens = fpt_div(FIXEDPT_ONE, args.motivity);
|
||||
fpt maximum_sens = args.motivity;
|
||||
|
||||
// if sharpness >= 16, use linear clamp for activation function.
|
||||
// linear clamp means: fpt_clamp(input_speed, -1, 1).
|
||||
if (use_linear_clamp) {
|
||||
fpt log_space = fpt_mul(gamma_const, (fpt_ln(input_speed) - log_syncspeed));
|
||||
|
||||
if (log_space < -FIXEDPT_ONE) {
|
||||
return minimum_sens;
|
||||
}
|
||||
|
||||
if (log_space > FIXEDPT_ONE) {
|
||||
return maximum_sens;
|
||||
}
|
||||
|
||||
return fpt_exp(fpt_mul(log_space, log_motivity));
|
||||
}
|
||||
|
||||
if (input_speed == syncspeed) {
|
||||
return FIXEDPT_ONE;
|
||||
}
|
||||
|
||||
fpt log_x = fpt_ln(input_speed);
|
||||
fpt log_diff = log_x - log_syncspeed;
|
||||
|
||||
if (log_diff > 0) {
|
||||
fpt log_space = fpt_mul(gamma_const, log_diff);
|
||||
fpt exponent =
|
||||
fpt_pow(fpt_tanh(fpt_pow(log_space, sharpness)), sharpness_recip);
|
||||
return fpt_exp(fpt_mul(exponent, log_motivity));
|
||||
} else {
|
||||
fpt log_space = fpt_mul(-gamma_const, log_diff);
|
||||
fpt exponent =
|
||||
-fpt_pow(fpt_tanh(fpt_pow(log_space, sharpness)), sharpness_recip);
|
||||
return fpt_exp(fpt_mul(exponent, log_motivity));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,87 @@
|
||||
#ifndef _ACCELK_H_
|
||||
#define _ACCELK_H_
|
||||
|
||||
#include "accel.h"
|
||||
#include "accel/linear.h"
|
||||
#include "accel/mode.h"
|
||||
#include "fixedptc.h"
|
||||
#include "linux/ktime.h"
|
||||
#include "params.h"
|
||||
#include "speed.h"
|
||||
|
||||
static struct accel_args collect_args(void) {
|
||||
struct accel_args accel = {0};
|
||||
|
||||
enum accel_mode mode = PARAM_MODE;
|
||||
accel.tag = mode;
|
||||
|
||||
accel.sens_mult = atofp(PARAM_SENS_MULT);
|
||||
accel.yx_ratio = atofp(PARAM_YX_RATIO);
|
||||
accel.input_dpi = atofp(PARAM_INPUT_DPI);
|
||||
accel.angle_rotation_deg = atofp(PARAM_ANGLE_ROTATION);
|
||||
|
||||
switch (mode) {
|
||||
case synchronous: {
|
||||
accel.args.synchronous.gamma = atofp(PARAM_GAMMA);
|
||||
accel.args.synchronous.smooth = atofp(PARAM_SMOOTH);
|
||||
accel.args.synchronous.motivity = atofp(PARAM_MOTIVITY);
|
||||
accel.args.synchronous.sync_speed = atofp(PARAM_SYNC_SPEED);
|
||||
break;
|
||||
}
|
||||
case natural: {
|
||||
accel.args.natural.decay_rate = atofp(PARAM_DECAY_RATE);
|
||||
accel.args.natural.offset = atofp(PARAM_OFFSET);
|
||||
accel.args.natural.limit = atofp(PARAM_LIMIT);
|
||||
break;
|
||||
}
|
||||
case linear: {
|
||||
accel.args.linear.accel = atofp(PARAM_ACCEL);
|
||||
accel.args.linear.offset = atofp(PARAM_OFFSET);
|
||||
accel.args.linear.output_cap = atofp(PARAM_OUTPUT_CAP);
|
||||
break;
|
||||
}
|
||||
case no_accel:
|
||||
default: {
|
||||
}
|
||||
};
|
||||
return accel;
|
||||
}
|
||||
|
||||
#if FIXEDPT_BITS == 64
|
||||
const fpt UNIT_PER_MS = fpt_rconst(1000000); // 1 million nanoseconds
|
||||
#else
|
||||
const fpt UNIT_PER_MS = fpt_rconst(1000); // 1 thousand microsends
|
||||
#endif
|
||||
|
||||
static inline void accelerate(int *x, int *y) {
|
||||
dbg("FIXEDPT_BITS = %d", FIXEDPT_BITS);
|
||||
|
||||
static ktime_t last_time;
|
||||
ktime_t now = ktime_get();
|
||||
|
||||
#if FIXEDPT_BITS == 64
|
||||
s64 unit_time = ktime_to_ns(now - last_time);
|
||||
dbg("ktime interval -> now (%llu) vs last_ktime (%llu), diff = %llins", now,
|
||||
last_time, unit_time);
|
||||
#else
|
||||
s64 unit_time = ktime_to_us(now - last_time);
|
||||
dbg("ktime interval -> now (%llu) vs last_ktime (%llu), diff = %llius", now,
|
||||
last_time, unit_time);
|
||||
#endif
|
||||
last_time = now;
|
||||
|
||||
fpt _unit_time = fpt_fromint(unit_time);
|
||||
fpt millisecond = fpt_div(_unit_time, UNIT_PER_MS);
|
||||
|
||||
#if FIXEDPT_BITS == 64
|
||||
dbg("ktime interval -> converting to ns: %lluns -> %sms", unit_time,
|
||||
fptoa(millisecond));
|
||||
#else
|
||||
dbg("ktime interval, converting to us: %lluus -> %sms", unit_time,
|
||||
fptoa(millisecond));
|
||||
#endif
|
||||
|
||||
return f_accelerate(x, y, millisecond, collect_args());
|
||||
}
|
||||
|
||||
#endif // !_ACCELK_H_
|
||||
@@ -0,0 +1,6 @@
|
||||
#include "accel.h"
|
||||
|
||||
extern inline struct vector sensitivity_rs(fpt input_speed,
|
||||
struct accel_args args) {
|
||||
return sensitivity(input_speed, args);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
#ifdef DEBUG
|
||||
#define DEBUG_TEST 1
|
||||
#else
|
||||
#define DEBUG_TEST 0
|
||||
#endif
|
||||
|
||||
#ifdef __KERNEL__
|
||||
#include <linux/printk.h>
|
||||
#define dbg(fmt, ...) dbg_k(fmt, __VA_ARGS__)
|
||||
#else
|
||||
#include <stdio.h>
|
||||
#define dbg(fmt, ...) dbg_std(fmt, __VA_ARGS__)
|
||||
#endif
|
||||
|
||||
#if defined __KERNEL__ && defined __clang__
|
||||
#define dbg_k(fmt, ...) \
|
||||
_Pragma("clang diagnostic push") \
|
||||
_Pragma("clang diagnostic ignored \"-Wstatic-local-in-inline\"") do { \
|
||||
if (DEBUG_TEST) \
|
||||
printk(KERN_INFO "%s:%d:%s(): " #fmt "\n", __FILE__, __LINE__, __func__, \
|
||||
__VA_ARGS__); \
|
||||
} \
|
||||
while (0) \
|
||||
_Pragma("clang diagnostic pop")
|
||||
#elif defined __KERNEL__
|
||||
#define dbg_k(fmt, ...) \
|
||||
do { \
|
||||
if (DEBUG_TEST) \
|
||||
printk(KERN_INFO "%s:%d:%s(): " #fmt "\n", __FILE__, __LINE__, __func__, \
|
||||
__VA_ARGS__); \
|
||||
} while (0)
|
||||
#else
|
||||
#define dbg_std(fmt, ...) \
|
||||
do { \
|
||||
if (DEBUG_TEST) \
|
||||
fprintf(stderr, "%s:%d:%s(): " fmt "\n", __FILE__, __LINE__, __func__, \
|
||||
__VA_ARGS__); \
|
||||
} while (0)
|
||||
#endif
|
||||
@@ -0,0 +1,437 @@
|
||||
#ifndef _FIXEDPTC_H_
|
||||
#define _FIXEDPTC_H_
|
||||
|
||||
/*
|
||||
* fixedptc.h is a 32-bit or 64-bit fixed point numeric library.
|
||||
*
|
||||
* The symbol FIXEDPT_BITS, if defined before this library header file
|
||||
* is included, determines the number of bits in the data type (its "width").
|
||||
* The default width is 32-bit (FIXEDPT_BITS=32) and it can be used
|
||||
* on any recent C99 compiler. The 64-bit precision (FIXEDPT_BITS=64) is
|
||||
* available on compilers which implement 128-bit "long long" types. This
|
||||
* precision has been tested on GCC 4.2+.
|
||||
*
|
||||
* The FIXEDPT_WBITS symbols governs how many bits are dedicated to the
|
||||
* "whole" part of the number (to the left of the decimal point). The larger
|
||||
* this width is, the larger the numbers which can be stored in the fixedpt
|
||||
* number. The rest of the bits (available in the FIXEDPT_FBITS symbol) are
|
||||
* dedicated to the fraction part of the number (to the right of the decimal
|
||||
* point).
|
||||
*
|
||||
* Since the number of bits in both cases is relatively low, many complex
|
||||
* functions (more complex than div & mul) take a large hit on the precision
|
||||
* of the end result because errors in precision accumulate.
|
||||
* This loss of precision can be lessened by increasing the number of
|
||||
* bits dedicated to the fraction part, but at the loss of range.
|
||||
*
|
||||
* Adventurous users might utilize this library to build two data types:
|
||||
* one which has the range, and one which has the precision, and carefully
|
||||
* convert between them (including adding two number of each type to produce
|
||||
* a simulated type with a larger range and precision).
|
||||
*
|
||||
* The ideas and algorithms have been cherry-picked from a large number
|
||||
* of previous implementations available on the Internet.
|
||||
* Tim Hartrick has contributed cleanup and 64-bit support patches.
|
||||
*
|
||||
* == Special notes for the 32-bit precision ==
|
||||
* Signed 32-bit fixed point numeric library for the 24.8 format.
|
||||
* The specific limits are -8388608.999... to 8388607.999... and the
|
||||
* most precise number is 0.00390625. In practice, you should not count
|
||||
* on working with numbers larger than a million or to the precision
|
||||
* of more than 2 decimal places. Make peace with the fact that PI
|
||||
* is 3.14 here. :)
|
||||
*/
|
||||
|
||||
/*-
|
||||
* Copyright (c) 2010-2012 Ivan Voras <ivoras@freebsd.org>
|
||||
* Copyright (c) 2012 Tim Hartrick <tim@edgecast.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
#ifndef FIXEDPT_BITS
|
||||
#define FIXEDPT_BITS 64
|
||||
#endif
|
||||
|
||||
#ifdef __KERNEL__
|
||||
#include <linux/math64.h>
|
||||
#include <linux/stddef.h>
|
||||
#include <linux/types.h>
|
||||
#else
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
|
||||
#if FIXEDPT_BITS == 32
|
||||
typedef int32_t fpt;
|
||||
typedef int64_t fptd;
|
||||
typedef uint32_t fptu;
|
||||
typedef uint64_t fptud;
|
||||
|
||||
#define FIXEDPT_WBITS 16
|
||||
|
||||
#elif FIXEDPT_BITS == 64
|
||||
#include "Fixed64.utils.h"
|
||||
|
||||
typedef int64_t fpt;
|
||||
typedef __int128_t fptd;
|
||||
typedef uint64_t fptu;
|
||||
typedef __uint128_t fptud;
|
||||
|
||||
#define FIXEDPT_WBITS 32
|
||||
#else
|
||||
#error "FIXEDPT_BITS must be equal to 32 or 64"
|
||||
#endif
|
||||
|
||||
#if FIXEDPT_WBITS >= FIXEDPT_BITS
|
||||
#error "FIXEDPT_WBITS must be less than or equal to FIXEDPT_BITS"
|
||||
#endif
|
||||
|
||||
#define FIXEDPT_VCSID "$Id$"
|
||||
|
||||
#define FIXEDPT_FBITS (FIXEDPT_BITS - FIXEDPT_WBITS)
|
||||
#define FIXEDPT_FMASK (((fpt)1 << FIXEDPT_FBITS) - 1)
|
||||
|
||||
#define fpt_rconst(R) ((fpt)((R) * FIXEDPT_ONE + ((R) >= 0 ? 0.5 : -0.5)))
|
||||
#define fpt_fromint(I) ((fptd)(I) << FIXEDPT_FBITS)
|
||||
#define fpt_toint(F) ((F) >> FIXEDPT_FBITS)
|
||||
#define fpt_add(A, B) ((A) + (B))
|
||||
#define fpt_sub(A, B) ((A) - (B))
|
||||
#define fpt_xmul(A, B) ((fpt)(((fptd)(A) * (fptd)(B)) >> FIXEDPT_FBITS))
|
||||
#define fpt_xdiv(A, B) ((fpt)(((fptd)(A) << FIXEDPT_FBITS) / (fptd)(B)))
|
||||
#define fpt_fracpart(A) ((fpt)(A) & FIXEDPT_FMASK)
|
||||
|
||||
#define FIXEDPT_ONE ((fpt)((fpt)1 << FIXEDPT_FBITS))
|
||||
#define FIXEDPT_ONE_HALF (FIXEDPT_ONE >> 1)
|
||||
#define FIXEDPT_TWO (FIXEDPT_ONE + FIXEDPT_ONE)
|
||||
#define FIXEDPT_PI fpt_rconst(3.14159265358979323846)
|
||||
#define FIXEDPT_TWO_PI fpt_rconst(2 * 3.14159265358979323846)
|
||||
#define FIXEDPT_HALF_PI fpt_rconst(3.14159265358979323846 / 2)
|
||||
#define FIXEDPT_E fpt_rconst(2.7182818284590452354)
|
||||
|
||||
#define fpt_abs(A) ((A) < 0 ? -(A) : (A))
|
||||
|
||||
/* fpt is meant to be usable in environments without floating point support
|
||||
* (e.g. microcontrollers, kernels), so we can't use floating point types
|
||||
* directly. Putting them only in macros will effectively make them optional. */
|
||||
#define fpt_tofloat(T) \
|
||||
((float)((T) * ((float)(1) / (float)(1L << FIXEDPT_FBITS))))
|
||||
|
||||
#define fpt_todouble(T) \
|
||||
((double)((T) * ((double)(1) / (double)(1L << FIXEDPT_FBITS))))
|
||||
|
||||
/* Multiplies two fpt numbers, returns the result. */
|
||||
static inline fpt fpt_mul(fpt A, fpt B) {
|
||||
return (((fptd)A * (fptd)B) >> FIXEDPT_FBITS);
|
||||
}
|
||||
|
||||
#if FIXEDPT_BITS == 64
|
||||
static inline fpt div128_s64_s64(fpt dividend, fpt divisor) {
|
||||
fpt high = dividend >> FIXEDPT_FBITS;
|
||||
fpt low = dividend << FIXEDPT_FBITS;
|
||||
|
||||
fpt result = div128_s64_s64_s64(high, low, divisor);
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Divides two fpt numbers, returns the result. */
|
||||
static inline fpt fpt_div(fpt A, fpt B) {
|
||||
#if FIXEDPT_BITS == 64
|
||||
return div128_s64_s64(A, B);
|
||||
#endif
|
||||
return (((fptd)A << FIXEDPT_FBITS) / (fptd)B);
|
||||
}
|
||||
|
||||
/*
|
||||
* Note: adding and subtracting fpt numbers can be done by using
|
||||
* the regular integer operators + and -.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Convert the given fpt number to a decimal string.
|
||||
* The max_dec argument specifies how many decimal digits to the right
|
||||
* of the decimal point to generate. If set to -1, the "default" number
|
||||
* of decimal digits will be used (2 for 32-bit fpt width, 10 for
|
||||
* 64-bit fpt width); If set to -2, "all" of the digits will
|
||||
* be returned, meaning there will be invalid, bogus digits outside the
|
||||
* specified precisions.
|
||||
*/
|
||||
static inline void fpt_str(fpt A, char *str, int max_dec) {
|
||||
int ndec = 0, slen = 0;
|
||||
char tmp[12] = {0};
|
||||
fptud fr, ip;
|
||||
const fptud one = (fptud)1 << FIXEDPT_BITS;
|
||||
const fptud mask = one - 1;
|
||||
|
||||
if (max_dec == -1)
|
||||
#if FIXEDPT_BITS == 32
|
||||
#if FIXEDPT_WBITS > 16
|
||||
max_dec = 2;
|
||||
#else
|
||||
max_dec = 4;
|
||||
#endif
|
||||
#elif FIXEDPT_BITS == 64
|
||||
max_dec = 10;
|
||||
#else
|
||||
#error Invalid width
|
||||
#endif
|
||||
else if (max_dec == -2)
|
||||
max_dec = 15;
|
||||
|
||||
if (A < 0) {
|
||||
str[slen++] = '-';
|
||||
A *= -1;
|
||||
}
|
||||
|
||||
ip = fpt_toint(A);
|
||||
do {
|
||||
tmp[ndec++] = '0' + ip % 10;
|
||||
ip /= 10;
|
||||
} while (ip != 0);
|
||||
|
||||
while (ndec > 0)
|
||||
str[slen++] = tmp[--ndec];
|
||||
str[slen++] = '.';
|
||||
|
||||
fr = (fpt_fracpart(A) << FIXEDPT_WBITS) & mask;
|
||||
do {
|
||||
fr = (fr & mask) * 10;
|
||||
|
||||
str[slen++] = '0' + (fr >> FIXEDPT_BITS) % 10;
|
||||
ndec++;
|
||||
} while (fr != 0 && ndec < max_dec);
|
||||
|
||||
if (ndec > 1 && str[slen - 1] == '0')
|
||||
str[slen - 1] = '\0'; /* cut off trailing 0 */
|
||||
else
|
||||
str[slen] = '\0';
|
||||
}
|
||||
|
||||
/* Converts the given fpt number into a string, using a static
|
||||
* (non-threadsafe) string buffer */
|
||||
static inline char *fptoa(const fpt A) {
|
||||
static char str[25];
|
||||
#if FIXEDPT_BITS == 64
|
||||
FP64_ToString(A, str);
|
||||
#else
|
||||
fpt_str(A, str, 10);
|
||||
#endif
|
||||
return (str);
|
||||
}
|
||||
|
||||
static fpt atofp(char *num_string) {
|
||||
fptu n = 0;
|
||||
int sign = 0;
|
||||
|
||||
for (int idx = 0; num_string[idx] != '\0'; idx++) {
|
||||
char c = num_string[idx];
|
||||
switch (c) {
|
||||
case ' ':
|
||||
continue;
|
||||
case '\n':
|
||||
continue;
|
||||
case '-':
|
||||
sign = 1;
|
||||
continue;
|
||||
default:
|
||||
if (is_digit(c)) {
|
||||
int digit = c - '0';
|
||||
n = n * 10 + digit;
|
||||
} else {
|
||||
dbg("Hit an unsupported character while parsing a number: '%c'", c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sign ? -n : n;
|
||||
}
|
||||
|
||||
/* Returns the square root of the given number, or -1 in case of error */
|
||||
static inline fpt fpt_sqrt(fpt A) {
|
||||
int invert = 0;
|
||||
int iter = FIXEDPT_FBITS;
|
||||
int i;
|
||||
fpt l;
|
||||
|
||||
if (A < 0)
|
||||
return (-1);
|
||||
if (A == 0 || A == FIXEDPT_ONE)
|
||||
return (A);
|
||||
if (A < FIXEDPT_ONE && A > 6) {
|
||||
invert = 1;
|
||||
A = fpt_div(FIXEDPT_ONE, A);
|
||||
}
|
||||
if (A > FIXEDPT_ONE) {
|
||||
fpt s = A;
|
||||
|
||||
iter = 0;
|
||||
while (s > 0) {
|
||||
s >>= 2;
|
||||
iter++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Newton's iterations */
|
||||
l = (A >> 1) + 1;
|
||||
for (i = 0; i < iter; i++)
|
||||
l = (l + fpt_div(A, l)) >> 1;
|
||||
if (invert)
|
||||
return (fpt_div(FIXEDPT_ONE, l));
|
||||
return (l);
|
||||
}
|
||||
|
||||
/* Returns the sine of the given fpt number.
|
||||
* Note: the loss of precision is extraordinary! */
|
||||
static inline fpt fpt_sin(fpt fp) {
|
||||
int sign = 1;
|
||||
fpt sqr, result;
|
||||
const fpt SK[2] = {fpt_rconst(7.61e-03), fpt_rconst(1.6605e-01)};
|
||||
|
||||
fp %= 2 * FIXEDPT_PI;
|
||||
if (fp < 0)
|
||||
fp = FIXEDPT_PI * 2 + fp;
|
||||
if ((fp > FIXEDPT_HALF_PI) && (fp <= FIXEDPT_PI))
|
||||
fp = FIXEDPT_PI - fp;
|
||||
else if ((fp > FIXEDPT_PI) && (fp <= (FIXEDPT_PI + FIXEDPT_HALF_PI))) {
|
||||
fp = fp - FIXEDPT_PI;
|
||||
sign = -1;
|
||||
} else if (fp > (FIXEDPT_PI + FIXEDPT_HALF_PI)) {
|
||||
fp = (FIXEDPT_PI << 1) - fp;
|
||||
sign = -1;
|
||||
}
|
||||
sqr = fpt_mul(fp, fp);
|
||||
result = SK[0];
|
||||
result = fpt_mul(result, sqr);
|
||||
result -= SK[1];
|
||||
result = fpt_mul(result, sqr);
|
||||
result += FIXEDPT_ONE;
|
||||
result = fpt_mul(result, fp);
|
||||
return sign * result;
|
||||
}
|
||||
|
||||
/* Returns the cosine of the given fpt number */
|
||||
static inline fpt fpt_cos(fpt A) { return (fpt_sin(FIXEDPT_HALF_PI - A)); }
|
||||
|
||||
/* Returns the tangent of the given fpt number */
|
||||
static inline fpt fpt_tan(fpt A) { return fpt_div(fpt_sin(A), fpt_cos(A)); }
|
||||
|
||||
/* Returns the value exp(x), i.e. e^x of the given fpt number. */
|
||||
static inline fpt fpt_exp(fpt fp) {
|
||||
fpt xabs, k, z, R, xp;
|
||||
const fpt LN2 = fpt_rconst(0.69314718055994530942);
|
||||
const fpt LN2_INV = fpt_rconst(1.4426950408889634074);
|
||||
const fpt EXP_P[5] = {
|
||||
fpt_rconst(1.66666666666666019037e-01),
|
||||
fpt_rconst(-2.77777777770155933842e-03),
|
||||
fpt_rconst(6.61375632143793436117e-05),
|
||||
fpt_rconst(-1.65339022054652515390e-06),
|
||||
fpt_rconst(4.13813679705723846039e-08),
|
||||
};
|
||||
|
||||
if (fp == 0)
|
||||
return (FIXEDPT_ONE);
|
||||
xabs = fpt_abs(fp);
|
||||
k = fpt_mul(xabs, LN2_INV);
|
||||
k += FIXEDPT_ONE_HALF;
|
||||
k &= ~FIXEDPT_FMASK;
|
||||
if (fp < 0)
|
||||
k = -k;
|
||||
fp -= fpt_mul(k, LN2);
|
||||
z = fpt_mul(fp, fp);
|
||||
/* Taylor */
|
||||
R = FIXEDPT_TWO +
|
||||
fpt_mul(
|
||||
z,
|
||||
EXP_P[0] +
|
||||
fpt_mul(
|
||||
z, EXP_P[1] +
|
||||
fpt_mul(z, EXP_P[2] +
|
||||
fpt_mul(z, EXP_P[3] +
|
||||
fpt_mul(z, EXP_P[4])))));
|
||||
xp = FIXEDPT_ONE + fpt_div(fpt_mul(fp, FIXEDPT_TWO), R - fp);
|
||||
if (k < 0)
|
||||
k = FIXEDPT_ONE >> (-k >> FIXEDPT_FBITS);
|
||||
else
|
||||
k = FIXEDPT_ONE << (k >> FIXEDPT_FBITS);
|
||||
return (fpt_mul(k, xp));
|
||||
}
|
||||
|
||||
/* Returns the hyperbolic tangent of the given fpt number */
|
||||
static inline fpt fpt_tanh(fpt X) {
|
||||
fpt e_to_the_2_x = fpt_exp(fpt_mul(FIXEDPT_TWO, X));
|
||||
fpt sinh = e_to_the_2_x - FIXEDPT_ONE;
|
||||
fpt cosh = e_to_the_2_x + FIXEDPT_ONE;
|
||||
return fpt_div(sinh, cosh);
|
||||
}
|
||||
|
||||
/* Returns the natural logarithm of the given fpt number. */
|
||||
static inline fpt fpt_ln(fpt x) {
|
||||
fpt log2, xi;
|
||||
fpt f, s, z, w, R;
|
||||
const fpt LN2 = fpt_rconst(0.69314718055994530942);
|
||||
const fpt LG[7] = {fpt_rconst(6.666666666666735130e-01),
|
||||
fpt_rconst(3.999999999940941908e-01),
|
||||
fpt_rconst(2.857142874366239149e-01),
|
||||
fpt_rconst(2.222219843214978396e-01),
|
||||
fpt_rconst(1.818357216161805012e-01),
|
||||
fpt_rconst(1.531383769920937332e-01),
|
||||
fpt_rconst(1.479819860511658591e-01)};
|
||||
|
||||
if (x < 0)
|
||||
return (0);
|
||||
if (x == 0)
|
||||
return 0xffffffff;
|
||||
|
||||
log2 = 0;
|
||||
xi = x;
|
||||
while (xi > FIXEDPT_TWO) {
|
||||
xi >>= 1;
|
||||
log2++;
|
||||
}
|
||||
f = xi - FIXEDPT_ONE;
|
||||
s = fpt_div(f, FIXEDPT_TWO + f);
|
||||
z = fpt_mul(s, s);
|
||||
w = fpt_mul(z, z);
|
||||
R = fpt_mul(w, LG[1] + fpt_mul(w, LG[3] + fpt_mul(w, LG[5]))) +
|
||||
fpt_mul(z, LG[0] +
|
||||
fpt_mul(w, LG[2] + fpt_mul(w, LG[4] + fpt_mul(w, LG[6]))));
|
||||
return (fpt_mul(LN2, (log2 << FIXEDPT_FBITS)) + f - fpt_mul(s, f - R));
|
||||
}
|
||||
|
||||
/* Returns the logarithm of the given base of the given fpt number */
|
||||
static inline fpt fpt_log(fpt x, fpt base) {
|
||||
return (fpt_div(fpt_ln(x), fpt_ln(base)));
|
||||
}
|
||||
|
||||
/* Return the power value (n^exp) of the given fpt numbers */
|
||||
static inline fpt fpt_pow(fpt n, fpt exp) {
|
||||
if (exp == 0)
|
||||
return (FIXEDPT_ONE);
|
||||
if (n < 0)
|
||||
return 0;
|
||||
return (fpt_exp(fpt_mul(fpt_ln(n), exp)));
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,93 @@
|
||||
#ifndef _INPUT_ECHO_
|
||||
#define _INPUT_ECHO_
|
||||
|
||||
#include "fixedptc.h"
|
||||
#include "linux/cdev.h"
|
||||
#include "linux/fs.h"
|
||||
#include "speed.h"
|
||||
#include <linux/version.h>
|
||||
|
||||
int create_char_device(void);
|
||||
void destroy_char_device(void);
|
||||
|
||||
static struct cdev device;
|
||||
static struct class *device_class;
|
||||
static dev_t device_number;
|
||||
|
||||
/*
|
||||
* Convert an int into an array of four/eight bytes, in big endian (MSB first)
|
||||
*/
|
||||
static void fpt_to_int_be_bytes(fpt num, char bytes[sizeof(fpt)]) {
|
||||
#define byte(i) (FIXEDPT_BITS - (i * 8))
|
||||
bytes[0] = (num >> byte(1)) & 0xFF; // Most significant byte
|
||||
bytes[1] = (num >> byte(2)) & 0xFF;
|
||||
bytes[2] = (num >> byte(3)) & 0xFF;
|
||||
#if FIXEDPT_BITS == 64
|
||||
if (sizeof(fpt) == 8) {
|
||||
bytes[3] = (num >> byte(4)) & 0xFF;
|
||||
bytes[4] = (num >> byte(5)) & 0xFF;
|
||||
bytes[5] = (num >> byte(6)) & 0xFF;
|
||||
bytes[6] = (num >> byte(7)) & 0xFF;
|
||||
bytes[7] = num & 0xFF; // Least significant byte
|
||||
return;
|
||||
}
|
||||
#else
|
||||
bytes[3] = num & 0xFF; // Least significant byte
|
||||
#endif
|
||||
}
|
||||
|
||||
static ssize_t read(struct file *f, char __user *user_buffer, size_t size,
|
||||
loff_t *offset) {
|
||||
dbg("echoing speed to userspace: %s", fptoa(LAST_INPUT_MOUSE_SPEED));
|
||||
|
||||
char be_bytes_for_int[sizeof(fpt)] = {0};
|
||||
fpt_to_int_be_bytes(LAST_INPUT_MOUSE_SPEED, be_bytes_for_int);
|
||||
|
||||
int err =
|
||||
copy_to_user(user_buffer, be_bytes_for_int, sizeof(be_bytes_for_int));
|
||||
if (err)
|
||||
return -EFAULT;
|
||||
|
||||
return sizeof(be_bytes_for_int);
|
||||
}
|
||||
|
||||
struct file_operations fops = {.owner = THIS_MODULE, .read = read};
|
||||
|
||||
int create_char_device(void) {
|
||||
int err;
|
||||
err = alloc_chrdev_region(&device_number, 0, 1, "maccel");
|
||||
if (err)
|
||||
return -EIO;
|
||||
|
||||
cdev_init(&device, &fops);
|
||||
cdev_add(&device, device_number, 1);
|
||||
|
||||
#if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0))
|
||||
device_class = class_create(THIS_MODULE, "maccel");
|
||||
#else
|
||||
device.owner = THIS_MODULE;
|
||||
device_class = class_create("maccel");
|
||||
#endif
|
||||
|
||||
if (IS_ERR(device_class)) {
|
||||
goto err_free_cdev;
|
||||
}
|
||||
|
||||
device_create(device_class, NULL, device_number, NULL, "maccel");
|
||||
|
||||
return 0;
|
||||
|
||||
err_free_cdev:
|
||||
cdev_del(&device);
|
||||
unregister_chrdev_region(device_number, 1);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
void destroy_char_device(void) {
|
||||
device_destroy(device_class, device_number);
|
||||
class_destroy(device_class);
|
||||
cdev_del(&device);
|
||||
unregister_chrdev_region(device_number, 1);
|
||||
}
|
||||
|
||||
#endif // !_INPUT_ECHO_
|
||||
@@ -0,0 +1,231 @@
|
||||
#include "./accel_k.h"
|
||||
#include "linux/input.h"
|
||||
#include "mouse_move.h"
|
||||
#include <linux/hid.h>
|
||||
#include <linux/version.h>
|
||||
|
||||
#if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 11, 0))
|
||||
#define __cleanup_events 0
|
||||
#else
|
||||
#define __cleanup_events 1
|
||||
#endif
|
||||
|
||||
static inline bool rotation_is_enabled(void) {
|
||||
/* Fast check: the default is "0", so skip atofp unless the string differs */
|
||||
return PARAM_ANGLE_ROTATION[0] != '0' || PARAM_ANGLE_ROTATION[1] != '\0';
|
||||
}
|
||||
|
||||
/*
|
||||
* Collect the events EV_REL REL_X and EV_REL REL_Y, once we have both then
|
||||
* we accelerate the (x, y) vector and set the EV_REL event's value
|
||||
* through the `value_ptr` of each collected event.
|
||||
*/
|
||||
static void event(struct input_handle *handle, struct input_value *value_ptr) {
|
||||
/* printk(KERN_INFO "type %d, code %d, value %d", type, code, value); */
|
||||
|
||||
switch (value_ptr->type) {
|
||||
case EV_REL: {
|
||||
dbg("EV_REL => code %d, value %d", value_ptr->code, value_ptr->value);
|
||||
update_mouse_move(value_ptr);
|
||||
return;
|
||||
}
|
||||
case EV_SYN: {
|
||||
int x = get_x(MOVEMENT);
|
||||
int y = get_y(MOVEMENT);
|
||||
if (x || y) {
|
||||
dbg("EV_SYN => code %d", value_ptr->code);
|
||||
|
||||
/*
|
||||
* When rotation is active and one axis is missing from this frame,
|
||||
* point the missing axis to synthetic storage so f_accelerate can
|
||||
* write the rotated cross-axis component into it. The synthetic
|
||||
* value is later injected into the event stream by maccel_events().
|
||||
*
|
||||
* Only on >= 6.11.0: injection into the event buffer is not
|
||||
* possible on older kernels, so there is no point in computing
|
||||
* the cross-axis component.
|
||||
*/
|
||||
#if __cleanup_events
|
||||
if (rotation_is_enabled()) {
|
||||
ensure_axes_for_rotation();
|
||||
}
|
||||
#endif
|
||||
|
||||
accelerate(&x, &y);
|
||||
dbg("accelerated -> (%d, %d)", x, y);
|
||||
set_x_move(x);
|
||||
set_y_move(y);
|
||||
|
||||
clear_mouse_move();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#if __cleanup_events
|
||||
static unsigned int maccel_events(struct input_handle *handle,
|
||||
struct input_value *vals,
|
||||
unsigned int count) {
|
||||
#else
|
||||
static void maccel_events(struct input_handle *handle,
|
||||
const struct input_value *vals, unsigned int count) {
|
||||
#endif
|
||||
struct input_value *v;
|
||||
for (v = vals; v != vals + count; v++) {
|
||||
event(handle, v);
|
||||
}
|
||||
|
||||
struct input_value *end = vals;
|
||||
|
||||
for (v = vals; v != vals + count; v++) {
|
||||
if (v->type == EV_REL && v->value == NONE_EVENT_VALUE)
|
||||
continue;
|
||||
if (end != v) {
|
||||
*end = *v;
|
||||
}
|
||||
end++;
|
||||
}
|
||||
|
||||
int _count = end - vals;
|
||||
|
||||
#if __cleanup_events
|
||||
/*
|
||||
* Inject synthetic EV_REL events for axes that rotation produced
|
||||
* but weren't present in the original frame. Insert before SYN_REPORT
|
||||
* so downstream handlers see a valid event sequence.
|
||||
*
|
||||
* Gated to >= 6.11.0 only: on older kernels the handler returns void
|
||||
* and cannot communicate a new event count to the input subsystem.
|
||||
* Writing extra events into the buffer on those kernels is undefined
|
||||
* behavior — the kernel won't deliver them and may behave erratically.
|
||||
*/
|
||||
{
|
||||
struct input_value *syn_pos = NULL;
|
||||
unsigned int max = handle->dev->max_vals;
|
||||
|
||||
/* Find the last SYN_REPORT so we can insert before it */
|
||||
for (v = vals; v != end; v++) {
|
||||
if (v->type == EV_SYN && v->code == SYN_REPORT)
|
||||
syn_pos = v;
|
||||
}
|
||||
|
||||
if (injected_x && synthetic_x_val != NONE_EVENT_VALUE && _count < max) {
|
||||
if (syn_pos) {
|
||||
/* Shift SYN_REPORT and everything after it forward by one */
|
||||
memmove(syn_pos + 1, syn_pos, (end - syn_pos) * sizeof(*syn_pos));
|
||||
syn_pos->type = EV_REL;
|
||||
syn_pos->code = REL_X;
|
||||
syn_pos->value = synthetic_x_val;
|
||||
syn_pos++;
|
||||
end++;
|
||||
_count++;
|
||||
}
|
||||
dbg("rotation: injected synthetic REL_X = %d", synthetic_x_val);
|
||||
}
|
||||
|
||||
if (injected_y && synthetic_y_val != NONE_EVENT_VALUE && _count < max) {
|
||||
if (syn_pos) {
|
||||
memmove(syn_pos + 1, syn_pos, (end - syn_pos) * sizeof(*syn_pos));
|
||||
syn_pos->type = EV_REL;
|
||||
syn_pos->code = REL_Y;
|
||||
syn_pos->value = synthetic_y_val;
|
||||
end++;
|
||||
_count++;
|
||||
}
|
||||
dbg("rotation: injected synthetic REL_Y = %d", synthetic_y_val);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
handle->dev->num_vals = _count;
|
||||
#if __cleanup_events
|
||||
return _count;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Same as Linux's input_register_handle but we always add the handle to the
|
||||
* head of handlers */
|
||||
static int input_register_handle_head(struct input_handle *handle) {
|
||||
struct input_handler *handler = handle->handler;
|
||||
struct input_dev *dev = handle->dev;
|
||||
|
||||
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 11, 7))
|
||||
/* In 6.11.7 an additional handler pointer was added:
|
||||
* https://github.com/torvalds/linux/commit/071b24b54d2d05fbf39ddbb27dee08abd1d713f3
|
||||
*/
|
||||
if (handler->events)
|
||||
handle->handle_events = handler->events;
|
||||
#endif
|
||||
|
||||
int error = mutex_lock_interruptible(&dev->mutex);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
list_add_rcu(&handle->d_node, &dev->h_list);
|
||||
mutex_unlock(&dev->mutex);
|
||||
list_add_tail_rcu(&handle->h_node, &handler->h_list);
|
||||
if (handler->start)
|
||||
handler->start(handle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int maccel_connect(struct input_handler *handler, struct input_dev *dev,
|
||||
const struct input_device_id *id) {
|
||||
struct input_handle *handle;
|
||||
int error;
|
||||
|
||||
handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL);
|
||||
if (!handle)
|
||||
return -ENOMEM;
|
||||
|
||||
handle->dev = input_get_device(dev);
|
||||
handle->handler = handler;
|
||||
handle->name = "maccel";
|
||||
|
||||
error = input_register_handle_head(handle);
|
||||
if (error)
|
||||
goto err_free_mem;
|
||||
|
||||
error = input_open_device(handle);
|
||||
if (error)
|
||||
goto err_unregister_handle;
|
||||
|
||||
printk(KERN_INFO pr_fmt("maccel flags: DEBUG=%s; FIXEDPT_BITS=%d"),
|
||||
DEBUG_TEST ? "true" : "false", FIXEDPT_BITS);
|
||||
|
||||
printk(KERN_INFO pr_fmt("maccel connecting to device: %s (%s at %s)"),
|
||||
dev_name(&dev->dev), dev->name ?: "unknown", dev->phys ?: "unknown");
|
||||
|
||||
return 0;
|
||||
|
||||
err_unregister_handle:
|
||||
input_unregister_handle(handle);
|
||||
|
||||
err_free_mem:
|
||||
kfree(handle);
|
||||
return error;
|
||||
}
|
||||
|
||||
static void maccel_disconnect(struct input_handle *handle) {
|
||||
input_close_device(handle);
|
||||
input_unregister_handle(handle);
|
||||
kfree(handle);
|
||||
}
|
||||
|
||||
static const struct input_device_id my_ids[] = {
|
||||
{.flags = INPUT_DEVICE_ID_MATCH_EVBIT,
|
||||
.evbit = {BIT_MASK(EV_REL)}}, // Match all relative pointer values
|
||||
{},
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(input, my_ids);
|
||||
|
||||
struct input_handler maccel_handler = {.events = maccel_events,
|
||||
.connect = maccel_connect,
|
||||
.disconnect = maccel_disconnect,
|
||||
.name = "maccel",
|
||||
.id_table = my_ids};
|
||||
@@ -0,0 +1,35 @@
|
||||
#include "./input_handler.h"
|
||||
#include "input_echo.h"
|
||||
|
||||
/*
|
||||
* We initialize the character driver for the userspace visualizations,
|
||||
* and we register the input_handler.
|
||||
*/
|
||||
static int __init driver_initialization(void) {
|
||||
int error;
|
||||
error = create_char_device();
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
error = input_register_handler(&maccel_handler);
|
||||
if (error)
|
||||
goto err_free_chrdev;
|
||||
|
||||
return 0;
|
||||
|
||||
err_free_chrdev:
|
||||
destroy_char_device();
|
||||
return error;
|
||||
}
|
||||
|
||||
static void __exit driver_exit(void) {
|
||||
input_unregister_handler(&maccel_handler);
|
||||
destroy_char_device();
|
||||
}
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Gnarus-G");
|
||||
MODULE_DESCRIPTION("Mouse acceleration driver.");
|
||||
|
||||
module_init(driver_initialization);
|
||||
module_exit(driver_exit);
|
||||
@@ -0,0 +1,25 @@
|
||||
#ifndef _MATH_H_
|
||||
#define _MATH_H_
|
||||
|
||||
#include "fixedptc.h"
|
||||
|
||||
struct vector {
|
||||
fpt x;
|
||||
fpt y;
|
||||
};
|
||||
|
||||
static inline fpt magnitude(struct vector v) {
|
||||
fpt x_square = fpt_mul(v.x, v.x);
|
||||
fpt y_square = fpt_mul(v.y, v.y);
|
||||
fpt x_square_plus_y_square = fpt_add(x_square, y_square);
|
||||
|
||||
dbg("dx^2 (in) %s", fptoa(x_square));
|
||||
dbg("dy^2 (in) %s", fptoa(y_square));
|
||||
dbg("square modulus (in) %s", fptoa(x_square_plus_y_square));
|
||||
|
||||
return fpt_sqrt(x_square_plus_y_square);
|
||||
}
|
||||
|
||||
static inline fpt minsd(fpt a, fpt b) { return (a < b) ? a : b; }
|
||||
|
||||
#endif // !_MATH_H_
|
||||
@@ -0,0 +1,93 @@
|
||||
#include "dbg.h"
|
||||
#include "linux/input.h"
|
||||
#include <linux/stddef.h>
|
||||
|
||||
#define NONE_EVENT_VALUE 0
|
||||
|
||||
typedef struct {
|
||||
int *x;
|
||||
int *y;
|
||||
} mouse_move;
|
||||
|
||||
static mouse_move MOVEMENT = {.x = NULL, .y = NULL};
|
||||
|
||||
/*
|
||||
* Track whether we injected synthetic storage for a missing axis.
|
||||
* When rotation is active and the mouse only reports one axis (e.g. pure
|
||||
* horizontal movement -> only REL_X), we need a place for f_accelerate
|
||||
* to write the rotated cross-axis component. These synthetic values
|
||||
* are later injected into the event stream by maccel_events().
|
||||
*/
|
||||
static bool injected_x = false;
|
||||
static bool injected_y = false;
|
||||
static int synthetic_x_val = 0;
|
||||
static int synthetic_y_val = 0;
|
||||
|
||||
static inline void update_mouse_move(struct input_value *value) {
|
||||
switch (value->code) {
|
||||
case REL_X:
|
||||
MOVEMENT.x = &value->value;
|
||||
break;
|
||||
case REL_Y:
|
||||
MOVEMENT.y = &value->value;
|
||||
break;
|
||||
default:
|
||||
dbg("bad movement input_value: (code, value) = (%d, %d)", value->code,
|
||||
value->value);
|
||||
}
|
||||
}
|
||||
|
||||
static inline int get_x(mouse_move movement) {
|
||||
if (movement.x == NULL) {
|
||||
return NONE_EVENT_VALUE;
|
||||
}
|
||||
return *movement.x;
|
||||
}
|
||||
|
||||
static inline int get_y(mouse_move movement) {
|
||||
if (movement.y == NULL) {
|
||||
return NONE_EVENT_VALUE;
|
||||
}
|
||||
return *movement.y;
|
||||
}
|
||||
|
||||
static inline void set_x_move(int value) {
|
||||
if (MOVEMENT.x == NULL) {
|
||||
return;
|
||||
}
|
||||
*MOVEMENT.x = value;
|
||||
}
|
||||
|
||||
static inline void set_y_move(int value) {
|
||||
if (MOVEMENT.y == NULL) {
|
||||
return;
|
||||
}
|
||||
*MOVEMENT.y = value;
|
||||
}
|
||||
|
||||
/*
|
||||
* When rotation is active and one axis is missing from the frame,
|
||||
* point the missing axis to synthetic storage so f_accelerate can
|
||||
* write the rotated component into it.
|
||||
*/
|
||||
static inline void ensure_axes_for_rotation(void) {
|
||||
if (MOVEMENT.x == NULL) {
|
||||
synthetic_x_val = 0;
|
||||
MOVEMENT.x = &synthetic_x_val;
|
||||
injected_x = true;
|
||||
dbg("rotation: injecting synthetic REL_X storage (x=%d)", 0);
|
||||
}
|
||||
if (MOVEMENT.y == NULL) {
|
||||
synthetic_y_val = 0;
|
||||
MOVEMENT.y = &synthetic_y_val;
|
||||
injected_y = true;
|
||||
dbg("rotation: injecting synthetic REL_Y storage (y=%d)", 0);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void clear_mouse_move(void) {
|
||||
MOVEMENT.x = NULL;
|
||||
MOVEMENT.y = NULL;
|
||||
injected_x = false;
|
||||
injected_y = false;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
#ifndef _PARAM_H_
|
||||
#define _PARAM_H_
|
||||
|
||||
#include "accel/mode.h"
|
||||
#include "fixedptc.h"
|
||||
#include "linux/moduleparam.h"
|
||||
|
||||
#define RW_USER_GROUP 0664
|
||||
|
||||
#define PARAM(param, default_value, desc) \
|
||||
char *PARAM_##param = #default_value; \
|
||||
module_param_named(param, PARAM_##param, charp, RW_USER_GROUP); \
|
||||
MODULE_PARM_DESC(param, desc);
|
||||
|
||||
#if FIXEDPT_BITS == 64
|
||||
PARAM(
|
||||
SENS_MULT, 4294967296, // 1 << 32
|
||||
"A factor applied by the sensitivity calculation after ACCEL is applied.");
|
||||
PARAM(YX_RATIO, 4294967296, // 1 << 32
|
||||
"A factor (Y/X) by which the final sensitivity calculated is multiplied "
|
||||
"to produce the sensitivity applied to the Y axis.");
|
||||
PARAM(INPUT_DPI, 4294967296000, // 1000 << 32
|
||||
"The DPI of the mouse, used to normalize input to 1000 DPI equivalent "
|
||||
"for consistent acceleration across different mice.");
|
||||
#else
|
||||
PARAM(SENS_MULT, 65536, // 1 << 16
|
||||
"A factor applied the sensitivity calculation after ACCEL is applied.");
|
||||
PARAM(YX_RATIO, 65536, // 1 << 16
|
||||
"A factor (Y/X) by which the final sensitivity calculated is multiplied "
|
||||
"to produce the sensitivity applied to the Y axis.");
|
||||
PARAM(INPUT_DPI, 65536000, // 1000 << 16
|
||||
"The DPI of the mouse, used to normalize input to 1000 DPI equivalent "
|
||||
"for consistent acceleration across different mice.");
|
||||
#endif
|
||||
|
||||
PARAM(ANGLE_ROTATION, 0,
|
||||
"Apply rotation (degrees) to the mouse movement input");
|
||||
// For Linear Mode
|
||||
|
||||
PARAM(ACCEL, 0, "Control the sensitivity calculation.");
|
||||
PARAM(OFFSET, 0, "Input speed threshold (counts/ms) before acceleration begins.");
|
||||
PARAM(OUTPUT_CAP, 0, "Control the maximum sensitivity.");
|
||||
|
||||
// For Natural Mode
|
||||
|
||||
#if FIXEDPT_BITS == 64
|
||||
PARAM(DECAY_RATE, 429496730, // 0.1 << 32
|
||||
"Decay rate of the Natural curve.");
|
||||
PARAM(LIMIT, 6442450944, // 1.5 << 32
|
||||
"Limit of the Natural curve.");
|
||||
#else
|
||||
PARAM(DECAY_RATE, 6554, // 0.1 << 16
|
||||
"Decay rate of the Natural curve");
|
||||
PARAM(LIMIT, 98304, // 1.5 << 16
|
||||
"Limit of the Natural curve");
|
||||
#endif
|
||||
|
||||
// For Synchronous Mode
|
||||
|
||||
#if FIXEDPT_BITS == 64
|
||||
PARAM(GAMMA, 4294967296, // 1 << 32
|
||||
"Control how fast you get from low to fast around the midpoint");
|
||||
PARAM(SMOOTH, 2147483648, // 0.5 << 32
|
||||
"Control the suddeness of the sensitivity increase.");
|
||||
PARAM(MOTIVITY, 6442450944, // 1.5 << 32
|
||||
"Set the maximum sensitivity while also setting the minimum to "
|
||||
"1/MOTIVITY");
|
||||
PARAM(SYNC_SPEED, 21474836480, // 5 << 32
|
||||
"Set The middle sensitivity between you min and max sensitivity");
|
||||
#else
|
||||
PARAM(GAMMA, 65536, // 1 << 16
|
||||
"Control how fast you get from low to fast around the midpoint");
|
||||
PARAM(SMOOTH, 32768, // 0.5 << 16
|
||||
"Control the suddeness of the sensitivity increase.");
|
||||
PARAM(MOTIVITY, 98304, // 1.5 << 16
|
||||
"Set the maximum sensitivity while also setting the minimum to "
|
||||
"1/MOTIVITY");
|
||||
PARAM(SYNC_SPEED, 327680, // 5 << 16
|
||||
"Set The middle sensitivity between you min and max sensitivity");
|
||||
#endif
|
||||
|
||||
// Flags
|
||||
#define PARAM_FLAG(param, default_value, desc) \
|
||||
unsigned char PARAM_##param = default_value; \
|
||||
module_param_named(param, PARAM_##param, byte, RW_USER_GROUP); \
|
||||
MODULE_PARM_DESC(param, desc);
|
||||
|
||||
PARAM_FLAG(MODE, linear, "Desired type of acceleration.");
|
||||
|
||||
#endif // !_PARAM_H_
|
||||
@@ -0,0 +1,34 @@
|
||||
#ifndef __SPEED_H__
|
||||
#define __SPEED_H__
|
||||
|
||||
#include "dbg.h"
|
||||
#include "fixedptc.h"
|
||||
#include "math.h"
|
||||
|
||||
/**
|
||||
* Track this to enable the UI to show the last noted
|
||||
* input counts/ms (speed).
|
||||
*/
|
||||
static fpt LAST_INPUT_MOUSE_SPEED = 0;
|
||||
|
||||
static inline fpt input_speed(fpt dx, fpt dy, fpt time_ms) {
|
||||
|
||||
fpt distance = magnitude((struct vector){dx, dy});
|
||||
|
||||
if (distance == -1) {
|
||||
dbg("distance calculation failed: t = %s", fptoa(time_ms));
|
||||
return 0;
|
||||
}
|
||||
|
||||
dbg("distance (in) %s", fptoa(distance));
|
||||
|
||||
fpt speed = fpt_div(distance, time_ms);
|
||||
LAST_INPUT_MOUSE_SPEED = speed;
|
||||
|
||||
dbg("time interval %s", fptoa(time_ms));
|
||||
dbg("speed (in) %s", fptoa(speed));
|
||||
|
||||
return speed;
|
||||
}
|
||||
|
||||
#endif // !__SPEED_H__
|
||||
@@ -0,0 +1,187 @@
|
||||
#include "../accel.h"
|
||||
#include "test_utils.h"
|
||||
#include <stdio.h>
|
||||
|
||||
static int test_acceleration(const char *filename, struct accel_args args) {
|
||||
const int LINE_LEN = 26;
|
||||
const int MIN = -128;
|
||||
const int MAX = 127;
|
||||
|
||||
char content[256 * 256 * LINE_LEN + 1];
|
||||
strcpy(content, ""); // initialize as an empty string
|
||||
|
||||
for (int x = MIN; x < MAX; x++) {
|
||||
for (int y = MIN; y < MAX; y++) {
|
||||
|
||||
int x_out = x;
|
||||
int y_out = y;
|
||||
|
||||
f_accelerate(&x_out, &y_out, FIXEDPT_ONE, args);
|
||||
|
||||
char curr_debug_print[LINE_LEN];
|
||||
|
||||
sprintf(curr_debug_print, "(%d, %d) => (%d, %d)\n", x, y, x_out, y_out);
|
||||
|
||||
strcat(content, curr_debug_print);
|
||||
}
|
||||
}
|
||||
|
||||
assert_snapshot(filename, content);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_linear_acceleration(const char *filename, fpt param_sens_mult,
|
||||
fpt param_yx_ratio, fpt param_accel,
|
||||
fpt param_offset, fpt param_output_cap) {
|
||||
struct linear_curve_args _args =
|
||||
(struct linear_curve_args){.accel = param_accel,
|
||||
.offset = param_offset,
|
||||
.output_cap = param_output_cap};
|
||||
|
||||
struct accel_args args = {
|
||||
.sens_mult = param_sens_mult,
|
||||
.yx_ratio = param_yx_ratio,
|
||||
.input_dpi = fpt_fromint(1000),
|
||||
.tag = linear,
|
||||
.args = (union __accel_args){.linear = _args},
|
||||
};
|
||||
|
||||
return test_acceleration(filename, args);
|
||||
}
|
||||
|
||||
static int test_natural_acceleration(const char *filename, fpt param_sens_mult,
|
||||
fpt param_yx_ratio, fpt param_decay_rate,
|
||||
fpt param_offset, fpt param_limit) {
|
||||
struct natural_curve_args _args =
|
||||
(struct natural_curve_args){.decay_rate = param_decay_rate,
|
||||
.offset = param_offset,
|
||||
.limit = param_limit};
|
||||
|
||||
struct accel_args args = {
|
||||
.sens_mult = param_sens_mult,
|
||||
.yx_ratio = param_yx_ratio,
|
||||
.input_dpi = fpt_fromint(1000),
|
||||
.tag = natural,
|
||||
.args = (union __accel_args){.natural = _args},
|
||||
};
|
||||
|
||||
return test_acceleration(filename, args);
|
||||
}
|
||||
|
||||
static int test_synchronous_acceleration(const char *filename,
|
||||
fpt param_sens_mult,
|
||||
fpt param_yx_ratio, fpt param_gamma,
|
||||
fpt param_smooth, fpt param_motivity,
|
||||
fpt param_sync_speed) {
|
||||
struct synchronous_curve_args _args =
|
||||
(struct synchronous_curve_args){.gamma = param_gamma,
|
||||
.smooth = param_smooth,
|
||||
.motivity = param_motivity,
|
||||
.sync_speed = param_sync_speed};
|
||||
|
||||
struct accel_args args = {
|
||||
.sens_mult = param_sens_mult,
|
||||
.yx_ratio = param_yx_ratio,
|
||||
.input_dpi = fpt_fromint(1000),
|
||||
.tag = synchronous,
|
||||
.args = (union __accel_args){.synchronous = _args},
|
||||
};
|
||||
|
||||
return test_acceleration(filename, args);
|
||||
}
|
||||
|
||||
static int test_no_accel_acceleration(const char *filename, fpt param_sens_mult,
|
||||
fpt param_yx_ratio) {
|
||||
struct no_accel_curve_args _args = (struct no_accel_curve_args){};
|
||||
|
||||
struct accel_args args = {
|
||||
.sens_mult = param_sens_mult,
|
||||
.yx_ratio = param_yx_ratio,
|
||||
.input_dpi = fpt_fromint(1000),
|
||||
.tag = no_accel,
|
||||
.args = (union __accel_args){.no_accel = _args},
|
||||
};
|
||||
|
||||
return test_acceleration(filename, args);
|
||||
}
|
||||
|
||||
static int test_rotation_no_accel(const char *filename, fpt param_sens_mult,
|
||||
fpt param_angle_deg) {
|
||||
struct no_accel_curve_args _args = (struct no_accel_curve_args){};
|
||||
|
||||
struct accel_args args = {
|
||||
.sens_mult = param_sens_mult,
|
||||
.yx_ratio = FIXEDPT_ONE,
|
||||
.input_dpi = fpt_fromint(1000),
|
||||
.angle_rotation_deg = param_angle_deg,
|
||||
.tag = no_accel,
|
||||
.args = (union __accel_args){.no_accel = _args},
|
||||
};
|
||||
|
||||
return test_acceleration(filename, args);
|
||||
}
|
||||
|
||||
#define test_linear(sens_mult, yx_ratio, accel, offset, cap) \
|
||||
assert(test_linear_acceleration( \
|
||||
"SENS_MULT-" #sens_mult "-ACCEL-" #accel "-OFFSET" #offset \
|
||||
"-OUTPUT_CAP-" #cap ".snapshot", \
|
||||
fpt_rconst(sens_mult), fpt_rconst(yx_ratio), fpt_rconst(accel), \
|
||||
fpt_rconst(offset), fpt_rconst(cap)) == 0);
|
||||
|
||||
#define test_natural(sens_mult, yx_ratio, decay_rate, offset, limit) \
|
||||
assert(test_natural_acceleration( \
|
||||
"Natural__SENS_MULT-" #sens_mult "-DECAY_RATE-" #decay_rate \
|
||||
"-OFFSET" #offset "-LIMIT-" #limit ".snapshot", \
|
||||
fpt_rconst(sens_mult), fpt_rconst(yx_ratio), \
|
||||
fpt_rconst(decay_rate), fpt_rconst(offset), \
|
||||
fpt_rconst(limit)) == 0);
|
||||
|
||||
#define test_synchronous(sens_mult, yx_ratio, gamma, smooth, motivity, \
|
||||
sync_speed) \
|
||||
assert(test_synchronous_acceleration( \
|
||||
"Synchronous__SENS_MULT-" #sens_mult "-GAMMA-" #gamma \
|
||||
"-SMOOTH" #smooth "-MOTIVITY-" #motivity \
|
||||
"-SYNC_SPEED-" #sync_speed ".snapshot", \
|
||||
fpt_rconst(sens_mult), fpt_rconst(yx_ratio), fpt_rconst(gamma), \
|
||||
fpt_rconst(smooth), fpt_rconst(motivity), \
|
||||
fpt_rconst(sync_speed)) == 0);
|
||||
|
||||
#define test_no_accel(sens_mult, yx_ratio) \
|
||||
assert(test_no_accel_acceleration( \
|
||||
"NoAccel__SENS_MULT-" #sens_mult "-YX_RATIO-" #yx_ratio \
|
||||
".snapshot", \
|
||||
fpt_rconst(sens_mult), fpt_rconst(yx_ratio)) == 0);
|
||||
|
||||
#define test_rotation(sens_mult, angle_deg) \
|
||||
assert(test_rotation_no_accel( \
|
||||
"Rotation__SENS_MULT-" #sens_mult "-ANGLE-" #angle_deg \
|
||||
".snapshot", \
|
||||
fpt_rconst(sens_mult), fpt_rconst(angle_deg)) == 0);
|
||||
|
||||
int main(void) {
|
||||
test_linear(1, 1, 0, 0, 0);
|
||||
test_linear(1, 1, 0.3, 2, 2);
|
||||
test_linear(0.1325, 1, 0.3, 21.333333, 2);
|
||||
test_linear(0.1875, 1, 0.05625, 10.6666666, 2);
|
||||
test_linear(0.0917, 1, 0.002048, 78.125, 2.0239);
|
||||
test_linear(0.07, 1.15, 0.055, 21, 3);
|
||||
|
||||
test_natural(1, 1, 0, 0, 0);
|
||||
test_natural(1, 1, 0.1, 0, 0);
|
||||
test_natural(1, 1, 0.1, 8, 0);
|
||||
test_natural(1, 1, 0.03, 8, 1.5);
|
||||
|
||||
test_synchronous(1, 1.15, 0.8, 0.5, 1.5, 32);
|
||||
|
||||
test_no_accel(1, 1);
|
||||
test_no_accel(0.5, 1.5);
|
||||
|
||||
/* Rotation tests: verify cross-axis output when one axis is 0.
|
||||
* At 45 degrees, (10, 0) should produce roughly (7, 7) - not (10, 0).
|
||||
* At 90 degrees, (10, 0) should produce roughly (0, 10). */
|
||||
test_rotation(1, 45);
|
||||
test_rotation(1, 90);
|
||||
|
||||
print_success;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
#include "../fixedptc.h"
|
||||
#include "./test_utils.h"
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
|
||||
void test_eq(char *value, double expected) {
|
||||
fpt n = atofp(value);
|
||||
double actual = fpt_todouble(n);
|
||||
dbg("actual: (%li) %.15f, vs expected: %.15f\n", n, actual, expected);
|
||||
assert(actual == expected);
|
||||
}
|
||||
|
||||
void super_tiny_micro_minuscule_bench() {
|
||||
int iterations = 100000;
|
||||
double sum = 0;
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
struct timespec begin, end;
|
||||
clock_gettime(CLOCK_MONOTONIC_RAW, &begin);
|
||||
|
||||
fpt n = atofp("1826512586328");
|
||||
dbg("atofp(\"1826512586328\") = %li\n", n);
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC_RAW, &end);
|
||||
sum += (double)(end.tv_nsec - begin.tv_nsec);
|
||||
}
|
||||
|
||||
double avg = sum / iterations;
|
||||
printf(" Avg run time is %fns\n", avg);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
test_eq("1073741824", 0.25);
|
||||
test_eq("536870912", 0.125);
|
||||
test_eq("1342177280", 0.3125);
|
||||
test_eq("-335007449088", -78);
|
||||
|
||||
print_success;
|
||||
|
||||
super_tiny_micro_minuscule_bench();
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#include "../dbg.h"
|
||||
#include "../fixedptc.h"
|
||||
#include "test_utils.h"
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
|
||||
void test_custom_division_against_fixedpt(double a, double b) {
|
||||
#if FIXEDPT_BITS == 32
|
||||
return;
|
||||
#else
|
||||
|
||||
fpt n = fpt_rconst(a);
|
||||
fpt divisor = fpt_rconst(b);
|
||||
|
||||
fpt quotient = div128_s64_s64(n, divisor);
|
||||
fpt quotient1 = fpt_xdiv(n, divisor);
|
||||
|
||||
double actual = fpt_todouble(quotient);
|
||||
double expected = fpt_todouble(quotient1);
|
||||
|
||||
dbg("actual = (%li) -> %.10f", quotient, actual);
|
||||
dbg("expect = (%li) -> %.10f", quotient1, expected);
|
||||
|
||||
assert(actual == expected);
|
||||
#endif
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
|
||||
test_custom_division_against_fixedpt(57, 5.5);
|
||||
|
||||
test_custom_division_against_fixedpt(0, 2.57);
|
||||
|
||||
test_custom_division_against_fixedpt(-1, 3);
|
||||
|
||||
test_custom_division_against_fixedpt(-128, 4);
|
||||
|
||||
test_custom_division_against_fixedpt(127, 1.5);
|
||||
|
||||
/* test_custom_division_against_fixedpth(135, 0); */ // You only crash once!
|
||||
|
||||
print_success;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#include "../fixedptc.h"
|
||||
#include "test_utils.h"
|
||||
#include <assert.h>
|
||||
#include <linux/limits.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int assert_string_value(char *filename, double value) {
|
||||
fpt v = fpt_rconst(value);
|
||||
char *_v = fptoa(v);
|
||||
|
||||
dbg("to_string %f = %s", value, _v);
|
||||
|
||||
assert_snapshot(filename, _v);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define test_str(value) \
|
||||
assert(assert_string_value(__FILE_NAME__ "_" #value ".snapshot", value) == 0)
|
||||
|
||||
int main(void) {
|
||||
test_str(0.25);
|
||||
test_str(0.125);
|
||||
test_str(0.3125);
|
||||
test_str(-785);
|
||||
|
||||
print_success;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
#include "../speed.h"
|
||||
#include "./test_utils.h"
|
||||
#include <stdio.h>
|
||||
|
||||
int assert_string_value(char *filename, double x, double y, double t) {
|
||||
fpt dx = fpt_rconst(x);
|
||||
fpt dy = fpt_rconst(y);
|
||||
fpt dt = fpt_rconst(t);
|
||||
|
||||
dbg("in (%f, %f)", x, y);
|
||||
dbg("in: x (fpt conversion) %s", fptoa(x));
|
||||
dbg("in: y (fpt conversion) %s", fptoa(y));
|
||||
|
||||
fpt s = input_speed(dx, dy, dt);
|
||||
|
||||
double res = fpt_todouble(s);
|
||||
dbg("(%f, %f) dt = %f -> %f\n", x, y, t, res);
|
||||
|
||||
char content[100];
|
||||
sprintf(content, "(sqrt(%f, %f) / %f) = %f\n", x, y, t, res);
|
||||
|
||||
assert_snapshot(filename, content);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define test(x, y, time) \
|
||||
assert(assert_string_value(__FILE_NAME__ "_sqrt_" #x "_" #y "_" #time \
|
||||
".snapshot", \
|
||||
x, y, time) == 0)
|
||||
|
||||
int main(void) {
|
||||
test(1, 1, 1);
|
||||
test(1, 21, 1);
|
||||
test(64, -37, 1);
|
||||
test(1, 4, 1);
|
||||
|
||||
test(-1, 1, 4);
|
||||
|
||||
test(1, 0, 100);
|
||||
|
||||
test(1, -1, 100);
|
||||
|
||||
test(-1, -24, 1);
|
||||
|
||||
print_success;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
for test in tests/*.test.c; do
|
||||
if [[ ! $test =~ $TEST_NAME ]]; then
|
||||
continue
|
||||
fi
|
||||
gcc ${test} -o maccel_test -lm $DRIVER_CFLAGS || exit 1
|
||||
./maccel_test || exit 1
|
||||
rm maccel_test
|
||||
done
|
||||
@@ -0,0 +1 @@
|
||||
-785.000000000
|
||||
@@ -0,0 +1 @@
|
||||
0.125000000
|
||||
@@ -0,0 +1 @@
|
||||
0.250000000
|
||||
@@ -0,0 +1 @@
|
||||
0.312500000
|
||||
@@ -0,0 +1 @@
|
||||
(sqrt(-1.000000, -24.000000) / 1.000000) = 24.020824
|
||||
@@ -0,0 +1 @@
|
||||
(sqrt(-1.000000, 1.000000) / 4.000000) = 0.353553
|
||||
@@ -0,0 +1 @@
|
||||
(sqrt(1.000000, -1.000000) / 100.000000) = 0.014142
|
||||
@@ -0,0 +1 @@
|
||||
(sqrt(1.000000, 0.000000) / 100.000000) = 0.010000
|
||||
@@ -0,0 +1 @@
|
||||
(sqrt(1.000000, 1.000000) / 1.000000) = 1.414214
|
||||
@@ -0,0 +1 @@
|
||||
(sqrt(1.000000, 21.000000) / 1.000000) = 21.023796
|
||||
@@ -0,0 +1 @@
|
||||
(sqrt(1.000000, 4.000000) / 1.000000) = 4.123106
|
||||
@@ -0,0 +1 @@
|
||||
(sqrt(64.000000, -37.000000) / 1.000000) = 73.925638
|
||||
@@ -0,0 +1,137 @@
|
||||
#include <assert.h>
|
||||
#include <linux/limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static int diff(const char *content, const char *filename) {
|
||||
|
||||
int pipe_fd[2];
|
||||
pid_t child_pid;
|
||||
|
||||
// Create a pipe for communication
|
||||
if (pipe(pipe_fd) == -1) {
|
||||
perror("Pipe creation failed");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Fork the process
|
||||
if ((child_pid = fork()) == -1) {
|
||||
perror("Fork failed");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (child_pid == 0) { // Child process
|
||||
// Close the write end of the pipe
|
||||
close(pipe_fd[1]);
|
||||
|
||||
// Redirect stdin to read from the pipe
|
||||
dup2(pipe_fd[0], STDIN_FILENO);
|
||||
|
||||
// Execute a command (e.g., "wc -l")
|
||||
execlp("diff", "diff", "-u", "--color", filename, "-", NULL);
|
||||
|
||||
// If execlp fails
|
||||
perror("Exec failed");
|
||||
exit(EXIT_FAILURE);
|
||||
} else { // Parent process
|
||||
// Close the read end of the pipe
|
||||
close(pipe_fd[0]);
|
||||
|
||||
// Write data to the child process
|
||||
if (write(pipe_fd[1], content, strlen(content)) == -1) {
|
||||
perror("failed to write content to the pipe for diff");
|
||||
}
|
||||
|
||||
close(pipe_fd[1]);
|
||||
|
||||
// Wait for the child process to finish
|
||||
wait(NULL);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int get_current_working_dir(char *buf, size_t buf_size) {
|
||||
if (getcwd(buf, buf_size) != NULL) {
|
||||
return 0;
|
||||
}
|
||||
perror("getcwd() error");
|
||||
return 1;
|
||||
}
|
||||
|
||||
static char *create_snapshot_file_path(const char *filename) {
|
||||
char cwd[PATH_MAX];
|
||||
if (get_current_working_dir(cwd, PATH_MAX)) {
|
||||
return NULL;
|
||||
};
|
||||
|
||||
static char filepath[PATH_MAX];
|
||||
sprintf(filepath, "%s/tests/snapshots/%s", cwd, filename);
|
||||
return filepath;
|
||||
}
|
||||
|
||||
static void assert_snapshot(const char *__filename, const char *content) {
|
||||
char *filename = create_snapshot_file_path(__filename);
|
||||
if (filename == NULL) {
|
||||
fprintf(stderr, "failed to create snapshot file: %s\n", filename);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
int snapshot_file_exists = access(filename, F_OK) != -1;
|
||||
FILE *snapshot_file;
|
||||
|
||||
if (snapshot_file_exists) {
|
||||
snapshot_file = fopen(filename, "r");
|
||||
} else {
|
||||
snapshot_file = fopen(filename, "w");
|
||||
}
|
||||
|
||||
if (snapshot_file == NULL) {
|
||||
fprintf(stderr, "failed to open or create the snapshot file: %s\n",
|
||||
filename);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (snapshot_file_exists) {
|
||||
struct stat stats;
|
||||
int file_size;
|
||||
|
||||
stat(filename, &stats);
|
||||
file_size = stats.st_size;
|
||||
char *snapshot = (char *)malloc(stats.st_size + 1);
|
||||
|
||||
if (snapshot == NULL) {
|
||||
fprintf(stderr,
|
||||
"failed to allocate %zd bytes of a string for the snapshot "
|
||||
"content in file: %s\n",
|
||||
stats.st_size, filename);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
size_t bytes_read = fread(snapshot, 1, file_size, snapshot_file);
|
||||
if (bytes_read != file_size) {
|
||||
fprintf(stderr, "failed to read a snapshot file %s\n", filename);
|
||||
exit(1);
|
||||
}
|
||||
snapshot[file_size] = 0; // null byte terminator
|
||||
|
||||
int string_test_diff = strcmp(snapshot, content);
|
||||
|
||||
diff(content, filename);
|
||||
|
||||
/* dbg("diff in content = %d: snapshot '%s' vs now '%s'", string_test, */
|
||||
/* snapshot, content); */
|
||||
assert(string_test_diff == 0);
|
||||
} else {
|
||||
fprintf(snapshot_file, "%s", content);
|
||||
printf("created a snapshot file %s\n", filename);
|
||||
}
|
||||
|
||||
fclose(snapshot_file);
|
||||
}
|
||||
|
||||
#define print_success printf("[%s]\t\tAll tests passed!\n", __FILE_NAME__)
|
||||
@@ -0,0 +1,28 @@
|
||||
#ifndef _UTILS_H_
|
||||
#define _UTILS_H_
|
||||
|
||||
#ifdef __KERNEL__
|
||||
#include <linux/types.h>
|
||||
#else
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
|
||||
#include "dbg.h"
|
||||
|
||||
static inline int64_t div128_s64_s64_s64(int64_t high, int64_t low,
|
||||
int64_t divisor) {
|
||||
int64_t result;
|
||||
// s.high.low
|
||||
// high -> rdx
|
||||
// low -> rax
|
||||
uint64_t remainder;
|
||||
__asm__("idivq %[B]"
|
||||
: "=a"(result), "=d"(remainder)
|
||||
: [B] "r"(divisor), "a"(low), "d"(high));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static inline int is_digit(char c) { return '0' <= c && c <= '9'; }
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user