Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rumble protocol throttling and concurrency fixups #204

Merged
merged 6 commits into from
Jun 27, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 109 additions & 30 deletions hid-xpadneo/src/hid-xpadneo.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@
* Copyright (c) 2017 Florian Dollinger <[email protected]>
*/

#include <linux/delay.h>
#include <linux/hid.h>
#include <linux/input.h>
#include <linux/module.h>
#include <linux/power_supply.h>
#include <linux/input.h> /* ff_memless(), ... */
#include <linux/module.h> /* MODULE_*, module_*, ... */
#include <linux/slab.h> /* kzalloc(), kfree(), ... */
#include <linux/delay.h> /* mdelay(), ... */
#include <linux/slab.h>
#include <linux/time.h>
#include "hid-ids.h" /* VENDOR_ID... */

#define DEBUG
Expand Down Expand Up @@ -81,6 +82,9 @@ MODULE_PARM_DESC(quriks,

static DEFINE_IDA(xpadneo_device_id_allocator);

#define XPADNEO_RUMBLE_THROTTLE_DELAY (10L * HZ / 1000)
#define XPADNEO_RUMBLE_THROTTLE_JIFFIES (jiffies + XPADNEO_RUMBLE_THROTTLE_DELAY)

enum {
FF_RUMBLE_NONE = 0x00,
FF_RUMBLE_WEAK = 0x01,
Expand Down Expand Up @@ -123,6 +127,7 @@ struct xpadneo_devdata {
u16 quirks;

/* battery information */
spinlock_t battery_lock;
struct power_supply_desc psy_desc;
u8 battery_report_id;
u8 battery_flags;
Expand All @@ -133,7 +138,10 @@ struct xpadneo_devdata {
s32 last_abs_rz;

/* buffer for ff_worker */
struct work_struct ff_worker;
spinlock_t ff_lock;
struct delayed_work ff_worker;
unsigned long ff_throttle_until;
bool ff_scheduled;
struct ff_data ff;
struct ff_data ff_shadow;
void *output_report_dmabuf;
Expand Down Expand Up @@ -202,16 +210,19 @@ static const struct usage_map xpadneo_usage_maps[] = {

static void xpadneo_ff_worker(struct work_struct *work)
{
struct xpadneo_devdata *xdata = container_of(work, struct xpadneo_devdata, ff_worker);
struct xpadneo_devdata *xdata =
container_of(to_delayed_work(work), struct xpadneo_devdata, ff_worker);
struct hid_device *hdev = xdata->hdev;
struct ff_report *r = xdata->output_report_dmabuf;
int ret;
unsigned long flags;

memset(r, 0, sizeof(*r));
r->report_id = XPADNEO_XB1S_FF_REPORT;
r->ff.enable = FF_RUMBLE_ALL;

/* if pulse is not supported, we do not have to care about explicitly
/*
* if pulse is not supported, we do not have to care about explicitly
* stopping the effect, the kernel will do this for us as part of its
* ff-memless emulation
*/
Expand All @@ -225,6 +236,11 @@ static void xpadneo_ff_worker(struct work_struct *work)
r->ff.loop_count = U8_MAX;
}

spin_lock_irqsave(&xdata->ff_lock, flags);

/* let our scheduler know we've been called */
xdata->ff_scheduled = false;

if (unlikely(xdata->quirks & XPADNEO_QUIRK_NO_TRIGGER_RUMBLE)) {
/* do not send these bits if not supported */
r->ff.enable &= ~FF_RUMBLE_TRIGGERS;
Expand All @@ -249,12 +265,22 @@ static void xpadneo_ff_worker(struct work_struct *work)
r->ff.enable &= ~FF_RUMBLE_RIGHT;

/* do not send a report if nothing changed */
if (unlikely(r->ff.enable == FF_RUMBLE_NONE))
if (unlikely(r->ff.enable == FF_RUMBLE_NONE)) {
spin_unlock_irqrestore(&xdata->ff_lock, flags);
return;
}

/* shadow our current rumble values for the next cycle */
memcpy(&xdata->ff_shadow, &xdata->ff, sizeof(xdata->ff));

/*
* throttle next command submission, the firmware doesn't like us to
* send rumble data any faster
*/
xdata->ff_throttle_until = XPADNEO_RUMBLE_THROTTLE_JIFFIES;

spin_unlock_irqrestore(&xdata->ff_lock, flags);

/* do not send these bits if not supported */
if (unlikely(xdata->quirks & XPADNEO_QUIRK_NO_MOTOR_MASK)) {
r->ff.enable = 0;
Expand All @@ -275,6 +301,8 @@ static int xpadneo_ff_play(struct input_dev *dev, void *data, struct ff_effect *
QUARTER = DIRECTION_LEFT,
};

unsigned long flags, ff_run_at, ff_throttle_until;
long delay_work;
int fraction_TL, fraction_TR, fraction_MAIN;
s32 weak, strong, direction, max_damped, max_unscaled;

Expand Down Expand Up @@ -355,10 +383,6 @@ static int xpadneo_ff_play(struct input_dev *dev, void *data, struct ff_effect *
break;
}

/* calculate the physical magnitudes, scale from 16 bit to 0..100 */
xdata->ff.magnitude_strong = (u8)((strong * fraction_MAIN) / U16_MAX);
xdata->ff.magnitude_weak = (u8)((weak * fraction_MAIN) / U16_MAX);

/*
* we want to keep the rumbling at the triggers at the maximum
* of the weak and strong main rumble
Expand All @@ -374,12 +398,50 @@ static int xpadneo_ff_play(struct input_dev *dev, void *data, struct ff_effect *
else
max_damped = max_unscaled;

spin_lock_irqsave(&xdata->ff_lock, flags);

/* calculate the physical magnitudes, scale from 16 bit to 0..100 */
xdata->ff.magnitude_strong = (u8)((strong * fraction_MAIN) / U16_MAX);
xdata->ff.magnitude_weak = (u8)((weak * fraction_MAIN) / U16_MAX);

/* calculate the physical magnitudes, scale from 16 bit to 0..100 */
xdata->ff.magnitude_left = (u8)((max_damped * fraction_TL) / U16_MAX);
xdata->ff.magnitude_right = (u8)((max_damped * fraction_TR) / U16_MAX);

/* synchronize: is our worker still scheduled? */
if (xdata->ff_scheduled) {
/* the worker is still guarding rumble programming */
hid_notice_once(hdev, "throttling rumble reprogramming\n");
goto unlock_and_return;
}

/* we want to run now but may be throttled */
ff_run_at = jiffies;
ff_throttle_until = xdata->ff_throttle_until;
if (time_before(ff_run_at, ff_throttle_until)) {
/* last rumble was recently executed */
delay_work = (long)ff_throttle_until - (long)ff_run_at;
} else {
/* the firmware is ready */
delay_work = 0;
}

/*
* sanitize: If 0 > delay > 1000ms, something is weird: this
* may happen if the delay between two rumble requests is
* several weeks long
*/
delay_work = min((long)HZ, delay_work);
delay_work = max(0L, delay_work);

/* schedule writing a rumble report to the controller */
schedule_work(&xdata->ff_worker);
if (schedule_delayed_work(&xdata->ff_worker, delay_work))
xdata->ff_scheduled = true;
else
hid_err(hdev, "lost rumble packet\n");

unlock_and_return:
spin_unlock_irqrestore(&xdata->ff_lock, flags);
return 0;
}

Expand Down Expand Up @@ -474,7 +536,7 @@ static int xpadneo_init_ff(struct hid_device *hdev)
struct xpadneo_devdata *xdata = hid_get_drvdata(hdev);
struct input_dev *idev = xdata->idev;

INIT_WORK(&xdata->ff_worker, xpadneo_ff_worker);
INIT_DELAYED_WORK(&xdata->ff_worker, xpadneo_ff_worker);
xdata->output_report_dmabuf = devm_kzalloc(&hdev->dev,
sizeof(struct ff_report), GFP_KERNEL);
if (xdata->output_report_dmabuf == NULL)
Expand All @@ -486,6 +548,9 @@ static int xpadneo_init_ff(struct hid_device *hdev)
if (param_ff_connect_notify)
xpadneo_welcome_rumble(hdev);

/* initialize our rumble command throttle */
xdata->ff_throttle_until = XPADNEO_RUMBLE_THROTTLE_JIFFIES;

input_set_capability(idev, EV_FF, FF_RUMBLE);
return input_ff_create_memless(idev, NULL, xpadneo_ff_play);
}
Expand All @@ -501,10 +566,9 @@ static int xpadneo_get_battery_property(struct power_supply *psy,
union power_supply_propval *val)
{
struct xpadneo_devdata *xdata = power_supply_get_drvdata(psy);

u8 flags = xdata->battery_flags;
u8 level = XPADNEO_BATTERY_CAPACITY_LEVEL(flags);
u8 charging = XPADNEO_BATTERY_CHARGING(flags);
unsigned long flags;
int ret = 0;
u8 battery_flags, level, charging;

static int capacity_level_map[] = {
[0] = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL,
Expand All @@ -516,6 +580,12 @@ static int xpadneo_get_battery_property(struct power_supply *psy,
if (!xdata->battery)
return -EINVAL;

spin_lock_irqsave(&xdata->battery_lock, flags);

battery_flags = xdata->battery_flags;
level = XPADNEO_BATTERY_CAPACITY_LEVEL(battery_flags);
charging = XPADNEO_BATTERY_CHARGING(battery_flags);

switch (property) {
case POWER_SUPPLY_PROP_CAPACITY:
val->intval = xdata->battery_capacity;
Expand All @@ -525,8 +595,8 @@ static int xpadneo_get_battery_property(struct power_supply *psy,
if (level >= ARRAY_SIZE(capacity_level_map)
|| xdata->psy_desc.type == POWER_SUPPLY_TYPE_UNKNOWN)
val->intval = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
else if (XPADNEO_BATTERY_MODE(flags)
|| XPADNEO_BATTERY_CHARGING(flags))
else if (XPADNEO_BATTERY_MODE(battery_flags)
|| XPADNEO_BATTERY_CHARGING(battery_flags))
val->intval = capacity_level_map[level];
else
val->intval = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
Expand All @@ -537,11 +607,11 @@ static int xpadneo_get_battery_property(struct power_supply *psy,
break;

case POWER_SUPPLY_PROP_ONLINE:
val->intval = XPADNEO_BATTERY_ONLINE(flags);
val->intval = XPADNEO_BATTERY_ONLINE(battery_flags);
break;

case POWER_SUPPLY_PROP_PRESENT:
val->intval = XPADNEO_BATTERY_PRESENT(flags);
val->intval = XPADNEO_BATTERY_PRESENT(battery_flags);
break;

case POWER_SUPPLY_PROP_SCOPE:
Expand All @@ -551,21 +621,24 @@ static int xpadneo_get_battery_property(struct power_supply *psy,
case POWER_SUPPLY_PROP_STATUS:
if (xdata->psy_desc.type == POWER_SUPPLY_TYPE_UNKNOWN)
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
else if (!charging && XPADNEO_BATTERY_CAPACITY_LEVEL(flags) == 3)
else if (!charging && XPADNEO_BATTERY_CAPACITY_LEVEL(battery_flags) == 3)
val->intval = POWER_SUPPLY_STATUS_FULL;
else if (charging)
val->intval = POWER_SUPPLY_STATUS_CHARGING;
else if (XPADNEO_BATTERY_PRESENT(flags))
else if (XPADNEO_BATTERY_PRESENT(battery_flags))
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
else
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
break;

default:
return -EINVAL;
ret = -EINVAL;
break;
}

return 0;
spin_unlock_irqrestore(&xdata->battery_lock, flags);

return ret;
}

static int xpadneo_setup_battery(struct hid_device *hdev, struct hid_field *field)
Expand Down Expand Up @@ -644,14 +717,14 @@ static int xpadneo_input_mapping(struct hid_device *hdev, struct hid_input *hi,

if (usage->hid == HID_DC_BATTERYSTRENGTH) {
xpadneo_setup_battery(hdev, field);
return -1;
return MAP_IGNORE;
}

for (i = 0; i < ARRAY_SIZE(xpadneo_usage_maps); i++) {
const struct usage_map *entry = &xpadneo_usage_maps[i];

if (entry->usage == usage->hid) {
if (entry->behaviour == 1) {
if (entry->behaviour == MAP_STATIC) {
hid_map_usage_clear(hi, usage, bit, max,
entry->ev.event_type, entry->ev.input_code);
}
Expand All @@ -660,7 +733,7 @@ static int xpadneo_input_mapping(struct hid_device *hdev, struct hid_input *hi,
}

/* let HID handle this */
return 0;
return MAP_AUTO;
}

static u8 *xpadneo_report_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *rsize)
Expand Down Expand Up @@ -714,6 +787,10 @@ static u8 *xpadneo_report_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int

static void xpadneo_update_battery(struct xpadneo_devdata *xdata, u8 value)
{
unsigned long flags;

spin_lock_irqsave(&xdata->battery_lock, flags);

xdata->battery_flags = value;
switch (XPADNEO_BATTERY_MODE(value)) {
case 0:
Expand Down Expand Up @@ -745,6 +822,8 @@ static void xpadneo_update_battery(struct xpadneo_devdata *xdata, u8 value)
}
}

spin_unlock_irqrestore(&xdata->battery_lock, flags);

power_supply_changed(xdata->battery);
}

Expand Down Expand Up @@ -972,7 +1051,7 @@ static void xpadneo_remove(struct hid_device *hdev)

hid_hw_close(hdev);

cancel_work_sync(&xdata->ff_worker);
cancel_delayed_work_sync(&xdata->ff_worker);

xpadneo_release_device_id(xdata);
hid_hw_stop(hdev);
Expand Down