Skip to content

Commit

Permalink
HID: playstation: stop DualSense output work on remove.
Browse files Browse the repository at this point in the history
Ensure we don't schedule any new output work on removal and wait
for any existing work to complete. If we don't do this e.g. rumble
work can get queued during deletion and we trigger a kernel crash.

Signed-off-by: Roderick Colenbrander <roderick.colenbrander@sony.com>
CC: stable@vger.kernel.org
Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
Link: https://lore.kernel.org/r/20221010212313.78275-2-roderick.colenbrander@sony.com
  • Loading branch information
Roderick Colenbrander authored and Benjamin Tissoires committed Oct 14, 2022
1 parent bb5f0c8 commit 182934a
Showing 1 changed file with 36 additions and 5 deletions.
41 changes: 36 additions & 5 deletions drivers/hid/hid-playstation.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ struct ps_device {
uint32_t fw_version;

int (*parse_report)(struct ps_device *dev, struct hid_report *report, u8 *data, int size);
void (*remove)(struct ps_device *dev);
};

/* Calibration data for playstation motion sensors. */
Expand Down Expand Up @@ -174,6 +175,7 @@ struct dualsense {
struct led_classdev player_leds[5];

struct work_struct output_worker;
bool output_worker_initialized;
void *output_report_dmabuf;
uint8_t output_seq; /* Sequence number for output report. */
};
Expand Down Expand Up @@ -299,6 +301,7 @@ static const struct {int x; int y; } ps_gamepad_hat_mapping[] = {
{0, 0},
};

static inline void dualsense_schedule_work(struct dualsense *ds);
static void dualsense_set_lightbar(struct dualsense *ds, uint8_t red, uint8_t green, uint8_t blue);

/*
Expand Down Expand Up @@ -789,6 +792,7 @@ static int dualsense_get_calibration_data(struct dualsense *ds)
return ret;
}


static int dualsense_get_firmware_info(struct dualsense *ds)
{
uint8_t *buf;
Expand Down Expand Up @@ -878,7 +882,7 @@ static int dualsense_player_led_set_brightness(struct led_classdev *led, enum le
ds->update_player_leds = true;
spin_unlock_irqrestore(&ds->base.lock, flags);

schedule_work(&ds->output_worker);
dualsense_schedule_work(ds);

return 0;
}
Expand Down Expand Up @@ -922,6 +926,16 @@ static void dualsense_init_output_report(struct dualsense *ds, struct dualsense_
}
}

static inline void dualsense_schedule_work(struct dualsense *ds)
{
unsigned long flags;

spin_lock_irqsave(&ds->base.lock, flags);
if (ds->output_worker_initialized)
schedule_work(&ds->output_worker);
spin_unlock_irqrestore(&ds->base.lock, flags);
}

/*
* Helper function to send DualSense output reports. Applies a CRC at the end of a report
* for Bluetooth reports.
Expand Down Expand Up @@ -1082,7 +1096,7 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r
spin_unlock_irqrestore(&ps_dev->lock, flags);

/* Schedule updating of microphone state at hardware level. */
schedule_work(&ds->output_worker);
dualsense_schedule_work(ds);
}
ds->last_btn_mic_state = btn_mic_state;

Expand Down Expand Up @@ -1197,10 +1211,22 @@ static int dualsense_play_effect(struct input_dev *dev, void *data, struct ff_ef
ds->motor_right = effect->u.rumble.weak_magnitude / 256;
spin_unlock_irqrestore(&ds->base.lock, flags);

schedule_work(&ds->output_worker);
dualsense_schedule_work(ds);
return 0;
}

static void dualsense_remove(struct ps_device *ps_dev)
{
struct dualsense *ds = container_of(ps_dev, struct dualsense, base);
unsigned long flags;

spin_lock_irqsave(&ds->base.lock, flags);
ds->output_worker_initialized = false;
spin_unlock_irqrestore(&ds->base.lock, flags);

cancel_work_sync(&ds->output_worker);
}

static int dualsense_reset_leds(struct dualsense *ds)
{
struct dualsense_output_report report;
Expand Down Expand Up @@ -1237,7 +1263,7 @@ static void dualsense_set_lightbar(struct dualsense *ds, uint8_t red, uint8_t gr
ds->lightbar_blue = blue;
spin_unlock_irqrestore(&ds->base.lock, flags);

schedule_work(&ds->output_worker);
dualsense_schedule_work(ds);
}

static void dualsense_set_player_leds(struct dualsense *ds)
Expand All @@ -1260,7 +1286,7 @@ static void dualsense_set_player_leds(struct dualsense *ds)

ds->update_player_leds = true;
ds->player_leds_state = player_ids[player_id];
schedule_work(&ds->output_worker);
dualsense_schedule_work(ds);
}

static struct ps_device *dualsense_create(struct hid_device *hdev)
Expand Down Expand Up @@ -1299,7 +1325,9 @@ static struct ps_device *dualsense_create(struct hid_device *hdev)
ps_dev->battery_capacity = 100; /* initial value until parse_report. */
ps_dev->battery_status = POWER_SUPPLY_STATUS_UNKNOWN;
ps_dev->parse_report = dualsense_parse_report;
ps_dev->remove = dualsense_remove;
INIT_WORK(&ds->output_worker, dualsense_output_worker);
ds->output_worker_initialized = true;
hid_set_drvdata(hdev, ds);

max_output_report_size = sizeof(struct dualsense_output_report_bt);
Expand Down Expand Up @@ -1461,6 +1489,9 @@ static void ps_remove(struct hid_device *hdev)
ps_devices_list_remove(dev);
ps_device_release_player_id(dev);

if (dev->remove)
dev->remove(dev);

hid_hw_close(hdev);
hid_hw_stop(hdev);
}
Expand Down

0 comments on commit 182934a

Please sign in to comment.