Skip to content

Commit 182934a

Browse files
Roderick Colenbranderbentiss
authored andcommitted
HID: playstation: stop DualSense output work on remove.
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 <[email protected]> CC: [email protected] Signed-off-by: Benjamin Tissoires <[email protected]> Link: https://lore.kernel.org/r/[email protected]
1 parent bb5f0c8 commit 182934a

File tree

1 file changed

+36
-5
lines changed

1 file changed

+36
-5
lines changed

drivers/hid/hid-playstation.c

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ struct ps_device {
4646
uint32_t fw_version;
4747

4848
int (*parse_report)(struct ps_device *dev, struct hid_report *report, u8 *data, int size);
49+
void (*remove)(struct ps_device *dev);
4950
};
5051

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

176177
struct work_struct output_worker;
178+
bool output_worker_initialized;
177179
void *output_report_dmabuf;
178180
uint8_t output_seq; /* Sequence number for output report. */
179181
};
@@ -299,6 +301,7 @@ static const struct {int x; int y; } ps_gamepad_hat_mapping[] = {
299301
{0, 0},
300302
};
301303

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

304307
/*
@@ -789,6 +792,7 @@ static int dualsense_get_calibration_data(struct dualsense *ds)
789792
return ret;
790793
}
791794

795+
792796
static int dualsense_get_firmware_info(struct dualsense *ds)
793797
{
794798
uint8_t *buf;
@@ -878,7 +882,7 @@ static int dualsense_player_led_set_brightness(struct led_classdev *led, enum le
878882
ds->update_player_leds = true;
879883
spin_unlock_irqrestore(&ds->base.lock, flags);
880884

881-
schedule_work(&ds->output_worker);
885+
dualsense_schedule_work(ds);
882886

883887
return 0;
884888
}
@@ -922,6 +926,16 @@ static void dualsense_init_output_report(struct dualsense *ds, struct dualsense_
922926
}
923927
}
924928

929+
static inline void dualsense_schedule_work(struct dualsense *ds)
930+
{
931+
unsigned long flags;
932+
933+
spin_lock_irqsave(&ds->base.lock, flags);
934+
if (ds->output_worker_initialized)
935+
schedule_work(&ds->output_worker);
936+
spin_unlock_irqrestore(&ds->base.lock, flags);
937+
}
938+
925939
/*
926940
* Helper function to send DualSense output reports. Applies a CRC at the end of a report
927941
* for Bluetooth reports.
@@ -1082,7 +1096,7 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r
10821096
spin_unlock_irqrestore(&ps_dev->lock, flags);
10831097

10841098
/* Schedule updating of microphone state at hardware level. */
1085-
schedule_work(&ds->output_worker);
1099+
dualsense_schedule_work(ds);
10861100
}
10871101
ds->last_btn_mic_state = btn_mic_state;
10881102

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

1200-
schedule_work(&ds->output_worker);
1214+
dualsense_schedule_work(ds);
12011215
return 0;
12021216
}
12031217

1218+
static void dualsense_remove(struct ps_device *ps_dev)
1219+
{
1220+
struct dualsense *ds = container_of(ps_dev, struct dualsense, base);
1221+
unsigned long flags;
1222+
1223+
spin_lock_irqsave(&ds->base.lock, flags);
1224+
ds->output_worker_initialized = false;
1225+
spin_unlock_irqrestore(&ds->base.lock, flags);
1226+
1227+
cancel_work_sync(&ds->output_worker);
1228+
}
1229+
12041230
static int dualsense_reset_leds(struct dualsense *ds)
12051231
{
12061232
struct dualsense_output_report report;
@@ -1237,7 +1263,7 @@ static void dualsense_set_lightbar(struct dualsense *ds, uint8_t red, uint8_t gr
12371263
ds->lightbar_blue = blue;
12381264
spin_unlock_irqrestore(&ds->base.lock, flags);
12391265

1240-
schedule_work(&ds->output_worker);
1266+
dualsense_schedule_work(ds);
12411267
}
12421268

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

12611287
ds->update_player_leds = true;
12621288
ds->player_leds_state = player_ids[player_id];
1263-
schedule_work(&ds->output_worker);
1289+
dualsense_schedule_work(ds);
12641290
}
12651291

12661292
static struct ps_device *dualsense_create(struct hid_device *hdev)
@@ -1299,7 +1325,9 @@ static struct ps_device *dualsense_create(struct hid_device *hdev)
12991325
ps_dev->battery_capacity = 100; /* initial value until parse_report. */
13001326
ps_dev->battery_status = POWER_SUPPLY_STATUS_UNKNOWN;
13011327
ps_dev->parse_report = dualsense_parse_report;
1328+
ps_dev->remove = dualsense_remove;
13021329
INIT_WORK(&ds->output_worker, dualsense_output_worker);
1330+
ds->output_worker_initialized = true;
13031331
hid_set_drvdata(hdev, ds);
13041332

13051333
max_output_report_size = sizeof(struct dualsense_output_report_bt);
@@ -1461,6 +1489,9 @@ static void ps_remove(struct hid_device *hdev)
14611489
ps_devices_list_remove(dev);
14621490
ps_device_release_player_id(dev);
14631491

1492+
if (dev->remove)
1493+
dev->remove(dev);
1494+
14641495
hid_hw_close(hdev);
14651496
hid_hw_stop(hdev);
14661497
}

0 commit comments

Comments
 (0)