On Sun, Sep 6, 2015 at 11:18 PM, Clément Vuchener
<[email protected]> wrote:
> Hello,
>
> I am trying to write a driver that uses LED class devices using works for 
> setting the LED brightness but I am not sure of how to unregister the devices.
>
> I have been using code like this:
>         led_classdev_unregister(&drvdata->backlight.cdev);
>         cancel_work_sync(&drvdata->backlight.work);
> trying with both flush_work or cancel_work_sync as I have seen it in other 
> drivers.
>
> Using flush_work, the kernel oops in my work function when I unplug the 
> device. cancel_work_sync seems to fix that, but I am not sure it will work 
> every time. I would like to understand what happens and if I am doing 
> something wrong, to be sure it will not break in some different setup.

Can you post the backtrace?

>
> Another problem I have with this unregistering is that the LED brightness is 
> set to zero when unregistering when the hardware is supposed to remember the 
> last settings. Thus the LED state is reset when detaching and reattaching the 
> driver.
>
> Below is the full code from my driver, if you need it:
>
> /*
>  * HID driver for Corsair devices
>  *
>  * Supported devices:
>  *  - Vengeance K90 Keyboard
>  *
>  * Copyright (c) 2015 Clement Vuchener
>  */
>
> /*
>  * This program is free software; you can redistribute it and/or modify it
>  * under the terms of the GNU General Public License as published by the Free
>  * Software Foundation; either version 2 of the License, or (at your option)
>  * any later version.
>  */
>
> #include <linux/hid.h>
> #include <linux/module.h>
> #include <linux/usb.h>
> #include <linux/leds.h>
>
> #include "hid-ids.h"
>
> struct k90_led {
>         struct led_classdev cdev;
>         int brightness;
>         struct work_struct work;
> };
>
> struct k90_drvdata {
>         int current_profile;
>         int macro_mode;
>         int meta_locked;
>         struct k90_led backlight;
>         struct k90_led record_led;
> };
>
> #define K90_GKEY_COUNT  18
>
> static int k90_usage_to_gkey(unsigned int usage)
> {
>         /* G1 (0xd0) to G16 (0xdf) */
>         if (usage >= 0xd0 && usage <= 0xdf)
>                 return usage - 0xd0 + 1;
>         /* G17 (0xe8) to G18 (0xe9) */
>         if (usage >= 0xe8 && usage <= 0xe9)
>                 return usage - 0xe8 + 17;
>         return 0;
> }
>
> static unsigned short k90_gkey_map[K90_GKEY_COUNT] = {
>         BTN_TRIGGER_HAPPY1,
>         BTN_TRIGGER_HAPPY2,
>         BTN_TRIGGER_HAPPY3,
>         BTN_TRIGGER_HAPPY4,
>         BTN_TRIGGER_HAPPY5,
>         BTN_TRIGGER_HAPPY6,
>         BTN_TRIGGER_HAPPY7,
>         BTN_TRIGGER_HAPPY8,
>         BTN_TRIGGER_HAPPY9,
>         BTN_TRIGGER_HAPPY10,
>         BTN_TRIGGER_HAPPY11,
>         BTN_TRIGGER_HAPPY12,
>         BTN_TRIGGER_HAPPY13,
>         BTN_TRIGGER_HAPPY14,
>         BTN_TRIGGER_HAPPY15,
>         BTN_TRIGGER_HAPPY16,
>         BTN_TRIGGER_HAPPY17,
>         BTN_TRIGGER_HAPPY18,
> };
>
> module_param_array_named(gkey_codes, k90_gkey_map, ushort, NULL, S_IRUGO);
>
> #define K90_USAGE_SPECIAL_MIN 0xf0
> #define K90_USAGE_SPECIAL_MAX 0xff
>
> #define K90_USAGE_MACRO_RECORD_START 0xf6
> #define K90_USAGE_MACRO_RECORD_STOP 0xf7
>
> #define K90_USAGE_PROFILE 0xf1
> #define K90_USAGE_M1 0xf1
> #define K90_USAGE_M2 0xf2
> #define K90_USAGE_M3 0xf3
> #define K90_USAGE_PROFILE_MAX 0xf3
>
> #define K90_USAGE_META_OFF 0xf4
> #define K90_USAGE_META_ON  0xf5
>
> #define K90_USAGE_LIGHT 0xfa
> #define K90_USAGE_LIGHT_OFF 0xfa
> #define K90_USAGE_LIGHT_DIM 0xfb
> #define K90_USAGE_LIGHT_MEDIUM 0xfc
> #define K90_USAGE_LIGHT_BRIGHT 0xfd
> #define K90_USAGE_LIGHT_MAX 0xfd
>
> /* USB control protocol */
>
> #define K90_REQUEST_BRIGHTNESS 49
> #define K90_REQUEST_MACRO_MODE 2
> #define K90_REQUEST_STATUS 4
> #define K90_REQUEST_GET_MODE 5
> #define K90_REQUEST_PROFILE 20
>
> #define K90_MACRO_MODE_SW 0x0030
> #define K90_MACRO_MODE_HW 0x0001
>
> #define K90_MACRO_LED_ON  0x0020
> #define K90_MACRO_LED_OFF 0x0040
>
> /*
>  * LED class devices
>  */
>
> #define K90_BACKLIGHT_LED_SUFFIX ":blue:backlight"
> #define K90_RECORD_LED_SUFFIX ":red:record"
>
> static enum led_brightness k90_brightness_get(struct led_classdev *led_cdev)
> {
>         struct k90_led *led = container_of(led_cdev, struct k90_led, cdev);
>
>         return led->brightness;
> }
>
> static void k90_brightness_set(struct led_classdev *led_cdev,
>                                enum led_brightness brightness)
> {
>         struct k90_led *led = container_of(led_cdev, struct k90_led, cdev);
>
>         led->brightness = brightness;
>         schedule_work(&led->work);
> }
>
> static void k90_backlight_work(struct work_struct *work)
> {
>         int ret;
>         struct k90_led *led = container_of(work, struct k90_led, work);
>         struct device *dev = led->cdev.dev->parent;
>         struct usb_interface *usbif = to_usb_interface(dev->parent);
>         struct usb_device *usbdev = interface_to_usbdev(usbif);
>
>         ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0),
>                               K90_REQUEST_BRIGHTNESS,
>                               USB_DIR_OUT | USB_TYPE_VENDOR |
>                               USB_RECIP_DEVICE, led->brightness, 0,
>                               NULL, 0, USB_CTRL_SET_TIMEOUT);
>         if (ret != 0)
>                 dev_warn(dev, "Failed to set backlight brightness (error: 
> %d).\n",
>                          ret);
> }
>
> static void k90_record_led_work(struct work_struct *work)
> {
>         int ret;
>         struct k90_led *led = container_of(work, struct k90_led, work);
>         struct device *dev = led->cdev.dev->parent;
>         struct usb_interface *usbif = to_usb_interface(dev->parent);
>         struct usb_device *usbdev = interface_to_usbdev(usbif);
>         int value;
>
>         if (led->brightness > 0)
>                 value = K90_MACRO_LED_ON;
>         else
>                 value = K90_MACRO_LED_OFF;
>
>         ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0),
>                               K90_REQUEST_MACRO_MODE,
>                               USB_DIR_OUT | USB_TYPE_VENDOR |
>                               USB_RECIP_DEVICE, value, 0, NULL, 0,
>                               USB_CTRL_SET_TIMEOUT);
>         if (ret != 0)
>                 dev_warn(dev, "Failed to set record LED state (error: %d).\n",
>                          ret);
> }
>
> /*
>  * Keyboard attributes
>  */
>
> static ssize_t k90_show_macro_mode(struct device *dev,
>                                    struct device_attribute *attr, char *buf)
> {
>         struct k90_drvdata *drvdata = dev_get_drvdata(dev);
>
>         return snprintf(buf, PAGE_SIZE, "%s\n",
>                         (drvdata->macro_mode ? "HW" : "SW"));
> }
>
> static ssize_t k90_store_macro_mode(struct device *dev,
>                                     struct device_attribute *attr,
>                                     const char *buf, size_t count)
> {
>         int ret;
>         struct usb_interface *usbif = to_usb_interface(dev->parent);
>         struct usb_device *usbdev = interface_to_usbdev(usbif);
>         struct k90_drvdata *drvdata = dev_get_drvdata(dev);
>         __u16 value;
>
>         if (strncmp(buf, "SW", 2) == 0)
>                 value = K90_MACRO_MODE_SW;
>         else if (strncmp(buf, "HW", 2) == 0)
>                 value = K90_MACRO_MODE_HW;
>         else
>                 return -EINVAL;
>
>         ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0),
>                               K90_REQUEST_MACRO_MODE,
>                               USB_DIR_OUT | USB_TYPE_VENDOR |
>                               USB_RECIP_DEVICE, value, 0, NULL, 0,
>                               USB_CTRL_SET_TIMEOUT);
>         if (ret != 0)
>                 return ret;
>
>         drvdata->macro_mode = (value == K90_MACRO_MODE_HW);
>
>         return count;
> }
>
> static ssize_t k90_show_current_profile(struct device *dev,
>                                         struct device_attribute *attr,
>                                         char *buf)
> {
>         struct k90_drvdata *drvdata = dev_get_drvdata(dev);
>
>         return snprintf(buf, PAGE_SIZE, "%d\n", drvdata->current_profile);
> }
>
> static ssize_t k90_store_current_profile(struct device *dev,
>                                          struct device_attribute *attr,
>                                          const char *buf, size_t count)
> {
>         int ret;
>         struct usb_interface *usbif = to_usb_interface(dev->parent);
>         struct usb_device *usbdev = interface_to_usbdev(usbif);
>         struct k90_drvdata *drvdata = dev_get_drvdata(dev);
>         int profile;
>
>         if (kstrtoint(buf, 10, &profile))
>                 return -EINVAL;
>         if (profile < 1 || profile > 3)
>                 return -EINVAL;
>
>         ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0),
>                               K90_REQUEST_PROFILE,
>                               USB_DIR_OUT | USB_TYPE_VENDOR |
>                               USB_RECIP_DEVICE, profile, 0, NULL, 0,
>                               USB_CTRL_SET_TIMEOUT);
>         if (ret != 0)
>                 return ret;
>
>         drvdata->current_profile = profile;
>
>         return count;
> }
>
> static DEVICE_ATTR(macro_mode, 0644, k90_show_macro_mode, 
> k90_store_macro_mode);
> static DEVICE_ATTR(current_profile, 0644, k90_show_current_profile,
>                    k90_store_current_profile);
>
> static struct attribute *k90_attrs[] = {
>         &dev_attr_macro_mode.attr,
>         &dev_attr_current_profile.attr,
>         NULL
> };
>
> static const struct attribute_group k90_attr_group = {
>         .attrs = k90_attrs,
> };
>
> /*
>  * Driver functions
>  */
>
> static int k90_init_special_functions(struct hid_device *dev)
> {
>         int ret;
>         struct usb_interface *usbif = to_usb_interface(dev->dev.parent);
>         struct usb_device *usbdev = interface_to_usbdev(usbif);
>         char data[8];
>         struct k90_drvdata *drvdata =
>             kzalloc(sizeof(struct k90_drvdata), GFP_KERNEL);
>         size_t name_sz;
>         char *name;
>         struct k90_led *led;
>
>         if (!drvdata) {
>                 ret = -ENOMEM;
>                 goto fail_drvdata;
>         }
>         hid_set_drvdata(dev, drvdata);
>
>         /* Get current status */
>         ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0),
>                               K90_REQUEST_STATUS,
>                               USB_DIR_IN | USB_TYPE_VENDOR |
>                               USB_RECIP_DEVICE, 0, 0, data, 8,
>                               USB_CTRL_SET_TIMEOUT);
>         if (ret < 0) {
>                 hid_warn(dev, "Failed to get K90 initial state (error %d).\n",
>                          ret);
>                 drvdata->backlight.brightness = 0;
>                 drvdata->current_profile = 1;
>         } else {
>                 drvdata->backlight.brightness = data[4];
>                 drvdata->current_profile = data[7];
>         }
>         /* Get current mode */
>         ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0),
>                               K90_REQUEST_GET_MODE,
>                               USB_DIR_IN | USB_TYPE_VENDOR |
>                               USB_RECIP_DEVICE, 0, 0, data, 2,
>                               USB_CTRL_SET_TIMEOUT);
>         if (ret < 0)
>                 hid_warn(dev, "Failed to get K90 initial mode (error %d).\n",
>                          ret);
>         else {
>                 switch (data[0]) {
>                 case K90_MACRO_MODE_HW:
>                         drvdata->macro_mode = 1;
>                         break;
>                 case K90_MACRO_MODE_SW:
>                         drvdata->macro_mode = 0;
>                         break;
>                 default:
>                         hid_warn(dev, "K90 in unknown mode: %02x.\n",
>                                  data[0]);
>                 }
>         }
>
>         /* Init LED device for backlight */
>         name_sz =
>             strlen(dev_name(&dev->dev)) + sizeof(K90_BACKLIGHT_LED_SUFFIX);
>         name = devm_kzalloc(&dev->dev, name_sz, GFP_KERNEL);
>         if (!name) {
>                 ret = -ENOMEM;
>                 goto fail_backlight;
>         }
>         snprintf(name, name_sz, "%s" K90_BACKLIGHT_LED_SUFFIX,
>                  dev_name(&dev->dev));
>         led = &drvdata->backlight;
>         led->cdev.name = name;
>         led->cdev.max_brightness = 3;
>         led->cdev.brightness_set = k90_brightness_set;
>         led->cdev.brightness_get = k90_brightness_get;
>         INIT_WORK(&led->work, k90_backlight_work);
>         ret = led_classdev_register(&dev->dev, &led->cdev);
>         if (ret != 0)
>                 goto fail_backlight;
>
>         /* Init LED device for record LED */
>         name_sz = strlen(dev_name(&dev->dev)) + sizeof(K90_RECORD_LED_SUFFIX);
>         name = devm_kzalloc(&dev->dev, name_sz, GFP_KERNEL);
>         if (!name) {
>                 ret = -ENOMEM;
>                 goto fail_record_led;
>         }
>         snprintf(name, name_sz, "%s" K90_RECORD_LED_SUFFIX,
>                  dev_name(&dev->dev));
>         led = &drvdata->record_led;
>         led->cdev.name = name;
>         led->cdev.max_brightness = 1;
>         led->cdev.brightness_set = k90_brightness_set;
>         led->cdev.brightness_get = k90_brightness_get;
>         INIT_WORK(&led->work, k90_record_led_work);
>         ret = led_classdev_register(&dev->dev, &led->cdev);
>         if (ret != 0)
>                 goto fail_record_led;
>
>         /* Init attributes */
>         ret = sysfs_create_group(&dev->dev.kobj, &k90_attr_group);
>         if (ret != 0)
>                 goto fail_sysfs;
>
>         return 0;
>
> fail_sysfs:
>         led_classdev_unregister(&drvdata->record_led.cdev);
>         cancel_work_sync(&drvdata->record_led.work);
> fail_record_led:
>         led_classdev_unregister(&drvdata->backlight.cdev);
>         cancel_work_sync(&drvdata->backlight.work);
> fail_backlight:
>         kfree(drvdata);
> fail_drvdata:
>         hid_set_drvdata(dev, NULL);
>         return ret;
> }
>
> static void k90_cleanup_special_functions(struct hid_device *dev)
> {
>         struct k90_drvdata *drvdata = hid_get_drvdata(dev);
>
>         if (drvdata) {
>                 sysfs_remove_group(&dev->dev.kobj, &k90_attr_group);
>                 led_classdev_unregister(&drvdata->record_led.cdev);
>                 led_classdev_unregister(&drvdata->backlight.cdev);

flush_work/cancel_work before doing unregistering?

>                 cancel_work_sync(&drvdata->record_led.work);
>                 cancel_work_sync(&drvdata->backlight.work);
>                 kfree(drvdata);
>         }
> }
>
> static int k90_probe(struct hid_device *dev, const struct hid_device_id *id)
> {
>         int ret;
>         struct usb_interface *usbif = to_usb_interface(dev->dev.parent);
>
>         ret = hid_parse(dev);
>         if (ret != 0) {
>                 hid_err(dev, "parse failed\n");
>                 return ret;
>         }
>         ret = hid_hw_start(dev, HID_CONNECT_DEFAULT);
>         if (ret != 0) {
>                 hid_err(dev, "hw start failed\n");
>                 return ret;
>         }
>
>         if (usbif->cur_altsetting->desc.bInterfaceNumber == 0) {
>                 ret = k90_init_special_functions(dev);
>                 if (ret != 0)
>                         hid_warn(dev, "Failed to initialize K90 special 
> functions.\n");
>         } else
>                 hid_set_drvdata(dev, NULL);
>
>         return 0;
> }
>
> static void k90_remove(struct hid_device *dev)
> {
>         struct usb_interface *usbif = to_usb_interface(dev->dev.parent);
>
>         if (usbif->cur_altsetting->desc.bInterfaceNumber == 0)
>                 k90_cleanup_special_functions(dev);
>
>         hid_hw_stop(dev);
> }
>
> static int k90_event(struct hid_device *dev, struct hid_field *field,
>                      struct hid_usage *usage, __s32 value)
> {
>         struct k90_drvdata *drvdata = hid_get_drvdata(dev);
>
>         if (!drvdata)
>                 return 0;
>
>         switch (usage->hid & HID_USAGE) {
>         case K90_USAGE_MACRO_RECORD_START:
>                 drvdata->record_led.brightness = 1;
>                 break;
>         case K90_USAGE_MACRO_RECORD_STOP:
>                 drvdata->record_led.brightness = 0;
>                 break;
>         case K90_USAGE_M1:
>         case K90_USAGE_M2:
>         case K90_USAGE_M3:
>                 drvdata->current_profile =
>                     (usage->hid & HID_USAGE) - K90_USAGE_PROFILE + 1;
>                 break;
>         case K90_USAGE_META_OFF:
>                 drvdata->meta_locked = 0;
>                 break;
>         case K90_USAGE_META_ON:
>                 drvdata->meta_locked = 1;
>                 break;
>         case K90_USAGE_LIGHT_OFF:
>         case K90_USAGE_LIGHT_DIM:
>         case K90_USAGE_LIGHT_MEDIUM:
>         case K90_USAGE_LIGHT_BRIGHT:
>                 drvdata->backlight.brightness = (usage->hid & HID_USAGE) -
>                     K90_USAGE_LIGHT;
>                 break;
>         default:
>                 break;
>         }
>
>         return 0;
> }
>
> static int k90_input_mapping(struct hid_device *dev, struct hid_input *input,
>                              struct hid_field *field, struct hid_usage *usage,
>                              unsigned long **bit, int *max)
> {
>         int gkey;
>
>         gkey = k90_usage_to_gkey(usage->hid & HID_USAGE);
>         if (gkey != 0) {
>                 hid_map_usage_clear(input, usage, bit, max, EV_KEY,
>                                     k90_gkey_map[gkey - 1]);
>                 return 1;
>         }
>         if ((usage->hid & HID_USAGE) >= K90_USAGE_SPECIAL_MIN &&
>             (usage->hid & HID_USAGE) <= K90_USAGE_SPECIAL_MAX)
>                 return -1;
>
>         return 0;
> }
>
> static const struct hid_device_id k90_devices[] = {
>         { HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_K90) },
>         {}
> };
>
> MODULE_DEVICE_TABLE(hid, k90_devices);
>
> static struct hid_driver k90_driver = {
>         .name = "k90",
>         .id_table = k90_devices,
>         .probe = k90_probe,
>         .event = k90_event,
>         .remove = k90_remove,
>         .input_mapping = k90_input_mapping,
> };
>
> static int __init k90_init(void)
> {
>         return hid_register_driver(&k90_driver);
> }
>
> static void k90_exit(void)
> {
>         hid_unregister_driver(&k90_driver);
> }
>
> module_init(k90_init);
> module_exit(k90_exit);
>
> MODULE_LICENSE("GPL");
> MODULE_AUTHOR("Clement Vuchener");
> MODULE_DESCRIPTION("HID driver for Corsair Vengeance K90 Keyboard");
>
> _______________________________________________
> Kernelnewbies mailing list
> [email protected]
> http://lists.kernelnewbies.org/mailman/listinfo/kernelnewbies



-- 
        ---P.K.S
--
To unsubscribe from this list: send the line "unsubscribe linux-leds" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to