mirror of
https://github.com/torvalds/linux.git
synced 2026-11-06 21:58:01 +01:00
Merge branch 'for-7.4/asus' into for-linus
- SG Mobile LED support (Denis Benato) - power management fix for ROG Ally (Deni Benato) - asus_work() hardening (Denis Benato) - fix for invalid keyboard feature reports being sent on ASUS laptops with I2C/HID touchpads (Panz Dev)
This commit is contained in:
commit
5ac62bdef7
|
|
@ -51,6 +51,8 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
|
|||
#define FEATURE_KBD_LED_REPORT_ID1 0x5d
|
||||
#define FEATURE_KBD_LED_REPORT_ID2 0x5e
|
||||
|
||||
#define ROG_XGM_REPORT_SIZE 300
|
||||
|
||||
#define ROG_ALLY_REPORT_SIZE 64
|
||||
#define ROG_ALLY_X_MIN_MCU 313
|
||||
#define ROG_ALLY_MIN_MCU 319
|
||||
|
|
@ -144,6 +146,11 @@ struct asus_worker {
|
|||
bool removed;
|
||||
};
|
||||
|
||||
struct asus_xgm_led {
|
||||
struct led_classdev cdev;
|
||||
struct hid_device *hdev;
|
||||
};
|
||||
|
||||
struct asus_touchpad_info {
|
||||
int max_x;
|
||||
int max_y;
|
||||
|
|
@ -170,6 +177,7 @@ struct asus_drvdata {
|
|||
unsigned long battery_next_query;
|
||||
struct asus_hid_listener listener;
|
||||
bool fn_lock;
|
||||
struct asus_xgm_led *xgm_led;
|
||||
};
|
||||
|
||||
static int asus_report_battery(struct asus_drvdata *, u8 *, int);
|
||||
|
|
@ -765,9 +773,14 @@ static void asus_work(struct work_struct *work)
|
|||
struct asus_work_action *action = NULL;
|
||||
unsigned long flags;
|
||||
|
||||
/* Save the action to be performed and clear the flag */
|
||||
/*
|
||||
* Dequeue the next action, if any. Once teardown has begun .removed
|
||||
* is set and asus_worker_stop() drains the queue: leave the queued
|
||||
* actions alone, they are dropped instead of being executed against
|
||||
* a device that is being removed.
|
||||
*/
|
||||
spin_lock_irqsave(&worker->lock, flags);
|
||||
if (!list_empty(&worker->actions)) {
|
||||
if (!worker->removed && !list_empty(&worker->actions)) {
|
||||
action = list_first_entry(&worker->actions,
|
||||
struct asus_work_action, node);
|
||||
list_del(&action->node);
|
||||
|
|
@ -817,6 +830,25 @@ static int asus_worker_create(struct hid_device *hdev, struct asus_drvdata *drvd
|
|||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* asus_worker_stop - quiesce the worker
|
||||
* @worker: the worker to quiesce
|
||||
*
|
||||
* Once this function returns no more actions can be queued and no instance
|
||||
* of asus_work() is running or pending.
|
||||
*
|
||||
* Callers must do this before hid_hw_stop(): actions are executed while the
|
||||
* device is fully operational, since they send raw requests to it and, in
|
||||
* the fan-key fallback path, re-inject raw reports into the HID core. After
|
||||
* hid_hw_stop() the transport is gone (usbhid_stop() frees the URBs and the
|
||||
* I/O buffers) and the input devices have been unregistered.
|
||||
*
|
||||
* The quiescing is race free because every site that queues an action holds
|
||||
* worker->lock across the .removed check, the list insertion and
|
||||
* schedule_work(): anything scheduled before .removed is set here is caught
|
||||
* by the cancel_work_sync() below, anything after it is discarded by
|
||||
* asus_worker_schedule().
|
||||
*/
|
||||
static void asus_worker_stop(struct asus_worker *worker)
|
||||
{
|
||||
struct asus_work_action *action, *tmp;
|
||||
|
|
@ -1137,6 +1169,26 @@ static int asus_battery_probe(struct hid_device *hdev)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int asus_xgm_led_set(struct led_classdev *led_cdev, enum led_brightness value)
|
||||
{
|
||||
const u8 buf[ROG_XGM_REPORT_SIZE] = {
|
||||
FEATURE_KBD_LED_REPORT_ID2, 0xC5, (value) ? 0x50 : 0x00
|
||||
};
|
||||
struct asus_xgm_led *xgm = container_of(led_cdev, struct asus_xgm_led, cdev);
|
||||
int ret;
|
||||
|
||||
ret = asus_kbd_set_report(xgm->hdev, buf, ROG_XGM_REPORT_SIZE);
|
||||
if (ret < 0) {
|
||||
hid_err(xgm->hdev, "Unable to set XG mobile led state: %d\n", ret);
|
||||
return ret;
|
||||
} else if (ret != ROG_XGM_REPORT_SIZE) {
|
||||
hid_err(xgm->hdev, "Unexpected partial transfer to XG mobile: %d\n", ret);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int asus_input_configured(struct hid_device *hdev, struct hid_input *hi)
|
||||
{
|
||||
struct input_dev *input = hi->input;
|
||||
|
|
@ -1360,6 +1412,71 @@ static int asus_start_multitouch(struct hid_device *hdev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize the reports of the device.
|
||||
*
|
||||
* Failures are intentionally not fatal: asus_kbd_init() tolerates a wrong
|
||||
* handshake until this is verified to work for all devices, so a failure
|
||||
* is only reported and the initialization of the remaining reports is
|
||||
* still attempted.
|
||||
*/
|
||||
static void asus_initialize_reports(struct hid_device *hdev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
for (int r = 0; r < ARRAY_SIZE(asus_report_id_init); r++) {
|
||||
if (asus_has_report_id(hdev, asus_report_id_init[r])) {
|
||||
ret = asus_kbd_init(hdev, asus_report_id_init[r]);
|
||||
if (ret < 0)
|
||||
hid_warn(hdev, "Failed to initialize 0x%x: %d.\n",
|
||||
asus_report_id_init[r], ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int asus_xgm_init(struct hid_device *hdev, struct asus_drvdata *drvdata)
|
||||
{
|
||||
const char *name;
|
||||
int ret;
|
||||
|
||||
drvdata->xgm_led = devm_kzalloc(&hdev->dev, sizeof(*drvdata->xgm_led), GFP_KERNEL);
|
||||
if (drvdata->xgm_led == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "asus:xgm-%s:led",
|
||||
strlen(hdev->uniq) ? hdev->uniq : dev_name(&hdev->dev));
|
||||
|
||||
if (name == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto asus_xgm_init_err;
|
||||
}
|
||||
|
||||
drvdata->xgm_led->hdev = hdev;
|
||||
drvdata->xgm_led->cdev.name = name;
|
||||
drvdata->xgm_led->cdev.brightness = 1;
|
||||
drvdata->xgm_led->cdev.max_brightness = 1;
|
||||
drvdata->xgm_led->cdev.brightness_set_blocking = asus_xgm_led_set;
|
||||
drvdata->xgm_led->cdev.flags = LED_CORE_SUSPENDRESUME;
|
||||
|
||||
/* LED state is arbitrary on boot, set a default */
|
||||
ret = asus_xgm_led_set(&drvdata->xgm_led->cdev, drvdata->xgm_led->cdev.brightness);
|
||||
if (ret) {
|
||||
hid_err(hdev, "Asus failed to set xgm led: %d\n", ret);
|
||||
goto asus_xgm_init_err;
|
||||
}
|
||||
|
||||
ret = devm_led_classdev_register(&hdev->dev, &drvdata->xgm_led->cdev);
|
||||
if (ret) {
|
||||
hid_err(hdev, "Asus failed to register xgm led: %d\n", ret);
|
||||
goto asus_xgm_init_err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
asus_xgm_init_err:
|
||||
drvdata->xgm_led = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __maybe_unused asus_resume(struct hid_device *hdev)
|
||||
{
|
||||
struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
|
||||
|
|
@ -1379,6 +1496,9 @@ static int __maybe_unused asus_reset_resume(struct hid_device *hdev)
|
|||
{
|
||||
struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
|
||||
|
||||
if (!drvdata->tp)
|
||||
asus_initialize_reports(hdev);
|
||||
|
||||
if (drvdata->tp)
|
||||
return asus_start_multitouch(hdev);
|
||||
|
||||
|
|
@ -1493,12 +1613,16 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
|||
return ret;
|
||||
}
|
||||
|
||||
for (int r = 0; r < ARRAY_SIZE(asus_report_id_init); r++) {
|
||||
if (asus_has_report_id(hdev, asus_report_id_init[r])) {
|
||||
ret = asus_kbd_init(hdev, asus_report_id_init[r]);
|
||||
if (ret < 0)
|
||||
hid_warn(hdev, "Failed to initialize 0x%x: %d.\n",
|
||||
asus_report_id_init[r], ret);
|
||||
if (!drvdata->tp)
|
||||
asus_initialize_reports(hdev);
|
||||
|
||||
if (asus_has_report_id(hdev, FEATURE_KBD_REPORT_ID) &&
|
||||
((hdev->product == USB_DEVICE_ID_ASUSTEK_XGM_2022) ||
|
||||
(hdev->product == USB_DEVICE_ID_ASUSTEK_XGM_2023))) {
|
||||
ret = asus_xgm_init(hdev, drvdata);
|
||||
if (ret) {
|
||||
hid_err(hdev, "Failed to initialize xg mobile: %d\n", ret);
|
||||
goto err_stop_hw;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1539,6 +1663,9 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
|||
if (drvdata->listener.brightness_set)
|
||||
asus_hid_unregister_listener(&drvdata->listener);
|
||||
|
||||
if (drvdata->xgm_led)
|
||||
devm_led_classdev_unregister(&hdev->dev, &drvdata->xgm_led->cdev);
|
||||
|
||||
asus_worker_stop(drvdata->worker);
|
||||
hid_hw_stop(hdev);
|
||||
return ret;
|
||||
|
|
@ -1551,6 +1678,9 @@ static void asus_remove(struct hid_device *hdev)
|
|||
if (drvdata->listener.brightness_set)
|
||||
asus_hid_unregister_listener(&drvdata->listener);
|
||||
|
||||
if (drvdata->xgm_led)
|
||||
devm_led_classdev_unregister(&hdev->dev, &drvdata->xgm_led->cdev);
|
||||
|
||||
asus_worker_stop(drvdata->worker);
|
||||
hid_hw_stop(hdev);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user