Re: [PATCH v7] usb:serial:pl2303: add GPIOs interface on PL2303

From: Johan Hovold
Date: Tue Aug 12 2014 - 10:47:52 EST


On Sat, Aug 09, 2014 at 01:28:28PM +0800, Wang YanQing wrote:
> PL2303 USB Serial devices always has GPIOs,

Always? Are you sure? It's probably better to write "might have" as they
are unlikely to be accessible even if the pins exist.

> this patch add
> generic gpio support interfaces for pl2303 USB Serial devices
> in pl2303_type_data and pl2303_serial_private, then use them

No need to mention these implementation details.

> to add GPIOs support for one type of them, PL2303HXA.
>
> Known issue:
> If gpios are in use(export to userspace through sysfs interface, etc),
> then call pl2303_release(unplug usb-serial convertor, modprobe -r, etc),
> will cause trouble, so we need to make sure there is no gpio user before
> call pl2303_release.

This is a real problem that we need to address. gpiolib isn't really
able to handle devices that just disappear. In fact, it's API claims that
we must not call gpiochip_remove with requested gpios and this is
exactly what you might do in pl2303hx_gpio_release below.

As I mentioned earlier, this crashes the kernel when a new gpiochip is
later added (the gpiochip data structures are likely corrupted and we
get a NULL pointer deref in gpiochip_find_base).

Linus, any thoughts on this?

> Signed-off-by: Wang YanQing <udknight@xxxxxxxxx>
> ---
> Changes
> v6-v7:
> 1: add generic gpio support interfaces for pl2303 USB Serial devices
> in pl2303_type_data and pl2303_serial_private suggested by Andreas Mohr.
> 2: drop different label names for different pl2303 instance suggested by Johan Hovold.
> 3: fix missing lock corrected by Johan Hovold.
> 4: use prefix pl2303hx_gpio instead pl2303_gpio, pl2303_gpio is over generic for current code,
> and now we move gpio_startup|gpio_release into type-specified data structure, so pl2303hx_gpio
> is a better prefix.

I'm not sure that was such a good idea. More below.

> 5: make words in Kconfig a little more useful and clarified.
> 6: many misc code quality enhancement suggested by Johan Hovold.
>
> v5-v6:
> 1: fix typo error in Kconfig reported by Andreas Mohr
> 2: add const qulification to gpio_dir_mask and gpio_value_mask suggested by Andreas Mohr
>
> v4-v5:
> 1: fix gpio_chip.lable been overwrited by template_chip.
> 2: use idr to manage minor instead of crude monotonous atomic increment.
>
> v3-v4:
> 1: fix missing static for gpio_dir_mask and gpio_value_mask
> 2: reduce unneeded compile macro defined suggested by gnomes@xxxxxxxxxxxxxxxxxxx
> 3: use true instead of 1 corrected by Linus Walleij
> 4: ignore return value of gpiochip_remove suggested by Linus Walleij
> 5: fix multi gpio_chips registered by pl2303 can't be distinguished in kernel space.
>
> v2-v3:
> 1: fix errors and warnings reported by Daniele Forsi checked with checkpatch.pl
> 2: fix missing GPIOLIB dependence in Kconfig
> 3: fix pl2303_gpio_get can't work
>
> v1-v2:
> 1:drop gpio-pl2303.c and relation stuff
> 2:hang gpio stuff off of pl2303.c
>
> drivers/usb/serial/Kconfig | 10 +++
> drivers/usb/serial/pl2303.c | 202 ++++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 212 insertions(+)
>
> diff --git a/drivers/usb/serial/Kconfig b/drivers/usb/serial/Kconfig
> index 3ce5c74..008ec57 100644
> --- a/drivers/usb/serial/Kconfig
> +++ b/drivers/usb/serial/Kconfig
> @@ -516,6 +516,16 @@ config USB_SERIAL_PL2303
> To compile this driver as a module, choose M here: the
> module will be called pl2303.
>
> +config USB_SERIAL_PL2303_GPIO
> + bool "USB Prolific 2303 Single Port GPIOs support"
> + depends on USB_SERIAL_PL2303 && GPIOLIB
> + ---help---
> + Say Y here if you want to use GPIOs on PL2303 USB Serial single port
> + adapter from Prolific.
> +
> + It supports 2 dedicated GPIOs on PL2303HXA only currently.
> + If unsure, say N.
> +
> config USB_SERIAL_OTI6858
> tristate "USB Ours Technology Inc. OTi-6858 USB To RS232 Bridge Controller"
> help
> diff --git a/drivers/usb/serial/pl2303.c b/drivers/usb/serial/pl2303.c
> index b3d5a35..edbe634 100644
> --- a/drivers/usb/serial/pl2303.c
> +++ b/drivers/usb/serial/pl2303.c
> @@ -28,6 +28,8 @@
> #include <linux/usb.h>
> #include <linux/usb/serial.h>
> #include <asm/unaligned.h>
> +#include <linux/gpio.h>
> +#include <linux/mutex.h>
> #include "pl2303.h"
>
>
> @@ -131,6 +133,8 @@ MODULE_DEVICE_TABLE(usb, id_table);
> #define UART_OVERRUN_ERROR 0x40
> #define UART_CTS 0x80
>
> +#define PL2303HX_REG_GPIO_CONTROL 0x01
> +#define PL2303HX_REG_GPIO_STATE 0x81
>
> enum pl2303_type {
> TYPE_01, /* Type 0 and 1 (difference unknown) */
> @@ -141,11 +145,15 @@ enum pl2303_type {
> struct pl2303_type_data {
> speed_t max_baud_rate;
> unsigned long quirks;
> + u16 ngpio;

u8 should be enough.

> + int (*gpio_startup)(struct usb_serial *serial);
> + void (*gpio_release)(struct usb_serial *serial);

This isn't the right place for this abstraction. Most of the setup code
would be common for any device type with GPIOs.

Just keep the generic gpio_startup and release from v6, and verify that
ngpio > 0. Any further abstraction should only be added once we know how
the other types handles GPIOs.

> };
>
> struct pl2303_serial_private {
> const struct pl2303_type_data *type;
> unsigned long quirks;
> + void *gpio;

No void pointers, please.

> };
>
> struct pl2303_private {
> @@ -156,11 +164,24 @@ struct pl2303_private {
> u8 line_settings[7];
> };
>
> +#ifdef CONFIG_USB_SERIAL_PL2303_GPIO
> +static int pl2303hx_gpio_startup(struct usb_serial *serial);
> +static void pl2303hx_gpio_release(struct usb_serial *serial);
> +#else
> +static inline int pl2303hx_gpio_startup(struct usb_serial *serial) { return 0; }
> +static inline void pl2303hx_gpio_release(struct usb_serial *serial) {}
> +#endif

So rename these back to pl2303_gpio_startup/release again.

Drop the hx suffix you just added from all functions until we have a
need to differentiate the types.

> +
> static const struct pl2303_type_data pl2303_type_data[TYPE_COUNT] = {
> [TYPE_01] = {
> .max_baud_rate = 1228800,
> .quirks = PL2303_QUIRK_LEGACY,
> },
> + [TYPE_HX] = {
> + .ngpio = 2,
> + .gpio_startup = pl2303hx_gpio_startup,
> + .gpio_release = pl2303hx_gpio_release,
> + }
> };
>
> static int pl2303_vendor_read(struct usb_serial *serial, u16 value,
> @@ -213,6 +234,176 @@ static int pl2303_probe(struct usb_serial *serial,
> return 0;
> }
>
> +#ifdef CONFIG_USB_SERIAL_PL2303_GPIO
> +struct pl2303hx_gpio_data {
> + /*
> + * 0..3: unknown (zero)
> + * 4: gp0 output enable (1: gp0 pin is output, 0: gp0 pin is input)
> + * 5: gp1 output enable (1: gp1 pin is output, 0: gp1 pin is input)
> + * 6: gp0 pin value
> + * 7: gp1 pin value
> + */
> + u8 index;

Perhaps index isn't the most descriptive name of this shadow register.
Why not call it ctrl_reg, or similar.

> + struct mutex lock;
> + struct usb_serial *serial;
> + struct gpio_chip gpio_chip;
> +};
> +
> +static inline struct pl2303hx_gpio_data *to_pl2303hx_gpio_data(struct gpio_chip *chip)
> +{
> + return container_of(chip, struct pl2303hx_gpio_data, gpio_chip);
> +}
> +
> +static u8 pl2303hx_gpio_dir_mask(unsigned offset)
> +{
> + switch(offset)
> + {
> + case 0:
> + return 0x10;
> + case 1:
> + return 0x20;
> + default:
> + break;
> + };
> + return 0;
> +}
> +
> +static u8 pl2303hx_gpio_value_mask(unsigned offset)
> +{
> + switch(offset)
> + {
> + case 0:
> + return 0x40;
> + case 1:
> + return 0x80;
> + default:
> + break;
> + };
> + return 0;
> +}

Use bitshifts rather than switch statements. Perhaps you don't even need
the helpers if you add temporary mask variables.

> +
> +static int pl2303hx_gpio_direction_in(struct gpio_chip *chip, unsigned offset)
> +{
> + struct pl2303hx_gpio_data *gpio = to_pl2303hx_gpio_data(chip);
> + int ret;
> +
> + mutex_lock(&gpio->lock);
> + gpio->index &= ~pl2303hx_gpio_dir_mask(offset);
> + ret = pl2303_vendor_write(gpio->serial, PL2303HX_REG_GPIO_CONTROL, gpio->index);

This line is too long.

Apparently you did not run checkpatch on v7 because there are a whole
bunch of warning and errors now.

> + mutex_unlock(&gpio->lock);
> +
> + return ret;
> +}
> +
> +static int pl2303hx_gpio_direction_out(struct gpio_chip *chip,
> + unsigned offset, int value)
> +{
> + struct pl2303hx_gpio_data *gpio = to_pl2303hx_gpio_data(chip);
> + int ret;
> +
> + mutex_lock(&gpio->lock);
> + gpio->index |= pl2303hx_gpio_dir_mask(offset);
> + if (value)
> + gpio->index |= pl2303hx_gpio_value_mask(offset);
> + else
> + gpio->index &= ~pl2303hx_gpio_value_mask(offset);
> +
> + ret = pl2303_vendor_write(gpio->serial, PL2303HX_REG_GPIO_CONTROL, gpio->index);
> + mutex_unlock(&gpio->lock);
> +
> + return ret;
> +}
> +
> +static void pl2303hx_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
> +{
> + struct pl2303hx_gpio_data *gpio = to_pl2303hx_gpio_data(chip);
> +
> + mutex_lock(&gpio->lock);
> + if (value)
> + gpio->index |= pl2303hx_gpio_value_mask(offset);
> + else
> + gpio->index &= ~pl2303hx_gpio_value_mask(offset);
> +
> + pl2303_vendor_write(gpio->serial, PL2303HX_REG_GPIO_CONTROL, gpio->index);
> + mutex_unlock(&gpio->lock);
> +}
> +
> +static int pl2303hx_gpio_get(struct gpio_chip *chip, unsigned offset)
> +{
> + struct pl2303hx_gpio_data *gpio = to_pl2303hx_gpio_data(chip);
> + unsigned char *buf;
> + int value = 0;

No need to initialise.

> +
> + buf = kmalloc(1, GFP_KERNEL);
> + if (!buf) {
> + return -ENOMEM;
> + }

No braces.

> +
> + mutex_lock(&gpio->lock);
> + if (pl2303_vendor_read(gpio->serial, PL2303HX_REG_GPIO_STATE, buf)) {

Please use a temporary variable for the returned status (e.g. int res)
rather than calling vendor read from within the if construct.

> + mutex_unlock(&gpio->lock);
> + return -EIO;
> + }
> +
> + value = buf[0] & pl2303hx_gpio_value_mask(offset);
> + mutex_unlock(&gpio->lock);
> + kfree(buf);
> +
> + return value;
> +}
> +
> +static int pl2303hx_gpio_startup(struct usb_serial *serial)
> +{
> + struct pl2303_serial_private *spriv = usb_get_serial_data(serial);
> + struct pl2303hx_gpio_data *gpio;
> + int ret;
> +
> + gpio = kzalloc(sizeof(struct pl2303hx_gpio_data), GFP_KERNEL);
> + if (!gpio) {
> + dev_err(&serial->interface->dev,
> + "Failed to allocate pl2303_gpio_data\n");

No need to print an error message on failed allocations as this has
already been taken care of.

> + return -ENOMEM;
> + }
> +
> + gpio->index = 0x00;
> + gpio->serial = serial;
> + mutex_init(&gpio->lock);
> +
> + gpio->gpio_chip.label = "pl2303";
> + gpio->gpio_chip.owner = THIS_MODULE;
> + gpio->gpio_chip.direction_input = pl2303hx_gpio_direction_in;
> + gpio->gpio_chip.get = pl2303hx_gpio_get;
> + gpio->gpio_chip.direction_output = pl2303hx_gpio_direction_out;
> + gpio->gpio_chip.set = pl2303hx_gpio_set;
> + gpio->gpio_chip.can_sleep = true;
^
Runaway whitespace.

> + gpio->gpio_chip.ngpio = spriv->type->ngpio;
> + gpio->gpio_chip.base = -1;
> + gpio->gpio_chip.dev = &serial->interface->dev;
> +
> + /* initialize GPIOs, input mode as default */
> + pl2303_vendor_write(gpio->serial, PL2303HX_REG_GPIO_CONTROL, gpio->index);
> +
> + ret = gpiochip_add(&gpio->gpio_chip);
> + if (ret < 0) {
> + dev_err(&serial->interface->dev,
> + "Could not register gpiochip, %d\n", ret);
> + kfree(gpio);
> + return ret;
> + }
> + spriv->gpio = gpio;
> + return 0;
> +}
> +
> +static void pl2303hx_gpio_release(struct usb_serial *serial)
> +{
> + struct pl2303_serial_private *spriv = usb_get_serial_data(serial);
> + struct pl2303hx_gpio_data *gpio = (struct pl2303hx_gpio_data *)spriv->gpio;
> +
> + gpiochip_remove(&gpio->gpio_chip);

So this will corrupt the gpiolib structures if there are requested /
exported gpios.

> + kfree(gpio);
> +}
> +#endif
> +
> static int pl2303_startup(struct usb_serial *serial)
> {
> struct pl2303_serial_private *spriv;
> @@ -262,6 +453,15 @@ static int pl2303_startup(struct usb_serial *serial)
>
> kfree(buf);
>
> + if (spriv->type->ngpio > 0) {
> + int ret;

Please declare all variables at the start of the function.

> +
> + ret = spriv->type->gpio_startup(serial);

So call pl2303_gpio_startup() here and check ngpio in that function.

> + if (ret) {
> + kfree(spriv);
> + return ret;
> + }
> + }
> return 0;
> }
>
> @@ -269,6 +469,8 @@ static void pl2303_release(struct usb_serial *serial)
> {
> struct pl2303_serial_private *spriv = usb_get_serial_data(serial);
>
> + if (spriv->type->ngpio > 0)
> + spriv->type->gpio_release(serial);

Call pl2303_gpio_release() and check ngpio there.

> kfree(spriv);
> }

Thanks,
Johan
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/