From patchwork Sat Mar 21 11:47:37 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Michal_Mal=C3=BD?= X-Patchwork-Id: 6063981 X-Patchwork-Delegate: jikos@jikos.cz Return-Path: X-Original-To: patchwork-linux-input@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork2.web.kernel.org (Postfix) with ESMTP id 83642BF90F for ; Sat, 21 Mar 2015 11:50:25 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 92E51202EB for ; Sat, 21 Mar 2015 11:50:23 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 7DECC202E6 for ; Sat, 21 Mar 2015 11:50:21 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751965AbbCULtX (ORCPT ); Sat, 21 Mar 2015 07:49:23 -0400 Received: from gitweb.devoid-pointer.net ([31.31.77.140]:59566 "EHLO smtp.devoid-pointer.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751759AbbCULsY (ORCPT ); Sat, 21 Mar 2015 07:48:24 -0400 Received: from Labrat.localdomain.localdomain (228.123.broadband5.iol.cz [88.100.123.228]) (using TLSv1.2 with cipher AES128-SHA256 (128/128 bits)) (No client certificate requested) by smtp.devoid-pointer.net (Postfix) with ESMTPSA id 9BEF021A26; Sat, 21 Mar 2015 12:48:12 +0100 (CET) From: =?UTF-8?q?Michal=20Mal=C3=BD?= To: jkosina@suse.cz Cc: linux-input@vger.kernel.org, linux-kernel@vger.kernel.org, elias.vds@gmail.com, simon@mungewell.org, =?UTF-8?q?Michal=20Mal=C3=BD?= Subject: [PATCH 07/12] HID: hid-lg4ff: Protect concurrent access to the output HID report values with a spinlock. Date: Sat, 21 Mar 2015 12:47:37 +0100 Message-Id: <1426938462-884-8-git-send-email-madcatxster@devoid-pointer.net> X-Mailer: git-send-email 2.3.3 In-Reply-To: <1426938462-884-1-git-send-email-madcatxster@devoid-pointer.net> References: <1426938462-884-1-git-send-email-madcatxster@devoid-pointer.net> MIME-Version: 1.0 Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org X-Spam-Status: No, score=-6.9 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_HI, T_RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Since all functions that need to send some data to the device they manage share the same HID report some synchronization is needed to prevent sending bogus data to the device. Signed-off-by: Michal MalĂ˝ --- drivers/hid/hid-lg.c | 4 +- drivers/hid/hid-lg4ff.c | 293 ++++++++++++++++++++++++++++++++++-------------- 2 files changed, 213 insertions(+), 84 deletions(-) diff --git a/drivers/hid/hid-lg.c b/drivers/hid/hid-lg.c index c797781..c3981da 100644 --- a/drivers/hid/hid-lg.c +++ b/drivers/hid/hid-lg.c @@ -734,8 +734,8 @@ static void lg_remove(struct hid_device *hdev) struct lg_drv_data *drv_data = hid_get_drvdata(hdev); if (drv_data->quirks & LG_FF4) lg4ff_deinit(hdev); - - hid_hw_stop(hdev); + else + hid_hw_stop(hdev); kfree(drv_data); } diff --git a/drivers/hid/hid-lg4ff.c b/drivers/hid/hid-lg4ff.c index a2f47ee..0bbdeea 100644 --- a/drivers/hid/hid-lg4ff.c +++ b/drivers/hid/hid-lg4ff.c @@ -71,7 +71,7 @@ static void lg4ff_set_range_dfp(struct hid_device *hid, u16 range); static void lg4ff_set_range_g25(struct hid_device *hid, u16 range); -struct lg4ff_device_entry { +struct lg4ff_wheel_data { u32 product_id; u16 range; u16 min_range; @@ -84,9 +84,15 @@ struct lg4ff_device_entry { const char *real_tag; const char *real_name; u16 real_product_id; + void (*set_range)(struct hid_device *hid, u16 range); }; +struct lg4ff_device_entry { + spinlock_t report_lock; + struct lg4ff_wheel_data wdata; +}; + static const signed short lg4ff_wheel_effects[] = { FF_CONSTANT, FF_AUTOCENTER, @@ -278,11 +284,11 @@ int lg4ff_adjust_input_event(struct hid_device *hid, struct hid_field *field, return 0; } - switch (entry->product_id) { + switch (entry->wdata.product_id) { case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: switch (usage->code) { case ABS_X: - new_value = lg4ff_adjust_dfp_x_axis(value, entry->range); + new_value = lg4ff_adjust_dfp_x_axis(value, entry->wdata.range); input_event(field->hidinput->input, usage->type, usage->code, new_value); return 1; default: @@ -298,8 +304,23 @@ static int lg4ff_play(struct input_dev *dev, void *data, struct ff_effect *effec struct hid_device *hid = input_get_drvdata(dev); struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; struct hid_report *report = list_entry(report_list->next, struct hid_report, list); + struct lg4ff_device_entry *entry; + struct lg_drv_data *drv_data; s32 *value = report->field[0]->value; int x; + unsigned long flags; + + drv_data = hid_get_drvdata(hid); + if (!drv_data) { + hid_err(hid, "Private driver data not found!\n"); + return -EINVAL; + } + + entry = drv_data->device_props; + if (!entry) { + hid_err(hid, "Device properties not found!\n"); + return -EINVAL; + } #define CLAMP(x) do { if (x < 0) x = 0; else if (x > 0xff) x = 0xff; } while (0) @@ -308,6 +329,7 @@ static int lg4ff_play(struct input_dev *dev, void *data, struct ff_effect *effec x = effect->u.ramp.start_level + 0x80; /* 0x80 is no force */ CLAMP(x); + spin_lock_irqsave(&entry->report_lock, flags); if (x == 0x80) { /* De-activate force in slot-1*/ value[0] = 0x13; @@ -319,6 +341,7 @@ static int lg4ff_play(struct input_dev *dev, void *data, struct ff_effect *effec value[6] = 0x00; hid_hw_request(hid, report, HID_REQ_SET_REPORT); + spin_unlock_irqrestore(&entry->report_lock, flags); return 0; } @@ -331,6 +354,7 @@ static int lg4ff_play(struct input_dev *dev, void *data, struct ff_effect *effec value[6] = 0x00; hid_hw_request(hid, report, HID_REQ_SET_REPORT); + spin_unlock_irqrestore(&entry->report_lock, flags); break; } return 0; @@ -343,10 +367,11 @@ static void lg4ff_set_autocenter_default(struct input_dev *dev, u16 magnitude) struct hid_device *hid = input_get_drvdata(dev); struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; struct hid_report *report = list_entry(report_list->next, struct hid_report, list); - s32 *value = report->field[0]->value; - u32 expand_a, expand_b; struct lg4ff_device_entry *entry; struct lg_drv_data *drv_data; + s32 *value = report->field[0]->value; + u32 expand_a, expand_b; + unsigned long flags; drv_data = hid_get_drvdata(hid); if (!drv_data) { @@ -361,6 +386,7 @@ static void lg4ff_set_autocenter_default(struct input_dev *dev, u16 magnitude) } /* De-activate Auto-Center */ + spin_lock_irqsave(&entry->report_lock, flags); if (magnitude == 0) { value[0] = 0xf5; value[1] = 0x00; @@ -371,6 +397,7 @@ static void lg4ff_set_autocenter_default(struct input_dev *dev, u16 magnitude) value[6] = 0x00; hid_hw_request(hid, report, HID_REQ_SET_REPORT); + spin_unlock_irqrestore(&entry->report_lock, flags); return; } @@ -383,7 +410,7 @@ static void lg4ff_set_autocenter_default(struct input_dev *dev, u16 magnitude) } /* Adjust for non-MOMO wheels */ - switch (entry->product_id) { + switch (entry->wdata.product_id) { case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL: case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2: break; @@ -412,6 +439,7 @@ static void lg4ff_set_autocenter_default(struct input_dev *dev, u16 magnitude) value[6] = 0x00; hid_hw_request(hid, report, HID_REQ_SET_REPORT); + spin_unlock_irqrestore(&entry->report_lock, flags); } /* Sends autocentering command compatible with Formula Force EX */ @@ -420,9 +448,25 @@ static void lg4ff_set_autocenter_ffex(struct input_dev *dev, u16 magnitude) struct hid_device *hid = input_get_drvdata(dev); struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; struct hid_report *report = list_entry(report_list->next, struct hid_report, list); + struct lg4ff_device_entry *entry; + struct lg_drv_data *drv_data; + unsigned long flags; s32 *value = report->field[0]->value; magnitude = magnitude * 90 / 65535; + drv_data = hid_get_drvdata(hid); + if (!drv_data) { + hid_err(hid, "Private driver data not found!\n"); + return; + } + + entry = drv_data->device_props; + if (!entry) { + hid_err(hid, "Device properties not found!\n"); + return; + } + + spin_lock_irqsave(&entry->report_lock, flags); value[0] = 0xfe; value[1] = 0x03; value[2] = magnitude >> 14; @@ -432,6 +476,7 @@ static void lg4ff_set_autocenter_ffex(struct input_dev *dev, u16 magnitude) value[6] = 0x00; hid_hw_request(hid, report, HID_REQ_SET_REPORT); + spin_unlock_irqrestore(&entry->report_lock, flags); } /* Sends command to set range compatible with G25/G27/Driving Force GT */ @@ -439,10 +484,25 @@ static void lg4ff_set_range_g25(struct hid_device *hid, u16 range) { struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; struct hid_report *report = list_entry(report_list->next, struct hid_report, list); + struct lg4ff_device_entry *entry; + struct lg_drv_data *drv_data; s32 *value = report->field[0]->value; + unsigned long flags; + drv_data = hid_get_drvdata(hid); + if (!drv_data) { + hid_err(hid, "Private driver data not found!\n"); + return; + } + + entry = drv_data->device_props; + if (!entry) { + hid_err(hid, "Device properties not found!\n"); + return; + } dbg_hid("G25/G27/DFGT: setting range to %u\n", range); + spin_lock_irqsave(&entry->report_lock, flags); value[0] = 0xf8; value[1] = 0x81; value[2] = range & 0x00ff; @@ -452,6 +512,7 @@ static void lg4ff_set_range_g25(struct hid_device *hid, u16 range) value[6] = 0x00; hid_hw_request(hid, report, HID_REQ_SET_REPORT); + spin_unlock_irqrestore(&entry->report_lock, flags); } /* Sends commands to set range compatible with Driving Force Pro wheel */ @@ -459,11 +520,26 @@ static void lg4ff_set_range_dfp(struct hid_device *hid, u16 range) { struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; struct hid_report *report = list_entry(report_list->next, struct hid_report, list); + struct lg4ff_device_entry *entry; + struct lg_drv_data *drv_data; int start_left, start_right, full_range; s32 *value = report->field[0]->value; + unsigned long flags; + drv_data = hid_get_drvdata(hid); + if (!drv_data) { + hid_err(hid, "Private driver data not found!\n"); + return; + } + + entry = drv_data->device_props; + if (!entry) { + hid_err(hid, "Device properties not found!\n"); + return; + } dbg_hid("Driving Force Pro: setting range to %u\n", range); + spin_lock_irqsave(&entry->report_lock, flags); /* Prepare "coarse" limit command */ value[0] = 0xf8; value[1] = 0x00; /* Set later */ @@ -493,6 +569,7 @@ static void lg4ff_set_range_dfp(struct hid_device *hid, u16 range) if (range == 200 || range == 900) { /* Do not apply any fine limit */ hid_hw_request(hid, report, HID_REQ_SET_REPORT); + spin_unlock_irqrestore(&entry->report_lock, flags); return; } @@ -507,6 +584,7 @@ static void lg4ff_set_range_dfp(struct hid_device *hid, u16 range) value[6] = 0xff; hid_hw_request(hid, report, HID_REQ_SET_REPORT); + spin_unlock_irqrestore(&entry->report_lock, flags); } static const struct lg4ff_compat_mode_switch *lg4ff_get_mode_switch_command(const u16 real_product_id, const u16 target_product_id) @@ -570,9 +648,25 @@ static int lg4ff_switch_compatibility_mode(struct hid_device *hid, const struct { struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; struct hid_report *report = list_entry(report_list->next, struct hid_report, list); + struct lg4ff_device_entry *entry; + struct lg_drv_data *drv_data; s32 *value = report->field[0]->value; u8 i; + unsigned long flags; + + drv_data = hid_get_drvdata(hid); + if (!drv_data) { + hid_err(hid, "Private driver data not found!\n"); + return -EINVAL; + } + + entry = drv_data->device_props; + if (!entry) { + hid_err(hid, "Device properties not found!\n"); + return -EINVAL; + } + spin_lock_irqsave(&entry->report_lock, flags); for (i = 0; i < s->cmd_count; i++) { u8 j; @@ -581,6 +675,8 @@ static int lg4ff_switch_compatibility_mode(struct hid_device *hid, const struct hid_hw_request(hid, report, HID_REQ_SET_REPORT); } + spin_unlock_irqrestore(&entry->report_lock, flags); + hid_hw_wait(hid); return 0; } @@ -605,23 +701,23 @@ static ssize_t lg4ff_alternate_modes_show(struct device *dev, struct device_attr return 0; } - if (!entry->real_name) { + if (!entry->wdata.real_name) { hid_err(hid, "NULL pointer to string\n"); return 0; } for (i = 0; i < LG4FF_MODE_MAX_IDX; i++) { - if (entry->alternate_modes & BIT(i)) { + if (entry->wdata.alternate_modes & BIT(i)) { /* Print tag and full name */ count += scnprintf(buf + count, PAGE_SIZE - count, "%s: %s", lg4ff_alternate_modes[i].tag, - !lg4ff_alternate_modes[i].product_id ? entry->real_name : lg4ff_alternate_modes[i].name); + !lg4ff_alternate_modes[i].product_id ? entry->wdata.real_name : lg4ff_alternate_modes[i].name); if (count >= PAGE_SIZE - 1) return count; /* Mark the currently active mode with an asterisk */ - if (lg4ff_alternate_modes[i].product_id == entry->product_id || - (lg4ff_alternate_modes[i].product_id == 0 && entry->product_id == entry->real_product_id)) + if (lg4ff_alternate_modes[i].product_id == entry->wdata.product_id || + (lg4ff_alternate_modes[i].product_id == 0 && entry->wdata.product_id == entry->wdata.real_product_id)) count += scnprintf(buf + count, PAGE_SIZE - count, " *\n"); else count += scnprintf(buf + count, PAGE_SIZE - count, "\n"); @@ -674,10 +770,10 @@ static ssize_t lg4ff_alternate_modes_store(struct device *dev, struct device_att const u16 mode_product_id = lg4ff_alternate_modes[i].product_id; const char *tag = lg4ff_alternate_modes[i].tag; - if (entry->alternate_modes & BIT(i)) { + if (entry->wdata.alternate_modes & BIT(i)) { if (!strcmp(tag, lbuf)) { if (!mode_product_id) - target_product_id = entry->real_product_id; + target_product_id = entry->wdata.real_product_id; else target_product_id = mode_product_id; break; @@ -692,24 +788,24 @@ static ssize_t lg4ff_alternate_modes_store(struct device *dev, struct device_att } kfree(lbuf); /* Not needed anymore */ - if (target_product_id == entry->product_id) /* Nothing to do */ + if (target_product_id == entry->wdata.product_id) /* Nothing to do */ return count; /* Automatic switching has to be disabled for the switch to DF-EX mode to work correctly */ if (target_product_id == USB_DEVICE_ID_LOGITECH_WHEEL && !lg4ff_no_autoswitch) { hid_info(hid, "\"%s\" cannot be switched to \"DF-EX\" mode. Load the \"hid_logitech\" module with \"lg4ff_no_autoswitch=1\" parameter set and try again\n", - entry->real_name); + entry->wdata.real_name); return -EINVAL; } /* Take care of hardware limitations */ - if ((entry->real_product_id == USB_DEVICE_ID_LOGITECH_DFP_WHEEL || entry->real_product_id == USB_DEVICE_ID_LOGITECH_G25_WHEEL) && - entry->product_id > target_product_id) { - hid_info(hid, "\"%s\" cannot be switched back into \"%s\" mode\n", entry->real_name, lg4ff_alternate_modes[i].name); + if ((entry->wdata.real_product_id == USB_DEVICE_ID_LOGITECH_DFP_WHEEL || entry->wdata.real_product_id == USB_DEVICE_ID_LOGITECH_G25_WHEEL) && + entry->wdata.product_id > target_product_id) { + hid_info(hid, "\"%s\" cannot be switched back into \"%s\" mode\n", entry->wdata.real_name, lg4ff_alternate_modes[i].name); return -EINVAL; } - s = lg4ff_get_mode_switch_command(entry->real_product_id, target_product_id); + s = lg4ff_get_mode_switch_command(entry->wdata.real_product_id, target_product_id); if (!s) { hid_err(hid, "Invalid target product ID %X\n", target_product_id); return -EINVAL; @@ -741,7 +837,7 @@ static ssize_t lg4ff_range_show(struct device *dev, struct device_attribute *att return 0; } - count = scnprintf(buf, PAGE_SIZE, "%u\n", entry->range); + count = scnprintf(buf, PAGE_SIZE, "%u\n", entry->wdata.range); return count; } @@ -768,13 +864,13 @@ static ssize_t lg4ff_range_store(struct device *dev, struct device_attribute *at } if (range == 0) - range = entry->max_range; + range = entry->wdata.max_range; /* Check if the wheel supports range setting * and that the range is within limits for the wheel */ - if (entry->set_range != NULL && range >= entry->min_range && range <= entry->max_range) { - entry->set_range(hid, range); - entry->range = range; + if (entry->wdata.set_range && range >= entry->wdata.min_range && range <= entry->wdata.max_range) { + entry->wdata.set_range(hid, range); + entry->wdata.range = range; } return count; @@ -800,12 +896,12 @@ static ssize_t lg4ff_real_id_show(struct device *dev, struct device_attribute *a return 0; } - if (!entry->real_tag || !entry->real_name) { + if (!entry->wdata.real_tag || !entry->wdata.real_name) { hid_err(hid, "NULL pointer to string\n"); return 0; } - count = scnprintf(buf, PAGE_SIZE, "%s: %s\n", entry->real_tag, entry->real_name); + count = scnprintf(buf, PAGE_SIZE, "%s: %s\n", entry->wdata.real_tag, entry->wdata.real_name); return count; } @@ -821,8 +917,24 @@ static void lg4ff_set_leds(struct hid_device *hid, u8 leds) { struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; struct hid_report *report = list_entry(report_list->next, struct hid_report, list); + struct lg_drv_data *drv_data = hid_get_drvdata(hid); + struct lg4ff_device_entry *entry; s32 *value = report->field[0]->value; + unsigned long flags; + + drv_data = hid_get_drvdata(hid); + if (!drv_data) { + hid_err(hid, "Private driver data not found!\n"); + return; + } + entry = drv_data->device_props; + if (!entry) { + hid_err(hid, "Device properties not found!\n"); + return; + } + + spin_lock_irqsave(&entry->report_lock, flags); value[0] = 0xf8; value[1] = 0x12; value[2] = leds; @@ -831,6 +943,7 @@ static void lg4ff_set_leds(struct hid_device *hid, u8 leds) value[5] = 0x00; value[6] = 0x00; hid_hw_request(hid, report, HID_REQ_SET_REPORT); + spin_unlock_irqrestore(&entry->report_lock, flags); } static void lg4ff_led_set_brightness(struct led_classdev *led_cdev, @@ -855,15 +968,15 @@ static void lg4ff_led_set_brightness(struct led_classdev *led_cdev, } for (i = 0; i < 5; i++) { - if (led_cdev != entry->led[i]) + if (led_cdev != entry->wdata.led[i]) continue; - state = (entry->led_state >> i) & 1; + state = (entry->wdata.led_state >> i) & 1; if (value == LED_OFF && state) { - entry->led_state &= ~(1 << i); - lg4ff_set_leds(hid, entry->led_state); + entry->wdata.led_state &= ~(1 << i); + lg4ff_set_leds(hid, entry->wdata.led_state); } else if (value != LED_OFF && !state) { - entry->led_state |= 1 << i; - lg4ff_set_leds(hid, entry->led_state); + entry->wdata.led_state |= 1 << i; + lg4ff_set_leds(hid, entry->wdata.led_state); } break; } @@ -890,8 +1003,8 @@ static enum led_brightness lg4ff_led_get_brightness(struct led_classdev *led_cde } for (i = 0; i < 5; i++) - if (led_cdev == entry->led[i]) { - value = (entry->led_state >> i) & 1; + if (led_cdev == entry->wdata.led[i]) { + value = (entry->wdata.led_state >> i) & 1; break; } @@ -1002,6 +1115,17 @@ int lg4ff_init(struct hid_device *hid) if (!hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 7)) return -1; + drv_data = hid_get_drvdata(hid); + if (!drv_data) { + hid_err(hid, "Cannot add device, private driver data not allocated\n"); + return -1; + } + entry = kzalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) + return -ENOMEM; + spin_lock_init(&entry->report_lock); + drv_data->device_props = entry; + /* Check if a multimode wheel has been connected and * handle it appropriately */ mmode_ret = lg4ff_handle_multimode_wheel(hid, &real_product_id, bcdDevice); @@ -1011,6 +1135,11 @@ int lg4ff_init(struct hid_device *hid) */ if (mmode_ret == LG4FF_MMODE_SWITCHED) return 0; + else if (mmode_ret < 0) { + hid_err(hid, "Unable to switch device mode during initialization, errno %d\n", mmode_ret); + error = mmode_ret; + goto err_init; + } /* Check what wheel has been connected */ for (i = 0; i < ARRAY_SIZE(lg4ff_devices); i++) { @@ -1022,7 +1151,8 @@ int lg4ff_init(struct hid_device *hid) if (i == ARRAY_SIZE(lg4ff_devices)) { hid_err(hid, "This device is flagged to be handled by the lg4ff module but this module does not know how to handle it. Please report this as a bug to LKML, Simon Wood or Michal Maly \n"); - return -1; + error = -1; + goto err_init; } if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) { @@ -1033,7 +1163,8 @@ int lg4ff_init(struct hid_device *hid) if (mmode_idx == ARRAY_SIZE(lg4ff_multimode_wheels)) { hid_err(hid, "Device product ID %X is not listed as a multimode wheel", real_product_id); - return -1; + error = -1; + goto err_init; } } @@ -1044,33 +1175,18 @@ int lg4ff_init(struct hid_device *hid) error = input_ff_create_memless(dev, NULL, lg4ff_play); if (error) - return error; - - /* Get private driver data */ - drv_data = hid_get_drvdata(hid); - if (!drv_data) { - hid_err(hid, "Cannot add device, private driver data not allocated\n"); - return -1; - } + goto err_init; - /* Initialize device properties */ - entry = kzalloc(sizeof(struct lg4ff_device_entry), GFP_KERNEL); - if (!entry) { - hid_err(hid, "Cannot add device, insufficient memory to allocate device properties.\n"); - return -ENOMEM; - } - drv_data->device_props = entry; - - entry->product_id = lg4ff_devices[i].product_id; - entry->real_product_id = real_product_id; - entry->min_range = lg4ff_devices[i].min_range; - entry->max_range = lg4ff_devices[i].max_range; - entry->set_range = lg4ff_devices[i].set_range; + entry->wdata.product_id = lg4ff_devices[i].product_id; + entry->wdata.real_product_id = real_product_id; + entry->wdata.min_range = lg4ff_devices[i].min_range; + entry->wdata.max_range = lg4ff_devices[i].max_range; + entry->wdata.set_range = lg4ff_devices[i].set_range; if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) { BUG_ON(mmode_idx == -1); - entry->alternate_modes = lg4ff_multimode_wheels[mmode_idx].alternate_modes; - entry->real_tag = lg4ff_multimode_wheels[mmode_idx].real_tag; - entry->real_name = lg4ff_multimode_wheels[mmode_idx].real_name; + entry->wdata.alternate_modes = lg4ff_multimode_wheels[mmode_idx].alternate_modes; + entry->wdata.real_tag = lg4ff_multimode_wheels[mmode_idx].real_tag; + entry->wdata.real_name = lg4ff_multimode_wheels[mmode_idx].real_name; } /* Check if autocentering is available and @@ -1089,27 +1205,32 @@ int lg4ff_init(struct hid_device *hid) /* Create sysfs interface */ error = device_create_file(&hid->dev, &dev_attr_range); if (error) - return error; + goto err_init; if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) { error = device_create_file(&hid->dev, &dev_attr_real_id); - if (error) - return error; + if (error) { + device_remove_file(&hid->dev, &dev_attr_range); + goto err_init; + } error = device_create_file(&hid->dev, &dev_attr_alternate_modes); - if (error) - return error; + if (error) { + device_remove_file(&hid->dev, &dev_attr_real_id); + device_remove_file(&hid->dev, &dev_attr_range); + goto err_init; + } } dbg_hid("sysfs interface created\n"); /* Set the maximum range to start with */ - entry->range = entry->max_range; - if (entry->set_range != NULL) - entry->set_range(hid, entry->range); + entry->wdata.range = entry->wdata.max_range; + if (entry->wdata.set_range) + entry->wdata.set_range(hid, entry->wdata.range); #ifdef CONFIG_LEDS_CLASS /* register led subsystem - G27 only */ - entry->led_state = 0; + entry->wdata.led_state = 0; for (j = 0; j < 5; j++) - entry->led[j] = NULL; + entry->wdata.led[j] = NULL; if (lg4ff_devices[i].product_id == USB_DEVICE_ID_LOGITECH_G27_WHEEL) { struct led_classdev *led; @@ -1124,7 +1245,7 @@ int lg4ff_init(struct hid_device *hid) led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL); if (!led) { hid_err(hid, "can't allocate memory for LED %d\n", j); - goto err; + goto err_leds; } name = (void *)(&led[1]); @@ -1135,16 +1256,16 @@ int lg4ff_init(struct hid_device *hid) led->brightness_get = lg4ff_led_get_brightness; led->brightness_set = lg4ff_led_set_brightness; - entry->led[j] = led; + entry->wdata.led[j] = led; error = led_classdev_register(&hid->dev, led); if (error) { hid_err(hid, "failed to register LED %d. Aborting.\n", j); -err: +err_leds: /* Deregister LEDs (if any) */ for (j = 0; j < 5; j++) { - led = entry->led[j]; - entry->led[j] = NULL; + led = entry->wdata.led[j]; + entry->wdata.led[j] = NULL; if (!led) continue; led_classdev_unregister(led); @@ -1158,6 +1279,11 @@ out: #endif hid_info(hid, "Force feedback support for Logitech Gaming Wheels\n"); return 0; + +err_init: + drv_data->device_props = NULL; + kfree(entry); + return error; } int lg4ff_deinit(struct hid_device *hid) @@ -1174,14 +1300,19 @@ int lg4ff_deinit(struct hid_device *hid) if (!entry) goto out; /* Nothing more to do */ - device_remove_file(&hid->dev, &dev_attr_range); - /* Multimode devices will have at least the "MODE_NATIVE" bit set */ - if (entry->alternate_modes) { + if (entry->wdata.alternate_modes) { device_remove_file(&hid->dev, &dev_attr_real_id); device_remove_file(&hid->dev, &dev_attr_alternate_modes); } + device_remove_file(&hid->dev, &dev_attr_range); + + /* Make sure that nothing will try to touch the device while it is + * being removed */ + hid_hw_stop(hid); + drv_data->device_props = NULL; + #ifdef CONFIG_LEDS_CLASS { int j; @@ -1190,8 +1321,8 @@ int lg4ff_deinit(struct hid_device *hid) /* Deregister LEDs (if any) */ for (j = 0; j < 5; j++) { - led = entry->led[j]; - entry->led[j] = NULL; + led = entry->wdata.led[j]; + entry->wdata.led[j] = NULL; if (!led) continue; led_classdev_unregister(led); @@ -1200,9 +1331,7 @@ int lg4ff_deinit(struct hid_device *hid) } #endif - /* Deallocate memory */ kfree(entry); - out: dbg_hid("Device successfully unregistered\n"); return 0;