gpio: Remove VLA from gpiolib
PATCH v9 1/1 gpio: Remove VLA from gpiolib
The new challenge is to remove VLAs from the kernel (see https://lkml.org/lkml/2018/3/7/621) to eventually turn on -Wvla.
Using a kmalloc array is the easy way to fix this but kmalloc is still more expensive than stack allocation. Introduce a fast path with a fixed size stack array to cover most chip with gpios below some fixed amount. The slow path dynamically allocates an array to cover those chips with a large number of gpios.
---
v9: switch to bitmap_zero
--- drivers/gpio/Kconfig | 12 ++++++ drivers/gpio/gpiolib.c | 76 +++++++++++++++++++++++++++-------- drivers/gpio/gpiolib.h | 2 +- include/linux/gpio/consumer.h | 10 +++-- 4 files changed, 78 insertions(+), 22 deletions(-)
drivers/gpio/Kconfig +12 -0
@@ -22,6 +22,18 @@ menuconfig GPIOLIB if GPIOLIB +config GPIOLIB_FASTPATH_LIMIT + int "Maximum number of GPIOs for fast path" + range 32 512 + default 512 + help + This adjusts the point at which certain APIs will switch from + using a stack allocated buffer to a dynamically allocated buffer. + + You shouldn't need to change this unless you really need to + optimize either stack space or performance. Change this carefully + since setting an incorrect value could cause stack corruption. + config OF_GPIO def_bool y depends on OF
drivers/gpio/gpiolib.c +59 -17
168 lines not on this page · raw message
drivers/gpio/gpiolib.h +1 -1
@@ -188,7 +188,7 @@ int gpiod_get_array_value_complex(bool raw, bool can_sleep,unsigned int array_size, struct gpio_desc **desc_array, int *value_array); -void gpiod_set_array_value_complex (bool raw,bool can_sleep, +int gpiod_set_array_value_complex (bool raw,bool can_sleep,unsigned int array_size,struct gpio_desc **desc_array,int *value_array);
include/linux/gpio/consumer.h +6 -4
@@ -116,7 +116,7 @@ int gpiod_get_raw_array_value(unsigned int array_size,struct gpio_desc **desc_array, int *value_array);void gpiod_set_raw_value (struct gpio_desc *desc,int value); -void gpiod_set_raw_array_value (unsigned int array_size, +int gpiod_set_raw_array_value (unsigned int array_size,struct gpio_desc **desc_array,int *value_array);
@@ -134,7 +134,7 @@ int gpiod_get_raw_array_value_cansleep(unsigned int array_size,struct gpio_desc **desc_array, int *value_array);void gpiod_set_raw_value_cansleep (struct gpio_desc *desc,int value); -void gpiod_set_raw_array_value_cansleep (unsigned int array_size, +int gpiod_set_raw_array_value_cansleep (unsigned int array_size,struct gpio_desc **desc_array,int *value_array);
@@ -369,12 +369,13 @@ static inline void gpiod_set_raw_value(struct gpio_desc *desc, int value)/* GPIO can never have been requested */ WARN_ON (1 ); } -static inline void gpiod_set_raw_array_value (unsigned int array_size, +static inline int gpiod_set_raw_array_value (unsigned int array_size,struct gpio_desc **desc_array,int *value_array) {/* GPIO can never have been requested */ WARN_ON (1 ); +return 0 ; }static inline int gpiod_get_value_cansleep (const struct gpio_desc *desc)
@@ -423,12 +424,13 @@ static inline void gpiod_set_raw_value_cansleep(struct gpio_desc *desc,/* GPIO can never have been requested */ WARN_ON (1 ); } -static inline void gpiod_set_raw_array_value_cansleep (unsigned int array_size, +static inline int gpiod_set_raw_array_value_cansleep (unsigned int array_size,struct gpio_desc **desc_array,int *value_array) {/* GPIO can never have been requested */ WARN_ON (1 ); +return 0 ; }static inline int gpiod_set_debounce (struct gpio_desc *desc,unsigned debounce)
Signature · 1 line
2.17.0
Quoted 10 lines · Laura Abbott
The new challenge is to remove VLAs from the kernel (see https://lkml.org/lkml/2018/3/7/621) to eventually turn on -Wvla.
Using a kmalloc array is the easy way to fix this but kmalloc is still more expensive than stack allocation. Introduce a fast path with a fixed size stack array to cover most chip with gpios below some fixed amount. The slow path dynamically allocates an array to cover those chips with a large number of gpios.
LGTM
Quoted 288 lines
Reviewed-by Geert Uytterhoeven <[email protected]>Reviewed-by Phil Reid <[email protected]>Reviewed-and-tested-by Lukas Wunner <[email protected]>Signed-off-by Lukas Wunner <[email protected]>Signed-off-by Laura Abbott <[email protected]>---
v9: switch tobitmap_zero--- drivers/gpio/Kconfig | 12 ++++++ drivers/gpio/gpiolib.c | 76 +++++++++++++++++++++++++++-------- drivers/gpio/gpiolib.h | 2 +- include/linux/gpio/consumer.h | 10 +++-- 4 files changed, 78 insertions(+), 22 deletions(-)drivers/gpio/Kconfig +12
-0@@ -22,6 +22,18 @@ menuconfig GPIOLIB if GPIOLIB +config GPIOLIB_FASTPATH_LIMIT + int "Maximum number of GPIOs for fast path" + range 32 512 + default 512 + help + This adjusts the point at which certain APIs will switch from + using a stack allocated buffer to a dynamically allocated buffer. + + You shouldn't need to change this unless you really need to + optimize either stack space or performance. Change this carefully + since setting an incorrect value could cause stack corruption. + config OF_GPIO def_bool y depends on OFdrivers/gpio/gpiolib.c +59
-17@@ -61,6 +61,11 @@ static struct bus_type gpio_bus_type = { .name = "gpio", }; +/* +* Number of GPIOs to use for the fast path in set array +*/ +#define FASTPATH_NGPIO CONFIG_GPIOLIB_FASTPATH_LIMIT +/* gpio_lock prevents conflicts during gpio_desc[] table updates. * While any GPIO is requested, its gpio_chip is not removable; * each GPIO's "requested" flag serves as a lock and refcount. @@ -450,12 +455,11 @@ static long linehandle_ioctl(struct file *filep, unsigned int cmd, vals[i] = !!ghd.values[i];/* Reuse the array setting function */ -gpiod_set_array_value_complex (false, +return gpiod_set_array_value_complex (false, true, lh->numdescs, lh->descs, vals); -return 0 ; }return -EINVAL ; }@@ -1244,6 +1248,10 @@ int gpiochip_add_data_with_key(struct gpio_chip *chip, void *data, goto err_free_descs; } +if (chip->ngpio >FASTPATH_NGPIO ) +chip_warn (chip,"line cnt %u is greater than fast path cnt %u\n" , + chip->ngpio,FASTPATH_NGPIO ); + gdev->label =kstrdup_const (chip->label ?:"unknown" ,GFP_KERNEL );if (!gdev->label) { status = -ENOMEM ;@@ -2719,16 +2727,28 @@ int gpiod_get_array_value_complex(bool raw, bool can_sleep,while (i < array_size) {struct gpio_chip *chip = desc_array[i]->gdev->chip; -unsigned long mask[BITS_TO_LONGS (chip->ngpio)]; -unsigned long bits[BITS_TO_LONGS (chip->ngpio)]; +unsigned long fastpath[2 *BITS_TO_LONGS (FASTPATH_NGPIO )]; +unsigned long *mask, *bits;int first, j, ret; +if (likely (chip->ngpio <=FASTPATH_NGPIO )) { + mask = fastpath; + }else { + mask =kmalloc_array (2 *BITS_TO_LONGS (chip->ngpio), +sizeof (*mask), + can_sleep ?GFP_KERNEL :GFP_ATOMIC ); +if (!mask) +return -ENOMEM ; + } + + bits = mask +BITS_TO_LONGS (chip->ngpio); +bitmap_zero (mask, chip->ngpio); +if (!can_sleep)WARN_ON (chip->can_sleep);/* collect all inputs belonging to the same chip */ first = i; -memset (mask,0 ,sizeof (mask)); do { const struct gpio_desc *desc = desc_array[i]; int hwgpio = gpio_chip_hwgpio(desc);@@ -2739,8 +2759,11 @@ int gpiod_get_array_value_complex(bool raw, bool can_sleep, (desc_array[i]->gdev->chip == chip)); ret =gpio_chip_get_multiple (chip, mask, bits); -if (ret) +if (ret) { +if (mask != fastpath) +kfree (mask);return ret; + }for (j = first; j < i; j++) {const struct gpio_desc *desc = desc_array[j];@@ -2752,6 +2775,9 @@ int gpiod_get_array_value_complex(bool raw, bool can_sleep, value_array[j] = value;trace_gpio_value (desc_to_gpio (desc),1 , value); } + +if (mask != fastpath) +kfree (mask); }return 0 ; }@@ -2935,7 +2961,7 @@ static void gpio_chip_set_multiple(struct gpio_chip *chip, } } -void gpiod_set_array_value_complex (bool raw,bool can_sleep, +int gpiod_set_array_value_complex (bool raw,bool can_sleep,unsigned int array_size,struct gpio_desc **desc_array,int *value_array)@@ -2944,14 +2970,26 @@ void gpiod_set_array_value_complex(bool raw, bool can_sleep,while (i < array_size) {struct gpio_chip *chip = desc_array[i]->gdev->chip; -unsigned long mask[BITS_TO_LONGS (chip->ngpio)]; -unsigned long bits[BITS_TO_LONGS (chip->ngpio)]; +unsigned long fastpath[2 *BITS_TO_LONGS (FASTPATH_NGPIO )]; +unsigned long *mask, *bits;int count =0 ; +if (likely (chip->ngpio <=FASTPATH_NGPIO )) { + mask = fastpath; + }else { + mask =kmalloc_array (2 *BITS_TO_LONGS (chip->ngpio), +sizeof (*mask), + can_sleep ?GFP_KERNEL :GFP_ATOMIC ); +if (!mask) +return -ENOMEM ; + } + + bits = mask +BITS_TO_LONGS (chip->ngpio); +bitmap_zero (mask, chip->ngpio); +if (!can_sleep)WARN_ON (chip->can_sleep); -memset (mask,0 ,sizeof (mask)); do { struct gpio_desc *desc = desc_array[i]; int hwgpio = gpio_chip_hwgpio(desc);@@ -2982,7 +3020,11 @@ void gpiod_set_array_value_complex(bool raw, bool can_sleep,/* push collected bits to outputs */ if (count !=0 )gpio_chip_set_multiple (chip, mask, bits); + +if (mask != fastpath) +kfree (mask); } +return 0 ; }/** @@ -3057,13 +3099,13 @@ EXPORT_SYMBOL_GPL(gpiod_set_value);* This function should be called from contexts where we cannot sleep, and will * complain if the GPIO chip functions potentially sleep. */ -void gpiod_set_raw_array_value (unsigned int array_size, +int gpiod_set_raw_array_value (unsigned int array_size,struct gpio_desc **desc_array,int *value_array) {if (!desc_array) -return ; -gpiod_set_array_value_complex (true, false, array_size, desc_array, - value_array); +return -EINVAL ; +return gpiod_set_array_value_complex (true, false, array_size, + desc_array, value_array); }EXPORT_SYMBOL_GPL (gpiod_set_raw_array_value);@@ -3383,14 +3425,14 @@ EXPORT_SYMBOL_GPL(gpiod_set_value_cansleep);* * This function is to be called from contexts that can sleep. */ -void gpiod_set_raw_array_value_cansleep (unsigned int array_size, +int gpiod_set_raw_array_value_cansleep (unsigned int array_size,struct gpio_desc **desc_array,int *value_array) {might_sleep_if (extra_checks);if (!desc_array) -return ; -gpiod_set_array_value_complex (true, true, array_size, desc_array, +return -EINVAL ; +return gpiod_set_array_value_complex (true, true, array_size, desc_array, value_array); }EXPORT_SYMBOL_GPL (gpiod_set_raw_array_value_cansleep);drivers/gpio/gpiolib.h +1
-1@@ -188,7 +188,7 @@ int gpiod_get_array_value_complex(bool raw, bool can_sleep,unsigned int array_size, struct gpio_desc **desc_array, int *value_array); -void gpiod_set_array_value_complex (bool raw,bool can_sleep, +int gpiod_set_array_value_complex (bool raw,bool can_sleep,unsigned int array_size,struct gpio_desc **desc_array,int *value_array);include/linux/gpio/consumer.h +6
-5@@ -116,7 +116,7 @@ int gpiod_get_raw_array_value(unsigned int array_size,struct gpio_desc **desc_array, int *value_array);void gpiod_set_raw_value (struct gpio_desc *desc,int value); -void gpiod_set_raw_array_value (unsigned int array_size, +int gpiod_set_raw_array_value (unsigned int array_size,struct gpio_desc **desc_array,int *value_array);@@ -134,7 +134,7 @@ int gpiod_get_raw_array_value_cansleep(unsigned int array_size,struct gpio_desc **desc_array, int *value_array);void gpiod_set_raw_value_cansleep (struct gpio_desc *desc,int value); -void gpiod_set_raw_array_value_cansleep (unsigned int array_size, +int gpiod_set_raw_array_value_cansleep (unsigned int array_size,struct gpio_desc **desc_array,int *value_array);@@ -369,12 +369,13 @@ static inline void gpiod_set_raw_value(struct gpio_desc *desc, int value)/* GPIO can never have been requested */ WARN_ON (1 ); } -static inline void gpiod_set_raw_array_value (unsigned int array_size, +static inline int gpiod_set_raw_array_value (unsigned int array_size,struct gpio_desc **desc_array,int *value_array) {/* GPIO can never have been requested */ WARN_ON (1 ); +return 0 ; }static inline int gpiod_get_value_cansleep (const struct gpio_desc *desc)@@ -423,12 +424,13 @@ static inline void gpiod_set_raw_value_cansleep(struct gpio_desc *desc,/* GPIO can never have been requested */ WARN_ON (1 ); } -static inline void gpiod_set_raw_array_value_cansleep (unsigned int array_size, +static inline int gpiod_set_raw_array_value_cansleep (unsigned int array_size,struct gpio_desc **desc_array,int *value_array) {/* GPIO can never have been requested */ WARN_ON (1 ); +return 0 ; }static inline int gpiod_set_debounce (struct gpio_desc *desc,unsigned debounce) --2.17.0
Signature · 2 lines
With Best Regards, Andy Shevchenko
Quoted 10 lines · Laura Abbott
The new challenge is to remove VLAs from the kernel (see https://lkml.org/lkml/2018/3/7/621) to eventually turn on -Wvla.
Using a kmalloc array is the easy way to fix this but kmalloc is still more expensive than stack allocation. Introduce a fast path with a fixed size stack array to cover most chip with gpios below some fixed amount. The slow path dynamically allocates an array to cover those chips with a large number of gpios.
Quoted 7 lines
Reviewed-by Geert Uytterhoeven <[email protected]>Reviewed-by Phil Reid <[email protected]>Reviewed-and-tested-by Lukas Wunner <[email protected]>Signed-off-by Lukas Wunner <[email protected]>Signed-off-by Laura Abbott <[email protected]>---
v9: switch tobitmap_zero
This v9 applied with Andy's review tag on top and pushed to the servers for testing!
THANKS A LOT for your tireless work on this, it looks really really good now, and as it seems we cleaned up some other code on the way.
Yours,
Linus Walleij