gpio: Remove VLA from gpiolib
PATCH v6 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.
---
v6: Introduce a config option for setting the fast path GPIOs because there are too many combinations to make the arch default workable. I went with a default of 512 in the Kconfig.
Also to other points: I don't think the warning should be triggerable from userspace, it should only happen on probe. I also think only memsetting half the array is more likely to be error prone. We can change it if there is significant overhead.
--- drivers/gpio/Kconfig | 10 +++++ drivers/gpio/gpiolib.c | 76 +++++++++++++++++++++++++++-------- drivers/gpio/gpiolib.h | 2 +- include/linux/gpio/consumer.h | 10 +++-- 4 files changed, 76 insertions(+), 22 deletions(-)
drivers/gpio/Kconfig +10 -0
@@ -22,6 +22,16 @@ menuconfig GPIOLIB if GPIOLIB +config GPIOLIB_FASTPATH_LIMIT + int "Maximum number of GPIOs for fast path" + default 512 + help + This adjusts the point at which certain APIs will switch from + using a statically allocated fixed size buffer to a dynamically + allocated buffer. This is a trade-off in stackspace vs. speed. + You shouldn't need to change this unless you really need to + optimize one of those two. + 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 289 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.
Reviewed-and-tested-by Lukas Wunner <[email protected]>Signed-off-by Lukas Wunner <[email protected]>Signed-off-by Laura Abbott <[email protected]>---
v6: Introduce a config option for setting the fast path GPIOs because there are too many combinations to make the arch default workable. I went with a default of 512 in theKconfig.Also to other points: I don't think the warning should be triggerable from userspace, it should only happen on probe. I also think only memsetting half the array is more likely to be error prone. We can change it if there is significant overhead.
--- drivers/gpio/Kconfig | 10 +++++ drivers/gpio/gpiolib.c | 76 +++++++++++++++++++++++++++-------- drivers/gpio/gpiolib.h | 2 +- include/linux/gpio/consumer.h | 10 +++-- 4 files changed, 76 insertions(+), 22 deletions(-)drivers/gpio/Kconfig +10
-0@@ -22,6 +22,16 @@ menuconfig GPIOLIB if GPIOLIB +config GPIOLIB_FASTPATH_LIMIT + int "Maximum number of GPIOs for fast path" + default 512 + help + This adjusts the point at which certain APIs will switch from + using a statically allocated fixed size buffer to a dynamically + allocated buffer. This is a trade-off in stackspace vs. speed. + You shouldn't need to change this unless you really need to + optimize one of those two. + 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. @@ -399,12 +404,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 ; }@@ -1192,6 +1196,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 %d is greater than fast path cnt %d\n" , + chip->ngpio,FASTPATH_NGPIO ); + gdev->label =kstrdup_const (chip->label ?:"unknown" ,GFP_KERNEL );if (!gdev->label) { status = -ENOMEM ;@@ -2662,16 +2670,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 )) { +memset (fastpath,0 ,sizeof (fastpath)); + mask = fastpath; + bits = fastpath +BITS_TO_LONGS (FASTPATH_NGPIO ); + }else { + mask =kcalloc (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); + } +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);@@ -2682,8 +2702,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];@@ -2695,6 +2718,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 ; }@@ -2878,7 +2904,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)@@ -2887,14 +2913,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 )) { +memset (fastpath,0 ,sizeof (fastpath)); + mask = fastpath; + bits = fastpath +BITS_TO_LONGS (FASTPATH_NGPIO ); + }else { + mask =kcalloc (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); + } +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);@@ -2925,7 +2963,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 ; }/** @@ -3000,13 +3042,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);@@ -3326,14 +3368,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 +4
-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,
include/linux/gpio/consumer.h static inline void gpiod_set_raw_value_cansleep(struct gpio_desc *desc, · line 436
Quoted 10 lines
include/linux/gpio/consumer.h +2
-0+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)
G'day Laura,
Looks good to me.
Signature · 2 lines
Regards Phil Reid
Hi Laura,
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 3 lines
Reviewed-and-tested-by Lukas Wunner <[email protected]>Signed-off-by Lukas Wunner <[email protected]>Signed-off-by Laura Abbott <[email protected]>
Thanks for your patch!
Quoted 4 lines
Also to other points: I don't think the warning should be triggerable from userspace, it should only happen on probe. I also think only memsetting half the array is more likely to be error prone. We can change it if there is significant overhead.
With the default of 512, that's a memset of 128 bytes. Not so insignificant on embedded 32 bit.
drivers/gpio/Kconfig menuconfig GPIOLIB · line 27
Quoted 9 lines
drivers/gpio/Kconfig +3
-0@@ -22,6 +22,16 @@ menuconfig GPIOLIB if GPIOLIB +config GPIOLIB_FASTPATH_LIMIT + int "Maximum number of GPIOs for fast path" + default 512
I think you need a range here. Else someone will pick a too large value, causing stack overflow.
512 (128 bytes for each recursion level) sounds like a safe maximum to me.
drivers/gpio/Kconfig menuconfig GPIOLIB · line 30
Quoted 3 lines
drivers/gpio/Kconfig +3
-0+ help + This adjusts the point at which certain APIs will switch from + using a statically allocated fixed size buffer to a dynamically
The fast path doesn't use a statically allocated buffer (it cannot, due to recursion), but a buffer on the stack. I think you need to make that very clear in the help text, as this has the potential of causing random crashes.
drivers/gpio/gpiolib.c int gpiochip_add_data_with_key(struct gpio_chip *chip, void *data, · line 1200
Quoted 13 lines
drivers/gpio/Kconfig +3
-0+ allocated buffer. This is a trade-off in stackspace vs. speed. + You shouldn't need to change this unless you really need to + optimize one of those two.drivers/gpio/gpiolib.c +2
-0@@ -1192,6 +1196,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 %d is greater than fast path cnt %d\n",
%u (twice)
Quoted 17 lines
drivers/gpio/gpiolib.c +6
-2+ chip->ngpio, FASTPATH_NGPIO); + gdev->label =kstrdup_const (chip->label ?:"unknown" ,GFP_KERNEL );if (!gdev->label) { status = -ENOMEM ;@@ -2662,16 +2670,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 )) { +memset (fastpath,0 ,sizeof (fastpath));
drivers/gpio/gpiolib.c int gpiod_get_array_value_complex(bool raw, bool can_sleep, · line 2688
Quoted 10 lines
drivers/gpio/gpiolib.c +10
-0+ mask = fastpath; + bits = fastpath +BITS_TO_LONGS (FASTPATH_NGPIO ); + }else { + mask =kcalloc (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); + }
The assignment to bits could be made common, and moved out of the if/else.
Likewise for the memset, which means you would usually clear a single word again, instead of 128 bytes (or more).
Gr{oetje,eeting}s,
Geert
Signature · 5 lines
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- [email protected] In personal conversations with technical people, I call myself a hacker. But when I'm talking to journalists I just say "programmer" or something like that. -- Linus Torvalds