]> git.proxmox.com Git - mirror_ubuntu-eoan-kernel.git/commitdiff
gpiolib: Defer gpio device setup until after gpiolib initialization
authorGuenter Roeck <linux@roeck-us.net>
Thu, 31 Mar 2016 15:11:30 +0000 (08:11 -0700)
committerLinus Walleij <linus.walleij@linaro.org>
Fri, 8 Apr 2016 08:10:09 +0000 (10:10 +0200)
Since commit ff2b13592299 ("gpio: make the gpiochip a real device"),
attempts to add a gpio chip prior to gpiolib initialization cause
the system to crash. This happens because gpio_bus_type has not been
registered yet. Defer creating gpio devices until after gpiolib has
been initialized to fix the problem.

Cc: Greg Ungerer <gerg@uclinux.org>
Cc: Alexandre Courbot <gnurou@gmail.com>
Fixes: ff2b13592299 ("gpio: make the gpiochip a real device")
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
drivers/gpio/gpiolib.c

index 5ca7c9aa4fab6cb0e7a40ccc8e620ce407f584cd..b747c76fd2b1f4f8f1e117b6ce2836525b1166fd 100644 (file)
@@ -68,6 +68,7 @@ LIST_HEAD(gpio_devices);
 static void gpiochip_free_hogs(struct gpio_chip *chip);
 static void gpiochip_irqchip_remove(struct gpio_chip *gpiochip);
 
+static bool gpiolib_initialized;
 
 static inline void desc_set_label(struct gpio_desc *d, const char *label)
 {
@@ -445,6 +446,58 @@ static void gpiodevice_release(struct device *dev)
        kfree(gdev);
 }
 
+static int gpiochip_setup_dev(struct gpio_device *gdev)
+{
+       int status;
+
+       cdev_init(&gdev->chrdev, &gpio_fileops);
+       gdev->chrdev.owner = THIS_MODULE;
+       gdev->chrdev.kobj.parent = &gdev->dev.kobj;
+       gdev->dev.devt = MKDEV(MAJOR(gpio_devt), gdev->id);
+       status = cdev_add(&gdev->chrdev, gdev->dev.devt, 1);
+       if (status < 0)
+               chip_warn(gdev->chip, "failed to add char device %d:%d\n",
+                         MAJOR(gpio_devt), gdev->id);
+       else
+               chip_dbg(gdev->chip, "added GPIO chardev (%d:%d)\n",
+                        MAJOR(gpio_devt), gdev->id);
+       status = device_add(&gdev->dev);
+       if (status)
+               goto err_remove_chardev;
+
+       status = gpiochip_sysfs_register(gdev);
+       if (status)
+               goto err_remove_device;
+
+       /* From this point, the .release() function cleans up gpio_device */
+       gdev->dev.release = gpiodevice_release;
+       get_device(&gdev->dev);
+       pr_debug("%s: registered GPIOs %d to %d on device: %s (%s)\n",
+                __func__, gdev->base, gdev->base + gdev->ngpio - 1,
+                dev_name(&gdev->dev), gdev->chip->label ? : "generic");
+
+       return 0;
+
+err_remove_device:
+       device_del(&gdev->dev);
+err_remove_chardev:
+       cdev_del(&gdev->chrdev);
+       return status;
+}
+
+static void gpiochip_setup_devs(void)
+{
+       struct gpio_device *gdev;
+       int err;
+
+       list_for_each_entry(gdev, &gpio_devices, list) {
+               err = gpiochip_setup_dev(gdev);
+               if (err)
+                       pr_err("%s: Failed to initialize gpio device (%d)\n",
+                              dev_name(&gdev->dev), err);
+       }
+}
+
 /**
  * gpiochip_add_data() - register a gpio_chip
  * @chip: the chip to register, with chip->base initialized
@@ -459,6 +512,9 @@ static void gpiodevice_release(struct device *dev)
  * the gpio framework's arch_initcall().  Otherwise sysfs initialization
  * for GPIOs will fail rudely.
  *
+ * gpiochip_add_data() must only be called after gpiolib initialization,
+ * ie after core_initcall().
+ *
  * If chip->base is negative, this requests dynamic assignment of
  * a range of valid GPIOs.
  */
@@ -515,7 +571,7 @@ int gpiochip_add_data(struct gpio_chip *chip, void *data)
        if (chip->ngpio == 0) {
                chip_err(chip, "tried to insert a GPIO chip with zero lines\n");
                status = -EINVAL;
-               goto err_free_gdev;
+               goto err_free_descs;
        }
 
        if (chip->label)
@@ -597,39 +653,16 @@ int gpiochip_add_data(struct gpio_chip *chip, void *data)
         * we get a device node entry in sysfs under
         * /sys/bus/gpio/devices/gpiochipN/dev that can be used for
         * coldplug of device nodes and other udev business.
+        * We can do this only if gpiolib has been initialized.
+        * Otherwise, defer until later.
         */
-       cdev_init(&gdev->chrdev, &gpio_fileops);
-       gdev->chrdev.owner = THIS_MODULE;
-       gdev->chrdev.kobj.parent = &gdev->dev.kobj;
-       gdev->dev.devt = MKDEV(MAJOR(gpio_devt), gdev->id);
-       status = cdev_add(&gdev->chrdev, gdev->dev.devt, 1);
-       if (status < 0)
-               chip_warn(chip, "failed to add char device %d:%d\n",
-                         MAJOR(gpio_devt), gdev->id);
-       else
-               chip_dbg(chip, "added GPIO chardev (%d:%d)\n",
-                        MAJOR(gpio_devt), gdev->id);
-       status = device_add(&gdev->dev);
-       if (status)
-               goto err_remove_chardev;
-
-       status = gpiochip_sysfs_register(gdev);
-       if (status)
-               goto err_remove_device;
-
-       /* From this point, the .release() function cleans up gpio_device */
-       gdev->dev.release = gpiodevice_release;
-       get_device(&gdev->dev);
-       pr_debug("%s: registered GPIOs %d to %d on device: %s (%s)\n",
-                __func__, gdev->base, gdev->base + gdev->ngpio - 1,
-                dev_name(&gdev->dev), chip->label ? : "generic");
-
+       if (gpiolib_initialized) {
+               status = gpiochip_setup_dev(gdev);
+               if (status)
+                       goto err_remove_chip;
+       }
        return 0;
 
-err_remove_device:
-       device_del(&gdev->dev);
-err_remove_chardev:
-       cdev_del(&gdev->chrdev);
 err_remove_chip:
        acpi_gpiochip_remove(chip);
        gpiochip_free_hogs(chip);
@@ -2842,6 +2875,9 @@ static int __init gpiolib_dev_init(void)
        if (ret < 0) {
                pr_err("gpiolib: failed to allocate char dev region\n");
                bus_unregister(&gpio_bus_type);
+       } else {
+               gpiolib_initialized = true;
+               gpiochip_setup_devs();
        }
        return ret;
 }